VulpesOS/tools/modernize-syntax.py
OverSu b38dea0597 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.
2026-09-30 23:13:03 +02:00

121 lines
12 KiB
Python

#!/usr/bin/env python3
"""Explicit, idempotent syntax repairs for sources and prebuilt migration assets."""
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
replacements = {
"timestamp: String(message.timestamp)": "timestamp: String(+message.timestamp)",
"timestamp:String(message.timestamp)": "timestamp:String(+message.timestamp)",
"if (l10nAttrs.raw) {": 'if (typeof l10nAttrs.raw === "string") {',
"if(l10nAttrs.raw){": 'if(typeof l10nAttrs.raw==="string"){',
"this.startupMetadata.splice(entry, 1);\n icon.updateName();": "this.startupMetadata.splice(entry, 1);\n icon.updateName();\n if (window.VulpesCompat && icon.app) icon.refresh(); // Replace legacy rasterized cache with original app assets.",
"this.startupMetadata.splice(entry,1);icon.updateName();": "this.startupMetadata.splice(entry,1);icon.updateName();if(window.VulpesCompat&&icon.app)icon.refresh();",
"menuStatus = document.getElementById('fxa-desc');": "menuStatus = document.getElementById('fxa-desc');\n if(window.VulpesCompat){menuStatus.removeAttribute('data-l10n-id');menuStatus.textContent=(document.documentElement.lang||navigator.language).startsWith('fr')?'Service non disponible sur ce bureau':'Service unavailable on this desktop';return;}",
"menuStatus=document.getElementById('fxa-desc');": "menuStatus=document.getElementById('fxa-desc');if(window.VulpesCompat){menuStatus.removeAttribute('data-l10n-id');menuStatus.textContent=(document.documentElement.lang||navigator.language).startsWith('fr')?'Service non disponible sur ce bureau':'Service unavailable on this desktop';return;}",
"var onImageLoad = (evt) => {\n window.requestAnimationFrame(() => {\n var newActiveImage = evt.target.closest('img:not(.active)');": "var onImageLoad = (evt) => {\n const image = evt.target;\n window.requestAnimationFrame(() => {\n var newActiveImage = image.closest('img:not(.active)');",
"var onImageLoad=(evt)=>{window.requestAnimationFrame(()=>{var newActiveImage=evt.target.closest('img:not(.active)');": "var onImageLoad=(evt)=>{const image=evt.target;window.requestAnimationFrame(()=>{var newActiveImage=image.closest('img:not(.active)');",
"var sdcard = navigator.getDeviceStorage('sdcard');\n var request = sdcard.get(captionsFilename);": "var sdcard = navigator.getDeviceStorage('sdcard');\n if (!sdcard) return; // Captions are optional on desktop.\n var request = sdcard.get(captionsFilename);",
"var sdcard=navigator.getDeviceStorage('sdcard');var request=sdcard.get(captionsFilename);": "var sdcard=navigator.getDeviceStorage('sdcard');if(!sdcard)return;var request=sdcard.get(captionsFilename);",
"this.activeAlarm.snoozeAlarm(alert.id);\n window.close();": "this.activeAlarm.snoozeAlarm(alert.id).then(() => window.close()).catch(console.error);",
"this.activeAlarm.close(alert.type, alert.id);\n window.close();": "this.activeAlarm.close(alert.type, alert.id).then(() => window.close()).catch(console.error);",
"_messageHandler: function(evt) {\n var data = evt.data;": "_messageHandler: function(evt) {\n if (!evt.source || evt.origin !== window.location.origin || !evt.data) return;\n var data = evt.data;",
"offscreenImage.src = '';": "offscreenImage.onload = offscreenImage.onerror = null; offscreenImage.removeAttribute('src');",
"offscreenImage.src='';": "offscreenImage.onload=offscreenImage.onerror=null;offscreenImage.removeAttribute('src');",
"exports.scheduler = exports.scheduler || new Scheduler();": "if (!exports.scheduler || typeof exports.scheduler.mutation !== 'function') Object.defineProperty(exports, 'scheduler', {value:new Scheduler(),configurable:true,writable:true});",
"exports.scheduler=exports.scheduler||new Scheduler();": "if(!exports.scheduler||typeof exports.scheduler.mutation!=='function')Object.defineProperty(exports,'scheduler',{value:new Scheduler(),configurable:true,writable:true});",
"function postMessageSync(win, data, transfer) {": "function postMessageSync(win, data, transfer) { try { void win.document; } catch (_) { win.postMessage(data, location.origin, transfer || []); return; }",
"function postMessageSync(win,data,transfer){": "function postMessageSync(win,data,transfer){try{void win.document;}catch(_){win.postMessage(data,location.origin,transfer||[]);return;}",
"this.childWindow = window.open(this.url, '_blank', 'attention');": "this.childWindow = window.VulpesCompat ? VulpesCompat.openAttention(this.url) : window.open(this.url, '_blank', 'attention');",
"style: function(baseUrl) {": "style: function(baseUrl) { if (window.VulpesCompat) { window.VulpesCompat.componentStyle(this, baseUrl).catch(console.error); return; }",
"style:function(baseUrl){": "style:function(baseUrl){if(window.VulpesCompat){window.VulpesCompat.componentStyle(this,baseUrl).catch(console.error);return;}",
"shimHostIframe.contentDocument.readyState === 'complete' ?": "typeof shimHostIframe.contentWindow.bootstrap === 'function' ?",
"shimHostIframe.contentDocument.readyState==='complete'?": "typeof shimHostIframe.contentWindow.bootstrap==='function'?",
"hardwareHomeButton: '(-moz-physical-home-button)'": "hardwareHomeButton: window.VulpesCompat ? '(min-width: 0px)' : '(-moz-physical-home-button)'",
"hardwareHomeButton:'(-moz-physical-home-button)'": "hardwareHomeButton:window.VulpesCompat?'(min-width: 0px)':'(-moz-physical-home-button)'",
"new AudioContext(this._channel)": "new AudioContext()",
"var doc = (parent.location === window.location) ?\n document : parent.document;": 'var doc = document; try { if (!document.getElementById("confirmation-message")) doc = parent.document; } catch (_) {}',
"var doc=(parent.location===window.location)?document:parent.document;": 'var doc=document;try{if(!document.getElementById("confirmation-message"))doc=parent.document;}catch(_){}',
"window.scheduler || schedulerShim()": '(window.scheduler && typeof window.scheduler.mutation === "function" ? window.scheduler : schedulerShim())',
"window.scheduler||schedulerShim()": '(window.scheduler&&typeof window.scheduler.mutation==="function"?window.scheduler:schedulerShim())',
"div.querySelector(':host'); return true;": "div.querySelector(':host'); div.querySelector('::content'); return true;",
"div.querySelector(':host');return true;": "div.querySelector(':host');div.querySelector('::content');return true;",
"this._screenBrightnessTransition &&\n this._screenBrightnessTransition.transitionTo(this._targetBrightness);": "if (this._screenBrightnessTransition) {\n this._screenBrightnessTransition.transitionTo(this._targetBrightness);\n } else {\n power.screenBrightness = this._targetBrightness;\n }",
"this._screenBrightnessTransition&&this._screenBrightnessTransition.transitionTo(this._targetBrightness);": "this._screenBrightnessTransition?this._screenBrightnessTransition.transitionTo(this._targetBrightness):(power.screenBrightness=this._targetBrightness);",
"window.open(url, '_blank', 'remote=true');": "new MozActivity({name:'view',data:{type:'url',url:url}});",
"window.open(url,'_blank','remote=true');": "new MozActivity({name:'view',data:{type:'url',url:url}});",
"newDate.toLocaleFormat('%p')": "new Intl.DateTimeFormat(navigator.language, {hour:'numeric', hour12:true}).formatToParts(newDate).find(part => part.type === 'dayPeriod').value",
"gaia-container { position: relative; display: block; }": " :host { position: relative; display: block; }",
"[for (v of voices) v.lang.split('-')[0]]": "voices.map(v => v.lang.split('-')[0])",
"[for (p of pat) new SingleMatchPattern(p)]": "Array.from(pat, p => new SingleMatchPattern(p))",
"[for(p of pat)new SingleMatchPattern(p)]": "Array.from(pat, p => new SingleMatchPattern(p))",
"window.scheduler || {": '(window.scheduler && typeof window.scheduler.mutation === "function" ? window.scheduler : null) || {',
"window.scheduler||{": '(window.scheduler&&typeof window.scheduler.mutation==="function"?window.scheduler:null)||{',
"this._pendingIconUrl = 'app-icon';": "this._pendingIconUrl = 'app-icon';\n if (window.VulpesCompat) { this._vulpesSourceIcon = blob; this._hasUserSetIcon = true; } // Keep original app pixels; skip the legacy canvas pass.",
"this._pendingIconUrl='app-icon';": "this._pendingIconUrl='app-icon';if(window.VulpesCompat){this._vulpesSourceIcon=blob;this._hasUserSetIcon=true;}",
"Object.defineProperty(proto, 'icon', {\n get: function() {\n return new Promise((resolve, reject) => {": "Object.defineProperty(proto, 'icon', {\n get: function() {\n if (window.VulpesCompat && this.app && this._vulpesSourceIcon) return Promise.resolve(this._vulpesSourceIcon);\n return new Promise((resolve, reject) => {",
"Object.defineProperty(proto,'icon',{get:function(){return new Promise((resolve,reject)=>{": "Object.defineProperty(proto,'icon',{get:function(){if(window.VulpesCompat&&this.app&&this._vulpesSourceIcon)return Promise.resolve(this._vulpesSourceIcon);return new Promise((resolve,reject)=>{",
"if (window.VulpesCompat && appOrBookmark.manifest?.icons?.['512']?.includes('vulpes-browser.png')) icon.refresh();": "if (window.VulpesCompat && icon.app) icon.refresh(); // Replace legacy rasterized cache with original app assets.",
"if(window.VulpesCompat&&appOrBookmark.manifest?.icons?.['512']?.includes('vulpes-browser.png'))icon.refresh();": "if(window.VulpesCompat&&icon.app)icon.refresh();",
}
for name in ["gaia", "assets/webapps"]:
count = 0
for path in (ROOT / name).rglob("*.js"):
if not path.is_file() or "/test/" in str(path):
continue
try:
original = path.read_text()
except UnicodeDecodeError:
continue
text = original
if path.name == "main.js" and "camera.gaiamobile.org" in str(path):
text = (
text.replace(
"require(['main']);",
"if (!window.VulpesCompat || navigator.mozCameras) require(['main']);",
)
if "if (!window.VulpesCompat || navigator.mozCameras)" not in text
else text
)
if path.name == "fxa_iac_client.js":
text = text.replace(
"callback&&callback();});",
"callback&&callback();}).catch(function(error){callback&&callback(error);});",
)
text = text.replace(
"connect(function(){_sendMessage(message,successCb,errorCb);",
"connect(function(error){if(error){errorCb&&errorCb(error);return;}_sendMessage(message,successCb,errorCb);",
)
for old, new in replacements.items():
if new not in text:
text = text.replace(old, new)
if text != original:
path.write_text(text)
count += 1
print(name, count, "files updated")
# Native boot overlays keep the immutable Gaia system image. Include both SMS
# entry points: the old architecture lazy-loads conversation.js separately.
for relative in ("views/conversation/js/conversation.js", "views/conversation/gaia_build_defer_index.js"):
source = ROOT / "assets/webapps/sms.gaiamobile.org" / relative
target = ROOT / "overrides/sms.gaiamobile.org" / relative
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(source.read_bytes())
# Preserve bundled item definitions, replacing only the root dependency list.
# Unsupported hardware modules must not execute and break unrelated Settings UI.
path = ROOT / "assets/webapps/settings.gaiamobile.org/js/panels/root/low_priority_items.js"
if path.is_file():
text = path.read_text()
marker = "/* VULPES_ROOT_CAPABILITIES */"
old = "define('panels/root/low_priority_items',"
start = text.find(marker) if marker in text else text.find(old)
if start < 0:
raise RuntimeError("Settings root bundle has changed; review capability adapter")
replacement = (
text[:start]
+ marker
+ "\n"
+ (ROOT / "gaia/apps/settings/js/panels/root/low_priority_items.js").read_text()
)
if replacement != text:
path.write_text(replacement)