injectProxyCompatibilityScript static method
Injects a small compatibility patch for proxied documents rendered from
data: origin, preventing hard failures on unsupported APIs.
Implementation
static String injectProxyCompatibilityScript(String html) {
if (html.isEmpty) return html;
const marker = 'data-swebview-proxy-compat';
if (html.contains(marker)) return html;
// The script patches six things that break when a page is served from a
// data: URL (null origin):
//
// 1. Service-worker registration — SW scope must match origin; fails silently.
// 2. IndexedDB — browsers block IDBFactory.open() from null origin and throw
// a synchronous SecurityError. We patch IDBFactory.prototype.open with a
// tiny in-memory shim that resolves successfully for common get/put paths.
// This helps feature-gated plugins that only need lightweight persistence.
// 3. history.replaceState / pushState — cannot push external URLs from null
// origin; we wrap both and swallow the SecurityError.
// 4. "Unlegal embed" overlay — some apps (e.g. Windy) check
// window.location.origin and show a blocking overlay when it is not their
// own domain. We inject CSS and also continuously force-hide the node
// because page JS can toggle inline styles after load.
// 5. API proxy rewriting — when a page makes cross-origin API calls (e.g.,
// Axios to dir.aviapages.com), the null origin blocks them. We extract
// the proxy base from the injected <base> tag and rewrite fetch/XHR/Axios
// requests to *.aviapages.com URLs through that same proxy.
// 6. Known benign noisy errors (Unlegal embed / IDBFactory / gl-particles)
// are swallowed to reduce console spam while keeping page execution alive.
const script = '<script $marker="1">(function(){try{'
'var _stats=(window.__swebviewCompatStats&&typeof window.__swebviewCompatStats==="object")'
'?window.__swebviewCompatStats:{errors:0,swallowedErrors:0,unhandledRejections:0,pluginFailures:0,messages:[]};'
'window.__swebviewCompatStats=_stats;'
'var _pushMsg=function(m){try{m=String(m||"");_stats.messages.push(m);'
'if(_stats.messages.length>25)_stats.messages.splice(0,_stats.messages.length-25);}catch(e){}};'
// Extract proxy base from <base> tag for API rewriting
'var _proxyBase="";'
'try{var b=document.querySelector("base[href]");'
'if(b&&b.href){'
'var m=b.href.match(/^(https?:\\/\\/[^\\/]+(?:\\/[^\\/]+)*(?:\\?[^=]+=)?)/);'
'if(m)_proxyBase=m[1];'
'}}catch(e){}'
// 1. Service workers
'if(typeof navigator!=="undefined"&&"serviceWorker" in navigator){'
'navigator.serviceWorker.register=function(){'
'return Promise.reject(new Error("sw-disabled-proxy"));};}'
// 2. IndexedDB
'if(typeof IDBFactory!=="undefined"&&window.indexedDB){'
'var _o=IDBFactory.prototype.open;'
'var _dbMem={};'
'var _mkReq=function(result,error){'
'var r={readyState:"pending",result:undefined,error:error||null,'
'onsuccess:null,onerror:null,onblocked:null,onupgradeneeded:null};'
'setTimeout(function(){'
'r.readyState="done";'
'if(error){if(typeof r.onerror==="function")r.onerror({target:r,type:"error"});return;}'
'r.result=result;'
'if(typeof r.onsuccess==="function")r.onsuccess({target:r,type:"success"});'
'},0);'
'return r;};'
'var _mkStore=function(name){'
'if(!_dbMem[name])_dbMem[name]={};'
'var s=_dbMem[name];'
'return {'
'get:function(k){return _mkReq(s[String(k)]);},'
'put:function(v,k){if(typeof k==="undefined"&&v&&typeof v==="object"&&("id" in v))k=v.id;'
's[String(k)]=v;return _mkReq(k);},'
'add:function(v,k){if(typeof k==="undefined"&&v&&typeof v==="object"&&("id" in v))k=v.id;'
'if(s.hasOwnProperty(String(k)))return _mkReq(undefined,new Error("ConstraintError"));'
's[String(k)]=v;return _mkReq(k);},'
'delete:function(k){delete s[String(k)];return _mkReq(undefined);},'
'clear:function(){for(var p in s){if(Object.prototype.hasOwnProperty.call(s,p))delete s[p];}return _mkReq(undefined);}'
'};};'
'var _storeNames=[];'
'var _ensureStore=function(n){'
'n=String(n);'
'if(_storeNames.indexOf(n)===-1)_storeNames.push(n);'
'if(!_dbMem[n])_dbMem[n]={};'
'};'
'var _dropStore=function(n){'
'n=String(n);'
'delete _dbMem[n];'
'var i=_storeNames.indexOf(n);'
'if(i!==-1)_storeNames.splice(i,1);'
'};'
'var _mkStoreNamesObj=function(){return {'
'contains:function(n){return _storeNames.indexOf(String(n))!==-1;},'
'item:function(i){return _storeNames[i]||null;},'
'get length(){return _storeNames.length;}'
'};};'
'var _mkDb=function(){return {'
'close:function(){},'
'get objectStoreNames(){return _mkStoreNamesObj();},'
'createObjectStore:function(n){_ensureStore(n);return _mkStore(n);},'
'deleteObjectStore:function(n){_dropStore(n);},'
'transaction:function(names){'
'var list=Array.isArray(names)?names:[names];'
'for(var i=0;i<list.length;i++){_ensureStore(list[i]);}'
'return {'
'oncomplete:null,onerror:null,onabort:null,'
'objectStore:function(n){_ensureStore(n);if(list.indexOf(n)===-1)list.push(n);return _mkStore(n);}'
'};'
'}'
'};};'
'IDBFactory.prototype.open=function(){'
'try{return _o.apply(this,arguments);}catch(e){'
'var req=_mkReq(_mkDb());'
'setTimeout(function(){if(typeof req.onupgradeneeded==="function")req.onupgradeneeded({target:req,type:"upgradeneeded"});},0);'
'return req;}};}'
// 3. history
'if(typeof history!=="undefined"){'
'var _rs=history.replaceState;'
'history.replaceState=function(s,t,u){try{_rs.call(history,s,t,u);}catch(e){}};'
'var _ps=history.pushState;'
'history.pushState=function(s,t,u){try{_ps.call(history,s,t,u);}catch(e){}};}'
// 4. Keep windy "unlegal-embed" overlay hidden even if script toggles it
'var _hideUE=function(){try{var n=document.getElementById("unlegal-embed");'
'if(n){n.style.setProperty("display","none","important");'
'n.setAttribute("aria-hidden","true");}}catch(e){}};'
'_hideUE();setInterval(_hideUE,500);'
// 3.5 API proxy rewriting for cross-origin API calls (Axios/fetch)
'if(_proxyBase){'
'var _rewriteApiUrl=function(u){try{u=String(u||"");'
'if((u.indexOf("dir.aviapages.com")!==-1||u.indexOf("api.aviapages.com")!==-1)'
'&&(u.indexOf("http://")===0||u.indexOf("https://")===0)){'
'return _proxyBase+encodeURIComponent(u);'
'}}catch(e){}_pushMsg("API rewrite failed: "+e);}return u;};'
// Wrap fetch()
'if(typeof window.fetch==="function"){'
'var _origFetch=window.fetch;'
'window.fetch=function(r){if(typeof r==="string")r=_rewriteApiUrl(r);'
'return _origFetch.apply(this,arguments);};'
'}'
// Wrap XMLHttpRequest.open()
'if(typeof XMLHttpRequest!=="undefined"){'
'var _origOpen=XMLHttpRequest.prototype.open;'
'XMLHttpRequest.prototype.open=function(method,url){'
'if(typeof url==="string")url=_rewriteApiUrl(url);'
'return _origOpen.apply(this,arguments);};'
'}'
// Wrap Axios if available
'if(typeof window.axios!=="undefined"&&window.axios){'
'var _origRequest=window.axios.request;'
'if(typeof _origRequest==="function"){'
'window.axios.request=function(cfg){if(cfg&&cfg.url)cfg.url=_rewriteApiUrl(cfg.url);'
'return _origRequest.apply(this,arguments);};'
'}'
'}'
'}'
// 5. Reduce known noisy errors in proxied data-origin mode
'var _swallow=function(msg){msg=String(msg||"");'
'return msg.indexOf("Unlegal embed")!==-1||'
'msg.indexOf("IDBFactory")!==-1||'
'msg.indexOf("gl-particles")!==-1;};'
'var _prevOnError=window.onerror;'
'window.onerror=function(m,s,l,c,e){'
'_stats.errors=(_stats.errors||0)+1;'
'_pushMsg(m||(e&&e.message)||"");'
'if(_swallow(m)||(e&&_swallow(e.message))){_stats.swallowedErrors=(_stats.swallowedErrors||0)+1;return true;}'
'if(typeof _prevOnError==="function"){return _prevOnError.apply(this,arguments);}'
'return false;};'
'window.addEventListener("unhandledrejection",function(ev){'
'_stats.unhandledRejections=(_stats.unhandledRejections||0)+1;'
'var r=ev&&ev.reason;var m=(r&&r.message)||r;'
'_pushMsg(m);'
'if(_swallow(m)){ev.preventDefault();}});'
'if(window.console&&typeof window.console.error==="function"){'
'var _prevErr=window.console.error.bind(window.console);'
'window.console.error=function(){'
'try{var a=Array.prototype.slice.call(arguments).join(" ");'
'_pushMsg(a);if(String(a).indexOf("plugin")!==-1){_stats.pluginFailures=(_stats.pluginFailures||0)+1;}}catch(e){}'
'return _prevErr.apply(window.console,arguments);'
'};}'
'}catch(_){}})();</script>'
// 4. Unlegal-embed overlay (CSS !important beats any inline style JS may set)
'<style $marker="1">#unlegal-embed{display:none!important}</style>';
if (_headTag.hasMatch(html)) {
return html.replaceFirstMapped(_headTag, (m) => '${m.group(0)}$script');
}
return '$script$html';
}