diff options
| author | Arpit Chakladar <arpitchakladar+git@gmail.com> | 2026-07-13 11:35:08 +0530 |
|---|---|---|
| committer | Arpit Chakladar <arpitchakladar+git@gmail.com> | 2026-07-13 11:35:08 +0530 |
| commit | 600c337b1cdc884aebf59083eb57b178b43441ce (patch) | |
| tree | d1f3cb7eb1020bcdcd66d9dbf0279f79d5856332 | |
| parent | 98e7019c61ea0ffe344ef7ce809d38d62254a9fc (diff) | |
| download | banglar-bhumi-utils-600c337b1cdc884aebf59083eb57b178b43441ce.tar.gz banglar-bhumi-utils-600c337b1cdc884aebf59083eb57b178b43441ce.zip | |
docs: added some documentation comments to all functions
29 files changed, 323 insertions, 0 deletions
diff --git a/config/webpack.config.js b/config/webpack.config.js index 8fd3f15..347d204 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -1,3 +1,17 @@ +/** + * Webpack configuration for the Banglar Bhumi Utils Chrome extension. + * + * Produces four separate bundles: + * 1. **Background script** – service worker entry point. + * 2. **Uninjected scripts** – content scripts that run as-is. + * 3. **Injected scripts** – content scripts that are further processed + * so they can be injected into the page DOM. + * 4. **Shared modules** – libraries compiled as standalone files and + * referenced via global variables (`$<hash>`). + * + * Custom loaders and plugins handle dependency ordering, import counting, + * asset-URL rewriting, manifest generation, and declarative-net-rule creation. + */ import path from "path"; import fs from "fs"; import { fileURLToPath } from "url"; diff --git a/config/webpack/loader/arrange-shared-module-loader.js b/config/webpack/loader/arrange-shared-module-loader.js index 2265185..d4c22a9 100644 --- a/config/webpack/loader/arrange-shared-module-loader.js +++ b/config/webpack/loader/arrange-shared-module-loader.js @@ -3,6 +3,15 @@ import { ImportManager } from "import-manager"; import sharedModules from "../utils/shared-modules.js"; +/** + * Webpack loader that arranges shared modules in dependency order. + * As each shared module is processed, its own shared-module dependencies + * are inserted before it in the global `sortedSharedModules` array so that + * the final output respects the import graph. + * + * @param {string} source - The source code of the current module. + * @returns {string} The unmodified source (this loader is side-effect only). + */ export default function(source) { const currentSharedModuleName = path.basename(this.resourcePath.substring(0, this.resourcePath.length - 3)); diff --git a/config/webpack/loader/count-imports-loader.js b/config/webpack/loader/count-imports-loader.js index 2586332..3342921 100644 --- a/config/webpack/loader/count-imports-loader.js +++ b/config/webpack/loader/count-imports-loader.js @@ -1,6 +1,14 @@ import { ImportManager } from "import-manager"; import sharedModules from "../utils/shared-modules.js"; +/** + * Webpack loader that counts how many times each shared module is imported. + * This count is used later to determine which shared modules need to be + * included in the compiled output. + * + * @param {string} source - The source code of the current module. + * @returns {string} The unmodified source (this loader is side-effect only). + */ export default function(source) { const { sharedModulesImportedCount } = this.getOptions(); const manager = new ImportManager(source); diff --git a/config/webpack/loader/to-string-loader.js b/config/webpack/loader/to-string-loader.js index 248574a..244ae7a 100644 --- a/config/webpack/loader/to-string-loader.js +++ b/config/webpack/loader/to-string-loader.js @@ -1,3 +1,13 @@ +/** + * Webpack loader that converts HTML template files into JavaScript modules. + * It escapes template literals and replaces `$name$` placeholders with + * `${name}` interpolation syntax so the exported function can accept a + * replacements object. + * + * @param {string} source - Raw HTML file content. + * @returns {string} A JS module that exports a function accepting + * replacement values and returning the interpolated HTML string. + */ export default function(source) { source = source .replaceAll("\\", "\\\\") diff --git a/config/webpack/plugins/create-injected-shared-modules-webpack-plugin.js b/config/webpack/plugins/create-injected-shared-modules-webpack-plugin.js index 695d4ef..2adb017 100644 --- a/config/webpack/plugins/create-injected-shared-modules-webpack-plugin.js +++ b/config/webpack/plugins/create-injected-shared-modules-webpack-plugin.js @@ -4,12 +4,24 @@ import webpack from "webpack"; import { getFileName } from "../utils/build-file.js"; +/** + * Webpack plugin that generates an injected shared-modules bundle. + * After all assets are processed, it creates a small JS file that + * calls the script injector for each shared module that was imported + * by injected scripts. + */ class CreateInjectedSharedModulesPlugin { + /** + * @param {{ injectedSharedModulesImportedCount: Record<string, number>, sortedSharedModules: string[] }} options + */ constructor({ injectedSharedModulesImportedCount, sortedSharedModules }) { this.injectedSharedModulesImportedCount = injectedSharedModulesImportedCount; this.sortedSharedModules = sortedSharedModules; } + /** + * @param {import("webpack").Compiler} compiler + */ apply(compiler) { compiler.hooks.compilation.tap("CreateInjectedSharedModulesPlugin", compilation => { compilation.hooks.processAssets.tap( diff --git a/config/webpack/plugins/create-manifest-webpack-plugin/index.js b/config/webpack/plugins/create-manifest-webpack-plugin/index.js index 6f1e2be..f012f1e 100644 --- a/config/webpack/plugins/create-manifest-webpack-plugin/index.js +++ b/config/webpack/plugins/create-manifest-webpack-plugin/index.js @@ -11,13 +11,32 @@ const manifest = JSON.parse( ), ); +/** + * Webpack plugin that dynamically generates the extension's + * `manifest.json` asset. It reads the template, resolves the + * current version from `package.json`, and builds `content_scripts` + * entries by matching script definitions from `scripts.json` against + * page URL patterns. Shared modules, injected scripts, and + * web-accessible resources are added automatically based on import + * counts collected during the build. + */ class CreateManifestPlugin { + /** + * @param {{ + * sharedModulesImportedCount: Record<string, number>, + * injectedSharedModulesImportedCount: Record<string, number>, + * sortedSharedModules: string[] + * }} options + */ constructor({ sharedModulesImportedCount, injectedSharedModulesImportedCount, sortedSharedModules }) { this.sharedModulesImportedCount = sharedModulesImportedCount; this.injectedSharedModulesImportedCount = injectedSharedModulesImportedCount; this.sortedSharedModules = sortedSharedModules; } + /** + * @param {import("webpack").Compiler} compiler + */ apply(compiler) { compiler.hooks.compilation.tap("CreateManifestPlugin", compilation => { compilation.hooks.processAssets.tap( diff --git a/config/webpack/plugins/create-rules-webpack-plugin.js b/config/webpack/plugins/create-rules-webpack-plugin.js index 5573ddf..55b60f7 100644 --- a/config/webpack/plugins/create-rules-webpack-plugin.js +++ b/config/webpack/plugins/create-rules-webpack-plugin.js @@ -2,7 +2,15 @@ import path from "path"; import fs from "fs"; import webpack from "webpack"; +/** + * Webpack plugin that reads all JSON rule files from `src/rules/`, + * assigns sequential IDs to each rule, and emits a single `rules.json` + * asset used by `declarativeNetRequest`. + */ class CreateRulesPlugin { + /** + * @param {import("webpack").Compiler} compiler + */ apply(compiler) { compiler.hooks.compilation.tap("CreateRulesPlugin", compilation => { compilation.hooks.processAssets.tap( diff --git a/config/webpack/plugins/inject-script-webpack-plugin.js b/config/webpack/plugins/inject-script-webpack-plugin.js index 2b60022..43c5d9f 100644 --- a/config/webpack/plugins/inject-script-webpack-plugin.js +++ b/config/webpack/plugins/inject-script-webpack-plugin.js @@ -4,7 +4,16 @@ import path from "path"; import { getFileName } from "../utils/build-file.js"; import { getInjectedCode } from "../utils/injected-code.js"; +/** + * Webpack plugin that post-processes every `.js` asset to replace + * extension-asset URL placeholders with runtime lookup code, and + * emits an additional "injected" script that the content script + * injects into the page via the script-injector module. + */ class InjectScriptPlugin { + /** + * @param {import("webpack").Compiler} compiler + */ apply(compiler) { compiler.hooks.compilation.tap("InjectScriptPlugin", compilation => { compilation.hooks.processAssets.tapPromise( diff --git a/config/webpack/utils/build-file.js b/config/webpack/utils/build-file.js index 6945911..17f0b03 100644 --- a/config/webpack/utils/build-file.js +++ b/config/webpack/utils/build-file.js @@ -1,5 +1,15 @@ import crypto from "crypto"; +/** + * Generates a deterministic output filename for a module. + * In production the result is a short MD5 hash; in development the + * original name is preserved alongside the hash for easier debugging. + * + * @param {string} fileName - The original module filename. + * @param {string} prefix - A namespace prefix (e.g. "shared", "injected"). + * @param {boolean} justHash - When true, always return only the hash part. + * @returns {string} The transformed filename. + */ export const getFileName = (fileName, prefix, justHash = false) => { let hash = crypto .createHash("md5") diff --git a/config/webpack/utils/injected-code.js b/config/webpack/utils/injected-code.js index a2884f6..2b8f750 100644 --- a/config/webpack/utils/injected-code.js +++ b/config/webpack/utils/injected-code.js @@ -1,5 +1,12 @@ import crypto from "crypto"; +/** + * Computes an MD5 hash suffix (last 16 hex characters) for a given URL. + * Used to create unique attribute names for injected script data. + * + * @param {string} url - The URL to hash. + * @returns {string} A 16-character hex hash. + */ const getHash = url => crypto .createHash("md5") @@ -7,6 +14,15 @@ const getHash = url => .digest("hex") .substring(16); +/** + * Transforms extension asset URL placeholders (`"$l{ url }l$"`) in the + * compiled JavaScript into `document.currentScript.getAttribute("data-<hash>")` + * lookups. The mapping of hashes to resolved `chrome.runtime.getURL()` calls + * is returned separately so the injector script can embed it as data attributes. + * + * @param {string} code - The compiled JavaScript bundle. + * @returns {[string, string]} A tuple of [transformedCode, extensionAssetsJSON]. + */ export const getInjectedCode = code => { const extensionAssets = {}; let i = 0; diff --git a/config/webpack/utils/inline-javascript.js b/config/webpack/utils/inline-javascript.js index 456dc11..205bda4 100644 --- a/config/webpack/utils/inline-javascript.js +++ b/config/webpack/utils/inline-javascript.js @@ -1 +1,8 @@ +/** + * Wraps JavaScript source code in a base64-encoded data URI so it can be + * used as an inline webpack entry point without writing a physical file. + * + * @param {string} code - The JavaScript source to inline. + * @returns {string} A `data:text/javascript;base64,…` URI. + */ export const inlineJavascript = code => `data:text/javascript;base64,${Buffer.from(code).toString("base64")}`; diff --git a/config/webpack/utils/script-runtime.js b/config/webpack/utils/script-runtime.js index a7c02f7..1c23922 100644 --- a/config/webpack/utils/script-runtime.js +++ b/config/webpack/utils/script-runtime.js @@ -1,3 +1,14 @@ +/** + * Maps a human-readable script type to a Chrome content-script + * `run_at` value. + * + * - `"before"`, `"injected-after"`, `"injected-before"` → `document_start` + * - `"rendered"` → `document_end` + * - `"loaded"` (or unknown) → `document_idle` + * + * @param {string} scriptType - The script type from scripts.json. + * @returns {string} The corresponding `run_at` value. + */ export const getScriptRuntimeFromType = scriptType => { switch (scriptType) { case "before": diff --git a/config/webpack/utils/scripts.js b/config/webpack/utils/scripts.js index af58e0a..09a2fc0 100644 --- a/config/webpack/utils/scripts.js +++ b/config/webpack/utils/scripts.js @@ -1,6 +1,14 @@ import path from "path"; import fs from "fs"; +/** + * Reads `src/scripts.json` and re-indexes it so the outer key is the + * URL path fragment and the inner key is the script type. This makes it + * trivial to look up which scripts run on which page. + * + * @returns {Record<string, Record<string, string[]>>} e.g. + * `{ "*": { "injected": ["stop-blocking.ts"] }, "MuteApplication.action": … }` + */ const scripts = JSON.parse( fs.readFileSync( path.resolve("./src/scripts.json"), diff --git a/config/webpack/utils/shared-modules.js b/config/webpack/utils/shared-modules.js index 2b16fa5..74ee555 100644 --- a/config/webpack/utils/shared-modules.js +++ b/config/webpack/utils/shared-modules.js @@ -1,6 +1,12 @@ import path from "path"; import fs from "fs"; +/** + * Scans `src/shared/` and returns the list of shared module filenames + * (with the `.ts` extension stripped), excluding `import-shared.js`. + * + * @returns {string[]} e.g. `["generate-web-page", "intercept-jquery-ajax", …]` + */ export default fs.readdirSync(path.resolve("src/shared")) .filter(sharedModule => !sharedModule.endsWith("import-shared.js")) .map(sharedModule => sharedModule.substring(0, sharedModule.length - 3)); diff --git a/src/background/run-ocr.ts b/src/background/run-ocr.ts index 7975fc0..4d0b080 100644 --- a/src/background/run-ocr.ts +++ b/src/background/run-ocr.ts @@ -1,6 +1,10 @@ const OFFSCREEN_DOCUMENT_PATH = "offscreen/ocr/index.html"; // Function to ensure the offscreen document is open +/** + * Ensures the offscreen OCR document is open. If it already exists + * this is a no-op. + */ async function setupOffscreenDocument() { if (await chrome.offscreen.hasDocument()) { return; // Offscreen document already open @@ -13,6 +17,11 @@ async function setupOffscreenDocument() { } // Listen for messages from content scripts (and popup if applicable) +/** + * Listens for `"OCR"` messages from content scripts. Forwards the + * image data URL to an offscreen document for Tesseract.js processing + * and sends the recognised text back to the caller. + */ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === "OCR" && message.dataURL) { (async () => { diff --git a/src/offscreen/ocr/index.js b/src/offscreen/ocr/index.js index 432811e..aab9da8 100644 --- a/src/offscreen/ocr/index.js +++ b/src/offscreen/ocr/index.js @@ -4,6 +4,15 @@ let ocrWorker = null; const WORKER_PATH = chrome.runtime.getURL("offscreen/ocr/static/worker.min.js"); const CORE_PATH = chrome.runtime.getURL("offscreen/ocr/static"); +/** + * Creates (or reuses) a Tesseract.js worker and recognises text from + * the given image data URL. The worker is configured to only recognise + * uppercase alphanumerics (excluding `0`, `O`, `1`, `I`) suitable for + * CAPTCHA text. + * + * @param {string} dataURL - A `data:image/png;base64,…` string. + * @returns {{ text: string, confidence: number }} + */ async function performOcrInOffscreen(dataURL) { if (!ocrWorker) { ocrWorker = await Tesseract.createWorker("eng", 1, { @@ -23,6 +32,11 @@ async function performOcrInOffscreen(dataURL) { } // Listen for messages from the Service Worker +/** + * Handles two message types from the background service worker: + * - `"OFFSCREEN_OCR_REQUEST"`: runs OCR on the supplied data URL. + * - `"OFFSCREEN_TERMINATE_OCR_WORKER"`: terminates the worker to free memory. + */ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === "OFFSCREEN_OCR_REQUEST" && message.dataURL) { performOcrInOffscreen(message.dataURL) diff --git a/src/scripts/captcha/application-receipt.ts b/src/scripts/captcha/application-receipt.ts index 595195e..c5bb657 100644 --- a/src/scripts/captcha/application-receipt.ts +++ b/src/scripts/captcha/application-receipt.ts @@ -1,3 +1,8 @@ +/** + * Hides the CAPTCHA section on the application-receipt page and + * stubs the form validation so the user can proceed without + * solving a CAPTCHA. + */ document.addEventListener("DOMContentLoaded", () => { $("#werter > div > form > div:nth-child(4)").hide(); (window as any).validateForm = () => true; diff --git a/src/scripts/captcha/know-your-property.ts b/src/scripts/captcha/know-your-property.ts index 6e11a3f..3abdf08 100644 --- a/src/scripts/captcha/know-your-property.ts +++ b/src/scripts/captcha/know-your-property.ts @@ -1,3 +1,8 @@ +/** + * Hides the CAPTCHA section on the Know-Your-Property page and + * stubs the captcha validation so the user can proceed without + * solving a CAPTCHA. + */ document.addEventListener("DOMContentLoaded", () => { $("#khatianPlotDiv > div:nth-child(6) > div").hide(); (window as any).validateCaptcha = () => true; diff --git a/src/scripts/functionality/mutation-application.ts b/src/scripts/functionality/mutation-application.ts index 96aec8a..c067b25 100644 --- a/src/scripts/functionality/mutation-application.ts +++ b/src/scripts/functionality/mutation-application.ts @@ -3,6 +3,12 @@ import { observeDOM } from "@/shared/observe-dom"; const sanghaFacilitationCentreBannerUrl = "$l{ /assets/sangha-facilitation-centre-banner.jpg }l$"; +/** + * On the Mutation Application form, removes the form action (to prevent + * the default server-side submit) and attaches a custom click handler + * that fetches the declaration PDF, overlays a banner image and date + * stamp on the last page, then triggers a download. + */ document.addEventListener("DOMContentLoaded", () => { observeDOM(() => { const formElement = $("#form_MutationApplication > div > div:nth-child(14) > div.col-sm-2.btreset > form"); diff --git a/src/scripts/functionality/sheet-map/index.ts b/src/scripts/functionality/sheet-map/index.ts index 71228fe..3d566c2 100644 --- a/src/scripts/functionality/sheet-map/index.ts +++ b/src/scripts/functionality/sheet-map/index.ts @@ -23,6 +23,13 @@ let plotNumberLabelTextElements = ""; document.body.appendChild(plotInformation); +/** + * Creates a header toolbar button and appends it to the page header. + * The button starts hidden and is shown when data is ready. + * + * @param text - The button label text. + * @returns The created button element. + */ const createHeaderButton = (text: string) => { const buttonContainer = document.createElement("td"); const button = document.createElement("button"); @@ -33,6 +40,12 @@ const createHeaderButton = (text: string) => { return button; }; +/** + * Displays the area and plot number for the given polygon in a floating + * info panel, or shows "0.000" / empty if no polygon is selected. + * + * @param plotPolygon - The selected plot's data, or `null`/`undefined` to clear. + */ const setPlotInformation = (plotPolygon: PlotPolygon | null | undefined = null) => { plotInformation.innerHTML = plotPolygon ? getPlotInformationElement({ area: (plotPolygon.plotArea/1000).toFixed(3), @@ -43,7 +56,14 @@ const setPlotInformation = (plotPolygon: PlotPolygon | null | undefined = null) }); }; +/** + * Opens a new window with a printable PDF view of the map, optionally + * including plot-number labels. + * + * @param labelPoints - Whether to include plot number text labels. + */ const downloadPDF = (labelPoints: boolean = true) => { + /** Extracts a detail value (district/block/mouza) from the header table by column index. */ const _getMapDetail = (i: number) => { const detail = document.querySelector(`#headerTable > tbody > tr > td:nth-child(${i})`)! .innerHTML @@ -147,6 +167,12 @@ setPlotInformation(); } }, 300); + /** + * Highlights the clicked plot on the SVG map and shows its + * information in the info panel. + * + * @param e - The mouse click event. + */ const handlePlotClick = (e: MouseEvent) => { document.querySelectorAll("path").forEach(e => { e.setAttribute("fill", "#ffcc66"); diff --git a/src/scripts/functionality/view-khatian/index.ts b/src/scripts/functionality/view-khatian/index.ts index 93d03cf..34e6765 100644 --- a/src/scripts/functionality/view-khatian/index.ts +++ b/src/scripts/functionality/view-khatian/index.ts @@ -14,6 +14,12 @@ const styles = Array.from(submitButtonElementComputedStyles) "" ); +/** + * Reads the currently selected text from a `<select>` element. + * + * @param selector - CSS selector for the select element. + * @returns The text of the selected option. + */ const getValueOfSelectElement = (selector: string) => { const element = document.querySelector<HTMLSelectElement>(selector)!; return element.options[element.selectedIndex].text; @@ -21,6 +27,10 @@ const getValueOfSelectElement = (selector: string) => { let isPlotInformation: boolean | null = null; +/** + * Opens a new window with a printable PDF view of the current khatian + * or plot details, including district/block/mouza info. + */ const downloadInformationPDF = () => { generateWebPage( getDownloadInformationPDFPageContent({ @@ -36,6 +46,13 @@ const downloadInformationPDF = () => { ); }; +/** + * Intercepts the success callback of a jQuery AJAX call. When a valid + * details table is returned, it inserts a "Download PDF" button; + * otherwise it removes an existing button. + * + * @param args - The `arguments` object from the intercepted `$.post` call. + */ const showDownloadButton = (args: any) => { if (isPlotInformation !== null) { const callback = args[2]; diff --git a/src/scripts/login/index.ts b/src/scripts/login/index.ts index bed05f9..dcf43e3 100644 --- a/src/scripts/login/index.ts +++ b/src/scripts/login/index.ts @@ -1,18 +1,33 @@ import { interceptPost, interceptGet } from "@/shared/intercept-jquery-ajax"; +/** + * Pre-processes a CAPTCHA image on a canvas by removing grayish + * background noise while preserving dark pixels, producing a clean + * binary image suitable for OCR. + * + * @param ctx - The 2D rendering context of the canvas. + * @param width - Canvas width in pixels. + * @param height - Canvas height in pixels. + */ function prepareCaptcha(ctx: CanvasRenderingContext2D, width: number, height: number) { const imgData = ctx.getImageData(0, 0, width, height); const data = imgData.data; const radius = 1; + /** Returns true when all three channels are below 50 (very dark). */ function isBlack(r: number, g: number, b: number) { return r < 50 && g < 50 && b < 50; } + /** Returns true when the colour is a mid-range grey (no strong hue). */ function isGrayish(r: number, g: number, b: number) { return Math.abs(r - g) < 15 && Math.abs(g - b) < 15 && r > 100 && r < 200; } + /** + * Checks whether a black pixel exists within `radius` pixels of (x, y). + * Used to preserve dark structures when removing grey noise. + */ function hasNearbyBlack(x: number, y: number) { for (let dx = -radius; dx <= radius; dx++) { for (let dy = -radius; dy <= radius; dy++) { diff --git a/src/scripts/stop-blocking.ts b/src/scripts/stop-blocking.ts index b8f4783..3fa3de6 100644 --- a/src/scripts/stop-blocking.ts +++ b/src/scripts/stop-blocking.ts @@ -1,5 +1,12 @@ +/** + * Proxies jQuery's `.bind()` (for the `"cut copy paste"` event) and + * `.keydown()` so that the website's copy/paste/right-click blocking + * is disabled. Reverts the prototypes after patching to avoid + * interfering with other code. + */ document.addEventListener("DOMContentLoaded", () => { const proxiedBind = $.prototype.bind; + /** Proxies jQuery `.bind()` to no-op the `"cut copy paste"` event. */ $.prototype.bind = function() { if (arguments[0].trim() === "cut copy paste") { arguments[1] = (_: any) => {}; @@ -9,6 +16,7 @@ document.addEventListener("DOMContentLoaded", () => { } const proxiedKeydown = $.prototype.keydown; + /** Proxies jQuery `.keydown()` so all key presses are allowed. */ $.prototype.keydown = function() { arguments[0] = (_: any) => true; diff --git a/src/shared/generate-web-page.ts b/src/shared/generate-web-page.ts index a07575f..990f430 100644 --- a/src/shared/generate-web-page.ts +++ b/src/shared/generate-web-page.ts @@ -1,3 +1,10 @@ +/** + * Opens a new browser tab and writes the given HTML content into it. + * Used to display dynamically-generated pages (e.g. PDF-printable views). + * + * @param content - The full HTML string to write. + * @param title - The document title (default: `"Banglar Bhumi"`). + */ export function generateWebPage(content: string, title: string = "Banglar Bhumi") { const tab = window.open("about:blank", "_blank"); if (!tab) { diff --git a/src/shared/intercept-jquery-ajax.ts b/src/shared/intercept-jquery-ajax.ts index a77dd44..7e1f54e 100644 --- a/src/shared/intercept-jquery-ajax.ts +++ b/src/shared/intercept-jquery-ajax.ts @@ -8,10 +8,15 @@ interface InterceptJqueryEntry { const postIntercepts: InterceptJqueryEntry[] = []; const getIntercepts: InterceptJqueryEntry[] = []; +/** + * On DOMContentLoaded, proxies jQuery's `$.post` and `$.get` so that + * registered interceptors are called before the real request. + */ document.addEventListener("DOMContentLoaded", () => { const proxiedPost = $.post; const proxiedGet = $.get; + /** Proxied `$.post` that invokes registered interceptors before the real call. */ $.post = function() { for (const { url, callback } of postIntercepts) { if (arguments[0].endsWith(url)) { @@ -21,6 +26,7 @@ document.addEventListener("DOMContentLoaded", () => { return proxiedPost.apply(this, Array.from(arguments) as any); }; + /** Proxied `$.get` that invokes registered interceptors before the real call. */ $.get = function() { for (const { url, callback } of getIntercepts) { if (arguments[0].endsWith(url)) { @@ -32,10 +38,24 @@ document.addEventListener("DOMContentLoaded", () => { }; }); +/** + * Registers a callback that fires whenever a jQuery POST request matches + * the given URL suffix. + * + * @param url - The URL suffix to match (checked via `endsWith`). + * @param callback - Receives the original `arguments` from `$.post`. + */ export function interceptPost(url: string, callback: InterceptJqueryAjaxCallback) { postIntercepts.push({ url, callback }); }; +/** + * Registers a callback that fires whenever a jQuery GET request matches + * the given URL suffix. + * + * @param url - The URL suffix to match (checked via `endsWith`). + * @param callback - Receives the original `arguments` from `$.get`. + */ export function interceptGet(url: string, callback: InterceptJqueryAjaxCallback) { getIntercepts.push({ url, callback }); }; diff --git a/src/shared/modify-dom.ts b/src/shared/modify-dom.ts index 22b8e8e..b268ae2 100644 --- a/src/shared/modify-dom.ts +++ b/src/shared/modify-dom.ts @@ -10,6 +10,11 @@ type DOMModificationRules = (DOMModificaitonRule | null)[]; let domModificationRules: DOMModificationRules = []; let domModificationFinishedCount = 0; +/** + * Runs whenever the DOM mutates. Iterates over pending modification + * rules; when the selector matches, the element's innerHTML and + * attributes are updated and the rule is marked as complete. + */ observeDOM(() => { for (let i = 0; i < domModificationRules.length; i++) { if (domModificationRules[i]) { @@ -45,6 +50,15 @@ observeDOM(() => { return false; }); +/** + * Registers one or more DOM modification rules. Each rule is a tuple of + * `[selector, attributeMap]`. When an element matching the selector is + * found, its `innerHTML` is replaced (if `attributeMap.innerHTML` is set) + * and all other entries in the map are applied as `setAttribute` calls + * (or `removeAttribute` when the value is `null`). + * + * @param modificationRules - Array of `[selector, attributes]` rules. + */ export function modifyDOM(modificationRules: DOMModificationRules) { domModificationRules = domModificationRules.concat(modificationRules); }; diff --git a/src/shared/observe-dom.ts b/src/shared/observe-dom.ts index e82ce1e..90a52ff 100644 --- a/src/shared/observe-dom.ts +++ b/src/shared/observe-dom.ts @@ -3,6 +3,12 @@ type ObserveDOMCallback = () => boolean; let callbacks: (ObserveDOMCallback | null)[] = []; let callbacksCalledCount = 0; +/** + * Starts a MutationObserver on the document that invokes each + * registered callback. A callback should return `true` when it + * has completed its work; once all callbacks are done the observer + * disconnects. + */ const observer = new MutationObserver(() => { for (let i = 0; i < callbacks.length; i++) { const callback = callbacks[i]; @@ -24,6 +30,14 @@ observer.observe(document, { subtree: true }); +/** + * Registers a callback to run on DOM mutations. + * The callback is invoked on each mutation tick and should return `true` + * when its work is finished. When all callbacks have completed the + * observer stops. + * + * @param callback - Function that returns `true` when done. + */ export function observeDOM(callback: ObserveDOMCallback) { callbacks.push(callback); }; diff --git a/src/shared/script-injector.ts b/src/shared/script-injector.ts index 697c8d6..16cb17c 100644 --- a/src/shared/script-injector.ts +++ b/src/shared/script-injector.ts @@ -2,6 +2,15 @@ type DataType = { [key: string]: string }; +/** + * Injects a `<script>` element into the document head (or `<html>`) with + * the given `src` pointing to an extension resource. Optional key/value + * data is attached as `data-*` attributes so the injected script can + * read them via `document.currentScript`. + * + * @param src - The extension-relative script path. + * @param data - Optional key/value pairs to set as `data-*` attributes. + */ export function injectScriptHead(src: string, data: DataType = {}) { const s = document.createElement("script") as HTMLScriptElement; s.src = chrome.runtime.getURL(src); diff --git a/src/shared/styling.ts b/src/shared/styling.ts index 759fecb..b19ace4 100644 --- a/src/shared/styling.ts +++ b/src/shared/styling.ts @@ -1,3 +1,10 @@ +/** + * Applies a set of inline CSS styles to every element matching the + * given CSS selector. + * + * @param selector - CSS selector string. + * @param styles - A map of CSS property names to values. + */ export function styles(selector: string, styles: Record<string, string>) { const elements = Array.from(document.querySelectorAll(selector)) as HTMLElement[]; |
