Skip to content

Commit

Permalink
Remove class wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
toasted-nutbread committed Dec 27, 2023
1 parent adcae18 commit c3e6e5a
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 170 deletions.
15 changes: 5 additions & 10 deletions ext/js/accessibility/accessibility-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import {isContentScriptRegistered, registerContentScript, unregisterContentScript} from '../background/script-manager.js';
import {log} from '../core.js';

/**
* This class controls the registration of accessibility handlers.
*/
export class AccessibilityController {
/**
* Creates a new instance.
* @param {import('../background/script-manager.js').ScriptManager} scriptManager An instance of the `ScriptManager` class.
*/
constructor(scriptManager) {
/** @type {import('../background/script-manager.js').ScriptManager} */
this._scriptManager = scriptManager;
constructor() {
/** @type {?import('core').TokenObject} */
this._updateGoogleDocsAccessibilityToken = null;
/** @type {?Promise<void>} */
Expand Down Expand Up @@ -90,17 +85,17 @@ export class AccessibilityController {
const id = 'googleDocsAccessibility';
try {
if (forceGoogleDocsHtmlRenderingAny) {
if (await this._scriptManager.isContentScriptRegistered(id)) { return; }
if (await isContentScriptRegistered(id)) { return; }
/** @type {import('script-manager').RegistrationDetails} */
const details = {
allFrames: true,
matches: ['*://docs.google.com/*'],
runAt: 'document_start',
js: ['js/accessibility/google-docs.js']
};
await this._scriptManager.registerContentScript(id, details);
await registerContentScript(id, details);
} else {
await this._scriptManager.unregisterContentScript(id);
await unregisterContentScript(id);
}
} catch (e) {
log.error(e);
Expand Down
8 changes: 3 additions & 5 deletions ext/js/background/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {MediaUtil} from '../media/media-util.js';
import {ClipboardReaderProxy, DictionaryDatabaseProxy, OffscreenProxy, TranslatorProxy} from './offscreen-proxy.js';
import {ProfileConditionsUtil} from './profile-conditions-util.js';
import {RequestBuilder} from './request-builder.js';
import {ScriptManager} from './script-manager.js';
import {injectStylesheet} from './script-manager.js';

/**
* This class controls the core logic of the extension, including API calls
Expand Down Expand Up @@ -110,10 +110,8 @@ export class Backend {
});
/** @type {OptionsUtil} */
this._optionsUtil = new OptionsUtil();
/** @type {ScriptManager} */
this._scriptManager = new ScriptManager();
/** @type {AccessibilityController} */
this._accessibilityController = new AccessibilityController(this._scriptManager);
this._accessibilityController = new AccessibilityController();

/** @type {?number} */
this._searchPopupTabId = null;
Expand Down Expand Up @@ -650,7 +648,7 @@ export class Backend {
async _onApiInjectStylesheet({type, value}, sender) {
const {frameId, tab} = sender;
if (typeof tab !== 'object' || tab === null || typeof tab.id !== 'number') { throw new Error('Invalid tab'); }
return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId, false);
return await injectStylesheet(type, value, tab.id, frameId, false);
}

/** @type {import('api').ApiHandler<'getStylesheetContent'>} */
Expand Down
303 changes: 148 additions & 155 deletions ext/js/background/script-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,176 +17,169 @@
*/

/**
* This class is used to manage script injection into content tabs.
* Injects a stylesheet into a tab.
* @param {'file'|'code'} type The type of content to inject; either 'file' or 'code'.
* @param {string} content The content to inject.
* - If type is `'file'`, this argument should be a path to a file.
* - If type is `'code'`, this argument should be the CSS content.
* @param {number} tabId The id of the tab to inject into.
* @param {number|undefined} frameId The id of the frame to inject into.
* @param {boolean} allFrames Whether or not the stylesheet should be injected into all frames.
* @returns {Promise<void>}
*/
export class ScriptManager {
/**
* Injects a stylesheet into a tab.
* @param {'file'|'code'} type The type of content to inject; either 'file' or 'code'.
* @param {string} content The content to inject.
* If type is 'file', this argument should be a path to a file.
* If type is 'code', this argument should be the CSS content.
* @param {number} tabId The id of the tab to inject into.
* @param {number|undefined} frameId The id of the frame to inject into.
* @param {boolean} allFrames Whether or not the stylesheet should be injected into all frames.
* @returns {Promise<void>}
*/
injectStylesheet(type, content, tabId, frameId, allFrames) {
return new Promise((resolve, reject) => {
/** @type {chrome.scripting.InjectionTarget} */
const target = {
tabId,
allFrames
};
/** @type {chrome.scripting.CSSInjection} */
const details = (
type === 'file' ?
{origin: 'AUTHOR', files: [content], target} :
{origin: 'USER', css: content, target}
);
if (!allFrames && typeof frameId === 'number') {
details.target.frameIds = [frameId];
export function injectStylesheet(type, content, tabId, frameId, allFrames) {
return new Promise((resolve, reject) => {
/** @type {chrome.scripting.InjectionTarget} */
const target = {
tabId,
allFrames
};
/** @type {chrome.scripting.CSSInjection} */
const details = (
type === 'file' ?
{origin: 'AUTHOR', files: [content], target} :
{origin: 'USER', css: content, target}
);
if (!allFrames && typeof frameId === 'number') {
details.target.frameIds = [frameId];
}
chrome.scripting.insertCSS(details, () => {
const e = chrome.runtime.lastError;
if (e) {
reject(new Error(e.message));
} else {
resolve();
}
chrome.scripting.insertCSS(details, () => {
const e = chrome.runtime.lastError;
if (e) {
reject(new Error(e.message));
} else {
resolve();
}
});
});
}
});
}

/**
* Injects a script into a tab.
* @param {string} file The path to a file to inject.
* @param {number} tabId The id of the tab to inject into.
* @param {number|undefined} frameId The id of the frame to inject into.
* @param {boolean} allFrames Whether or not the script should be injected into all frames.
* @returns {Promise<{frameId: number|undefined, result: unknown}>} The id of the frame and the result of the script injection.
*/
injectScript(file, tabId, frameId, allFrames) {
return new Promise((resolve, reject) => {
/** @type {chrome.scripting.ScriptInjection<unknown[], unknown>} */
const details = {
injectImmediately: true,
files: [file],
target: {tabId, allFrames}
};
if (!allFrames && typeof frameId === 'number') {
details.target.frameIds = [frameId];
/**
* Injects a script into a tab.
* @param {string} file The path to a file to inject.
* @param {number} tabId The id of the tab to inject into.
* @param {number|undefined} frameId The id of the frame to inject into.
* @param {boolean} allFrames Whether or not the script should be injected into all frames.
* @returns {Promise<{frameId: number|undefined, result: unknown}>} The id of the frame and the result of the script injection.
*/
export function injectScript(file, tabId, frameId, allFrames) {
return new Promise((resolve, reject) => {
/** @type {chrome.scripting.ScriptInjection<unknown[], unknown>} */
const details = {
injectImmediately: true,
files: [file],
target: {tabId, allFrames}
};
if (!allFrames && typeof frameId === 'number') {
details.target.frameIds = [frameId];
}
chrome.scripting.executeScript(details, (results) => {
const e = chrome.runtime.lastError;
if (e) {
reject(new Error(e.message));
} else {
const {frameId: frameId2, result} = results[0];
resolve({frameId: frameId2, result});
}
chrome.scripting.executeScript(details, (results) => {
const e = chrome.runtime.lastError;
if (e) {
reject(new Error(e.message));
} else {
const {frameId: frameId2, result} = results[0];
resolve({frameId: frameId2, result});
}
});
});
}
});
}

/**
* Checks whether or not a content script is registered.
* @param {string} id The identifier used with a call to `registerContentScript`.
* @returns {Promise<boolean>} `true` if a script is registered, `false` otherwise.
*/
async isContentScriptRegistered(id) {
const scripts = await new Promise((resolve, reject) => {
chrome.scripting.getRegisteredContentScripts({ids: [id]}, (result) => {
const e = chrome.runtime.lastError;
if (e) {
reject(new Error(e.message));
} else {
resolve(result);
}
});
});
for (const script of scripts) {
if (script.id === id) {
return true;
/**
* Checks whether or not a content script is registered.
* @param {string} id The identifier used with a call to `registerContentScript`.
* @returns {Promise<boolean>} `true` if a script is registered, `false` otherwise.
*/
export async function isContentScriptRegistered(id) {
const scripts = await new Promise((resolve, reject) => {
chrome.scripting.getRegisteredContentScripts({ids: [id]}, (result) => {
const e = chrome.runtime.lastError;
if (e) {
reject(new Error(e.message));
} else {
resolve(result);
}
});
});
for (const script of scripts) {
if (script.id === id) {
return true;
}
return false;
}
return false;
}

/**
* Registers a dynamic content script.
* Note: if the fallback handler is used and the 'webNavigation' permission isn't granted,
* there is a possibility that the script can be injected more than once due to the events used.
* Therefore, a reentrant check may need to be performed by the content script.
* @param {string} id A unique identifier for the registration.
* @param {import('script-manager').RegistrationDetails} details The script registration details.
* @throws An error is thrown if the id is already in use.
*/
async registerContentScript(id, details) {
if (await this.isContentScriptRegistered(id)) {
throw new Error('Registration already exists');
}

const details2 = this._createContentScriptRegistrationOptionsChrome(details, id);
await /** @type {Promise<void>} */ (new Promise((resolve, reject) => {
chrome.scripting.registerContentScripts([details2], () => {
const e = chrome.runtime.lastError;
if (e) {
reject(new Error(e.message));
} else {
resolve();
}
});
}));
/**
* Registers a dynamic content script.
* Note: if the fallback handler is used and the 'webNavigation' permission isn't granted,
* there is a possibility that the script can be injected more than once due to the events used.
* Therefore, a reentrant check may need to be performed by the content script.
* @param {string} id A unique identifier for the registration.
* @param {import('script-manager').RegistrationDetails} details The script registration details.
* @throws An error is thrown if the id is already in use.
*/
export async function registerContentScript(id, details) {
if (await isContentScriptRegistered(id)) {
throw new Error('Registration already exists');
}

/**
* Unregisters a previously registered content script.
* @param {string} id The identifier passed to a previous call to `registerContentScript`.
* @returns {Promise<void>}
*/
async unregisterContentScript(id) {
return new Promise((resolve, reject) => {
chrome.scripting.unregisterContentScripts({ids: [id]}, () => {
const e = chrome.runtime.lastError;
if (e) {
reject(new Error(e.message));
} else {
resolve();
}
});
const details2 = createContentScriptRegistrationOptions(details, id);
await /** @type {Promise<void>} */ (new Promise((resolve, reject) => {
chrome.scripting.registerContentScripts([details2], () => {
const e = chrome.runtime.lastError;
if (e) {
reject(new Error(e.message));
} else {
resolve();
}
});
}
}));
}

// Private
/**
* Unregisters a previously registered content script.
* @param {string} id The identifier passed to a previous call to `registerContentScript`.
* @returns {Promise<void>}
*/
export async function unregisterContentScript(id) {
return new Promise((resolve, reject) => {
chrome.scripting.unregisterContentScripts({ids: [id]}, () => {
const e = chrome.runtime.lastError;
if (e) {
reject(new Error(e.message));
} else {
resolve();
}
});
});
}

/**
* @param {import('script-manager').RegistrationDetails} details
* @param {string} id
* @returns {chrome.scripting.RegisteredContentScript}
*/
_createContentScriptRegistrationOptionsChrome(details, id) {
const {css, js, allFrames, matches, runAt} = details;
/** @type {chrome.scripting.RegisteredContentScript} */
const options = {
id: id,
persistAcrossSessions: true
};
if (Array.isArray(css)) {
options.css = [...css];
}
if (Array.isArray(js)) {
options.js = [...js];
}
if (typeof allFrames !== 'undefined') {
options.allFrames = allFrames;
}
if (Array.isArray(matches)) {
options.matches = [...matches];
}
if (typeof runAt !== 'undefined') {
options.runAt = runAt;
}
return options;
/**
* @param {import('script-manager').RegistrationDetails} details
* @param {string} id
* @returns {chrome.scripting.RegisteredContentScript}
*/
function createContentScriptRegistrationOptions(details, id) {
const {css, js, allFrames, matches, runAt} = details;
/** @type {chrome.scripting.RegisteredContentScript} */
const options = {
id: id,
persistAcrossSessions: true
};
if (Array.isArray(css)) {
options.css = [...css];
}
if (Array.isArray(js)) {
options.js = [...js];
}
if (typeof allFrames !== 'undefined') {
options.allFrames = allFrames;
}
if (Array.isArray(matches)) {
options.matches = [...matches];
}
if (typeof runAt !== 'undefined') {
options.runAt = runAt;
}
return options;
}

0 comments on commit c3e6e5a

Please sign in to comment.