1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-23 02:10:12 +01:00
metamask-extension/app/scripts/lib/notification-manager.js
Mark Stacey 22f931e6b2
Prevent automatic rejection of confirmations (#13194)
* Prevent automatic rejection of confirmations

Confirmations are now only automatically rejected if a user explicitly
closes the notification window. If we close the window programmatically
because there are no notifications left to show, nothing gets rejected.

This partially avoids a race condition where a confirmation gets
rejected automatically without the user having seen the confirmation
first. This could happen if the confirmation was processed just as the
notification window was being closed.

It's still possible for a confirmation that the user has never seen to
get rejected as a result of the user closing the window. But at least
now it's no longer possible for a confirmation to get rejected in this
manner after the user resolves the last confirmation in the queue.

* Fix bug that prevented automatic closure detection

All windows were being detected as explicit window closures,
essentially just as they were previously, because this variable was
cleared too soon.

* Re-open popup when necessary

After the window is automatically closed, a confirmation may have been
queued up while the window was closing. If so, the popup is now re-
opened.
2022-01-05 13:39:19 -03:30

121 lines
3.7 KiB
JavaScript

import EventEmitter from 'safe-event-emitter';
import ExtensionPlatform from '../platforms/extension';
const NOTIFICATION_HEIGHT = 620;
const NOTIFICATION_WIDTH = 360;
export const NOTIFICATION_MANAGER_EVENTS = {
POPUP_CLOSED: 'onPopupClosed',
};
export default class NotificationManager extends EventEmitter {
/**
* A collection of methods for controlling the showing and hiding of the notification popup.
*
* @typedef {Object} NotificationManager
*
*/
constructor() {
super();
this.platform = new ExtensionPlatform();
this.platform.addOnRemovedListener(this._onWindowClosed.bind(this));
}
/**
* Mark the notification popup as having been automatically closed.
*
* This lets us differentiate between the cases where we close the
* notification popup v.s. when the user closes the popup window directly.
*/
markAsAutomaticallyClosed() {
this._popupAutomaticallyClosed = true;
}
/**
* Either brings an existing MetaMask notification window into focus, or creates a new notification window. New
* notification windows are given a 'popup' type.
*
*/
async showPopup() {
const popup = await this._getPopup();
// Bring focus to chrome popup
if (popup) {
// bring focus to existing chrome popup
await this.platform.focusWindow(popup.id);
} else {
let left = 0;
let top = 0;
try {
const lastFocused = await this.platform.getLastFocusedWindow();
// Position window in top right corner of lastFocused window.
top = lastFocused.top;
left = lastFocused.left + (lastFocused.width - NOTIFICATION_WIDTH);
} catch (_) {
// The following properties are more than likely 0, due to being
// opened from the background chrome process for the extension that
// has no physical dimensions
const { screenX, screenY, outerWidth } = window;
top = Math.max(screenY, 0);
left = Math.max(screenX + (outerWidth - NOTIFICATION_WIDTH), 0);
}
// create new notification popup
const popupWindow = await this.platform.openWindow({
url: 'notification.html',
type: 'popup',
width: NOTIFICATION_WIDTH,
height: NOTIFICATION_HEIGHT,
left,
top,
});
// Firefox currently ignores left/top for create, but it works for update
if (popupWindow.left !== left && popupWindow.state !== 'fullscreen') {
await this.platform.updateWindowPosition(popupWindow.id, left, top);
}
this._popupId = popupWindow.id;
}
}
_onWindowClosed(windowId) {
if (windowId === this._popupId) {
this._popupId = undefined;
this.emit(NOTIFICATION_MANAGER_EVENTS.POPUP_CLOSED, {
automaticallyClosed: this._popupAutomaticallyClosed,
});
this._popupAutomaticallyClosed = undefined;
}
}
/**
* Checks all open MetaMask windows, and returns the first one it finds that is a notification window (i.e. has the
* type 'popup')
*
* @private
* @param {Function} cb - A node style callback that to which the found notification window will be passed.
*
*/
async _getPopup() {
const windows = await this.platform.getAllWindows();
return this._getPopupIn(windows);
}
/**
* Given an array of windows, returns the 'popup' that has been opened by MetaMask, or null if no such window exists.
*
* @private
* @param {Array} windows - An array of objects containing data about the open MetaMask extension windows.
*
*/
_getPopupIn(windows) {
return windows
? windows.find((win) => {
// Returns notification popup
return win && win.type === 'popup' && win.id === this._popupId;
})
: null;
}
}