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 <a.artamonov@sftpro.ru> * roadmap: tweak * roadmap: update done * roadmap: remove overall * typedef: change some voids to Promise<void>s * typedef: add Window.controls * release: 0.9.0 Co-authored-by: CanadaHonk <oj@oojmed.com> Co-authored-by: Beef <beefers@riseup.net> Co-authored-by: Alexander Artamonov <47431914+artamonovtech@users.noreply.github.com> Co-authored-by: a.artamonov <a.artamonov@sftpro.ru>
This commit is contained in:
parent
11ef8cc79b
commit
a86305eb41
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,6 +2,9 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
|
||||
# pnpm
|
||||
pnpm-lock.yaml
|
||||
|
||||
# gluon
|
||||
build
|
||||
chrome_data
|
@ -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
|
||||
<!-- - **No forks needed** - Gluon doesn't need forks of Deno or Chromium/etc to use them, it just uses normal versions -->
|
||||
|
||||
@ -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`.
|
13
changelog.md
13
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
|
||||
|
77
gluon.d.ts
vendored
77
gluon.d.ts
vendored
@ -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<any>
|
||||
};
|
||||
|
||||
@ -62,16 +65,15 @@ type IdleAutoOptions = {
|
||||
|
||||
type IdleApi = {
|
||||
/** Put the window into hibernation. */
|
||||
hibernate(): void,
|
||||
hibernate(): Promise<void>,
|
||||
|
||||
/**
|
||||
* Put the window to sleep.
|
||||
* @todo Unimplemented (for Idle v2).
|
||||
*/
|
||||
sleep(): void,
|
||||
sleep(): Promise<void>,
|
||||
|
||||
/** Wake up the window from hibernation or sleep. */
|
||||
wake(): void,
|
||||
wake(): Promise<void>,
|
||||
|
||||
/** 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<void>,
|
||||
|
||||
/**
|
||||
* Maximize the browser window.
|
||||
* Doesn't make the window appear (use show() before as well).
|
||||
*/
|
||||
maximize(): Promise<void>,
|
||||
|
||||
/** Show (unminimize) the browser window. */
|
||||
show(): Promise<void>
|
||||
}
|
||||
|
||||
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 = {
|
||||
|
@ -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",
|
||||
|
25
roadmap.md
25
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
|
||||
## v0.8.0
|
||||
- [X] Rewrite browser detection to support more setups
|
||||
- [X] Close API
|
||||
|
||||
## v0.7.0
|
||||
- [X] Early Idle Window API
|
20
src/api/controls.js
vendored
Normal file
20
src/api/controls.js
vendored
Normal file
@ -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');
|
||||
}
|
||||
};
|
||||
};
|
166
src/api/idle.js
Normal file
166
src/api/idle.js
Normal file
@ -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();
|
||||
}
|
||||
};
|
||||
};
|
@ -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);
|
||||
|
30
src/index.js
30
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;
|
||||
};
|
||||
};
|
||||
|
@ -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)
|
||||
}
|
||||
};
|
||||
};
|
@ -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);
|
||||
};
|
@ -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();
|
||||
}
|
||||
};
|
||||
};
|
113
src/lib/idle.js
113
src/lib/idle.js
@ -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();
|
||||
}
|
||||
};
|
||||
};
|
@ -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: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user