Popup positioning improvements (#2135)
* Rename elementRect to sourceRect * Add getRects function to TextSourceElement and TextSourceRange * Add jsdocs * Remove unnecessary valid parameter * Remove default parameter * Make optionsContext optional * Remove unnecessary checks * Update sourceRect to use left/right rather than x/y * Update the return type of Popup*.getFrameRect * Rename some unrelated rect vars for disambiguation * Disambiguate between Popup.Rect and Popup.ValidRect * Move sourceRect destructuring * Pass multiple source rects * Simplify * Change Rect to use right/bottom rather than width/height * Update how popup offset is applied * Simplify frame offset * Remove _applyFrameOffset * Use right/bottom rather than width/height * Simplify some positioning settings * Update parameter names for clarity * Fix typos * Refactor data type for _getPosition* functions * Support using multiple source rects * Combine _getPosition functions * Refactor * Expose after dataset value * Consistently use this's property * Add jsdoc
This commit is contained in:
parent
96f5a06c80
commit
63d37c872b
@ -565,7 +565,7 @@ class Frontend {
|
|||||||
textSource = this._textScanner.getCurrentTextSource();
|
textSource = this._textScanner.getCurrentTextSource();
|
||||||
if (textSource === null) { return; }
|
if (textSource === null) { return; }
|
||||||
}
|
}
|
||||||
this._showPopupContent(textSource, null);
|
this._showPopupContent(textSource, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
_showContent(textSource, focus, dictionaryEntries, type, sentence, documentTitle, optionsContext) {
|
_showContent(textSource, focus, dictionaryEntries, type, sentence, documentTitle, optionsContext) {
|
||||||
@ -601,14 +601,17 @@ class Frontend {
|
|||||||
this._showPopupContent(textSource, optionsContext, details);
|
this._showPopupContent(textSource, optionsContext, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
_showPopupContent(textSource, optionsContext, details=null) {
|
_showPopupContent(textSource, optionsContext, details) {
|
||||||
const {left, top, width, height} = textSource.getRect();
|
const sourceRects = [];
|
||||||
|
for (const {left, top, right, bottom} of textSource.getRects()) {
|
||||||
|
sourceRects.push({left, top, right, bottom});
|
||||||
|
}
|
||||||
this._lastShowPromise = (
|
this._lastShowPromise = (
|
||||||
this._popup !== null ?
|
this._popup !== null ?
|
||||||
this._popup.showContent(
|
this._popup.showContent(
|
||||||
{
|
{
|
||||||
optionsContext,
|
optionsContext,
|
||||||
elementRect: {x: left, y: top, width, height, valid: true},
|
sourceRects,
|
||||||
writingMode: textSource.getWritingMode()
|
writingMode: textSource.getWritingMode()
|
||||||
},
|
},
|
||||||
details
|
details
|
||||||
@ -661,7 +664,7 @@ class Frontend {
|
|||||||
this._popup !== null &&
|
this._popup !== null &&
|
||||||
await this._popup.isVisible()
|
await this._popup.isVisible()
|
||||||
) {
|
) {
|
||||||
this._showPopupContent(textSource, null);
|
this._showPopupContent(textSource, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +250,9 @@ class PopupFactory {
|
|||||||
|
|
||||||
async _onApiContainsPoint({id, x, y}) {
|
async _onApiContainsPoint({id, x, y}) {
|
||||||
const popup = this._getPopup(id);
|
const popup = this._getPopup(id);
|
||||||
[x, y] = this._convertPopupPointToRootPagePoint(popup, x, y);
|
const offset = this._getPopupOffset(popup);
|
||||||
|
x += offset.x;
|
||||||
|
y += offset.y;
|
||||||
return await popup.containsPoint(x, y);
|
return await popup.containsPoint(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,9 +260,13 @@ class PopupFactory {
|
|||||||
const popup = this._getPopup(id);
|
const popup = this._getPopup(id);
|
||||||
if (!this._popupCanShow(popup)) { return; }
|
if (!this._popupCanShow(popup)) { return; }
|
||||||
|
|
||||||
const {elementRect} = details;
|
const offset = this._getPopupOffset(popup);
|
||||||
if (typeof elementRect !== 'undefined') {
|
const {sourceRects} = details;
|
||||||
[elementRect.x, elementRect.y] = this._convertPopupPointToRootPagePoint(popup, elementRect.x, elementRect.y);
|
for (const sourceRect of sourceRects) {
|
||||||
|
sourceRect.left += offset.x;
|
||||||
|
sourceRect.top += offset.y;
|
||||||
|
sourceRect.right += offset.x;
|
||||||
|
sourceRect.bottom += offset.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await popup.showContent(details, displayDetails);
|
return await popup.showContent(details, displayDetails);
|
||||||
@ -311,16 +317,15 @@ class PopupFactory {
|
|||||||
return popup;
|
return popup;
|
||||||
}
|
}
|
||||||
|
|
||||||
_convertPopupPointToRootPagePoint(popup, x, y) {
|
_getPopupOffset(popup) {
|
||||||
const {parent} = popup;
|
const {parent} = popup;
|
||||||
if (parent !== null) {
|
if (parent !== null) {
|
||||||
const popupRect = parent.getFrameRect();
|
const popupRect = parent.getFrameRect();
|
||||||
if (popupRect.valid) {
|
if (popupRect.valid) {
|
||||||
x += popupRect.x;
|
return {x: popupRect.left, y: popupRect.top};
|
||||||
y += popupRect.y;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [x, y];
|
return {x: 0, y: 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
_popupCanShow(popup) {
|
_popupCanShow(popup) {
|
||||||
|
@ -40,7 +40,8 @@ class PopupProxy extends EventDispatcher {
|
|||||||
this._frameId = frameId;
|
this._frameId = frameId;
|
||||||
this._frameOffsetForwarder = frameOffsetForwarder;
|
this._frameOffsetForwarder = frameOffsetForwarder;
|
||||||
|
|
||||||
this._frameOffset = [0, 0];
|
this._frameOffsetX = 0;
|
||||||
|
this._frameOffsetY = 0;
|
||||||
this._frameOffsetPromise = null;
|
this._frameOffsetPromise = null;
|
||||||
this._frameOffsetUpdatedAt = null;
|
this._frameOffsetUpdatedAt = null;
|
||||||
this._frameOffsetExpireTimeout = 1000;
|
this._frameOffsetExpireTimeout = 1000;
|
||||||
@ -178,22 +179,28 @@ class PopupProxy extends EventDispatcher {
|
|||||||
async containsPoint(x, y) {
|
async containsPoint(x, y) {
|
||||||
if (this._frameOffsetForwarder !== null) {
|
if (this._frameOffsetForwarder !== null) {
|
||||||
await this._updateFrameOffset();
|
await this._updateFrameOffset();
|
||||||
[x, y] = this._applyFrameOffset(x, y);
|
x += this._frameOffsetX;
|
||||||
|
y += this._frameOffsetY;
|
||||||
}
|
}
|
||||||
return await this._invokeSafe('PopupFactory.containsPoint', {id: this._id, x, y}, false);
|
return await this._invokeSafe('PopupFactory.containsPoint', {id: this._id, x, y}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows and updates the positioning and content of the popup.
|
* Shows and updates the positioning and content of the popup.
|
||||||
* @param {{optionsContext: object, elementRect: {x: number, y: number, width: number, height: number}, writingMode: string}} details Settings for the outer popup.
|
* @param {Popup.ContentDetails} details Settings for the outer popup.
|
||||||
* @param {object} displayDetails The details parameter passed to `Display.setContent`; see that function for details.
|
* @param {Display.ContentDetails} displayDetails The details parameter passed to `Display.setContent`.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async showContent(details, displayDetails) {
|
async showContent(details, displayDetails) {
|
||||||
const {elementRect} = details;
|
if (this._frameOffsetForwarder !== null) {
|
||||||
if (typeof elementRect !== 'undefined' && this._frameOffsetForwarder !== null) {
|
const {sourceRects} = details;
|
||||||
await this._updateFrameOffset();
|
await this._updateFrameOffset();
|
||||||
[elementRect.x, elementRect.y] = this._applyFrameOffset(elementRect.x, elementRect.y);
|
for (const sourceRect of sourceRects) {
|
||||||
|
sourceRect.left += this._frameOffsetX;
|
||||||
|
sourceRect.top += this._frameOffsetY;
|
||||||
|
sourceRect.right += this._frameOffsetX;
|
||||||
|
sourceRect.bottom += this._frameOffsetY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return await this._invokeSafe('PopupFactory.showContent', {id: this._id, details, displayDetails});
|
return await this._invokeSafe('PopupFactory.showContent', {id: this._id, details, displayDetails});
|
||||||
}
|
}
|
||||||
@ -254,11 +261,11 @@ class PopupProxy extends EventDispatcher {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the rectangle of the DOM frame, synchronously.
|
* Gets the rectangle of the DOM frame, synchronously.
|
||||||
* @returns {{x: number, y: number, width: number, height: number, valid: boolean}} The rect.
|
* @returns {Popup.ValidRect} The rect.
|
||||||
* `valid` is `false` for `PopupProxy`, since the DOM node is hosted in a different frame.
|
* `valid` is `false` for `PopupProxy`, since the DOM node is hosted in a different frame.
|
||||||
*/
|
*/
|
||||||
getFrameRect() {
|
getFrameRect() {
|
||||||
return {x: 0, y: 0, width: 0, height: 0, valid: false};
|
return {left: 0, top: 0, right: 0, bottom: 0, valid: false};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -317,8 +324,12 @@ class PopupProxy extends EventDispatcher {
|
|||||||
this._frameOffsetPromise = this._frameOffsetForwarder.getOffset();
|
this._frameOffsetPromise = this._frameOffsetForwarder.getOffset();
|
||||||
try {
|
try {
|
||||||
const offset = await this._frameOffsetPromise;
|
const offset = await this._frameOffsetPromise;
|
||||||
this._frameOffset = offset !== null ? offset : [0, 0];
|
if (offset !== null) {
|
||||||
if (offset === null) {
|
this._frameOffsetX = offset[0];
|
||||||
|
this._frameOffsetY = offset[1];
|
||||||
|
} else {
|
||||||
|
this._frameOffsetX = 0;
|
||||||
|
this._frameOffsetY = 0;
|
||||||
this.trigger('offsetNotFound');
|
this.trigger('offsetNotFound');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -329,9 +340,4 @@ class PopupProxy extends EventDispatcher {
|
|||||||
this._frameOffsetPromise = null;
|
this._frameOffsetPromise = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_applyFrameOffset(x, y) {
|
|
||||||
const [offsetX, offsetY] = this._frameOffset;
|
|
||||||
return [x + offsetX, y + offsetY];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -168,8 +168,8 @@ class PopupWindow extends EventDispatcher {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows and updates the positioning and content of the popup.
|
* Shows and updates the positioning and content of the popup.
|
||||||
* @param {{optionsContext: object, elementRect: {x: number, y: number, width: number, height: number}, writingMode: string}} details Settings for the outer popup.
|
* @param {Popup.ContentDetails} details Settings for the outer popup.
|
||||||
* @param {object} displayDetails The details parameter passed to `Display.setContent`; see that function for details.
|
* @param {Display.ContentDetails} displayDetails The details parameter passed to `Display.setContent`.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async showContent(_details, displayDetails) {
|
async showContent(_details, displayDetails) {
|
||||||
@ -229,11 +229,11 @@ class PopupWindow extends EventDispatcher {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the rectangle of the DOM frame, synchronously.
|
* Gets the rectangle of the DOM frame, synchronously.
|
||||||
* @returns {{x: number, y: number, width: number, height: number, valid: boolean}} The rect.
|
* @returns {Popup.ValidRect} The rect.
|
||||||
* `valid` is `false` for `PopupProxy`, since the DOM node is hosted in a different frame.
|
* `valid` is `false` for `PopupProxy`, since the DOM node is hosted in a different frame.
|
||||||
*/
|
*/
|
||||||
getFrameRect() {
|
getFrameRect() {
|
||||||
return {x: 0, y: 0, width: 0, height: 0, valid: false};
|
return {left: 0, top: 0, right: 0, bottom: 0, valid: false};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,6 +26,44 @@
|
|||||||
* This class is the container which hosts the display of search results.
|
* This class is the container which hosts the display of search results.
|
||||||
*/
|
*/
|
||||||
class Popup extends EventDispatcher {
|
class Popup extends EventDispatcher {
|
||||||
|
/**
|
||||||
|
* Information about how popup content should be shown, specifically related to the outer popup frame.
|
||||||
|
* @typedef {object} ContentDetails
|
||||||
|
* @property {?object} optionsContext The options context for the content to show.
|
||||||
|
* @property {Rect[]} sourceRects The rectangles of the source content.
|
||||||
|
* @property {'horizontal-tb' | 'vertical-rl' | 'vertical-lr' | 'sideways-rl' | 'sideways-lr'} writingMode The normalized CSS writing-mode value of the source content.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A rectangle representing a DOM region, similar to DOMRect.
|
||||||
|
* @typedef {object} Rect
|
||||||
|
* @property {number} left The left position of the rectangle.
|
||||||
|
* @property {number} top The top position of the rectangle.
|
||||||
|
* @property {number} right The right position of the rectangle.
|
||||||
|
* @property {number} bottom The bottom position of the rectangle.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A rectangle representing a DOM region, similar to DOMRect but with a `valid` property.
|
||||||
|
* @typedef {object} ValidRect
|
||||||
|
* @property {number} left The left position of the rectangle.
|
||||||
|
* @property {number} top The top position of the rectangle.
|
||||||
|
* @property {number} right The right position of the rectangle.
|
||||||
|
* @property {number} bottom The bottom position of the rectangle.
|
||||||
|
* @property {boolean} valid Whether or not the rectangle is valid.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A rectangle representing a DOM region for placing the popup frame.
|
||||||
|
* @typedef {object} SizeRect
|
||||||
|
* @property {number} left The left position of the rectangle.
|
||||||
|
* @property {number} top The top position of the rectangle.
|
||||||
|
* @property {number} width The width of the rectangle.
|
||||||
|
* @property {number} height The height of the rectangle.
|
||||||
|
* @property {boolean} after Whether or not the rectangle is positioned to the right of the source rectangle.
|
||||||
|
* @property {boolean} below Whether or not the rectangle is positioned below the source rectangle.
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* Creates a new instance.
|
||||||
* @param {object} details
|
* @param {object} details
|
||||||
@ -63,8 +101,9 @@ class Popup extends EventDispatcher {
|
|||||||
this._horizontalOffset2 = 10;
|
this._horizontalOffset2 = 10;
|
||||||
this._verticalOffset2 = 0;
|
this._verticalOffset2 = 0;
|
||||||
this._verticalTextPosition = 'before';
|
this._verticalTextPosition = 'before';
|
||||||
this._horizontalTextPosition = 'below';
|
this._horizontalTextPositionBelow = true;
|
||||||
this._displayMode = 'default';
|
this._displayMode = 'default';
|
||||||
|
this._displayModeIsFullWidth = false;
|
||||||
this._scaleRelativeToVisualViewport = true;
|
this._scaleRelativeToVisualViewport = true;
|
||||||
this._useSecureFrameUrl = true;
|
this._useSecureFrameUrl = true;
|
||||||
this._useShadowDom = true;
|
this._useShadowDom = true;
|
||||||
@ -237,7 +276,7 @@ class Popup extends EventDispatcher {
|
|||||||
async containsPoint(x, y) {
|
async containsPoint(x, y) {
|
||||||
for (let popup = this; popup !== null && popup.isVisibleSync(); popup = popup.child) {
|
for (let popup = this; popup !== null && popup.isVisibleSync(); popup = popup.child) {
|
||||||
const rect = popup.getFrameRect();
|
const rect = popup.getFrameRect();
|
||||||
if (rect.valid && x >= rect.x && y >= rect.y && x < rect.x + rect.width && y < rect.y + rect.height) {
|
if (rect.valid && x >= rect.left && y >= rect.top && x < rect.right && y < rect.bottom) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -246,21 +285,19 @@ class Popup extends EventDispatcher {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows and updates the positioning and content of the popup.
|
* Shows and updates the positioning and content of the popup.
|
||||||
* @param {{optionsContext: object, elementRect: {x: number, y: number, width: number, height: number}, writingMode: string}} details Settings for the outer popup.
|
* @param {ContentDetails} details Settings for the outer popup.
|
||||||
* @param {object} displayDetails The details parameter passed to `Display.setContent`; see that function for details.
|
* @param {Display.ContentDetails} displayDetails The details parameter passed to `Display.setContent`.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async showContent(details, displayDetails) {
|
async showContent(details, displayDetails) {
|
||||||
if (!this._optionsAssigned) { throw new Error('Options not assigned'); }
|
if (!this._optionsAssigned) { throw new Error('Options not assigned'); }
|
||||||
|
|
||||||
const {optionsContext, elementRect, writingMode} = details;
|
const {optionsContext, sourceRects, writingMode} = details;
|
||||||
if (optionsContext !== null) {
|
if (optionsContext !== null) {
|
||||||
await this._setOptionsContextIfDifferent(optionsContext);
|
await this._setOptionsContextIfDifferent(optionsContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof elementRect !== 'undefined' && typeof writingMode !== 'undefined') {
|
await this._show(sourceRects, writingMode);
|
||||||
await this._show(elementRect, writingMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayDetails !== null) {
|
if (displayDetails !== null) {
|
||||||
this._invokeSafe('Display.setContent', {details: displayDetails});
|
this._invokeSafe('Display.setContent', {details: displayDetails});
|
||||||
@ -327,12 +364,12 @@ class Popup extends EventDispatcher {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the rectangle of the DOM frame, synchronously.
|
* Gets the rectangle of the DOM frame, synchronously.
|
||||||
* @returns {{x: number, y: number, width: number, height: number, valid: boolean}} The rect.
|
* @returns {ValidRect} The rect.
|
||||||
* `valid` is `false` for `PopupProxy`, since the DOM node is hosted in a different frame.
|
* `valid` is `false` for `PopupProxy`, since the DOM node is hosted in a different frame.
|
||||||
*/
|
*/
|
||||||
getFrameRect() {
|
getFrameRect() {
|
||||||
const {left, top, width, height} = this._frame.getBoundingClientRect();
|
const {left, top, right, bottom} = this._frame.getBoundingClientRect();
|
||||||
return {x: left, y: top, width, height, valid: true};
|
return {left, top, right, bottom, valid: true};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -523,42 +560,25 @@ class Popup extends EventDispatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _show(elementRect, writingMode) {
|
async _show(sourceRects, writingMode) {
|
||||||
const injected = await this._inject();
|
const injected = await this._inject();
|
||||||
if (!injected) { return; }
|
if (!injected) { return; }
|
||||||
|
|
||||||
const frame = this._frame;
|
|
||||||
const frameRect = frame.getBoundingClientRect();
|
|
||||||
|
|
||||||
const viewport = this._getViewport(this._scaleRelativeToVisualViewport);
|
const viewport = this._getViewport(this._scaleRelativeToVisualViewport);
|
||||||
const scale = this._contentScale;
|
let {left, top, width, height, after, below} = this._getPosition(sourceRects, writingMode, viewport);
|
||||||
const scaleRatio = this._frameSizeContentScale === null ? 1.0 : scale / this._frameSizeContentScale;
|
|
||||||
this._frameSizeContentScale = scale;
|
|
||||||
const getPositionArgs = [
|
|
||||||
elementRect,
|
|
||||||
Math.max(frameRect.width * scaleRatio, this._initialWidth * scale),
|
|
||||||
Math.max(frameRect.height * scaleRatio, this._initialHeight * scale),
|
|
||||||
viewport,
|
|
||||||
scale,
|
|
||||||
writingMode
|
|
||||||
];
|
|
||||||
let [x, y, width, height, below] = (
|
|
||||||
writingMode === 'horizontal-tb' || this._verticalTextPosition === 'default' ?
|
|
||||||
this._getPositionForHorizontalText(...getPositionArgs) :
|
|
||||||
this._getPositionForVerticalText(...getPositionArgs)
|
|
||||||
);
|
|
||||||
|
|
||||||
frame.dataset.popupDisplayMode = this._displayMode;
|
if (this._displayModeIsFullWidth) {
|
||||||
frame.dataset.below = `${below}`;
|
left = viewport.left;
|
||||||
|
top = below ? viewport.bottom - height : viewport.top;
|
||||||
if (this._displayMode === 'full-width') {
|
|
||||||
x = viewport.left;
|
|
||||||
y = below ? viewport.bottom - height : viewport.top;
|
|
||||||
width = viewport.right - viewport.left;
|
width = viewport.right - viewport.left;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.style.left = `${x}px`;
|
const frame = this._frame;
|
||||||
frame.style.top = `${y}px`;
|
frame.dataset.popupDisplayMode = this._displayMode;
|
||||||
|
frame.dataset.after = `${after}`;
|
||||||
|
frame.dataset.below = `${below}`;
|
||||||
|
frame.style.left = `${left}px`;
|
||||||
|
frame.style.top = `${top}px`;
|
||||||
this._setFrameSize(width, height);
|
this._setFrameSize(width, height);
|
||||||
|
|
||||||
this._setVisible(true);
|
this._setVisible(true);
|
||||||
@ -652,52 +672,97 @@ class Popup extends EventDispatcher {
|
|||||||
return fullscreenElement;
|
return fullscreenElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getPositionForHorizontalText(elementRect, width, height, viewport, offsetScale) {
|
/**
|
||||||
const preferBelow = (this._horizontalTextPosition === 'below');
|
* @param {Rect[]} sourceRects
|
||||||
const horizontalOffset = this._horizontalOffset * offsetScale;
|
* @param {string} writingMode
|
||||||
const verticalOffset = this._verticalOffset * offsetScale;
|
* @returns {SizeRect}
|
||||||
|
*/
|
||||||
|
_getPosition(sourceRects, writingMode, viewport) {
|
||||||
|
const scale = this._contentScale;
|
||||||
|
const scaleRatio = this._frameSizeContentScale === null ? 1.0 : scale / this._frameSizeContentScale;
|
||||||
|
this._frameSizeContentScale = scale;
|
||||||
|
const frameRect = this._frame.getBoundingClientRect();
|
||||||
|
const frameWidth = Math.max(frameRect.width * scaleRatio, this._initialWidth * scale);
|
||||||
|
const frameHeight = Math.max(frameRect.height * scaleRatio, this._initialHeight * scale);
|
||||||
|
|
||||||
const [x, w] = this._getConstrainedPosition(
|
const horizontal = (writingMode === 'horizontal-tb' || this._verticalTextPosition === 'default');
|
||||||
elementRect.x + elementRect.width - horizontalOffset,
|
let preferAfter;
|
||||||
elementRect.x + horizontalOffset,
|
let horizontalOffset;
|
||||||
width,
|
let verticalOffset;
|
||||||
|
if (horizontal) {
|
||||||
|
preferAfter = this._horizontalTextPositionBelow;
|
||||||
|
horizontalOffset = this._horizontalOffset;
|
||||||
|
verticalOffset = this._verticalOffset;
|
||||||
|
} else {
|
||||||
|
preferAfter = this._isVerticalTextPopupOnRight(this._verticalTextPosition, writingMode);
|
||||||
|
horizontalOffset = this._horizontalOffset2;
|
||||||
|
verticalOffset = this._verticalOffset2;
|
||||||
|
}
|
||||||
|
horizontalOffset *= scale;
|
||||||
|
verticalOffset *= scale;
|
||||||
|
|
||||||
|
let best = null;
|
||||||
|
const sourceRectsLength = sourceRects.length;
|
||||||
|
for (let i = 0, ii = (sourceRectsLength > 1 ? sourceRectsLength : 0); i <= ii; ++i) {
|
||||||
|
const sourceRect = i < sourceRectsLength ? sourceRects[i] : this._getBoundingSourceRect(sourceRects);
|
||||||
|
const result = (
|
||||||
|
horizontal ?
|
||||||
|
this._getPositionForHorizontalText(sourceRect, frameWidth, frameHeight, viewport, horizontalOffset, verticalOffset, preferAfter) :
|
||||||
|
this._getPositionForVerticalText(sourceRect, frameWidth, frameHeight, viewport, horizontalOffset, verticalOffset, preferAfter)
|
||||||
|
);
|
||||||
|
if (i < ii && this._isOverlapping(result, sourceRects, i)) { continue; }
|
||||||
|
if (best === null || result.height > best.height) {
|
||||||
|
best = result;
|
||||||
|
if (result.height >= frameHeight) { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {SizeRect}
|
||||||
|
*/
|
||||||
|
_getPositionForHorizontalText(sourceRect, frameWidth, frameHeight, viewport, horizontalOffset, verticalOffset, preferBelow) {
|
||||||
|
const [left, width, after] = this._getConstrainedPosition(
|
||||||
|
sourceRect.right - horizontalOffset,
|
||||||
|
sourceRect.left + horizontalOffset,
|
||||||
|
frameWidth,
|
||||||
viewport.left,
|
viewport.left,
|
||||||
viewport.right,
|
viewport.right,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
const [y, h, below] = this._getConstrainedPositionBinary(
|
const [top, height, below] = this._getConstrainedPositionBinary(
|
||||||
elementRect.y - verticalOffset,
|
sourceRect.top - verticalOffset,
|
||||||
elementRect.y + elementRect.height + verticalOffset,
|
sourceRect.bottom + verticalOffset,
|
||||||
height,
|
frameHeight,
|
||||||
viewport.top,
|
viewport.top,
|
||||||
viewport.bottom,
|
viewport.bottom,
|
||||||
preferBelow
|
preferBelow
|
||||||
);
|
);
|
||||||
return [x, y, w, h, below];
|
return {left, top, width, height, after, below};
|
||||||
}
|
}
|
||||||
|
|
||||||
_getPositionForVerticalText(elementRect, width, height, viewport, offsetScale, writingMode) {
|
/**
|
||||||
const preferRight = this._isVerticalTextPopupOnRight(this._verticalTextPosition, writingMode);
|
* @returns {SizeRect}
|
||||||
const horizontalOffset = this._horizontalOffset2 * offsetScale;
|
*/
|
||||||
const verticalOffset = this._verticalOffset2 * offsetScale;
|
_getPositionForVerticalText(sourceRect, frameWidth, frameHeight, viewport, horizontalOffset, verticalOffset, preferRight) {
|
||||||
|
const [left, width, after] = this._getConstrainedPositionBinary(
|
||||||
const [x, w] = this._getConstrainedPositionBinary(
|
sourceRect.left - horizontalOffset,
|
||||||
elementRect.x - horizontalOffset,
|
sourceRect.right + horizontalOffset,
|
||||||
elementRect.x + elementRect.width + horizontalOffset,
|
frameWidth,
|
||||||
width,
|
|
||||||
viewport.left,
|
viewport.left,
|
||||||
viewport.right,
|
viewport.right,
|
||||||
preferRight
|
preferRight
|
||||||
);
|
);
|
||||||
const [y, h, below] = this._getConstrainedPosition(
|
const [top, height, below] = this._getConstrainedPosition(
|
||||||
elementRect.y + elementRect.height - verticalOffset,
|
sourceRect.bottom - verticalOffset,
|
||||||
elementRect.y + verticalOffset,
|
sourceRect.top + verticalOffset,
|
||||||
height,
|
frameHeight,
|
||||||
viewport.top,
|
viewport.top,
|
||||||
viewport.bottom,
|
viewport.bottom,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
return [x, y, w, h, below];
|
return {left, top, width, height, after, below};
|
||||||
}
|
}
|
||||||
|
|
||||||
_isVerticalTextPopupOnRight(positionPreference, writingMode) {
|
_isVerticalTextPopupOnRight(positionPreference, writingMode) {
|
||||||
@ -706,10 +771,9 @@ class Popup extends EventDispatcher {
|
|||||||
return !this._isWritingModeLeftToRight(writingMode);
|
return !this._isWritingModeLeftToRight(writingMode);
|
||||||
case 'after':
|
case 'after':
|
||||||
return this._isWritingModeLeftToRight(writingMode);
|
return this._isWritingModeLeftToRight(writingMode);
|
||||||
case 'left':
|
|
||||||
return false;
|
|
||||||
case 'right':
|
case 'right':
|
||||||
return true;
|
return true;
|
||||||
|
// case 'left':
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -806,8 +870,9 @@ class Popup extends EventDispatcher {
|
|||||||
this._horizontalOffset2 = general.popupHorizontalOffset2;
|
this._horizontalOffset2 = general.popupHorizontalOffset2;
|
||||||
this._verticalOffset2 = general.popupVerticalOffset2;
|
this._verticalOffset2 = general.popupVerticalOffset2;
|
||||||
this._verticalTextPosition = general.popupVerticalTextPosition;
|
this._verticalTextPosition = general.popupVerticalTextPosition;
|
||||||
this._horizontalTextPosition = general.popupHorizontalTextPosition;
|
this._horizontalTextPositionBelow = (this._verticalTextPosition === 'below');
|
||||||
this._displayMode = general.popupDisplayMode;
|
this._displayMode = general.popupDisplayMode;
|
||||||
|
this._displayModeIsFullWidth = (this._displayMode === 'full-width');
|
||||||
this._scaleRelativeToVisualViewport = general.popupScaleRelativeToVisualViewport;
|
this._scaleRelativeToVisualViewport = general.popupScaleRelativeToVisualViewport;
|
||||||
this._useSecureFrameUrl = general.useSecurePopupFrameUrl;
|
this._useSecureFrameUrl = general.useSecurePopupFrameUrl;
|
||||||
this._useShadowDom = general.usePopupShadowDom;
|
this._useShadowDom = general.usePopupShadowDom;
|
||||||
@ -820,4 +885,49 @@ class Popup extends EventDispatcher {
|
|||||||
if (deepEqual(this._optionsContext, optionsContext)) { return; }
|
if (deepEqual(this._optionsContext, optionsContext)) { return; }
|
||||||
await this._setOptionsContext(optionsContext);
|
await this._setOptionsContext(optionsContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Rect[]} sourceRects
|
||||||
|
* @returns {Rect}
|
||||||
|
*/
|
||||||
|
_getBoundingSourceRect(sourceRects) {
|
||||||
|
switch (sourceRects.length) {
|
||||||
|
case 0: return {left: 0, top: 0, right: 0, bottom: 0};
|
||||||
|
case 1: return sourceRects[0];
|
||||||
|
}
|
||||||
|
let {left, top, right, bottom} = sourceRects[0];
|
||||||
|
for (let i = 1, ii = sourceRects.length; i < ii; ++i) {
|
||||||
|
const sourceRect = sourceRects[i];
|
||||||
|
left = Math.min(left, sourceRect.left);
|
||||||
|
top = Math.min(top, sourceRect.top);
|
||||||
|
right = Math.max(right, sourceRect.right);
|
||||||
|
bottom = Math.max(bottom, sourceRect.bottom);
|
||||||
|
}
|
||||||
|
return {left, top, right, bottom};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {SizeRect} sizeRect
|
||||||
|
* @param {Rect[]} sourceRects
|
||||||
|
* @param {number} ignoreIndex
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
_isOverlapping(sizeRect, sourceRects, ignoreIndex) {
|
||||||
|
const {left, top} = sizeRect;
|
||||||
|
const right = left + sizeRect.width;
|
||||||
|
const bottom = top + sizeRect.height;
|
||||||
|
for (let i = 0, ii = sourceRects.length; i < ii; ++i) {
|
||||||
|
if (i === ignoreIndex) { continue; }
|
||||||
|
const sourceRect = sourceRects[i];
|
||||||
|
if (
|
||||||
|
left < sourceRect.right &&
|
||||||
|
right > sourceRect.left &&
|
||||||
|
top < sourceRect.bottom &&
|
||||||
|
bottom > sourceRect.top
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class Display extends EventDispatcher {
|
class Display extends EventDispatcher {
|
||||||
|
/**
|
||||||
|
* Information about how popup content should be shown, specifically related to the inner popup content.
|
||||||
|
* @typedef {object} ContentDetails
|
||||||
|
*/
|
||||||
|
|
||||||
constructor(tabId, frameId, pageType, japaneseUtil, documentFocusController, hotkeyHandler) {
|
constructor(tabId, frameId, pageType, japaneseUtil, documentFocusController, hotkeyHandler) {
|
||||||
super();
|
super();
|
||||||
this._tabId = tabId;
|
this._tabId = tabId;
|
||||||
@ -341,6 +346,10 @@ class Display extends EventDispatcher {
|
|||||||
this.trigger('optionsUpdated', {options});
|
this.trigger('optionsUpdated', {options});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the content of the display.
|
||||||
|
* @param {ContentDetails} details
|
||||||
|
*/
|
||||||
setContent(details) {
|
setContent(details) {
|
||||||
const {focus, params, state, content} = details;
|
const {focus, params, state, content} = details;
|
||||||
const historyMode = this._historyHasChanged ? details.historyMode : 'clear';
|
const historyMode = this._historyHasChanged ? details.historyMode : 'clear';
|
||||||
|
@ -154,8 +154,8 @@ class PopupMenu extends EventDispatcher {
|
|||||||
|
|
||||||
// Position
|
// Position
|
||||||
const menu = this._node;
|
const menu = this._node;
|
||||||
const fullRect = this._containerNode.getBoundingClientRect();
|
const containerNodeRect = this._containerNode.getBoundingClientRect();
|
||||||
const sourceRect = this._sourceElement.getBoundingClientRect();
|
const sourceElementRect = this._sourceElement.getBoundingClientRect();
|
||||||
const menuRect = menu.getBoundingClientRect();
|
const menuRect = menu.getBoundingClientRect();
|
||||||
let top = menuRect.top;
|
let top = menuRect.top;
|
||||||
let bottom = menuRect.bottom;
|
let bottom = menuRect.bottom;
|
||||||
@ -166,19 +166,19 @@ class PopupMenu extends EventDispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let x = (
|
let x = (
|
||||||
sourceRect.left +
|
sourceElementRect.left +
|
||||||
sourceRect.width * ((-horizontal * horizontalCover + 1) * 0.5) +
|
sourceElementRect.width * ((-horizontal * horizontalCover + 1) * 0.5) +
|
||||||
menuRect.width * ((-horizontal + 1) * -0.5)
|
menuRect.width * ((-horizontal + 1) * -0.5)
|
||||||
);
|
);
|
||||||
let y = (
|
let y = (
|
||||||
sourceRect.top +
|
sourceElementRect.top +
|
||||||
(menuRect.top - top) +
|
(menuRect.top - top) +
|
||||||
sourceRect.height * ((-vertical * verticalCover + 1) * 0.5) +
|
sourceElementRect.height * ((-vertical * verticalCover + 1) * 0.5) +
|
||||||
(bottom - top) * ((-vertical + 1) * -0.5)
|
(bottom - top) * ((-vertical + 1) * -0.5)
|
||||||
);
|
);
|
||||||
|
|
||||||
x = Math.max(0.0, Math.min(fullRect.width - menuRect.width, x));
|
x = Math.max(0.0, Math.min(containerNodeRect.width - menuRect.width, x));
|
||||||
y = Math.max(0.0, Math.min(fullRect.height - menuRect.height, y));
|
y = Math.max(0.0, Math.min(containerNodeRect.height - menuRect.height, y));
|
||||||
|
|
||||||
menu.style.left = `${x}px`;
|
menu.style.left = `${x}px`;
|
||||||
menu.style.top = `${y}px`;
|
menu.style.top = `${y}px`;
|
||||||
|
@ -94,6 +94,10 @@ class TextSourceElement {
|
|||||||
return this._element.getBoundingClientRect();
|
return this._element.getBoundingClientRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRects() {
|
||||||
|
return this.getClientRects();
|
||||||
|
}
|
||||||
|
|
||||||
getWritingMode() {
|
getWritingMode() {
|
||||||
return 'horizontal-tb';
|
return 'horizontal-tb';
|
||||||
}
|
}
|
||||||
|
@ -94,6 +94,10 @@ class TextSourceRange {
|
|||||||
return this._range.getBoundingClientRect();
|
return this._range.getBoundingClientRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRects() {
|
||||||
|
return this._range.getClientRects();
|
||||||
|
}
|
||||||
|
|
||||||
getWritingMode() {
|
getWritingMode() {
|
||||||
return TextSourceRange.getElementWritingMode(TextSourceRange.getParentElement(this._range.startContainer));
|
return TextSourceRange.getElementWritingMode(TextSourceRange.getParentElement(this._range.startContainer));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user