Skip to content

Upgrade to Manifest V3 #1714

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Update options to not rely on background page access
  • Loading branch information
Methuselah96 committed Aug 15, 2024
commit 4b43b513d39723f8624f09a58c8651f6a98bc27a
4 changes: 2 additions & 2 deletions extension/src/background/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import configureStore from './store/backgroundStore';
import openDevToolsWindow, { DevToolsPosition } from './openWindow';
import { createMenu, removeMenu } from './contextMenus';
import createSyncOptions from '../options/syncOptions';
import { getOptions } from '../options/syncOptions';

// Expose the extension's store globally to access it from the windows
// via chrome.runtime.getBackgroundPage
Expand All @@ -16,7 +16,7 @@ chrome.commands.onCommand.addListener((shortcut) => {
chrome.runtime.onInstalled.addListener(() => {
chrome.action.disable();

createSyncOptions().get((option) => {
getOptions((option) => {
if (option.showContextMenus) createMenu();
});
});
Expand Down
38 changes: 13 additions & 25 deletions extension/src/background/store/apiMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ import {
TOGGLE_PERSIST,
UPDATE_STATE,
} from '@redux-devtools/app';
import createSyncOptions, {
Options,
OptionsMessage,
} 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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -196,7 +198,7 @@ interface SplitUpdateStateAction<S, A extends Action<string>> {
export type TabMessage =
| StartAction
| StopAction
| OptionsMessage
| OptionsAction
| DispatchAction
| ImportAction
| ActionAction
Expand Down Expand Up @@ -414,14 +416,11 @@ 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);
}
}

const syncOptions = createSyncOptions(toAllTabs);

function monitorInstances(shouldMonitor: boolean, id?: string) {
if (!id && isMonitored === shouldMonitor) return;
const action = {
Expand Down Expand Up @@ -463,26 +462,17 @@ 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;
Expand All @@ -498,10 +488,8 @@ function messaging<S, A extends Action<string>>(
chrome.runtime.openOptionsPage();
return;
}
if (request.type === 'GET_OPTIONS') {
syncOptions.get((options) => {
sendResponse!({ options });
});
if (request.type === 'OPTIONS') {
toAllTabs({ type: 'OPTIONS', options: request.options });
return;
}
if (request.type === 'GET_REPORT') {
Expand Down
37 changes: 31 additions & 6 deletions extension/src/contentScript/index.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -84,14 +86,22 @@ 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
| DispatchAction
| ImportAction
| ActionAction
| ExportAction
| UpdateAction;
| UpdateAction
| OptionsAction;

interface ImportStatePayload<S, A extends Action<string>> {
readonly type: 'IMPORT_STATE';
Expand All @@ -112,6 +122,7 @@ export type ListenerMessage<S, A extends Action<string>> =
| ActionAction
| ExportAction
| UpdateAction
| OptionsAction
| ImportStateDispatchAction<S, A>;

function postToPageScript(message: ContentScriptToPageScriptMessage) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -317,6 +340,8 @@ function handleMessages<S, A extends Action<string>>(
tryCatch(send, message);
}

prefetchOptions();

window.addEventListener('message', handleMessages, false);

setInterval(() => {
Expand Down
33 changes: 18 additions & 15 deletions extension/src/options/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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} />);
};
const renderOptions = (options: Options) => {
const root = createRoot(document.getElementById('root')!);
root.render(<OptionsComponent options={options} saveOption={saveOption} />);
};

syncOptions.subscribe(renderOptions);
syncOptions.get((options) => {
renderOptions(options);
});
subscribeToOptions(renderOptions);
getOptions((options) => {
renderOptions(options);
});
93 changes: 28 additions & 65 deletions extension/src/options/syncOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,22 @@ 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]) => {
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!));
};
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;
for (const subscriber of subscribers) {
subscriber(options!);
}
};

const migrateOldOptions = (oldOptions: OldOrNewOptions): Options => ({
...oldOptions,
Expand All @@ -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(
Expand All @@ -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,
allowlist:
newOptions.filter !== FilterState.DO_NOT_FILTER
? toReg(newOptions.allowlist)!
: newOptions.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);
});
*/
get((newOptions) => {
injectOptions(newOptions);
}); // Legacy
};
export const prepareOptionsForPage = (options: Options): Options => ({
...options,
allowlist:
options.filter !== FilterState.DO_NOT_FILTER
? toReg(options.allowlist)!
: options.allowlist,
denylist:
options.filter !== FilterState.DO_NOT_FILTER
? toReg(options.denylist)!
: options.denylist,
});

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 createSyncOptions(toAllTabs?: ToAllTabs): SyncOptions {
if (toAllTabs && !options) get(() => {}); // Initialize
return {
save: save(toAllTabs),
get: get,
subscribe: subscribe,
};
}
7 changes: 7 additions & 0 deletions extension/src/pageScript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down