2024-11-01 20:55:18 -04:00
import fs from 'fs' ;
import mhtml2html from 'mhtml2html' ;
import { BaseWindow , WebContentsView } from 'electron' ;
import { createWebview } from '../utils/webviewHelpers.js' ;
import { CACHE _DIRECTORY , getLoadPath , saveTabState } from '../utils/clearCache.js' ;
import path from 'path' ;
import { logger } from './imports.js' ;
const webViewContentsMap = { } ; // Memory storage for active tabs
/**
* returns the focused window, if there is no focused window, returns the first one spawned
*/
2024-12-30 14:16:40 +02:00
const getCurrentWindow = ( ) => {
2024-11-01 20:55:18 -04:00
const allWins = BaseWindow . getAllWindows ( ) ,
w = allWins . find ( ( win ) => win . isFocused ( ) ) ;
if ( ! w && allWins . length > 0 ) return allWins ? . at ( 0 ) ;
else return w ;
} ;
/**
* @returns {Electron.CrossProcessExports.WebContentsView | undefined}
*/
const getCurrentTab = ( ) => {
const cw = getCurrentWindow ( ) ;
return cw ? . currentView ;
}
2024-12-30 14:16:40 +02:00
const settabqual = ( tabId ) => {
const t = ( tabId ) ? getCurrentWindow ( ) ? . contentView . children . find ( v => v . id === tabId ) : getCurrentTab ( ) ;
const r = t . webContents . executeJavaScript ( 'setQuality()' ) . catch ( err => logger . warn ( ` setting quality for window ${ tabId } failed with reason: \` \` \` ${ err } \` \` \` ` ) ) ;
return r ;
}
2024-11-01 20:55:18 -04:00
/**
2024-12-30 14:16:40 +02:00
* Switch to the specified view by its ID
2024-11-01 20:55:18 -04:00
* @param {string | Electron.WebContentsView} tabId
*/
async function switchToView ( tabId ) {
const currentWindow = getCurrentWindow ( ) ;
/** @type {WebContentsView} */
const viewData = ( tabId instanceof WebContentsView ) ? tabId : webViewContentsMap [ tabId ] ;
if ( ! viewData || ! currentWindow ) return ;
else if ( viewData . id < 0 ) return ; // Don't modify views with negative IDs
viewData . webContents . setBackgroundThrottling ( false ) ;
const id = tabId . id || tabId ;
// Save the current active view state
// find the non-background-playing window
/** @type {WebContentsView} */
const oldView = currentWindow . contentView . children . find ( ( view ) => view instanceof WebContentsView && ( view . id >= 0 && ! view . webContents . isCurrentlyAudible ( ) ) ) ;
// undo the optimizations
const undoOptimize = async ( ) => {
await viewData . webContents . executeJavaScript ( 'revertQuality()' ) ;
viewData . setVisible ( true ) ;
}
if ( oldView ) {
// REMOVEME
// currentWindow.contentView.removeChildView(oldView);
console . log ( ` saving ` , JSON . stringify ( id ) ) ;
// DO NOT AWAIT THIS CALL FFS, INEFFICIENT!!!
saveTabState ( oldView . id , oldView ) . then ( ( ) => console . log ( ` saved ${ id } ` ) ) ;
currentWindow . contentView . removeChildView ( oldView ) ;
if ( viewData . webContents . isCurrentlyAudible ( ) ) return undoOptimize ( ) ;
}
else if ( viewData ? . webContents ? . isCurrentlyAudible ( ) ) return undoOptimize ( ) ;
// Set the new view as active and add it to the window
// viewData.webContents.setBackgroundThrottling(true);
currentWindow . contentView . addChildView ( viewData ) ;
2024-12-30 14:16:40 +02:00
// await viewData.webContents.loadURL('https://start.duckduckgo.com');
2024-11-01 20:55:18 -04:00
currentWindow . contentView . children . map ( c => c . setVisible ( ( c . id === id ) || c . id < 0 ) ) ;
currentWindow . contentView . children . map ( c => console . log ( c . id , c . webContents . isCurrentlyAudible ( ) ) ) ;
2024-12-30 14:16:40 +02:00
settabqual ( id ) ;
2024-11-01 20:55:18 -04:00
}
/**
* moves the tab to the "background" and tries to minimize the footprint
* @param {Electron.BaseWindow} currentWindow
* @param {String} tabId
* @param {Electron.WebContentsView} oldView
*/
async function shiftTabToBK ( currentWindow , tabId , oldView , customSession ) {
try {
webViewContentsMap [ oldView . id ] = oldView ;
// this is currently playing stuff, keep it open
oldView . webContents . setBackgroundThrottling ( false ) ;
oldView . setVisible ( false ) ;
// TODO: optimize the page more
settabqual ( tabId ) ;
2024-12-30 14:16:40 +02:00
2024-11-01 20:55:18 -04:00
const newView = await createWebview ( tabId , currentWindow , customSession ) ;
webViewContentsMap [ tabId ] = newView ;
switchToView ( newView ) ;
}
catch ( err ) {
console . error ( err ) ;
return null ;
}
}
/**
* Add a new tab with caching.
* @param {string} tabId
* @param {Electron.Session} customSession
* @param {string} [url]
*/
async function addTab ( event , tabId , customSession , url = 'https://duckduckgo.com' , isOpen = false ) {
2024-12-30 14:16:40 +02:00
console . log ( 'opening' , url , typeof url ) ;
2024-11-01 20:55:18 -04:00
const currentWindow = getCurrentWindow ( ) ;
const tabPath = getLoadPath ( tabId ) ,
currentTab = getCurrentTab ( ) ;
if ( currentTab ? . webContents ? . isCurrentlyAudible ( ) && ! isOpen ) return shiftTabToBK ( currentWindow , tabId , currentTab , customSession ) ;
else if ( webViewContentsMap [ tabId ] ? . webContents . isCurrentlyAudible ( ) ) return switchToView ( tabId ) ;
const newView = await createWebview ( tabId , currentWindow , customSession ) ;
webViewContentsMap [ tabId ] = newView ;
if ( tabPath && fs . existsSync ( tabPath ) ) newView . webContents . loadFile ( tabPath ) ;
else newView . webContents . loadURL ( url ) ;
2024-12-30 14:16:40 +02:00
switchToView ( newView , url ) ;
2024-11-01 20:55:18 -04:00
}
/**
* Close a tab and save its state to disk.
* @param {string} tabId
*/
2024-12-30 14:16:40 +02:00
async function closeTab ( _ , tabId ) {
2024-11-01 20:55:18 -04:00
const currentWindow = getCurrentWindow ( ) ;
const view = webViewContentsMap [ tabId ] ;
2024-12-30 14:16:40 +02:00
console . log ( tabId ) ;
2024-11-01 20:55:18 -04:00
if ( view && view . id >= 0 ) {
await saveTabState ( tabId , view . webContents ) ;
currentWindow . contentView . removeChildView ( view ) ;
view . webContents . destroy ( ) ;
delete webViewContentsMap [ tabId ] ;
}
}
/**
* Open an existing tab by restoring its cached state.
* @param {string} tabId
*/
function openTab ( event , tabId , customSession ) {
addTab ( event , tabId , customSession , undefined , true ) ; // Reopen the tab by calling addTab with its ID
}
function organizeTabIds ( ) {
const tabs = fs . readdirSync ( CACHE _DIRECTORY , { withFileTypes : true } )
. filter ( o => ( o . isFile ( ) && o . name . endsWith ( '.html' ) ) )
. map ( o => o . name ) ;
const tmpcachepath = 'cache/tmp/tabs' ;
fs . mkdirSync ( tmpcachepath , { recursive : true } ) ;
for ( let i = 0 ; i < tabs . length ; i ++ ) {
fs . cpSync ( ` ${ CACHE _DIRECTORY } / ${ tabs [ i ] } ` , ` ${ tmpcachepath } / ${ i } .html ` ) ;
}
fs . rmSync ( CACHE _DIRECTORY , { recursive : true } ) ;
fs . cpSync ( tmpcachepath , CACHE _DIRECTORY , { recursive : true } ) ;
fs . rmSync ( tmpcachepath , { recursive : true } ) ;
}
2024-12-30 14:16:40 +02:00
async function addTabExternal ( url = 'https://start.duckduckgo.com' ) {
const tabview = getCurrentWindow ( ) ? . contentView . children . find ( o => ( o . id === - 1 ) ) ;
await tabview ? . webContents . executeJavaScript ( ` sessionStorage.setItem('templock', " ${ url } ") ` ) ;
await tabview ? . webContents . executeJavaScript ( "document.querySelector('#addtabbtn')?.click()" ) ;
await tabview ? . webContents . executeJavaScript ( ` sessionStorage.removeItem('templock') ` ) ;
}
export { closeTab , addTab , openTab , getCurrentWindow , getCurrentTab , organizeTabIds , addTabExternal } ;