Improve popup window ownership (#1364)

* Update frameInformationGet to also return the tab ID

* Add tabId to Frontend

* Pass tabId/frameId to Display

* Pass ownership information using setContent

* Remove ownerFrameId for Popup classes

* Use frameId instead of ownerFrameId for screenshotting

* Use contentOrigin instead of owner

* Update _invokeContentOrigin implementation
This commit is contained in:
toasted-nutbread 2021-02-09 22:56:04 -05:00 committed by GitHub
parent 0f5fb804d0
commit 166451b8f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 106 additions and 63 deletions

View File

@ -525,8 +525,10 @@ class Backend {
} }
_onApiFrameInformationGet(params, sender) { _onApiFrameInformationGet(params, sender) {
const tab = sender.tab;
const tabId = tab ? tab.id : void 0;
const frameId = sender.frameId; const frameId = sender.frameId;
return Promise.resolve({frameId}); return Promise.resolve({tabId, frameId});
} }
_onApiInjectStylesheet({type, value}, sender) { _onApiInjectStylesheet({type, value}, sender) {
@ -1505,17 +1507,17 @@ class Backend {
return isValidTab ? tab : null; return isValidTab ? tab : null;
} }
async _getScreenshot(windowId, tabId, ownerFrameId, format, quality) { async _getScreenshot(windowId, tabId, frameId, format, quality) {
if (typeof windowId !== 'number') { if (typeof windowId !== 'number') {
throw new Error('Invalid window ID'); throw new Error('Invalid window ID');
} }
let token = null; let token = null;
try { try {
if (typeof tabId === 'number' && typeof ownerFrameId === 'number') { if (typeof tabId === 'number' && typeof frameId === 'number') {
const action = 'setAllVisibleOverride'; const action = 'setAllVisibleOverride';
const params = {value: false, priority: 0, awaitFrame: true}; const params = {value: false, priority: 0, awaitFrame: true};
token = await this._sendMessageTabPromise(tabId, {action, params}, {frameId: ownerFrameId}); token = await this._sendMessageTabPromise(tabId, {action, params}, {frameId});
} }
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
@ -1533,7 +1535,7 @@ class Backend {
const action = 'clearAllVisibleOverride'; const action = 'clearAllVisibleOverride';
const params = {token}; const params = {token};
try { try {
await this._sendMessageTabPromise(tabId, {action, params}, {frameId: ownerFrameId}); await this._sendMessageTabPromise(tabId, {action, params}, {frameId});
} catch (e) { } catch (e) {
// NOP // NOP
} }
@ -1634,8 +1636,8 @@ class Backend {
} }
async _injectAnkNoteScreenshot(ankiConnect, timestamp, definitionDetails, details) { async _injectAnkNoteScreenshot(ankiConnect, timestamp, definitionDetails, details) {
const {windowId, tabId, ownerFrameId, format, quality} = details; const {windowId, tabId, frameId, format, quality} = details;
const dataUrl = await this._getScreenshot(windowId, tabId, ownerFrameId, format, quality); const dataUrl = await this._getScreenshot(windowId, tabId, frameId, format, quality);
const {mediaType, data} = this._getDataUrlInfo(dataUrl); const {mediaType, data} = this._getDataUrlInfo(dataUrl);
const extension = this._mediaUtility.getFileExtensionFromImageMediaType(mediaType); const extension = this._mediaUtility.getFileExtensionFromImageMediaType(mediaType);

View File

@ -32,12 +32,14 @@
api.forwardLogsToBackend(); api.forwardLogsToBackend();
await yomichan.backendReady(); await yomichan.backendReady();
const {tabId, frameId} = await api.frameInformationGet();
const japaneseUtil = new JapaneseUtil(wanakana); const japaneseUtil = new JapaneseUtil(wanakana);
const hotkeyHandler = new HotkeyHandler(); const hotkeyHandler = new HotkeyHandler();
hotkeyHandler.prepare(); hotkeyHandler.prepare();
const displaySearch = new DisplaySearch(japaneseUtil, documentFocusController, hotkeyHandler); const displaySearch = new DisplaySearch(tabId, frameId, japaneseUtil, documentFocusController, hotkeyHandler);
await displaySearch.prepare(); await displaySearch.prepare();
document.documentElement.dataset.loaded = 'true'; document.documentElement.dataset.loaded = 'true';

View File

@ -23,8 +23,8 @@
*/ */
class DisplaySearch extends Display { class DisplaySearch extends Display {
constructor(japaneseUtil, documentFocusController, hotkeyHandler) { constructor(tabId, frameId, japaneseUtil, documentFocusController, hotkeyHandler) {
super('search', japaneseUtil, documentFocusController, hotkeyHandler); super('search', tabId, frameId, japaneseUtil, documentFocusController, hotkeyHandler);
this._searchButton = document.querySelector('#search-button'); this._searchButton = document.querySelector('#search-button');
this._queryInput = document.querySelector('#search-textbox'); this._queryInput = document.querySelector('#search-textbox');
this._introElement = document.querySelector('#intro'); this._introElement = document.querySelector('#intro');
@ -353,7 +353,11 @@ class DisplaySearch extends Display {
}, },
content: { content: {
definitions: null, definitions: null,
animate animate,
contentOrigin: {
tabId: this.tabId,
frameId: this.frameId
}
} }
}; };
if (!lookup) { details.params.lookup = 'false'; } if (!lookup) { details.params.lookup = 'false'; }

View File

@ -26,7 +26,7 @@
try { try {
api.forwardLogsToBackend(); api.forwardLogsToBackend();
const {frameId} = await api.frameInformationGet(); const {tabId, frameId} = await api.frameInformationGet();
const hotkeyHandler = new HotkeyHandler(); const hotkeyHandler = new HotkeyHandler();
hotkeyHandler.prepare(); hotkeyHandler.prepare();
@ -34,7 +34,7 @@
const popupFactory = new PopupFactory(frameId); const popupFactory = new PopupFactory(frameId);
popupFactory.prepare(); popupFactory.prepare();
const preview = new PopupPreviewFrame(frameId, popupFactory, hotkeyHandler); const preview = new PopupPreviewFrame(tabId, frameId, popupFactory, hotkeyHandler);
await preview.prepare(); await preview.prepare();
document.documentElement.dataset.loaded = 'true'; document.documentElement.dataset.loaded = 'true';

View File

@ -23,7 +23,8 @@
*/ */
class PopupPreviewFrame { class PopupPreviewFrame {
constructor(frameId, popupFactory, hotkeyHandler) { constructor(tabId, frameId, popupFactory, hotkeyHandler) {
this._tabId = tabId;
this._frameId = frameId; this._frameId = frameId;
this._popupFactory = popupFactory; this._popupFactory = popupFactory;
this._hotkeyHandler = hotkeyHandler; this._hotkeyHandler = hotkeyHandler;
@ -67,6 +68,7 @@ class PopupPreviewFrame {
// Overwrite frontend // Overwrite frontend
this._frontend = new Frontend({ this._frontend = new Frontend({
tabId: this._tabId,
frameId: this._frameId, frameId: this._frameId,
popupFactory: this._popupFactory, popupFactory: this._popupFactory,
depth: 0, depth: 0,

View File

@ -27,7 +27,7 @@
api.forwardLogsToBackend(); api.forwardLogsToBackend();
await yomichan.backendReady(); await yomichan.backendReady();
const {frameId} = await api.frameInformationGet(); const {tabId, frameId} = await api.frameInformationGet();
if (typeof frameId !== 'number') { if (typeof frameId !== 'number') {
throw new Error('Failed to get frameId'); throw new Error('Failed to get frameId');
} }
@ -39,6 +39,7 @@
popupFactory.prepare(); popupFactory.prepare();
const frontend = new Frontend({ const frontend = new Frontend({
tabId,
frameId, frameId,
popupFactory, popupFactory,
depth: 0, depth: 0,

View File

@ -32,12 +32,14 @@
api.forwardLogsToBackend(); api.forwardLogsToBackend();
await yomichan.backendReady(); await yomichan.backendReady();
const {tabId, frameId} = await api.frameInformationGet();
const japaneseUtil = new JapaneseUtil(null); const japaneseUtil = new JapaneseUtil(null);
const hotkeyHandler = new HotkeyHandler(); const hotkeyHandler = new HotkeyHandler();
hotkeyHandler.prepare(); hotkeyHandler.prepare();
const display = new Display('popup', japaneseUtil, documentFocusController, hotkeyHandler); const display = new Display(tabId, frameId, 'popup', japaneseUtil, documentFocusController, hotkeyHandler);
await display.prepare(); await display.prepare();
const displayProfileSelection = new DisplayProfileSelection(display); const displayProfileSelection = new DisplayProfileSelection(display);
displayProfileSelection.prepare(); displayProfileSelection.prepare();

View File

@ -28,6 +28,7 @@ class Frontend {
pageType, pageType,
popupFactory, popupFactory,
depth, depth,
tabId,
frameId, frameId,
parentPopupId, parentPopupId,
parentFrameId, parentFrameId,
@ -40,6 +41,7 @@ class Frontend {
this._pageType = pageType; this._pageType = pageType;
this._popupFactory = popupFactory; this._popupFactory = popupFactory;
this._depth = depth; this._depth = depth;
this._tabId = tabId;
this._frameId = frameId; this._frameId = frameId;
this._parentPopupId = parentPopupId; this._parentPopupId = parentPopupId;
this._parentFrameId = parentFrameId; this._parentFrameId = parentFrameId;
@ -437,7 +439,6 @@ class Frontend {
return await this._popupFactory.getOrCreatePopup({ return await this._popupFactory.getOrCreatePopup({
frameId: this._frameId, frameId: this._frameId,
ownerFrameId: this._frameId,
depth: this._depth, depth: this._depth,
childrenSupported: this._childrenSupported childrenSupported: this._childrenSupported
}); });
@ -446,7 +447,6 @@ class Frontend {
async _getProxyPopup() { async _getProxyPopup() {
return await this._popupFactory.getOrCreatePopup({ return await this._popupFactory.getOrCreatePopup({
frameId: this._parentFrameId, frameId: this._parentFrameId,
ownerFrameId: this._frameId,
depth: this._depth, depth: this._depth,
parentPopupId: this._parentPopupId, parentPopupId: this._parentPopupId,
childrenSupported: this._childrenSupported childrenSupported: this._childrenSupported
@ -469,7 +469,6 @@ class Frontend {
const popup = await this._popupFactory.getOrCreatePopup({ const popup = await this._popupFactory.getOrCreatePopup({
frameId: targetFrameId, frameId: targetFrameId,
ownerFrameId: this._frameId,
id: popupId, id: popupId,
childrenSupported: this._childrenSupported childrenSupported: this._childrenSupported
}); });
@ -482,7 +481,6 @@ class Frontend {
async _getPopupWindow() { async _getPopupWindow() {
return await this._popupFactory.getOrCreatePopup({ return await this._popupFactory.getOrCreatePopup({
ownerFrameId: this._frameId,
depth: this._depth, depth: this._depth,
popupWindow: true, popupWindow: true,
childrenSupported: this._childrenSupported childrenSupported: this._childrenSupported
@ -537,7 +535,11 @@ class Frontend {
documentTitle documentTitle
}, },
content: { content: {
definitions definitions,
contentOrigin: {
tabId: this._tabId,
frameId: this._frameId
}
} }
}; };
if (textSource instanceof TextSourceElement && textSource.fullContent !== query) { if (textSource instanceof TextSourceElement && textSource.fullContent !== query) {

View File

@ -56,7 +56,6 @@ class PopupFactory {
async getOrCreatePopup({ async getOrCreatePopup({
frameId=null, frameId=null,
ownerFrameId=null,
id=null, id=null,
parentPopupId=null, parentPopupId=null,
depth=null, depth=null,
@ -103,8 +102,7 @@ class PopupFactory {
const popup = new PopupWindow({ const popup = new PopupWindow({
id, id,
depth, depth,
frameId: this._frameId, frameId: this._frameId
ownerFrameId
}); });
this._popups.set(id, popup); this._popups.set(id, popup);
return popup; return popup;
@ -117,7 +115,6 @@ class PopupFactory {
id, id,
depth, depth,
frameId: this._frameId, frameId: this._frameId,
ownerFrameId,
childrenSupported childrenSupported
}); });
if (parent !== null) { if (parent !== null) {
@ -139,14 +136,12 @@ class PopupFactory {
id, id,
parentPopupId, parentPopupId,
frameId, frameId,
ownerFrameId,
childrenSupported childrenSupported
})); }));
const popup = new PopupProxy({ const popup = new PopupProxy({
id, id,
depth, depth,
frameId, frameId,
ownerFrameId,
frameOffsetForwarder: useFrameOffsetForwarder ? this._frameOffsetForwarder : null frameOffsetForwarder: useFrameOffsetForwarder ? this._frameOffsetForwarder : null
}); });
this._popups.set(id, popup); this._popups.set(id, popup);

View File

@ -24,14 +24,12 @@ class PopupProxy extends EventDispatcher {
id, id,
depth, depth,
frameId, frameId,
ownerFrameId,
frameOffsetForwarder frameOffsetForwarder
}) { }) {
super(); super();
this._id = id; this._id = id;
this._depth = depth; this._depth = depth;
this._frameId = frameId; this._frameId = frameId;
this._ownerFrameId = ownerFrameId;
this._frameOffsetForwarder = frameOffsetForwarder; this._frameOffsetForwarder = frameOffsetForwarder;
this._frameOffset = [0, 0]; this._frameOffset = [0, 0];

View File

@ -23,14 +23,12 @@ class PopupWindow extends EventDispatcher {
constructor({ constructor({
id, id,
depth, depth,
frameId, frameId
ownerFrameId
}) { }) {
super(); super();
this._id = id; this._id = id;
this._depth = depth; this._depth = depth;
this._frameId = frameId; this._frameId = frameId;
this._ownerFrameId = ownerFrameId;
this._popupTabId = null; this._popupTabId = null;
} }

View File

@ -27,14 +27,12 @@ class Popup extends EventDispatcher {
id, id,
depth, depth,
frameId, frameId,
ownerFrameId,
childrenSupported childrenSupported
}) { }) {
super(); super();
this._id = id; this._id = id;
this._depth = depth; this._depth = depth;
this._frameId = frameId; this._frameId = frameId;
this._ownerFrameId = ownerFrameId;
this._childrenSupported = childrenSupported; this._childrenSupported = childrenSupported;
this._parent = null; this._parent = null;
this._child = null; this._child = null;
@ -275,7 +273,6 @@ class Popup extends EventDispatcher {
depth: this._depth, depth: this._depth,
parentPopupId: this._id, parentPopupId: this._id,
parentFrameId: this._frameId, parentFrameId: this._frameId,
ownerFrameId: this._ownerFrameId,
childrenSupported: this._childrenSupported, childrenSupported: this._childrenSupported,
scale: this._contentScale, scale: this._contentScale,
optionsContext: this._optionsContext optionsContext: this._optionsContext

View File

@ -36,8 +36,10 @@
*/ */
class Display extends EventDispatcher { class Display extends EventDispatcher {
constructor(pageType, japaneseUtil, documentFocusController, hotkeyHandler) { constructor(tabId, frameId, pageType, japaneseUtil, documentFocusController, hotkeyHandler) {
super(); super();
this._tabId = tabId;
this._frameId = frameId;
this._pageType = pageType; this._pageType = pageType;
this._japaneseUtil = japaneseUtil; this._japaneseUtil = japaneseUtil;
this._documentFocusController = documentFocusController; this._documentFocusController = documentFocusController;
@ -98,7 +100,8 @@ class Display extends EventDispatcher {
this._depth = 0; this._depth = 0;
this._parentPopupId = null; this._parentPopupId = null;
this._parentFrameId = null; this._parentFrameId = null;
this._ownerFrameId = null; this._contentOriginTabId = tabId;
this._contentOriginFrameId = frameId;
this._childrenSupported = true; this._childrenSupported = true;
this._frameEndpoint = (pageType === 'popup' ? new FrameEndpoint() : null); this._frameEndpoint = (pageType === 'popup' ? new FrameEndpoint() : null);
this._browser = null; this._browser = null;
@ -199,6 +202,14 @@ class Display extends EventDispatcher {
return this._progressIndicatorVisible; return this._progressIndicatorVisible;
} }
get tabId() {
return this._tabId;
}
get frameId() {
return this._frameId;
}
async prepare() { async prepare() {
// State setup // State setup
const {documentElement} = document; const {documentElement} = document;
@ -246,6 +257,13 @@ class Display extends EventDispatcher {
} }
} }
getContentOrigin() {
return {
tabId: this._contentOriginTabId,
frameId: this._contentOriginFrameId
};
}
initializeState() { initializeState() {
this._onStateChanged(); this._onStateChanged();
if (this._frameEndpoint !== null) { if (this._frameEndpoint !== null) {
@ -394,7 +412,7 @@ class Display extends EventDispatcher {
close() { close() {
switch (this._pageType) { switch (this._pageType) {
case 'popup': case 'popup':
this._invokeOwner('closePopup'); this._invokeContentOrigin('closePopup');
break; break;
case 'search': case 'search':
this._closeTab(); this._closeTab();
@ -427,7 +445,8 @@ class Display extends EventDispatcher {
params: this._createSearchParams(type, query, false), params: this._createSearchParams(type, query, false),
state, state,
content: { content: {
definitions: null definitions: null,
contentOrigin: this.getContentOrigin()
} }
}; };
this.setContent(details); this.setContent(details);
@ -494,14 +513,10 @@ class Display extends EventDispatcher {
this._setContentScale(scale); this._setContentScale(scale);
} }
async _onMessageConfigure({depth, parentPopupId, parentFrameId, ownerFrameId, childrenSupported, scale, optionsContext}) { async _onMessageConfigure({depth, parentPopupId, parentFrameId, childrenSupported, scale, optionsContext}) {
this._depth = depth; this._depth = depth;
this._parentPopupId = parentPopupId; this._parentPopupId = parentPopupId;
this._parentFrameId = parentFrameId; this._parentFrameId = parentFrameId;
this._ownerFrameId = ownerFrameId;
if (this._pageType === 'popup') {
this._hotkeyHandler.forwardFrameId = ownerFrameId;
}
this._childrenSupported = childrenSupported; this._childrenSupported = childrenSupported;
this._setContentScale(scale); this._setContentScale(scale);
await this.setOptionsContext(optionsContext); await this.setOptionsContext(optionsContext);
@ -615,7 +630,8 @@ class Display extends EventDispatcher {
cause: 'queryParser' cause: 'queryParser'
}, },
content: { content: {
definitions definitions,
contentOrigin: this.getContentOrigin()
} }
}; };
this.setContent(details); this.setContent(details);
@ -629,7 +645,12 @@ class Display extends EventDispatcher {
history: false, history: false,
params: {type}, params: {type},
state: {}, state: {},
content: {} content: {
contentOrigin: {
tabId: this._tabId,
frameId: this._frameId
}
}
}; };
this.setContent(details); this.setContent(details);
} }
@ -691,7 +712,8 @@ class Display extends EventDispatcher {
documentTitle documentTitle
}, },
content: { content: {
definitions definitions,
contentOrigin: this.getContentOrigin()
} }
}; };
this.setContent(details); this.setContent(details);
@ -886,6 +908,24 @@ class Display extends EventDispatcher {
changeHistory = true; changeHistory = true;
} }
let contentOriginValid = false;
const {contentOrigin} = content;
if (typeof contentOrigin === 'object' && contentOrigin !== null) {
const {tabId, frameId} = contentOrigin;
if (typeof tabId === 'number' && typeof frameId === 'number') {
this._contentOriginTabId = tabId;
this._contentOriginFrameId = frameId;
if (this._pageType === 'popup') {
this._hotkeyHandler.forwardFrameId = (tabId === this._tabId ? frameId : null);
}
contentOriginValid = true;
}
}
if (!contentOriginValid) {
content.contentOrigin = this.getContentOrigin();
changeHistory = true;
}
await this._setOptionsContextIfDifferent(optionsContext); await this._setOptionsContextIfDifferent(optionsContext);
if (this._setContentToken !== token) { return; } if (this._setContentToken !== token) { return; }
@ -1499,10 +1539,10 @@ class Display extends EventDispatcher {
} = options; } = options;
const timestamp = Date.now(); const timestamp = Date.now();
const ownerFrameId = this._ownerFrameId; const screenshotFrameId = this._contentOriginFrameId;
const definitionDetails = this._getDefinitionDetailsForNote(definition); const definitionDetails = this._getDefinitionDetailsForNote(definition);
const audioDetails = (mode !== 'kanji' && this._ankiNoteBuilder.containsMarker(fields, 'audio') ? {sources, customSourceUrl, customSourceType} : null); const audioDetails = (mode !== 'kanji' && this._ankiNoteBuilder.containsMarker(fields, 'audio') ? {sources, customSourceUrl, customSourceType} : null);
const screenshotDetails = (this._ankiNoteBuilder.containsMarker(fields, 'screenshot') ? {ownerFrameId, format, quality} : null); const screenshotDetails = (this._ankiNoteBuilder.containsMarker(fields, 'screenshot') ? {frameId: screenshotFrameId, format, quality} : null);
const clipboardDetails = { const clipboardDetails = {
image: this._ankiNoteBuilder.containsMarker(fields, 'clipboard-image'), image: this._ankiNoteBuilder.containsMarker(fields, 'clipboard-image'),
text: this._ankiNoteBuilder.containsMarker(fields, 'clipboard-text') text: this._ankiNoteBuilder.containsMarker(fields, 'clipboard-text')
@ -1583,8 +1623,6 @@ class Display extends EventDispatcher {
parentFrameId: this._parentFrameId parentFrameId: this._parentFrameId
}; };
const {frameId} = await api.frameInformationGet();
await dynamicLoader.loadScripts([ await dynamicLoader.loadScripts([
'/mixed/js/text-scanner.js', '/mixed/js/text-scanner.js',
'/mixed/js/frame-client.js', '/mixed/js/frame-client.js',
@ -1597,12 +1635,13 @@ class Display extends EventDispatcher {
'/fg/js/frontend.js' '/fg/js/frontend.js'
]); ]);
const popupFactory = new PopupFactory(frameId); const popupFactory = new PopupFactory(this._frameId);
popupFactory.prepare(); popupFactory.prepare();
Object.assign(setupNestedPopupsOptions, { Object.assign(setupNestedPopupsOptions, {
depth: this._depth + 1, depth: this._depth + 1,
frameId, tabId: this._tabId,
frameId: this._frameId,
popupFactory, popupFactory,
pageType: this._pageType, pageType: this._pageType,
allowRootFramePopupProxy: true, allowRootFramePopupProxy: true,
@ -1615,15 +1654,15 @@ class Display extends EventDispatcher {
await frontend.prepare(); await frontend.prepare();
} }
async _invokeOwner(action, params={}) { async _invokeContentOrigin(action, params={}) {
if (this._ownerFrameId === null) { if (this._contentOriginTabId === this._tabId && this._contentOriginFrameId === this._frameId) {
throw new Error('No owner frame'); throw new Error('Content origin is same page');
} }
return await api.crossFrame.invoke(this._ownerFrameId, action, params); return await api.crossFrame.invokeTab(this._contentOriginTabId, this._contentOriginFrameId, action, params);
} }
_copyHostSelection() { _copyHostSelection() {
if (this._ownerFrameId === null || window.getSelection().toString()) { return false; } if (this._contentOriginFrameId === null || window.getSelection().toString()) { return false; }
this._copyHostSelectionInner(); this._copyHostSelectionInner();
return true; return true;
} }
@ -1635,7 +1674,7 @@ class Display extends EventDispatcher {
{ {
let text; let text;
try { try {
text = await this._invokeOwner('getSelectionText'); text = await this._invokeContentOrigin('getSelectionText');
} catch (e) { } catch (e) {
break; break;
} }
@ -1643,7 +1682,7 @@ class Display extends EventDispatcher {
} }
break; break;
default: default:
await this._invokeOwner('copySelection'); await this._invokeContentOrigin('copySelection');
break; break;
} }
} }
@ -1760,7 +1799,8 @@ class Display extends EventDispatcher {
documentTitle documentTitle
}, },
content: { content: {
definitions definitions,
contentOrigin: this.getContentOrigin()
} }
}; };
this._definitionTextScanner.clearSelection(true); this._definitionTextScanner.clearSelection(true);
@ -1816,7 +1856,7 @@ class Display extends EventDispatcher {
} }
async _initializeFrameResize(token) { async _initializeFrameResize(token) {
const size = await this._invokeOwner('getFrameSize'); const size = await this._invokeContentOrigin('getFrameSize');
if (this._frameResizeToken !== token) { return; } if (this._frameResizeToken !== token) { return; }
this._frameResizeStartSize = size; this._frameResizeStartSize = size;
} }
@ -1842,7 +1882,7 @@ class Display extends EventDispatcher {
height += y - this._frameResizeStartOffset.y; height += y - this._frameResizeStartOffset.y;
width = Math.max(Math.max(0, handleSize.width), width); width = Math.max(Math.max(0, handleSize.width), width);
height = Math.max(Math.max(0, handleSize.height), height); height = Math.max(Math.max(0, handleSize.height), height);
await this._invokeOwner('setFrameSize', {width, height}); await this._invokeContentOrigin('setFrameSize', {width, height});
} }
_updateHotkeys(options) { _updateHotkeys(options) {