mirror of
https://github.com/luxfi/electron-chrome-extensions.git
synced 2026-10-30 16:58:32 +00:00
4493 lines
130 KiB
JavaScript
4493 lines
130 KiB
JavaScript
/******/ (() => { // webpackBootstrap
|
|
/******/ var __webpack_modules__ = ({
|
|
|
|
/***/ "./src/browser/api/browser-action.ts":
|
|
/*!*******************************************!*\
|
|
!*** ./src/browser/api/browser-action.ts ***!
|
|
\*******************************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "BrowserActionAPI": () => (/* binding */ BrowserActionAPI)
|
|
/* harmony export */ });
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! electron */ "electron");
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(electron__WEBPACK_IMPORTED_MODULE_0__);
|
|
/* harmony import */ var _popup__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../popup */ "./src/browser/popup.ts");
|
|
/* harmony import */ var _common__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./common */ "./src/browser/api/common.ts");
|
|
|
|
|
|
|
|
|
|
const debug = __webpack_require__(/*! debug */ "./node_modules/debug/src/index.js")('electron-chrome-extensions:browserAction');
|
|
|
|
if (!electron__WEBPACK_IMPORTED_MODULE_0__.app.isReady()) {
|
|
electron__WEBPACK_IMPORTED_MODULE_0__.protocol.registerSchemesAsPrivileged([{
|
|
scheme: 'crx',
|
|
privileges: {
|
|
bypassCSP: true
|
|
}
|
|
}]);
|
|
}
|
|
|
|
const getBrowserActionDefaults = extension => {
|
|
const manifest = (0,_common__WEBPACK_IMPORTED_MODULE_2__.getExtensionManifest)(extension);
|
|
const {
|
|
browser_action
|
|
} = manifest;
|
|
|
|
if (typeof browser_action === 'object') {
|
|
const action = {};
|
|
action.title = browser_action.default_title || manifest.name;
|
|
const iconPath = (0,_common__WEBPACK_IMPORTED_MODULE_2__.getIconPath)(extension);
|
|
if (iconPath) action.icon = {
|
|
path: iconPath
|
|
};
|
|
|
|
if (browser_action.default_popup) {
|
|
action.popup = browser_action.default_popup;
|
|
}
|
|
|
|
return action;
|
|
}
|
|
};
|
|
|
|
class BrowserActionAPI {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
this.actionMap = new Map();
|
|
this.popup = void 0;
|
|
this.observers = new Set();
|
|
this.queuedUpdate = false;
|
|
|
|
this.handleCrxRequest = (request, callback) => {
|
|
debug('%s', request.url);
|
|
let response;
|
|
|
|
try {
|
|
const url = new URL(request.url);
|
|
const {
|
|
hostname: requestType
|
|
} = url;
|
|
|
|
switch (requestType) {
|
|
case 'extension-icon':
|
|
{
|
|
const tabId = url.searchParams.get('tabId');
|
|
const fragments = url.pathname.split('/');
|
|
const extensionId = fragments[1];
|
|
const imageSize = parseInt(fragments[2], 10);
|
|
const resizeType = parseInt(fragments[3], 10) || _common__WEBPACK_IMPORTED_MODULE_2__.ResizeType.Up;
|
|
const extension = this.ctx.session.getExtension(extensionId);
|
|
let iconDetails;
|
|
const action = this.actionMap.get(extensionId);
|
|
|
|
if (action) {
|
|
var _action$tabs$tabId;
|
|
|
|
iconDetails = tabId && ((_action$tabs$tabId = action.tabs[tabId]) === null || _action$tabs$tabId === void 0 ? void 0 : _action$tabs$tabId.icon) || action.icon;
|
|
}
|
|
|
|
let iconImage;
|
|
|
|
if (extension && iconDetails) {
|
|
if (typeof iconDetails.path === 'string') {
|
|
const iconAbsPath = (0,_common__WEBPACK_IMPORTED_MODULE_2__.resolveExtensionPath)(extension, iconDetails.path);
|
|
if (iconAbsPath) iconImage = electron__WEBPACK_IMPORTED_MODULE_0__.nativeImage.createFromPath(iconAbsPath);
|
|
} else if (typeof iconDetails.path === 'object') {
|
|
const imagePath = (0,_common__WEBPACK_IMPORTED_MODULE_2__.matchSize)(iconDetails.path, imageSize, resizeType);
|
|
const iconAbsPath = imagePath && (0,_common__WEBPACK_IMPORTED_MODULE_2__.resolveExtensionPath)(extension, imagePath);
|
|
if (iconAbsPath) iconImage = electron__WEBPACK_IMPORTED_MODULE_0__.nativeImage.createFromPath(iconAbsPath);
|
|
} else if (typeof iconDetails.imageData === 'string') {
|
|
iconImage = electron__WEBPACK_IMPORTED_MODULE_0__.nativeImage.createFromDataURL(iconDetails.imageData);
|
|
} else if (typeof iconDetails.imageData === 'object') {
|
|
const imageData = (0,_common__WEBPACK_IMPORTED_MODULE_2__.matchSize)(iconDetails.imageData, imageSize, resizeType);
|
|
iconImage = imageData ? electron__WEBPACK_IMPORTED_MODULE_0__.nativeImage.createFromDataURL(imageData) : undefined;
|
|
}
|
|
}
|
|
|
|
if (iconImage) {
|
|
response = {
|
|
statusCode: 200,
|
|
mimeType: 'image/png',
|
|
data: iconImage.toPNG()
|
|
};
|
|
} else {
|
|
response = {
|
|
statusCode: 400
|
|
};
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
response = {
|
|
statusCode: 400
|
|
};
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
response = {
|
|
statusCode: 500
|
|
};
|
|
}
|
|
|
|
callback(response);
|
|
};
|
|
|
|
const handle = this.ctx.router.apiHandler();
|
|
|
|
const getter = propName => ({
|
|
extension
|
|
}, details = {}) => {
|
|
const {
|
|
tabId
|
|
} = details;
|
|
const action = this.getAction(extension.id);
|
|
let result;
|
|
|
|
if (tabId) {
|
|
if (action.tabs[tabId]) {
|
|
result = action.tabs[tabId][propName];
|
|
} else {
|
|
result = action[propName];
|
|
}
|
|
} else {
|
|
result = action[propName];
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
const setDetails = ({
|
|
extension
|
|
}, details, propName) => {
|
|
const {
|
|
tabId
|
|
} = details;
|
|
let value = details[propName] || undefined;
|
|
|
|
if (typeof value === 'undefined') {
|
|
const defaults = getBrowserActionDefaults(extension);
|
|
value = defaults ? defaults[propName] : value;
|
|
}
|
|
|
|
const valueObj = {
|
|
[propName]: value
|
|
};
|
|
const action = this.getAction(extension.id);
|
|
|
|
if (tabId) {
|
|
const tabAction = action.tabs[tabId] || (action.tabs[tabId] = {});
|
|
Object.assign(tabAction, valueObj);
|
|
} else {
|
|
Object.assign(action, valueObj);
|
|
}
|
|
|
|
this.onUpdate();
|
|
};
|
|
|
|
const setter = propName => (event, details) => setDetails(event, details, propName);
|
|
|
|
const handleProp = (prop, key) => {
|
|
handle(`browserAction.get${prop}`, getter(key));
|
|
handle(`browserAction.set${prop}`, setter(key));
|
|
};
|
|
|
|
handleProp('BadgeBackgroundColor', 'color');
|
|
handleProp('BadgeText', 'text');
|
|
handleProp('Title', 'title');
|
|
handleProp('Popup', 'popup'); // setIcon is unique in that it can pass in a variety of properties. Here we normalize them
|
|
// to use 'icon'.
|
|
|
|
handle('browserAction.setIcon', (event, {
|
|
tabId,
|
|
...details
|
|
}) => {
|
|
setDetails(event, {
|
|
tabId,
|
|
icon: details
|
|
}, 'icon');
|
|
setDetails(event, {
|
|
tabId,
|
|
iconModified: Date.now()
|
|
}, 'iconModified');
|
|
}); // browserAction preload API
|
|
|
|
const preloadOpts = {
|
|
allowRemote: true,
|
|
extensionContext: false
|
|
};
|
|
handle('browserAction.getState', this.getState.bind(this), preloadOpts);
|
|
handle('browserAction.activate', this.activate.bind(this), preloadOpts);
|
|
handle('browserAction.addObserver', event => {
|
|
const {
|
|
sender: webContents
|
|
} = event;
|
|
this.observers.add(webContents);
|
|
webContents.once('destroyed', () => {
|
|
this.observers.delete(webContents);
|
|
});
|
|
}, preloadOpts);
|
|
handle('browserAction.removeObserver', event => {
|
|
const {
|
|
sender: webContents
|
|
} = event;
|
|
this.observers.delete(webContents);
|
|
}, preloadOpts);
|
|
this.ctx.store.on('active-tab-changed', () => {
|
|
this.onUpdate();
|
|
}); // Clear out tab details when removed
|
|
|
|
this.ctx.store.on('tab-removed', tabId => {
|
|
for (const [, actionDetails] of this.actionMap) {
|
|
if (actionDetails.tabs[tabId]) {
|
|
delete actionDetails.tabs[tabId];
|
|
}
|
|
}
|
|
|
|
this.onUpdate();
|
|
});
|
|
this.setupSession(this.ctx.session);
|
|
}
|
|
|
|
setupSession(session) {
|
|
session.on('extension-loaded', (event, extension) => {
|
|
this.processExtension(extension);
|
|
});
|
|
session.on('extension-unloaded', (event, extension) => {
|
|
this.removeActions(extension.id);
|
|
});
|
|
session.protocol.registerBufferProtocol('crx', this.handleCrxRequest);
|
|
}
|
|
|
|
getAction(extensionId) {
|
|
let action = this.actionMap.get(extensionId);
|
|
|
|
if (!action) {
|
|
action = {
|
|
tabs: {}
|
|
};
|
|
this.actionMap.set(extensionId, action);
|
|
this.onUpdate();
|
|
}
|
|
|
|
return action;
|
|
} // TODO: Make private for v4 major release.
|
|
|
|
|
|
removeActions(extensionId) {
|
|
if (this.actionMap.has(extensionId)) {
|
|
this.actionMap.delete(extensionId);
|
|
}
|
|
|
|
this.onUpdate();
|
|
}
|
|
|
|
getPopupUrl(extensionId, tabId) {
|
|
var _action$tabs$tabId2;
|
|
|
|
const action = this.getAction(extensionId);
|
|
const popupPath = ((_action$tabs$tabId2 = action.tabs[tabId]) === null || _action$tabs$tabId2 === void 0 ? void 0 : _action$tabs$tabId2.popup) || action.popup || undefined;
|
|
return popupPath && `chrome-extension://${extensionId}/${popupPath}`;
|
|
} // TODO: Make private for v4 major release.
|
|
|
|
|
|
processExtension(extension) {
|
|
const defaultAction = getBrowserActionDefaults(extension);
|
|
|
|
if (defaultAction) {
|
|
const action = this.getAction(extension.id);
|
|
Object.assign(action, defaultAction);
|
|
}
|
|
}
|
|
|
|
getState() {
|
|
// Get state without icon data.
|
|
const actions = Array.from(this.actionMap.entries()).map(([id, details]) => {
|
|
const {
|
|
icon,
|
|
tabs,
|
|
...rest
|
|
} = details;
|
|
const tabsInfo = {};
|
|
|
|
for (const tabId of Object.keys(tabs)) {
|
|
const {
|
|
icon,
|
|
...rest
|
|
} = tabs[tabId];
|
|
tabsInfo[tabId] = rest;
|
|
}
|
|
|
|
return {
|
|
id,
|
|
tabs: tabsInfo,
|
|
...rest
|
|
};
|
|
});
|
|
const activeTab = this.ctx.store.getActiveTabOfCurrentWindow();
|
|
return {
|
|
activeTabId: activeTab === null || activeTab === void 0 ? void 0 : activeTab.id,
|
|
actions
|
|
};
|
|
}
|
|
|
|
activate(extEvt, details) {
|
|
const {
|
|
eventType,
|
|
extensionId,
|
|
tabId
|
|
} = details;
|
|
debug(`activate [eventType: ${eventType}, extensionId: '${extensionId}', tabId: ${tabId}, senderId: ${extEvt.sender.id}]`);
|
|
|
|
switch (eventType) {
|
|
case 'click':
|
|
this.activateClick(details);
|
|
break;
|
|
|
|
case 'contextmenu':
|
|
this.activateContextMenu(details, extEvt);
|
|
break;
|
|
|
|
default:
|
|
console.debug(`Ignoring unknown browserAction.activate event '${eventType}'`);
|
|
}
|
|
}
|
|
|
|
activateClick(details) {
|
|
const {
|
|
extensionId,
|
|
tabId,
|
|
anchorRect
|
|
} = details;
|
|
|
|
if (this.popup) {
|
|
const toggleExtension = !this.popup.isDestroyed() && this.popup.extensionId === extensionId;
|
|
this.popup.destroy();
|
|
this.popup = undefined;
|
|
|
|
if (toggleExtension) {
|
|
debug('skipping activate to close popup');
|
|
return;
|
|
}
|
|
}
|
|
|
|
const tab = tabId >= 0 ? this.ctx.store.getTabById(tabId) : this.ctx.store.getActiveTabOfCurrentWindow();
|
|
|
|
if (!tab) {
|
|
throw new Error(`Unable to get active tab`);
|
|
}
|
|
|
|
const popupUrl = this.getPopupUrl(extensionId, tab.id);
|
|
|
|
if (popupUrl) {
|
|
const win = this.ctx.store.tabToWindow.get(tab);
|
|
|
|
if (!win) {
|
|
throw new Error('Unable to get BrowserWindow from active tab');
|
|
}
|
|
|
|
this.popup = new _popup__WEBPACK_IMPORTED_MODULE_1__.PopupView({
|
|
extensionId,
|
|
session: this.ctx.session,
|
|
parent: win,
|
|
url: popupUrl,
|
|
anchorRect
|
|
});
|
|
debug(`opened popup: ${popupUrl}`);
|
|
this.ctx.emit('browser-action-popup-created', this.popup);
|
|
} else {
|
|
debug(`dispatching onClicked for ${extensionId}`);
|
|
const tabDetails = this.ctx.store.tabDetailsCache.get(tab.id);
|
|
this.ctx.router.sendEvent(extensionId, 'browserAction.onClicked', tabDetails);
|
|
}
|
|
}
|
|
|
|
activateContextMenu(details, event) {
|
|
var _manifest$options_ui;
|
|
|
|
const {
|
|
extensionId,
|
|
anchorRect
|
|
} = details;
|
|
const extension = this.ctx.session.getExtension(extensionId);
|
|
|
|
if (!extension) {
|
|
throw new Error(`Unregistered extension '${extensionId}'`);
|
|
}
|
|
|
|
const manifest = (0,_common__WEBPACK_IMPORTED_MODULE_2__.getExtensionManifest)(extension);
|
|
const menu = new electron__WEBPACK_IMPORTED_MODULE_0__.Menu();
|
|
|
|
const append = opts => menu.append(new electron__WEBPACK_IMPORTED_MODULE_0__.MenuItem(opts));
|
|
|
|
const appendSeparator = () => menu.append(new electron__WEBPACK_IMPORTED_MODULE_0__.MenuItem({
|
|
type: 'separator'
|
|
}));
|
|
|
|
append({
|
|
label: extension.name,
|
|
click: () => {
|
|
const homePageUrl = manifest.homepage_url || `https://chrome.google.com/webstore/detail/${extension.id}`;
|
|
this.ctx.store.createTab({
|
|
url: homePageUrl
|
|
}, event);
|
|
}
|
|
});
|
|
appendSeparator();
|
|
const contextMenuItems = this.ctx.store.buildMenuItems(extensionId, 'browser_action');
|
|
|
|
if (contextMenuItems.length > 0) {
|
|
contextMenuItems.forEach(item => menu.append(item));
|
|
appendSeparator();
|
|
}
|
|
|
|
const optionsPage = manifest.options_page || ((_manifest$options_ui = manifest.options_ui) === null || _manifest$options_ui === void 0 ? void 0 : _manifest$options_ui.page);
|
|
const optionsPageUrl = optionsPage ? (0,_common__WEBPACK_IMPORTED_MODULE_2__.getExtensionUrl)(extension, optionsPage) : undefined;
|
|
append({
|
|
label: 'Options',
|
|
enabled: typeof optionsPageUrl === 'string',
|
|
click: () => {
|
|
this.ctx.store.createTab({
|
|
url: optionsPageUrl
|
|
}, event);
|
|
}
|
|
});
|
|
menu.popup({
|
|
x: Math.floor(anchorRect.x),
|
|
y: Math.floor(anchorRect.y + anchorRect.height)
|
|
});
|
|
}
|
|
|
|
onUpdate() {
|
|
if (this.queuedUpdate) return;
|
|
this.queuedUpdate = true;
|
|
queueMicrotask(() => {
|
|
this.queuedUpdate = false;
|
|
debug(`dispatching update to ${this.observers.size} observer(s)`);
|
|
Array.from(this.observers).forEach(observer => {
|
|
if (!observer.isDestroyed()) {
|
|
observer.send('browserAction.update');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/browser/api/commands.ts":
|
|
/*!*************************************!*\
|
|
!*** ./src/browser/api/commands.ts ***!
|
|
\*************************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "CommandsAPI": () => (/* binding */ CommandsAPI)
|
|
/* harmony export */ });
|
|
/**
|
|
* Stub implementation for chrome.commands API.
|
|
*/
|
|
class CommandsAPI {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
const handle = this.ctx.router.apiHandler();
|
|
handle('commands.getAll', this.getAll);
|
|
}
|
|
|
|
getAll() {
|
|
return [];
|
|
}
|
|
|
|
}
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/browser/api/common.ts":
|
|
/*!***********************************!*\
|
|
!*** ./src/browser/api/common.ts ***!
|
|
\***********************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "ResizeType": () => (/* binding */ ResizeType),
|
|
/* harmony export */ "getExtensionManifest": () => (/* binding */ getExtensionManifest),
|
|
/* harmony export */ "getExtensionUrl": () => (/* binding */ getExtensionUrl),
|
|
/* harmony export */ "getIconImage": () => (/* binding */ getIconImage),
|
|
/* harmony export */ "getIconPath": () => (/* binding */ getIconPath),
|
|
/* harmony export */ "matchSize": () => (/* binding */ matchSize),
|
|
/* harmony export */ "matchesPattern": () => (/* binding */ matchesPattern),
|
|
/* harmony export */ "resolveExtensionPath": () => (/* binding */ resolveExtensionPath),
|
|
/* harmony export */ "validateExtensionResource": () => (/* binding */ validateExtensionResource)
|
|
/* harmony export */ });
|
|
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! fs */ "fs");
|
|
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__);
|
|
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! path */ "path");
|
|
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__);
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! electron */ "electron");
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(electron__WEBPACK_IMPORTED_MODULE_2__);
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Get the extension's properly typed Manifest.
|
|
*
|
|
* I can't seem to get TS's merged type declarations working so I'm using this
|
|
* instead for now.
|
|
*/
|
|
const getExtensionManifest = extension => extension.manifest;
|
|
const getExtensionUrl = (extension, uri) => {
|
|
try {
|
|
return new URL(uri, extension.url).href;
|
|
} catch {}
|
|
};
|
|
const resolveExtensionPath = (extension, uri) => {
|
|
const relpath = uri.indexOf(extension.url) === 0 ? uri.slice(extension.url.length) : uri;
|
|
const resPath = path__WEBPACK_IMPORTED_MODULE_1__.join(extension.path, relpath); // prevent any parent traversals
|
|
|
|
if (!resPath.startsWith(extension.path)) return;
|
|
return resPath;
|
|
};
|
|
const validateExtensionResource = async (extension, uri) => {
|
|
const resPath = resolveExtensionPath(extension, uri);
|
|
if (!resPath) return;
|
|
|
|
try {
|
|
await fs__WEBPACK_IMPORTED_MODULE_0__.promises.stat(resPath);
|
|
} catch {
|
|
return; // doesn't exist
|
|
}
|
|
|
|
return resPath;
|
|
};
|
|
let ResizeType;
|
|
|
|
(function (ResizeType) {
|
|
ResizeType[ResizeType["Exact"] = 0] = "Exact";
|
|
ResizeType[ResizeType["Up"] = 1] = "Up";
|
|
ResizeType[ResizeType["Down"] = 2] = "Down";
|
|
})(ResizeType || (ResizeType = {}));
|
|
|
|
const matchSize = (imageSet, size, match) => {
|
|
// TODO: match based on size
|
|
const first = parseInt(Object.keys(imageSet).pop(), 10);
|
|
return imageSet[first];
|
|
};
|
|
/** Gets the relative path to the extension's default icon. */
|
|
|
|
const getIconPath = (extension, iconSize = 32, resizeType = ResizeType.Up) => {
|
|
const {
|
|
browser_action,
|
|
icons
|
|
} = getExtensionManifest(extension);
|
|
const {
|
|
default_icon
|
|
} = browser_action || {};
|
|
|
|
if (typeof default_icon === 'string') {
|
|
const iconPath = default_icon;
|
|
return iconPath;
|
|
} else if (typeof default_icon === 'object') {
|
|
const iconPath = matchSize(default_icon, iconSize, resizeType);
|
|
return iconPath;
|
|
} else if (typeof icons === 'object') {
|
|
const iconPath = matchSize(icons, iconSize, resizeType);
|
|
return iconPath;
|
|
}
|
|
};
|
|
const getIconImage = extension => {
|
|
const iconPath = getIconPath(extension);
|
|
const iconAbsolutePath = iconPath && resolveExtensionPath(extension, iconPath);
|
|
return iconAbsolutePath ? electron__WEBPACK_IMPORTED_MODULE_2__.nativeImage.createFromPath(iconAbsolutePath) : undefined;
|
|
};
|
|
|
|
const escapePattern = pattern => pattern.replace(/[\\^$+?.()|[\]{}]/g, '\\$&');
|
|
/**
|
|
* @see https://developer.chrome.com/extensions/match_patterns
|
|
*/
|
|
|
|
|
|
const matchesPattern = (pattern, url) => {
|
|
if (pattern === '<all_urls>') return true;
|
|
const regexp = new RegExp(`^${pattern.split('*').map(escapePattern).join('.*')}$`);
|
|
return url.match(regexp);
|
|
};
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/browser/api/context-menus.ts":
|
|
/*!******************************************!*\
|
|
!*** ./src/browser/api/context-menus.ts ***!
|
|
\******************************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "ContextMenusAPI": () => (/* binding */ ContextMenusAPI)
|
|
/* harmony export */ });
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! electron */ "electron");
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(electron__WEBPACK_IMPORTED_MODULE_0__);
|
|
/* harmony import */ var _common__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./common */ "./src/browser/api/common.ts");
|
|
|
|
|
|
const DEFAULT_CONTEXTS = ['page'];
|
|
|
|
const getContextTypesFromParams = params => {
|
|
const contexts = new Set(['all']);
|
|
|
|
switch (params.mediaType) {
|
|
case 'audio':
|
|
case 'video':
|
|
case 'image':
|
|
contexts.add(params.mediaType);
|
|
}
|
|
|
|
if (params.pageURL) contexts.add('page');
|
|
if (params.linkURL) contexts.add('link');
|
|
if (params.frameURL) contexts.add('frame');
|
|
if (params.selectionText) contexts.add('selection');
|
|
if (params.isEditable) contexts.add('editable');
|
|
return contexts;
|
|
};
|
|
|
|
const formatTitle = (title, params) => {
|
|
if (params.selectionText && title.includes('%s')) {
|
|
title = title.split('%s').join(params.selectionText);
|
|
}
|
|
|
|
return title;
|
|
};
|
|
|
|
const matchesConditions = (props, conditions) => {
|
|
if (props.visible === false) return false;
|
|
const {
|
|
contextTypes,
|
|
targetUrl,
|
|
documentUrl
|
|
} = conditions;
|
|
const contexts = props.contexts || DEFAULT_CONTEXTS;
|
|
const inContext = contexts.some(context => contextTypes.has(context));
|
|
if (!inContext) return false;
|
|
|
|
if (props.targetUrlPatterns && props.targetUrlPatterns.length > 0 && targetUrl) {
|
|
if (!props.targetUrlPatterns.some(pattern => (0,_common__WEBPACK_IMPORTED_MODULE_1__.matchesPattern)(pattern, targetUrl))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (props.documentUrlPatterns && props.documentUrlPatterns.length > 0 && documentUrl) {
|
|
if (!props.documentUrlPatterns.some(pattern => (0,_common__WEBPACK_IMPORTED_MODULE_1__.matchesPattern)(pattern, documentUrl))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
class ContextMenusAPI {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
this.menus = new Map();
|
|
|
|
this.buildMenuItem = opts => {
|
|
const {
|
|
extension,
|
|
props,
|
|
webContents,
|
|
params
|
|
} = opts; // TODO: try to get the appropriately sized image before resizing
|
|
|
|
let icon = opts.showIcon ? (0,_common__WEBPACK_IMPORTED_MODULE_1__.getIconImage)(extension) : undefined;
|
|
|
|
if (icon) {
|
|
icon = icon.resize({
|
|
width: 16,
|
|
height: 16
|
|
});
|
|
}
|
|
|
|
const menuItemOptions = {
|
|
id: props.id,
|
|
type: props.type,
|
|
label: params ? formatTitle(props.title || '', params) : props.title || '',
|
|
icon,
|
|
enabled: props.enabled,
|
|
click: () => {
|
|
this.onClicked(extension.id, props.id, webContents, params);
|
|
}
|
|
};
|
|
return menuItemOptions;
|
|
};
|
|
|
|
this.buildMenuItemsFromTemplate = menuItemTemplates => {
|
|
const itemMap = new Map(); // Group by ID
|
|
|
|
for (const item of menuItemTemplates) {
|
|
const menuItem = this.buildMenuItem(item);
|
|
itemMap.set(item.props.id, menuItem);
|
|
} // Organize in tree
|
|
|
|
|
|
for (const item of menuItemTemplates) {
|
|
const menuItem = itemMap.get(item.props.id);
|
|
|
|
if (item.props.parentId) {
|
|
const parentMenuItem = itemMap.get(item.props.parentId);
|
|
|
|
if (parentMenuItem) {
|
|
const submenu = parentMenuItem.submenu || [];
|
|
submenu.push(menuItem);
|
|
parentMenuItem.submenu = submenu;
|
|
}
|
|
}
|
|
}
|
|
|
|
const menuItems = [];
|
|
|
|
const buildFromTemplate = opts => {
|
|
if (Array.isArray(opts.submenu)) {
|
|
const submenu = new electron__WEBPACK_IMPORTED_MODULE_0__.Menu();
|
|
opts.submenu.forEach(item => submenu.append(buildFromTemplate(item)));
|
|
opts.submenu = submenu;
|
|
}
|
|
|
|
return new electron__WEBPACK_IMPORTED_MODULE_0__.MenuItem(opts);
|
|
}; // Build all final MenuItems in-order
|
|
|
|
|
|
for (const item of menuItemTemplates) {
|
|
// Items with parents will be handled recursively
|
|
if (item.props.parentId) continue;
|
|
const menuItem = itemMap.get(item.props.id);
|
|
menuItems.push(buildFromTemplate(menuItem));
|
|
}
|
|
|
|
return menuItems;
|
|
};
|
|
|
|
this.create = ({
|
|
extension
|
|
}, createProperties) => {
|
|
const {
|
|
id,
|
|
type,
|
|
title
|
|
} = createProperties;
|
|
|
|
if (this.menus.has(id)) {
|
|
// TODO: duplicate error
|
|
return;
|
|
}
|
|
|
|
if (!title && type !== 'separator') {
|
|
// TODO: error
|
|
return;
|
|
}
|
|
|
|
this.addContextItem(extension.id, createProperties);
|
|
};
|
|
|
|
this.remove = ({
|
|
extension
|
|
}, menuItemId) => {
|
|
const items = this.menus.get(extension.id);
|
|
|
|
if (items && items.has(menuItemId)) {
|
|
items.delete(menuItemId);
|
|
|
|
if (items.size === 0) {
|
|
this.menus.delete(extension.id);
|
|
}
|
|
}
|
|
};
|
|
|
|
this.removeAll = ({
|
|
extension
|
|
}) => {
|
|
this.menus.delete(extension.id);
|
|
};
|
|
|
|
const handle = this.ctx.router.apiHandler();
|
|
handle('contextMenus.create', this.create);
|
|
handle('contextMenus.remove', this.remove);
|
|
handle('contextMenus.removeAll', this.removeAll);
|
|
this.ctx.session.on('extension-unloaded', (event, extension) => {
|
|
if (this.menus.has(extension.id)) {
|
|
this.menus.delete(extension.id);
|
|
}
|
|
});
|
|
this.ctx.store.buildMenuItems = this.buildMenuItemsForExtension.bind(this);
|
|
}
|
|
|
|
addContextItem(extensionId, props) {
|
|
let contextItems = this.menus.get(extensionId);
|
|
|
|
if (!contextItems) {
|
|
contextItems = new Map();
|
|
this.menus.set(extensionId, contextItems);
|
|
}
|
|
|
|
contextItems.set(props.id, props);
|
|
}
|
|
|
|
buildMenuItemsForParams(webContents, params) {
|
|
if (webContents.session !== this.ctx.session) return [];
|
|
const menuItemOptions = [];
|
|
const conditions = {
|
|
contextTypes: getContextTypesFromParams(params),
|
|
targetUrl: params.srcURL || params.linkURL,
|
|
documentUrl: params.frameURL || params.pageURL
|
|
};
|
|
|
|
for (const [extensionId, propItems] of this.menus) {
|
|
const extension = this.ctx.session.getExtension(extensionId);
|
|
if (!extension) continue;
|
|
|
|
for (const [, props] of propItems) {
|
|
if (matchesConditions(props, conditions)) {
|
|
const menuItem = {
|
|
extension,
|
|
props,
|
|
webContents,
|
|
params,
|
|
showIcon: true
|
|
};
|
|
menuItemOptions.push(menuItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.buildMenuItemsFromTemplate(menuItemOptions);
|
|
}
|
|
|
|
buildMenuItemsForExtension(extensionId, menuType) {
|
|
const extensionItems = this.menus.get(extensionId);
|
|
const extension = this.ctx.session.getExtension(extensionId);
|
|
const activeTab = this.ctx.store.getActiveTabOfCurrentWindow();
|
|
const menuItemOptions = [];
|
|
|
|
if (extensionItems && extension && activeTab) {
|
|
const conditions = {
|
|
contextTypes: new Set(['all', menuType])
|
|
};
|
|
|
|
for (const [, props] of extensionItems) {
|
|
if (matchesConditions(props, conditions)) {
|
|
const menuItem = {
|
|
extension,
|
|
props,
|
|
webContents: activeTab
|
|
};
|
|
menuItemOptions.push(menuItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.buildMenuItemsFromTemplate(menuItemOptions);
|
|
}
|
|
|
|
onClicked(extensionId, menuItemId, webContents, params) {
|
|
if (webContents.isDestroyed()) return;
|
|
const tab = this.ctx.store.tabDetailsCache.get(webContents.id);
|
|
|
|
if (!tab) {
|
|
console.error(`[Extensions] Unable to find tab for id=${webContents.id}`);
|
|
return;
|
|
}
|
|
|
|
const data = {
|
|
selectionText: params === null || params === void 0 ? void 0 : params.selectionText,
|
|
checked: false,
|
|
// TODO
|
|
menuItemId,
|
|
frameId: -1,
|
|
// TODO: match frameURL with webFrameMain in Electron 12
|
|
frameUrl: params === null || params === void 0 ? void 0 : params.frameURL,
|
|
editable: (params === null || params === void 0 ? void 0 : params.isEditable) || false,
|
|
mediaType: params === null || params === void 0 ? void 0 : params.mediaType,
|
|
wasChecked: false,
|
|
// TODO
|
|
pageUrl: params === null || params === void 0 ? void 0 : params.pageURL,
|
|
// types are inaccurate
|
|
linkUrl: params === null || params === void 0 ? void 0 : params.linkURL,
|
|
parentMenuItemId: -1,
|
|
// TODO
|
|
srcUrl: params === null || params === void 0 ? void 0 : params.srcURL
|
|
};
|
|
this.ctx.router.sendEvent(extensionId, 'contextMenus.onClicked', data, tab);
|
|
}
|
|
|
|
}
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/browser/api/cookies.ts":
|
|
/*!************************************!*\
|
|
!*** ./src/browser/api/cookies.ts ***!
|
|
\************************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "CookiesAPI": () => (/* binding */ CookiesAPI)
|
|
/* harmony export */ });
|
|
var CookieStoreID;
|
|
|
|
(function (CookieStoreID) {
|
|
CookieStoreID["Default"] = "0";
|
|
CookieStoreID["Incognito"] = "1";
|
|
})(CookieStoreID || (CookieStoreID = {}));
|
|
|
|
const onChangedCauseTranslation = {
|
|
'expired-overwrite': 'expired_overwrite'
|
|
};
|
|
|
|
const createCookieDetails = cookie => ({ ...cookie,
|
|
domain: cookie.domain || '',
|
|
hostOnly: Boolean(cookie.hostOnly),
|
|
session: Boolean(cookie.session),
|
|
path: cookie.path || '',
|
|
httpOnly: Boolean(cookie.httpOnly),
|
|
secure: Boolean(cookie.secure),
|
|
storeId: CookieStoreID.Default
|
|
});
|
|
|
|
class CookiesAPI {
|
|
get cookies() {
|
|
return this.ctx.session.cookies;
|
|
}
|
|
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
|
|
this.onChanged = (event, cookie, cause, removed) => {
|
|
const changeInfo = {
|
|
cause: onChangedCauseTranslation[cause] || cause,
|
|
cookie: createCookieDetails(cookie),
|
|
removed
|
|
};
|
|
this.ctx.router.broadcastEvent('cookies.onChanged', changeInfo);
|
|
};
|
|
|
|
const handle = this.ctx.router.apiHandler();
|
|
handle('cookies.get', this.get.bind(this));
|
|
handle('cookies.getAll', this.getAll.bind(this));
|
|
handle('cookies.set', this.set.bind(this));
|
|
handle('cookies.remove', this.remove.bind(this));
|
|
handle('cookies.getAllCookieStores', this.getAllCookieStores.bind(this));
|
|
this.cookies.addListener('changed', this.onChanged);
|
|
}
|
|
|
|
async get(event, details) {
|
|
// TODO: storeId
|
|
const cookies = await this.cookies.get({
|
|
url: details.url,
|
|
name: details.name
|
|
}); // TODO: If more than one cookie of the same name exists for the given URL,
|
|
// the one with the longest path will be returned. For cookies with the
|
|
// same path length, the cookie with the earliest creation time will be returned.
|
|
|
|
return cookies.length > 0 ? createCookieDetails(cookies[0]) : null;
|
|
}
|
|
|
|
async getAll(event, details) {
|
|
// TODO: storeId
|
|
const cookies = await this.cookies.get({
|
|
url: details.url,
|
|
name: details.name,
|
|
domain: details.domain,
|
|
path: details.path,
|
|
secure: details.secure,
|
|
session: details.session
|
|
});
|
|
return cookies.map(createCookieDetails);
|
|
}
|
|
|
|
async set(event, details) {
|
|
await this.cookies.set(details);
|
|
const cookies = await this.cookies.get(details);
|
|
return cookies.length > 0 ? createCookieDetails(cookies[0]) : null;
|
|
}
|
|
|
|
async remove(event, details) {
|
|
try {
|
|
await this.cookies.remove(details.url, details.name);
|
|
} catch {
|
|
return null;
|
|
}
|
|
|
|
return details;
|
|
}
|
|
|
|
async getAllCookieStores(event) {
|
|
const tabIds = Array.from(this.ctx.store.tabs).map(tab => tab.isDestroyed() ? undefined : tab.id).filter(Boolean);
|
|
return [{
|
|
id: CookieStoreID.Default,
|
|
tabIds
|
|
}];
|
|
}
|
|
|
|
}
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/browser/api/notifications.ts":
|
|
/*!******************************************!*\
|
|
!*** ./src/browser/api/notifications.ts ***!
|
|
\******************************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "NotificationsAPI": () => (/* binding */ NotificationsAPI)
|
|
/* harmony export */ });
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! electron */ "electron");
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(electron__WEBPACK_IMPORTED_MODULE_0__);
|
|
/* harmony import */ var _common__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./common */ "./src/browser/api/common.ts");
|
|
|
|
|
|
var TemplateType;
|
|
|
|
(function (TemplateType) {
|
|
TemplateType["Basic"] = "basic";
|
|
TemplateType["Image"] = "image";
|
|
TemplateType["List"] = "list";
|
|
TemplateType["Progress"] = "progress";
|
|
})(TemplateType || (TemplateType = {}));
|
|
|
|
const getBody = opts => {
|
|
const {
|
|
type = TemplateType.Basic
|
|
} = opts;
|
|
|
|
switch (type) {
|
|
case TemplateType.List:
|
|
{
|
|
if (!Array.isArray(opts.items)) {
|
|
throw new Error('List items must be provided for list type');
|
|
}
|
|
|
|
return opts.items.map(item => `${item.title} - ${item.message}`).join('\n');
|
|
}
|
|
|
|
default:
|
|
return opts.message || '';
|
|
}
|
|
};
|
|
|
|
const getUrgency = priority => {
|
|
if (typeof priority !== 'number') {
|
|
return 'normal';
|
|
} else if (priority >= 2) {
|
|
return 'critical';
|
|
} else if (priority < 0) {
|
|
return 'low';
|
|
} else {
|
|
return 'normal';
|
|
}
|
|
};
|
|
|
|
const createScopedIdentifier = (extension, id) => `${extension.id}-${id}`;
|
|
|
|
const stripScopeFromIdentifier = id => {
|
|
const index = id.indexOf('-');
|
|
return id.substr(index + 1);
|
|
};
|
|
|
|
class NotificationsAPI {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
this.registry = new Map();
|
|
|
|
this.clear = ({
|
|
extension
|
|
}, id) => {
|
|
const notificationId = createScopedIdentifier(extension, id);
|
|
|
|
if (this.registry.has(notificationId)) {
|
|
var _this$registry$get;
|
|
|
|
(_this$registry$get = this.registry.get(notificationId)) === null || _this$registry$get === void 0 ? void 0 : _this$registry$get.close();
|
|
}
|
|
};
|
|
|
|
this.create = async ({
|
|
extension
|
|
}, arg1, arg2) => {
|
|
let id;
|
|
let opts;
|
|
|
|
if (typeof arg1 === 'object') {
|
|
id = 'guid'; // TODO: generate uuid
|
|
|
|
opts = arg1;
|
|
} else if (typeof arg1 === 'string') {
|
|
id = arg1;
|
|
opts = arg2;
|
|
} else {
|
|
throw new Error('Invalid arguments');
|
|
}
|
|
|
|
if (typeof opts !== 'object' || !opts.type || !opts.iconUrl || !opts.title || !opts.message) {
|
|
throw new Error('Missing required notification options');
|
|
}
|
|
|
|
const notificationId = createScopedIdentifier(extension, id);
|
|
|
|
if (this.registry.has(notificationId)) {
|
|
var _this$registry$get2;
|
|
|
|
(_this$registry$get2 = this.registry.get(notificationId)) === null || _this$registry$get2 === void 0 ? void 0 : _this$registry$get2.close();
|
|
}
|
|
|
|
let icon;
|
|
|
|
if (opts.iconUrl) {
|
|
var _url;
|
|
|
|
let url;
|
|
|
|
try {
|
|
url = new URL(opts.iconUrl);
|
|
} catch {}
|
|
|
|
if (((_url = url) === null || _url === void 0 ? void 0 : _url.protocol) === 'data:') {
|
|
icon = opts.iconUrl;
|
|
} else {
|
|
icon = await (0,_common__WEBPACK_IMPORTED_MODULE_1__.validateExtensionResource)(extension, opts.iconUrl);
|
|
}
|
|
|
|
if (!icon) {
|
|
throw new Error('Invalid iconUrl');
|
|
}
|
|
} // TODO: buttons, template types
|
|
|
|
|
|
const notification = new electron__WEBPACK_IMPORTED_MODULE_0__.Notification({
|
|
title: opts.title,
|
|
subtitle: electron__WEBPACK_IMPORTED_MODULE_0__.app.name,
|
|
body: getBody(opts),
|
|
silent: opts.silent,
|
|
icon,
|
|
urgency: getUrgency(opts.priority),
|
|
timeoutType: opts.requireInteraction ? 'never' : 'default'
|
|
});
|
|
this.registry.set(notificationId, notification);
|
|
notification.on('click', () => {
|
|
this.ctx.router.sendEvent(extension.id, 'notifications.onClicked', id);
|
|
});
|
|
notification.once('close', () => {
|
|
const byUser = true; // TODO
|
|
|
|
this.ctx.router.sendEvent(extension.id, 'notifications.onClosed', id, byUser);
|
|
this.registry.delete(notificationId);
|
|
});
|
|
notification.show();
|
|
return id;
|
|
};
|
|
|
|
this.getAll = ({
|
|
extension
|
|
}) => {
|
|
return Array.from(this.registry.keys()).filter(key => key.startsWith(extension.id)).map(stripScopeFromIdentifier);
|
|
};
|
|
|
|
this.getPermissionLevel = event => {
|
|
return electron__WEBPACK_IMPORTED_MODULE_0__.Notification.isSupported() ? 'granted' : 'denied';
|
|
};
|
|
|
|
this.update = ({
|
|
extension
|
|
}, id, opts) => {
|
|
const notificationId = createScopedIdentifier(extension, id);
|
|
const notification = this.registry.get(notificationId);
|
|
|
|
if (!notification) {
|
|
return false;
|
|
} // TODO: remaining opts
|
|
|
|
|
|
if (opts.priority) notification.urgency = getUrgency(opts.priority);
|
|
if (opts.silent) notification.silent = opts.silent;
|
|
};
|
|
|
|
const handle = this.ctx.router.apiHandler();
|
|
handle('notifications.clear', this.clear);
|
|
handle('notifications.create', this.create);
|
|
handle('notifications.getAll', this.getAll);
|
|
handle('notifications.getPermissionLevel', this.getPermissionLevel);
|
|
handle('notifications.update', this.update);
|
|
this.ctx.session.on('extension-unloaded', (event, extension) => {
|
|
for (const [key, notification] of this.registry) {
|
|
if (key.startsWith(extension.id)) {
|
|
notification.close();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/browser/api/runtime.ts":
|
|
/*!************************************!*\
|
|
!*** ./src/browser/api/runtime.ts ***!
|
|
\************************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "RuntimeAPI": () => (/* binding */ RuntimeAPI)
|
|
/* harmony export */ });
|
|
/* harmony import */ var events__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! events */ "events");
|
|
/* harmony import */ var events__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(events__WEBPACK_IMPORTED_MODULE_0__);
|
|
/* harmony import */ var _common__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./common */ "./src/browser/api/common.ts");
|
|
|
|
|
|
class RuntimeAPI extends events__WEBPACK_IMPORTED_MODULE_0__.EventEmitter {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
|
|
this.openOptionsPage = async event => {
|
|
// TODO: options page shouldn't appear in Tabs API
|
|
// https://developer.chrome.com/extensions/options#tabs-api
|
|
const extension = event.extension;
|
|
const manifest = (0,_common__WEBPACK_IMPORTED_MODULE_1__.getExtensionManifest)(extension);
|
|
|
|
if (manifest.options_ui) {
|
|
// Embedded option not support (!options_ui.open_in_new_tab)
|
|
const url = `chrome-extension://${extension.id}/${manifest.options_ui.page}`;
|
|
await this.ctx.store.createTab({
|
|
url,
|
|
active: true
|
|
}, event);
|
|
} else if (manifest.options_page) {
|
|
const url = `chrome-extension://${extension.id}/${manifest.options_page}`;
|
|
await this.ctx.store.createTab({
|
|
url,
|
|
active: true
|
|
}, event);
|
|
}
|
|
};
|
|
|
|
const handle = this.ctx.router.apiHandler();
|
|
handle('runtime.openOptionsPage', this.openOptionsPage);
|
|
}
|
|
|
|
}
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/browser/api/tabs.ts":
|
|
/*!*********************************!*\
|
|
!*** ./src/browser/api/tabs.ts ***!
|
|
\*********************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "TabsAPI": () => (/* binding */ TabsAPI)
|
|
/* harmony export */ });
|
|
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! node:path */ "node:path");
|
|
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(node_path__WEBPACK_IMPORTED_MODULE_0__);
|
|
/* harmony import */ var node_url__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! node:url */ "node:url");
|
|
/* harmony import */ var node_url__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(node_url__WEBPACK_IMPORTED_MODULE_1__);
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! electron */ "electron");
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(electron__WEBPACK_IMPORTED_MODULE_2__);
|
|
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../utils */ "./src/browser/utils.ts");
|
|
/* harmony import */ var _windows__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./windows */ "./src/browser/api/windows.ts");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const debug = __webpack_require__(/*! debug */ "./node_modules/debug/src/index.js")('electron-chrome-extensions:tabs');
|
|
|
|
class TabsAPI {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
const handle = this.ctx.router.apiHandler();
|
|
handle('tabs.get', this.get.bind(this));
|
|
handle('tabs.getAllInWindow', this.getAllInWindow.bind(this));
|
|
handle('tabs.getCurrent', this.getCurrent.bind(this));
|
|
handle('tabs.create', this.create.bind(this));
|
|
handle('tabs.insertCSS', this.insertCSS.bind(this));
|
|
handle('tabs.query', this.query.bind(this));
|
|
handle('tabs.reload', this.reload.bind(this));
|
|
handle('tabs.update', this.update.bind(this));
|
|
handle('tabs.remove', this.remove.bind(this));
|
|
handle('tabs.goForward', this.goForward.bind(this));
|
|
handle('tabs.goBack', this.goBack.bind(this));
|
|
this.ctx.store.on('tab-added', this.observeTab.bind(this));
|
|
}
|
|
|
|
observeTab(tab) {
|
|
const tabId = tab.id;
|
|
const updateEvents = ['page-title-updated', // title
|
|
'did-start-loading', // status
|
|
'did-stop-loading', // status
|
|
'media-started-playing', // audible
|
|
'media-paused', // audible
|
|
'did-start-navigation', // url
|
|
'did-redirect-navigation', // url
|
|
'did-navigate-in-page' // url
|
|
];
|
|
|
|
const updateHandler = () => {
|
|
this.onUpdated(tabId);
|
|
};
|
|
|
|
updateEvents.forEach(eventName => {
|
|
tab.on(eventName, updateHandler);
|
|
});
|
|
|
|
const faviconHandler = (event, favicons) => {
|
|
;
|
|
tab.favicon = favicons[0];
|
|
this.onUpdated(tabId);
|
|
};
|
|
|
|
tab.on('page-favicon-updated', faviconHandler);
|
|
tab.once('destroyed', () => {
|
|
updateEvents.forEach(eventName => {
|
|
tab.off(eventName, updateHandler);
|
|
});
|
|
tab.off('page-favicon-updated', faviconHandler);
|
|
this.ctx.store.removeTab(tab);
|
|
this.onRemoved(tabId);
|
|
});
|
|
this.onCreated(tabId);
|
|
this.onActivated(tabId);
|
|
debug(`Observing tab[${tabId}][${tab.getType()}] ${tab.getURL()}`);
|
|
}
|
|
|
|
createTabDetails(tab) {
|
|
var _win;
|
|
|
|
const tabId = tab.id;
|
|
const activeTab = this.ctx.store.getActiveTabFromWebContents(tab);
|
|
let win = this.ctx.store.tabToWindow.get(tab);
|
|
if ((_win = win) !== null && _win !== void 0 && _win.isDestroyed()) win = undefined;
|
|
const [width = 0, height = 0] = win ? win.getSize() : [];
|
|
const details = {
|
|
active: (activeTab === null || activeTab === void 0 ? void 0 : activeTab.id) === tabId,
|
|
audible: tab.isCurrentlyAudible(),
|
|
autoDiscardable: true,
|
|
discarded: false,
|
|
favIconUrl: tab.favicon || undefined,
|
|
height,
|
|
highlighted: false,
|
|
id: tabId,
|
|
incognito: false,
|
|
index: -1,
|
|
// TODO
|
|
mutedInfo: {
|
|
muted: tab.audioMuted
|
|
},
|
|
pinned: false,
|
|
selected: true,
|
|
status: tab.isLoading() ? 'loading' : 'complete',
|
|
title: tab.getTitle(),
|
|
url: tab.getURL(),
|
|
// TODO: tab.mainFrame.url (Electron 12)
|
|
width,
|
|
windowId: win ? win.id : -1
|
|
};
|
|
|
|
if (typeof this.ctx.store.impl.assignTabDetails === 'function') {
|
|
this.ctx.store.impl.assignTabDetails(details, tab);
|
|
}
|
|
|
|
this.ctx.store.tabDetailsCache.set(tab.id, details);
|
|
return details;
|
|
}
|
|
|
|
getTabDetails(tab) {
|
|
if (this.ctx.store.tabDetailsCache.has(tab.id)) {
|
|
return this.ctx.store.tabDetailsCache.get(tab.id);
|
|
}
|
|
|
|
const details = this.createTabDetails(tab);
|
|
return details;
|
|
}
|
|
|
|
get(event, tabId) {
|
|
const tab = this.ctx.store.getTabById(tabId);
|
|
if (!tab) return {
|
|
id: TabsAPI.TAB_ID_NONE
|
|
};
|
|
return this.getTabDetails(tab);
|
|
}
|
|
|
|
getAllInWindow(event, windowId = TabsAPI.WINDOW_ID_CURRENT) {
|
|
if (windowId === TabsAPI.WINDOW_ID_CURRENT) windowId = this.ctx.store.lastFocusedWindowId;
|
|
const tabs = Array.from(this.ctx.store.tabs).filter(tab => {
|
|
if (tab.isDestroyed()) return false;
|
|
const browserWindow = this.ctx.store.tabToWindow.get(tab);
|
|
if (!browserWindow || browserWindow.isDestroyed()) return;
|
|
return browserWindow.id === windowId;
|
|
});
|
|
return tabs.map(this.getTabDetails.bind(this));
|
|
}
|
|
|
|
getCurrent(event) {
|
|
const tab = this.ctx.store.getActiveTabFromWebContents(event.sender);
|
|
return tab ? this.getTabDetails(tab) : undefined;
|
|
}
|
|
|
|
async create(event, details = {}) {
|
|
let url = details.url; // TODO: add test for this process
|
|
|
|
if (url && event.extension) {
|
|
const urlInfo = (0,node_url__WEBPACK_IMPORTED_MODULE_1__.parse)(url || '');
|
|
const baseURL = (0,_utils__WEBPACK_IMPORTED_MODULE_3__.ensureSuffix)(event.extension.url, '/');
|
|
|
|
if (!urlInfo.protocol && !urlInfo.hostname) {
|
|
url = node_path__WEBPACK_IMPORTED_MODULE_0___default().posix.join(baseURL, url);
|
|
} else if (!urlInfo.hostname) {
|
|
url = node_path__WEBPACK_IMPORTED_MODULE_0___default().posix.join(baseURL, url);
|
|
}
|
|
}
|
|
|
|
const tab = await this.ctx.store.createTab({ ...details,
|
|
url
|
|
}, event);
|
|
if (!tab) return;
|
|
const tabDetails = this.getTabDetails(tab);
|
|
|
|
if (details.active) {
|
|
queueMicrotask(() => this.onActivated(tab.id));
|
|
}
|
|
|
|
return tabDetails;
|
|
}
|
|
|
|
insertCSS(event, tabId, details) {
|
|
const tab = this.ctx.store.getTabById(tabId);
|
|
if (!tab) return; // TODO: move to webFrame in renderer?
|
|
|
|
if (details.code) {
|
|
tab.insertCSS(details.code);
|
|
}
|
|
}
|
|
|
|
async query(event, info = {}) {
|
|
const isSet = value => typeof value !== 'undefined';
|
|
|
|
const lastFocusedWindow = this.ctx.store.getLastFocusedWindow() || null;
|
|
const currentWin = await this.ctx.store.windowsGetCurrent(event);
|
|
const filteredTabs = Array.from(this.ctx.store.tabs).map(this.getTabDetails.bind(this)).filter(tab => {
|
|
var _tab$mutedInfo;
|
|
|
|
if (!tab) return false;
|
|
if (isSet(info.active) && info.active !== tab.active) return false;
|
|
if (isSet(info.pinned) && info.pinned !== tab.pinned) return false;
|
|
if (isSet(info.audible) && info.audible !== tab.audible) return false;
|
|
if (isSet(info.muted) && info.muted !== ((_tab$mutedInfo = tab.mutedInfo) === null || _tab$mutedInfo === void 0 ? void 0 : _tab$mutedInfo.muted)) return false;
|
|
if (isSet(info.highlighted) && info.highlighted !== tab.highlighted) return false;
|
|
if (isSet(info.discarded) && info.discarded !== tab.discarded) return false;
|
|
if (isSet(info.autoDiscardable) && info.autoDiscardable !== tab.autoDiscardable) return false;
|
|
|
|
if (isSet(info.currentWindow)) {
|
|
if (info.currentWindow && !currentWin) return false;
|
|
if (info.currentWindow && (currentWin === null || currentWin === void 0 ? void 0 : currentWin.id) !== tab.windowId) return false;
|
|
}
|
|
|
|
if (isSet(info.lastFocusedWindow)) {
|
|
if (info.lastFocusedWindow && !lastFocusedWindow) return false;
|
|
if (info.lastFocusedWindow && (lastFocusedWindow === null || lastFocusedWindow === void 0 ? void 0 : lastFocusedWindow.id) !== tab.windowId) return false;
|
|
}
|
|
|
|
if (isSet(info.status) && info.status !== tab.status) return false;
|
|
if (isSet(info.title) && info.title !== tab.title) return false; // TODO: pattern match
|
|
|
|
if (isSet(info.url) && info.url !== tab.url) return false; // TODO: match URL pattern
|
|
|
|
if (isSet(info.windowId)) {
|
|
if (info.windowId === TabsAPI.WINDOW_ID_CURRENT) {
|
|
if (!currentWin || (currentWin === null || currentWin === void 0 ? void 0 : currentWin.id) !== tab.windowId) return false;
|
|
} else if (info.windowId !== tab.windowId) {
|
|
return false;
|
|
}
|
|
} // if (isSet(info.windowType) && info.windowType !== tab.windowType) return false
|
|
// if (isSet(info.index) && info.index !== tab.index) return false
|
|
|
|
|
|
return true;
|
|
}).map((tab, index) => {
|
|
if (tab) {
|
|
tab.index = index;
|
|
}
|
|
|
|
return tab;
|
|
});
|
|
return filteredTabs;
|
|
}
|
|
|
|
reload(event, arg1, arg2) {
|
|
const tabId = typeof arg1 === 'number' ? arg1 : undefined;
|
|
const reloadProperties = typeof arg1 === 'object' ? arg1 : typeof arg2 === 'object' ? arg2 : {};
|
|
const tab = tabId ? this.ctx.store.getTabById(tabId) : this.ctx.store.getActiveTabFromWebContents(event.sender);
|
|
if (!tab) return;
|
|
|
|
if (reloadProperties !== null && reloadProperties !== void 0 && reloadProperties.bypassCache) {
|
|
tab.reloadIgnoringCache();
|
|
} else {
|
|
tab.reload();
|
|
}
|
|
}
|
|
|
|
async update(event, arg1, arg2) {
|
|
let tabId = typeof arg1 === 'number' ? arg1 : undefined;
|
|
const updateProperties = (typeof arg1 === 'object' ? arg1 : arg2) || {};
|
|
const tab = tabId ? this.ctx.store.getTabById(tabId) : this.ctx.store.getActiveTabFromWebContents(event.sender);
|
|
if (!tab) return;
|
|
tabId = tab.id;
|
|
const props = updateProperties; // TODO: validate URL, prevent 'javascript:'
|
|
|
|
if (props.url) await tab.loadURL(props.url);
|
|
if (typeof props.muted === 'boolean') tab.setAudioMuted(props.muted);
|
|
if (props.active) this.onActivated(tabId);
|
|
this.onUpdated(tabId);
|
|
return this.createTabDetails(tab);
|
|
}
|
|
|
|
remove(event, id) {
|
|
const ids = Array.isArray(id) ? id : [id];
|
|
ids.forEach(tabId => {
|
|
const tab = this.ctx.store.getTabById(tabId);
|
|
if (tab) this.ctx.store.removeTab(tab);
|
|
this.onRemoved(tabId);
|
|
});
|
|
}
|
|
|
|
goForward(event, arg1) {
|
|
const tabId = typeof arg1 === 'number' ? arg1 : undefined;
|
|
const tab = tabId ? this.ctx.store.getTabById(tabId) : this.ctx.store.getActiveTabFromWebContents(event.sender);
|
|
if (!tab) return;
|
|
tab.goForward();
|
|
}
|
|
|
|
goBack(event, arg1) {
|
|
const tabId = typeof arg1 === 'number' ? arg1 : undefined;
|
|
const tab = tabId ? this.ctx.store.getTabById(tabId) : this.ctx.store.getActiveTabFromWebContents(event.sender);
|
|
if (!tab) return;
|
|
tab.goBack();
|
|
}
|
|
|
|
onCreated(tabId) {
|
|
const tab = this.ctx.store.getTabById(tabId);
|
|
if (!tab) return;
|
|
const tabDetails = this.getTabDetails(tab);
|
|
this.ctx.router.broadcastEvent('tabs.onCreated', tabDetails);
|
|
}
|
|
|
|
onUpdated(tabId) {
|
|
const tab = this.ctx.store.getTabById(tabId);
|
|
if (!tab) return;
|
|
let prevDetails;
|
|
|
|
if (this.ctx.store.tabDetailsCache.has(tab.id)) {
|
|
prevDetails = this.ctx.store.tabDetailsCache.get(tab.id);
|
|
}
|
|
|
|
if (!prevDetails) return;
|
|
const details = this.createTabDetails(tab);
|
|
const compareProps = ['status', 'url', 'pinned', 'audible', 'discarded', 'autoDiscardable', 'mutedInfo', 'favIconUrl', 'title'];
|
|
let didUpdate = false;
|
|
const changeInfo = {};
|
|
|
|
for (const prop of compareProps) {
|
|
if (details[prop] !== prevDetails[prop]) {
|
|
;
|
|
changeInfo[prop] = details[prop];
|
|
didUpdate = true;
|
|
}
|
|
}
|
|
|
|
if (!didUpdate) return;
|
|
this.ctx.router.broadcastEvent('tabs.onUpdated', tab.id, changeInfo, details);
|
|
}
|
|
|
|
onRemoved(tabId) {
|
|
const details = this.ctx.store.tabDetailsCache.has(tabId) ? this.ctx.store.tabDetailsCache.get(tabId) : null;
|
|
this.ctx.store.tabDetailsCache.delete(tabId);
|
|
const windowId = details ? details.windowId : _windows__WEBPACK_IMPORTED_MODULE_4__.WindowsAPI.WINDOW_ID_NONE;
|
|
const win = typeof windowId !== 'undefined' && windowId > -1 ? electron__WEBPACK_IMPORTED_MODULE_2__.BrowserWindow.getAllWindows().find(win => win.id === windowId) : null;
|
|
this.ctx.router.broadcastEvent('tabs.onRemoved', tabId, {
|
|
windowId,
|
|
isWindowClosing: win ? win.isDestroyed() : false
|
|
});
|
|
}
|
|
|
|
onActivated(tabId) {
|
|
const tab = this.ctx.store.getTabById(tabId);
|
|
if (!tab) return;
|
|
const activeTab = this.ctx.store.getActiveTabFromWebContents(tab);
|
|
const activeChanged = (activeTab === null || activeTab === void 0 ? void 0 : activeTab.id) !== tabId;
|
|
if (!activeChanged) return;
|
|
const win = this.ctx.store.tabToWindow.get(tab);
|
|
this.ctx.store.setActiveTab(tab); // invalidate cache since 'active' has changed
|
|
|
|
this.ctx.store.tabDetailsCache.forEach((tabInfo, cacheTabId) => {
|
|
tabInfo.active = tabId === cacheTabId;
|
|
});
|
|
this.ctx.router.broadcastEvent('tabs.onActivated', {
|
|
tabId,
|
|
windowId: win === null || win === void 0 ? void 0 : win.id
|
|
});
|
|
}
|
|
|
|
}
|
|
TabsAPI.TAB_ID_NONE = -1;
|
|
TabsAPI.WINDOW_ID_NONE = -1;
|
|
TabsAPI.WINDOW_ID_CURRENT = -2;
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/browser/api/web-navigation.ts":
|
|
/*!*******************************************!*\
|
|
!*** ./src/browser/api/web-navigation.ts ***!
|
|
\*******************************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "WebNavigationAPI": () => (/* binding */ WebNavigationAPI)
|
|
/* harmony export */ });
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! electron */ "electron");
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(electron__WEBPACK_IMPORTED_MODULE_0__);
|
|
|
|
|
|
const debug = __webpack_require__(/*! debug */ "./node_modules/debug/src/index.js")('electron-chrome-extensions:webNavigation'); // https://github.com/electron/electron/pull/25464
|
|
|
|
|
|
const getFrame = (frameProcessId, frameRoutingId) => {
|
|
return "webFrameMain" in electron__WEBPACK_IMPORTED_MODULE_0__ && electron__WEBPACK_IMPORTED_MODULE_0__.webFrameMain.fromId(frameProcessId, frameRoutingId) || null;
|
|
};
|
|
|
|
const getFrameId = frame => "webFrameMain" in electron__WEBPACK_IMPORTED_MODULE_0__ ? frame === frame.top ? 0 : frame.frameTreeNodeId : -1;
|
|
|
|
const getParentFrameId = frame => {
|
|
const parentFrame = frame === null || frame === void 0 ? void 0 : frame.parent;
|
|
return parentFrame ? getFrameId(parentFrame) : -1;
|
|
};
|
|
|
|
const getFrameDetails = frame => ({
|
|
errorOccurred: false,
|
|
// TODO
|
|
processId: frame.processId,
|
|
frameId: getFrameId(frame),
|
|
parentFrameId: getParentFrameId(frame),
|
|
url: frame.url
|
|
});
|
|
|
|
class WebNavigationAPI {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
|
|
this.sendNavigationEvent = (eventName, details) => {
|
|
debug(`${eventName} [url: ${details.url}]`);
|
|
this.ctx.router.broadcastEvent(`webNavigation.${eventName}`, details);
|
|
};
|
|
|
|
this.onCreatedNavigationTarget = (event, url, isInPlace, isMainFrame, frameProcessId, frameRoutingId) => {
|
|
const frame = getFrame(frameProcessId, frameRoutingId);
|
|
const tab = event.sender;
|
|
const details = {
|
|
sourceTabId: tab.id,
|
|
sourceProcessId: frameProcessId,
|
|
sourceFrameId: getFrameId(frame),
|
|
url,
|
|
tabId: tab.id,
|
|
timeStamp: Date.now()
|
|
};
|
|
this.sendNavigationEvent('onCreatedNavigationTarget', details);
|
|
};
|
|
|
|
this.onBeforeNavigate = (event, url, isInPlace, isMainFrame, frameProcessId, frameRoutingId) => {
|
|
if (isInPlace) return;
|
|
const frame = getFrame(frameProcessId, frameRoutingId);
|
|
const tab = event.sender;
|
|
const details = {
|
|
frameId: getFrameId(frame),
|
|
parentFrameId: getParentFrameId(frame),
|
|
processId: frameProcessId,
|
|
tabId: tab.id,
|
|
timeStamp: Date.now(),
|
|
url
|
|
};
|
|
this.sendNavigationEvent('onBeforeNavigate', details);
|
|
};
|
|
|
|
this.onCommitted = (event, url, httpResponseCode, httpStatusText, isMainFrame, frameProcessId, frameRoutingId) => {
|
|
const frame = getFrame(frameProcessId, frameRoutingId);
|
|
const tab = event.sender;
|
|
const details = {
|
|
frameId: getFrameId(frame),
|
|
parentFrameId: getParentFrameId(frame),
|
|
processId: frameProcessId,
|
|
tabId: tab.id,
|
|
timeStamp: Date.now(),
|
|
url
|
|
};
|
|
this.sendNavigationEvent('onCommitted', details);
|
|
};
|
|
|
|
this.onHistoryStateUpdated = (event, url, isMainFrame, frameProcessId, frameRoutingId) => {
|
|
const frame = getFrame(frameProcessId, frameRoutingId);
|
|
const tab = event.sender;
|
|
const details = {
|
|
transitionType: '',
|
|
// TODO
|
|
transitionQualifiers: [],
|
|
// TODO
|
|
frameId: getFrameId(frame),
|
|
parentFrameId: getParentFrameId(frame),
|
|
processId: frameProcessId,
|
|
tabId: tab.id,
|
|
timeStamp: Date.now(),
|
|
url
|
|
};
|
|
this.sendNavigationEvent('onHistoryStateUpdated', details);
|
|
};
|
|
|
|
this.onDOMContentLoaded = (tab, frame) => {
|
|
const details = {
|
|
frameId: getFrameId(frame),
|
|
parentFrameId: getParentFrameId(frame),
|
|
processId: frame.processId,
|
|
tabId: tab.id,
|
|
timeStamp: Date.now(),
|
|
url: frame.url
|
|
};
|
|
this.sendNavigationEvent('onDOMContentLoaded', details);
|
|
|
|
if (!tab.isLoadingMainFrame()) {
|
|
this.sendNavigationEvent('onCompleted', details);
|
|
}
|
|
};
|
|
|
|
this.onFinishLoad = (event, isMainFrame, frameProcessId, frameRoutingId) => {
|
|
const frame = getFrame(frameProcessId, frameRoutingId);
|
|
const tab = event.sender;
|
|
const url = tab.getURL();
|
|
const details = {
|
|
frameId: getFrameId(frame),
|
|
parentFrameId: getParentFrameId(frame),
|
|
processId: frameProcessId,
|
|
tabId: tab.id,
|
|
timeStamp: Date.now(),
|
|
url
|
|
};
|
|
this.sendNavigationEvent('onCompleted', details);
|
|
};
|
|
|
|
const handle = this.ctx.router.apiHandler();
|
|
handle('webNavigation.getFrame', this.getFrame.bind(this));
|
|
handle('webNavigation.getAllFrames', this.getAllFrames.bind(this));
|
|
this.ctx.store.on('tab-added', this.observeTab.bind(this));
|
|
}
|
|
|
|
observeTab(tab) {
|
|
tab.once('will-navigate', this.onCreatedNavigationTarget);
|
|
tab.on('did-start-navigation', this.onBeforeNavigate);
|
|
tab.on('did-frame-finish-load', this.onFinishLoad);
|
|
tab.on('did-frame-navigate', this.onCommitted);
|
|
tab.on('did-navigate-in-page', this.onHistoryStateUpdated);
|
|
tab.on('frame-created', (e, {
|
|
frame
|
|
}) => {
|
|
if (frame.top === frame) return;
|
|
frame.on('dom-ready', () => {
|
|
this.onDOMContentLoaded(tab, frame);
|
|
});
|
|
}); // Main frame dom-ready event
|
|
|
|
tab.on('dom-ready', () => {
|
|
if ('mainFrame' in tab) {
|
|
this.onDOMContentLoaded(tab, tab.mainFrame);
|
|
}
|
|
});
|
|
}
|
|
|
|
getFrame(event, details) {
|
|
const tab = this.ctx.store.getTabById(details.tabId);
|
|
if (!tab) return null;
|
|
let targetFrame;
|
|
|
|
if (typeof details.frameId === 'number') {
|
|
// https://github.com/electron/electron/pull/25464
|
|
if ('mainFrame' in tab) {
|
|
const mainFrame = tab.mainFrame;
|
|
targetFrame = mainFrame.framesInSubtree.find(frame => {
|
|
const isMainFrame = frame === frame.top;
|
|
return isMainFrame ? details.frameId === 0 : details.frameId === frame.frameTreeNodeId;
|
|
});
|
|
}
|
|
}
|
|
|
|
return targetFrame ? getFrameDetails(targetFrame) : null;
|
|
}
|
|
|
|
getAllFrames(event, details) {
|
|
const tab = this.ctx.store.getTabById(details.tabId);
|
|
if (!tab || !('mainFrame' in tab)) return [];
|
|
return tab.mainFrame.framesInSubtree.map(getFrameDetails);
|
|
}
|
|
|
|
}
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/browser/api/windows.ts":
|
|
/*!************************************!*\
|
|
!*** ./src/browser/api/windows.ts ***!
|
|
\************************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "WindowsAPI": () => (/* binding */ WindowsAPI)
|
|
/* harmony export */ });
|
|
/* harmony import */ var node_url__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! node:url */ "node:url");
|
|
/* harmony import */ var node_url__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(node_url__WEBPACK_IMPORTED_MODULE_0__);
|
|
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../utils */ "./src/browser/utils.ts");
|
|
|
|
|
|
|
|
const debug = __webpack_require__(/*! debug */ "./node_modules/debug/src/index.js")('electron-chrome-extensions:windows');
|
|
|
|
const getWindowState = win => {
|
|
if (win.isMaximized()) return 'maximized';
|
|
if (win.isMinimized()) return 'minimized';
|
|
if (win.isFullScreen()) return 'fullscreen';
|
|
return 'normal';
|
|
};
|
|
|
|
class WindowsAPI {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
const handle = this.ctx.router.apiHandler();
|
|
handle('windows.get', this.get.bind(this));
|
|
handle('windows.getCurrent', this.getCurrent.bind(this));
|
|
handle('windows.getLastFocused', this.getLastFocused.bind(this));
|
|
handle('windows.getAll', this.getAll.bind(this));
|
|
handle('windows.create', this.create.bind(this));
|
|
handle('windows.update', this.update.bind(this));
|
|
handle('windows.remove', this.remove.bind(this));
|
|
this.ctx.store.on('window-added', this.observeWindow.bind(this));
|
|
}
|
|
|
|
observeWindow(window) {
|
|
if (!window || window.isDestroyed()) {
|
|
debug('observeWindow: window is destroyed');
|
|
return;
|
|
}
|
|
|
|
const windowId = window.id;
|
|
|
|
const onFocus = () => {
|
|
this.onFocusChanged(windowId);
|
|
};
|
|
|
|
const onClosed = () => {
|
|
this.ctx.store.windowDetailsCache.delete(windowId);
|
|
this.ctx.store.removeWindow(window);
|
|
this.onRemoved(windowId);
|
|
};
|
|
|
|
const onRestoreOrMove = () => {
|
|
const details = this.getWindowDetails(window);
|
|
details.state = 'normal';
|
|
this.ctx.store.windowDetailsCache.set(windowId, details);
|
|
};
|
|
|
|
const onMinimize = () => {
|
|
const details = this.getWindowDetails(window);
|
|
details.state = 'minimized';
|
|
this.ctx.store.windowDetailsCache.set(windowId, details);
|
|
};
|
|
|
|
const onMaximize = () => {
|
|
const details = this.getWindowDetails(window);
|
|
details.state = 'maximized';
|
|
this.ctx.store.windowDetailsCache.set(windowId, details);
|
|
};
|
|
|
|
const onEnterFullscreen = () => {
|
|
const details = this.getWindowDetails(window);
|
|
details.state = 'fullscreen';
|
|
this.ctx.store.windowDetailsCache.set(windowId, details);
|
|
};
|
|
|
|
const onLeaveFullscreen = () => {
|
|
const details = this.getWindowDetails(window);
|
|
details.state = getWindowState(window);
|
|
this.ctx.store.windowDetailsCache.set(windowId, details);
|
|
};
|
|
|
|
window.on('focus', onFocus);
|
|
window.once('closed', onClosed);
|
|
window.on('restore', onRestoreOrMove);
|
|
|
|
if (process.platform === 'win32') {
|
|
window.on('moved', onRestoreOrMove);
|
|
}
|
|
|
|
window.on('minimize', onMinimize);
|
|
window.on('maximize', onMaximize);
|
|
window.on('enter-full-screen', onEnterFullscreen);
|
|
window.on('enter-html-full-screen', onEnterFullscreen);
|
|
window.on('leave-full-screen', onLeaveFullscreen);
|
|
window.on('leave-html-full-screen', onLeaveFullscreen);
|
|
this.onCreated(windowId);
|
|
debug(`Observing window[${windowId}]`);
|
|
}
|
|
|
|
createWindowDetails(win) {
|
|
const details = {
|
|
id: win.id,
|
|
focused: win.isFocused(),
|
|
top: win.getPosition()[1],
|
|
left: win.getPosition()[0],
|
|
width: win.getSize()[0],
|
|
height: win.getSize()[1],
|
|
tabs: Array.from(this.ctx.store.tabs).filter(tab => {
|
|
const ownerWindow = this.ctx.store.tabToWindow.get(tab);
|
|
return ownerWindow !== null && ownerWindow !== void 0 && ownerWindow.isDestroyed() ? false : (ownerWindow === null || ownerWindow === void 0 ? void 0 : ownerWindow.id) === win.id;
|
|
}).map(tab => this.ctx.store.tabDetailsCache.get(tab.id)).filter(Boolean),
|
|
incognito: !win.webContents.session.isPersistent(),
|
|
type: 'normal',
|
|
// TODO
|
|
state: getWindowState(win),
|
|
alwaysOnTop: win.isAlwaysOnTop(),
|
|
sessionId: 'default' // TODO
|
|
|
|
};
|
|
this.ctx.store.windowDetailsCache.set(win.id, details);
|
|
return details;
|
|
}
|
|
|
|
getWindowDetails(win) {
|
|
if (this.ctx.store.windowDetailsCache.has(win.id)) {
|
|
return this.ctx.store.windowDetailsCache.get(win.id);
|
|
}
|
|
|
|
const details = this.createWindowDetails(win);
|
|
return details;
|
|
}
|
|
|
|
async getWindowFromId(event, id) {
|
|
if (id === WindowsAPI.WINDOW_ID_CURRENT) {
|
|
return (await this.ctx.store.windowsGetCurrent(event)) || this.ctx.store.getLastFocusedWindow();
|
|
} else {
|
|
return this.ctx.store.getWindowById(event, id);
|
|
}
|
|
}
|
|
|
|
async get(event, windowId) {
|
|
let win;
|
|
|
|
if (windowId === WindowsAPI.WINDOW_ID_CURRENT) {
|
|
win = (await this.ctx.store.windowsGetCurrent(event)) || this.ctx.store.getLastFocusedWindow() || null;
|
|
} else {
|
|
win = (await this.getWindowFromId(event, windowId)) || null;
|
|
}
|
|
|
|
if (!win) return {
|
|
id: WindowsAPI.WINDOW_ID_NONE
|
|
};
|
|
return this.getWindowDetails(win);
|
|
}
|
|
|
|
getLastFocused(event) {
|
|
const win = this.ctx.store.getLastFocusedWindow();
|
|
return win ? this.getWindowDetails(win) : null;
|
|
} // not same with `getLastFocused`, `getCurrent` means the host window where javascript executing
|
|
|
|
|
|
async getCurrent(event) {
|
|
const win = await this.ctx.store.windowsGetCurrent(event);
|
|
return win ? this.getWindowDetails(win) : null;
|
|
}
|
|
|
|
getAll(event) {
|
|
return Array.from(this.ctx.store.windows).map(this.getWindowDetails.bind(this));
|
|
}
|
|
|
|
async create(event, details) {
|
|
const baseURL = (0,_utils__WEBPACK_IMPORTED_MODULE_1__.ensureSuffix)(event.extension.url, '/');
|
|
let url = Array.isArray(details.url) ? details.url[0] : details.url || '';
|
|
const urlInfo = (0,node_url__WEBPACK_IMPORTED_MODULE_0__.parse)(url);
|
|
|
|
if (!urlInfo.protocol && !urlInfo.hostname) {
|
|
url = baseURL + (0,_utils__WEBPACK_IMPORTED_MODULE_1__.unPrefix)(url, ['./', '/']);
|
|
} else if (!urlInfo.hostname) {
|
|
url = baseURL + (0,_utils__WEBPACK_IMPORTED_MODULE_1__.unPrefix)(url, ['./', '/']);
|
|
}
|
|
|
|
const win = await this.ctx.store.createWindow(event, { ...details,
|
|
url
|
|
});
|
|
return this.getWindowDetails(win);
|
|
}
|
|
|
|
async update(event, windowId, updateProperties = {}) {
|
|
const win = await this.getWindowFromId(event, windowId);
|
|
if (!win) return;
|
|
const props = updateProperties;
|
|
|
|
if (props.state) {
|
|
switch (props.state) {
|
|
case 'maximized':
|
|
win.maximize();
|
|
break;
|
|
|
|
case 'minimized':
|
|
win.minimize();
|
|
break;
|
|
|
|
case 'fullscreen':
|
|
if (win.fullScreenable) win.setFullScreen(true);else {
|
|
console.warn('window is not fullScreenable');
|
|
}
|
|
break;
|
|
|
|
case 'normal':
|
|
{
|
|
if (win.isFullScreen()) {
|
|
win.setFullScreen(false);
|
|
} else if (win.isMinimized() || win.isMaximized()) {
|
|
win.restore();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.createWindowDetails(win);
|
|
}
|
|
|
|
async remove(event, windowId = WindowsAPI.WINDOW_ID_CURRENT) {
|
|
const win = await this.getWindowFromId(event, windowId);
|
|
if (!win) return;
|
|
const removedWindowId = win.id;
|
|
await this.ctx.store.removeWindow(win);
|
|
this.onRemoved(removedWindowId);
|
|
}
|
|
|
|
onCreated(windowId) {
|
|
const window = this.ctx.store.findWindowById(windowId);
|
|
if (!window) return;
|
|
const windowDetails = this.getWindowDetails(window);
|
|
this.ctx.router.broadcastEvent('windows.onCreated', windowDetails);
|
|
}
|
|
|
|
onRemoved(windowId) {
|
|
this.ctx.router.broadcastEvent('windows.onRemoved', windowId);
|
|
}
|
|
|
|
onFocusChanged(windowId) {
|
|
if (this.ctx.store.lastFocusedWindowId === windowId) return;
|
|
this.ctx.store.lastFocusedWindowId = windowId;
|
|
this.ctx.router.broadcastEvent('windows.onFocusChanged', windowId);
|
|
}
|
|
|
|
}
|
|
WindowsAPI.WINDOW_ID_NONE = -1;
|
|
WindowsAPI.WINDOW_ID_CURRENT = -2;
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/browser/index.ts":
|
|
/*!******************************!*\
|
|
!*** ./src/browser/index.ts ***!
|
|
\******************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "ElectronChromeExtensions": () => (/* binding */ ElectronChromeExtensions),
|
|
/* harmony export */ "Extensions": () => (/* binding */ Extensions)
|
|
/* harmony export */ });
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! electron */ "electron");
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(electron__WEBPACK_IMPORTED_MODULE_0__);
|
|
/* harmony import */ var events__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! events */ "events");
|
|
/* harmony import */ var events__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(events__WEBPACK_IMPORTED_MODULE_1__);
|
|
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! path */ "path");
|
|
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__);
|
|
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! fs */ "fs");
|
|
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_3__);
|
|
/* harmony import */ var _api_browser_action__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./api/browser-action */ "./src/browser/api/browser-action.ts");
|
|
/* harmony import */ var _api_tabs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./api/tabs */ "./src/browser/api/tabs.ts");
|
|
/* harmony import */ var _api_windows__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./api/windows */ "./src/browser/api/windows.ts");
|
|
/* harmony import */ var _api_web_navigation__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./api/web-navigation */ "./src/browser/api/web-navigation.ts");
|
|
/* harmony import */ var _store__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./store */ "./src/browser/store.ts");
|
|
/* harmony import */ var _api_context_menus__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./api/context-menus */ "./src/browser/api/context-menus.ts");
|
|
/* harmony import */ var _api_runtime__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./api/runtime */ "./src/browser/api/runtime.ts");
|
|
/* harmony import */ var _api_cookies__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./api/cookies */ "./src/browser/api/cookies.ts");
|
|
/* harmony import */ var _api_notifications__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./api/notifications */ "./src/browser/api/notifications.ts");
|
|
/* harmony import */ var _api_commands__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./api/commands */ "./src/browser/api/commands.ts");
|
|
/* harmony import */ var _router__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./router */ "./src/browser/router.ts");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const sessionMap = new WeakMap();
|
|
/**
|
|
* Provides an implementation of various Chrome extension APIs to a session.
|
|
*/
|
|
|
|
class ElectronChromeExtensions extends events__WEBPACK_IMPORTED_MODULE_1__.EventEmitter {
|
|
/** Retrieve an instance of this class associated with the given session. */
|
|
static fromSession(session) {
|
|
return sessionMap.get(session);
|
|
}
|
|
|
|
constructor(opts) {
|
|
super();
|
|
this.ctx = void 0;
|
|
this.modulePath = void 0;
|
|
this.preloadPath = void 0;
|
|
this.api = void 0;
|
|
const {
|
|
session = electron__WEBPACK_IMPORTED_MODULE_0__.session.defaultSession,
|
|
modulePath,
|
|
preloadPath,
|
|
...impl
|
|
} = opts || {};
|
|
|
|
if (sessionMap.has(session)) {
|
|
throw new Error(`Extensions instance already exists for the given session`);
|
|
}
|
|
|
|
sessionMap.set(session, this);
|
|
const router = new _router__WEBPACK_IMPORTED_MODULE_14__.ExtensionRouter(session);
|
|
const store = new _store__WEBPACK_IMPORTED_MODULE_8__.ExtensionStore(impl);
|
|
this.ctx = {
|
|
emit: this.emit.bind(this),
|
|
router,
|
|
session,
|
|
store
|
|
};
|
|
this.modulePath = modulePath || path__WEBPACK_IMPORTED_MODULE_2___default().join(__dirname, '..');
|
|
this.preloadPath = preloadPath || '';
|
|
this.api = {
|
|
browserAction: new _api_browser_action__WEBPACK_IMPORTED_MODULE_4__.BrowserActionAPI(this.ctx),
|
|
contextMenus: new _api_context_menus__WEBPACK_IMPORTED_MODULE_9__.ContextMenusAPI(this.ctx),
|
|
commands: new _api_commands__WEBPACK_IMPORTED_MODULE_13__.CommandsAPI(this.ctx),
|
|
cookies: new _api_cookies__WEBPACK_IMPORTED_MODULE_11__.CookiesAPI(this.ctx),
|
|
notifications: new _api_notifications__WEBPACK_IMPORTED_MODULE_12__.NotificationsAPI(this.ctx),
|
|
runtime: new _api_runtime__WEBPACK_IMPORTED_MODULE_10__.RuntimeAPI(this.ctx),
|
|
tabs: new _api_tabs__WEBPACK_IMPORTED_MODULE_5__.TabsAPI(this.ctx),
|
|
webNavigation: new _api_web_navigation__WEBPACK_IMPORTED_MODULE_7__.WebNavigationAPI(this.ctx),
|
|
windows: new _api_windows__WEBPACK_IMPORTED_MODULE_6__.WindowsAPI(this.ctx)
|
|
};
|
|
this.prependPreload();
|
|
}
|
|
|
|
async prependPreload() {
|
|
const {
|
|
session
|
|
} = this.ctx;
|
|
let preloads = session.getPreloads();
|
|
const preloadPath = this.preloadPath || path__WEBPACK_IMPORTED_MODULE_2___default().join(this.modulePath, 'dist/preload.js');
|
|
const preloadIndex = preloads.indexOf(preloadPath);
|
|
|
|
if (preloadIndex > -1) {
|
|
preloads.splice(preloadIndex, 1);
|
|
}
|
|
|
|
preloads = [preloadPath, ...preloads];
|
|
session.setPreloads(preloads);
|
|
let preloadExists = false;
|
|
|
|
try {
|
|
const stat = await fs__WEBPACK_IMPORTED_MODULE_3__.promises.stat(preloadPath);
|
|
preloadExists = stat.isFile();
|
|
} catch {}
|
|
|
|
if (!preloadExists) {
|
|
console.error(`Unable to access electron-chrome-extensions preload file (${preloadPath}). Consider configuring the 'modulePath' constructor option.`);
|
|
}
|
|
}
|
|
/** Add webContents to be tracked as a tab. */
|
|
|
|
|
|
addTab(tab, window) {
|
|
this.ctx.store.addTab(tab, window);
|
|
}
|
|
/** Add window created outside/before extensions initialized */
|
|
|
|
|
|
addWindow(window) {
|
|
this.ctx.store.addWindow(window);
|
|
}
|
|
/** Notify extension system that the active tab has changed. */
|
|
|
|
|
|
selectTab(tab) {
|
|
if (this.ctx.store.tabs.has(tab)) {
|
|
this.api.tabs.onActivated(tab.id);
|
|
}
|
|
}
|
|
/**
|
|
* Add webContents to be tracked as an extension host which will receive
|
|
* extension events when a chrome-extension:// resource is loaded.
|
|
*
|
|
* This is usually reserved for extension background pages and popups, but
|
|
* can also be used in other special cases.
|
|
*
|
|
* @deprecated Extension hosts are now tracked lazily when they send
|
|
* extension IPCs to the main process.
|
|
*/
|
|
|
|
|
|
addExtensionHost(host) {
|
|
console.warn('ElectronChromeExtensions.addExtensionHost() is deprecated');
|
|
}
|
|
/**
|
|
* Get collection of menu items managed by the `chrome.contextMenus` API.
|
|
* @see https://developer.chrome.com/extensions/contextMenus
|
|
*/
|
|
|
|
|
|
getContextMenuItems(webContents, params) {
|
|
return this.api.contextMenus.buildMenuItemsForParams(webContents, params);
|
|
}
|
|
/**
|
|
* Add extensions to be visible as an extension action button.
|
|
*
|
|
* @deprecated Not needed in Electron >=12.
|
|
*/
|
|
|
|
|
|
addExtension(extension) {
|
|
console.warn('ElectronChromeExtensions.addExtension() is deprecated');
|
|
this.api.browserAction.processExtension(extension);
|
|
}
|
|
/**
|
|
* Remove extensions from the list of visible extension action buttons.
|
|
*
|
|
* @deprecated Not needed in Electron >=12.
|
|
*/
|
|
|
|
|
|
removeExtension(extension) {
|
|
console.warn('ElectronChromeExtensions.removeExtension() is deprecated');
|
|
this.api.browserAction.removeActions(extension.id);
|
|
}
|
|
|
|
}
|
|
/**
|
|
* @deprecated Use `ElectronChromeExtensions` instead.
|
|
*/
|
|
|
|
const Extensions = ElectronChromeExtensions;
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/browser/popup.ts":
|
|
/*!******************************!*\
|
|
!*** ./src/browser/popup.ts ***!
|
|
\******************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "PopupView": () => (/* binding */ PopupView)
|
|
/* harmony export */ });
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! electron */ "electron");
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(electron__WEBPACK_IMPORTED_MODULE_0__);
|
|
|
|
|
|
const debug = __webpack_require__(/*! debug */ "./node_modules/debug/src/index.js")('electron-chrome-extensions:popup');
|
|
|
|
class PopupView {
|
|
/** Preferred size changes are only received in Electron v12+ */
|
|
constructor(opts) {
|
|
this.browserWindow = void 0;
|
|
this.parent = void 0;
|
|
this.extensionId = void 0;
|
|
this.anchorRect = void 0;
|
|
this.destroyed = false;
|
|
this.usingPreferredSize = false;
|
|
this.readyPromise = void 0;
|
|
|
|
this.destroy = () => {
|
|
if (this.destroyed) return;
|
|
this.destroyed = true;
|
|
debug(`destroying ${this.extensionId}`);
|
|
|
|
if (this.parent) {
|
|
if (!this.parent.isDestroyed()) {
|
|
this.parent.off('closed', this.destroy);
|
|
}
|
|
|
|
this.parent = undefined;
|
|
}
|
|
|
|
if (this.browserWindow) {
|
|
if (!this.browserWindow.isDestroyed()) {
|
|
const {
|
|
webContents
|
|
} = this.browserWindow;
|
|
|
|
if (!webContents.isDestroyed() && webContents.isDevToolsOpened()) {
|
|
webContents.closeDevTools();
|
|
}
|
|
|
|
this.browserWindow.off('closed', this.destroy);
|
|
this.browserWindow.destroy();
|
|
}
|
|
|
|
this.browserWindow = undefined;
|
|
}
|
|
};
|
|
|
|
this.maybeClose = () => {
|
|
var _this$browserWindow, _this$browserWindow2;
|
|
|
|
// Keep open if webContents is being inspected
|
|
if (!((_this$browserWindow = this.browserWindow) !== null && _this$browserWindow !== void 0 && _this$browserWindow.isDestroyed()) && (_this$browserWindow2 = this.browserWindow) !== null && _this$browserWindow2 !== void 0 && _this$browserWindow2.webContents.isDevToolsOpened()) {
|
|
debug('preventing close due to DevTools being open');
|
|
return;
|
|
} // For extension popups with a login form, the user may need to access a
|
|
// program outside of the app. Closing the popup would then add
|
|
// inconvenience.
|
|
|
|
|
|
if (!electron__WEBPACK_IMPORTED_MODULE_0__.BrowserWindow.getFocusedWindow()) {
|
|
debug('preventing close due to focus residing outside of the app');
|
|
return;
|
|
}
|
|
|
|
this.destroy();
|
|
};
|
|
|
|
this.updatePreferredSize = (event, size) => {
|
|
debug('updatePreferredSize', size);
|
|
this.usingPreferredSize = true;
|
|
this.setSize(size);
|
|
this.updatePosition();
|
|
};
|
|
|
|
this.parent = opts.parent;
|
|
this.extensionId = opts.extensionId;
|
|
this.anchorRect = opts.anchorRect;
|
|
this.browserWindow = new electron__WEBPACK_IMPORTED_MODULE_0__.BrowserWindow({
|
|
show: false,
|
|
frame: false,
|
|
parent: opts.parent,
|
|
movable: false,
|
|
maximizable: false,
|
|
minimizable: false,
|
|
resizable: false,
|
|
skipTaskbar: true,
|
|
backgroundColor: '#ffffff',
|
|
webPreferences: {
|
|
session: opts.session,
|
|
sandbox: true,
|
|
nodeIntegration: false,
|
|
nodeIntegrationInWorker: false,
|
|
nativeWindowOpen: true,
|
|
worldSafeExecuteJavaScript: true,
|
|
contextIsolation: true,
|
|
...{
|
|
enablePreferredSizeMode: true
|
|
}
|
|
}
|
|
});
|
|
const untypedWebContents = this.browserWindow.webContents;
|
|
untypedWebContents.on('preferred-size-changed', this.updatePreferredSize);
|
|
this.browserWindow.webContents.on('devtools-closed', this.maybeClose);
|
|
this.browserWindow.on('blur', this.maybeClose);
|
|
this.browserWindow.on('closed', this.destroy);
|
|
this.parent.once('closed', this.destroy);
|
|
this.readyPromise = this.load(opts.url);
|
|
}
|
|
|
|
async load(url) {
|
|
const win = this.browserWindow;
|
|
|
|
try {
|
|
await win.webContents.loadURL(url);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
|
|
if (this.destroyed) return;
|
|
|
|
if (!this.usingPreferredSize) {
|
|
// Set large initial size to avoid overflow
|
|
this.setSize({
|
|
width: PopupView.BOUNDS.maxWidth,
|
|
height: PopupView.BOUNDS.maxHeight
|
|
}); // Wait for content and layout to load
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
if (this.destroyed) return;
|
|
await this.queryPreferredSize();
|
|
if (this.destroyed) return;
|
|
}
|
|
|
|
win.show();
|
|
}
|
|
|
|
isDestroyed() {
|
|
return this.destroyed;
|
|
}
|
|
/** Resolves when the popup finishes loading. */
|
|
|
|
|
|
whenReady() {
|
|
return this.readyPromise;
|
|
}
|
|
|
|
setSize(rect) {
|
|
var _this$browserWindow3;
|
|
|
|
if (!this.browserWindow || !this.parent) return;
|
|
const width = Math.floor(Math.min(PopupView.BOUNDS.maxWidth, Math.max(rect.width || 0, PopupView.BOUNDS.minWidth)));
|
|
const height = Math.floor(Math.min(PopupView.BOUNDS.maxHeight, Math.max(rect.height || 0, PopupView.BOUNDS.minHeight)));
|
|
debug(`setSize`, {
|
|
width,
|
|
height
|
|
});
|
|
(_this$browserWindow3 = this.browserWindow) === null || _this$browserWindow3 === void 0 ? void 0 : _this$browserWindow3.setBounds({ ...this.browserWindow.getBounds(),
|
|
width,
|
|
height
|
|
});
|
|
}
|
|
|
|
updatePosition() {
|
|
if (!this.browserWindow || !this.parent) return;
|
|
const winBounds = this.parent.getBounds();
|
|
const viewBounds = this.browserWindow.getBounds(); // TODO: support more orientations than just top-right
|
|
|
|
let x = winBounds.x + this.anchorRect.x + this.anchorRect.width - viewBounds.width;
|
|
let y = winBounds.y + this.anchorRect.y + this.anchorRect.height + PopupView.POSITION_PADDING; // Convert to ints
|
|
|
|
x = Math.floor(x);
|
|
y = Math.floor(y);
|
|
debug(`updatePosition`, {
|
|
x,
|
|
y
|
|
});
|
|
this.browserWindow.setBounds({ ...this.browserWindow.getBounds(),
|
|
x,
|
|
y
|
|
});
|
|
}
|
|
/** Backwards compat for Electron <12 */
|
|
|
|
|
|
async queryPreferredSize() {
|
|
if (this.usingPreferredSize || this.destroyed) return;
|
|
const rect = await this.browserWindow.webContents.executeJavaScript(`((${() => {
|
|
const rect = document.body.getBoundingClientRect();
|
|
return {
|
|
width: rect.width,
|
|
height: rect.height
|
|
};
|
|
}})())`);
|
|
if (this.destroyed) return;
|
|
this.setSize({
|
|
width: rect.width,
|
|
height: rect.height
|
|
});
|
|
this.updatePosition();
|
|
}
|
|
|
|
}
|
|
PopupView.POSITION_PADDING = 5;
|
|
PopupView.BOUNDS = {
|
|
minWidth: 25,
|
|
minHeight: 25,
|
|
maxWidth: 800,
|
|
maxHeight: 600
|
|
};
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/browser/router.ts":
|
|
/*!*******************************!*\
|
|
!*** ./src/browser/router.ts ***!
|
|
\*******************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "ExtensionRouter": () => (/* binding */ ExtensionRouter)
|
|
/* harmony export */ });
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! electron */ "electron");
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(electron__WEBPACK_IMPORTED_MODULE_0__);
|
|
/* harmony import */ var debug__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! debug */ "./node_modules/debug/src/index.js");
|
|
/* harmony import */ var debug__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(debug__WEBPACK_IMPORTED_MODULE_1__);
|
|
/* harmony import */ var _isomorphic_ipc__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../isomorphic/ipc */ "./src/isomorphic/ipc.ts");
|
|
|
|
|
|
// Shorten base64 encoded icons
|
|
|
|
const shortenValues = (k, v) => typeof v === 'string' && v.length > 128 ? v.substr(0, 128) + '...' : v;
|
|
|
|
(debug__WEBPACK_IMPORTED_MODULE_1___default().formatters.r) = value => {
|
|
return value ? JSON.stringify(value, shortenValues, ' ') : value;
|
|
};
|
|
|
|
const debug = debug__WEBPACK_IMPORTED_MODULE_1___default()('electron-chrome-extensions:router');
|
|
const DEFAULT_SESSION = '_self';
|
|
let gRoutingDelegate;
|
|
/**
|
|
* Handles event routing IPCs and delivers them to the observer with the
|
|
* associated session.
|
|
*/
|
|
|
|
class RoutingDelegate {
|
|
static get() {
|
|
return gRoutingDelegate || (gRoutingDelegate = new RoutingDelegate());
|
|
}
|
|
|
|
constructor() {
|
|
this.sessionMap = new WeakMap();
|
|
|
|
this.onRouterMessage = async (event, extensionId, handlerName, ...args) => {
|
|
debug(`received '${handlerName}'`, args);
|
|
const observer = this.sessionMap.get(event.sender.session);
|
|
return observer === null || observer === void 0 ? void 0 : observer.onExtensionMessage(event, extensionId, handlerName, ...args);
|
|
};
|
|
|
|
this.onRemoteMessage = async (event, sessionPartition, handlerName, ...args) => {
|
|
debug(`received remote '${handlerName}' for '${sessionPartition}'`, args);
|
|
const ses = sessionPartition === DEFAULT_SESSION ? event.sender.session : electron__WEBPACK_IMPORTED_MODULE_0__.session.fromPartition(sessionPartition);
|
|
const observer = this.sessionMap.get(ses);
|
|
return observer === null || observer === void 0 ? void 0 : observer.onExtensionMessage(event, undefined, handlerName, ...args);
|
|
};
|
|
|
|
this.onAddListener = (event, extensionId, eventName) => {
|
|
const observer = this.sessionMap.get(event.sender.session);
|
|
const listener = {
|
|
host: event.sender,
|
|
extensionId
|
|
};
|
|
return observer === null || observer === void 0 ? void 0 : observer.addListener(listener, extensionId, eventName);
|
|
};
|
|
|
|
this.onRemoveListener = (event, extensionId, eventName) => {
|
|
const observer = this.sessionMap.get(event.sender.session);
|
|
const listener = {
|
|
host: event.sender,
|
|
extensionId
|
|
};
|
|
return observer === null || observer === void 0 ? void 0 : observer.removeListener(listener, extensionId, eventName);
|
|
};
|
|
|
|
electron__WEBPACK_IMPORTED_MODULE_0__.ipcMain.handle('crx-msg', this.onRouterMessage);
|
|
electron__WEBPACK_IMPORTED_MODULE_0__.ipcMain.handle('crx-msg-remote', this.onRemoteMessage);
|
|
electron__WEBPACK_IMPORTED_MODULE_0__.ipcMain.on('crx-add-listener', this.onAddListener);
|
|
electron__WEBPACK_IMPORTED_MODULE_0__.ipcMain.on('crx-remove-listener', this.onRemoveListener);
|
|
}
|
|
|
|
addObserver(observer) {
|
|
this.sessionMap.set(observer.session, observer);
|
|
}
|
|
|
|
}
|
|
|
|
const eventListenerEquals = eventListener => other => other.host === eventListener.host && other.extensionId === eventListener.extensionId;
|
|
|
|
class ExtensionRouter {
|
|
/**
|
|
* Collection of all extension hosts in the session.
|
|
*
|
|
* Currently the router has no ability to wake up non-persistent background
|
|
* scripts to deliver events. For now we just hold a reference to them to
|
|
* prevent them from being terminated.
|
|
*/
|
|
constructor(session, delegate = RoutingDelegate.get()) {
|
|
this.session = session;
|
|
this.delegate = delegate;
|
|
this.handlers = new Map();
|
|
this.listeners = new Map();
|
|
this.extensionHosts = new Set();
|
|
this.delegate.addObserver(this);
|
|
session.on('extension-unloaded', (event, extension) => {
|
|
this.filterListeners(listener => listener.extensionId !== extension.id);
|
|
});
|
|
electron__WEBPACK_IMPORTED_MODULE_0__.app.on('web-contents-created', (event, webContents) => {
|
|
if (webContents.session === this.session && webContents.getType() === 'backgroundPage') {
|
|
debug(`storing reference to background host [url:'${webContents.getURL()}']`);
|
|
this.extensionHosts.add(webContents);
|
|
}
|
|
});
|
|
}
|
|
|
|
filterListeners(predicate) {
|
|
for (const [eventName, listeners] of this.listeners) {
|
|
const filteredListeners = listeners.filter(predicate);
|
|
const delta = listeners.length - filteredListeners.length;
|
|
|
|
if (filteredListeners.length > 0) {
|
|
this.listeners.set(eventName, filteredListeners);
|
|
} else {
|
|
this.listeners.delete(eventName);
|
|
}
|
|
|
|
if (delta > 0) {
|
|
debug(`removed ${delta} listener(s) for '${eventName}'`);
|
|
}
|
|
}
|
|
}
|
|
|
|
observeListenerHost(host) {
|
|
debug(`observing listener [id:${host.id}, url:'${host.getURL()}']`);
|
|
host.once('destroyed', () => {
|
|
debug(`extension host destroyed [id:${host.id}]`);
|
|
this.filterListeners(listener => listener.host !== host);
|
|
});
|
|
}
|
|
|
|
addListener(listener, extensionId, eventName) {
|
|
const {
|
|
listeners,
|
|
session
|
|
} = this;
|
|
const extension = session.getExtension(extensionId);
|
|
|
|
if (!extension) {
|
|
throw new Error(`extension not registered in session [extensionId:${extensionId}]`);
|
|
}
|
|
|
|
if (!listeners.has(eventName)) {
|
|
listeners.set(eventName, []);
|
|
}
|
|
|
|
const eventListeners = listeners.get(eventName);
|
|
const existingEventListener = eventListeners.find(eventListenerEquals(listener));
|
|
|
|
if (existingEventListener) {
|
|
debug(`ignoring existing '${eventName}' event listener for ${extensionId}`);
|
|
} else {
|
|
debug(`adding '${eventName}' event listener for ${extensionId}`);
|
|
eventListeners.push(listener);
|
|
this.observeListenerHost(listener.host);
|
|
}
|
|
}
|
|
|
|
removeListener(listener, extensionId, eventName) {
|
|
const {
|
|
listeners
|
|
} = this;
|
|
const eventListeners = listeners.get(eventName);
|
|
|
|
if (!eventListeners) {
|
|
console.error(`event listener not registered for '${eventName}'`);
|
|
return;
|
|
}
|
|
|
|
const index = eventListeners.findIndex(eventListenerEquals(listener));
|
|
|
|
if (index >= 0) {
|
|
debug(`removing '${eventName}' event listener for ${extensionId}`);
|
|
eventListeners.splice(index, 1);
|
|
}
|
|
|
|
if (eventListeners.length === 0) {
|
|
listeners.delete(eventName);
|
|
}
|
|
}
|
|
|
|
getHandler(handlerName) {
|
|
const handler = this.handlers.get(handlerName);
|
|
|
|
if (!handler) {
|
|
throw new Error(`${handlerName} is not a registered handler`);
|
|
}
|
|
|
|
return handler;
|
|
}
|
|
|
|
async onExtensionMessage(event, extensionId, handlerName, ...args) {
|
|
const {
|
|
session
|
|
} = this;
|
|
const {
|
|
sender
|
|
} = event;
|
|
const handler = this.getHandler(handlerName);
|
|
|
|
if (sender.session !== session && !handler.allowRemote) {
|
|
throw new Error(`${handlerName} does not support calling from a remote session`);
|
|
}
|
|
|
|
const extension = extensionId ? sender.session.getExtension(extensionId) : undefined;
|
|
|
|
if (!extension && handler.extensionContext) {
|
|
throw new Error(`${handlerName} was sent from an unknown extension context`);
|
|
}
|
|
|
|
const extEvent = {
|
|
sender,
|
|
extension: extension
|
|
};
|
|
const result = await handler.callback(extEvent, ...args);
|
|
debug(`${handlerName} result: %r`, result);
|
|
return result;
|
|
}
|
|
|
|
handle(name, callback, opts) {
|
|
this.handlers.set(name, {
|
|
callback,
|
|
extensionContext: typeof (opts === null || opts === void 0 ? void 0 : opts.extensionContext) === 'boolean' ? opts.extensionContext : true,
|
|
allowRemote: typeof (opts === null || opts === void 0 ? void 0 : opts.allowRemote) === 'boolean' ? opts.allowRemote : false
|
|
});
|
|
}
|
|
/** Returns a callback to register API handlers for the given context. */
|
|
|
|
|
|
apiHandler() {
|
|
return (name, callback, opts) => {
|
|
this.handle(name, callback, opts);
|
|
};
|
|
}
|
|
/**
|
|
* Sends extension event to the host for the given extension ID if it
|
|
* registered a listener for it.
|
|
*/
|
|
|
|
|
|
sendEvent(extensionId, eventName, ...args) {
|
|
const {
|
|
listeners
|
|
} = this;
|
|
let eventListeners = listeners.get(eventName);
|
|
|
|
if (extensionId) {
|
|
var _eventListeners;
|
|
|
|
// TODO: extension permissions check
|
|
debug(`sendEvent '${eventName}' to extension '${extensionId}'`);
|
|
eventListeners = (_eventListeners = eventListeners) === null || _eventListeners === void 0 ? void 0 : _eventListeners.filter(el => el.extensionId === extensionId);
|
|
} else {
|
|
debug(`broadcast event '${eventName}' to all extensions`);
|
|
}
|
|
|
|
if (!eventListeners || eventListeners.length === 0) {
|
|
debug(`ignoring '${eventName}' event with no listeners`);
|
|
return;
|
|
}
|
|
|
|
for (const {
|
|
host
|
|
} of eventListeners) {
|
|
// TODO: may need to wake lazy extension context
|
|
if (host.isDestroyed()) {
|
|
console.error(`Unable to send '${eventName}' to extension host for ${extensionId}`);
|
|
continue;
|
|
}
|
|
|
|
host.send((0,_isomorphic_ipc__WEBPACK_IMPORTED_MODULE_2__.formatIpcName)(eventName), ...args);
|
|
}
|
|
}
|
|
/** Broadcasts extension event to all extension hosts listening for it. */
|
|
|
|
|
|
broadcastEvent(eventName, ...args) {
|
|
this.sendEvent(undefined, eventName, ...args);
|
|
}
|
|
|
|
}
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/browser/store.ts":
|
|
/*!******************************!*\
|
|
!*** ./src/browser/store.ts ***!
|
|
\******************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "ExtensionStore": () => (/* binding */ ExtensionStore)
|
|
/* harmony export */ });
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! electron */ "electron");
|
|
/* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(electron__WEBPACK_IMPORTED_MODULE_0__);
|
|
/* harmony import */ var events__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! events */ "events");
|
|
/* harmony import */ var events__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(events__WEBPACK_IMPORTED_MODULE_1__);
|
|
|
|
|
|
|
|
const debug = __webpack_require__(/*! debug */ "./node_modules/debug/src/index.js")('electron-chrome-extensions:store');
|
|
|
|
class ExtensionStore extends events__WEBPACK_IMPORTED_MODULE_1__.EventEmitter {
|
|
/** Tabs observed by the extensions system. */
|
|
|
|
/** Windows observed by the extensions system. */
|
|
|
|
/**
|
|
* Map of tabs to their parent window.
|
|
*
|
|
* It's not possible to access the parent of a BrowserView so we must manage
|
|
* this ourselves.
|
|
*/
|
|
|
|
/** Map of windows to their active tab. */
|
|
constructor(impl) {
|
|
super();
|
|
this.impl = impl;
|
|
this.tabs = new Set();
|
|
this.windows = new Set();
|
|
this.lastFocusedWindowId = void 0;
|
|
this.tabToWindow = new WeakMap();
|
|
this.windowToActiveTab = new WeakMap();
|
|
this.tabDetailsCache = new Map();
|
|
this.windowDetailsCache = new Map();
|
|
}
|
|
|
|
async getWindowById(event, windowId) {
|
|
var _this$impl$getWindowB, _this$impl;
|
|
|
|
const foundWindow = this.findWindowById(windowId);
|
|
const window = (_this$impl$getWindowB = (_this$impl = this.impl).getWindowById) === null || _this$impl$getWindowB === void 0 ? void 0 : _this$impl$getWindowB.call(_this$impl, {
|
|
event,
|
|
foundWindow
|
|
}, windowId);
|
|
return window || foundWindow || null;
|
|
}
|
|
|
|
findWindowById(windowId) {
|
|
return Array.from(this.windows).find(window => !window.isDestroyed() && window.id === windowId);
|
|
}
|
|
|
|
getLastFocusedWindow() {
|
|
return this.lastFocusedWindowId ? this.findWindowById(this.lastFocusedWindowId) : null;
|
|
}
|
|
|
|
addWindow(window) {
|
|
if (this.windows.has(window)) return;
|
|
this.windows.add(window);
|
|
|
|
if (typeof this.lastFocusedWindowId !== 'number') {
|
|
this.lastFocusedWindowId = window.id;
|
|
}
|
|
|
|
this.emit('window-added', window);
|
|
}
|
|
|
|
async windowsGetCurrent(event) {
|
|
const wc = event.sender;
|
|
let win = this.tabToWindow.get(wc) || electron__WEBPACK_IMPORTED_MODULE_0__.BrowserWindow.fromWebContents(wc);
|
|
|
|
if (typeof this.impl.windowsGetCurrent === 'function') {
|
|
const lastFocusedWindow = this.getLastFocusedWindow() || null;
|
|
win = await this.impl.windowsGetCurrent(win, {
|
|
event,
|
|
lastFocusedWindow
|
|
});
|
|
}
|
|
|
|
return win;
|
|
}
|
|
|
|
async createWindow(event, details) {
|
|
if (typeof this.impl.createWindow !== 'function') {
|
|
throw new Error('createWindow is not implemented');
|
|
}
|
|
|
|
const win = await this.impl.createWindow(details, {
|
|
event
|
|
});
|
|
this.addWindow(win);
|
|
return win;
|
|
}
|
|
|
|
removeWindow(window) {
|
|
if (!this.windows.has(window)) return;
|
|
this.windows.delete(window);
|
|
|
|
if (typeof this.impl.removeWindow === 'function') {
|
|
this.impl.removeWindow(window);
|
|
} else {
|
|
window.destroy();
|
|
}
|
|
}
|
|
|
|
getTabById(tabId) {
|
|
return Array.from(this.tabs).find(tab => !tab.isDestroyed() && tab.id === tabId);
|
|
}
|
|
|
|
addTab(tab, window) {
|
|
if (this.tabs.has(tab)) return;
|
|
this.tabs.add(tab);
|
|
this.tabToWindow.set(tab, window);
|
|
this.addWindow(window);
|
|
const activeTab = this.getActiveTabFromWebContents(tab);
|
|
|
|
if (!activeTab) {
|
|
this.setActiveTab(tab);
|
|
}
|
|
|
|
this.emit('tab-added', tab);
|
|
}
|
|
|
|
removeTab(tab) {
|
|
if (!this.tabs.has(tab)) return;
|
|
const tabId = tab.id;
|
|
const win = this.tabToWindow.get(tab);
|
|
this.tabs.delete(tab);
|
|
this.tabToWindow.delete(tab) // TODO: clear active tab
|
|
;
|
|
|
|
(async () => {
|
|
var _this$impl$getTabbedB, _this$impl2;
|
|
|
|
const behaviors = await ((_this$impl$getTabbedB = (_this$impl2 = this.impl).getTabbedBrowserWindowBehavior) === null || _this$impl$getTabbedB === void 0 ? void 0 : _this$impl$getTabbedB.call(_this$impl2, {
|
|
tabs: this.tabs,
|
|
window: win
|
|
})); // Clear window if it has no remaining tabs
|
|
|
|
const windowHasTabs = Array.from(this.tabs).find(tab => this.tabToWindow.get(tab) === win);
|
|
|
|
if (!windowHasTabs && !(behaviors !== null && behaviors !== void 0 && behaviors.keepRefWindowOnAllTabsClosed)) {
|
|
// TODO: maybe we should check if windows destroyed to determine if we should dispose all listeners on it.
|
|
this.windows.delete(win);
|
|
}
|
|
|
|
if (typeof this.impl.removeTab === 'function') {
|
|
this.impl.removeTab(tab, win);
|
|
}
|
|
|
|
this.emit('tab-removed', tabId);
|
|
})();
|
|
}
|
|
|
|
async createTab(details, event) {
|
|
if (typeof this.impl.createTab !== 'function') {
|
|
throw new Error('createTab is not implemented');
|
|
} // Fallback to current window
|
|
|
|
|
|
if (!details.windowId) {
|
|
details.windowId = this.lastFocusedWindowId;
|
|
}
|
|
|
|
const result = await this.impl.createTab(details, {
|
|
event
|
|
});
|
|
if (!result) return result;
|
|
|
|
if (!Array.isArray(result)) {
|
|
throw new Error('createTab must return an array of [tab, window]');
|
|
}
|
|
|
|
const [tab, window] = result;
|
|
|
|
if (typeof tab !== 'object' || !electron__WEBPACK_IMPORTED_MODULE_0__.webContents.fromId(tab.id)) {
|
|
throw new Error('createTab must return a WebContents');
|
|
} else if (typeof window !== 'object') {
|
|
throw new Error('createTab must return a BrowserWindow');
|
|
}
|
|
|
|
this.addTab(tab, window);
|
|
return tab;
|
|
}
|
|
|
|
getActiveTabFromWindow(win) {
|
|
const activeTab = win && !win.isDestroyed() && this.windowToActiveTab.get(win);
|
|
return activeTab && !activeTab.isDestroyed() && activeTab || undefined;
|
|
}
|
|
|
|
getActiveTabFromWebContents(wc) {
|
|
const win = this.tabToWindow.get(wc) || electron__WEBPACK_IMPORTED_MODULE_0__.BrowserWindow.fromWebContents(wc);
|
|
return win ? this.getActiveTabFromWindow(win) : undefined;
|
|
}
|
|
|
|
getActiveTabOfCurrentWindow() {
|
|
const win = this.getLastFocusedWindow();
|
|
return win ? this.getActiveTabFromWindow(win) : undefined;
|
|
}
|
|
|
|
setActiveTab(tab) {
|
|
const win = this.tabToWindow.get(tab);
|
|
|
|
if (!win) {
|
|
throw new Error('Active tab has no parent window');
|
|
}
|
|
|
|
const prevActiveTab = this.getActiveTabFromWebContents(tab);
|
|
this.windowToActiveTab.set(win, tab);
|
|
|
|
if (tab.id !== (prevActiveTab === null || prevActiveTab === void 0 ? void 0 : prevActiveTab.id)) {
|
|
this.emit('active-tab-changed', tab, win);
|
|
|
|
if (typeof this.impl.selectTab === 'function') {
|
|
this.impl.selectTab(tab, win);
|
|
}
|
|
}
|
|
}
|
|
|
|
buildMenuItems(extensionId, menuType) {
|
|
// This function is overwritten by ContextMenusAPI
|
|
return [];
|
|
}
|
|
|
|
}
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/browser/utils.ts":
|
|
/*!******************************!*\
|
|
!*** ./src/browser/utils.ts ***!
|
|
\******************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "ensureSuffix": () => (/* binding */ ensureSuffix),
|
|
/* harmony export */ "unPrefix": () => (/* binding */ unPrefix)
|
|
/* harmony export */ });
|
|
function ensureSuffix(str = '', suffix = '/') {
|
|
if (str.endsWith(suffix)) return str;
|
|
return str + suffix;
|
|
}
|
|
function unPrefix(str = '', prefix = '/') {
|
|
const prefixes = Array.isArray(prefix) ? prefix : [prefix];
|
|
let ret = str;
|
|
prefixes.forEach(prefix => {
|
|
if (ret.startsWith(prefix)) ret = ret.slice(prefix.length);
|
|
});
|
|
return ret;
|
|
}
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/isomorphic/ipc.ts":
|
|
/*!*******************************!*\
|
|
!*** ./src/isomorphic/ipc.ts ***!
|
|
\*******************************/
|
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "formatIpcName": () => (/* binding */ formatIpcName)
|
|
/* harmony export */ });
|
|
const formatIpcName = name => `crx-${name}`;
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./node_modules/debug/src/browser.js":
|
|
/*!*******************************************!*\
|
|
!*** ./node_modules/debug/src/browser.js ***!
|
|
\*******************************************/
|
|
/***/ ((module, exports, __webpack_require__) => {
|
|
|
|
/* eslint-env browser */
|
|
|
|
/**
|
|
* This is the web browser implementation of `debug()`.
|
|
*/
|
|
|
|
exports.formatArgs = formatArgs;
|
|
exports.save = save;
|
|
exports.load = load;
|
|
exports.useColors = useColors;
|
|
exports.storage = localstorage();
|
|
exports.destroy = (() => {
|
|
let warned = false;
|
|
|
|
return () => {
|
|
if (!warned) {
|
|
warned = true;
|
|
console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
|
|
}
|
|
};
|
|
})();
|
|
|
|
/**
|
|
* Colors.
|
|
*/
|
|
|
|
exports.colors = [
|
|
'#0000CC',
|
|
'#0000FF',
|
|
'#0033CC',
|
|
'#0033FF',
|
|
'#0066CC',
|
|
'#0066FF',
|
|
'#0099CC',
|
|
'#0099FF',
|
|
'#00CC00',
|
|
'#00CC33',
|
|
'#00CC66',
|
|
'#00CC99',
|
|
'#00CCCC',
|
|
'#00CCFF',
|
|
'#3300CC',
|
|
'#3300FF',
|
|
'#3333CC',
|
|
'#3333FF',
|
|
'#3366CC',
|
|
'#3366FF',
|
|
'#3399CC',
|
|
'#3399FF',
|
|
'#33CC00',
|
|
'#33CC33',
|
|
'#33CC66',
|
|
'#33CC99',
|
|
'#33CCCC',
|
|
'#33CCFF',
|
|
'#6600CC',
|
|
'#6600FF',
|
|
'#6633CC',
|
|
'#6633FF',
|
|
'#66CC00',
|
|
'#66CC33',
|
|
'#9900CC',
|
|
'#9900FF',
|
|
'#9933CC',
|
|
'#9933FF',
|
|
'#99CC00',
|
|
'#99CC33',
|
|
'#CC0000',
|
|
'#CC0033',
|
|
'#CC0066',
|
|
'#CC0099',
|
|
'#CC00CC',
|
|
'#CC00FF',
|
|
'#CC3300',
|
|
'#CC3333',
|
|
'#CC3366',
|
|
'#CC3399',
|
|
'#CC33CC',
|
|
'#CC33FF',
|
|
'#CC6600',
|
|
'#CC6633',
|
|
'#CC9900',
|
|
'#CC9933',
|
|
'#CCCC00',
|
|
'#CCCC33',
|
|
'#FF0000',
|
|
'#FF0033',
|
|
'#FF0066',
|
|
'#FF0099',
|
|
'#FF00CC',
|
|
'#FF00FF',
|
|
'#FF3300',
|
|
'#FF3333',
|
|
'#FF3366',
|
|
'#FF3399',
|
|
'#FF33CC',
|
|
'#FF33FF',
|
|
'#FF6600',
|
|
'#FF6633',
|
|
'#FF9900',
|
|
'#FF9933',
|
|
'#FFCC00',
|
|
'#FFCC33'
|
|
];
|
|
|
|
/**
|
|
* Currently only WebKit-based Web Inspectors, Firefox >= v31,
|
|
* and the Firebug extension (any Firefox version) are known
|
|
* to support "%c" CSS customizations.
|
|
*
|
|
* TODO: add a `localStorage` variable to explicitly enable/disable colors
|
|
*/
|
|
|
|
// eslint-disable-next-line complexity
|
|
function useColors() {
|
|
// NB: In an Electron preload script, document will be defined but not fully
|
|
// initialized. Since we know we're in Chrome, we'll just detect this case
|
|
// explicitly
|
|
if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {
|
|
return true;
|
|
}
|
|
|
|
// Internet Explorer and Edge do not support colors.
|
|
if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
|
|
return false;
|
|
}
|
|
|
|
// Is webkit? http://stackoverflow.com/a/16459606/376773
|
|
// document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
|
|
return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||
|
|
// Is firebug? http://stackoverflow.com/a/398120/376773
|
|
(typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||
|
|
// Is firefox >= v31?
|
|
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
|
|
(typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) ||
|
|
// Double check webkit in userAgent just in case we are in a worker
|
|
(typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/));
|
|
}
|
|
|
|
/**
|
|
* Colorize log arguments if enabled.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
function formatArgs(args) {
|
|
args[0] = (this.useColors ? '%c' : '') +
|
|
this.namespace +
|
|
(this.useColors ? ' %c' : ' ') +
|
|
args[0] +
|
|
(this.useColors ? '%c ' : ' ') +
|
|
'+' + module.exports.humanize(this.diff);
|
|
|
|
if (!this.useColors) {
|
|
return;
|
|
}
|
|
|
|
const c = 'color: ' + this.color;
|
|
args.splice(1, 0, c, 'color: inherit');
|
|
|
|
// The final "%c" is somewhat tricky, because there could be other
|
|
// arguments passed either before or after the %c, so we need to
|
|
// figure out the correct index to insert the CSS into
|
|
let index = 0;
|
|
let lastC = 0;
|
|
args[0].replace(/%[a-zA-Z%]/g, match => {
|
|
if (match === '%%') {
|
|
return;
|
|
}
|
|
index++;
|
|
if (match === '%c') {
|
|
// We only are interested in the *last* %c
|
|
// (the user may have provided their own)
|
|
lastC = index;
|
|
}
|
|
});
|
|
|
|
args.splice(lastC, 0, c);
|
|
}
|
|
|
|
/**
|
|
* Invokes `console.debug()` when available.
|
|
* No-op when `console.debug` is not a "function".
|
|
* If `console.debug` is not available, falls back
|
|
* to `console.log`.
|
|
*
|
|
* @api public
|
|
*/
|
|
exports.log = console.debug || console.log || (() => {});
|
|
|
|
/**
|
|
* Save `namespaces`.
|
|
*
|
|
* @param {String} namespaces
|
|
* @api private
|
|
*/
|
|
function save(namespaces) {
|
|
try {
|
|
if (namespaces) {
|
|
exports.storage.setItem('debug', namespaces);
|
|
} else {
|
|
exports.storage.removeItem('debug');
|
|
}
|
|
} catch (error) {
|
|
// Swallow
|
|
// XXX (@Qix-) should we be logging these?
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load `namespaces`.
|
|
*
|
|
* @return {String} returns the previously persisted debug modes
|
|
* @api private
|
|
*/
|
|
function load() {
|
|
let r;
|
|
try {
|
|
r = exports.storage.getItem('debug');
|
|
} catch (error) {
|
|
// Swallow
|
|
// XXX (@Qix-) should we be logging these?
|
|
}
|
|
|
|
// If debug isn't set in LS, and we're in Electron, try to load $DEBUG
|
|
if (!r && typeof process !== 'undefined' && 'env' in process) {
|
|
r = process.env.DEBUG;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* Localstorage attempts to return the localstorage.
|
|
*
|
|
* This is necessary because safari throws
|
|
* when a user disables cookies/localstorage
|
|
* and you attempt to access it.
|
|
*
|
|
* @return {LocalStorage}
|
|
* @api private
|
|
*/
|
|
|
|
function localstorage() {
|
|
try {
|
|
// TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context
|
|
// The Browser also has localStorage in the global context.
|
|
return localStorage;
|
|
} catch (error) {
|
|
// Swallow
|
|
// XXX (@Qix-) should we be logging these?
|
|
}
|
|
}
|
|
|
|
module.exports = __webpack_require__(/*! ./common */ "./node_modules/debug/src/common.js")(exports);
|
|
|
|
const {formatters} = module.exports;
|
|
|
|
/**
|
|
* Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
|
|
*/
|
|
|
|
formatters.j = function (v) {
|
|
try {
|
|
return JSON.stringify(v);
|
|
} catch (error) {
|
|
return '[UnexpectedJSONParseError]: ' + error.message;
|
|
}
|
|
};
|
|
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./node_modules/debug/src/common.js":
|
|
/*!******************************************!*\
|
|
!*** ./node_modules/debug/src/common.js ***!
|
|
\******************************************/
|
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
|
|
|
|
/**
|
|
* This is the common logic for both the Node.js and web browser
|
|
* implementations of `debug()`.
|
|
*/
|
|
|
|
function setup(env) {
|
|
createDebug.debug = createDebug;
|
|
createDebug.default = createDebug;
|
|
createDebug.coerce = coerce;
|
|
createDebug.disable = disable;
|
|
createDebug.enable = enable;
|
|
createDebug.enabled = enabled;
|
|
createDebug.humanize = __webpack_require__(/*! ms */ "./node_modules/ms/index.js");
|
|
createDebug.destroy = destroy;
|
|
|
|
Object.keys(env).forEach(key => {
|
|
createDebug[key] = env[key];
|
|
});
|
|
|
|
/**
|
|
* The currently active debug mode names, and names to skip.
|
|
*/
|
|
|
|
createDebug.names = [];
|
|
createDebug.skips = [];
|
|
|
|
/**
|
|
* Map of special "%n" handling functions, for the debug "format" argument.
|
|
*
|
|
* Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
|
|
*/
|
|
createDebug.formatters = {};
|
|
|
|
/**
|
|
* Selects a color for a debug namespace
|
|
* @param {String} namespace The namespace string for the debug instance to be colored
|
|
* @return {Number|String} An ANSI color code for the given namespace
|
|
* @api private
|
|
*/
|
|
function selectColor(namespace) {
|
|
let hash = 0;
|
|
|
|
for (let i = 0; i < namespace.length; i++) {
|
|
hash = ((hash << 5) - hash) + namespace.charCodeAt(i);
|
|
hash |= 0; // Convert to 32bit integer
|
|
}
|
|
|
|
return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
|
|
}
|
|
createDebug.selectColor = selectColor;
|
|
|
|
/**
|
|
* Create a debugger with the given `namespace`.
|
|
*
|
|
* @param {String} namespace
|
|
* @return {Function}
|
|
* @api public
|
|
*/
|
|
function createDebug(namespace) {
|
|
let prevTime;
|
|
let enableOverride = null;
|
|
let namespacesCache;
|
|
let enabledCache;
|
|
|
|
function debug(...args) {
|
|
// Disabled?
|
|
if (!debug.enabled) {
|
|
return;
|
|
}
|
|
|
|
const self = debug;
|
|
|
|
// Set `diff` timestamp
|
|
const curr = Number(new Date());
|
|
const ms = curr - (prevTime || curr);
|
|
self.diff = ms;
|
|
self.prev = prevTime;
|
|
self.curr = curr;
|
|
prevTime = curr;
|
|
|
|
args[0] = createDebug.coerce(args[0]);
|
|
|
|
if (typeof args[0] !== 'string') {
|
|
// Anything else let's inspect with %O
|
|
args.unshift('%O');
|
|
}
|
|
|
|
// Apply any `formatters` transformations
|
|
let index = 0;
|
|
args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
|
|
// If we encounter an escaped % then don't increase the array index
|
|
if (match === '%%') {
|
|
return '%';
|
|
}
|
|
index++;
|
|
const formatter = createDebug.formatters[format];
|
|
if (typeof formatter === 'function') {
|
|
const val = args[index];
|
|
match = formatter.call(self, val);
|
|
|
|
// Now we need to remove `args[index]` since it's inlined in the `format`
|
|
args.splice(index, 1);
|
|
index--;
|
|
}
|
|
return match;
|
|
});
|
|
|
|
// Apply env-specific formatting (colors, etc.)
|
|
createDebug.formatArgs.call(self, args);
|
|
|
|
const logFn = self.log || createDebug.log;
|
|
logFn.apply(self, args);
|
|
}
|
|
|
|
debug.namespace = namespace;
|
|
debug.useColors = createDebug.useColors();
|
|
debug.color = createDebug.selectColor(namespace);
|
|
debug.extend = extend;
|
|
debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release.
|
|
|
|
Object.defineProperty(debug, 'enabled', {
|
|
enumerable: true,
|
|
configurable: false,
|
|
get: () => {
|
|
if (enableOverride !== null) {
|
|
return enableOverride;
|
|
}
|
|
if (namespacesCache !== createDebug.namespaces) {
|
|
namespacesCache = createDebug.namespaces;
|
|
enabledCache = createDebug.enabled(namespace);
|
|
}
|
|
|
|
return enabledCache;
|
|
},
|
|
set: v => {
|
|
enableOverride = v;
|
|
}
|
|
});
|
|
|
|
// Env-specific initialization logic for debug instances
|
|
if (typeof createDebug.init === 'function') {
|
|
createDebug.init(debug);
|
|
}
|
|
|
|
return debug;
|
|
}
|
|
|
|
function extend(namespace, delimiter) {
|
|
const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
|
|
newDebug.log = this.log;
|
|
return newDebug;
|
|
}
|
|
|
|
/**
|
|
* Enables a debug mode by namespaces. This can include modes
|
|
* separated by a colon and wildcards.
|
|
*
|
|
* @param {String} namespaces
|
|
* @api public
|
|
*/
|
|
function enable(namespaces) {
|
|
createDebug.save(namespaces);
|
|
createDebug.namespaces = namespaces;
|
|
|
|
createDebug.names = [];
|
|
createDebug.skips = [];
|
|
|
|
let i;
|
|
const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
|
|
const len = split.length;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
if (!split[i]) {
|
|
// ignore empty strings
|
|
continue;
|
|
}
|
|
|
|
namespaces = split[i].replace(/\*/g, '.*?');
|
|
|
|
if (namespaces[0] === '-') {
|
|
createDebug.skips.push(new RegExp('^' + namespaces.slice(1) + '$'));
|
|
} else {
|
|
createDebug.names.push(new RegExp('^' + namespaces + '$'));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disable debug output.
|
|
*
|
|
* @return {String} namespaces
|
|
* @api public
|
|
*/
|
|
function disable() {
|
|
const namespaces = [
|
|
...createDebug.names.map(toNamespace),
|
|
...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace)
|
|
].join(',');
|
|
createDebug.enable('');
|
|
return namespaces;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the given mode name is enabled, false otherwise.
|
|
*
|
|
* @param {String} name
|
|
* @return {Boolean}
|
|
* @api public
|
|
*/
|
|
function enabled(name) {
|
|
if (name[name.length - 1] === '*') {
|
|
return true;
|
|
}
|
|
|
|
let i;
|
|
let len;
|
|
|
|
for (i = 0, len = createDebug.skips.length; i < len; i++) {
|
|
if (createDebug.skips[i].test(name)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (i = 0, len = createDebug.names.length; i < len; i++) {
|
|
if (createDebug.names[i].test(name)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Convert regexp to namespace
|
|
*
|
|
* @param {RegExp} regxep
|
|
* @return {String} namespace
|
|
* @api private
|
|
*/
|
|
function toNamespace(regexp) {
|
|
return regexp.toString()
|
|
.substring(2, regexp.toString().length - 2)
|
|
.replace(/\.\*\?$/, '*');
|
|
}
|
|
|
|
/**
|
|
* Coerce `val`.
|
|
*
|
|
* @param {Mixed} val
|
|
* @return {Mixed}
|
|
* @api private
|
|
*/
|
|
function coerce(val) {
|
|
if (val instanceof Error) {
|
|
return val.stack || val.message;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
/**
|
|
* XXX DO NOT USE. This is a temporary stub function.
|
|
* XXX It WILL be removed in the next major release.
|
|
*/
|
|
function destroy() {
|
|
console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
|
|
}
|
|
|
|
createDebug.enable(createDebug.load());
|
|
|
|
return createDebug;
|
|
}
|
|
|
|
module.exports = setup;
|
|
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./node_modules/debug/src/index.js":
|
|
/*!*****************************************!*\
|
|
!*** ./node_modules/debug/src/index.js ***!
|
|
\*****************************************/
|
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
|
|
/**
|
|
* Detect Electron renderer / nwjs process, which is node, but we should
|
|
* treat as a browser.
|
|
*/
|
|
|
|
if (typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs) {
|
|
module.exports = __webpack_require__(/*! ./browser.js */ "./node_modules/debug/src/browser.js");
|
|
} else {
|
|
module.exports = __webpack_require__(/*! ./node.js */ "./node_modules/debug/src/node.js");
|
|
}
|
|
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./node_modules/debug/src/node.js":
|
|
/*!****************************************!*\
|
|
!*** ./node_modules/debug/src/node.js ***!
|
|
\****************************************/
|
|
/***/ ((module, exports, __webpack_require__) => {
|
|
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
const tty = __webpack_require__(/*! tty */ "tty");
|
|
const util = __webpack_require__(/*! util */ "util");
|
|
|
|
/**
|
|
* This is the Node.js implementation of `debug()`.
|
|
*/
|
|
|
|
exports.init = init;
|
|
exports.log = log;
|
|
exports.formatArgs = formatArgs;
|
|
exports.save = save;
|
|
exports.load = load;
|
|
exports.useColors = useColors;
|
|
exports.destroy = util.deprecate(
|
|
() => {},
|
|
'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'
|
|
);
|
|
|
|
/**
|
|
* Colors.
|
|
*/
|
|
|
|
exports.colors = [6, 2, 3, 4, 5, 1];
|
|
|
|
try {
|
|
// Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json)
|
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
const supportsColor = __webpack_require__(/*! supports-color */ "./node_modules/supports-color/index.js");
|
|
|
|
if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
|
|
exports.colors = [
|
|
20,
|
|
21,
|
|
26,
|
|
27,
|
|
32,
|
|
33,
|
|
38,
|
|
39,
|
|
40,
|
|
41,
|
|
42,
|
|
43,
|
|
44,
|
|
45,
|
|
56,
|
|
57,
|
|
62,
|
|
63,
|
|
68,
|
|
69,
|
|
74,
|
|
75,
|
|
76,
|
|
77,
|
|
78,
|
|
79,
|
|
80,
|
|
81,
|
|
92,
|
|
93,
|
|
98,
|
|
99,
|
|
112,
|
|
113,
|
|
128,
|
|
129,
|
|
134,
|
|
135,
|
|
148,
|
|
149,
|
|
160,
|
|
161,
|
|
162,
|
|
163,
|
|
164,
|
|
165,
|
|
166,
|
|
167,
|
|
168,
|
|
169,
|
|
170,
|
|
171,
|
|
172,
|
|
173,
|
|
178,
|
|
179,
|
|
184,
|
|
185,
|
|
196,
|
|
197,
|
|
198,
|
|
199,
|
|
200,
|
|
201,
|
|
202,
|
|
203,
|
|
204,
|
|
205,
|
|
206,
|
|
207,
|
|
208,
|
|
209,
|
|
214,
|
|
215,
|
|
220,
|
|
221
|
|
];
|
|
}
|
|
} catch (error) {
|
|
// Swallow - we only care if `supports-color` is available; it doesn't have to be.
|
|
}
|
|
|
|
/**
|
|
* Build up the default `inspectOpts` object from the environment variables.
|
|
*
|
|
* $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js
|
|
*/
|
|
|
|
exports.inspectOpts = Object.keys(process.env).filter(key => {
|
|
return /^debug_/i.test(key);
|
|
}).reduce((obj, key) => {
|
|
// Camel-case
|
|
const prop = key
|
|
.substring(6)
|
|
.toLowerCase()
|
|
.replace(/_([a-z])/g, (_, k) => {
|
|
return k.toUpperCase();
|
|
});
|
|
|
|
// Coerce string value into JS value
|
|
let val = process.env[key];
|
|
if (/^(yes|on|true|enabled)$/i.test(val)) {
|
|
val = true;
|
|
} else if (/^(no|off|false|disabled)$/i.test(val)) {
|
|
val = false;
|
|
} else if (val === 'null') {
|
|
val = null;
|
|
} else {
|
|
val = Number(val);
|
|
}
|
|
|
|
obj[prop] = val;
|
|
return obj;
|
|
}, {});
|
|
|
|
/**
|
|
* Is stdout a TTY? Colored output is enabled when `true`.
|
|
*/
|
|
|
|
function useColors() {
|
|
return 'colors' in exports.inspectOpts ?
|
|
Boolean(exports.inspectOpts.colors) :
|
|
tty.isatty(process.stderr.fd);
|
|
}
|
|
|
|
/**
|
|
* Adds ANSI color escape codes if enabled.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
function formatArgs(args) {
|
|
const {namespace: name, useColors} = this;
|
|
|
|
if (useColors) {
|
|
const c = this.color;
|
|
const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c);
|
|
const prefix = ` ${colorCode};1m${name} \u001B[0m`;
|
|
|
|
args[0] = prefix + args[0].split('\n').join('\n' + prefix);
|
|
args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + '\u001B[0m');
|
|
} else {
|
|
args[0] = getDate() + name + ' ' + args[0];
|
|
}
|
|
}
|
|
|
|
function getDate() {
|
|
if (exports.inspectOpts.hideDate) {
|
|
return '';
|
|
}
|
|
return new Date().toISOString() + ' ';
|
|
}
|
|
|
|
/**
|
|
* Invokes `util.format()` with the specified arguments and writes to stderr.
|
|
*/
|
|
|
|
function log(...args) {
|
|
return process.stderr.write(util.format(...args) + '\n');
|
|
}
|
|
|
|
/**
|
|
* Save `namespaces`.
|
|
*
|
|
* @param {String} namespaces
|
|
* @api private
|
|
*/
|
|
function save(namespaces) {
|
|
if (namespaces) {
|
|
process.env.DEBUG = namespaces;
|
|
} else {
|
|
// If you set a process.env field to null or undefined, it gets cast to the
|
|
// string 'null' or 'undefined'. Just delete instead.
|
|
delete process.env.DEBUG;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load `namespaces`.
|
|
*
|
|
* @return {String} returns the previously persisted debug modes
|
|
* @api private
|
|
*/
|
|
|
|
function load() {
|
|
return process.env.DEBUG;
|
|
}
|
|
|
|
/**
|
|
* Init logic for `debug` instances.
|
|
*
|
|
* Create a new `inspectOpts` object in case `useColors` is set
|
|
* differently for a particular `debug` instance.
|
|
*/
|
|
|
|
function init(debug) {
|
|
debug.inspectOpts = {};
|
|
|
|
const keys = Object.keys(exports.inspectOpts);
|
|
for (let i = 0; i < keys.length; i++) {
|
|
debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
|
|
}
|
|
}
|
|
|
|
module.exports = __webpack_require__(/*! ./common */ "./node_modules/debug/src/common.js")(exports);
|
|
|
|
const {formatters} = module.exports;
|
|
|
|
/**
|
|
* Map %o to `util.inspect()`, all on a single line.
|
|
*/
|
|
|
|
formatters.o = function (v) {
|
|
this.inspectOpts.colors = this.useColors;
|
|
return util.inspect(v, this.inspectOpts)
|
|
.split('\n')
|
|
.map(str => str.trim())
|
|
.join(' ');
|
|
};
|
|
|
|
/**
|
|
* Map %O to `util.inspect()`, allowing multiple lines if needed.
|
|
*/
|
|
|
|
formatters.O = function (v) {
|
|
this.inspectOpts.colors = this.useColors;
|
|
return util.inspect(v, this.inspectOpts);
|
|
};
|
|
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./node_modules/has-flag/index.js":
|
|
/*!****************************************!*\
|
|
!*** ./node_modules/has-flag/index.js ***!
|
|
\****************************************/
|
|
/***/ ((module) => {
|
|
|
|
"use strict";
|
|
|
|
module.exports = (flag, argv) => {
|
|
argv = argv || process.argv;
|
|
const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--');
|
|
const pos = argv.indexOf(prefix + flag);
|
|
const terminatorPos = argv.indexOf('--');
|
|
return pos !== -1 && (terminatorPos === -1 ? true : pos < terminatorPos);
|
|
};
|
|
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./node_modules/ms/index.js":
|
|
/*!**********************************!*\
|
|
!*** ./node_modules/ms/index.js ***!
|
|
\**********************************/
|
|
/***/ ((module) => {
|
|
|
|
/**
|
|
* Helpers.
|
|
*/
|
|
|
|
var s = 1000;
|
|
var m = s * 60;
|
|
var h = m * 60;
|
|
var d = h * 24;
|
|
var w = d * 7;
|
|
var y = d * 365.25;
|
|
|
|
/**
|
|
* Parse or format the given `val`.
|
|
*
|
|
* Options:
|
|
*
|
|
* - `long` verbose formatting [false]
|
|
*
|
|
* @param {String|Number} val
|
|
* @param {Object} [options]
|
|
* @throws {Error} throw an error if val is not a non-empty string or a number
|
|
* @return {String|Number}
|
|
* @api public
|
|
*/
|
|
|
|
module.exports = function(val, options) {
|
|
options = options || {};
|
|
var type = typeof val;
|
|
if (type === 'string' && val.length > 0) {
|
|
return parse(val);
|
|
} else if (type === 'number' && isFinite(val)) {
|
|
return options.long ? fmtLong(val) : fmtShort(val);
|
|
}
|
|
throw new Error(
|
|
'val is not a non-empty string or a valid number. val=' +
|
|
JSON.stringify(val)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Parse the given `str` and return milliseconds.
|
|
*
|
|
* @param {String} str
|
|
* @return {Number}
|
|
* @api private
|
|
*/
|
|
|
|
function parse(str) {
|
|
str = String(str);
|
|
if (str.length > 100) {
|
|
return;
|
|
}
|
|
var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
|
|
str
|
|
);
|
|
if (!match) {
|
|
return;
|
|
}
|
|
var n = parseFloat(match[1]);
|
|
var type = (match[2] || 'ms').toLowerCase();
|
|
switch (type) {
|
|
case 'years':
|
|
case 'year':
|
|
case 'yrs':
|
|
case 'yr':
|
|
case 'y':
|
|
return n * y;
|
|
case 'weeks':
|
|
case 'week':
|
|
case 'w':
|
|
return n * w;
|
|
case 'days':
|
|
case 'day':
|
|
case 'd':
|
|
return n * d;
|
|
case 'hours':
|
|
case 'hour':
|
|
case 'hrs':
|
|
case 'hr':
|
|
case 'h':
|
|
return n * h;
|
|
case 'minutes':
|
|
case 'minute':
|
|
case 'mins':
|
|
case 'min':
|
|
case 'm':
|
|
return n * m;
|
|
case 'seconds':
|
|
case 'second':
|
|
case 'secs':
|
|
case 'sec':
|
|
case 's':
|
|
return n * s;
|
|
case 'milliseconds':
|
|
case 'millisecond':
|
|
case 'msecs':
|
|
case 'msec':
|
|
case 'ms':
|
|
return n;
|
|
default:
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Short format for `ms`.
|
|
*
|
|
* @param {Number} ms
|
|
* @return {String}
|
|
* @api private
|
|
*/
|
|
|
|
function fmtShort(ms) {
|
|
var msAbs = Math.abs(ms);
|
|
if (msAbs >= d) {
|
|
return Math.round(ms / d) + 'd';
|
|
}
|
|
if (msAbs >= h) {
|
|
return Math.round(ms / h) + 'h';
|
|
}
|
|
if (msAbs >= m) {
|
|
return Math.round(ms / m) + 'm';
|
|
}
|
|
if (msAbs >= s) {
|
|
return Math.round(ms / s) + 's';
|
|
}
|
|
return ms + 'ms';
|
|
}
|
|
|
|
/**
|
|
* Long format for `ms`.
|
|
*
|
|
* @param {Number} ms
|
|
* @return {String}
|
|
* @api private
|
|
*/
|
|
|
|
function fmtLong(ms) {
|
|
var msAbs = Math.abs(ms);
|
|
if (msAbs >= d) {
|
|
return plural(ms, msAbs, d, 'day');
|
|
}
|
|
if (msAbs >= h) {
|
|
return plural(ms, msAbs, h, 'hour');
|
|
}
|
|
if (msAbs >= m) {
|
|
return plural(ms, msAbs, m, 'minute');
|
|
}
|
|
if (msAbs >= s) {
|
|
return plural(ms, msAbs, s, 'second');
|
|
}
|
|
return ms + ' ms';
|
|
}
|
|
|
|
/**
|
|
* Pluralization helper.
|
|
*/
|
|
|
|
function plural(ms, msAbs, n, name) {
|
|
var isPlural = msAbs >= n * 1.5;
|
|
return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : '');
|
|
}
|
|
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./node_modules/supports-color/index.js":
|
|
/*!**********************************************!*\
|
|
!*** ./node_modules/supports-color/index.js ***!
|
|
\**********************************************/
|
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
|
|
"use strict";
|
|
|
|
const os = __webpack_require__(/*! os */ "os");
|
|
const hasFlag = __webpack_require__(/*! has-flag */ "./node_modules/has-flag/index.js");
|
|
|
|
const env = process.env;
|
|
|
|
let forceColor;
|
|
if (hasFlag('no-color') ||
|
|
hasFlag('no-colors') ||
|
|
hasFlag('color=false')) {
|
|
forceColor = false;
|
|
} else if (hasFlag('color') ||
|
|
hasFlag('colors') ||
|
|
hasFlag('color=true') ||
|
|
hasFlag('color=always')) {
|
|
forceColor = true;
|
|
}
|
|
if ('FORCE_COLOR' in env) {
|
|
forceColor = env.FORCE_COLOR.length === 0 || parseInt(env.FORCE_COLOR, 10) !== 0;
|
|
}
|
|
|
|
function translateLevel(level) {
|
|
if (level === 0) {
|
|
return false;
|
|
}
|
|
|
|
return {
|
|
level,
|
|
hasBasic: true,
|
|
has256: level >= 2,
|
|
has16m: level >= 3
|
|
};
|
|
}
|
|
|
|
function supportsColor(stream) {
|
|
if (forceColor === false) {
|
|
return 0;
|
|
}
|
|
|
|
if (hasFlag('color=16m') ||
|
|
hasFlag('color=full') ||
|
|
hasFlag('color=truecolor')) {
|
|
return 3;
|
|
}
|
|
|
|
if (hasFlag('color=256')) {
|
|
return 2;
|
|
}
|
|
|
|
if (stream && !stream.isTTY && forceColor !== true) {
|
|
return 0;
|
|
}
|
|
|
|
const min = forceColor ? 1 : 0;
|
|
|
|
if (process.platform === 'win32') {
|
|
// Node.js 7.5.0 is the first version of Node.js to include a patch to
|
|
// libuv that enables 256 color output on Windows. Anything earlier and it
|
|
// won't work. However, here we target Node.js 8 at minimum as it is an LTS
|
|
// release, and Node.js 7 is not. Windows 10 build 10586 is the first Windows
|
|
// release that supports 256 colors. Windows 10 build 14931 is the first release
|
|
// that supports 16m/TrueColor.
|
|
const osRelease = os.release().split('.');
|
|
if (
|
|
Number(process.versions.node.split('.')[0]) >= 8 &&
|
|
Number(osRelease[0]) >= 10 &&
|
|
Number(osRelease[2]) >= 10586
|
|
) {
|
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
if ('CI' in env) {
|
|
if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(sign => sign in env) || env.CI_NAME === 'codeship') {
|
|
return 1;
|
|
}
|
|
|
|
return min;
|
|
}
|
|
|
|
if ('TEAMCITY_VERSION' in env) {
|
|
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
|
}
|
|
|
|
if (env.COLORTERM === 'truecolor') {
|
|
return 3;
|
|
}
|
|
|
|
if ('TERM_PROGRAM' in env) {
|
|
const version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
|
|
|
|
switch (env.TERM_PROGRAM) {
|
|
case 'iTerm.app':
|
|
return version >= 3 ? 3 : 2;
|
|
case 'Apple_Terminal':
|
|
return 2;
|
|
// No default
|
|
}
|
|
}
|
|
|
|
if (/-256(color)?$/i.test(env.TERM)) {
|
|
return 2;
|
|
}
|
|
|
|
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
|
return 1;
|
|
}
|
|
|
|
if ('COLORTERM' in env) {
|
|
return 1;
|
|
}
|
|
|
|
if (env.TERM === 'dumb') {
|
|
return min;
|
|
}
|
|
|
|
return min;
|
|
}
|
|
|
|
function getSupportLevel(stream) {
|
|
const level = supportsColor(stream);
|
|
return translateLevel(level);
|
|
}
|
|
|
|
module.exports = {
|
|
supportsColor: getSupportLevel,
|
|
stdout: getSupportLevel(process.stdout),
|
|
stderr: getSupportLevel(process.stderr)
|
|
};
|
|
|
|
|
|
/***/ }),
|
|
|
|
/***/ "electron":
|
|
/*!***************************!*\
|
|
!*** external "electron" ***!
|
|
\***************************/
|
|
/***/ ((module) => {
|
|
|
|
"use strict";
|
|
module.exports = require("electron");
|
|
|
|
/***/ }),
|
|
|
|
/***/ "events":
|
|
/*!*************************!*\
|
|
!*** external "events" ***!
|
|
\*************************/
|
|
/***/ ((module) => {
|
|
|
|
"use strict";
|
|
module.exports = require("events");
|
|
|
|
/***/ }),
|
|
|
|
/***/ "fs":
|
|
/*!*********************!*\
|
|
!*** external "fs" ***!
|
|
\*********************/
|
|
/***/ ((module) => {
|
|
|
|
"use strict";
|
|
module.exports = require("fs");
|
|
|
|
/***/ }),
|
|
|
|
/***/ "node:path":
|
|
/*!****************************!*\
|
|
!*** external "node:path" ***!
|
|
\****************************/
|
|
/***/ ((module) => {
|
|
|
|
"use strict";
|
|
module.exports = require("node:path");
|
|
|
|
/***/ }),
|
|
|
|
/***/ "node:url":
|
|
/*!***************************!*\
|
|
!*** external "node:url" ***!
|
|
\***************************/
|
|
/***/ ((module) => {
|
|
|
|
"use strict";
|
|
module.exports = require("node:url");
|
|
|
|
/***/ }),
|
|
|
|
/***/ "os":
|
|
/*!*********************!*\
|
|
!*** external "os" ***!
|
|
\*********************/
|
|
/***/ ((module) => {
|
|
|
|
"use strict";
|
|
module.exports = require("os");
|
|
|
|
/***/ }),
|
|
|
|
/***/ "path":
|
|
/*!***********************!*\
|
|
!*** external "path" ***!
|
|
\***********************/
|
|
/***/ ((module) => {
|
|
|
|
"use strict";
|
|
module.exports = require("path");
|
|
|
|
/***/ }),
|
|
|
|
/***/ "tty":
|
|
/*!**********************!*\
|
|
!*** external "tty" ***!
|
|
\**********************/
|
|
/***/ ((module) => {
|
|
|
|
"use strict";
|
|
module.exports = require("tty");
|
|
|
|
/***/ }),
|
|
|
|
/***/ "util":
|
|
/*!***********************!*\
|
|
!*** external "util" ***!
|
|
\***********************/
|
|
/***/ ((module) => {
|
|
|
|
"use strict";
|
|
module.exports = require("util");
|
|
|
|
/***/ })
|
|
|
|
/******/ });
|
|
/************************************************************************/
|
|
/******/ // The module cache
|
|
/******/ var __webpack_module_cache__ = {};
|
|
/******/
|
|
/******/ // The require function
|
|
/******/ function __webpack_require__(moduleId) {
|
|
/******/ // Check if module is in cache
|
|
/******/ var cachedModule = __webpack_module_cache__[moduleId];
|
|
/******/ if (cachedModule !== undefined) {
|
|
/******/ return cachedModule.exports;
|
|
/******/ }
|
|
/******/ // Create a new module (and put it into the cache)
|
|
/******/ var module = __webpack_module_cache__[moduleId] = {
|
|
/******/ // no module.id needed
|
|
/******/ // no module.loaded needed
|
|
/******/ exports: {}
|
|
/******/ };
|
|
/******/
|
|
/******/ // Execute the module function
|
|
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
/******/
|
|
/******/ // Return the exports of the module
|
|
/******/ return module.exports;
|
|
/******/ }
|
|
/******/
|
|
/************************************************************************/
|
|
/******/ /* webpack/runtime/compat get default export */
|
|
/******/ (() => {
|
|
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
|
/******/ __webpack_require__.n = (module) => {
|
|
/******/ var getter = module && module.__esModule ?
|
|
/******/ () => (module['default']) :
|
|
/******/ () => (module);
|
|
/******/ __webpack_require__.d(getter, { a: getter });
|
|
/******/ return getter;
|
|
/******/ };
|
|
/******/ })();
|
|
/******/
|
|
/******/ /* webpack/runtime/define property getters */
|
|
/******/ (() => {
|
|
/******/ // define getter functions for harmony exports
|
|
/******/ __webpack_require__.d = (exports, definition) => {
|
|
/******/ for(var key in definition) {
|
|
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
|
|
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
|
|
/******/ }
|
|
/******/ }
|
|
/******/ };
|
|
/******/ })();
|
|
/******/
|
|
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
|
/******/ (() => {
|
|
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
|
/******/ })();
|
|
/******/
|
|
/******/ /* webpack/runtime/make namespace object */
|
|
/******/ (() => {
|
|
/******/ // define __esModule on exports
|
|
/******/ __webpack_require__.r = (exports) => {
|
|
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
|
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
/******/ }
|
|
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
|
/******/ };
|
|
/******/ })();
|
|
/******/
|
|
/************************************************************************/
|
|
var __webpack_exports__ = {};
|
|
// This entry need to be wrapped in an IIFE because it need to be in strict mode.
|
|
(() => {
|
|
"use strict";
|
|
/*!**********************!*\
|
|
!*** ./src/index.ts ***!
|
|
\**********************/
|
|
__webpack_require__.r(__webpack_exports__);
|
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
/* harmony export */ "ElectronChromeExtensions": () => (/* reexport safe */ _browser__WEBPACK_IMPORTED_MODULE_0__.ElectronChromeExtensions),
|
|
/* harmony export */ "Extensions": () => (/* reexport safe */ _browser__WEBPACK_IMPORTED_MODULE_0__.Extensions)
|
|
/* harmony export */ });
|
|
/* harmony import */ var _browser__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./browser */ "./src/browser/index.ts");
|
|
|
|
})();
|
|
|
|
module.exports = __webpack_exports__;
|
|
/******/ })()
|
|
;
|
|
//# sourceMappingURL=index.js.map
|