This tutorial can be rightly titled something like:
*Adding a right-click Firefox menu item that calls a native program*
Everything is working except the final step, which should work in Linux,
but which isn't working (yet) in Windows. Yesterday I switched from gVim to Notepad++, and today I switched to Notepad.exe on the temporary file.
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.
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
-a-a C:\app\browser\firefox\openwithgvim\*.{js,bat,json,py,log,reg}
b. And create a subdirectory to watch if web pages have been changed.
-a-a C:\app\browser\firefox\openwithgvim\pagewatch\{report.txt,last.html}
c. Inside pagewatch, the Python host will maintain:
-a-a i. last.html, the last version of the page
-a-a ii. report.txt, the human-readable "changed / not changed" result
-a-a 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:
-a-a Write the new HTML to last.html
-a-a Write a report saying: FIRST RUN - baseline saved
-a-a Return { ok: true, changed: true }
B. If last.html does exist:
-a-a Read it
-a-a Compare it to the new HTML (simple string comparison)
C. If identical:
-a-a Write: NO CHANGE
-a-a Return { ok: true, changed: false }
D. If different:
-a-a Write:
-a-a-a CHANGED
-a-a-a Old length: ####
-a-a-a New length: ####
-a-a-a Timestamp: ...
-a-a Overwrite last.html with the new HTML
-a-a 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
-a C:\> type C:\app\browser\firefox\openwithgvim\pagewatch\report.txt
-a-a-a-a-a-a NO CHANGE
-a-a-a-a-a-a URL: file:///C:/data/amazon/vine/vine.htm
-a-a-a-a-a-a Timestamp: 2026-02-24 16:34:02
-a-a-a-a-a-a Length: 53565 bytes
9. Edit the source (Control+U or rightclick > View page source
-a Change something & refresh the page -a Note that ViewPageSource is an edit due to these settings:
-a-a about:config > view_source.editor
-a-a view_source.editor.external = true
-a-a view_source.editor.path = C:\app\editor\txt\vi\gvim.exe
10. Check the pagewatch report file for status
-a-a C:\> type C:\app\browser\firefox\openwithgvim\pagewatch\report.txt
-a-a-a-a-a-a-a CHANGED
-a-a-a-a-a-a-a URL: file:///C:/data/sys/apppath/vistuff/vine.htm
-a-a-a-a-a-a-a Timestamp: 2026-02-24 16:39:30
-a-a-a-a-a-a-a Old length: 53565 bytes
-a-a-a-a-a-a-a 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):
-a-a-a """Write debug messages to stderr log, but never crash if the file is locked."""
-a-a-a try:
-a-a-a-a-a-a-a with open(ERR_LOG, "a", encoding="utf-8") as f:
-a-a-a-a-a-a-a-a-a-a-a f.write(str(msg) + "\n")
-a-a-a except Exception:
-a-a-a-a-a-a-a # Windows sometimes locks files; logging must never kill the host
-a-a-a-a-a-a-a pass
def ensure_directories():
-a-a-a """Create the pagewatch directory if missing."""
-a-a-a if not os.path.exists(WATCH_DIR):
-a-a-a-a-a-a-a os.makedirs(WATCH_DIR, exist_ok=True)
def read_message():
-a-a-a """Read a native message from Firefox."""
-a-a-a raw_length = sys.stdin.buffer.read(4)
-a-a-a if not raw_length:
-a-a-a-a-a-a-a log("No length header, stdin closed")
-a-a-a-a-a-a-a return None
-a-a-a length = struct.unpack("<I", raw_length)[0]
-a-a-a data = sys.stdin.buffer.read(length).decode("utf-8")
-a-a-a return json.loads(data)
def send_message(msg):
-a-a-a """Send a native message back to Firefox."""
-a-a-a encoded = json.dumps(msg).encode("utf-8")
-a-a-a sys.stdout.buffer.write(struct.pack("<I", len(encoded)))
-a-a-a sys.stdout.buffer.write(encoded)
-a-a-a sys.stdout.buffer.flush()
def write_report(text):
-a-a-a """Write a human-readable report."""
-a-a-a try:
-a-a-a-a-a-a-a with open(REPORT_FILE, "w", encoding="utf-8") as f:
-a-a-a-a-a-a-a-a-a-a-a f.write(text)
-a-a-a except Exception as e:
-a-a-a-a-a-a-a log(f"Failed to write report: {e}")
def main():
-a-a-a ensure_directories()
-a-a-a while True:
-a-a-a-a-a-a-a msg = read_message()
-a-a-a-a-a-a-a if msg is None:
-a-a-a-a-a-a-a-a-a-a-a break
-a-a-a-a-a-a-a html = msg.get("html", "")
-a-a-a-a-a-a-a url = msg.get("url", "")
-a-a-a-a-a-a-a timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
-a-a-a-a-a-a-a # FIRST RUN - no last.html exists
-a-a-a-a-a-a-a if not os.path.exists(LAST_FILE):
-a-a-a-a-a-a-a-a-a-a-a try:
-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a with open(LAST_FILE, "w", encoding="utf-8") as f:
-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a f.write(html)
-a-a-a-a-a-a-a-a-a-a-a except Exception as e:
-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a log(f"Failed to write baseline last.html: {e}")
-a-a-a-a-a-a-a-a-a-a-a report = (
-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a f"FIRST RUN - baseline saved\n"
-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a f"URL: {url}\n"
-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a f"Timestamp: {timestamp}\n"
-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a f"Length: {len(html)} bytes\n"
-a-a-a-a-a-a-a-a-a-a-a )
-a-a-a-a-a-a-a-a-a-a-a write_report(report)
-a-a-a-a-a-a-a-a-a-a-a send_message({"ok": True, "changed": True, "first_run": True})
-a-a-a-a-a-a-a-a-a-a-a continue
-a-a-a-a-a-a-a # SUBSEQUENT RUNS - compare with last.html
-a-a-a-a-a-a-a try:
-a-a-a-a-a-a-a-a-a-a-a with open(LAST_FILE, "r", encoding="utf-8") as f:
-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a old_html = f.read()
-a-a-a-a-a-a-a except Exception as e:
-a-a-a-a-a-a-a-a-a-a-a log(f"Error reading last.html: {e}")
-a-a-a-a-a-a-a-a-a-a-a old_html = ""
-a-a-a-a-a-a-a changed = (html != old_html)
-a-a-a-a-a-a-a if not changed:
-a-a-a-a-a-a-a-a-a-a-a report = (
-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a f"NO CHANGE\n"
-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a f"URL: {url}\n"
-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a f"Timestamp: {timestamp}\n"
-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a f"Length: {len(html)} bytes\n"
-a-a-a-a-a-a-a-a-a-a-a )
-a-a-a-a-a-a-a-a-a-a-a write_report(report)
-a-a-a-a-a-a-a-a-a-a-a send_message({"ok": True, "changed": False})
-a-a-a-a-a-a-a-a-a-a-a continue
-a-a-a-a-a-a-a # If changed, overwrite last.html
-a-a-a-a-a-a-a try:
-a-a-a-a-a-a-a-a-a-a-a with open(LAST_FILE, "w", encoding="utf-8") as f:
-a-a-a-a-a-a-a-a-a-a-a-a-a-a-a f.write(html)
-a-a-a-a-a-a-a except Exception as e:
-a-a-a-a-a-a-a-a-a-a-a log(f"Failed to update last.html: {e}")
-a-a-a-a-a-a-a report = (
-a-a-a-a-a-a-a-a-a-a-a f"CHANGED\n"
-a-a-a-a-a-a-a-a-a-a-a f"URL: {url}\n"
-a-a-a-a-a-a-a-a-a-a-a f"Timestamp: {timestamp}\n"
-a-a-a-a-a-a-a-a-a-a-a f"Old length: {len(old_html)} bytes\n"
-a-a-a-a-a-a-a-a-a-a-a f"New length: {len(html)} bytes\n"
-a-a-a-a-a-a-a )
-a-a-a-a-a-a-a write_report(report)
-a-a-a-a-a-a-a send_message({"ok": True, "changed": True})
if __name__ == "__main__":
-a-a-a 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.
| Sysop: | Amessyroom |
|---|---|
| Location: | Fayetteville, NC |
| Users: | 59 |
| Nodes: | 6 (0 / 6) |
| Uptime: | 18:04:21 |
| Calls: | 810 |
| Calls today: | 1 |
| Files: | 1,287 |
| D/L today: |
10 files (21,017K bytes) |
| Messages: | 193,396 |