From a86305eb4187bd00d7406c6c5d595e814b31ef02 Mon Sep 17 00:00:00 2001 From: Drake <53356436+Ruthenic@users.noreply.github.com> Date: Tue, 3 Jan 2023 16:40:59 -0800 Subject: [PATCH] bump Deno branch to 0.9.0 (#27) * cdp: add internal close api * inject: add Window.close() api * typedef: add Window.close() * roadmap: add close api for 0.8.0 * release: 0.8.0 * readme: update project age * paths: add more linux browsers * readme: update screenshot * readme: move trying gluon to earlier up * inject: add useSessionId to Window.cdp.send() * typedef: add useSessionId to CDPApi.send() * idle: rewrite to use CDP to get processes instead of exec * index: remove now unneeded parameters for idle creation * chore: bump version to 0.9.0-alpha.0 * inject: pass IPC browser engine * api: add Window.versions * typedef: add future BrowserEngine and update Browser values * typedef: add Window.versions * index: rename internal variable * idle: v2 - added sleep, utils, documenting, and more * typedef: update for IdleApi v2 * roadmap: update * chore: bump version to 0.9.0-alpha.2 * readme: add more feature specific statuses * readme: make specific feature statuses into a table * cdp: return protocol errors * idle: move to new api dir * controls: new Window.controls API * darwin: full support (#4) * meta: add pnpm-lock.yaml to .gitignore for my own sanity * darwin: preliminary support * chore: disable menubar key in firefox * Add support new browsers Mac OS (#20) Co-authored-by: a.artamonov * roadmap: tweak * roadmap: update done * roadmap: remove overall * typedef: change some voids to Promises * typedef: add Window.controls * release: 0.9.0 Co-authored-by: CanadaHonk Co-authored-by: Beef Co-authored-by: Alexander Artamonov <47431914+artamonovtech@users.noreply.github.com> Co-authored-by: a.artamonov --- .gitignore | 3 + README.md | 4 +- changelog.md | 13 ++++ gluon.d.ts | 77 +++++++++++++++++-- package.json | 2 +- roadmap.md | 25 +++---- src/api/controls.js | 20 +++++ src/api/idle.js | 166 +++++++++++++++++++++++++++++++++++++++++ src/browser/firefox.js | 2 + src/index.js | 30 ++++++-- src/launcher/inject.js | 29 ++++++- src/launcher/start.js | 2 +- src/lib/cdp.js | 25 ++++++- src/lib/idle.js | 113 ---------------------------- src/lib/ipc.js | 4 +- 15 files changed, 362 insertions(+), 153 deletions(-) create mode 100644 src/api/controls.js create mode 100644 src/api/idle.js delete mode 100644 src/lib/idle.js diff --git a/.gitignore b/.gitignore index 59e94ec..1305422 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ node_modules package-lock.json +# pnpm +pnpm-lock.yaml + # gluon build chrome_data \ No newline at end of file diff --git a/README.md b/README.md index adea492..296ba76 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ - **Chromium *and Firefox* supported as browser engine**, unlike any other active framework - **Minimal and easy to use** - Gluon has a simple yet powerful API to make apps with a Deno backend - **Fast build times** - Gluon has build times under 1 second on most machines for small projects -- **Actively developed** and **listening to feedback** - already on 5th major revision in ~1 week of development, quickly adding unplanned requested features if liked by the community (like Firefox support!) +- **Actively developed** and **listening to feedback** - new updates are coming around weekly, quickly adding unplanned requested features if liked by the community (like Firefox support!) - **Lower memory usage** - compared to most other frameworks Gluon should have a slightly lower average memory usage by using browser flags to squeeze out more performance @@ -106,4 +106,4 @@ Basic (plain HTML) Hello World demo, measured on up to date Windows 10, on my ma [^3]: Includes Node.JS spinup time. [^4]: Built for win32 zip (not Squirrel) as a fairer comparison. [^5]: Cold build (includes deps compiling) in release mode. -[^6]: Using `neu build -r`. +[^6]: Using `neu build -r`. \ No newline at end of file diff --git a/changelog.md b/changelog.md index 7aa0688..82cab0c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,18 @@ # Gluon Changelog +## v0.9.0 [2023-01-03] +- New `Window.versions` API with browser version info +- New `Window.controls` API to manage window state (minimize/maximize/etc) +- New additions and improvements to `Window.idle`: + - `Window.idle.sleep()` now performs a light version of hibernation + - Now uses CDP commands instead of native to detect processes +- Added new `useSessionId` option to `Window.cdp.send()`, allowing to send browser-level CDP commands instead of just to target +- Added initial Mac support + +## v0.8.0 [2022-12-30] +- Rewrote browser detection to support more setups +- Added `Window.close()` API to close Gluon windows gracefully + ## 0.7.0 [2022-12-20] - Added typedef - Added async IPC listener support diff --git a/gluon.d.ts b/gluon.d.ts index dcee202..4c48abe 100644 --- a/gluon.d.ts +++ b/gluon.d.ts @@ -48,7 +48,10 @@ type CDPApi = { method: string, /** Parameters of CDP command. */ - params?: Object + params?: Object, + + /** Send session ID with the command (default true). */ + useSessionId?: Boolean = true ): Promise }; @@ -62,16 +65,15 @@ type IdleAutoOptions = { type IdleApi = { /** Put the window into hibernation. */ - hibernate(): void, + hibernate(): Promise, /** * Put the window to sleep. - * @todo Unimplemented (for Idle v2). */ - sleep(): void, + sleep(): Promise, /** Wake up the window from hibernation or sleep. */ - wake(): void, + wake(): Promise, /** Enable/disable automatic idle management, and set its options. */ auto( @@ -83,6 +85,54 @@ type IdleApi = { ): void }; +type VersionInfo = { + /** Name of component. */ + name: string, + + /** Full version of component. */ + version: string, + + /** Major version of component as a number. */ + major: number +}; + +type BrowserVersions = { + /** + * Product (browser) version and name. + * @example + * Window.versions.product // { name: 'Chrome Canary', version: '111.0.5513.0', major: 111 } + */ + product: VersionInfo, + + /** + * Browser engine (Chromium/Firefox) version and name. + * @example + * Window.versions.engine // { name: 'chromium', version: '111.0.5513.0', major: 111 } + */ + engine: VersionInfo, + + /** + * JS engine (V8/SpiderMonkey) version and name. + * @example + * Window.versions.jsEngine // { name: 'v8', version: '11.1.86', major: 11 } + */ + jsEngine: VersionInfo +}; + +type ControlsApi = { + /** Minimize the browser window. */ + minimize(): Promise, + + /** + * Maximize the browser window. + * Doesn't make the window appear (use show() before as well). + */ + maximize(): Promise, + + /** Show (unminimize) the browser window. */ + show(): Promise +} + type Window = { /** API for accessing the window itself. */ window: WindowApi, @@ -97,12 +147,25 @@ type Window = { * API for Gluon idle management (like hibernation). * @experimental */ - idle: IdleApi + idle: IdleApi, + + /** Browser version info of the window: product (browser), engine (Chromium/Firefox), and JS engine (V8/SpiderMonkey). */ + versions: BrowserVersions, + + /** Control (minimize, maximize, etc) the browser window. */ + controls: ControlsApi, + + /** Close the Gluon window. */ + close(): void }; /** A browser that Gluon supports. */ -type Browser = 'chrome'|'chrome_canary'|'chromium'|'edge'|'firefox'|'firefox_nightly'; +type Browser = 'chrome'|'chrome_canary'|'chromium'|'chromium_snapshot'|'edge'|'firefox'|'firefox_nightly'; + +/** A browser engine that Gluon supports. */ +type BrowserEngine = 'chromium'|'firefox'; + /** Additional options for opening */ type OpenOptions = { diff --git a/package.json b/package.json index 09a714e..21c7bff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gluon-framework/gluon", - "version": "0.8.0-dev", + "version": "0.9.0", "description": "Make websites into desktop apps with system installed browsers and NodeJS.", "main": "src/index.js", "types": "gluon.d.ts", diff --git a/roadmap.md b/roadmap.md index 29fb344..fc50bca 100644 --- a/roadmap.md +++ b/roadmap.md @@ -3,20 +3,19 @@ > **Note** | > Want more info on what some of these mean/are? Ask in [our Discord](https://discord.gg/RFtUCA8fST)! -## v0.8.0 -- [X] Rewrite browser detection to support more setups +## v0.10.0 - [ ] Resources Window API - [ ] On/await load Window API -## v0.7.0 -- [X] Early Idle Window API +## v0.9.0 +- [X] Browser version info API +- [X] Idle API v2 +- [X] Mac support +- [X] Window controls API -## Overall - December 2022 - January 2023 -- [X] Total internal rewrite -- [X] Type definitions -- [X] "Hibernation" feasibility study -- [X] Early hibernation API -- [ ] Automated PR/commit CI testing -- [ ] System Tray API -- [ ] Electron "compatibility layer" for basic/simple apps as a demo of versatile API / pros? -- More? Need to know what's wanted \ No newline at end of file +## v0.8.0 +- [X] Rewrite browser detection to support more setups +- [X] Close API + +## v0.7.0 +- [X] Early Idle Window API \ No newline at end of file diff --git a/src/api/controls.js b/src/api/controls.js new file mode 100644 index 0000000..485aa19 --- /dev/null +++ b/src/api/controls.js @@ -0,0 +1,20 @@ +export default async (CDP) => { + const { windowId } = await CDP.send('Browser.getWindowForTarget'); + + const setWindowState = async state => await CDP.send('Browser.setWindowBounds', { windowId, bounds: { windowState: state }}); + + return { + minimize: async () => { + await setWindowState('minimized'); + }, + + maximize: async () => { + await setWindowState('maximized'); + }, + + show: async () => { + await setWindowState('minimized'); + await setWindowState('normal'); + } + }; +}; \ No newline at end of file diff --git a/src/api/idle.js b/src/api/idle.js new file mode 100644 index 0000000..620c920 --- /dev/null +++ b/src/api/idle.js @@ -0,0 +1,166 @@ +import { exec } from 'https://deno.land/std@0.170.0/node/child_process.ts'; + +const killProcesses = async pids => process.platform !== 'win32' ? Promise.resolve('') : new Promise(resolve => exec(`taskkill /F ${pids.map(x => `/PID ${x}`).join(' ')}`, (e, out) => resolve(out))); + +export default async (CDP, { browserType }) => { + if (browserType !== 'chromium') { // current implementation is for chromium-based only + const warning = () => log(`Warning: Idle API is currently only for Chromium (running on ${browserType})`); + + return { + hibernate: warning, + sleep: warning, + wake: warning, + auto: warning + }; + } + + const killNonCrit = async () => { // kill non-critical processes to save memory - crashes chromium internally but not fully + const procs = (await CDP.send('SystemInfo.getProcessInfo', {}, false)).processInfo; + const nonCriticalProcs = procs.filter(x => x.type !== 'browser'); // browser = the actual main chromium binary + + await killProcesses(nonCriticalProcs.map(x => x.id)); + log(`killed ${nonCriticalProcs.length} processes`); + }; + + const purgeMemory = async () => { // purge most memory we can + await CDP.send('Memory.forciblyPurgeJavaScriptMemory'); + await CDP.send('HeapProfiler.collectGarbage'); + }; + + const getScreenshot = async () => { // get a screenshot a webm base64 data url + const { data } = await CDP.send(`Page.captureScreenshot`, { + format: 'webp' + }); + + return `data:image/webp;base64,${data}`; + }; + + const getLastUrl = async () => { + const history = await CDP.send('Page.getNavigationHistory'); + return history.entries[history.currentIndex].url; + }; + + + let wakeUrl, hibernating = false; + const hibernate = async () => { // hibernate - crashing chromium internally to save max memory. users will see a crash/gone wrong page but we hopefully "reload" quick enough once visible again for not much notice. + if (hibernating) return; + if (process.platform !== 'win32') return sleep(); // sleep instead - full hibernation is windows only for now due to needing to do native things + + hibernating = true; + + const startTime = performance.now(); + + wakeUrl = await getLastUrl(); + + purgeMemory(); + await killNonCrit(); + purgeMemory(); + + log(`hibernated in ${(performance.now() - startTime).toFixed(2)}ms`); + }; + + const sleep = async () => { // light hibernate - instead of killing chromium processes we just navigate to a screenshot of the current page. + if (hibernating) return; + hibernating = true; + + const startTime = performance.now(); + + wakeUrl = await getLastUrl(); + + purgeMemory(); + + await CDP.send(`Page.navigate`, { + url: lastScreenshot + }); + + purgeMemory(); + + log(`slept in ${(performance.now() - startTime).toFixed(2)}ms`); + }; + + + const wake = async () => { // wake up from hibernation/sleep by navigating to the original page + if (!hibernating) return; + + const startTime = performance.now(); + + await CDP.send('Page.navigate', { + url: wakeUrl + }); + + log(`began wake in ${(performance.now() - startTime).toFixed(2)}ms`); + + hibernating = false; + }; + + + const { windowId } = await CDP.send('Browser.getWindowForTarget'); + + let autoEnabled = Deno.args.includes('--force-auto-idle'), autoOptions = { + timeMinimizedToHibernate: 5 + }; + + let autoInterval; + const startAuto = () => { + if (autoInterval) return; // already started + + let lastState = '', lastStateWhen = performance.now(); + autoInterval = setInterval(async () => { + const { bounds: { windowState } } = await CDP.send('Browser.getWindowBounds', { windowId }); + + if (windowState !== lastState) { + lastState = windowState; + lastStateWhen = performance.now(); + } + + if (!hibernating && windowState === 'minimized' && performance.now() - lastStateWhen > autoOptions.timeMinimizedToHibernate * 1000) await hibernate(); + else if (hibernating && windowState !== 'minimized') await wake(); + }, 200); + + log('started auto idle'); + }; + + const stopAuto = () => { + if (!autoInterval) return; // already stopped + + clearInterval(autoInterval); + autoInterval = null; + + log('stopped auto idle'); + }; + + let lastScreenshot, takingScreenshot = false; + const screenshotInterval = setInterval(async () => { + if (takingScreenshot) return; + + takingScreenshot = true; + lastScreenshot = await getScreenshot(); + takingScreenshot = false; + }, 10000); + + getScreenshot().then(x => lastScreenshot = x); + + log(`idle API active (window id: ${windowId})`); + if (autoEnabled) startAuto(); + + const setWindowState = async state => await CDP.send('Browser.setWindowBounds', { windowId, bounds: { windowState: state }}); + + + return { + hibernate, + sleep, + wake, + + auto: (enabled, options) => { + autoEnabled = enabled; + + autoOptions = { + ...options, + ...autoOptions + }; + + if (enabled) startAuto(); + else stopAuto(); + } + }; +}; \ No newline at end of file diff --git a/src/browser/firefox.js b/src/browser/firefox.js index c244eda..70a3d56 100644 --- a/src/browser/firefox.js +++ b/src/browser/firefox.js @@ -18,6 +18,8 @@ user_pref('privacy.window.maxInnerHeight', ${windowSize[1]});`} user_pref('privacy.resistFingerprinting', true); user_pref('fission.bfcacheInParent', false); user_pref('fission.webContentIsolationStrategy', 0); +user_pref('ui.key.menuAccessKeyFocuses', false); +${process.platform === 'darwin' ? `user_pref('browser.tabs.inTitlebar', 0);` : `` } `); // user_pref('privacy.resistFingerprinting', false); diff --git a/src/index.js b/src/index.js index e39e546..4add195 100644 --- a/src/index.js +++ b/src/index.js @@ -3,10 +3,9 @@ window.log = (...args) => console.log(`[${rgb(88, 101, 242, 'Gluon')}]`, ...args Deno.version = { // have to do this because... Deno ...Deno.version, - gluon: '0.8.0-deno-dev' + gluon: '0.9.0-deno-dev' }; - import { join, dirname, delimiter, sep } from 'https://deno.land/std@0.170.0/node/path.ts'; import { access, readdir } from 'https://deno.land/std@0.170.0/node/fs/promises.ts'; import { fileURLToPath } from 'https://deno.land/std@0.170.0/node/url.ts'; @@ -14,7 +13,8 @@ import { fileURLToPath } from 'https://deno.land/std@0.170.0/node/url.ts'; import Chromium from './browser/chromium.js'; import Firefox from './browser/firefox.js'; -import IdleAPI from './lib/idle.js'; +import IdleAPI from './api/idle.js'; +import ControlsAPI from './api/controls.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -32,8 +32,23 @@ const browserPaths = ({ linux: { // these should be in path so just use the name of the binary chrome: [ 'chrome', 'google-chrome', 'chrome-browser', 'google-chrome-stable' ], chrome_canary: [ 'chrome-canary', 'google-chrome-canary', 'google-chrome-unstable', 'chrome-unstable' ], + chromium: [ 'chromium', 'chromium-browser' ], + chromium_snapshot: [ 'chromium-snapshot', 'chromium-snapshot-bin' ], + firefox: 'firefox', + firefox_nightly: 'firefox-nightly' + }, + + darwin: { + chrome: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + chrome_canary: '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary', + edge: '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge', + + chromium: '/Applications/Chromium.app/Contents/MacOS/Chromium', + + firefox: '/Applications/Firefox.app/Contents/MacOS/firefox', + firefox_nightly: '/Applications/Firefox Nightly.app/Contents/MacOS/firefox' } })[Deno.build.os]; @@ -98,7 +113,7 @@ const startBrowser = async (url, { windowSize, forceBrowser }) => { const browserType = browserName.startsWith('firefox') ? 'firefox' : 'chromium'; - const Browser = await (browserType === 'firefox' ? Firefox : Chromium)({ + const Window = await (browserType === 'firefox' ? Firefox : Chromium)({ browserName: browserFriendlyName, dataPath, browserPath @@ -107,9 +122,10 @@ const startBrowser = async (url, { windowSize, forceBrowser }) => { windowSize }); - Browser.idle = await IdleAPI(Browser.cdp, { browserType, dataPath }); + Window.idle = await IdleAPI(Window.cdp, { browserType }); + Window.controls = await ControlsAPI(Window.cdp); - return Browser; + return Window; }; export const open = async (url, { windowSize, onLoad, forceBrowser } = {}) => { @@ -133,4 +149,4 @@ export const open = async (url, { windowSize, onLoad, forceBrowser } = {}) => { } return Browser; -}; \ No newline at end of file +}; diff --git a/src/launcher/inject.js b/src/launcher/inject.js index 047c672..da9020e 100644 --- a/src/launcher/inject.js +++ b/src/launcher/inject.js @@ -1,6 +1,6 @@ import IPCApi from '../lib/ipc.js'; -export default async (CDP, injectionType = 'browser', { browserName }) => { +export default async (CDP, proc, injectionType = 'browser', { browserName } = { browserName: 'unknown' }) => { let pageLoadCallback = () => {}, onWindowMessage = () => {}; CDP.onMessage(msg => { if (msg.method === 'Runtime.bindingCalled' && msg.params.name === '_gluonSend') onWindowMessage(JSON.parse(msg.params.payload)); @@ -27,6 +27,9 @@ export default async (CDP, injectionType = 'browser', { browserName }) => { log('browser:', browserInfo.product); } + const browserEngine = browserInfo.jsVersion.startsWith('1.') ? 'firefox' : 'chromium'; + + CDP.sendMessage('Runtime.enable', {}, sessionId); // enable runtime API CDP.sendMessage('Runtime.addBinding', { // setup sending from window to Node via Binding @@ -42,17 +45,24 @@ export default async (CDP, injectionType = 'browser', { browserName }) => { const [ ipcMessageCallback, injectIPC, IPC ] = await IPCApi({ browserName, - browserInfo + browserInfo, + browserEngine }, { evalInWindow, evalOnNewDocument: source => CDP.sendMessage('Page.addScriptToEvaluateOnNewDocument', { source }, sessionId), pageLoadPromise: new Promise(res => pageLoadCallback = res) }); + onWindowMessage = ipcMessageCallback; - log('finished setup'); + const generateVersionInfo = (name, version) => ({ + name, + version, + major: parseInt(version.split('.')[0]) + }); + return { window: { eval: evalInWindow, @@ -61,7 +71,18 @@ export default async (CDP, injectionType = 'browser', { browserName }) => { ipc: IPC, cdp: { - send: (method, params) => CDP.sendMessage(method, params, sessionId) + send: (method, params, useSessionId = true) => CDP.sendMessage(method, params, useSessionId ? sessionId : undefined) + }, + + close: () => { + CDP.close(); + proc.kill(); + }, + + versions: { + product: generateVersionInfo(browserName, browserInfo.product.split('/')[1]), + engine: generateVersionInfo(browserEngine, browserInfo.product.split('/')[1]), + jsEngine: generateVersionInfo(browserEngine === 'chromium' ? 'v8' : 'spidermonkey', browserInfo.jsVersion) } }; }; \ No newline at end of file diff --git a/src/launcher/start.js b/src/launcher/start.js index 2c79d07..9e32bd7 100644 --- a/src/launcher/start.js +++ b/src/launcher/start.js @@ -30,5 +30,5 @@ export default async (browserPath, args, transport, extra) => { break; } - return await InjectInto(CDP, transport === 'stdio' ? 'browser' : 'target', extra); + return await InjectInto(CDP, proc, transport === 'stdio' ? 'browser' : 'target', extra); }; \ No newline at end of file diff --git a/src/lib/cdp.js b/src/lib/cdp.js index c581799..7894ff4 100644 --- a/src/lib/cdp.js +++ b/src/lib/cdp.js @@ -3,6 +3,8 @@ import { get } from 'https://deno.land/std@0.170.0/node/http.ts'; export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => { let messageCallbacks = [], onReply = {}; const onMessage = msg => { + if (closed) return; // closed, ignore + msg = JSON.parse(msg); // log('received', msg); @@ -16,10 +18,13 @@ export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => { for (const callback of messageCallbacks) callback(msg); }; - let _send; + let closed = false; + let _send, _close; let msgId = 0; const sendMessage = async (method, params = {}, sessionId = undefined) => { + if (closed) throw new Error('CDP connection closed'); + const id = msgId++; const msg = { @@ -38,6 +43,8 @@ export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => { onReply[id] = msg => res(msg); }); + if (reply.error) return new Error(reply.error.message); + return reply.result; }; @@ -79,11 +86,15 @@ export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => { const ws = new WebSocket(target.webSocketDebuggerUrl); await new Promise(resolve => ws.onopen = resolve); - _send = data => ws.send(data); ws.onmessage = ({ data }) => onMessage(data); + + _send = data => ws.send(data); + _close = () => ws.close(); } else { let pending = ''; pipeRead.on('data', buf => { + if (closed) return; // closed, ignore + let end = buf.indexOf('\0'); // messages are null separated if (end === -1) { // no complete message yet @@ -110,6 +121,8 @@ export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => { pipeWrite.write(data); pipeWrite.write('\0'); }; + + _close = () => {}; } return { @@ -121,6 +134,12 @@ export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => { messageCallbacks.push(callback); }, - sendMessage + + sendMessage, + + close: () => { + closed = true; + _close(); + } }; }; \ No newline at end of file diff --git a/src/lib/idle.js b/src/lib/idle.js deleted file mode 100644 index 90d286e..0000000 --- a/src/lib/idle.js +++ /dev/null @@ -1,113 +0,0 @@ -import { exec } from 'https://deno.land/std@0.170.0/node/child_process.ts'; - -const getProcesses = async containing => Deno.build.os !== 'windows' ? Promise.resolve([]) : new Promise(resolve => exec(`wmic process get Commandline,ProcessID /format:csv`, (e, out) => { - resolve(out.toString().split('\r\n').slice(2).map(x => { - const parsed = x.trim().split(',').slice(1).reverse(); - - return [ - parseInt(parsed[0]) || parsed[0], // pid to int - parsed.slice(1).join(',') - ]; - }).filter(x => x[1] && x[1].includes(containing))); -})); - -const killProcesses = async pids => Deno.build.os !== 'windows' ? Promise.resolve('') : new Promise(resolve => exec(`taskkill /F ${pids.map(x => `/PID ${x}`).join(' ')}`, (e, out) => resolve(out))); - -export default async (CDP, { browserType, dataPath }) => { - if (browserType !== 'chromium') { // current implementation is for chromium-based only - const warning = () => log(`Warning: Idle API is currently only for Chromium (running on ${browserType})`); - - return { - hibernate: warning, - sleep: warning, - wake: warning, - auto: warning - }; - }; - - const killNonCrit = async () => { - const procs = await getProcesses(dataPath); - const nonCriticalProcs = procs.filter(x => x[1].includes('type=')); - - await killProcesses(nonCriticalProcs.map(x => x[0])); - log(`killed ${nonCriticalProcs.length} processes`); - }; - - let hibernating = false; - const hibernate = async () => { - hibernating = true; - - const startTime = performance.now(); - - await killNonCrit(); - // await killNonCrit(); - - log(`hibernated in ${(performance.now() - startTime).toFixed(2)}ms`); - }; - - const wake = async () => { - const startTime = performance.now(); - - await CDP.send('Page.reload'); - - log(`began wake in ${(performance.now() - startTime).toFixed(2)}ms`); - - hibernating = false; - }; - - const { windowId } = await CDP.send('Browser.getWindowForTarget'); - - let autoEnabled = Deno.args.includes('--force-auto-idle'), autoOptions = { - timeMinimizedToHibernate: 5 - }; - - let autoInterval; - const startAuto = () => { - if (autoInterval) return; // already started - - let lastState = '', lastStateWhen = performance.now(); - autoInterval = setInterval(async () => { - const { bounds: { windowState } } = await CDP.send('Browser.getWindowBounds', { windowId }); - - if (windowState !== lastState) { - lastState = windowState; - lastStateWhen = performance.now(); - } - - if (!hibernating && windowState === 'minimized' && performance.now() - lastStateWhen > autoOptions.timeMinimizedToHibernate * 1000) await hibernate(); - else if (hibernating && windowState !== 'minimized') await wake(); - }, 200); - - log('started auto idle'); - }; - - const stopAuto = () => { - if (!autoInterval) return; // already stopped - - clearInterval(autoInterval); - autoInterval = null; - - log('stopped auto idle'); - }; - - log(`idle API active (window id: ${windowId})`); - if (autoEnabled) startAuto(); - - return { - hibernate, - sleep: () => {}, - wake, - - auto: (enabled, options) => { - autoEnabled = enabled; - - autoOptions = { - ...options, - ...autoOptions - }; - - if (enabled) startAuto(); - else stopAuto(); - } - }; -}; \ No newline at end of file diff --git a/src/lib/ipc.js b/src/lib/ipc.js index 6139111..d96a35a 100644 --- a/src/lib/ipc.js +++ b/src/lib/ipc.js @@ -1,4 +1,4 @@ -export default ({ browserName, browserInfo }, { evalInWindow, evalOnNewDocument, pageLoadPromise }) => { +export default ({ browserName, browserInfo, browserEngine }, { evalInWindow, evalOnNewDocument, pageLoadPromise }) => { const injection = `(() => { if (window.Gluon) return; let onIPCReply = {}, ipcListeners = {}; @@ -8,7 +8,7 @@ window.Gluon = { builder: '${'GLUGUN_VERSION' === 'G\LUGUN_VERSION' ? 'nothing' : 'Glugun GLUGUN_VERSION'}', deno: '${Deno.version.deno}', browser: '${browserInfo?.product.split('/')[1]}', - browserType: '${browserName.startsWith('Firefox') ? 'firefox' : 'chromium'}', + browserType: '${browserEngine}', product: '${browserName}', js: {