Files
timer/tools/probe_firefox.py
2026-09-19 07:28:18 -03:00

117 lines
3.8 KiB
Python

"""Probes Firefox's web-platform support by loading a page that reports back.
Usage: python3 tools/probe_firefox.py
Launches headless Firefox against a local page whose script POSTs its feature
detection results back to this server, then prints them.
"""
import http.server
import json
import os
import shutil
import subprocess
import threading
PORT = 8766
# Snap-confined Firefox cannot see /tmp, so the profile lives next to the repo.
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROFILE = os.path.join(ROOT, 'tmp-ffprof-probe')
USER_JS = '''
user_pref("browser.shell.checkDefaultBrowser", false);
user_pref("browser.startup.homepage_override.mstone", "ignore");
user_pref("datareporting.policy.dataSubmissionEnabled", false);
user_pref("toolkit.telemetry.enabled", false);
user_pref("app.update.auto", false);
'''
PAGE = """<!doctype html>
<html><head><link rel="manifest" href="/manifest.webmanifest"></head>
<body><script>
const info = {
userAgent: navigator.userAgent,
beforeinstallprompt: 'onbeforeinstallprompt' in window,
serviceWorker: 'serviceWorker' in navigator,
wakeLock: 'wakeLock' in navigator,
vibrate: typeof navigator.vibrate === 'function',
audioContext: !!(window.AudioContext || window.webkitAudioContext),
localStorage: (() => { try { localStorage.setItem('t', '1'); return true; } catch { return false; } })(),
standaloneDisplayMode: matchMedia('(display-mode: standalone)').matches,
secureContext: window.isSecureContext,
manifestParsed: (() => {
const m = document.querySelector('link[rel=manifest]');
return m ? m.href : null;
})(),
};
fetch('/report', { method: 'POST', body: JSON.stringify(info) });
</script></body></html>"""
report = {}
done = threading.Event()
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/manifest.webmanifest':
body = json.dumps({
"name": "Probe", "short_name": "Probe", "start_url": "./",
"display": "standalone",
"icons": [{"src": "/icon.png", "sizes": "192x192", "type": "image/png"}],
}).encode()
self.send_response(200)
self.send_header('Content-Type', 'application/manifest+json')
self.send_header('Content-Length', str(len(body)))
self.end_headers()
self.wfile.write(body)
return
body = PAGE.encode()
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.send_header('Content-Length', str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_POST(self):
length = int(self.headers.get('Content-Length', 0))
report.update(json.loads(self.rfile.read(length) or b'{}'))
self.send_response(204)
self.end_headers()
done.set()
def log_message(self, *args):
pass
def main():
server = http.server.ThreadingHTTPServer(('127.0.0.1', PORT), Handler)
threading.Thread(target=server.serve_forever, daemon=True).start()
shutil.rmtree(PROFILE, ignore_errors=True)
os.makedirs(PROFILE, exist_ok=True)
with open(os.path.join(PROFILE, 'user.js'), 'w') as fh:
fh.write(USER_JS)
proc = subprocess.Popen(
['firefox', '--headless', '--no-remote', '--new-instance',
'--profile', PROFILE, f'http://127.0.0.1:{PORT}/'],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
ok = done.wait(timeout=30)
proc.terminate()
try:
proc.wait(timeout=10)
except subprocess.TimeoutExpired:
proc.kill()
server.shutdown()
shutil.rmtree(PROFILE, ignore_errors=True)
if not ok:
print('probe: no report received (Firefox did not run the page)')
return
print(json.dumps(report, indent=2, sort_keys=True))
if __name__ == '__main__':
main()