45 How to execute a user script before a page is saved
Gildas edited this page 2026-10-20 02:52:26 +02:00
  1. When using SingleFile as:
  • an extension, enable the hidden option userScriptEnabled in the options editor. Open the options page and click the icon next to the title to open the Options editor. If two icons are displayed, the editor is the second one. Replace "userScriptEnabled": false with "userScriptEnabled": true, then click Save. You will also need to install an extension to store and run user scripts (search "user script" on stores).
  • a CLI tool, use the option --browser-script to pass script path(s) to SingleFile.
  1. Dispatch the custom event single-file-user-script-init in the user script.
dispatchEvent(new CustomEvent("single-file-user-script-init"));
  1. Listen to the custom event single-file-on-before-capture-request in the user script. The listener function is called just before the page is saved.
addEventListener("single-file-on-before-capture-request", () => {
  console.log("The page will be saved by SingleFile");
});

If the listener function has to be async, call event.preventDefault() and dispatch the custom event single-file-on-before-capture-response once the asynchronous tasks are terminated.

addEventListener("single-file-on-before-capture-request", async event => {
  event.preventDefault();
  try {
    await ...
  } finally {
    dispatchEvent(new CustomEvent("single-file-on-before-capture-response"));
  }
});

You can also get and set the options via the detail property of the single-file-on-before-capture-request and single-file-on-before-capture-response events. If the detail value is tainted (e.g. in Firefox), you should dispatch the single-file-user-script-init event with { detail: "jsonDetail" } as option. Then, you have to handle the detail value as a JSON string (instead of an object).

  1. Listen to the custom event single-file-on-after-capture-request in the user script to execute a script after the page is processed by SingleFile.
addEventListener("single-file-on-after-capture-request", () => {
  console.log("The page has been processed by SingleFile");
});

Like single-file-on-before-capture-request, you can call event.preventDefault() and dispatch the custom event single-file-on-after-capture-response if the listener is an async function.

  1. Good to know
  • Hidden elements are removed from the saved page when the option "HTML content > remove hidden elements" is enabled (the default). Two kinds of hidden elements are kept anyway:

    • the content of a <details> element, even when it is closed,
    • a popover element or a <dialog> element with an id, when a button of the page refers to it with a popovertarget or commandfor attribute.

    If your script builds content that must stay hidden until the user opens it (e.g. a dialog or a menu), use one of these elements. Buttons with popovertarget or commandfor open and close the element without JavaScript, so it also works in the saved page.

  • When the option "Stylesheets > remove unused styles" is enabled (the default), a CSS rule depending on the state of an element is kept if the selector matches without its pseudo-classes. For example, #toggle:checked ~ .panel { display: block; } is kept if #toggle ~ .panel matches an element. A checkbox and a <label for="toggle"> can therefore show and hide content in the saved page, even if scripts are removed.

  • With the CLI, the option --block-scripts is enabled by default. It removes the scripts from the saved page, but the scripts of the page still run while the page is loaded, so a user script can click on a button of the page before it is saved. Pass --block-scripts=false if the saved page must keep its scripts.

  1. Examples
  • This script removes images from the page just before saving it and restores them after the page is processed.
// ==UserScript==
// @name         Remove images
// @namespace    https://github.com/gildas-lormeau/SingleFile
// @version      1.0
// @description  [SingleFile] Remove all the images
// @author       Gildas Lormeau
// @match        *://*/*
// @grant        none
// ==/UserScript==


(() => {

  const elements = new Map();
  const removedElementsSelector = "img";
  dispatchEvent(new CustomEvent("single-file-user-script-init"));

  addEventListener("single-file-on-before-capture-request", () => {
    document.querySelectorAll(removedElementsSelector).forEach(element => {
      const placeHolderElement = document.createElement(element.tagName);
      elements.set(placeHolderElement, element);
      element.parentElement.replaceChild(placeHolderElement, element);
    });
  });

  addEventListener("single-file-on-after-capture-request", () => {
    Array.from(elements).forEach(([placeHolderElement, element]) => {
      placeHolderElement.parentElement.replaceChild(element, placeHolderElement);
    });
    elements.clear();
  });

})();
  • This script converts the title to kebab-case and restores it after the page is processed.
// ==UserScript==
// @name         Convert title to kebab-case
// @namespace    https://github.com/gildas-lormeau/SingleFile
// @version      1.0
// @description  [SingleFile] Convert title to kebab-case
// @author       Gildas Lormeau
// @match        *://*/*
// @noframes
// @grant        none
// ==/UserScript==


(() => {

  let title;
  dispatchEvent(new CustomEvent("single-file-user-script-init"));

  addEventListener("single-file-on-before-capture-request", () => {
    title = document.title;
    document.title = document.title
      .replace(/[^a-z0-9-\s]/gi, "")
      .replace(/[-\s]+/g, "-")
      .toLowerCase();
  });

  addEventListener("single-file-on-after-capture-request", () => {
    document.title = title;
  });

})();