Code cleaning

parent f8521de0
# Chrome Extension Runtime API Emulation
This directory contains a test extension and implementation for emulating the Chrome Extension Runtime API in our Qt6-based browser.
## Overview
The implementation consists of:
1. **RuntimeBridge** (`assets/browser/js/runtime_bridge.py`): A Python QObject that bridges between the browser and JavaScript
2. **chrome-runtime-api.js** (`assets/browser/js/chrome-runtime-api.js`): JavaScript implementation of the chrome.runtime API
3. **Test Extension** (`assets/browser/extensions/test-extension/`): A simple extension to test the API implementation
## Supported APIs
The following chrome.runtime APIs are supported:
- `chrome.runtime.id` - Returns the extension ID
- `chrome.runtime.getURL()` - Converts a relative path to a fully-qualified extension URL
- `chrome.runtime.getManifest()` - Returns the extension's manifest.json contents
- `chrome.runtime.sendMessage()` - Sends a message to the extension or another extension
- `chrome.runtime.onMessage` - Event fired when a message is received
- `chrome.runtime.connect()` - Creates a connection to the extension or another extension
- `chrome.runtime.onConnect` - Event fired when a connection is made
- `chrome.runtime.getBackgroundPage()` - Returns the background page
- Various lifecycle events: `onInstalled`, `onStartup`, `onSuspend`, `onUpdateAvailable`
## Test Extension
The test extension demonstrates the use of the chrome.runtime API. It includes:
- **Popup** (`popup.html`, `popup.js`): A simple UI to test various chrome.runtime methods
- **Background Script** (`background.js`): A background script that listens for messages and connections
- **Content Script** (`content.js`): A content script that injects a control panel into web pages
## Testing
To test the chrome.runtime API implementation:
1. Run the test script: `python test_extension.py`
2. Click the "Test Extension" button in the toolbar
3. Use the buttons in the popup to test various chrome.runtime API methods
4. Check the browser console for log messages
You can also test the content script by navigating to any website and looking for the control panel in the bottom-right corner.
## Implementation Details
### Extension Loading
Extensions are loaded from the `assets/browser/extensions` directory and copied to the browser profile's extensions directory. The browser scans for extensions on startup and registers them with the runtime bridge.
### URL Scheme Handling
The browser uses a custom URL scheme handler (`qextension://`) to load extension resources. This allows extensions to use relative paths in their manifest and code.
### Message Passing
Message passing is implemented using QWebChannel to communicate between JavaScript and Python. The RuntimeBridge class handles routing messages between different parts of an extension.
### Background Pages
Background pages are loaded in hidden tabs. For service workers, a special HTML page is created that loads the service worker script.
## Limitations
- This is a simplified implementation and does not support all chrome.runtime APIs
- Service workers are not fully supported; they are loaded in a regular page context
- Cross-extension messaging is limited
- Some APIs may behave differently than in Chrome
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
(function(a,b){var r=a0b,c=a();while(!![]){try{var d=-parseInt(r(0x177))/0x1*(-parseInt(r(0x18e))/0x2)+parseInt(r(0x19d))/0x3+-parseInt(r(0x183))/0x4*(-parseInt(r(0x182))/0x5)+-parseInt(r(0x189))/0x6+-parseInt(r(0x17d))/0x7*(parseInt(r(0x18d))/0x8)+-parseInt(r(0x178))/0x9+-parseInt(r(0x19c))/0xa*(parseInt(r(0x18f))/0xb);if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a0a,0x19149),!(function(){var A=a0b,a,b;a=function(){var s=a0b,c=document[s(0x184)](s(0x19f));c['innerHTML']=s(0x17c),document[s(0x19b)][s(0x194)](c),publicApi(s(0x17e))[s(0x179)](f=>{var t=s,g=f,h=document['querySelector'](t(0x1a0)),i=document['querySelector']('#cbMessageInputSet'),j=document[t(0x190)](t(0x198)),k=document[t(0x190)]('#cbSendMessageSetBtn');g&&void 0x0!==g?(localStorage['setItem']('cbMessageApi',!0x0),window['onunload']=function(){var u=t;localStorage[u(0x188)]('cbMessageApi');},j[t(0x193)](t(0x18b),function(m){var v=t;let p=h[v(0x17b)];p=v(0x199)==typeof p?JSON[v(0x19e)](p):p,g[v(0x18c)](p);}),k[t(0x193)](t(0x18b),function(){var w=t;g[w(0x181)](i['value'],w(0x180));})):j[t(0x193)](t(0x18b),function(){var x=t;window[x(0x197)][x(0x181)](h[x(0x17b)],x(0x180));});let l={'displayName':g['model'][t(0x187)](),'username':g['model'][t(0x191)]()};h[t(0x17a)](t(0x196),JSON['stringify'](l)),g[t(0x193)](t(0x19a),({amount:m,username:p,displayName:q})=>{var y=t;window[y(0x195)]({'type':y(0x19a),'from':y(0x17f),'tip':{'amount':m,'tipperName':q,'username':p,'cParameter':''}});}),g[t(0x193)](t(0x185),({message:m})=>{var z=t;window['postMessage']({'type':z(0x192),'from':'bongacams','message':m});});});},'interactive'===(b=document[A(0x186)])||A(0x18a)===b?a():window[A(0x193)](A(0x176),a);}()));function a0b(a,b){var c=a0a();return a0b=function(d,e){d=d-0x176;var f=c[d];return f;},a0b(a,b);}function a0a(){var B=['1483155beMQRr','then','setAttribute','value','\x0a\x20\x20\x20\x20\x20\x20<input\x20type=\x27button\x27\x20style=\x27display:\x20none;\x27\x20id=\x27cbSendMessageBtn\x27/>\x0a\x20\x20\x20\x20\x20\x20<input\x20type=\x27text\x27\x20style=\x27display:none;\x27\x20id=\x27cbMessageInput\x27/>\x0a\x20\x20\x20\x20\x20\x20<input\x20type=\x27button\x27\x20style=\x27display:\x20none;\x27\x20id=\x27cbSendMessageSetBtn\x27/>\x0a\x20\x20\x20\x20\x20\x20<input\x20type=\x27text\x27\x20style=\x27display:none;\x27\x20id=\x27cbMessageInputSet\x27/>\x0a\x20\x20\x20\x20','21xZbwoS','lovense','bongacams','btn','sendMessage','5yCBsZF','820288PDCiyg','createElement','order','readyState','getDisplayName','removeItem','394380wJoPNa','complete','click','send','54744HXmhJK','4arBiLz','35079ZLUbyQ','querySelector','getUsername','orderMessage','addEventListener','appendChild','postMessage','data','chat','#cbSendMessageBtn','string','tip','body','40acdosV','410037eFFxca','parse','div','#cbMessageInput','DOMContentLoaded','12394RshGtw'];a0a=function(){return B;};return a0a();}
\ No newline at end of file
function a1b(a,b){var c=a1a();return a1b=function(d,e){d=d-0x112;var f=c[d];return f;},a1b(a,b);}function a1a(){var i=['760588ipeXxo','5eLbVuT','sendMessage','1086620GaZhHN','14071360qeKQTq','3085188xfUxLF','#cbMessageInput','click','4290930gpYhql','265786GqqelY','chat','1475229EwekQg','val'];a1a=function(){return i;};return a1a();}var a1g=a1b;(function(a,b){var f=a1b,c=a();while(!![]){try{var d=parseInt(f(0x11e))/0x1+parseInt(f(0x11a))/0x2+parseInt(f(0x11c))/0x3+-parseInt(f(0x114))/0x4+-parseInt(f(0x112))/0x5*(-parseInt(f(0x116))/0x6)+parseInt(f(0x119))/0x7+-parseInt(f(0x115))/0x8;if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a1a,0x75a2d),$('#cbSendMessageBtn')['on'](a1g(0x118),function(){var h=a1g;Chat[h(0x113)]($(h(0x117))[h(0x11d)](),h(0x11b));}));
\ No newline at end of file
var a2g=a2b;(function(a,b){var f=a2b,c=a();while(!![]){try{var d=-parseInt(f(0xd2))/0x1+-parseInt(f(0xdc))/0x2*(-parseInt(f(0xd0))/0x3)+parseInt(f(0xda))/0x4+parseInt(f(0xd5))/0x5+-parseInt(f(0xd6))/0x6+-parseInt(f(0xd8))/0x7*(-parseInt(f(0xd3))/0x8)+parseInt(f(0xdd))/0x9*(parseInt(f(0xdb))/0xa);if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a2a,0x51a8c),$('#cbSendMessageBtn')['on'](a2g(0xd9),function(){var h=a2g;window[h(0xd7)][h(0xd1)][h(0xcf)]($(h(0xd4))[h(0xce)]());}));function a2b(a,b){var c=a2a();return a2b=function(d,e){d=d-0xce;var f=c[d];return f;},a2b(a,b);}function a2a(){var i=['3818034CgqBbR','TSHandler','21ooRqWG','click','601348owEtCZ','3210jtUWqn','1134062buHlnR','7785TbEeDn','val','send_room_message','3xeiOLi','message_outbound','528920WBDWzj','428776uFDiLB','#cbMessageInput','1719555AjOjFO'];a2a=function(){return i;};return a2a();}
\ No newline at end of file
function a3b(a,b){var c=a3a();return a3b=function(d,e){d=d-0x1f4;var f=c[d];return f;},a3b(a,b);}(function(a,b){var f=a3b,c=a();while(!![]){try{var d=-parseInt(f(0x208))/0x1+-parseInt(f(0x1fd))/0x2+-parseInt(f(0x1fa))/0x3+parseInt(f(0x202))/0x4+parseInt(f(0x1fc))/0x5*(parseInt(f(0x205))/0x6)+parseInt(f(0x204))/0x7*(parseInt(f(0x1f5))/0x8)+-parseInt(f(0x1fe))/0x9;if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a3a,0x21ae7),!(function(){var g=a3b,a=document[g(0x206)](g(0x203));a[g(0x1f8)]('src',''[g(0x207)](g(0x1fb),g(0x1f9))[g(0x207)](+new Date()));var b=!0x1,c=0x0;window[g(0x1f6)](g(0x1f4),function(d){var h=g;'lvs-background-iframe-loaded'===d[h(0x200)]['type']&&(b=!0x0);}),setInterval(function(){var i=g;!b&&c<0x1e?(c++,a['setAttribute'](i(0x201),''['concat']('https://extension.lovense.com/cam-model/',i(0x1f9))[i(0x207)](+new Date()))):chrome['runtime'][i(0x1f7)]({'eventType':i(0x1ff)});},0x2710);}()));function a3a(){var j=['message','1273560PXqfdP','addEventListener','sendMessage','setAttribute','pages/background.html?t=','377580eKTVqb','https://extension.lovense.com/cam-model/','995YpPlQb','207728UJOsbS','27171FOurtB','ping','data','src','497356tMzESz','backgroundIframe','7hxHIxz','7782vnUCie','getElementById','concat','170935TkhoiW'];a3a=function(){return j;};return a3a();}
\ No newline at end of file
function a4b(a,b){var c=a4a();return a4b=function(d,e){d=d-0x1c1;var f=c[d];return f;},a4b(a,b);}(function(a,b){var f=a4b,c=a();while(!![]){try{var d=parseInt(f(0x1c6))/0x1*(parseInt(f(0x1d2))/0x2)+-parseInt(f(0x1cd))/0x3+parseInt(f(0x1ea))/0x4*(parseInt(f(0x1c7))/0x5)+parseInt(f(0x1d0))/0x6*(-parseInt(f(0x1e4))/0x7)+-parseInt(f(0x1c8))/0x8*(parseInt(f(0x1c2))/0x9)+-parseInt(f(0x1e3))/0xa*(-parseInt(f(0x1dd))/0xb)+parseInt(f(0x1e6))/0xc;if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a4a,0x8b83d),!(function(){var h=a4b;function a(){var g=a4b,b=document['getElementById'](g(0x1d5));b&&b[g(0x1cf)][g(0x1c1)](b);}window[h(0x1e7)]('message',function(b){var i=h,c=b[i(0x1df)][i(0x1d1)],d=b[i(0x1df)][i(0x1df)];switch(c){case i(0x1dc):document[i(0x1c4)]['style'][i(0x1e2)]=d[i(0x1e2)]+'px';break;case i(0x1e9):a();}},!0x1),window['onload']=function(){var j=h;if('zh-CN'!==navigator[j(0x1d7)]&&j(0x1d9)!==navigator[j(0x1d7)]&&'zh'!==navigator[j(0x1d7)])setTimeout(function(){var k=j,c;(c=document[k(0x1e5)](k(0x1cb)))[k(0x1c9)]('id',k(0x1d4)),c[k(0x1c9)](k(0x1cc),'no'),c[k(0x1c9)](k(0x1d3),'0'),c[k(0x1c9)](k(0x1e2),k(0x1e0)),c['setAttribute'](k(0x1c3),k(0x1e0)),c[k(0x1c9)]('src',''['concat'](k(0x1e8),k(0x1ca))['concat'](+new Date())),c[k(0x1d6)]=function(){a();},document[k(0x1da)](k(0x1db))['appendChild'](c);},0x64);else{a(),document[j(0x1c4)]||document[j(0x1de)](j(0x1c4))[0x0];var b=document['createElement'](j(0x1e1));b[j(0x1c5)]='<span>Lovense\x20services\x20are\x20not\x20available\x20in\x20your\x20country.</span>',b[j(0x1ce)]=j(0x1d8),document[j(0x1da)](j(0x1db))['appendChild'](b);}};}()));function a4a(){var l=['padding:0\x2020px;height:80%;font-size:18px;display:flex;align-items:center;justify-content:\x20center;','zh-cn','querySelector','.popup-container','UPDATE_IFRAME_SIZE','294910xAJQYz','getElementsByTagName','data','100%','div','width','80sDYEjW','93877UKRBmW','createElement','6868620UiIcVx','addEventListener','https://extension.lovense.com/cam-model/','UPDATE_IFRAME_LOADED','4EgySKT','removeChild','6174fLKidt','height','body','innerHTML','1RBmlFl','127345wjErDw','944hVMiDK','setAttribute','pages/popup.html?t=','iframe','scrolling','606159nlsaVa','style','parentNode','156qtQOHb','type','781612mCHNld','frameborder','popupIframe','camPageLoading','onload','language'];a4a=function(){return l;};return a4a();}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
function a7b(a,b){var c=a7a();return a7b=function(d,e){d=d-0x17c;var f=c[d];return f;},a7b(a,b);}(function(a,b){var aw=a7b,c=a();while(!![]){try{var d=parseInt(aw(0x1ef))/0x1*(-parseInt(aw(0x195))/0x2)+parseInt(aw(0x1f0))/0x3+-parseInt(aw(0x1f6))/0x4+-parseInt(aw(0x1dd))/0x5+-parseInt(aw(0x224))/0x6+parseInt(aw(0x25c))/0x7*(-parseInt(aw(0x19e))/0x8)+-parseInt(aw(0x1c1))/0x9*(-parseInt(aw(0x19b))/0xa);if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a7a,0x464d2),!(function(){var a={0xe9a:function(d){var az=a7b;function f(g){var ax=a7b;return d[ax(0x18c)]=f=ax(0x226)==typeof Symbol&&'symbol'==typeof Symbol[ax(0x1a9)]?function(h){return typeof h;}:function(h){var ay=ax;return h&&ay(0x226)==typeof Symbol&&h[ay(0x1e1)]===Symbol&&h!==Symbol[ay(0x22a)]?ay(0x1c7):typeof h;},d['exports']['__esModule']=!0x0,d[ax(0x18c)]['default']=d[ax(0x18c)],f(g);}d[az(0x18c)]=f,d['exports'][az(0x212)]=!0x0,d['exports'][az(0x218)]=d[az(0x18c)];},0x1219:function(d,f,g){var aA=a7b,h=g(0xe9a)[aA(0x218)];function i(){'use strict';var aB=aA;d['exports']=i=function(){return z;},d['exports'][aB(0x212)]=!0x0,d['exports'][aB(0x218)]=d['exports'];var q,z={},A=Object[aB(0x22a)],B=A[aB(0x1de)],C=Object[aB(0x203)]||function(ae,af,ag){ae[af]=ag['value'];},D=aB(0x226)==typeof Symbol?Symbol:{},F=D[aB(0x1a9)]||aB(0x1fa),G=D[aB(0x193)]||'@@asyncIterator',H=D[aB(0x1db)]||'@@toStringTag';function I(ae,af,ag){var aC=aB;return Object[aC(0x203)](ae,af,{'value':ag,'enumerable':!0x0,'configurable':!0x0,'writable':!0x0}),ae[af];}try{I({},'');}catch(ae){I=function(af,ag,ah){return af[ag]=ah;};}function J(af,ag,ah,ai){var aD=aB,aj=ag&&ag[aD(0x22a)]instanceof Z?ag:Z,ak=Object[aD(0x21c)](aj[aD(0x22a)]),al=new ac(ai||[]);return C(ak,aD(0x20c),{'value':a8(af,ah,al)}),ak;}function K(af,ag,ah){var aE=aB;try{return{'type':aE(0x1a1),'arg':af[aE(0x181)](ag,ah)};}catch(ai){return{'type':aE(0x1c4),'arg':ai};}}z[aB(0x1b0)]=J;var Q=aB(0x22b),U=aB(0x1a0),V=aB(0x1e4),X=aB(0x1c9),Y={};function Z(){}function a0(){}function a1(){}var a2={};I(a2,F,function(){return this;});var a3=Object['getPrototypeOf'],a4=a3&&a3(a3(ad([])));a4&&a4!==A&&B[aB(0x181)](a4,F)&&(a2=a4);var a5=a1[aB(0x22a)]=Z[aB(0x22a)]=Object[aB(0x21c)](a2);function a6(af){var aF=aB;[aF(0x231),'throw','return']['forEach'](function(ag){I(af,ag,function(ah){var aG=a7b;return this[aG(0x20c)](ag,ah);});});}function a7(af,ag){var aL=aB;function ah(aj,ak,al,am){var aH=a7b,an=K(af[aj],af,ak);if(aH(0x1c4)!==an[aH(0x1f1)]){var ao=an[aH(0x1a7)],ap=ao[aH(0x19c)];return ap&&aH(0x24c)==h(ap)&&B[aH(0x181)](ap,aH(0x23b))?ag[aH(0x244)](ap[aH(0x23b)])[aH(0x1e2)](function(aq){var aI=aH;ah(aI(0x231),aq,al,am);},function(aq){var aJ=aH;ah(aJ(0x1c4),aq,al,am);}):ag[aH(0x244)](ap)['then'](function(aq){var aK=aH;ao[aK(0x19c)]=aq,al(ao);},function(aq){return ah('throw',aq,al,am);});}am(an[aH(0x1a7)]);}var ai;C(this,aL(0x20c),{'value':function(aj,ak){var aM=aL;function al(){return new ag(function(am,an){ah(aj,ak,am,an);});}return ai=ai?ai[aM(0x1e2)](al,al):al();}});}function a8(af,ag,ah){var ai=Q;return function(aj,ak){var aN=a7b;if(ai===V)throw Error(aN(0x248));if(ai===X){if('throw'===aj)throw ak;return{'value':q,'done':!0x0};}for(ah[aN(0x1e9)]=aj,ah[aN(0x1a7)]=ak;;){var al=ah[aN(0x204)];if(al){var am=a9(al,ah);if(am){if(am===Y)continue;return am;}}if(aN(0x231)===ah[aN(0x1e9)])ah[aN(0x1ca)]=ah[aN(0x202)]=ah['arg'];else{if('throw'===ah['method']){if(ai===Q)throw ai=X,ah['arg'];ah[aN(0x1bd)](ah[aN(0x1a7)]);}else aN(0x241)===ah[aN(0x1e9)]&&ah['abrupt']('return',ah[aN(0x1a7)]);}ai=V;var an=K(af,ag,ah);if(aN(0x1a1)===an['type']){if(ai=ah[aN(0x20d)]?X:U,an['arg']===Y)continue;return{'value':an[aN(0x1a7)],'done':ah['done']};}aN(0x1c4)===an['type']&&(ai=X,ah['method']='throw',ah[aN(0x1a7)]=an[aN(0x1a7)]);}};}function a9(af,ag){var aO=aB,ah=ag[aO(0x1e9)],ai=af[aO(0x1a9)][ah];if(ai===q)return ag[aO(0x204)]=null,aO(0x1c4)===ah&&af['iterator'][aO(0x241)]&&(ag[aO(0x1e9)]=aO(0x241),ag[aO(0x1a7)]=q,a9(af,ag),aO(0x1c4)===ag['method'])||aO(0x241)!==ah&&(ag[aO(0x1e9)]='throw',ag[aO(0x1a7)]=new TypeError(aO(0x21e)+ah+aO(0x246))),Y;var aj=K(ai,af[aO(0x1a9)],ag['arg']);if(aO(0x1c4)===aj[aO(0x1f1)])return ag[aO(0x1e9)]=aO(0x1c4),ag[aO(0x1a7)]=aj[aO(0x1a7)],ag['delegate']=null,Y;var ak=aj[aO(0x1a7)];return ak?ak[aO(0x20d)]?(ag[af['resultName']]=ak[aO(0x19c)],ag['next']=af[aO(0x1c0)],aO(0x241)!==ag['method']&&(ag['method']='next',ag['arg']=q),ag['delegate']=null,Y):ak:(ag[aO(0x1e9)]=aO(0x1c4),ag[aO(0x1a7)]=new TypeError('iterator\x20result\x20is\x20not\x20an\x20object'),ag[aO(0x204)]=null,Y);}function aa(af){var aP=aB,ag={'tryLoc':af[0x0]};0x1 in af&&(ag[aP(0x25a)]=af[0x1]),0x2 in af&&(ag[aP(0x24d)]=af[0x2],ag['afterLoc']=af[0x3]),this[aP(0x17e)][aP(0x1b9)](ag);}function ab(af){var aQ=aB,ag=af[aQ(0x249)]||{};ag[aQ(0x1f1)]=aQ(0x1a1),delete ag[aQ(0x1a7)],af[aQ(0x249)]=ag;}function ac(af){var aR=aB;this[aR(0x17e)]=[{'tryLoc':'root'}],af[aR(0x21a)](aa,this),this['reset'](!0x0);}function ad(af){var aS=aB;if(af||''===af){var ag=af[F];if(ag)return ag['call'](af);if(aS(0x226)==typeof af[aS(0x231)])return af;if(!isNaN(af[aS(0x1ba)])){var ah=-0x1,ai=function aj(){var aT=aS;for(;++ah<af['length'];)if(B[aT(0x181)](af,ah))return aj[aT(0x19c)]=af[ah],aj[aT(0x20d)]=!0x1,aj;return aj[aT(0x19c)]=q,aj['done']=!0x0,aj;};return ai[aS(0x231)]=ai;}}throw new TypeError(h(af)+aS(0x222));}return a0[aB(0x22a)]=a1,C(a5,aB(0x1e1),{'value':a1,'configurable':!0x0}),C(a1,aB(0x1e1),{'value':a0,'configurable':!0x0}),a0[aB(0x1da)]=I(a1,H,aB(0x1f9)),z[aB(0x22f)]=function(af){var aU=aB,ag=aU(0x226)==typeof af&&af[aU(0x1e1)];return!!ag&&(ag===a0||aU(0x1f9)===(ag[aU(0x1da)]||ag[aU(0x17f)]));},z['mark']=function(af){var aV=aB;return Object[aV(0x234)]?Object[aV(0x234)](af,a1):(af[aV(0x200)]=a1,I(af,H,aV(0x1f9))),af['prototype']=Object[aV(0x21c)](a5),af;},z[aB(0x1ea)]=function(af){return{'__await':af};},a6(a7[aB(0x22a)]),I(a7['prototype'],G,function(){return this;}),z['AsyncIterator']=a7,z[aB(0x187)]=function(af,ag,ah,ai,aj){var aW=aB;void 0x0===aj&&(aj=Promise);var ak=new a7(J(af,ag,ah,ai),aj);return z[aW(0x22f)](ag)?ak:ak[aW(0x231)]()[aW(0x1e2)](function(al){var aX=aW;return al[aX(0x20d)]?al[aX(0x19c)]:ak[aX(0x231)]();});},a6(a5),I(a5,H,aB(0x1a2)),I(a5,F,function(){return this;}),I(a5,aB(0x216),function(){var aY=aB;return aY(0x25b);}),z[aB(0x1fb)]=function(af){var aZ=aB,ag=Object(af),ah=[];for(var ai in ag)ah[aZ(0x1b9)](ai);return ah['reverse'](),function aj(){var b0=aZ;for(;ah[b0(0x1ba)];){var ak=ah[b0(0x24a)]();if(ak in ag)return aj['value']=ak,aj[b0(0x20d)]=!0x1,aj;}return aj['done']=!0x0,aj;};},z[aB(0x1cb)]=ad,ac[aB(0x22a)]={'constructor':ac,'reset':function(af){var b1=aB;if(this[b1(0x1aa)]=0x0,this[b1(0x231)]=0x0,this[b1(0x1ca)]=this[b1(0x202)]=q,this['done']=!0x1,this['delegate']=null,this[b1(0x1e9)]=b1(0x231),this[b1(0x1a7)]=q,this[b1(0x17e)][b1(0x21a)](ab),!af){for(var ag in this)'t'===ag[b1(0x1be)](0x0)&&B[b1(0x181)](this,ag)&&!isNaN(+ag[b1(0x207)](0x1))&&(this[ag]=q);}},'stop':function(){var b2=aB;this['done']=!0x0;var af=this['tryEntries'][0x0][b2(0x249)];if(b2(0x1c4)===af[b2(0x1f1)])throw af['arg'];return this['rval'];},'dispatchException':function(af){var b4=aB;if(this['done'])throw af;var ag=this;function ah(an,ao){var b3=a7b;return ak[b3(0x1f1)]=b3(0x1c4),ak[b3(0x1a7)]=af,ag[b3(0x231)]=an,ao&&(ag['method']=b3(0x231),ag[b3(0x1a7)]=q),!!ao;}for(var ai=this['tryEntries'][b4(0x1ba)]-0x1;ai>=0x0;--ai){var aj=this[b4(0x17e)][ai],ak=aj[b4(0x249)];if(b4(0x1a5)===aj[b4(0x1f4)])return ah(b4(0x1e6));if(aj[b4(0x1f4)]<=this[b4(0x1aa)]){var al=B[b4(0x181)](aj,'catchLoc'),am=B[b4(0x181)](aj,b4(0x24d));if(al&&am){if(this['prev']<aj[b4(0x25a)])return ah(aj[b4(0x25a)],!0x0);if(this[b4(0x1aa)]<aj[b4(0x24d)])return ah(aj[b4(0x24d)]);}else{if(al){if(this[b4(0x1aa)]<aj[b4(0x25a)])return ah(aj[b4(0x25a)],!0x0);}else{if(!am)throw Error('try\x20statement\x20without\x20catch\x20or\x20finally');if(this[b4(0x1aa)]<aj[b4(0x24d)])return ah(aj['finallyLoc']);}}}}},'abrupt':function(af,ag){var b5=aB;for(var ah=this[b5(0x17e)][b5(0x1ba)]-0x1;ah>=0x0;--ah){var ai=this[b5(0x17e)][ah];if(ai[b5(0x1f4)]<=this[b5(0x1aa)]&&B['call'](ai,b5(0x24d))&&this['prev']<ai['finallyLoc']){var aj=ai;break;}}aj&&('break'===af||b5(0x185)===af)&&aj[b5(0x1f4)]<=ag&&ag<=aj[b5(0x24d)]&&(aj=null);var ak=aj?aj[b5(0x249)]:{};return ak[b5(0x1f1)]=af,ak[b5(0x1a7)]=ag,aj?(this[b5(0x1e9)]='next',this[b5(0x231)]=aj[b5(0x24d)],Y):this[b5(0x25e)](ak);},'complete':function(af,ag){var b6=aB;if(b6(0x1c4)===af[b6(0x1f1)])throw af['arg'];return b6(0x1f2)===af[b6(0x1f1)]||'continue'===af[b6(0x1f1)]?this[b6(0x231)]=af[b6(0x1a7)]:b6(0x241)===af[b6(0x1f1)]?(this[b6(0x180)]=this[b6(0x1a7)]=af[b6(0x1a7)],this['method']='return',this['next']=b6(0x1e6)):b6(0x1a1)===af[b6(0x1f1)]&&ag&&(this['next']=ag),Y;},'finish':function(af){var b7=aB;for(var ag=this[b7(0x17e)]['length']-0x1;ag>=0x0;--ag){var ah=this[b7(0x17e)][ag];if(ah[b7(0x24d)]===af)return this[b7(0x25e)](ah[b7(0x249)],ah[b7(0x1b6)]),ab(ah),Y;}},'catch':function(af){var b8=aB;for(var ag=this['tryEntries'][b8(0x1ba)]-0x1;ag>=0x0;--ag){var ah=this[b8(0x17e)][ag];if(ah['tryLoc']===af){var ai=ah[b8(0x249)];if('throw'===ai['type']){var aj=ai[b8(0x1a7)];ab(ah);}return aj;}}throw Error(b8(0x1a3));},'delegateYield':function(af,ag,ah){var b9=aB;return this['delegate']={'iterator':ad(af),'resultName':ag,'nextLoc':ah},b9(0x231)===this[b9(0x1e9)]&&(this[b9(0x1a7)]=q),Y;}},z;}d['exports']=i,d[aA(0x18c)]['__esModule']=!0x0,d[aA(0x18c)][aA(0x218)]=d[aA(0x18c)];},0x1294:function(d,f,g){var ba=a7b,h=g(0x1219)();d['exports']=h;try{regeneratorRuntime=h;}catch(i){ba(0x24c)==typeof globalThis?globalThis[ba(0x1ad)]=h:Function('r','regeneratorRuntime\x20=\x20r')(h);}}},b={};function c(d){var bb=a7b,f=b[d];if(void 0x0!==f)return f[bb(0x18c)];var g=b[d]={'exports':{}};return a[d](g,g[bb(0x18c)],c),g['exports'];}c['n']=function(d){var bc=a7b,f=d&&d[bc(0x212)]?function(){return d['default'];}:function(){return d;};return c['d'](f,{'a':f}),f;},c['d']=function(d,f){var bd=a7b;for(var g in f)c['o'](f,g)&&!c['o'](d,g)&&Object[bd(0x203)](d,g,{'enumerable':!0x0,'get':f[g]});},c['o']=function(d,f){return Object['prototype']['hasOwnProperty']['call'](d,f);},(function(){'use strict';var bC=a7b;function q(ag,ah,ai,aj,ak,al,am){var be=a7b;try{var an=ag[al](am),ao=an[be(0x19c)];}catch(ap){return void ai(ap);}an['done']?ah(ao):Promise[be(0x244)](ao)[be(0x1e2)](aj,ak);}function z(ag){var bf=a7b;return z=bf(0x226)==typeof Symbol&&bf(0x1c7)==typeof Symbol[bf(0x1a9)]?function(ah){return typeof ah;}:function(ah){var bg=bf;return ah&&bg(0x226)==typeof Symbol&&ah['constructor']===Symbol&&ah!==Symbol[bg(0x22a)]?bg(0x1c7):typeof ah;},z(ag);}function A(ag){var bi=a7b,ah=function(ai,aj){var bh=a7b;if(bh(0x24c)!=z(ai)||!ai)return ai;var ak=ai[Symbol['toPrimitive']];if(void 0x0!==ak){var al=ak[bh(0x181)](ai,aj||bh(0x218));if(bh(0x24c)!=z(al))return al;throw new TypeError(bh(0x20f));}return(bh(0x1a6)===aj?String:Number)(ai);}(ag,bi(0x1a6));return bi(0x1c7)==z(ah)?ah:ah+'';}function B(ag,ah){var bj=a7b;for(var ai=0x0;ai<ah[bj(0x1ba)];ai++){var aj=ah[ai];aj[bj(0x219)]=aj[bj(0x219)]||!0x1,aj[bj(0x220)]=!0x0,'value'in aj&&(aj[bj(0x196)]=!0x0),Object[bj(0x203)](ag,A(aj[bj(0x23a)]),aj);}}function C(ag,ah,ai){var bk=a7b;return ah&&B(ag[bk(0x22a)],ah),ai&&B(ag,ai),Object['defineProperty'](ag,'prototype',{'writable':!0x1}),ag;}function D(ag,ah){var bl=a7b;if(!(ag instanceof ah))throw new TypeError(bl(0x186));}function F(ag,ah){var bm=a7b;if(ah&&(bm(0x24c)==z(ah)||'function'==typeof ah))return ah;if(void 0x0!==ah)throw new TypeError(bm(0x223));return function(ai){var bn=bm;if(void 0x0===ai)throw new ReferenceError(bn(0x23c));return ai;}(ag);}function G(ag){var bo=a7b;return G=Object['setPrototypeOf']?Object[bo(0x227)][bo(0x192)]():function(ah){var bp=bo;return ah[bp(0x200)]||Object[bp(0x227)](ah);},G(ag);}function H(ag,ah){return H=Object['setPrototypeOf']?Object['setPrototypeOf']['bind']():function(ai,aj){var bq=a7b;return ai[bq(0x200)]=aj,ai;},H(ag,ah);}function I(ag,ah){var br=a7b;if('function'!=typeof ah&&null!==ah)throw new TypeError(br(0x1fe));ag[br(0x22a)]=Object['create'](ah&&ah[br(0x22a)],{'constructor':{'value':ag,'writable':!0x0,'configurable':!0x0}}),Object[br(0x203)](ag,br(0x22a),{'writable':!0x1}),ah&&H(ag,ah);}var J=c(0x1294),K=c['n'](J);function Q(ag,ah,ai){var bt=a7b;(function(aj,ak){var bs=a7b;if(ak[bs(0x188)](aj))throw new TypeError(bs(0x1c8));}(ag,ah),ah[bt(0x22d)](ag,ai));}function U(ag,ah,ai){return ag['set'](X(ag,ah),ai),ai;}function V(ag,ah){return ag['get'](X(ag,ah));}function X(ag,ah,ai){var bu=a7b;if('function'==typeof ag?ag===ah:ag['has'](ah))return arguments[bu(0x1ba)]<0x3?ah:ai;throw new TypeError(bu(0x239));}var Y=new WeakMap(),Z=new WeakMap(),a0=new WeakMap(),a1=new WeakMap(),a2=new WeakMap(),a3=(function(){var bw=a7b;function ag(ah){var bv=a7b;if(D(this,ag),Q(this,Y,!0x1),Q(this,Z,void 0x0),Q(this,a0,void 0x0),Q(this,a1,void 0x0),Q(this,a2,void 0x0),0x1!==arguments[bv(0x1ba)])throw new TypeError(bv(0x230));bv(0x190)===ah?(U(Y,this,!0x0),ah=bv(0x255)):bv(0x1a6)!=typeof ah&&(ah=ah[bv(0x216)]());var ai=a7['_']['exec'](ah);if(!ai)throw new SyntaxError(bv(0x209)+ah);if(U(Z,this,ai[0x1]||'file'),U(a0,this,ai[0x2]||''),U(a1,this,ai[0x3]),bv(0x1b5)!==V(Z,this)&&!V(a0,this))throw new SyntaxError(bv(0x209)+ah);U(a2,this,this['XtoRegExp']());}return C(ag,[{'key':bw(0x20a),'value':function(){var bx=bw,ah='^';V(Y,this)?ah+=bx(0x1ec):'*'===V(Z,this)?ah+='https?://<host>':ah+=V(Z,this)+bx(0x205),ah=(ah+=bx(0x208))[bx(0x247)]('<scheme>',a4['_']);var ai='';if(V(a0,this)){if('*'===V(a0,this))ai=bx(0x213);else{var aj=V(a0,this);V(a0,this)[bx(0x189)]('*.')&&(ai=bx(0x1d1),aj=V(a0,this)['substr'](0x2)),ai+=aj[bx(0x247)](/[.-]/g,bx(0x18a));}ai=ai[bx(0x247)](/<label>/g,a5['_']);}return ah=(ah=ah[bx(0x247)]('<host>',ai))[bx(0x247)](bx(0x1bf),V(a1,this)[bx(0x247)](/[.+?^${}()|[\]\\\/]/g,'\x5c$&')[bx(0x247)](/\*/g,'.*')),new RegExp(ah);}},{'key':bw(0x1b3),'value':function(){return V(Y,this)?'<all_urls>':V(a0,this)?{'scheme':V(Z,this),'host':V(a0,this),'path':V(a1,this)}:{'scheme':V(Z,this),'path':V(a1,this)};}},{'key':bw(0x216),'value':function(){var by=bw;return V(Y,this)?by(0x190):V(Z,this)+'://'+V(a0,this)+V(a1,this);}},{'key':bw(0x25d),'value':function(){var bz=bw,ah=arguments[bz(0x1ba)]>0x0&&void 0x0!==arguments[0x0]?arguments[0x0]:'';return new RegExp(V(a2,this),ah);}},{'key':bw(0x1df),'value':function(ah){var bA=bw;if(!(ah instanceof URL))try{ah=new URL(ah);}catch(ai){return!0x1;}return ah=''[bA(0x217)](ah[bA(0x1f5)],'//')[bA(0x217)](ah['hostname'])[bA(0x217)](ah['pathname'])[bA(0x217)](ah['search']),V(a2,this)['test'](ah);}}],[{'key':'test','value':function(ah){var bB=bw,ai=arguments[bB(0x1ba)]>0x1&&void 0x0!==arguments[0x1]?arguments[0x1]:'';switch(arguments['length']){case 0x1:if(ah instanceof ag)return!0x0;try{return new ag(ah),!0x0;}catch(aj){return!0x1;}case 0x2:try{return ah instanceof ag||(ah=new ag(ah)),ah['test'](ai);}catch(ak){return!0x1;}default:throw new TypeError(bB(0x21b));}}},{'key':bw(0x25d),'value':function(ah,ai){return(ah=new ag(ah))['toRegExp'](ai);}}]);}()),a4={'_':'https?|wss?|ftp|file'},a5={'_':bC(0x1e7)},a6={'_':bC(0x1b8)['replace'](bC(0x20b),a4['_'])['replace'](bC(0x210),'\x5c*|(?:\x5c*\x5c.)?<label>(?:\x5c.<label>)*'['replace'](/<label>/g,a5['_']))[bC(0x247)](bC(0x1bf),bC(0x243))},a7={'_':new RegExp(a6['_'])},a8=a3;function a9(ag,ah,ai){var bD=bC;return ah=G(ah),F(ag,aa()?Reflect[bD(0x253)](ah,ai||[],G(ag)[bD(0x1e1)]):ah[bD(0x252)](ag,ai));}function aa(){var bE=bC;try{var ag=!Boolean[bE(0x22a)][bE(0x1b3)][bE(0x181)](Reflect[bE(0x253)](Boolean,[],function(){}));}catch(ah){}return(aa=function(){return!!ag;})();}var ab=localStorage[bC(0x182)]('lvsPlatformConfig'),ac=ab?JSON[bC(0x215)](ab):{'chaturbate':{'isRoomPage':[bC(0x18d),bC(0x228),'*://*.chaturbate.eu/b/*','*://*.chaturbate.la/b/*','*://*.chaturbate.me/b/*',bC(0x1af),bC(0x1ff),'*://*.chaturbatefreecams.com/b/*',bC(0x21f),bC(0x1f8),bC(0x23f),bC(0x18b),'*://*.cb.dev/b/*','*://*.cams.homelivesex.com/b/*','*://*.camru.top/b/*','*://*.camdudes.com/b/*','*://chaturbate.jjgirls.com/b/*','*://*.webmodels.live/b/*',bC(0x1b2),'*://*.ru2.camru.top/b/*',bC(0x206),'*://*.camangels.com/b/*','*://*.adultcams.su/b/*',bC(0x1b4),'*://*.lolycam.com/b/*','*://*.camscaster.com/b/*',bC(0x1e3),bC(0x1c6),bC(0x23d),bC(0x259),bC(0x1d3),bC(0x201),'*://*.cams.nudelive.com/b/*',bC(0x17c),bC(0x191),bC(0x229),bC(0x238),bC(0x1ee),bC(0x233),bC(0x240),bC(0x1cc),bC(0x1d2),'*://*.chaturbate.wang/b/*',bC(0x17d),'*://*.chinesecamsplus.com/b/*',bC(0x242)]},'bongacams':{'isRoomPage':[bC(0x1d9),'*://*.bongamodels2.com/chat-console*',bC(0x245),bC(0x197),bC(0x251)]},'cam4':{'isRoomPage':[bC(0x18e),bC(0x232),bC(0x22c),bC(0x1e5)]},'myfreecams':{'isRoomPage':['*://*.myfreecams.com/modelweb/*']},'camsoda':{'isRoomPage':[bC(0x1d4),bC(0x1eb),bC(0x1ac)]},'stripchat':{'isRoomPage':[bC(0x1c3),bC(0x221),bC(0x19d),'*://*.stripchat.dev/*',bC(0x1c2),'*://*.stripdev.com/*',bC(0x236),'*://*.strip.chat/*',bC(0x1f3),'*://*.stripchat3.com/*','*://*.xlivesex.com/*',bC(0x1ab),bC(0x1d7),bC(0x1e8)]}},ad={},ae={};function af(ag,ah){try{return a8['test'](ag,ah);}catch(ai){return!0x1;}}!function(ag,ah){var bF=bC,ai='',aj=location[bF(0x254)];for(var ak in ac){if(ac[ak]['isRoomPage'][bF(0x24b)](function(al){return af(al,aj);})){ai=bF(0x184)===ak&&/[^]*.cb.dev\/[^]*/g['test'](aj)?bF(0x257):bF(0x1d0)===ak?bF(0x211):ak,ah(ag);break;}}ag[bF(0x22e)](bF(0x1f7),function(){var bG=bF;ag[bG(0x1d5)](new CustomEvent(bG(0x23e),{'bubbles':!0x0,'cancelable':!0x1,'detail':ai}));}),ag[bF(0x22e)](bF(0x225),function(al){var bH=bF,am,an=al[bH(0x19f)]||{},ao=an[bH(0x1ae)],ap=void 0x0===ao?'':ao,aq=an[bH(0x20e)],ar=void 0x0===aq?'':aq;ad[ap]&&(null===(am=ad[ap])||void 0x0===am||am[bH(0x198)](ar));}),ag[bF(0x22e)](bF(0x1b1),function(al){var bI=bF,am,an=al[bI(0x19f)]||{},ao=an['url'],ap=void 0x0===ao?'':ao,aq=an['data'],ar=void 0x0===aq?'':aq;ae[ap]&&(null===(am=ae[ap])||void 0x0===am||am['postMessage'](ar));});}(window,function(ag){var bJ=bC;ag[bJ(0x199)]=function(ai){function aj(){var bK=a7b,ak;D(this,aj);for(var al=arguments[bK(0x1ba)],am=new Array(al),an=0x0;an<al;an++)am[an]=arguments[an];return(ak=a9(this,aj,[][bK(0x217)](am)))['addEventListener']('open',function(ao){var bL=bK;ad[ak[bL(0x1ae)]]=ak;}),ak[bK(0x22e)](bK(0x1d8),function(ao){delete ad[ak['url']];}),ak['addEventListener'](bK(0x19a),function(ao){var bM=bK,ap,aq={'data':null==ao?void 0x0:ao[bM(0x20e)],'url':null==ao||null===(ap=ao[bM(0x235)])||void 0x0===ap?void 0x0:ap[bM(0x1ae)]};ag[bM(0x1d5)](new CustomEvent(bM(0x1fc),{'bubbles':!0x0,'cancelable':!0x1,'detail':JSON[bM(0x1a4)](aq)}));}),ak;}return I(aj,ai),C(aj);}(ag[bJ(0x199)]),ag[bJ(0x24e)]=function(ai){function aj(){var bN=a7b,ak;D(this,aj);for(var al=arguments[bN(0x1ba)],am=new Array(al),an=0x0;an<al;an++)am[an]=arguments[an];return(ak=a9(this,aj,[][bN(0x217)](am)))[bN(0x22e)](bN(0x1a8),function(){var bO=bN;if(0x4===ak['readyState']&&bO(0x1cf)!==ak[bO(0x1fd)]){var ao;ao={'url':ak[bO(0x183)]||window['location'][bO(0x250)],'data':{'readyState':ak[bO(0x1b7)],'response':ak[bO(0x1ce)],'responseURL':ak[bO(0x183)],'responseType':ak[bO(0x1fd)],'status':ak[bO(0x18f)],'statusText':ak[bO(0x1c5)]}},ag[bO(0x1d5)](new CustomEvent('LVS_XHR_MESSAGE',{'bubbles':!0x0,'cancelable':!0x1,'detail':JSON[bO(0x1a4)](ao)}));}},!0x1),ak;}return I(aj,ai),C(aj);}(ag[bJ(0x24e)]);var ah=ag[bJ(0x1d6)];ag['fetch']=function(){var bP=bJ,ai=new URL(arguments['length']<=0x0?void 0x0:arguments[0x0],window[bP(0x21d)][bP(0x250)]),aj=ai[bP(0x1ae)],ak=ai[bP(0x254)];return aj||(aj=ak),ah['apply'](void 0x0,arguments)['then']((function(){var bQ=bP,al,am=(al=K()[bQ(0x237)](function an(ao){var bR=bQ,ap,aq,ar,as,at;return K()[bR(0x1b0)](function(au){var bS=bR;for(;;)switch(au[bS(0x1aa)]=au[bS(0x231)]){case 0x0:if(aj){au[bS(0x231)]=0x2;break;}return au[bS(0x1cd)]('return',ao);case 0x2:if(!((ap=ao[bS(0x1bc)]())instanceof Response)){au[bS(0x231)]=0x1e;break;}return aq=void 0x0,ar='',as='',au['prev']=0x7,au['next']=0xa,ao[bS(0x1bc)]()[bS(0x24f)]();case 0xa:aq=au[bS(0x1ca)],as=bS(0x24f),au[bS(0x231)]=0x11;break;case 0xe:au[bS(0x1aa)]=0xe,au['t0']=au[bS(0x214)](0x7);case 0x11:if(aq){au[bS(0x231)]=0x1c;break;}return au[bS(0x1aa)]=0x12,au['next']=0x15,ao['clone']()[bS(0x1e0)]();case 0x15:ar=au[bS(0x1ca)],as=bS(0x1e0),au[bS(0x231)]=0x1c;break;case 0x19:au[bS(0x1aa)]=0x19,au['t1']=au[bS(0x214)](0x12);case 0x1c:at={'url':aj,'data':{'readyState':0x4,'response':aq||ar,'responseURL':aj,'responseType':as,'status':ap['status'],'statusText':ap[bS(0x1c5)]}},ag['dispatchEvent'](new CustomEvent(bS(0x258),{'bubbles':!0x0,'cancelable':!0x1,'detail':JSON[bS(0x1a4)](at)}));case 0x1e:return au[bS(0x1cd)](bS(0x241),ao);case 0x1f:case bS(0x1e6):return au[bS(0x1bb)]();}},an,null,[[0x7,0xe],[0x12,0x19]]);}),function(){var ao=this,ap=arguments;return new Promise(function(aq,ar){var bT=a7b,as=al[bT(0x252)](ao,ap);function at(av){q(as,aq,ar,at,au,'next',av);}function au(av){q(as,aq,ar,at,au,'throw',av);}at(void 0x0);});});return function(ao){var bU=bQ;return am[bU(0x252)](this,arguments);};}()));},ag[bJ(0x1dc)]=function(ai){function aj(){var bV=a7b;for(var ak,al=arguments['length'],am=new Array(al),an=0x0;an<al;an++)am[an]=arguments[an];return D(this,aj),(ak=a9(this,aj,[][bV(0x217)](am)))[bV(0x22e)]('message',function(ao){var bW=bV,ap=JSON[bW(0x215)](JSON[bW(0x1a4)](new URL(am[0x0],window[bW(0x21d)]['origin'])));ae[ap]=ak,ag[bW(0x1d5)](new CustomEvent(bW(0x256),{'bubbles':!0x0,'cancelable':!0x1,'detail':{'url':ap,'data':null==ao?void 0x0:ao[bW(0x20e)]}}));}),ak;}return I(aj,ai),C(aj);}(ag['Worker']),bJ(0x1ed)in navigator&&navigator[bJ(0x1ed)][bJ(0x22e)](bJ(0x19a),function(ai){var bX=bJ;ag[bX(0x1d5)](new CustomEvent(bX(0x194),{'bubbles':!0x0,'cancelable':!0x1,'detail':ai}));});});}());}()));function a7a(){var bY=['Invalid\x20URL\x20match\x20pattern:\x20','XtoRegExp','<scheme>','_invoke','done','data','@@toPrimitive\x20must\x20return\x20a\x20primitive\x20value.','<host>','bongamodels','__esModule','(<label>\x5c.)*<label>','catch','parse','toString','concat','default','enumerable','forEach','URLMatchPattern.test\x20requires\x201\x20or\x202\x20arguments','create','location','The\x20iterator\x20does\x20not\x20provide\x20a\x20\x27','*://*.latinchaturbate.com/b/*','configurable','*://*.stripchat.com/cams/*','\x20is\x20not\x20iterable','Derived\x20constructors\x20may\x20only\x20return\x20object\x20or\x20undefined','2534502xVVIld','SEND_LVS_MESSAGE_BY_WEB_SOCKET','function','getPrototypeOf','*://*.chaturbate.com/b/*','*://chaturbate.megacams.me/b/*','prototype','suspendedStart','*://*.cam4.es/broadcast*','set','addEventListener','isGeneratorFunction','URLMatchPattern\x20requires\x20exact\x201\x20argument','next','*://*.cam4.fr/broadcast*','*://*.cams69.net/b/*','setPrototypeOf','target','*://*.xhamsterlive.com/*','mark','*://*.cam2it.com/b/*','Private\x20element\x20is\x20not\x20present\x20on\x20this\x20object','key','__await','this\x20hasn\x27t\x20been\x20initialised\x20-\x20super()\x20hasn\x27t\x20been\x20called','*://*.homeoffice.live/b/*','LVS_SITE_KEY','*://*.camvirt.com/b/*','*://*.chatur.cc/b/*','return','*://*.chatbang.net/b/*','/.*','resolve','*://*.bongamodels.com/console*','\x27\x20method','replace','Generator\x20is\x20already\x20running','completion','pop','some','object','finallyLoc','XMLHttpRequest','json','origin','*://*.camvoltmodels.com/console*','apply','construct','href','*://*/*','LVS_WORKER_MESSAGE','testbed','LVS_XHR_MESSAGE','*://*.pastabate.com/b/*','catchLoc','[object\x20Generator]','28SBjLgC','toRegExp','complete','*://*.freecams.me/b/*','*://*.chaturbate.su/b/*','tryEntries','name','rval','call','getItem','responseURL','chaturbate','continue','Cannot\x20call\x20a\x20class\x20as\x20a\x20function','async','has','startsWith','\x5c$&','*://*.cht.xxx/*','exports','*://*.chaturbate.asia/b/*','*://*.cam4.com/broadcast*','status','<all_urls>','*://cams.gaypage.com/b/*','bind','asyncIterator','LVS_SERVICE_WORKER_MESSAGE','17930hEQAaQ','writable','*://*.bongamodels2.com/console*','send','WebSocket','message','890QltVjQ','value','*://*.stripchat.com/*','265112CvhUDN','detail','suspendedYield','normal','Generator','illegal\x20catch\x20attempt','stringify','root','string','arg','readystatechange','iterator','prev','*://*.stripchat.global/*','*://*.cumchater.com/*','regeneratorRuntime','url','*://*.chatubate.me/b/*','wrap','SEND_LVS_MESSAGE_BY_WORKER','*://*.chaturbatecams.com/b/*','valueOf','*://*.cams.naughtyads.com.au/b/*','file','afterLoc','readyState','^<all_urls>$|^(?:(?:file://|(?:(\x5c*|<scheme>)://(<host>)))(<path>))$','push','length','stop','clone','dispatchException','charAt','<path>','nextLoc','129771bmOhAM','*://*.mywebcamroom.com/*','*://stripchat.com/cams/*','throw','statusText','*://*.thecarnal.cafe/b/*','symbol','Cannot\x20initialize\x20the\x20same\x20private\x20elements\x20twice\x20on\x20an\x20object','completed','sent','values','*://*.chatyourbate.com/b/*','abrupt','response','arraybuffer','bongacams','(<label>\x5c.)*','*://*.chaterbate.cc/b/*','*://*.nudes.show/b/*','*://*.camsoda.com/*','dispatchEvent','fetch','*://*.superchat.live/*','close','*://*.bongamodels.com/chat-console*','displayName','toStringTag','Worker','2490425IenWYW','hasOwnProperty','test','text','constructor','then','*://*.my.freecams6.com/b/*','executing','*://*.cam4.de.com/broadcast*','end','[0-9A-Za-z]+(?:\x5c-+[0-9A-Za-z]+)*','*://*.xham.live/*','method','awrap','*://*.elitemodelsnow.net/*','(file://|(<scheme>)://<host>)','serviceWorker','*://chat.chaturbate.lu/b/*','1YUNgjB','1572594DZVfYA','type','break','*://*.livelesbiansexcams.com/*','tryLoc','protocol','1830048JGUwYk','GET_LVS_SITE_KEY','*://*.privatecams.com/b/*','GeneratorFunction','@@iterator','keys','LVS_WEB_SOCKET_MESSAGE','responseType','Super\x20expression\x20must\x20either\x20be\x20null\x20or\x20a\x20function','*://*.chaturbate.global/b/*','__proto__','*://*.facetimegirls.com/b/*','_sent','defineProperty','delegate','://<host>','*://*.camgirllover.com/b/*','slice','<path>$'];a7a=function(){return bY;};return a7a();}
\ No newline at end of file
const a8h=a8b;(function(a,b){const g=a8b,c=a();while(!![]){try{const d=-parseInt(g(0x125))/0x1*(parseInt(g(0x123))/0x2)+-parseInt(g(0x12a))/0x3*(-parseInt(g(0x120))/0x4)+-parseInt(g(0x121))/0x5+parseInt(g(0x126))/0x6*(parseInt(g(0x11d))/0x7)+-parseInt(g(0x11f))/0x8+parseInt(g(0x127))/0x9+parseInt(g(0x11b))/0xa;if(d===b)break;else c['push'](c['shift']());}catch(f){c['push'](c['shift']());}}}(a8a,0x8a6d6));let headElem=document[a8h(0x124)](a8h(0x11c));function createScriptElement(a){const i=a8h,b=document['createElement'](i(0x11e));return b['type']=i(0x12b),b['src']=a,b;}function a8b(a,b){const c=a8a();return a8b=function(d,e){d=d-0x119;let f=c[d];return f;},a8b(a,b);}function a8a(){const j=['3068308PjmJiW','1528480UBcMDk','documentElement','50304rKbpGS','createElement','39gGGhbQ','882ahqxjn','2137041sYidJz','remove','runtime','3nEFAfQ','text/javascript','/js/intercept.js','appendChild','9156510OAUHdr','head','45626ajejhQ','script','8197608fPLyEN'];a8a=function(){return j;};return a8a();}try{headElem[a8h(0x11a)](createScriptElement(chrome[a8h(0x129)]['getURL'](a8h(0x119))));}catch(a8c){}document[a8h(0x122)][a8h(0x11a)](headElem),headElem[a8h(0x128)]();
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "Lovense Cam Extension",
"version": "31.5.6",
"description": "Have fun with your Lovense toys by customizing your own levels. ***Note: Remember to enable 'Allow user scripts' after installation. Click on 'Details' and turn on 'Allow user scripts'",
"author": "lovense",
"manifest_version": 3,
"minimum_chrome_version": "120",
"permissions": [
"offscreen",
"userScripts",
"storage",
"unlimitedStorage",
"desktopCapture"
],
"incognito": "split",
"icons": {
"16": "icons/icon-16.png",
"128": "icons/icon-128.png"
},
"options_page": "",
"action": {
"default_title": "Lovense Cam Extension",
"default_popup": "pages/chrome_popup.html"
},
"background": {
"service_worker": "js/service_worker.js"
},
"host_permissions": [
"<all_urls>"
],
"content_scripts": [
{
"js": [
"js/run-at-document-start.js"
],
"matches": [
"<all_urls>"
],
"exclude_matches": [
"*://*.lovense.com/cam-model/*",
"*://*.lovense.com/cam-model-v3/*",
"*://localhost/*"
],
"run_at": "document_start"
}
],
"content_security_policy": {
"extension_pages": "script-src 'self' http://localhost:*; object-src 'self';"
},
"externally_connectable": {
"matches": [
"*://*.lovense.com/cam-model/*",
"*://*.lovense.com/cam-model-v3/*",
"*://localhost/*"
]
},
"web_accessible_resources": [
{
"resources": [
"icons/*",
"img/*",
"js/*"
],
"matches": [
"<all_urls>"
]
}
],
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7zM8HealrzUCVVeJYzx5Vvz3VYwdWyfoQ5Vb7hP34PJvyJ9/5LnvzLoGHaUCjwSLHtT+IqFNkA6OUVhotpZzb4N1zaTq81QP0QnoOerHHQTqkiJyvtaVGiPhZlQ5dDhHoSLZJ1bNaltveGNG3CE2/yicohnJTlXmsh053trrQYVaR6HPJ5lICT37D7jsuMHayFjEsWTlZPLP6Wn80zrLEvpH93oA12UnWyQuPs5z/X5HhyrxzCmeJ4nZXtKuB6ICWU+dHSwzWm4N3l9qCxdvAjgsYwG64+34/i00E8eWgwISeOOVHV2p80UnOanQk7TBfiXJSBQgbosUbLZjmv7y6wIDAQAB"
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
Extension Background
</title>
<link rel="icon" href="/favicon.ico">
<style>
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<iframe id="backgroundIframe" width="100%" height="100%" frameborder="0" scrolling="no" allow="bluetooth;hid;serial"></iframe>
<script src="/js/chrome_background.js"></script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
Extension Popup
</title>
<link rel="icon" href="/favicon.ico">
<style>
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
}
body {
width: 400px;
height: 600px;
}
.popup-container {
width: 100%;
height: 100%;
}
.cam-page-loading {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
}
.loading-block {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
justify-content: center;
align-items: center;
}
.loading-ball {
width: 20px;
height: 20px;
border-radius: 50%;
}
.loading-ball+.loading-ball {
margin-left: 20px;
}
.loading-ball:nth-child(1) {
background-color: #0a0258;
animation: leftBallLoad 1s infinite linear;
}
.loading-ball:nth-child(2) {
background-color: #ff2d89;
animation: rightBallLoad 1s infinite linear;
}
@keyframes leftBallLoad {
0% {
transform: translate3d(0, 0, 0) scale(1);
}
25% {
transform: translate3d(20px, 0, 0) scale(1.3);
}
50% {
transform: translate3d(40px, 0, 0) scale(1);
}
75% {
transform: translate3d(20px, 0, 0) scale(0.7);
}
100% {
transform: translate3d(0, 0, 0) scale(1);
}
}
@keyframes rightBallLoad {
0% {
transform: translate3d(0, 0, 0) scale(1);
}
25% {
transform: translate3d(-20px, 0, 0) scale(0.7);
}
50% {
transform: translate3d(-40px, 0, 0) scale(1);
}
75% {
transform: translate3d(-20px, 0, 0) scale(1.3);
}
100% {
transform: translate3d(0, 0, 0) scale(1);
}
}
</style>
</head>
<body>
<div class="popup-container">
<div class="cam-page-loading" id="camPageLoading">
<div class="loading-block">
<div class="loading-ball"></div>
<div class="loading-ball"></div>
</div>
</div>
</div>
<script src="/js/chrome_popup.js"></script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
Stream Master
</title>
<link rel="icon" href="/cam-model/favicon.ico">
<style>
body {
margin: 0;
overflow: hidden;
}
.cam-page-loading {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
}
.hidden-cam-page-loading {
display: none !important;
}
.loading-block {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
justify-content: center;
align-items: center;
}
.loading-ball {
width: 20px;
height: 20px;
border-radius: 50%;
}
.loading-ball+.loading-ball {
margin-left: 20px;
}
.loading-ball:nth-child(1) {
background-color: #0a0258;
animation: leftBallLoad 1s infinite linear;
}
.loading-ball:nth-child(2) {
background-color: #ff2d89;
animation: rightBallLoad 1s infinite linear;
}
@keyframes leftBallLoad {
0% {
transform: translate3d(0, 0, 0) scale(1);
}
25% {
transform: translate3d(20px, 0, 0) scale(1.3);
}
50% {
transform: translate3d(40px, 0, 0) scale(1);
}
75% {
transform: translate3d(20px, 0, 0) scale(0.7);
}
100% {
transform: translate3d(0, 0, 0) scale(1);
}
}
@keyframes rightBallLoad {
0% {
transform: translate3d(0, 0, 0) scale(1);
}
25% {
transform: translate3d(-20px, 0, 0) scale(0.7);
}
50% {
transform: translate3d(-40px, 0, 0) scale(1);
}
75% {
transform: translate3d(-20px, 0, 0) scale(1.3);
}
100% {
transform: translate3d(0, 0, 0) scale(1);
}
}
</style>
</head>
<body>
<div id="app"></div>
<div class="cam-page-loading" id="camPageLoading">
<div class="loading-block">
<div class="loading-ball"></div>
<div class="loading-ball"></div>
</div>
</div>
<script src="/js/stream_master.js"></script>
</body>
</html>
console.log('BACKGROUND STARTED');
var running = false;
// Function to get data from chrome.storage
function getDataFromStorage(key) {
return new Promise((resolve, reject) => {
chrome.storage.local.get(key, (result) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve(result);
}
});
});
}
function stopRunning() {
running = false;
chrome.runtime.sendMessage({message: "stop"});
}
function startRunning() {
running = true;
chrome.runtime.sendMessage({message: "start"});
}
function updateRunning(msg) {
chrome.runtime.sendMessage({message: "start", state: msg});
}
async function initializeXHAM() {
const randomString = Math.random().toString(36).substring(2, 18);
const url = "https://xhamsterlive.com/api/front/v3/config/initial?timezoneOffset=-120&timezone=Africa%2FJohannesburg&skipTimezoneAutoSaving=true&requestPath=%2Fearnings%2Fpaying-users&updateTag=0&disableClient=0&uniq="+randomString;
return fetch(url);
}
async function fetchUserData(userID) {
const randomString2 = Math.random().toString(36).substring(2, 18);
const userurl = "https://xhamsterlive.com/api/front/v2/users/"+userID+"?uniq="+randomString2;
return fetch(userurl);
}
// Function to fetch data from the API
async function requestTippersFriends() {
startRunning();
updateRunning("starting...");
var initdata;
try {
updateRunning("getting initialization data...")
const initresponse = await initializeXHAM()
if (!initresponse.ok) {
throw new Error(`HTTP error! status: ${initresponse.status}`);
}
initdata = await initresponse.json();
} catch (error) {
stopRunning();
console.log('INITDATA RETRIVIAL FAILED', error);
}
updateRunning("getting tipping users...");
const randomString = Math.random().toString(36).substring(2, 18);
const url = 'https://xhamsterlive.com/api/front/users/21483393/transactions/users?isOnline=&offset=0&limit=1000&username=&period=0&sort=lastPaid&order=desc&uniq='+randomString;
try {
const response = await fetch(url);
// Check if the response is ok (status in the range 200-299)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(data.users); // Log the result
updateRunning("get storage saved user list");
var friendedUsers = { 0: {id: 0, username:'fakeused', 'isDeleted': true}};
try {
const result = await getDataFromStorage('XhamsterLiveFriended');
console.log('Retrieved object:', result.XhamsterLiveFriended);
if(result.XhamsterLiveFriended) friendedUsers = result.XhamsterLiveFriended;
} catch (error) {
console.log('No object found, created:', friendedUsers);
}
console.log('FrienderUsers:', friendedUsers);
updateRunning("remove already known users");
const usersToFetch = [];
const existingUserIds = new Set(Object.values(friendedUsers).map(user => user.id));
Object.entries(data.users).forEach(([userKey, user]) => {
if ( user.isDeleted == false ) {
if (!existingUserIds.has(user.id)) {
console.log(`NEW USER: Key: ${userKey}, User ID: ${user.id}, Username: ${user.username}`);
usersToFetch.push(user.id);
}
}
});
console.log("users to Fetch", usersToFetch, usersToFetch.length);
if(usersToFetch.length < 1) {
stopRunning();
return;
}
const reqdata = { csrfToken: initdata.initial.client.csrfToken,
csrfTimestamp: initdata.initial.client.csrfTimestamp,
csrfNotifyTimestamp: initdata.initial.client.csrfNotifyTimestamp,
userIds: usersToFetch,
uniq: randomString }
try {
updateRunning("check which users we can friend");
const prefres = await fetch("https://xhamsterlive.com/api/front/models/21483393/preferences", {
method: 'POST', // Specify the request method
headers: {
'Content-Type': 'application/json' // Set the content type to JSON
},
body: JSON.stringify(reqdata) // Convert the request payload to a JSON string
});
if (!prefres.ok) {
throw new Error(`HTTP error! status: ${prefres.status}`);
}
const prefdata = await prefres.json()
console.log('prefdata', prefdata);
Object.entries(prefdata.canFriend).forEach(([userid, canFriend]) => {
if ( canFriend == false ) {
usersToFetch.pop(userid);
friendedUsers[userid] = data.users[userid];
}
});
console.log('userToFriend:', usersToFetch);
const rdata = {
csrfToken: initdata.initial.client.csrfToken,
csrfTimestamp: initdata.initial.client.csrfTimestamp,
csrfNotifyTimestamp: initdata.initial.client.csrfNotifyTimestamp,
uniq: randomString
}
var index=1;
updateRunning("sending friends requests: "+index+"/"+usersToFetch.length);
// Rate limiting variables
const maxConcurrentRequests = 1;
const maxRequestsPerInterval = 1;
const intervalDuration = 2000; // milliseconds
let currentRequests = 0;
let requestCount = 0;
// Function to handle the fetching with rate limiting
const fetchWithRateLimit = async (userId) => {
while (currentRequests >= maxConcurrentRequests) {
await new Promise(resolve => setTimeout(resolve, intervalDuration)); // Wait until a slot is free
}
currentRequests++;
try {
const uri = "https://xhamsterlive.com/api/front/users/"+initdata.initial.client.user.id+"/friends/"+userId;
console.log("sending friend request:", index+"/"+usersToFetch.length, uri, "with data", rdata);
const freq = await fetch(uri, {
method: "PUT",
headers: {
'Content-Type': 'application/json' // Set the content type to JSON
},
body: JSON.stringify(rdata)
});
if (!freq.ok) {
if(freq.status != "400") throw new Error(`HTTP error! status: ${freq.status}`);
}
if(freq.ok || freq.status=="400") friendedUsers[userId] = data.users[userId];
const udata = await freq.json();
console.log('friend requested for user:', '('+index+"/"+usersToFetch.length+")", udata);
index=index+1;
updateRunning("sending friends requests: "+index+"/"+usersToFetch.length);
} finally {
currentRequests--;
requestCount++;
// Reset the request count after the interval duration
if (requestCount >= maxRequestsPerInterval) {
await new Promise(resolve => setTimeout(resolve, intervalDuration));
requestCount = 0;
}
}
};
// Process users in batches
const promises = usersToFetch.map(userId => fetchWithRateLimit(userId));
await Promise.all(promises);
updateRunning("saving processed users");
console.log("FriendedUsers to store", friendedUsers);
chrome.storage.local.set({"XhamsterLiveFriended": friendedUsers});
} catch (error) {
stopRunning();
console.log('PREFENCE DATA RETRIVIAL FAILED', error);
}
} catch (error) {
stopRunning();
console.error('Error fetching data:', error);
}
}
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message === "isrunning") {
sendResponse({isrunning: running});
}
if (request.message === "toggle") {
if (request.state === "run") {
console.log('RUN!!');
sendResponse({farewell: "Run!"});
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, { message: "run" }, (response) => {
console.log(response); // Log the response from the content script
});
});
requestTippersFriends();
stopRunning();
}
}
});
function fuckSnap2()
{
document.querySelector('[title="View friend requests"]').click();
var btns = document.evaluate("//span[contains(., 'Accept')]", document, null, XPathResult.ANY_TYPE, null );
var btn = btns.iterateNext();
while(btn != null) {
btn.parentElement.parentElement.click();
btn = btns.iterateNext();
}
}
//setInterval(fuckSnap2, 5000);
//
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message === "run") {
console.log('RUN!!');
sendResponse({ response: "Got it from content script!" });
}
});
console.log('LOADED!!!!');
{
"name":"XHamsterLive SHM extension",
"description":"Accept friends",
"version":"0.1.0",
"manifest_version":3,
"icons":{"16":"icon16.png","48":"icon48.png","128":"icon128.png"},
"action": {
"default_popup": "popup.html"
},
"background": {
"service_worker": "background.js"
},
"permissions": ["storage", "activeTab", "declarativeContent"],
"content_scripts":[
{
"matches":["https://xhamsterlive.com/*","http://xhamsterlive.com/*"],
"run_at":"document_end",
"js":["contentScript.js"]
}
]
}
<!DOCTYPE html>
<html>
<head>
<title>XhamsterLive SHM</title>
<style>
body {
width: 200px;
padding: 10px;
}
button {
width: 100%;
padding: 10px;
margin-top: 10px;
cursor: pointer;
}
</style>
</head>
<body>
<button id="run">Run</button>
<div id="runres"></div>
<script src="popup.js">
</script
</body>
</html>
console.log('POPUP.JS');
document.addEventListener('DOMContentLoaded', function() {
console.log('POPUP LOADED');
document.getElementById('run').addEventListener('click', function() {
chrome.runtime.sendMessage({message: "toggle", state: "run"}, function(response){
console.log(response.farewell);
}
);
console.log('PRESSED ON');
});
chrome.runtime.sendMessage({message: "isrunning"}, function(response){
console.log("isrunning?", response);
if(response.isrunning === true) document.getElementById("run").disabled = true;
else document.getElementById("run").disabled = false;
});
});
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message == "update") {
document.getElementById('runres').innerHTML(request.state);
}
if (request.message == "start") {
document.getElementById("run").disabled = true;
}
if (request.message == "stop") {
document.getElementById("run").disabled = false;
}
});
// Log when the background script is loaded
console.log('Background script loaded');
// Function to check if chrome.runtime is initialized
function isChromeRuntimeReady() {
// Check for our global initialization flags first
if (typeof window.__qtWebChannelTransportReady === 'boolean' &&
window.__qtWebChannelTransportReady === true) {
return true;
}
// Check if the isChromeRuntimeInitialized function exists and use it
if (typeof window.isChromeRuntimeInitialized === 'function') {
return window.isChromeRuntimeInitialized();
}
// Fallback to basic checks
return typeof chrome !== 'undefined' &&
chrome.runtime &&
typeof chrome.runtime.id === 'string';
}
// Function to wait for chrome.runtime to be initialized
function waitForChromeRuntime(callback, maxAttempts = 100) {
let attempts = 0;
function checkRuntime() {
if (isChromeRuntimeReady()) {
console.log('chrome.runtime API is ready');
callback();
} else if (attempts < maxAttempts) {
attempts++;
// Log less frequently to reduce console spam
if (attempts === 1 || attempts % 10 === 0) {
console.log(`Waiting for chrome.runtime API to initialize (attempt ${attempts}/${maxAttempts})...`);
}
// Try to trigger initialization if we're past several attempts
if (attempts > 20 && attempts % 20 === 0) {
console.log('Attempting to trigger chrome.runtime initialization');
document.dispatchEvent(new CustomEvent('requestWebChannelTransport', {
detail: { timestamp: Date.now() }
}));
}
setTimeout(checkRuntime, 100);
} else {
console.error('ERROR: chrome.runtime API failed to initialize after multiple attempts');
// Report initialization status if available
if (typeof window.reportInitializationStatus === 'function') {
window.reportInitializationStatus();
} else {
console.error('Initialization status:');
console.error(`- chrome defined: ${typeof chrome !== 'undefined'}`);
console.error(`- chrome.runtime defined: ${typeof chrome !== 'undefined' && typeof chrome.runtime !== 'undefined'}`);
console.error(`- __qtWebChannelTransportReady: ${window.__qtWebChannelTransportReady}`);
}
}
}
checkRuntime();
}
// Add a listener for the chrome.runtime.initialized event
document.addEventListener('chrome.runtime.initialized', (event) => {
console.log(`chrome.runtime API initialized for extension: ${event.detail.extensionId}`);
initializeBackgroundFunctionality();
});
// Initialize all background functionality
function initializeBackgroundFunctionality() {
// Verify chrome.runtime.onMessage exists before adding listener
if (!chrome.runtime || !chrome.runtime.onMessage) {
console.error('chrome.runtime.onMessage is not available');
// Try to create it if it doesn't exist
if (chrome.runtime && !chrome.runtime.onMessage) {
console.log('Attempting to create chrome.runtime.onMessage');
chrome.runtime.onMessage = {
addListener: function(callback) {
console.log('Added listener to newly created onMessage');
if (!this.listeners) this.listeners = [];
this.listeners.push(callback);
},
removeListener: function(callback) {
if (!this.listeners) return;
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1);
}
},
hasListener: function(callback) {
return this.listeners && this.listeners.includes(callback);
},
listeners: []
};
}
}
// Now try to add the message listener
try {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log('Background received message:', message);
console.log('Sender:', sender);
// Send a response back
setTimeout(() => {
sendResponse({
received: true,
from: 'background',
timestamp: Date.now(),
echo: message
});
}, 500); // Simulate some async processing
return true; // Keep the message channel open for async response
});
console.log('Message listener set up successfully in background');
} catch (e) {
console.error('Failed to add message listener in background:', e);
}
// Verify chrome.runtime.onConnect exists before adding listener
if (!chrome.runtime || !chrome.runtime.onConnect) {
console.error('chrome.runtime.onConnect is not available');
// Try to create it if it doesn't exist
if (chrome.runtime && !chrome.runtime.onConnect) {
console.log('Attempting to create chrome.runtime.onConnect');
chrome.runtime.onConnect = {
addListener: function(callback) {
console.log('Added listener to newly created onConnect');
if (!this.listeners) this.listeners = [];
this.listeners.push(callback);
},
removeListener: function(callback) {
if (!this.listeners) return;
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1);
}
},
hasListener: function(callback) {
return this.listeners && this.listeners.includes(callback);
},
listeners: []
};
}
}
// Now try to add the connect listener
try {
chrome.runtime.onConnect.addListener((port) => {
console.log(`Port connected: ${port.name}`);
// Listen for messages on this port
port.onMessage.addListener((message) => {
console.log(`Port message received on ${port.name}:`, message);
// Send a response back through the port
port.postMessage({
received: true,
from: 'background',
timestamp: Date.now(),
echo: message
});
});
// Handle port disconnection
port.onDisconnect.addListener(() => {
console.log(`Port disconnected: ${port.name}`);
if (chrome.runtime.lastError) {
console.error('Port error:', chrome.runtime.lastError);
}
});
// Send an initial message to the port
port.postMessage({
action: 'connected',
from: 'background',
timestamp: Date.now()
});
});
console.log('Connect listener set up successfully in background');
} catch (e) {
console.error('Failed to add connect listener in background:', e);
}
// Start the ping interval
startPingInterval();
// Log extension information
logExtensionInfo();
}
// Send a message to the popup every 10 seconds if it's open
function startPingInterval() {
setInterval(() => {
chrome.runtime.sendMessage({
action: 'ping',
from: 'background',
timestamp: Date.now()
}, (response) => {
if (chrome.runtime.lastError) {
// This is normal if the popup is not open
console.log('Ping failed (popup probably closed):', chrome.runtime.lastError.message);
} else {
console.log('Ping response:', response);
}
});
}, 10000);
}
// Log information about the extension
function logExtensionInfo() {
console.log('Extension ID:', chrome.runtime.id);
try {
const manifest = chrome.runtime.getManifest();
console.log('Manifest:', manifest);
} catch (error) {
console.error('Error getting manifest:', error);
}
}
// Check if runtime is ready and initialize, or wait for it
if (isChromeRuntimeReady()) {
console.log('chrome.runtime API is already available, initializing background');
initializeBackgroundFunctionality();
} else {
console.log('Waiting for chrome.runtime API to initialize...');
// The initialization will happen when the chrome.runtime.initialized event fires
}
\ No newline at end of file
// Log when the content script is loaded
console.log('Runtime API Test Extension: Content script loaded');
// Function to check if chrome.runtime is initialized
function isChromeRuntimeReady() {
// Check for our global initialization flags first
if (typeof window.__qtWebChannelTransportReady === 'boolean' &&
window.__qtWebChannelTransportReady === true) {
return true;
}
// Check if the isChromeRuntimeInitialized function exists and use it
if (typeof window.isChromeRuntimeInitialized === 'function') {
return window.isChromeRuntimeInitialized();
}
// Fallback to basic checks
return typeof chrome !== 'undefined' &&
chrome.runtime &&
typeof chrome.runtime.id === 'string';
}
// Function to wait for chrome.runtime to be initialized
function waitForChromeRuntime(callback, maxAttempts = 100) {
let attempts = 0;
function checkRuntime() {
if (isChromeRuntimeReady()) {
console.log('chrome.runtime API is ready');
callback();
} else if (attempts < maxAttempts) {
attempts++;
// Log less frequently to reduce console spam
if (attempts === 1 || attempts % 10 === 0) {
console.log(`Waiting for chrome.runtime API to initialize (attempt ${attempts}/${maxAttempts})...`);
}
// Try to trigger initialization if we're past several attempts
if (attempts > 20 && attempts % 20 === 0) {
console.log('Attempting to trigger chrome.runtime initialization');
document.dispatchEvent(new CustomEvent('requestWebChannelTransport', {
detail: { timestamp: Date.now() }
}));
}
setTimeout(checkRuntime, 100);
} else {
console.error('ERROR: chrome.runtime API failed to initialize after multiple attempts');
// Report initialization status if available
if (typeof window.reportInitializationStatus === 'function') {
window.reportInitializationStatus();
} else {
console.error('Initialization status:');
console.error(`- chrome defined: ${typeof chrome !== 'undefined'}`);
console.error(`- chrome.runtime defined: ${typeof chrome !== 'undefined' && typeof chrome.runtime !== 'undefined'}`);
console.error(`- __qtWebChannelTransportReady: ${window.__qtWebChannelTransportReady}`);
}
}
}
checkRuntime();
}
// Function to create a floating control panel
function createControlPanel() {
// Check if panel already exists
if (document.getElementById('runtime-api-test-panel')) {
return;
}
// Create panel container
const panel = document.createElement('div');
panel.id = 'runtime-api-test-panel';
panel.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
width: 300px;
background-color: #f0f0f0;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
font-family: Arial, sans-serif;
font-size: 12px;
z-index: 9999;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
`;
// Create header
const header = document.createElement('div');
header.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
border-bottom: 1px solid #ccc;
padding-bottom: 5px;
`;
const title = document.createElement('h3');
title.textContent = 'Runtime API Test';
title.style.margin = '0';
const closeBtn = document.createElement('button');
closeBtn.textContent = 'X';
closeBtn.style.cssText = `
background: none;
border: none;
cursor: pointer;
font-weight: bold;
`;
closeBtn.onclick = () => panel.remove();
header.appendChild(title);
header.appendChild(closeBtn);
panel.appendChild(header);
// Create buttons
const createButton = (text, onClick) => {
const button = document.createElement('button');
button.textContent = text;
button.style.cssText = `
margin: 5px 0;
padding: 5px;
width: 100%;
cursor: pointer;
`;
button.onclick = onClick;
return button;
};
// Add buttons for testing runtime API
panel.appendChild(createButton('Send Message to Background', () => {
waitForChromeRuntime(() => {
chrome.runtime.sendMessage(
{ action: 'test', from: 'content', url: window.location.href },
response => {
logOutput(`Response: ${JSON.stringify(response)}`);
}
);
logOutput('Message sent to background');
});
}));
panel.appendChild(createButton('Connect to Background', () => {
waitForChromeRuntime(() => {
const port = chrome.runtime.connect({ name: 'content-port' });
logOutput('Connected to background with port: content-port');
port.onMessage.addListener(msg => {
logOutput(`Port message received: ${JSON.stringify(msg)}`);
});
port.postMessage({ action: 'hello', from: 'content', url: window.location.href });
logOutput('Message sent through port');
});
}));
panel.appendChild(createButton('Get Extension URL', () => {
waitForChromeRuntime(() => {
const url = chrome.runtime.getURL('popup.html');
logOutput(`Extension URL: ${url}`);
});
}));
// Create output area
const output = document.createElement('div');
output.id = 'runtime-api-test-output';
output.style.cssText = `
margin-top: 10px;
padding: 5px;
background-color: #fff;
border: 1px solid #ddd;
height: 100px;
overflow-y: auto;
font-family: monospace;
font-size: 11px;
white-space: pre-wrap;
`;
panel.appendChild(output);
// Add to page
document.body.appendChild(panel);
// Log function
window.logOutput = function(message) {
const outputDiv = document.getElementById('runtime-api-test-output');
if (outputDiv) {
const timestamp = new Date().toLocaleTimeString();
outputDiv.innerHTML += `[${timestamp}] ${message}\n`;
outputDiv.scrollTop = outputDiv.scrollHeight;
}
};
logOutput('Content script panel initialized');
// Check chrome.runtime API status
if (isChromeRuntimeReady()) {
logOutput('chrome.runtime API is already available');
} else {
logOutput('Waiting for chrome.runtime API to initialize...');
}
// Setup message listener
setupMessageListener();
}
// Setup message listener when runtime is ready
function setupMessageListener() {
waitForChromeRuntime(() => {
// Verify chrome.runtime.onMessage exists before adding listener
if (!chrome.runtime || !chrome.runtime.onMessage) {
console.error('chrome.runtime.onMessage is not available');
// Try to create it if it doesn't exist
if (chrome.runtime && !chrome.runtime.onMessage) {
console.log('Attempting to create chrome.runtime.onMessage');
chrome.runtime.onMessage = {
addListener: function(callback) {
console.log('Added listener to newly created onMessage');
if (!this.listeners) this.listeners = [];
this.listeners.push(callback);
},
removeListener: function(callback) {
if (!this.listeners) return;
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1);
}
},
hasListener: function(callback) {
return this.listeners && this.listeners.includes(callback);
},
listeners: []
};
}
}
// Now try to add the listener
try {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log('Content script received message:', message);
if (document.getElementById('runtime-api-test-output')) {
logOutput(`Message from background: ${JSON.stringify(message)}`);
}
sendResponse({ received: true, from: 'content', url: window.location.href });
return true; // Keep the message channel open for async response
});
console.log('Message listener set up successfully');
} catch (e) {
console.error('Failed to add message listener:', e);
if (document.getElementById('runtime-api-test-output')) {
logOutput(`ERROR: Failed to add message listener: ${e.message}`);
}
}
});
}
// Add a listener for the chrome.runtime.initialized event
document.addEventListener('chrome.runtime.initialized', (event) => {
console.log(`chrome.runtime API initialized for extension: ${event.detail.extensionId}`);
if (document.getElementById('runtime-api-test-output')) {
logOutput(`chrome.runtime API initialized for extension: ${event.detail.extensionId}`);
}
});
// Wait for page to be fully loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', createControlPanel);
} else {
createControlPanel();
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Create Extension Icons</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
}
canvas {
border: 1px solid #ccc;
margin: 10px;
}
.icon-container {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
}
button {
margin: 5px;
padding: 5px 10px;
}
.instructions {
background-color: #f5f5f5;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<h1>Extension Icon Generator</h1>
<div class="instructions">
<h3>Instructions:</h3>
<ol>
<li>Click the "Generate Icons" button to create simple placeholder icons</li>
<li>Right-click on each canvas and select "Save Image As..."</li>
<li>Save each icon with the appropriate filename (icon16.png, icon48.png, icon128.png)</li>
<li>Place the saved icons in your extension directory</li>
</ol>
</div>
<button id="generateBtn">Generate Icons</button>
<div class="icon-container">
<h3>16x16 Icon</h3>
<canvas id="canvas16" width="16" height="16"></canvas>
<button id="download16">Download icon16.png</button>
</div>
<div class="icon-container">
<h3>48x48 Icon</h3>
<canvas id="canvas48" width="48" height="48"></canvas>
<button id="download48">Download icon48.png</button>
</div>
<div class="icon-container">
<h3>128x128 Icon</h3>
<canvas id="canvas128" width="128" height="128"></canvas>
<button id="download128">Download icon128.png</button>
</div>
<script>
function drawIcon(canvas, size) {
const ctx = canvas.getContext('2d');
// Clear canvas
ctx.clearRect(0, 0, size, size);
// Draw background
ctx.fillStyle = '#4285F4'; // Google blue
ctx.fillRect(0, 0, size, size);
// Draw border
ctx.strokeStyle = '#FFFFFF';
ctx.lineWidth = Math.max(1, size / 16);
ctx.strokeRect(ctx.lineWidth/2, ctx.lineWidth/2, size - ctx.lineWidth, size - ctx.lineWidth);
// Draw "R" for Runtime
ctx.fillStyle = '#FFFFFF';
ctx.font = `bold ${Math.floor(size * 0.7)}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('R', size/2, size/2);
}
function setupDownload(canvasId, downloadId, filename) {
document.getElementById(downloadId).addEventListener('click', () => {
const canvas = document.getElementById(canvasId);
const dataURL = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.download = filename;
link.href = dataURL;
link.click();
});
}
document.getElementById('generateBtn').addEventListener('click', () => {
drawIcon(document.getElementById('canvas16'), 16);
drawIcon(document.getElementById('canvas48'), 48);
drawIcon(document.getElementById('canvas128'), 128);
});
setupDownload('canvas16', 'download16', 'icon16.png');
setupDownload('canvas48', 'download48', 'icon48.png');
setupDownload('canvas128', 'download128', 'icon128.png');
// Generate icons on page load
window.onload = () => {
document.getElementById('generateBtn').click();
};
</script>
</body>
</html>
\ No newline at end of file
{
"name": "Runtime API Test Extension",
"version": "1.0",
"manifest_version": 3,
"description": "A test extension for chrome.runtime API",
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
}
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_idle"
}
],
"permissions": [
"storage"
],
"host_permissions": [
"<all_urls>"
]
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Runtime API Test Extension</title>
<style>
body {
width: 300px;
padding: 10px;
font-family: Arial, sans-serif;
}
button {
margin: 5px 0;
padding: 5px 10px;
width: 100%;
}
#output {
margin-top: 10px;
padding: 10px;
border: 1px solid #ccc;
background-color: #f5f5f5;
max-height: 200px;
overflow-y: auto;
white-space: pre-wrap;
font-family: monospace;
}
</style>
</head>
<body>
<h2>Runtime API Test</h2>
<button id="getUrl">Test chrome.runtime.getURL()</button>
<button id="sendMessage">Test chrome.runtime.sendMessage()</button>
<button id="connect">Test chrome.runtime.connect()</button>
<button id="getManifest">Test chrome.runtime.getManifest()</button>
<button id="getBackgroundPage">Test chrome.runtime.getBackgroundPage()</button>
<button id="clearOutput">Clear Output</button>
<div id="output"></div>
<script src="popup.js"></script>
</body>
</html>
\ No newline at end of file
// Function to log output to the output div
function log(message) {
const output = document.getElementById('output');
const timestamp = new Date().toLocaleTimeString();
output.innerHTML += `[${timestamp}] ${message}\n`;
output.scrollTop = output.scrollHeight;
}
// Function to check if chrome.runtime is initialized
function isChromeRuntimeReady() {
// Check for our global initialization flags first
if (typeof window.__qtWebChannelTransportReady === 'boolean' &&
window.__qtWebChannelTransportReady === true) {
return true;
}
// Check if the isChromeRuntimeInitialized function exists and use it
if (typeof window.isChromeRuntimeInitialized === 'function') {
return window.isChromeRuntimeInitialized();
}
// Fallback to basic checks
return typeof chrome !== 'undefined' &&
chrome.runtime &&
typeof chrome.runtime.id === 'string';
}
// Function to wait for chrome.runtime to be initialized
function waitForChromeRuntime(callback, maxAttempts = 100) {
let attempts = 0;
function checkRuntime() {
if (isChromeRuntimeReady()) {
log('chrome.runtime API is ready');
callback();
} else if (attempts < maxAttempts) {
attempts++;
// Log less frequently to reduce console spam
if (attempts === 1 || attempts % 10 === 0) {
log(`Waiting for chrome.runtime API to initialize (attempt ${attempts}/${maxAttempts})...`);
}
// Try to trigger initialization if we're past several attempts
if (attempts > 20 && attempts % 20 === 0) {
log('Attempting to trigger chrome.runtime initialization');
document.dispatchEvent(new CustomEvent('requestWebChannelTransport', {
detail: { timestamp: Date.now() }
}));
}
setTimeout(checkRuntime, 100);
} else {
log('ERROR: chrome.runtime API failed to initialize after multiple attempts');
// Report initialization status if available
if (typeof window.reportInitializationStatus === 'function') {
window.reportInitializationStatus();
} else {
log('Initialization status:');
log(`- chrome defined: ${typeof chrome !== 'undefined'}`);
log(`- chrome.runtime defined: ${typeof chrome !== 'undefined' && typeof chrome.runtime !== 'undefined'}`);
log(`- __qtWebChannelTransportReady: ${window.__qtWebChannelTransportReady}`);
}
}
}
checkRuntime();
}
// Add a listener for the chrome.runtime.initialized event
document.addEventListener('chrome.runtime.initialized', (event) => {
log(`chrome.runtime API initialized for extension: ${event.detail.extensionId}`);
});
// Test chrome.runtime.getURL()
document.getElementById('getUrl').addEventListener('click', () => {
waitForChromeRuntime(() => {
try {
const url = chrome.runtime.getURL('popup.html');
log(`getURL result: ${url}`);
} catch (error) {
log(`getURL error: ${error.message}`);
}
});
});
// Test chrome.runtime.sendMessage()
document.getElementById('sendMessage').addEventListener('click', () => {
waitForChromeRuntime(() => {
try {
const message = { action: 'test', from: 'popup', timestamp: Date.now() };
chrome.runtime.sendMessage(message, (response) => {
if (chrome.runtime.lastError) {
log(`sendMessage error: ${chrome.runtime.lastError.message}`);
} else {
log(`sendMessage response: ${JSON.stringify(response)}`);
}
});
log(`sendMessage sent: ${JSON.stringify(message)}`);
} catch (error) {
log(`sendMessage error: ${error.message}`);
}
});
});
// Test chrome.runtime.connect()
document.getElementById('connect').addEventListener('click', () => {
waitForChromeRuntime(() => {
try {
const port = chrome.runtime.connect({ name: 'popup-port' });
log(`connect: Port created with name 'popup-port'`);
// Verify port.onMessage exists before adding listener
if (!port.onMessage) {
log('port.onMessage is not available');
// Try to create it if it doesn't exist
port.onMessage = {
addListener: function(callback) {
log('Added listener to newly created port.onMessage');
if (!this.listeners) this.listeners = [];
this.listeners.push(callback);
},
removeListener: function(callback) {
if (!this.listeners) return;
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1);
}
},
hasListener: function(callback) {
return this.listeners && this.listeners.includes(callback);
},
listeners: []
};
}
// Now try to add the message listener
try {
port.onMessage.addListener((message) => {
log(`Port received message: ${JSON.stringify(message)}`);
});
log('Port message listener set up successfully');
} catch (e) {
log(`Failed to add port message listener: ${e.message}`);
}
// Verify port.onDisconnect exists before adding listener
if (!port.onDisconnect) {
log('port.onDisconnect is not available');
// Try to create it if it doesn't exist
port.onDisconnect = {
addListener: function(callback) {
log('Added listener to newly created port.onDisconnect');
if (!this.listeners) this.listeners = [];
this.listeners.push(callback);
},
removeListener: function(callback) {
if (!this.listeners) return;
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1);
}
},
hasListener: function(callback) {
return this.listeners && this.listeners.includes(callback);
},
listeners: []
};
}
// Now try to add the disconnect listener
try {
port.onDisconnect.addListener(() => {
log(`Port disconnected`);
});
log('Port disconnect listener set up successfully');
} catch (e) {
log(`Failed to add port disconnect listener: ${e.message}`);
}
// Try to send a message through the port
try {
port.postMessage({ action: 'hello', from: 'popup', timestamp: Date.now() });
log(`Port sent message`);
} catch (e) {
log(`Failed to send message through port: ${e.message}`);
}
} catch (error) {
log(`connect error: ${error.message}`);
}
});
});
// Test chrome.runtime.getManifest()
document.getElementById('getManifest').addEventListener('click', () => {
waitForChromeRuntime(() => {
try {
const manifest = chrome.runtime.getManifest();
log(`getManifest result: ${JSON.stringify(manifest, null, 2)}`);
} catch (error) {
log(`getManifest error: ${error.message}`);
}
});
});
// Test chrome.runtime.getBackgroundPage()
document.getElementById('getBackgroundPage').addEventListener('click', () => {
waitForChromeRuntime(() => {
try {
chrome.runtime.getBackgroundPage((backgroundPage) => {
if (chrome.runtime.lastError) {
log(`getBackgroundPage error: ${chrome.runtime.lastError.message}`);
} else {
log(`getBackgroundPage success: ${backgroundPage ? 'Background page accessed' : 'null'}`);
}
});
} catch (error) {
log(`getBackgroundPage error: ${error.message}`);
}
});
});
// Clear output
document.getElementById('clearOutput').addEventListener('click', () => {
document.getElementById('output').innerHTML = '';
});
// Setup message listener when runtime is ready
function setupMessageListener() {
waitForChromeRuntime(() => {
// Verify chrome.runtime.onMessage exists before adding listener
if (!chrome.runtime || !chrome.runtime.onMessage) {
log('chrome.runtime.onMessage is not available');
// Try to create it if it doesn't exist
if (chrome.runtime && !chrome.runtime.onMessage) {
log('Attempting to create chrome.runtime.onMessage');
chrome.runtime.onMessage = {
addListener: function(callback) {
log('Added listener to newly created onMessage');
if (!this.listeners) this.listeners = [];
this.listeners.push(callback);
},
removeListener: function(callback) {
if (!this.listeners) return;
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1);
}
},
hasListener: function(callback) {
return this.listeners && this.listeners.includes(callback);
},
listeners: []
};
}
}
// Now try to add the listener
try {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
log(`Received message from background: ${JSON.stringify(message)}`);
sendResponse({ received: true, from: 'popup' });
return true; // Keep the message channel open for async response
});
log('Message listener set up successfully');
} catch (e) {
log(`Failed to add message listener: ${e.message}`);
}
});
}
// Log when popup is loaded
document.addEventListener('DOMContentLoaded', () => {
log('Popup loaded. Click buttons to test chrome.runtime API.');
// Check chrome.runtime API status
if (isChromeRuntimeReady()) {
log('chrome.runtime API is already available');
} else {
log('Waiting for chrome.runtime API to initialize...');
}
// Setup message listener
setupMessageListener();
});
\ No newline at end of file
......@@ -14,7 +14,7 @@
return;
}
console.log('Initializing chrome.runtime API emulation');
console.log('Initializing chrome.runtime API emulation for {window.location.href)');
// Extract extension ID from URL
const extensionId = window.location.hostname;
......
<!DOCTYPE html>
<html>
<head>
<title>Chrome Runtime API Test</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f5f5f5;
}
h1 {
color: #333;
}
.container {
background-color: white;
border-radius: 5px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
button {
background-color: #4285f4;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover {
background-color: #3367d6;
}
pre {
background-color: #f0f0f0;
padding: 10px;
border-radius: 4px;
overflow: auto;
max-height: 300px;
}
.output {
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>Chrome Runtime API Test</h1>
<div>
<button id="testGetURL">Test chrome.runtime.getURL</button>
<button id="testGetManifest">Test chrome.runtime.getManifest</button>
<button id="testSendMessage">Test chrome.runtime.sendMessage</button>
<button id="testConnect">Test chrome.runtime.connect</button>
<button id="checkStatus" style="background-color: #ff9800;">Check Initialization Status</button>
</div>
<div class="output">
<h3>Output:</h3>
<pre id="output">Results will appear here...</pre>
</div>
</div>
<script>
// Helper function to log output
function log(message) {
const output = document.getElementById('output');
output.textContent += message + '\n';
console.log(message);
}
// Helper function to stringify objects
function stringify(obj) {
return JSON.stringify(obj, null, 2);
}
// Function to check if chrome.runtime is initialized
function isChromeRuntimeReady() {
// Check for our global initialization flags first
if (typeof window.__qtWebChannelTransportReady === 'boolean' &&
window.__qtWebChannelTransportReady === true) {
return true;
}
// Check if the isChromeRuntimeInitialized function exists and use it
if (typeof window.isChromeRuntimeInitialized === 'function') {
return window.isChromeRuntimeInitialized();
}
// Fallback to basic checks
return typeof chrome !== 'undefined' &&
chrome.runtime &&
typeof chrome.runtime.id === 'string';
}
// Function to wait for chrome.runtime to be initialized
function waitForChromeRuntime(callback, maxAttempts = 100) {
let attempts = 0;
function checkRuntime() {
if (isChromeRuntimeReady()) {
log('chrome.runtime API is ready');
callback();
} else if (attempts < maxAttempts) {
attempts++;
// Log less frequently to reduce output spam
if (attempts === 1 || attempts % 10 === 0) {
log(`Waiting for chrome.runtime API to initialize (attempt ${attempts}/${maxAttempts})...`);
}
// Try to trigger initialization if we're past several attempts
if (attempts > 20 && attempts % 20 === 0) {
log('Attempting to trigger chrome.runtime initialization');
document.dispatchEvent(new CustomEvent('requestWebChannelTransport', {
detail: { timestamp: Date.now() }
}));
}
setTimeout(checkRuntime, 100);
} else {
log('ERROR: chrome.runtime API failed to initialize after multiple attempts');
// Report initialization status if available
if (typeof window.reportInitializationStatus === 'function') {
log('Detailed initialization status:');
window.reportInitializationStatus();
} else {
log('Initialization status:');
log(`- chrome defined: ${typeof chrome !== 'undefined'}`);
log(`- chrome.runtime defined: ${typeof chrome !== 'undefined' && typeof chrome.runtime !== 'undefined'}`);
log(`- __qtWebChannelTransportReady: ${window.__qtWebChannelTransportReady}`);
}
}
}
checkRuntime();
}
// Add a listener for the chrome.runtime.initialized event
document.addEventListener('chrome.runtime.initialized', (event) => {
log(`chrome.runtime API initialized for extension: ${event.detail.extensionId}`);
});
// Test chrome.runtime.getURL
document.getElementById('testGetURL').addEventListener('click', function() {
waitForChromeRuntime(() => {
try {
const url = chrome.runtime.getURL('test.html');
log('chrome.runtime.getURL("test.html") => ' + url);
} catch (e) {
log('ERROR: ' + e.message);
}
});
});
// Test chrome.runtime.getManifest
document.getElementById('testGetManifest').addEventListener('click', function() {
waitForChromeRuntime(() => {
try {
const manifest = chrome.runtime.getManifest();
log('chrome.runtime.getManifest() => ' + stringify(manifest));
} catch (e) {
log('ERROR: ' + e.message);
}
});
});
// Test chrome.runtime.sendMessage
document.getElementById('testSendMessage').addEventListener('click', function() {
waitForChromeRuntime(() => {
try {
chrome.runtime.sendMessage(
{ greeting: 'hello', from: 'test page' },
function(response) {
log('chrome.runtime.sendMessage response => ' + stringify(response));
}
);
log('chrome.runtime.sendMessage sent');
} catch (e) {
log('ERROR: ' + e.message);
}
});
});
// Test chrome.runtime.connect
document.getElementById('testConnect').addEventListener('click', function() {
waitForChromeRuntime(() => {
try {
const port = chrome.runtime.connect({ name: 'test-port' });
log('chrome.runtime.connect created port: ' + port.name);
port.onMessage.addListener(function(message) {
log('Port received message: ' + stringify(message));
});
port.postMessage({ greeting: 'hello from port', from: 'test page' });
log('Port message sent');
// Disconnect after 2 seconds
setTimeout(function() {
port.disconnect();
log('Port disconnected');
}, 2000);
} catch (e) {
log('ERROR: ' + e.message);
}
});
});
// Check initialization status
document.getElementById('checkStatus').addEventListener('click', function() {
log('Checking chrome.runtime initialization status...');
if (typeof window.reportInitializationStatus === 'function') {
log('Detailed initialization status:');
window.reportInitializationStatus();
} else {
log('Initialization status:');
log(`- chrome defined: ${typeof chrome !== 'undefined'}`);
log(`- chrome.runtime defined: ${typeof chrome !== 'undefined' && typeof chrome.runtime !== 'undefined'}`);
log(`- chrome.runtime.id: ${typeof chrome !== 'undefined' && typeof chrome.runtime !== 'undefined' ? chrome.runtime.id : 'undefined'}`);
log(`- __qtWebChannelTransportReady: ${window.__qtWebChannelTransportReady}`);
log(`- __chromeRuntimeInitialized: ${window.__chromeRuntimeInitialized}`);
log(`- __initializationAttempts: ${window.__initializationAttempts}`);
log(`- QWebChannel available: ${typeof QWebChannel !== 'undefined'}`);
log(`- qt object available: ${typeof qt !== 'undefined'}`);
log(`- qt.webChannelTransport available: ${typeof qt !== 'undefined' && qt.webChannelTransport !== undefined}`);
// Try to trigger initialization
log('Attempting to trigger initialization...');
document.dispatchEvent(new CustomEvent('requestWebChannelTransport', {
detail: { timestamp: Date.now() }
}));
}
});
// Check if chrome.runtime API is available on page load
window.addEventListener('DOMContentLoaded', function() {
if (isChromeRuntimeReady()) {
log('chrome.runtime API is already available');
log('Extension ID: ' + chrome.runtime.id);
} else {
log('Waiting for chrome.runtime API to initialize...');
}
});
</script>
</body>
</html>
\ No newline at end of file
......@@ -52,6 +52,27 @@ from playwright.async_api import Page as PlaywrightPage
from playwright.async_api import BrowserContext as PlaywrightBrowserContext
# Register URL schemes before any QApplication is created
def register_url_schemes():
# Register qextension:// scheme
qextension_scheme = QWebEngineUrlScheme(b"qextension")
qextension_scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme |
QWebEngineUrlScheme.Flag.LocalScheme |
QWebEngineUrlScheme.Flag.LocalAccessAllowed |
QWebEngineUrlScheme.Flag.ServiceWorkersAllowed)
QWebEngineUrlScheme.registerScheme(qextension_scheme)
# Register chrome:// scheme
chrome_scheme = QWebEngineUrlScheme(b"chrome")
chrome_scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme |
QWebEngineUrlScheme.Flag.LocalScheme |
QWebEngineUrlScheme.Flag.LocalAccessAllowed |
QWebEngineUrlScheme.Flag.ServiceWorkersAllowed)
QWebEngineUrlScheme.registerScheme(chrome_scheme)
print("URL schemes registered")
class ChromeWebEnginePage(QWebEnginePage):
"""
Custom QWebEnginePage that allows navigation to chrome:// URLs
......@@ -1378,7 +1399,7 @@ class ContentScriptInjector:
// Directly inject QWebChannel.js content
{qwebchannel_js}
console.log("QWebChannel.js directly injected into content script");
console.log("QWebChannel.js directly injected into content script <ContentScriptInjector.create_injection_script>");
// Verify QWebChannel is defined
if (typeof QWebChannel === 'undefined') {{
......@@ -2454,6 +2475,7 @@ class Browser(QMainWindow):
self.extensions_button.clicked.connect(self.show_extensions)
self.extensions_button_action = self.toolbar.addWidget(self.extensions_button)
"""
# Add Chrome URLs button with dropdown menu
self.chrome_button = QPushButton()
self.chrome_button.setIcon(self._get_themed_icon(QStyle.StandardPixmap.SP_TitleBarMenuButton))
......@@ -2489,6 +2511,7 @@ class Browser(QMainWindow):
self.chrome_button.setMenu(self.chrome_menu)
self.toolbar.addWidget(self.chrome_button)
"""
# Add new window button
self.new_window_button = QPushButton()
......@@ -2504,6 +2527,12 @@ class Browser(QMainWindow):
self.debug_button.clicked.connect(self.toggle_devtools)
self.toolbar.addWidget(self.debug_button)
# Add a test extension button to the toolbar
self.test_extension_button = QPushButton("Test Extension")
self.test_extension_button.setToolTip("Launch Test Extension")
self.test_extension_button.clicked.connect(self.launch_test_extension)
self.toolbar.addWidget(self.test_extension_button)
self.layout.addWidget(self.toolbar)
# Create tab widget
......@@ -2732,14 +2761,6 @@ class Browser(QMainWindow):
self.runtime_api_script = create_runtime_api_script()
self.profile.scripts().insert(self.runtime_api_script)
# Load extensions from assets directory
self.load_extensions()
# Add a test extension button to the toolbar
self.test_extension_button = QPushButton("Test Extension")
self.test_extension_button.setToolTip("Launch Test Extension")
self.test_extension_button.clicked.connect(self.launch_test_extension)
self.toolbar.addWidget(self.test_extension_button)
if detached_tab:
widget, title = detached_tab
......@@ -2988,7 +3009,7 @@ class Browser(QMainWindow):
# Create a dialog to show debug options
debug_dialog = QDialog(self)
debug_dialog.setWindowTitle("Developer Tools")
debug_dialog.resize(800, 600)
debug_dialog.resize(1024, 768)
layout = QVBoxLayout()
......@@ -3538,70 +3559,6 @@ class Browser(QMainWindow):
self.open_extension_popups.append(popup_dialog)
popup_dialog.finished.connect(lambda: self.open_extension_popups.remove(popup_dialog))
def load_extensions(self):
"""Load extensions from the assets/browser/extensions directory."""
print("Loading extensions from assets/browser/extensions...")
# Path to the extensions directory
assets_extensions_dir = "assets/browser/extensions"
if not os.path.exists(assets_extensions_dir):
print(f"Extensions directory not found: {assets_extensions_dir}")
return
# Scan for extensions
for ext_name in os.listdir(assets_extensions_dir):
ext_path = os.path.join(assets_extensions_dir, ext_name)
if not os.path.isdir(ext_path):
continue
# Skip disabled extensions
if ext_name.endswith(".disabled"):
continue
# Check for manifest.json
manifest_path = os.path.join(ext_path, "manifest.json")
if not os.path.exists(manifest_path):
print(f"Skipping {ext_name}: No manifest.json found")
continue
try:
# Read the manifest
with open(manifest_path, 'r', encoding='utf-8') as f:
manifest = json.load(f)
# Store the extension info
self.loaded_extensions[ext_name] = {
'path': ext_path,
'manifest': manifest,
'id': ext_name
}
print(f"Loaded extension: {manifest.get('name', ext_name)} (ID: {ext_name})")
# Copy the extension to the profile's extensions directory if needed
profile_ext_path = os.path.join(self.extensions_dir, ext_name)
if not os.path.exists(profile_ext_path):
print(f"Installing extension {ext_name} to profile...")
os.makedirs(profile_ext_path, exist_ok=True)
# Copy all files
for item in os.listdir(ext_path):
s = os.path.join(ext_path, item)
d = os.path.join(profile_ext_path, item)
if os.path.isdir(s):
shutil.copytree(s, d, dirs_exist_ok=True)
else:
shutil.copy2(s, d)
print(f"Extension {ext_name} installed to profile")
except Exception as e:
print(f"Error loading extension {ext_name}: {e}")
print(f"Loaded {len(self.loaded_extensions)} extensions")
# Reload content scripts to pick up any changes
if hasattr(self, 'content_script_injector'):
self.content_script_injector.load_content_scripts()
def update_extension_buttons(self):
"""Scans for extensions and adds a button for each one with a popup to the main toolbar."""
......@@ -3613,6 +3570,9 @@ class Browser(QMainWindow):
if not self.extensions_dir or not os.path.exists(self.extensions_dir):
return
# refresh the loaded_extension list
self.loaded_extensions = {}
# Find all enabled extension directories
for ext_name in sorted(os.listdir(self.extensions_dir)):
if ext_name.endswith(".disabled"):
......@@ -3633,6 +3593,13 @@ class Browser(QMainWindow):
print(f"Error loading manifest for {ext_name}: {e}")
continue
# Store the extension info
self.loaded_extensions[ext_name] = {
'path': ext_path,
'manifest': manifest,
'id': ext_name
}
# Check for a popup action
popup_path = None
action = manifest.get('action') or manifest.get('browser_action') or manifest.get('page_action')
......@@ -3668,6 +3635,7 @@ class Browser(QMainWindow):
def launch_test_extension(self):
"""Launch the test extension in a new tab."""
# Check if the test extension is loaded
print(self.loaded_extensions)
if 'test-extension' not in self.loaded_extensions:
QMessageBox.warning(self, "Extension Not Found",
"The test extension was not found. Make sure it's in the assets/browser/extensions directory.")
......@@ -3701,6 +3669,7 @@ class Browser(QMainWindow):
def launch_extension_background(self, extension_id):
"""Launch an extension's background page or script in a hidden tab."""
print(self.loaded_extensions)
if extension_id not in self.loaded_extensions:
print(f"Extension {extension_id} not found")
return None
......@@ -4205,14 +4174,13 @@ async def main_async():
def main():
"""Main function to run the browser as a standalone application."""
# Always enable extension developer mode
print("Extension developer mode always enabled")
# Enable Autofill features and extension developer mode via environment variables
chromium_flags = "--enable-features=AutofillEnableAccountWalletStorage,AutofillAddressProfileSavePrompt,AutofillCreditCardUpload,AutofillEnableToolbarStatusChip,AutofillKeyboardAccessory,AutofillShowAllSuggestionsOnPrefsCheckout,AutofillShowTypePredictions,AutofillUpstream,PasswordGeneration,PasswordGenerationBottomSheetUI,PasswordGenerationExperiment,ExtensionsToolbarMenu,ChromeUIDebugTools --extensions-on-chrome-urls --allow-file-access-from-files --allow-running-insecure-content --enable-user-scripts --allow-universal-access-from-files --disable-web-security --disable-site-isolation-trials --allow-insecure-localhost --ignore-certificate-errors --ignore-urlfetcher-cert-requests --disable-features=BlockInsecurePrivateNetworkRequests"
# Always add extension developer mode flags
#chromium_flags += " --load-extension=assets/browser/extensions --force-dev-mode-highlighting"
chromium_flags += " --force-dev-mode-highlighting"
# Explicitly enable chrome:// URLs
chromium_flags += " --enable-chrome-urls --enable-features=ChromeUIDebugTools"
......@@ -4222,18 +4190,19 @@ def main():
os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = chromium_flags
debug_mode = "--debug" in sys.argv
if debug_mode:
os.environ["QTWEBENGINE_REMOTE_DEBUGGING"] = "9222"
# Print environment variables for debugging
#Print environment variables for debugging and set remote debug where needed
print(f"QTWEBENGINE_CHROMIUM_FLAGS: {os.environ.get('QTWEBENGINE_CHROMIUM_FLAGS')}")
if debug_mode:
os.environ["QTWEBENGINE_REMOTE_DEBUGGING"] = "9222"
print(f"QTWEBENGINE_REMOTE_DEBUGGING: {os.environ.get('QTWEBENGINE_REMOTE_DEBUGGING')}")
app = QApplication.instance() or QApplication(sys.argv)
# Allow Ctrl+C to kill the application gracefully
signal.signal(signal.SIGINT, signal.SIG_DFL)
# Set application logo
app.setWindowIcon(QIcon('assets/logo.jpg'))
# Set a modern, dark theme
......@@ -4285,26 +4254,6 @@ def main():
return app.exec()
# Register URL schemes before any QApplication is created
def register_url_schemes():
# Register qextension:// scheme
qextension_scheme = QWebEngineUrlScheme(b"qextension")
qextension_scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme |
QWebEngineUrlScheme.Flag.LocalScheme |
QWebEngineUrlScheme.Flag.LocalAccessAllowed |
QWebEngineUrlScheme.Flag.ServiceWorkersAllowed)
QWebEngineUrlScheme.registerScheme(qextension_scheme)
# Register chrome:// scheme
chrome_scheme = QWebEngineUrlScheme(b"chrome")
chrome_scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme |
QWebEngineUrlScheme.Flag.LocalScheme |
QWebEngineUrlScheme.Flag.LocalAccessAllowed |
QWebEngineUrlScheme.Flag.ServiceWorkersAllowed)
QWebEngineUrlScheme.registerScheme(chrome_scheme)
print("URL schemes registered")
# Register schemes before anything else
register_url_schemes()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment