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)