mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2024-11-21 17:16:42 +03:00
Upgrade to Manifest V3 (#1714)
* Update Chrome manifest.json * Remove use of window in background * Test devpanel * Inject pageScript using new API * Keep connection from devpanel to background alive * Keep connection from content script to background alive * Replace page action with action * Cleanup syncOptions * Update options to not rely on background page access * Start work on updating popup * Updates * Remove window * Get opening in a separate window working * Remove pageScriptWrap * Add socket to panelStore * Fix tests * Try to use MV3 for Firefox * Fix path * Fix Chrome E2E tests * Revert unintentional change * Skip Electron tests for now Looks like they're still working through stuff in https://github.com/electron/electron/issues/41613 * Better image centering The Firefox popup did not like the old CSS. This is still not perfect, but it's better than it was. * Create shaggy-taxis-cross.md
This commit is contained in:
parent
61ec00f505
commit
83b2c19a11
5
.changeset/shaggy-taxis-cross.md
Normal file
5
.changeset/shaggy-taxis-cross.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'remotedev-redux-devtools-extension': minor
|
||||
---
|
||||
|
||||
Upgrade to Manifest V3
|
|
@ -5,7 +5,7 @@ import pug from 'pug';
|
|||
const args = process.argv.slice(2);
|
||||
const prod = !args.includes('--dev');
|
||||
|
||||
const commonEsbuildOptions = {
|
||||
await esbuild.build({
|
||||
bundle: true,
|
||||
logLevel: 'info',
|
||||
outdir: 'dist',
|
||||
|
@ -15,40 +15,24 @@ const commonEsbuildOptions = {
|
|||
'process.env.NODE_ENV': prod ? '"production"' : '"development"',
|
||||
'process.env.BABEL_ENV': prod ? '"production"' : '"development"',
|
||||
},
|
||||
};
|
||||
|
||||
await esbuild.build({
|
||||
...commonEsbuildOptions,
|
||||
entryPoints: [
|
||||
{ out: 'background.bundle', in: 'src/background/index.ts' },
|
||||
{ out: 'options.bundle', in: 'src/options/index.tsx' },
|
||||
{ out: 'window.bundle', in: 'src/window/index.tsx' },
|
||||
{ out: 'remote.bundle', in: 'src/remote/index.tsx' },
|
||||
{ out: 'devpanel.bundle', in: 'src/devpanel/index.tsx' },
|
||||
{ out: 'devtools.bundle', in: 'src/devtools/index.ts' },
|
||||
{ out: 'content.bundle', in: 'src/contentScript/index.ts' },
|
||||
{ out: 'page.bundle', in: 'src/pageScript/index.ts' },
|
||||
...(prod ? [] : [{ out: 'pagewrap.bundle', in: 'src/pageScriptWrap.ts' }]),
|
||||
],
|
||||
loader: {
|
||||
'.woff2': 'file',
|
||||
},
|
||||
});
|
||||
|
||||
if (prod) {
|
||||
await esbuild.build({
|
||||
...commonEsbuildOptions,
|
||||
entryPoints: [{ out: 'pagewrap.bundle', in: 'src/pageScriptWrap.ts' }],
|
||||
loader: {
|
||||
'.js': 'text',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log();
|
||||
|
||||
console.log('Creating HTML files...');
|
||||
const htmlFiles = ['devpanel', 'devtools', 'options', 'remote', 'window'];
|
||||
const htmlFiles = ['devpanel', 'devtools', 'options', 'remote'];
|
||||
for (const htmlFile of htmlFiles) {
|
||||
fs.writeFileSync(
|
||||
`dist/${htmlFile}.html`,
|
||||
|
|
|
@ -3,26 +3,20 @@
|
|||
"name": "Redux DevTools",
|
||||
"description": "Redux DevTools for debugging application's state changes.",
|
||||
"homepage_url": "https://github.com/reduxjs/redux-devtools",
|
||||
"manifest_version": 2,
|
||||
"page_action": {
|
||||
"manifest_version": 3,
|
||||
"action": {
|
||||
"default_icon": "img/logo/gray.png",
|
||||
"default_title": "Redux DevTools",
|
||||
"default_popup": "window.html#popup"
|
||||
"default_popup": "devpanel.html#popup"
|
||||
},
|
||||
"commands": {
|
||||
"devtools-left": {
|
||||
"description": "DevTools window to left"
|
||||
},
|
||||
"devtools-right": {
|
||||
"description": "DevTools window to right"
|
||||
},
|
||||
"devtools-bottom": {
|
||||
"description": "DevTools window to bottom"
|
||||
"devtools-window": {
|
||||
"description": "DevTools window"
|
||||
},
|
||||
"devtools-remote": {
|
||||
"description": "Remote DevTools"
|
||||
},
|
||||
"_execute_page_action": {
|
||||
"_execute_action": {
|
||||
"suggested_key": {
|
||||
"default": "Ctrl+Shift+E"
|
||||
}
|
||||
|
@ -34,36 +28,37 @@
|
|||
"128": "img/logo/128x128.png"
|
||||
},
|
||||
"options_ui": {
|
||||
"page": "options.html",
|
||||
"chrome_style": true
|
||||
"page": "options.html"
|
||||
},
|
||||
"background": {
|
||||
"scripts": ["background.bundle.js"],
|
||||
"persistent": false
|
||||
"service_worker": "background.bundle.js"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"exclude_globs": ["https://www.google*"],
|
||||
"js": ["content.bundle.js", "pagewrap.bundle.js"],
|
||||
"js": ["content.bundle.js"],
|
||||
"run_at": "document_start",
|
||||
"all_frames": true
|
||||
},
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"exclude_globs": ["https://www.google*"],
|
||||
"js": ["page.bundle.js"],
|
||||
"run_at": "document_start",
|
||||
"all_frames": true,
|
||||
"world": "MAIN"
|
||||
}
|
||||
],
|
||||
"devtools_page": "devtools.html",
|
||||
"web_accessible_resources": ["page.bundle.js"],
|
||||
"externally_connectable": {
|
||||
"ids": ["*"]
|
||||
},
|
||||
"permissions": [
|
||||
"notifications",
|
||||
"contextMenus",
|
||||
"storage",
|
||||
"file:///*",
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
],
|
||||
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'; style-src * 'unsafe-inline'; img-src 'self' data:;",
|
||||
"permissions": ["notifications", "contextMenus", "storage"],
|
||||
"host_permissions": ["file:///*", "http://*/*", "https://*/*"],
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self'; object-src 'self'; style-src * 'unsafe-inline'; img-src 'self' data:;"
|
||||
},
|
||||
"update_url": "https://clients2.google.com/service/update2/crx",
|
||||
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsdJEPwY92xUACA9CcDBDBmbdbp8Ap3cKQ0DJTUuVQvqb4FQAv8RtKY3iUjGvdwuAcSJQIZwHXcP2aNDH3TiFik/NhRK2GRW8X3OZyTdkuDueABGP2KEX8q1WQDgjX/rPIinGYztUrvoICw/UerMPwNW62jwGoVU3YhAGf+15CgX2Y6a4tppnf/+1mPedKPidh0RsM+aJY98rX+r1SPAHPcGzMjocLkqcT75DZBXer8VQN14tOOzRCd6T6oy7qm7eWru8lJwcY66qMQvhk0osqEod2G3nA7aTWpmqPFS66VEiecP9PgZlp8gQdgZ3dFhA62exydlD55JuRhiMIR63yQIDAQAB"
|
||||
}
|
||||
|
|
|
@ -1,28 +1,22 @@
|
|||
{
|
||||
"version": "3.1.10",
|
||||
"name": "Redux DevTools",
|
||||
"manifest_version": 2,
|
||||
"manifest_version": 3,
|
||||
"description": "Redux Developer Tools for debugging application state changes.",
|
||||
"homepage_url": "https://github.com/reduxjs/redux-devtools",
|
||||
"applications": {
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "extension@redux.devtools"
|
||||
}
|
||||
},
|
||||
"page_action": {
|
||||
"action": {
|
||||
"default_icon": "img/logo/38x38.png",
|
||||
"default_title": "Redux DevTools",
|
||||
"default_popup": "window.html#popup"
|
||||
"default_popup": "devpanel.html#popup"
|
||||
},
|
||||
"commands": {
|
||||
"devtools-left": {
|
||||
"description": "DevTools window to left"
|
||||
},
|
||||
"devtools-right": {
|
||||
"description": "DevTools window to right"
|
||||
},
|
||||
"devtools-bottom": {
|
||||
"description": "DevTools window to bottom"
|
||||
"devtools-window": {
|
||||
"description": "DevTools window"
|
||||
},
|
||||
"devtools-remote": {
|
||||
"description": "Remote DevTools"
|
||||
|
@ -42,21 +36,22 @@
|
|||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["content.bundle.js", "pagewrap.bundle.js"],
|
||||
"js": ["content.bundle.js"],
|
||||
"run_at": "document_start",
|
||||
"all_frames": true
|
||||
},
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["page.bundle.js"],
|
||||
"run_at": "document_start",
|
||||
"all_frames": true,
|
||||
"world": "MAIN"
|
||||
}
|
||||
],
|
||||
"devtools_page": "devtools.html",
|
||||
"web_accessible_resources": ["page.bundle.js"],
|
||||
"permissions": [
|
||||
"notifications",
|
||||
"contextMenus",
|
||||
"tabs",
|
||||
"storage",
|
||||
"file:///*",
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
],
|
||||
"content_security_policy": "script-src 'self'; object-src 'self'; img-src 'self' data:;"
|
||||
"permissions": ["notifications", "contextMenus", "tabs", "storage"],
|
||||
"host_permissions": ["file:///*", "http://*/*", "https://*/*"],
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self'; object-src 'self'; img-src 'self' data:;"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
TopButtons,
|
||||
} from '@redux-devtools/app';
|
||||
import { GoBroadcast } from 'react-icons/go';
|
||||
import { MdBorderBottom, MdBorderLeft, MdBorderRight } from 'react-icons/md';
|
||||
import { MdOutlineWindow } from 'react-icons/md';
|
||||
import type { Position } from '../pageScript/api/openWindow';
|
||||
import type { SingleMessage } from '../background/store/apiMiddleware';
|
||||
|
||||
|
@ -98,31 +98,13 @@ class Actions extends Component<Props> {
|
|||
<DispatcherButton dispatcherIsOpen={this.props.dispatcherIsOpen} />
|
||||
)}
|
||||
<Divider />
|
||||
{!window.isElectron && position !== '#left' && (
|
||||
{!window.isElectron && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.openWindow('left');
|
||||
this.openWindow('window');
|
||||
}}
|
||||
>
|
||||
<MdBorderLeft />
|
||||
</Button>
|
||||
)}
|
||||
{!window.isElectron && position !== '#right' && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.openWindow('right');
|
||||
}}
|
||||
>
|
||||
<MdBorderRight />
|
||||
</Button>
|
||||
)}
|
||||
{!window.isElectron && position !== '#bottom' && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.openWindow('bottom');
|
||||
}}
|
||||
>
|
||||
<MdBorderBottom />
|
||||
<MdOutlineWindow />
|
||||
</Button>
|
||||
)}
|
||||
{!window.isElectron && (
|
||||
|
|
|
@ -2,29 +2,23 @@ import openDevToolsWindow, { DevToolsPosition } from './openWindow';
|
|||
|
||||
export function createMenu() {
|
||||
const menus = [
|
||||
{ id: 'devtools-left', title: 'To left' },
|
||||
{ id: 'devtools-right', title: 'To right' },
|
||||
{ id: 'devtools-bottom', title: 'To bottom' },
|
||||
{
|
||||
id: 'devtools-panel',
|
||||
title: 'Open in a panel (enable in browser settings)',
|
||||
},
|
||||
{ id: 'devtools-window', title: 'Open in a window' },
|
||||
{ id: 'devtools-remote', title: 'Open Remote DevTools' },
|
||||
];
|
||||
|
||||
let shortcuts: { [commandName: string]: string | undefined } = {};
|
||||
chrome.commands.getAll((commands) => {
|
||||
commands.forEach(({ name, shortcut }) => {
|
||||
for (const { name, shortcut } of commands) {
|
||||
shortcuts[name!] = shortcut;
|
||||
});
|
||||
}
|
||||
|
||||
menus.forEach(({ id, title }) => {
|
||||
for (const { id, title } of menus) {
|
||||
chrome.contextMenus.create({
|
||||
id: id,
|
||||
title: title + (shortcuts[id] ? ' (' + shortcuts[id] + ')' : ''),
|
||||
contexts: ['all'],
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +1,22 @@
|
|||
import '../chromeApiMock';
|
||||
import { Store } from 'redux';
|
||||
import configureStore, { BackgroundAction } from './store/backgroundStore';
|
||||
import configureStore from './store/backgroundStore';
|
||||
import openDevToolsWindow, { DevToolsPosition } from './openWindow';
|
||||
import { createMenu, removeMenu } from './contextMenus';
|
||||
import syncOptions from '../options/syncOptions';
|
||||
import { BackgroundState } from './store/backgroundReducer';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
store: Store<BackgroundState, BackgroundAction>;
|
||||
}
|
||||
}
|
||||
import { getOptions } from '../options/syncOptions';
|
||||
|
||||
// Expose the extension's store globally to access it from the windows
|
||||
// via chrome.runtime.getBackgroundPage
|
||||
window.store = configureStore();
|
||||
export const store = configureStore();
|
||||
|
||||
// Listen for keyboard shortcuts
|
||||
chrome.commands.onCommand.addListener((shortcut) => {
|
||||
openDevToolsWindow(shortcut as DevToolsPosition);
|
||||
});
|
||||
|
||||
// Create the context menu when installed
|
||||
// Disable the action by default and create the context menu when installed
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
syncOptions().get((option) => {
|
||||
chrome.action.disable();
|
||||
|
||||
getOptions((option) => {
|
||||
if (option.showContextMenus) createMenu();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { LIFTED_ACTION } from '@redux-devtools/app';
|
||||
import { store } from './index';
|
||||
|
||||
export function getReport(
|
||||
reportId: string,
|
||||
|
@ -24,7 +25,7 @@ export function getReport(
|
|||
.then((json) => {
|
||||
const { payload, preloadedState } = json;
|
||||
if (!payload) return;
|
||||
window.store.dispatch({
|
||||
store.dispatch({
|
||||
type: LIFTED_ACTION,
|
||||
message: 'IMPORT',
|
||||
state: JSON.stringify({ payload, preloadedState }),
|
||||
|
|
|
@ -1,83 +1,34 @@
|
|||
export type DevToolsPosition =
|
||||
| 'devtools-left'
|
||||
| 'devtools-right'
|
||||
| 'devtools-bottom'
|
||||
| 'devtools-panel'
|
||||
| 'devtools-remote';
|
||||
export type DevToolsPosition = 'devtools-window' | 'devtools-remote';
|
||||
|
||||
let windows: { [K in DevToolsPosition]?: number } = {};
|
||||
let lastPosition: DevToolsPosition | null = null;
|
||||
|
||||
export default function openDevToolsWindow(position: DevToolsPosition) {
|
||||
function popWindow(
|
||||
action: string,
|
||||
url: string,
|
||||
customOptions: chrome.windows.CreateData & chrome.windows.UpdateInfo,
|
||||
) {
|
||||
function focusIfExist(callback: () => void) {
|
||||
if (!windows[position]) {
|
||||
callback();
|
||||
lastPosition = position;
|
||||
createWindow(position);
|
||||
} else {
|
||||
let params = { focused: true };
|
||||
if (lastPosition !== position && position !== 'devtools-panel') {
|
||||
params = { ...params, ...customOptions };
|
||||
}
|
||||
chrome.windows.update(windows[position]!, params, () => {
|
||||
lastPosition = null;
|
||||
if (chrome.runtime.lastError) callback();
|
||||
chrome.windows.update(windows[position]!, { focused: true }, () => {
|
||||
if (chrome.runtime.lastError) createWindow(position);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
focusIfExist(() => {
|
||||
let options: chrome.windows.CreateData = {
|
||||
type: 'popup',
|
||||
...customOptions,
|
||||
};
|
||||
if (action === 'open') {
|
||||
options.url = chrome.extension.getURL(
|
||||
url + '#' + position.substr(position.indexOf('-') + 1),
|
||||
);
|
||||
chrome.windows.create(options, (win) => {
|
||||
function createWindow(position: DevToolsPosition) {
|
||||
const url = chrome.runtime.getURL(getPath(position));
|
||||
chrome.windows.create({ type: 'popup', url }, (win) => {
|
||||
windows[position] = win!.id;
|
||||
if (navigator.userAgent.indexOf('Firefox') !== -1) {
|
||||
chrome.windows.update(win!.id!, {
|
||||
focused: true,
|
||||
...customOptions,
|
||||
});
|
||||
}
|
||||
});
|
||||
chrome.windows.update(win!.id!, { focused: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let params: chrome.windows.CreateData & chrome.windows.UpdateInfo = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 380,
|
||||
height: window.screen.availHeight,
|
||||
};
|
||||
let url = 'window.html';
|
||||
function getPath(position: DevToolsPosition) {
|
||||
switch (position) {
|
||||
case 'devtools-right':
|
||||
params.left =
|
||||
(window.screen as unknown as { availLeft: number }).availLeft +
|
||||
window.screen.availWidth -
|
||||
params.width!;
|
||||
break;
|
||||
case 'devtools-bottom':
|
||||
params.height = 420;
|
||||
params.top = window.screen.height - params.height;
|
||||
params.width = window.screen.availWidth;
|
||||
break;
|
||||
case 'devtools-panel':
|
||||
params.type = 'panel';
|
||||
break;
|
||||
case 'devtools-window':
|
||||
return 'devpanel.html';
|
||||
case 'devtools-remote':
|
||||
params = { width: 850, height: 600 };
|
||||
url = 'remote.html';
|
||||
break;
|
||||
return 'remote.html';
|
||||
default:
|
||||
throw new Error(`Unrecognized position: ${position}`);
|
||||
}
|
||||
popWindow('open', url, params);
|
||||
}
|
||||
|
|
|
@ -11,11 +11,7 @@ import {
|
|||
TOGGLE_PERSIST,
|
||||
UPDATE_STATE,
|
||||
} from '@redux-devtools/app';
|
||||
import syncOptions, {
|
||||
Options,
|
||||
OptionsMessage,
|
||||
SyncOptions,
|
||||
} from '../../options/syncOptions';
|
||||
import type { Options, OptionsMessage } from '../../options/syncOptions';
|
||||
import openDevToolsWindow, { DevToolsPosition } from '../openWindow';
|
||||
import { getReport } from '../logging';
|
||||
import { Action, Dispatch, Middleware } from 'redux';
|
||||
|
@ -32,6 +28,7 @@ import { LiftedState } from '@redux-devtools/instrument';
|
|||
import type { BackgroundAction, LiftedActionAction } from './backgroundStore';
|
||||
import type { Position } from '../../pageScript/api/openWindow';
|
||||
import type { BackgroundState } from './backgroundReducer';
|
||||
import { store } from '../index';
|
||||
|
||||
interface TabMessageBase {
|
||||
readonly type: string;
|
||||
|
@ -51,6 +48,11 @@ interface StopAction extends TabMessageBase {
|
|||
readonly id?: never;
|
||||
}
|
||||
|
||||
interface OptionsAction {
|
||||
readonly type: 'OPTIONS';
|
||||
readonly options: Options;
|
||||
}
|
||||
|
||||
interface DispatchAction extends TabMessageBase {
|
||||
readonly type: 'DISPATCH';
|
||||
readonly action: AppDispatchAction;
|
||||
|
@ -196,7 +198,7 @@ interface SplitUpdateStateAction<S, A extends Action<string>> {
|
|||
export type TabMessage =
|
||||
| StartAction
|
||||
| StopAction
|
||||
| OptionsMessage
|
||||
| OptionsAction
|
||||
| DispatchAction
|
||||
| ImportAction
|
||||
| ActionAction
|
||||
|
@ -247,7 +249,6 @@ const chunks: {
|
|||
>;
|
||||
} = {};
|
||||
let monitors = 0;
|
||||
let isMonitored = false;
|
||||
|
||||
const getId = (sender: chrome.runtime.MessageSender, name?: string) =>
|
||||
sender.tab ? sender.tab.id! : name || sender.id!;
|
||||
|
@ -261,22 +262,18 @@ type MonitorAction<S, A extends Action<string>> =
|
|||
// Chrome message limit is 64 MB, but we're using 32 MB to include other object's parts
|
||||
const maxChromeMsgSize = 32 * 1024 * 1024;
|
||||
|
||||
// TODO Clean up args
|
||||
function toMonitors<S, A extends Action<string>>(
|
||||
action: MonitorAction<S, A>,
|
||||
tabId?: string | number,
|
||||
verbose?: boolean,
|
||||
) {
|
||||
for (const monitorPort of Object.values(connections.monitor)) {
|
||||
monitorPort.postMessage(
|
||||
verbose || action.type === 'ERROR' || action.type === SET_PERSIST
|
||||
? action
|
||||
: { type: UPDATE_STATE },
|
||||
);
|
||||
}
|
||||
|
||||
for (const panelPort of Object.values(connections.panel)) {
|
||||
for (const port of [
|
||||
...Object.values(connections.monitor),
|
||||
...Object.values(connections.panel),
|
||||
]) {
|
||||
try {
|
||||
panelPort.postMessage(action);
|
||||
port.postMessage(action);
|
||||
} catch (err) {
|
||||
if (
|
||||
action.type !== UPDATE_STATE ||
|
||||
|
@ -307,11 +304,11 @@ function toMonitors<S, A extends Action<string>>(
|
|||
value;
|
||||
}
|
||||
|
||||
panelPort.postMessage({ ...action, request: splitMessageStart });
|
||||
port.postMessage({ ...action, request: splitMessageStart });
|
||||
|
||||
for (let i = 0; i < toSplit.length; i++) {
|
||||
for (let j = 0; j < toSplit[i][1].length; j += maxChromeMsgSize) {
|
||||
panelPort.postMessage({
|
||||
port.postMessage({
|
||||
...action,
|
||||
request: {
|
||||
split: 'chunk',
|
||||
|
@ -324,7 +321,7 @@ function toMonitors<S, A extends Action<string>>(
|
|||
}
|
||||
}
|
||||
|
||||
panelPort.postMessage({ ...action, request: { split: 'end' } });
|
||||
port.postMessage({ ...action, request: { split: 'end' } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -346,7 +343,7 @@ function toContentScript(messageBody: ToContentScriptMessage) {
|
|||
type: message,
|
||||
action,
|
||||
state: nonReduxDispatch(
|
||||
window.store,
|
||||
store,
|
||||
message,
|
||||
instanceId,
|
||||
action as AppDispatchAction,
|
||||
|
@ -360,7 +357,7 @@ function toContentScript(messageBody: ToContentScriptMessage) {
|
|||
type: message,
|
||||
action,
|
||||
state: nonReduxDispatch(
|
||||
window.store,
|
||||
store,
|
||||
message,
|
||||
instanceId,
|
||||
action as unknown as AppDispatchAction,
|
||||
|
@ -374,7 +371,7 @@ function toContentScript(messageBody: ToContentScriptMessage) {
|
|||
type: message,
|
||||
action,
|
||||
state: nonReduxDispatch(
|
||||
window.store,
|
||||
store,
|
||||
message,
|
||||
instanceId,
|
||||
action as unknown as AppDispatchAction,
|
||||
|
@ -388,7 +385,7 @@ function toContentScript(messageBody: ToContentScriptMessage) {
|
|||
type: message,
|
||||
action,
|
||||
state: nonReduxDispatch(
|
||||
window.store,
|
||||
store,
|
||||
message,
|
||||
instanceId,
|
||||
action as unknown as AppDispatchAction,
|
||||
|
@ -402,7 +399,7 @@ function toContentScript(messageBody: ToContentScriptMessage) {
|
|||
type: message,
|
||||
action,
|
||||
state: nonReduxDispatch(
|
||||
window.store,
|
||||
store,
|
||||
message,
|
||||
instanceId,
|
||||
action as AppDispatchAction,
|
||||
|
@ -414,14 +411,12 @@ function toContentScript(messageBody: ToContentScriptMessage) {
|
|||
}
|
||||
|
||||
function toAllTabs(msg: TabMessage) {
|
||||
const tabs = connections.tab;
|
||||
Object.keys(tabs).forEach((id) => {
|
||||
tabs[id].postMessage(msg);
|
||||
});
|
||||
for (const tabPort of Object.values(connections.tab)) {
|
||||
tabPort.postMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
function monitorInstances(shouldMonitor: boolean, id?: string) {
|
||||
if (!id && isMonitored === shouldMonitor) return;
|
||||
const action = {
|
||||
type: shouldMonitor ? ('START' as const) : ('STOP' as const),
|
||||
};
|
||||
|
@ -430,11 +425,10 @@ function monitorInstances(shouldMonitor: boolean, id?: string) {
|
|||
} else {
|
||||
toAllTabs(action);
|
||||
}
|
||||
isMonitored = shouldMonitor;
|
||||
}
|
||||
|
||||
function getReducerError() {
|
||||
const instancesState = window.store.getState().instances;
|
||||
const instancesState = store.getState().instances;
|
||||
const payload = instancesState.states[instancesState.current];
|
||||
const computedState = payload.computedStates[payload.currentStateIndex];
|
||||
if (!computedState) return false;
|
||||
|
@ -442,11 +436,11 @@ function getReducerError() {
|
|||
}
|
||||
|
||||
function togglePersist() {
|
||||
const state = window.store.getState();
|
||||
const state = store.getState();
|
||||
if (state.instances.persisted) {
|
||||
Object.keys(state.instances.connections).forEach((id) => {
|
||||
if (connections.tab[id]) return;
|
||||
window.store.dispatch({ type: REMOVE_INSTANCE, id });
|
||||
store.dispatch({ type: REMOVE_INSTANCE, id });
|
||||
toMonitors({ type: 'NA', id });
|
||||
});
|
||||
}
|
||||
|
@ -461,34 +455,25 @@ interface OpenOptionsMessage {
|
|||
readonly type: 'OPEN_OPTIONS';
|
||||
}
|
||||
|
||||
interface GetOptionsMessage {
|
||||
readonly type: 'GET_OPTIONS';
|
||||
}
|
||||
|
||||
export type SingleMessage =
|
||||
| OpenMessage
|
||||
| OpenOptionsMessage
|
||||
| GetOptionsMessage;
|
||||
export type SingleMessage = OpenMessage | OpenOptionsMessage | OptionsMessage;
|
||||
|
||||
type BackgroundStoreMessage<S, A extends Action<string>> =
|
||||
| PageScriptToContentScriptMessageWithoutDisconnectOrInitInstance<S, A>
|
||||
| SplitMessage
|
||||
| SingleMessage;
|
||||
type BackgroundStoreResponse = { readonly options: Options };
|
||||
|
||||
// Receive messages from content scripts
|
||||
function messaging<S, A extends Action<string>>(
|
||||
request: BackgroundStoreMessage<S, A>,
|
||||
sender: chrome.runtime.MessageSender,
|
||||
sendResponse?: (response?: BackgroundStoreResponse) => void,
|
||||
) {
|
||||
let tabId = getId(sender);
|
||||
if (!tabId) return;
|
||||
if (sender.frameId) tabId = `${tabId}-${sender.frameId}`;
|
||||
|
||||
if (request.type === 'STOP') {
|
||||
if (!Object.keys(window.store.getState().instances.connections).length) {
|
||||
window.store.dispatch({ type: DISCONNECTED });
|
||||
if (!Object.keys(store.getState().instances.connections).length) {
|
||||
store.dispatch({ type: DISCONNECTED });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -496,10 +481,8 @@ function messaging<S, A extends Action<string>>(
|
|||
chrome.runtime.openOptionsPage();
|
||||
return;
|
||||
}
|
||||
if (request.type === 'GET_OPTIONS') {
|
||||
window.syncOptions.get((options) => {
|
||||
sendResponse!({ options });
|
||||
});
|
||||
if (request.type === 'OPTIONS') {
|
||||
toAllTabs({ type: 'OPTIONS', options: request.options });
|
||||
return;
|
||||
}
|
||||
if (request.type === 'GET_REPORT') {
|
||||
|
@ -507,12 +490,8 @@ function messaging<S, A extends Action<string>>(
|
|||
return;
|
||||
}
|
||||
if (request.type === 'OPEN') {
|
||||
let position: DevToolsPosition = 'devtools-left';
|
||||
if (
|
||||
['remote', 'panel', 'left', 'right', 'bottom'].indexOf(
|
||||
request.position,
|
||||
) !== -1
|
||||
) {
|
||||
let position: DevToolsPosition = 'devtools-window';
|
||||
if (['remote', 'window'].includes(request.position)) {
|
||||
position = ('devtools-' + request.position) as DevToolsPosition;
|
||||
}
|
||||
openDevToolsWindow(position);
|
||||
|
@ -560,7 +539,7 @@ function messaging<S, A extends Action<string>>(
|
|||
if (request.instanceId) {
|
||||
action.request.instanceId = instanceId;
|
||||
}
|
||||
window.store.dispatch(action);
|
||||
store.dispatch(action);
|
||||
|
||||
if (request.type === 'EXPORT') {
|
||||
toMonitors(action, tabId, true);
|
||||
|
@ -580,8 +559,8 @@ function disconnect(
|
|||
if (p) p.onDisconnect.removeListener(disconnectListener);
|
||||
delete connections[type][id];
|
||||
if (type === 'tab') {
|
||||
if (!window.store.getState().instances.persisted) {
|
||||
window.store.dispatch({ type: REMOVE_INSTANCE, id });
|
||||
if (!store.getState().instances.persisted) {
|
||||
store.dispatch({ type: REMOVE_INSTANCE, id });
|
||||
toMonitors({ type: 'NA', id });
|
||||
}
|
||||
} else {
|
||||
|
@ -595,21 +574,22 @@ function onConnect<S, A extends Action<string>>(port: chrome.runtime.Port) {
|
|||
let id: number | string;
|
||||
let listener;
|
||||
|
||||
window.store.dispatch({ type: CONNECTED, port });
|
||||
store.dispatch({ type: CONNECTED, port });
|
||||
|
||||
if (port.name === 'tab') {
|
||||
id = getId(port.sender!);
|
||||
if (port.sender!.frameId) id = `${id}-${port.sender!.frameId}`;
|
||||
connections.tab[id] = port;
|
||||
listener = (msg: ContentScriptToBackgroundMessage<S, A>) => {
|
||||
listener = (msg: ContentScriptToBackgroundMessage<S, A> | 'heartbeat') => {
|
||||
if (msg === 'heartbeat') return;
|
||||
if (msg.name === 'INIT_INSTANCE') {
|
||||
if (typeof id === 'number') {
|
||||
chrome.pageAction.show(id);
|
||||
chrome.pageAction.setIcon({ tabId: id, path: 'img/logo/38x38.png' });
|
||||
chrome.action.enable(id);
|
||||
chrome.action.setIcon({ tabId: id, path: 'img/logo/38x38.png' });
|
||||
}
|
||||
if (isMonitored) port.postMessage({ type: 'START' });
|
||||
port.postMessage({ type: 'START' });
|
||||
|
||||
const state = window.store.getState();
|
||||
const state = store.getState();
|
||||
if (state.instances.persisted) {
|
||||
const instanceId = `${id}/${msg.instanceId}`;
|
||||
const persistedState = state.instances.states[instanceId];
|
||||
|
@ -636,6 +616,11 @@ function onConnect<S, A extends Action<string>>(port: chrome.runtime.Port) {
|
|||
id = getId(port.sender!, port.name);
|
||||
connections.monitor[id] = port;
|
||||
monitorInstances(true);
|
||||
listener = (msg: BackgroundAction | 'heartbeat') => {
|
||||
if (msg === 'heartbeat') return;
|
||||
store.dispatch(msg);
|
||||
};
|
||||
port.onMessage.addListener(listener);
|
||||
monitors++;
|
||||
port.onDisconnect.addListener(disconnect('monitor', id));
|
||||
} else {
|
||||
|
@ -644,8 +629,9 @@ function onConnect<S, A extends Action<string>>(port: chrome.runtime.Port) {
|
|||
connections.panel[id] = port;
|
||||
monitorInstances(true, port.name);
|
||||
monitors++;
|
||||
listener = (msg: BackgroundAction) => {
|
||||
window.store.dispatch(msg);
|
||||
listener = (msg: BackgroundAction | 'heartbeat') => {
|
||||
if (msg === 'heartbeat') return;
|
||||
store.dispatch(msg);
|
||||
};
|
||||
port.onMessage.addListener(listener);
|
||||
port.onDisconnect.addListener(disconnect('panel', id, listener));
|
||||
|
@ -659,17 +645,9 @@ chrome.runtime.onMessageExternal.addListener(messaging);
|
|||
|
||||
chrome.notifications.onClicked.addListener((id) => {
|
||||
chrome.notifications.clear(id);
|
||||
openDevToolsWindow('devtools-right');
|
||||
openDevToolsWindow('devtools-window');
|
||||
});
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
syncOptions: SyncOptions;
|
||||
}
|
||||
}
|
||||
|
||||
window.syncOptions = syncOptions(toAllTabs); // Expose to the options page
|
||||
|
||||
const api: Middleware<{}, BackgroundState, Dispatch<BackgroundAction>> =
|
||||
(store) => (next) => (untypedAction) => {
|
||||
const action = untypedAction as BackgroundAction;
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import '../chromeApiMock';
|
||||
import {
|
||||
injectOptions,
|
||||
getOptionsFromBg,
|
||||
getOptions,
|
||||
isAllowed,
|
||||
Options,
|
||||
prefetchOptions,
|
||||
prepareOptionsForPage,
|
||||
} from '../options/syncOptions';
|
||||
import type { TabMessage } from '../background/store/apiMiddleware';
|
||||
import type {
|
||||
|
@ -84,6 +86,13 @@ interface UpdateAction {
|
|||
readonly source: typeof source;
|
||||
}
|
||||
|
||||
interface OptionsAction {
|
||||
readonly type: 'OPTIONS';
|
||||
readonly options: Options;
|
||||
readonly id: undefined;
|
||||
readonly source: typeof source;
|
||||
}
|
||||
|
||||
export type ContentScriptToPageScriptMessage =
|
||||
| StartAction
|
||||
| StopAction
|
||||
|
@ -91,7 +100,8 @@ export type ContentScriptToPageScriptMessage =
|
|||
| ImportAction
|
||||
| ActionAction
|
||||
| ExportAction
|
||||
| UpdateAction;
|
||||
| UpdateAction
|
||||
| OptionsAction;
|
||||
|
||||
interface ImportStatePayload<S, A extends Action<string>> {
|
||||
readonly type: 'IMPORT_STATE';
|
||||
|
@ -112,6 +122,7 @@ export type ListenerMessage<S, A extends Action<string>> =
|
|||
| ActionAction
|
||||
| ExportAction
|
||||
| UpdateAction
|
||||
| OptionsAction
|
||||
| ImportStateDispatchAction<S, A>;
|
||||
|
||||
function postToPageScript(message: ContentScriptToPageScriptMessage) {
|
||||
|
@ -156,8 +167,13 @@ function connect() {
|
|||
source,
|
||||
});
|
||||
}
|
||||
} else if ('options' in message) {
|
||||
injectOptions(message.options);
|
||||
} else if (message.type === 'OPTIONS') {
|
||||
postToPageScript({
|
||||
type: message.type,
|
||||
options: prepareOptionsForPage(message.options),
|
||||
id: undefined,
|
||||
source,
|
||||
});
|
||||
} else {
|
||||
postToPageScript({
|
||||
type: message.type,
|
||||
|
@ -289,7 +305,14 @@ function send<S, A extends Action<string>>(
|
|||
) {
|
||||
if (!connected) connect();
|
||||
if (message.type === 'INIT_INSTANCE') {
|
||||
getOptionsFromBg();
|
||||
getOptions((options) => {
|
||||
postToPageScript({
|
||||
type: 'OPTIONS',
|
||||
options: prepareOptionsForPage(options),
|
||||
id: undefined,
|
||||
source,
|
||||
});
|
||||
});
|
||||
postToBackground({ name: 'INIT_INSTANCE', instanceId: message.instanceId });
|
||||
} else {
|
||||
postToBackground({ name: 'RELAY', message });
|
||||
|
@ -317,4 +340,10 @@ function handleMessages<S, A extends Action<string>>(
|
|||
tryCatch(send, message);
|
||||
}
|
||||
|
||||
prefetchOptions();
|
||||
|
||||
window.addEventListener('message', handleMessages, false);
|
||||
|
||||
setInterval(() => {
|
||||
bg?.postMessage('heartbeat');
|
||||
}, 15000);
|
||||
|
|
|
@ -5,12 +5,13 @@ html
|
|||
meta(charset='UTF-8')
|
||||
title Redux DevTools
|
||||
include ../style.pug
|
||||
style.
|
||||
body {
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
body
|
||||
#root
|
||||
div(style='display: flex; justify-content: center; align-items: center')
|
||||
img(
|
||||
src='/img/loading.svg',
|
||||
height=300, width=350,
|
||||
)
|
||||
link(href='/devpanel.bundle.css', rel='stylesheet')
|
||||
script(src='/devpanel.bundle.js')
|
||||
|
|
|
@ -6,6 +6,7 @@ import { Persistor } from 'redux-persist';
|
|||
import {
|
||||
REMOVE_INSTANCE,
|
||||
StoreAction,
|
||||
StoreState,
|
||||
UPDATE_STATE,
|
||||
} from '@redux-devtools/app';
|
||||
import App from '../app/App';
|
||||
|
@ -18,19 +19,19 @@ import {
|
|||
SplitUpdateStateRequest,
|
||||
UpdateStateRequest,
|
||||
} from '../background/store/apiMiddleware';
|
||||
import type { StoreStateWithoutSocket } from './store/panelReducer';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
|
||||
const position = location.hash;
|
||||
const messageStyle: CSSProperties = {
|
||||
padding: '20px',
|
||||
paddingTop: '20px',
|
||||
width: '100%',
|
||||
textAlign: 'center',
|
||||
boxSizing: 'border-box',
|
||||
};
|
||||
|
||||
let rendered: boolean | undefined;
|
||||
let currentRoot: Root | undefined;
|
||||
let store: Store<StoreStateWithoutSocket, StoreAction> | undefined;
|
||||
let store: Store<StoreState, StoreAction> | undefined;
|
||||
let persistor: Persistor | undefined;
|
||||
let bgConnection: chrome.runtime.Port;
|
||||
let naTimeout: NodeJS.Timeout;
|
||||
|
@ -72,7 +73,12 @@ function renderNA() {
|
|||
.
|
||||
</div>
|
||||
);
|
||||
if (isChrome) {
|
||||
if (
|
||||
isChrome &&
|
||||
chrome &&
|
||||
chrome.devtools &&
|
||||
chrome.devtools.inspectedWindow
|
||||
) {
|
||||
chrome.devtools.inspectedWindow.getResources((resources) => {
|
||||
if (resources[0].url.substr(0, 4) === 'file') {
|
||||
message = (
|
||||
|
@ -101,17 +107,26 @@ function renderNA() {
|
|||
|
||||
let splitMessage: SplitUpdateStateRequest<unknown, Action<string>>;
|
||||
|
||||
function init(id: number) {
|
||||
function init() {
|
||||
renderNA();
|
||||
bgConnection = chrome.runtime.connect({
|
||||
name: id ? id.toString() : undefined,
|
||||
});
|
||||
|
||||
let name = 'monitor';
|
||||
if (chrome && chrome.devtools && chrome.devtools.inspectedWindow) {
|
||||
name += chrome.devtools.inspectedWindow.tabId;
|
||||
}
|
||||
bgConnection = chrome.runtime.connect({ name });
|
||||
|
||||
setInterval(() => {
|
||||
bgConnection.postMessage('heartbeat');
|
||||
}, 15000);
|
||||
|
||||
bgConnection.onMessage.addListener(
|
||||
<S, A extends Action<string>>(
|
||||
message: PanelMessageWithSplitAction<S, A>,
|
||||
) => {
|
||||
if (message.type === 'NA') {
|
||||
if (message.id === id) renderNA();
|
||||
// TODO Double-check this now that the name is different
|
||||
if (message.id === name) renderNA();
|
||||
else store!.dispatch({ type: REMOVE_INSTANCE, id: message.id });
|
||||
} else {
|
||||
if (!rendered) renderDevTools();
|
||||
|
@ -157,4 +172,7 @@ function init(id: number) {
|
|||
);
|
||||
}
|
||||
|
||||
init(chrome.devtools.inspectedWindow.tabId);
|
||||
if (position === '#popup') document.body.style.minWidth = '760px';
|
||||
if (position !== '#popup') document.body.style.minHeight = '100%';
|
||||
|
||||
init();
|
||||
|
|
|
@ -1,45 +1,29 @@
|
|||
import { combineReducers, Reducer } from 'redux';
|
||||
import {
|
||||
connection,
|
||||
ConnectionState,
|
||||
instances,
|
||||
InstancesState,
|
||||
monitor,
|
||||
MonitorState,
|
||||
notification,
|
||||
NotificationState,
|
||||
reports,
|
||||
ReportsState,
|
||||
section,
|
||||
SectionState,
|
||||
StateTreeSettings,
|
||||
socket,
|
||||
stateTreeSettings,
|
||||
StoreAction,
|
||||
StoreState,
|
||||
theme,
|
||||
ThemeState,
|
||||
} from '@redux-devtools/app';
|
||||
|
||||
export interface StoreStateWithoutSocket {
|
||||
readonly section: SectionState;
|
||||
readonly theme: ThemeState;
|
||||
readonly connection: ConnectionState;
|
||||
readonly monitor: MonitorState;
|
||||
readonly instances: InstancesState;
|
||||
readonly reports: ReportsState;
|
||||
readonly notification: NotificationState;
|
||||
readonly stateTreeSettings: StateTreeSettings;
|
||||
}
|
||||
|
||||
const rootReducer: Reducer<
|
||||
StoreStateWithoutSocket,
|
||||
StoreState,
|
||||
StoreAction,
|
||||
Partial<StoreStateWithoutSocket>
|
||||
Partial<StoreState>
|
||||
> = combineReducers({
|
||||
instances,
|
||||
monitor,
|
||||
reports,
|
||||
notification,
|
||||
section,
|
||||
socket,
|
||||
theme,
|
||||
connection,
|
||||
stateTreeSettings,
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import { createStore, applyMiddleware, Reducer, Store } from 'redux';
|
||||
import localForage from 'localforage';
|
||||
import { persistReducer, persistStore } from 'redux-persist';
|
||||
import { exportStateMiddleware, StoreAction } from '@redux-devtools/app';
|
||||
import {
|
||||
exportStateMiddleware,
|
||||
StoreAction,
|
||||
StoreState,
|
||||
} from '@redux-devtools/app';
|
||||
import panelDispatcher from './panelSyncMiddleware';
|
||||
import rootReducer, { StoreStateWithoutSocket } from './panelReducer';
|
||||
import rootReducer from './panelReducer';
|
||||
|
||||
const persistConfig = {
|
||||
key: 'redux-devtools',
|
||||
|
@ -11,8 +15,10 @@ const persistConfig = {
|
|||
storage: localForage,
|
||||
};
|
||||
|
||||
const persistedReducer: Reducer<StoreStateWithoutSocket, StoreAction> =
|
||||
persistReducer(persistConfig, rootReducer) as any;
|
||||
const persistedReducer: Reducer<StoreState, StoreAction> = persistReducer(
|
||||
persistConfig,
|
||||
rootReducer,
|
||||
) as any;
|
||||
|
||||
export default function configureStore(
|
||||
position: string,
|
||||
|
|
|
@ -7,23 +7,51 @@ import {
|
|||
TOGGLE_PERSIST,
|
||||
UPDATE_STATE,
|
||||
} from '@redux-devtools/app';
|
||||
import { Dispatch, Middleware } from 'redux';
|
||||
import { Dispatch, Middleware, MiddlewareAPI } from 'redux';
|
||||
|
||||
function selectInstance(
|
||||
tabId: number,
|
||||
store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>,
|
||||
next: (action: unknown) => unknown,
|
||||
) {
|
||||
const instances = store.getState().instances;
|
||||
if (instances.current === 'default') return;
|
||||
const connections = instances.connections[tabId];
|
||||
if (connections && connections.length === 1) {
|
||||
next({ type: SELECT_INSTANCE, selected: connections[0] });
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentTabId(next: (tabId: number) => void) {
|
||||
chrome.tabs.query(
|
||||
{
|
||||
active: true,
|
||||
lastFocusedWindow: true,
|
||||
},
|
||||
(tabs) => {
|
||||
const tab = tabs[0];
|
||||
if (!tab) return;
|
||||
next(tab.id!);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function panelDispatcher(
|
||||
bgConnection: chrome.runtime.Port,
|
||||
): Middleware<{}, StoreState, Dispatch<StoreAction>> {
|
||||
let autoselected = false;
|
||||
const tabId = chrome.devtools.inspectedWindow.tabId;
|
||||
|
||||
return (store) => (next) => (untypedAction) => {
|
||||
const action = untypedAction as StoreAction;
|
||||
|
||||
const result = next(action);
|
||||
if (!autoselected && action.type === UPDATE_STATE && tabId) {
|
||||
if (!autoselected && action.type === UPDATE_STATE) {
|
||||
autoselected = true;
|
||||
const connections = store.getState().instances.connections[tabId];
|
||||
if (connections && connections.length === 1) {
|
||||
next({ type: SELECT_INSTANCE, selected: connections[0] });
|
||||
|
||||
if (chrome.devtools && chrome.devtools.inspectedWindow) {
|
||||
selectInstance(chrome.devtools.inspectedWindow.tabId, store, next);
|
||||
} else {
|
||||
getCurrentTabId((tabId) => selectInstance(tabId, store, next));
|
||||
}
|
||||
}
|
||||
if (action.type === LIFTED_ACTION || action.type === TOGGLE_PERSIST) {
|
||||
|
|
|
@ -1,17 +1,6 @@
|
|||
function createPanel(url: string) {
|
||||
chrome.devtools.panels.create(
|
||||
'Redux',
|
||||
'img/logo/scalable.png',
|
||||
url,
|
||||
function () {},
|
||||
'devpanel.html',
|
||||
() => {},
|
||||
);
|
||||
}
|
||||
|
||||
if (chrome.runtime.getBackgroundPage) {
|
||||
// Check if the background page's object is accessible (not in incognito)
|
||||
chrome.runtime.getBackgroundPage((background) => {
|
||||
createPanel(background ? 'window.html' : 'devpanel.html');
|
||||
});
|
||||
} else {
|
||||
createPanel('devpanel.html');
|
||||
}
|
||||
|
|
|
@ -2,22 +2,25 @@ import '../chromeApiMock';
|
|||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import OptionsComponent from './Options';
|
||||
import { Options } from './syncOptions';
|
||||
import {
|
||||
getOptions,
|
||||
Options,
|
||||
OptionsMessage,
|
||||
saveOption,
|
||||
subscribeToOptions,
|
||||
} from './syncOptions';
|
||||
|
||||
chrome.runtime.getBackgroundPage((background) => {
|
||||
const syncOptions = background!.syncOptions;
|
||||
|
||||
const saveOption = <K extends keyof Options>(name: K, value: Options[K]) => {
|
||||
syncOptions.save(name, value);
|
||||
};
|
||||
subscribeToOptions((options) => {
|
||||
const message: OptionsMessage = { type: 'OPTIONS', options };
|
||||
chrome.runtime.sendMessage(message);
|
||||
});
|
||||
|
||||
const renderOptions = (options: Options) => {
|
||||
const root = createRoot(document.getElementById('root')!);
|
||||
root.render(<OptionsComponent options={options} saveOption={saveOption} />);
|
||||
};
|
||||
|
||||
syncOptions.subscribe(renderOptions);
|
||||
syncOptions.get((options) => {
|
||||
subscribeToOptions(renderOptions);
|
||||
getOptions((options) => {
|
||||
renderOptions(options);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -38,20 +38,21 @@ let options: Options | undefined;
|
|||
let subscribers: ((options: Options) => void)[] = [];
|
||||
|
||||
export interface OptionsMessage {
|
||||
readonly type: 'OPTIONS';
|
||||
readonly options: Options;
|
||||
}
|
||||
|
||||
type ToAllTabs = (msg: OptionsMessage) => void;
|
||||
|
||||
const save =
|
||||
(toAllTabs: ToAllTabs | undefined) =>
|
||||
<K extends keyof Options>(key: K, value: Options[K]) => {
|
||||
export const saveOption = <K extends keyof Options>(
|
||||
key: K,
|
||||
value: Options[K],
|
||||
) => {
|
||||
let obj: { [K1 in keyof Options]?: Options[K1] } = {};
|
||||
obj[key] = value;
|
||||
chrome.storage.sync.set(obj);
|
||||
options![key] = value;
|
||||
toAllTabs!({ options: options! });
|
||||
subscribers.forEach((s) => s(options!));
|
||||
for (const subscriber of subscribers) {
|
||||
subscriber(options!);
|
||||
}
|
||||
};
|
||||
|
||||
const migrateOldOptions = (oldOptions: OldOrNewOptions): Options => ({
|
||||
|
@ -71,7 +72,7 @@ const migrateOldOptions = (oldOptions: OldOrNewOptions): Options => ({
|
|||
: oldOptions.filter,
|
||||
});
|
||||
|
||||
const get = (callback: (options: Options) => void) => {
|
||||
export const getOptions = (callback: (options: Options) => void) => {
|
||||
if (options) callback(options);
|
||||
else {
|
||||
chrome.storage.sync.get(
|
||||
|
@ -98,67 +99,29 @@ const get = (callback: (options: Options) => void) => {
|
|||
}
|
||||
};
|
||||
|
||||
const subscribe = (callback: (options: Options) => void) => {
|
||||
export const prefetchOptions = () => getOptions(() => {});
|
||||
|
||||
export const subscribeToOptions = (callback: (options: Options) => void) => {
|
||||
subscribers = subscribers.concat(callback);
|
||||
};
|
||||
|
||||
const toReg = (str: string) =>
|
||||
str !== '' ? str.split('\n').filter(Boolean).join('|') : null;
|
||||
|
||||
export const injectOptions = (newOptions: Options) => {
|
||||
if (!newOptions) return;
|
||||
|
||||
options = {
|
||||
...newOptions,
|
||||
export const prepareOptionsForPage = (options: Options): Options => ({
|
||||
...options,
|
||||
allowlist:
|
||||
newOptions.filter !== FilterState.DO_NOT_FILTER
|
||||
? toReg(newOptions.allowlist)!
|
||||
: newOptions.allowlist,
|
||||
options.filter !== FilterState.DO_NOT_FILTER
|
||||
? toReg(options.allowlist)!
|
||||
: options.allowlist,
|
||||
denylist:
|
||||
newOptions.filter !== FilterState.DO_NOT_FILTER
|
||||
? toReg(newOptions.denylist)!
|
||||
: newOptions.denylist,
|
||||
};
|
||||
let s = document.createElement('script');
|
||||
s.type = 'text/javascript';
|
||||
s.appendChild(
|
||||
document.createTextNode(
|
||||
'window.devToolsOptions = Object.assign(window.devToolsOptions||{},' +
|
||||
JSON.stringify(options) +
|
||||
');',
|
||||
),
|
||||
);
|
||||
(document.head || document.documentElement).appendChild(s);
|
||||
s.parentNode!.removeChild(s);
|
||||
};
|
||||
|
||||
export const getOptionsFromBg = () => {
|
||||
/* chrome.runtime.sendMessage({ type: 'GET_OPTIONS' }, response => {
|
||||
if (response && response.options) injectOptions(response.options);
|
||||
options.filter !== FilterState.DO_NOT_FILTER
|
||||
? toReg(options.denylist)!
|
||||
: options.denylist,
|
||||
});
|
||||
*/
|
||||
get((newOptions) => {
|
||||
injectOptions(newOptions);
|
||||
}); // Legacy
|
||||
};
|
||||
|
||||
export const isAllowed = (localOptions = options) =>
|
||||
!localOptions ||
|
||||
localOptions.inject ||
|
||||
!localOptions.urls ||
|
||||
location.href.match(toReg(localOptions.urls)!);
|
||||
|
||||
export interface SyncOptions {
|
||||
readonly save: <K extends keyof Options>(key: K, value: Options[K]) => void;
|
||||
readonly get: (callback: (options: Options) => void) => void;
|
||||
readonly subscribe: (callback: (options: Options) => void) => void;
|
||||
}
|
||||
|
||||
export default function syncOptions(toAllTabs?: ToAllTabs): SyncOptions {
|
||||
if (toAllTabs && !options) get(() => {}); // Initialize
|
||||
return {
|
||||
save: save(toAllTabs),
|
||||
get: get,
|
||||
subscribe: subscribe,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Action } from 'redux';
|
||||
import type { PageScriptToContentScriptMessage } from './index';
|
||||
|
||||
export type Position = 'left' | 'right' | 'bottom' | 'panel' | 'remote';
|
||||
export type Position = 'window' | 'remote';
|
||||
|
||||
function post<S, A extends Action<string>>(
|
||||
message: PageScriptToContentScriptMessage<S, A>,
|
||||
|
@ -13,6 +13,6 @@ export default function openWindow(position?: Position) {
|
|||
post({
|
||||
source: '@devtools-page',
|
||||
type: 'OPEN',
|
||||
position: position || 'right',
|
||||
position: position ?? 'window',
|
||||
});
|
||||
}
|
||||
|
|
|
@ -432,6 +432,13 @@ function __REDUX_DEVTOOLS_EXTENSION__<S, A extends Action<string>>(
|
|||
serializeAction,
|
||||
);
|
||||
}
|
||||
return;
|
||||
case 'OPTIONS':
|
||||
window.devToolsOptions = Object.assign(
|
||||
window.devToolsOptions || {},
|
||||
message.options,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
// @ts-ignore
|
||||
import script from '../dist/page.bundle.js';
|
||||
|
||||
let s = document.createElement('script');
|
||||
s.type = 'text/javascript';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
s.appendChild(document.createTextNode(script));
|
||||
(document.head || document.documentElement).appendChild(s);
|
||||
s.parentNode!.removeChild(s);
|
||||
} else {
|
||||
s.src = chrome.extension.getURL('page.bundle.js');
|
||||
s.onload = function () {
|
||||
(this as HTMLScriptElement).parentNode!.removeChild(
|
||||
this as HTMLScriptElement,
|
||||
);
|
||||
};
|
||||
(document.head || document.documentElement).appendChild(s);
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { Provider } from 'react-redux';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
import { UPDATE_STATE } from '@redux-devtools/app';
|
||||
import App from '../app/App';
|
||||
import configureStore from './store/windowStore';
|
||||
import type { MonitorMessage } from '../background/store/apiMiddleware';
|
||||
|
||||
const position = location.hash;
|
||||
|
||||
chrome.runtime.getBackgroundPage((window) => {
|
||||
const { store } = window!;
|
||||
const { store: localStore, persistor } = configureStore(store, position);
|
||||
let name = 'monitor';
|
||||
if (chrome && chrome.devtools && chrome.devtools.inspectedWindow) {
|
||||
name += chrome.devtools.inspectedWindow.tabId;
|
||||
}
|
||||
const bg = chrome.runtime.connect({ name });
|
||||
const update = (action?: MonitorMessage) => {
|
||||
localStore.dispatch(action || { type: UPDATE_STATE });
|
||||
};
|
||||
bg.onMessage.addListener(update);
|
||||
update();
|
||||
|
||||
const root = createRoot(document.getElementById('root')!);
|
||||
root.render(
|
||||
<Provider store={localStore}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<App position={position} />
|
||||
</PersistGate>
|
||||
</Provider>,
|
||||
);
|
||||
});
|
||||
|
||||
if (position === '#popup') document.body.style.minWidth = '760px';
|
||||
if (position !== '#popup') document.body.style.minHeight = '100%';
|
|
@ -1,51 +0,0 @@
|
|||
import { Dispatch, Middleware, MiddlewareAPI } from 'redux';
|
||||
import {
|
||||
SELECT_INSTANCE,
|
||||
StoreAction,
|
||||
StoreState,
|
||||
UPDATE_STATE,
|
||||
} from '@redux-devtools/app';
|
||||
|
||||
function selectInstance(
|
||||
tabId: number,
|
||||
store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>,
|
||||
next: (action: unknown) => unknown,
|
||||
) {
|
||||
const instances = store.getState().instances;
|
||||
if (instances.current === 'default') return;
|
||||
const connections = instances.connections[tabId];
|
||||
if (connections && connections.length === 1) {
|
||||
next({ type: SELECT_INSTANCE, selected: connections[0] });
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentTabId(next: (tabId: number) => void) {
|
||||
chrome.tabs.query(
|
||||
{
|
||||
active: true,
|
||||
lastFocusedWindow: true,
|
||||
},
|
||||
(tabs) => {
|
||||
const tab = tabs[0];
|
||||
if (!tab) return;
|
||||
next(tab.id!);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const popupSelector: Middleware<{}, StoreState, Dispatch<StoreAction>> =
|
||||
(store) => (next) => (untypedAction) => {
|
||||
const action = untypedAction as StoreAction;
|
||||
|
||||
const result = next(action);
|
||||
if (action.type === UPDATE_STATE) {
|
||||
if (chrome.devtools && chrome.devtools.inspectedWindow) {
|
||||
selectInstance(chrome.devtools.inspectedWindow.tabId, store, next);
|
||||
} else {
|
||||
getCurrentTabId((tabId) => selectInstance(tabId, store, next));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export default popupSelector;
|
|
@ -1,34 +0,0 @@
|
|||
import {
|
||||
instancesInitialState,
|
||||
dispatchAction,
|
||||
UPDATE_STATE,
|
||||
SELECT_INSTANCE,
|
||||
LIFTED_ACTION,
|
||||
SET_PERSIST,
|
||||
} from '@redux-devtools/app';
|
||||
import type {
|
||||
ExpandedUpdateStateAction,
|
||||
WindowStoreAction,
|
||||
} from './windowStore';
|
||||
|
||||
export default function instances(
|
||||
state = instancesInitialState,
|
||||
action: WindowStoreAction,
|
||||
) {
|
||||
switch (action.type) {
|
||||
case UPDATE_STATE:
|
||||
return {
|
||||
...(action as ExpandedUpdateStateAction).instances,
|
||||
selected: state.selected,
|
||||
};
|
||||
case LIFTED_ACTION:
|
||||
if (action.message === 'DISPATCH') return dispatchAction(state, action);
|
||||
return state;
|
||||
case SELECT_INSTANCE:
|
||||
return { ...state, selected: action.selected };
|
||||
case SET_PERSIST:
|
||||
return { ...state, persisted: action.payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
import { combineReducers, Reducer } from 'redux';
|
||||
import {
|
||||
connection,
|
||||
monitor,
|
||||
notification,
|
||||
reports,
|
||||
section,
|
||||
socket,
|
||||
theme,
|
||||
stateTreeSettings,
|
||||
StoreState,
|
||||
} from '@redux-devtools/app';
|
||||
import instances from './instancesReducer';
|
||||
import type { WindowStoreAction } from './windowStore';
|
||||
|
||||
const rootReducer: Reducer<
|
||||
StoreState,
|
||||
WindowStoreAction,
|
||||
Partial<StoreState>
|
||||
> = combineReducers({
|
||||
instances,
|
||||
monitor,
|
||||
socket,
|
||||
reports,
|
||||
notification,
|
||||
section,
|
||||
theme,
|
||||
connection,
|
||||
stateTreeSettings,
|
||||
}) as any;
|
||||
|
||||
export default rootReducer;
|
|
@ -1,81 +0,0 @@
|
|||
import {
|
||||
createStore,
|
||||
compose,
|
||||
applyMiddleware,
|
||||
Store,
|
||||
StoreEnhancer,
|
||||
Reducer,
|
||||
} from 'redux';
|
||||
import localForage from 'localforage';
|
||||
import { persistReducer, persistStore } from 'redux-persist';
|
||||
import {
|
||||
api,
|
||||
CONNECT_REQUEST,
|
||||
exportStateMiddleware,
|
||||
InstancesState,
|
||||
StoreActionWithoutUpdateState,
|
||||
StoreState,
|
||||
UpdateStateAction,
|
||||
} from '@redux-devtools/app';
|
||||
import syncStores from './windowSyncMiddleware';
|
||||
import instanceSelector from './instanceSelectorMiddleware';
|
||||
import rootReducer from './windowReducer';
|
||||
import type { BackgroundState } from '../../background/store/backgroundReducer';
|
||||
import type { BackgroundAction } from '../../background/store/backgroundStore';
|
||||
import type {
|
||||
EmptyUpdateStateAction,
|
||||
NAAction,
|
||||
} from '../../background/store/apiMiddleware';
|
||||
|
||||
export interface ExpandedUpdateStateAction extends UpdateStateAction {
|
||||
readonly instances: InstancesState;
|
||||
}
|
||||
|
||||
export type WindowStoreAction =
|
||||
| StoreActionWithoutUpdateState
|
||||
| ExpandedUpdateStateAction
|
||||
| NAAction
|
||||
| EmptyUpdateStateAction;
|
||||
|
||||
const persistConfig = {
|
||||
key: 'redux-devtools',
|
||||
blacklist: ['instances', 'socket'],
|
||||
storage: localForage,
|
||||
};
|
||||
|
||||
const persistedReducer: Reducer<StoreState, WindowStoreAction> = persistReducer(
|
||||
persistConfig,
|
||||
rootReducer,
|
||||
) as any;
|
||||
|
||||
export default function configureStore(
|
||||
baseStore: Store<BackgroundState, BackgroundAction>,
|
||||
position: string,
|
||||
) {
|
||||
let enhancer: StoreEnhancer;
|
||||
const middlewares = [exportStateMiddleware, api, syncStores(baseStore)];
|
||||
if (!position || position === '#popup') {
|
||||
// select current tab instance for devPanel and pageAction
|
||||
middlewares.push(instanceSelector);
|
||||
}
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
enhancer = applyMiddleware(...middlewares);
|
||||
} else {
|
||||
enhancer = compose(
|
||||
applyMiddleware(...middlewares),
|
||||
window.__REDUX_DEVTOOLS_EXTENSION__
|
||||
? window.__REDUX_DEVTOOLS_EXTENSION__()
|
||||
: (noop: unknown) => noop,
|
||||
);
|
||||
}
|
||||
const store = createStore(persistedReducer, enhancer);
|
||||
const persistor = persistStore(store as Store, null, () => {
|
||||
if (store.getState().connection.type !== 'disabled') {
|
||||
store.dispatch({
|
||||
type: CONNECT_REQUEST,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return { store, persistor };
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
import {
|
||||
getActiveInstance,
|
||||
LIFTED_ACTION,
|
||||
StoreAction,
|
||||
StoreState,
|
||||
TOGGLE_PERSIST,
|
||||
UPDATE_STATE,
|
||||
} from '@redux-devtools/app';
|
||||
import { Dispatch, Middleware, Store } from 'redux';
|
||||
import type { BackgroundState } from '../../background/store/backgroundReducer';
|
||||
import type { BackgroundAction } from '../../background/store/backgroundStore';
|
||||
|
||||
const syncStores =
|
||||
(
|
||||
baseStore: Store<BackgroundState, BackgroundAction>,
|
||||
): Middleware<{}, StoreState, Dispatch<StoreAction>> =>
|
||||
(store) =>
|
||||
(next) =>
|
||||
(untypedAction) => {
|
||||
const action = untypedAction as StoreAction;
|
||||
|
||||
if (action.type === UPDATE_STATE) {
|
||||
return next({
|
||||
...action,
|
||||
instances: baseStore.getState().instances,
|
||||
});
|
||||
}
|
||||
if (action.type === LIFTED_ACTION || action.type === TOGGLE_PERSIST) {
|
||||
const instances = store.getState().instances;
|
||||
const instanceId = getActiveInstance(instances);
|
||||
const id = instances.options[instanceId].connectionId;
|
||||
baseStore.dispatch({ ...action, instanceId, id } as any);
|
||||
}
|
||||
return next(action);
|
||||
};
|
||||
|
||||
export default syncStores;
|
|
@ -1,18 +0,0 @@
|
|||
doctype html
|
||||
|
||||
html
|
||||
head
|
||||
meta(charset='UTF-8')
|
||||
title Redux DevTools
|
||||
include ../style.pug
|
||||
|
||||
body
|
||||
#root
|
||||
div(style='position: relative')
|
||||
img(
|
||||
src='/img/loading.svg',
|
||||
height=300, width=350,
|
||||
style='position: absolute; top: 50%; left: 50%; margin-top: -175px; margin-left: -175px;'
|
||||
)
|
||||
link(href='/window.bundle.css', rel='stylesheet')
|
||||
script(src='/window.bundle.js')
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from '../../../src/window/store/windowStore';
|
||||
import configureStore from '../../../src/devpanel/store/panelStore';
|
||||
import App from '../../../src/app/App';
|
||||
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
|
|
|
@ -20,16 +20,7 @@ describe('API', () => {
|
|||
expect(message).toEqual({
|
||||
source: '@devtools-page',
|
||||
type: 'OPEN',
|
||||
position: 'right',
|
||||
});
|
||||
|
||||
message = await listenMessage(() => {
|
||||
window.__REDUX_DEVTOOLS_EXTENSION__.open('left');
|
||||
});
|
||||
expect(message).toEqual({
|
||||
source: '@devtools-page',
|
||||
type: 'OPEN',
|
||||
position: 'left',
|
||||
position: 'window',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -27,9 +27,9 @@ describe('Chrome extension', function () {
|
|||
});
|
||||
|
||||
it("should open extension's window", async () => {
|
||||
await driver.get(`chrome-extension://${extensionId}/window.html#left`);
|
||||
await driver.get(`chrome-extension://${extensionId}/devpanel.html`);
|
||||
const url = await driver.getCurrentUrl();
|
||||
expect(url).toBe(`chrome-extension://${extensionId}/window.html#left`);
|
||||
expect(url).toBe(`chrome-extension://${extensionId}/devpanel.html`);
|
||||
});
|
||||
|
||||
it('should match document title', async () => {
|
||||
|
@ -37,25 +37,6 @@ describe('Chrome extension', function () {
|
|||
expect(title).toBe('Redux DevTools');
|
||||
});
|
||||
|
||||
it("should contain inspector monitor's component", async () => {
|
||||
await delay(1000);
|
||||
const val = await driver
|
||||
.findElement(webdriver.By.xpath('//div[@data-testid="inspector"]'))
|
||||
.getText();
|
||||
expect(val).toBeDefined();
|
||||
});
|
||||
|
||||
it('should contain an empty actions list', async () => {
|
||||
const val = await driver
|
||||
.findElement(webdriver.By.xpath('//div[@data-testid="actionListRows"]'))
|
||||
.getText();
|
||||
expect(val).toBe('');
|
||||
});
|
||||
|
||||
Object.keys(switchMonitorTests).forEach((description) =>
|
||||
it(description, () => switchMonitorTests[description](driver)),
|
||||
);
|
||||
|
||||
it('should get actions list', async () => {
|
||||
const url = 'https://zalmoxisus.github.io/examples/router/';
|
||||
await driver.executeScript(`window.open('${url}')`);
|
||||
|
@ -68,6 +49,7 @@ describe('Chrome extension', function () {
|
|||
|
||||
await driver.switchTo().window(tabs[0]);
|
||||
|
||||
await delay(1000);
|
||||
const result = await driver.wait(
|
||||
driver
|
||||
.findElement(webdriver.By.xpath('//div[@data-testid="actionListRows"]'))
|
||||
|
@ -80,4 +62,15 @@ describe('Chrome extension', function () {
|
|||
);
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should contain inspector monitor's component", async () => {
|
||||
const val = await driver
|
||||
.findElement(webdriver.By.xpath('//div[@data-testid="inspector"]'))
|
||||
.getText();
|
||||
expect(val).toBeDefined();
|
||||
});
|
||||
|
||||
Object.keys(switchMonitorTests).forEach((description) =>
|
||||
it(description, () => switchMonitorTests[description](driver)),
|
||||
);
|
||||
});
|
||||
|
|
|
@ -76,7 +76,7 @@ describe('DevTools panel for Electron', function () {
|
|||
expect(className).not.toMatch(/hidden/); // not hidden
|
||||
});
|
||||
|
||||
it('should have Redux DevTools UI on current tab', async () => {
|
||||
it.skip('should have Redux DevTools UI on current tab', async () => {
|
||||
await driver
|
||||
.switchTo()
|
||||
.frame(
|
||||
|
@ -87,7 +87,7 @@ describe('DevTools panel for Electron', function () {
|
|||
await delay(1000);
|
||||
});
|
||||
|
||||
it('should contain INIT action', async () => {
|
||||
it.skip('should contain INIT action', async () => {
|
||||
const element = await driver.wait(
|
||||
webdriver.until.elementLocated(
|
||||
webdriver.By.xpath('//div[@data-testid="actionListRows"]'),
|
||||
|
@ -99,7 +99,7 @@ describe('DevTools panel for Electron', function () {
|
|||
expect(val).toMatch(/@@INIT/);
|
||||
});
|
||||
|
||||
it("should contain Inspector monitor's component", async () => {
|
||||
it.skip("should contain Inspector monitor's component", async () => {
|
||||
const val = await driver
|
||||
.findElement(webdriver.By.xpath('//div[@data-testid="inspector"]'))
|
||||
.getText();
|
||||
|
@ -107,7 +107,7 @@ describe('DevTools panel for Electron', function () {
|
|||
});
|
||||
|
||||
Object.keys(switchMonitorTests).forEach((description) =>
|
||||
it(description, () => switchMonitorTests[description](driver)),
|
||||
it.skip(description, () => switchMonitorTests[description](driver)),
|
||||
);
|
||||
|
||||
/* it('should be no logs in console of main window', async () => {
|
||||
|
|
Loading…
Reference in New Issue
Block a user