@@ -647,26 +647,18 @@ function lookupOriginMap(origin) {
647647
648648// Dark mode detection. This can eventually be replaced by
649649// https://github.com/w3c/webextensions/issues/229
650- if ( typeof window !== 'undefined' && window . matchMedia ) {
651- // Firefox can detect dark mode from the background page.
652- ( async ( ) => {
650+ ( async ( ) => {
651+ if ( typeof window !== 'undefined' && window . matchMedia ) {
652+ // Firefox can detect dark mode from the background page.
653653 await optionsReady ;
654654 const query = window . matchMedia ( '(prefers-color-scheme: dark)' ) ;
655655 setColorIsDarkMode ( REGULAR_COLOR , query . matches ) ;
656656 query . addEventListener ( "change" , ( event ) => {
657657 setColorIsDarkMode ( REGULAR_COLOR , event . matches ) ;
658658 } ) ;
659- } ) ( ) ;
660- } else {
661- // Chrome needs an offscreen document to detect dark mode.
662- chrome . runtime . onMessage . addListener ( ( message ) => {
663- console . log ( "onMessage" , message ) ;
664- if ( message . hasOwnProperty ( "darkModeOffscreen" ) ) {
665- setColorIsDarkMode ( REGULAR_COLOR , message . darkModeOffscreen ) ;
666- }
667- } ) ;
668-
669- ( async ( ) => {
659+ } else {
660+ // Chrome needs an offscreen document to detect dark mode.
661+ // See the onMessage handler below.
670662 await optionsReady ;
671663 try {
672664 await chrome . offscreen . createDocument ( {
@@ -684,8 +676,51 @@ if (typeof window !== 'undefined' && window.matchMedia) {
684676 } catch {
685677 // ignore
686678 }
687- } ) ( ) ;
679+ }
680+ } ) ( ) ;
681+
682+ chrome . runtime . onMessage . addListener ( ( message ) => {
683+ if ( message . hasOwnProperty ( "darkModeOffscreen" ) ) {
684+ setColorIsDarkMode ( REGULAR_COLOR , message . darkModeOffscreen ) ;
685+ }
686+ if ( message . hasOwnProperty ( "setStorageSyncDebounce" ) ) {
687+ storageSyncDebouncer . set ( message . setStorageSyncDebounce ) ;
688+ }
689+ } ) ;
690+
691+ // This class prevents writing to storage.sync more than once per second,
692+ // so the user can type in a text field without spamming the network.
693+ // It runs in background.js to avoid data loss if the user closes the
694+ // options window within 1 second of typing.
695+ class StorageSyncDebouncer {
696+ latest = { } ;
697+ pending = { } ;
698+ writePromise = null ;
699+ set ( items ) {
700+ for ( let [ key , value ] of Object . entries ( items ) ) {
701+ if ( this . latest [ key ] !== value ) {
702+ this . latest [ key ] = value ;
703+ this . pending [ key ] = value ;
704+ }
705+ }
706+ if ( ! this . writePromise && Object . keys ( this . pending ) . length > 0 ) {
707+ this . writePromise = this . _writeWithDelay ( ) ;
708+ }
709+ }
710+ async _writeWithDelay ( ) {
711+ while ( Object . keys ( this . pending ) . length > 0 ) {
712+ const toWrite = this . pending ;
713+ this . pending = { } ;
714+ //console.log("writing", toWrite);
715+ await Promise . all ( [
716+ chrome . storage . sync . set ( toWrite ) ,
717+ new Promise ( resolve => setTimeout ( resolve , 1000 ) )
718+ ] ) ;
719+ }
720+ this . writePromise = null ;
721+ }
688722}
723+ const storageSyncDebouncer = new StorageSyncDebouncer ( ) ;
689724
690725// Must "await storageReady;" before reading maps.
691726// You can force initStorage() from the console for debugging purposes.
@@ -1103,33 +1138,28 @@ chrome.webRequest.onErrorOccurred.addListener(forgetRequest, FILTER_ALL_URLS);
11031138
11041139// -- contextMenus --
11051140
1106- // When the user right-clicks an IP address in the popup window, add a menu
1107- // item to look up the address on bgp.he.net. I don't like picking favorites,
1108- // so I'm open to making this a config option if someone recommends another
1109- // useful non-spammy service.
1110- //
1111- // Unless http://crbug.com/60758 gets resolved, the context menu's appearance
1112- // cannot vary based on content.
1141+ // When the user right-clicks a domain or IP address in the popup window,
1142+ // add a menu item that opens the requested lookup provider.
11131143const MENU_ID = "ipvfoo-lookup" ;
11141144
1115- chrome . contextMenus ?. removeAll ( ( ) => {
1116- chrome . contextMenus . create ( {
1117- title : "Look up on bgp.he.net" ,
1118- id : MENU_ID ,
1119- // Scope the menu to text selection in our popup windows.
1120- contexts : [ "selection" ] ,
1121- documentUrlPatterns : [ chrome . runtime . getURL ( "popup.html" ) ] ,
1122- } ) ;
1123- } ) ;
1124-
11251145chrome . contextMenus ?. onClicked . addListener ( ( info , tab ) => {
11261146 if ( info . menuItemId != MENU_ID ) return ;
1127- const text = info . selectionText ;
1128- if ( IP4_CHARS . test ( text ) || IP6_CHARS . test ( text ) ) {
1147+ let selectionType = "" ;
1148+ let text = info . selectionText ;
1149+ if ( DNS_CHARS . test ( text ) ) {
1150+ selectionType = "domain" ;
1151+ } else if ( IP4_CHARS . test ( text ) || IP6_CHARS . test ( text ) ) {
1152+ selectionType = "ip" ;
11291153 // bgp.he.net doesn't support dotted IPv6 addresses.
1130- chrome . tabs . create ( { url : `https://bgp.he.net/ip/${ reformatForNAT64 ( text , false ) } ` } ) ;
1131- } else if ( DNS_CHARS . test ( text ) ) {
1132- chrome . tabs . create ( { url : `https://bgp.he.net/dns/${ text } ` } ) ;
1154+ text = reformatForNAT64 ( text , false ) ;
1155+ }
1156+ const provider = options [ LOOKUP_PROVIDER ] ;
1157+ const pattern = ( provider == "custom" ) ?
1158+ options [ `${ CUSTOM_PROVIDER } ${ selectionType } ` ] :
1159+ LOOKUP_PROVIDERS [ provider ] ?. [ selectionType ] ;
1160+ const url = maybeLookupUrl ( pattern , text ) ?. href ;
1161+ if ( url ) {
1162+ chrome . tabs . create ( { url} ) ;
11331163 } else {
11341164 // Malformed selection; shake the popup content.
11351165 const tabId = / # ( \d + ) $ / . exec ( info . pageUrl ) ;
@@ -1158,4 +1188,29 @@ watchOptions(async (optionsChanged) => {
11581188 tabInfo . refreshPageAction ( ) ;
11591189 }
11601190 }
1161- } ) ;
1191+
1192+ if ( optionsChanged . has ( LOOKUP_PROVIDER ) ||
1193+ optionsChanged . has ( CUSTOM_PROVIDER_DOMAIN ) ||
1194+ optionsChanged . has ( CUSTOM_PROVIDER_IP ) ) {
1195+ let providerText = options [ LOOKUP_PROVIDER ] ;
1196+ if ( providerText == "custom" ) {
1197+ // Show something sensible, even when domain/ip use different providers.
1198+ const hostnames = [
1199+ maybeLookupUrl ( options [ CUSTOM_PROVIDER_DOMAIN ] ) ?. hostname ,
1200+ maybeLookupUrl ( options [ CUSTOM_PROVIDER_IP ] ) ?. hostname
1201+ ] . filter ( Boolean ) ;
1202+ providerText = [ ...new Set ( hostnames ) ] . join ( " | " ) ;
1203+ }
1204+ chrome . contextMenus ?. removeAll ( ( ) => {
1205+ if ( providerText ) {
1206+ chrome . contextMenus . create ( {
1207+ title : `Lookup on ${ providerText } ` ,
1208+ id : MENU_ID ,
1209+ // Scope the menu to text selection in our popup windows.
1210+ contexts : [ "selection" ] ,
1211+ documentUrlPatterns : [ chrome . runtime . getURL ( "popup.html" ) ] ,
1212+ } ) ;
1213+ }
1214+ } ) ;
1215+ }
1216+ } ) ;
0 commit comments