Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

114
tools/test-gaia-keyboard.py Normal file
View file

@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""Exercise Gaia's keyboard in an isolated desktop profile. Never send a message."""
import argparse
import importlib.util
import json
import os
from pathlib import Path
import subprocess
import time
from marionette_client import Client
ROOT = Path(__file__).resolve().parents[1]
spec = importlib.util.spec_from_file_location('probe', ROOT / 'tools/probe-desktop.py')
probe = importlib.util.module_from_spec(spec)
spec.loader.exec_module(probe)
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--touch-events', action='store_true')
parser.add_argument('--dpr', type=int, choices=(1, 3), default=1)
parser.add_argument('--rotation', action='store_true')
args = parser.parse_args()
if args.dpr != 1 and not args.touch_events:
parser.error('--dpr 3 requires --touch-events')
with Client(2857) as client:
probe.register(client)
def evaluate(app, code):
result = probe.evaluate(client, app, code)
if 'value' not in result:
raise RuntimeError(result)
return result['value']
def wait(app, code):
deadline = time.monotonic() + 12
while time.monotonic() < deadline:
result=probe.evaluate(client, app, code)
if result.get('value'):
return
if 'error' in result:
raise RuntimeError(result['error'])
time.sleep(.15)
raise TimeoutError(code)
def tap(app, selector):
if app == 'keyboard':
selector = '.keyboard-type-container[data-active] ' + selector
rect = evaluate(app, 'return document.querySelector(' + json.dumps(selector) + ').getBoundingClientRect().toJSON();')
point = {'x': rect['x'] + rect['width'] / 2, 'y': rect['y'] + rect['height'] / 2}
if args.touch_events:
script = '''const done=arguments[arguments.length-1];
const w=Services.wm.getMostRecentWindow('vulpes:host');
function find(b){if(b.currentWindowGlobal?.documentURI.spec.startsWith(PREFIX))return b;
for(const c of b.children){const f=find(c);if(f)return f;}}
find(w.document.getElementById('system').browsingContext).currentWindowGlobal
.getActor('VulpesProbe').sendQuery('Tap',POINT).then(done,e=>done({error:String(e)}));'''
script = script.replace('PREFIX', json.dumps('http://' + app + '.localhost:8765/')).replace('POINT', json.dumps(point))
result = client.call('WebDriver:ExecuteAsyncScript', {
'script': script, 'args': [], 'newSandbox': True, 'sandbox': 'system',
})['value']
assert result is True, result
else:
frame_selector = '#vulpes-gaia-keyboard' if app == 'keyboard' else 'iframe[src^="http://' + app + '.localhost:8765/"]'
frame = evaluate('system', 'return document.querySelector(' + json.dumps(frame_selector) + ').getBoundingClientRect().toJSON();')
subprocess.run(['xdotool', 'search', '--name', '^Vulpes', 'windowraise',
'windowfocus', 'mousemove', '--window', '%1',
str(round(frame['x'] + point['x'])), str(round(frame['y'] + point['y'])),
'click', '1'], env=os.environ, check=True)
time.sleep(.35)
def size(width, height):
client.execute(f"const w=Services.wm.getMostRecentWindow('vulpes:host');w.document.documentElement.style.minWidth='0';w.document.documentElement.style.minHeight='0';w.resizeTo({width},{height});return true;")
time.sleep(.6)
wait('system', 'return document.body?.getAttribute("ready-state")==="fullyLoaded";')
client.execute(f"Services.prefs.setCharPref('layout.css.devPixelsPerPx','{args.dpr}');return true;")
size(360, 740)
evaluate('system', 'ScreenManager.turnScreenOn(true);if(Service.query("locked"))await Service.request("unlock",{forcibly:true});return true;')
time.sleep(1)
evaluate('system', 'await VulpesCompat.call("apps.launch",{manifestURL:"http://sms.localhost:8765/manifest.webapp"});return true;')
wait('sms', 'return !!window.Navigation;')
evaluate('sms', 'await Navigation.toPanel("thread-list");await Navigation.toPanel("composer");return true;')
tap('sms', '.recipient[contenteditable]')
wait('keyboard', 'return navigator.mozInputMethod?.inputcontext?.inputType==="tel" && !!document.querySelector("button[aria-label=\\"2\\"]");')
tap('keyboard', 'button[aria-label="2"]')
tap('keyboard', 'button[aria-label="3"]')
number = evaluate('sms', 'return document.querySelector(".recipient[contenteditable]").textContent;')
assert number.strip() == '23', number
numeric_height = evaluate('keyboard', 'return document.querySelector("button[aria-label=\\"2\\"]").getBoundingClientRect().height;')
assert numeric_height >= 44, numeric_height
tap('sms', '#messages-input')
wait('keyboard', 'return navigator.mozInputMethod?.inputcontext?.inputType==="text" && !!document.querySelector("button[aria-label=\\"a\\"]");')
tap('keyboard', 'button[aria-label="a"]')
text = evaluate('sms', 'return document.querySelector("#messages-input").textContent;')
assert text.lower() == 'a', text
assert evaluate('sms', 'return document.querySelector(".recipient[contenteditable]").textContent;') == number, 'Letter changed the recipient'
evaluate('sms', 'getSelection().setPosition(document.querySelector("#messages-input").firstChild,0);return true;')
tap('keyboard', 'button[aria-label="b"]')
assert evaluate('sms', 'return document.querySelector("#messages-input").textContent.toLowerCase();') == 'ba'
tap('keyboard', 'button[data-keycode="8"]')
assert evaluate('sms', 'return document.querySelector("#messages-input").textContent.toLowerCase();') == 'a'
report = {'passed': True, 'sent': False, 'dpr': args.dpr,
'recipientUnchanged': True, 'messageTyped': True, 'caretAndBackspace': True,
'numericKeyHeight': numeric_height}
if args.rotation:
size(740, 360)
wait('keyboard', 'return !app.viewManager.screenInPortraitMode();')
report['landscape'] = evaluate('keyboard', 'return {width:innerWidth,height:innerHeight,rem:app.viewManager.getRemToPx(),font:parseFloat(getComputedStyle(document.documentElement).fontSize)};')
assert abs(report['landscape']['font'] - report['landscape']['width'] / 64) < .05
size(360, 740)
wait('keyboard', 'return app.viewManager.screenInPortraitMode();')
report['portrait'] = evaluate('keyboard', 'return {height:innerHeight,key:document.querySelector("button[aria-label=\\"a\\"]").getBoundingClientRect().height,font:parseFloat(getComputedStyle(document.documentElement).fontSize)};')
assert report['portrait']['key'] >= 44, report
print(json.dumps(report))