@@ -10,6 +10,7 @@ class BackgroundState {
1010 this . ports = new Set ( ) ;
1111 this . executedScripts = new Map ( ) ;
1212 this . creatingOffscreenDocument = null ;
13+ this . valueChangeListeners = new Map ( ) ;
1314 }
1415
1516 clearCache ( ) {
@@ -64,6 +65,23 @@ function notifyPorts(action) {
6465 disconnectedPorts . forEach ( ( port ) => state . ports . delete ( port ) ) ;
6566}
6667
68+ async function setupOffscreenDocument ( ) {
69+ if ( await chrome . offscreen . hasDocument ( ) ) {
70+ return ;
71+ }
72+ if ( state . creatingOffscreenDocument ) {
73+ await state . creatingOffscreenDocument ;
74+ return ;
75+ }
76+ state . creatingOffscreenDocument = chrome . offscreen . createDocument ( {
77+ url : 'offscreen/offscreen.html' ,
78+ reasons : [ 'CLIPBOARD' ] ,
79+ justification : 'Clipboard access' ,
80+ } ) ;
81+ await state . creatingOffscreenDocument ;
82+ state . creatingOffscreenDocument = null ;
83+ }
84+
6785// Script management
6886async function getFilteredScripts ( url , runAt = null ) {
6987 if ( ! url ?. startsWith ( "http" ) ) {
@@ -337,21 +355,125 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
337355 sendResponse ( { error : "Request payload is missing." } ) ;
338356 return true ;
339357 }
340- const { action, ...payload } = message . payload ;
358+ const { action, scriptId , ...payload } = message . payload ;
341359
342- if ( action === "getSettings" ) {
343- chrome . storage . local . get ( "settings" ) . then ( ( { settings = { } } ) => {
344- sendResponse ( { result : settings } ) ;
345- } ) ;
346- return true ;
347- } else if ( action === "xmlhttpRequest" ) {
348- // Still not working properly....
349- handleCrossOriginXmlhttpRequest ( payload . details , sender . tab . id , sendResponse ) ;
360+ const getStorage = async ( ) => {
361+ const key = `script-values-${ scriptId } ` ;
362+ const result = await chrome . storage . local . get ( key ) ;
363+ return result [ key ] || { } ;
364+ } ;
365+
366+ const setStorage = async ( values ) => {
367+ const key = `script-values-${ scriptId } ` ;
368+ await chrome . storage . local . set ( { [ key ] : values } ) ;
369+ } ;
370+
371+ const actionHandlers = {
372+ async setValue ( { name, value } ) {
373+ const values = await getStorage ( ) ;
374+ const oldValue = values [ name ] ;
375+ values [ name ] = value ;
376+ await setStorage ( values ) ;
377+
378+ const listeners = state . valueChangeListeners . get ( name ) ;
379+ if ( listeners ) {
380+ for ( const tabId of listeners ) {
381+ if ( tabId !== sender . tab . id ) { // Don't notify the tab that made the change
382+ chrome . tabs . sendMessage ( tabId , {
383+ type : "GM_VALUE_CHANGED" ,
384+ payload : { name, oldValue, newValue : value , remote : true } ,
385+ } ) . catch ( ( ) => { } ) ; // Ignore errors if tab is closed
386+ }
387+ }
388+ }
389+ sendResponse ( { result : null } ) ;
390+ } ,
391+ async getValue ( { name, defaultValue } ) {
392+ const values = await getStorage ( ) ;
393+ sendResponse ( { result : values [ name ] ?? defaultValue } ) ;
394+ } ,
395+ async deleteValue ( { name } ) {
396+ const values = await getStorage ( ) ;
397+ const oldValue = values [ name ] ;
398+ delete values [ name ] ;
399+ await setStorage ( values ) ;
400+
401+ const listeners = state . valueChangeListeners . get ( name ) ;
402+ if ( listeners ) {
403+ for ( const tabId of listeners ) {
404+ chrome . tabs . sendMessage ( tabId , {
405+ type : "GM_VALUE_CHANGED" ,
406+ payload : { name, oldValue, newValue : undefined , remote : tabId !== sender . tab . id } ,
407+ } ) . catch ( ( ) => { } ) ;
408+ }
409+ }
410+ sendResponse ( { result : null } ) ;
411+ } ,
412+ async listValues ( ) {
413+ const values = await getStorage ( ) ;
414+ sendResponse ( { result : Object . keys ( values ) } ) ;
415+ } ,
416+ addValueChangeListener ( { name } ) {
417+ if ( ! state . valueChangeListeners . has ( name ) ) {
418+ state . valueChangeListeners . set ( name , new Set ( ) ) ;
419+ }
420+ state . valueChangeListeners . get ( name ) . add ( sender . tab . id ) ;
421+ sendResponse ( { result : null } ) ;
422+ } ,
423+ removeValueChangeListener ( { name } ) {
424+ const listeners = state . valueChangeListeners . get ( name ) ;
425+ if ( listeners ) {
426+ listeners . delete ( sender . tab . id ) ;
427+ if ( listeners . size === 0 ) {
428+ state . valueChangeListeners . delete ( name ) ;
429+ }
430+ }
431+ sendResponse ( { result : null } ) ;
432+ } ,
433+ getSettings ( ) {
434+ chrome . storage . local . get ( "settings" ) . then ( ( { settings = { } } ) => {
435+ sendResponse ( { result : settings } ) ;
436+ } ) ;
437+ } ,
438+ xmlhttpRequest ( ) {
439+ handleCrossOriginXmlhttpRequest ( payload . details , sender . tab . id , sendResponse ) ;
440+ } ,
441+ notification ( { details } ) {
442+ const notificationOptions = {
443+ type : 'basic' ,
444+ iconUrl : details . image || 'assets/icons/icon128.png' ,
445+ title : details . title || 'CodeTweak Notification' ,
446+ message : details . text || ''
447+ } ;
448+ chrome . notifications . create ( notificationOptions , ( ) => {
449+ sendResponse ( { result : null } ) ;
450+ } ) ;
451+ return true ;
452+ } ,
453+ async setClipboard ( { data, type } ) {
454+ await setupOffscreenDocument ( ) ;
455+ chrome . runtime . sendMessage ( {
456+ target : 'offscreen' ,
457+ type : 'copy-to-clipboard' ,
458+ data : data
459+ } ) ;
460+ sendResponse ( { result : null } ) ;
461+ } ,
462+ download ( details ) {
463+ chrome . downloads . download ( { url : details . url , filename : details . name } , ( downloadId ) => {
464+ sendResponse ( { result : { downloadId } } ) ;
465+ } ) ;
466+ return true ;
467+ } ,
468+ } ;
469+
470+ if ( actionHandlers [ action ] ) {
471+ actionHandlers [ action ] ( payload ) ;
350472 return true ;
351- } else {
352- chrome . tabs . sendMessage ( sender . tab . id , message , sendResponse ) ;
353- return true ; // Async response
354473 }
474+
475+ sendResponse ( { error : `Unknown GM API action: ${ action } ` } ) ;
476+ return true ;
355477 }
356478 // Some errors around here.... not all errors being captured
357479 if ( message . type === "SCRIPT_ERROR" ) {
@@ -457,6 +579,10 @@ navigationEvents.forEach((event, index) => {
457579chrome . tabs . onRemoved . addListener ( ( tabId ) => {
458580 state . executedScripts . delete ( tabId ) ;
459581 clearInjectedCoreScriptsForTab ( tabId ) ;
582+
583+ for ( const listeners of state . valueChangeListeners . values ( ) ) {
584+ listeners . delete ( tabId ) ;
585+ }
460586} ) ;
461587
462588chrome . tabs . onUpdated . addListener ( ( tabId , changeInfo , tab ) => {
0 commit comments