• Re: Tutorial: Firefox right?click extension + native host on Windows (G

    From Hank Rogers@3:633/10 to All on Tue Feb 24 20:11:21 2026
    Subject: Re: Tutorial: Firefox right?click extension + native host on Windows (GUI launch mystery)

    Maria Sophia wrote on 2/24/2026 4:51 PM:
    Maria Sophia wrote:
    As far as I've been able to ascertain by testing, no GUI program will
    ever
    appear on a Windows desktop when launched from a Firefox native host on
    Windows, but, it should still work on Linux.

    Voila!
    Success at last!

    The native host runs inside a restricted, non-interactive Windows session. Unfortunately, we've learned that Windows-GUI programs apparently cannot appear from that session, but file I/O works perfectly.

    So the example file-based-change-detector below is a natural fit.

    Once I stopped fighting that one immovable Windows-GUI wall, a whole landscape of genuinely useful, practical, even elegant but non-GUI possibilities open up for our first working Firefox extension.
    Let's write a simple webpage-change detector instead. It will tell us if
    a web page has Changed or NotChanged.

    Overview:
    a. We'll keep everything in the Firefox home directory, as before
    ÿÿ C:\app\browser\firefox\openwithgvim\*.{js,bat,json,py,log,reg}
    b. And create a subdirectory to watch if web pages have been changed.
    ÿÿ C:\app\browser\firefox\openwithgvim\pagewatch\{report.txt,last.html}
    c. Inside pagewatch, the Python host will maintain:
    ÿÿ i. last.html, the last version of the page
    ÿÿ ii. report.txt, the human-readable "changed / not changed" result
    ÿÿ iii. (optional) snapshots/, if we ever want to archive versions
    This keeps everything tidy and avoids clutter

    The logic will be simple because this is to be an example extension.
    A. If last.html does not exist:
    ÿÿ Write the new HTML to last.html
    ÿÿ Write a report saying: FIRST RUN - baseline saved
    ÿÿ Return { ok: true, changed: true }
    B. If last.html does exist:
    ÿÿ Read it
    ÿÿ Compare it to the new HTML (simple string comparison)
    C. If identical:
    ÿÿ Write: NO CHANGE
    ÿÿ Return { ok: true, changed: false }
    D. If different:
    ÿÿ Write:
    ÿÿÿ CHANGED
    ÿÿÿ Old length: ####
    ÿÿÿ New length: ####
    ÿÿÿ Timestamp: ...
    ÿÿ Overwrite last.html with the new HTML
    ÿÿ Return { ok: true, changed: true }
    E. In testing, I had to add code to overcome file locks gracefully.

    Here is every step of the test procedure:
    1. Start Firefox
    2. Click your bookmark for about:debugging#/runtime/this-firefox
    3. Click "Load Temporary Add-on..."
    4. Select C:\app\browser\firefox\openwithgvim\manifest.json
    5. Open a local file file:///C:/data/amazon/vine/vine.htm
    6. Rightclick in white space on that local file
    7. Select "Open page source in gVim" (we never changed it)
    8. Check the pagewatch report file for status
    ÿ C:\> type C:\app\browser\firefox\openwithgvim\pagewatch\report.txt
    ÿÿÿÿÿÿ NO CHANGE
    ÿÿÿÿÿÿ URL: file:///C:/data/amazon/vine/vine.htm
    ÿÿÿÿÿÿ Timestamp: 2026-02-24 16:34:02
    ÿÿÿÿÿÿ Length: 53565 bytes
    9. Edit the source (Control+U or rightclick > View page source
    ÿ Change something & refresh the page ÿ Note that ViewPageSource is an
    edit due to these settings:
    ÿÿ about:config > view_source.editor
    ÿÿ view_source.editor.external = true
    ÿÿ view_source.editor.path = C:\app\editor\txt\vi\gvim.exe
    10. Check the pagewatch report file for status
    ÿÿ C:\> type C:\app\browser\firefox\openwithgvim\pagewatch\report.txt
    ÿÿÿÿÿÿÿ CHANGED
    ÿÿÿÿÿÿÿ URL: file:///C:/data/sys/apppath/vistuff/vine.htm
    ÿÿÿÿÿÿÿ Timestamp: 2026-02-24 16:39:30
    ÿÿÿÿÿÿÿ Old length: 53565 bytes
    ÿÿÿÿÿÿÿ New length: 53541 bytes

    The only file that changed was the python native messaging host script. =====< cut below for gvim_host.py >=====
    import sys
    import struct
    import json
    import os
    from datetime import datetime

    # Base directory for everything
    BASE_DIR = r"C:\app\browser\firefox\openwithgvim"
    WATCH_DIR = os.path.join(BASE_DIR, "pagewatch")

    LAST_FILE = os.path.join(WATCH_DIR, "last.html")
    REPORT_FILE = os.path.join(WATCH_DIR, "report.txt")

    DEBUG_LOG = os.path.join(BASE_DIR, "host_debug.log")
    ERR_LOG = os.path.join(BASE_DIR, "host_stderr.log")


    def log(msg):
    ÿÿÿ """Write debug messages to stderr log, but never crash if the file
    is locked."""
    ÿÿÿ try:
    ÿÿÿÿÿÿÿ with open(ERR_LOG, "a", encoding="utf-8") as f:
    ÿÿÿÿÿÿÿÿÿÿÿ f.write(str(msg) + "\n")
    ÿÿÿ except Exception:
    ÿÿÿÿÿÿÿ # Windows sometimes locks files; logging must never kill the host
    ÿÿÿÿÿÿÿ pass


    def ensure_directories():
    ÿÿÿ """Create the pagewatch directory if missing."""
    ÿÿÿ if not os.path.exists(WATCH_DIR):
    ÿÿÿÿÿÿÿ os.makedirs(WATCH_DIR, exist_ok=True)


    def read_message():
    ÿÿÿ """Read a native message from Firefox."""
    ÿÿÿ raw_length = sys.stdin.buffer.read(4)
    ÿÿÿ if not raw_length:
    ÿÿÿÿÿÿÿ log("No length header, stdin closed")
    ÿÿÿÿÿÿÿ return None

    ÿÿÿ length = struct.unpack("<I", raw_length)[0]
    ÿÿÿ data = sys.stdin.buffer.read(length).decode("utf-8")
    ÿÿÿ return json.loads(data)


    def send_message(msg):
    ÿÿÿ """Send a native message back to Firefox."""
    ÿÿÿ encoded = json.dumps(msg).encode("utf-8")
    ÿÿÿ sys.stdout.buffer.write(struct.pack("<I", len(encoded)))
    ÿÿÿ sys.stdout.buffer.write(encoded)
    ÿÿÿ sys.stdout.buffer.flush()


    def write_report(text):
    ÿÿÿ """Write a human-readable report."""
    ÿÿÿ try:
    ÿÿÿÿÿÿÿ with open(REPORT_FILE, "w", encoding="utf-8") as f:
    ÿÿÿÿÿÿÿÿÿÿÿ f.write(text)
    ÿÿÿ except Exception as e:
    ÿÿÿÿÿÿÿ log(f"Failed to write report: {e}")


    def main():
    ÿÿÿ ensure_directories()

    ÿÿÿ while True:
    ÿÿÿÿÿÿÿ msg = read_message()
    ÿÿÿÿÿÿÿ if msg is None:
    ÿÿÿÿÿÿÿÿÿÿÿ break

    ÿÿÿÿÿÿÿ html = msg.get("html", "")
    ÿÿÿÿÿÿÿ url = msg.get("url", "")

    ÿÿÿÿÿÿÿ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    ÿÿÿÿÿÿÿ # FIRST RUN - no last.html exists
    ÿÿÿÿÿÿÿ if not os.path.exists(LAST_FILE):
    ÿÿÿÿÿÿÿÿÿÿÿ try:
    ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ with open(LAST_FILE, "w", encoding="utf-8") as f:
    ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ f.write(html)
    ÿÿÿÿÿÿÿÿÿÿÿ except Exception as e:
    ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ log(f"Failed to write baseline last.html: {e}")

    ÿÿÿÿÿÿÿÿÿÿÿ report = (
    ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ f"FIRST RUN - baseline saved\n"
    ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ f"URL: {url}\n"
    ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ f"Timestamp: {timestamp}\n"
    ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ f"Length: {len(html)} bytes\n"
    ÿÿÿÿÿÿÿÿÿÿÿ )
    ÿÿÿÿÿÿÿÿÿÿÿ write_report(report)

    ÿÿÿÿÿÿÿÿÿÿÿ send_message({"ok": True, "changed": True, "first_run": True})
    ÿÿÿÿÿÿÿÿÿÿÿ continue

    ÿÿÿÿÿÿÿ # SUBSEQUENT RUNS - compare with last.html
    ÿÿÿÿÿÿÿ try:
    ÿÿÿÿÿÿÿÿÿÿÿ with open(LAST_FILE, "r", encoding="utf-8") as f:
    ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ old_html = f.read()
    ÿÿÿÿÿÿÿ except Exception as e:
    ÿÿÿÿÿÿÿÿÿÿÿ log(f"Error reading last.html: {e}")
    ÿÿÿÿÿÿÿÿÿÿÿ old_html = ""

    ÿÿÿÿÿÿÿ changed = (html != old_html)

    ÿÿÿÿÿÿÿ if not changed:
    ÿÿÿÿÿÿÿÿÿÿÿ report = (
    ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ f"NO CHANGE\n"
    ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ f"URL: {url}\n"
    ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ f"Timestamp: {timestamp}\n"
    ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ f"Length: {len(html)} bytes\n"
    ÿÿÿÿÿÿÿÿÿÿÿ )
    ÿÿÿÿÿÿÿÿÿÿÿ write_report(report)

    ÿÿÿÿÿÿÿÿÿÿÿ send_message({"ok": True, "changed": False})
    ÿÿÿÿÿÿÿÿÿÿÿ continue

    ÿÿÿÿÿÿÿ # If changed, overwrite last.html
    ÿÿÿÿÿÿÿ try:
    ÿÿÿÿÿÿÿÿÿÿÿ with open(LAST_FILE, "w", encoding="utf-8") as f:
    ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ f.write(html)
    ÿÿÿÿÿÿÿ except Exception as e:
    ÿÿÿÿÿÿÿÿÿÿÿ log(f"Failed to update last.html: {e}")

    ÿÿÿÿÿÿÿ report = (
    ÿÿÿÿÿÿÿÿÿÿÿ f"CHANGED\n"
    ÿÿÿÿÿÿÿÿÿÿÿ f"URL: {url}\n"
    ÿÿÿÿÿÿÿÿÿÿÿ f"Timestamp: {timestamp}\n"
    ÿÿÿÿÿÿÿÿÿÿÿ f"Old length: {len(old_html)} bytes\n"
    ÿÿÿÿÿÿÿÿÿÿÿ f"New length: {len(html)} bytes\n"
    ÿÿÿÿÿÿÿ )
    ÿÿÿÿÿÿÿ write_report(report)

    ÿÿÿÿÿÿÿ send_message({"ok": True, "changed": True})


    if __name__ == "__main__":
    ÿÿÿ main()
    =====< cut above for gvim_host.py >=====

    We never lose when customizing Firefox but sometimes it's not easy.
    Tested only on Windows 10, Firefox 133.0 (64-bit) using my file specs.

    I would like to see some tutorials on Seamonkey. Especially the later versions.


    --- PyGate Linux v1.5.12
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)