Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions apps/boss/assets/entitlements.mac.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<!-- https://github.com/electron/electron-notarize#prerequisites -->
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<!-- https://github.com/electron-userland/electron-builder/issues/3940 -->
<key>com.apple.security.cs.disable-library-validation</key>
</dict>
</plist>
16 changes: 8 additions & 8 deletions apps/boss/lib/Menubar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,16 @@ export class Menubar extends EventEmitter {
}

private hideWindow(): void {
if (!this.#browserWindow || !this.#isVisible) {
return;
}
(this.#nsEventMonitor as any)?.stop();

this.#browserWindow.hide();
this.#isVisible = false;
if (this.#blurTimeout) {
clearTimeout(this.#blurTimeout);
this.#blurTimeout = null;
}
(this.#nsEventMonitor as any)?.stop();

if (this.#browserWindow?.isVisible()) {
this.#browserWindow.hide();
}
this.#isVisible = false;
}

private async showWindow(trayPos?: Electron.Rectangle): Promise<void> {
Expand Down Expand Up @@ -134,9 +133,10 @@ export class Menubar extends EventEmitter {
console.warn('Quitting Ulixee Menubar');
e.preventDefault();
await this.stopServer();
this.hideWindow();
app.exit();
});
ChromeAliveCore.register();
ChromeAliveCore.register(true);
// for now auto-start
this.startServer().catch(console.error);
ShutdownHandler.exitOnSignal = false;
Expand Down
4 changes: 3 additions & 1 deletion apps/boss/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@
"extendInfo": {
"LSUIElement": 1
},
"target": "dmg"
"target": "dmg",
"entitlements": "assets/entitlements.mac.plist",
"entitlementsInherit": "assets/entitlements.mac.plist"
},
"dmg": {
"sign": false
Expand Down
18 changes: 17 additions & 1 deletion apps/chromealive-core/hero-plugins/WindowBoundsCorePlugin.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import { IBounds } from '@ulixee/apps-chromealive-interfaces/apis/IAppBoundsChangedApi';
import CorePlugin from '@ulixee/hero-plugin-utils/lib/CorePlugin';
import { IPuppetPage } from '@ulixee/hero-interfaces/IPuppetPage';
import { ISessionSummary } from '@ulixee/hero-interfaces/ICorePlugin';
import { IBrowserEmulatorConfig, ISessionSummary } from '@ulixee/hero-interfaces/ICorePlugin';
import { waitForChromeExtension } from '../lib/activateChromeExtension';
import AliveBarPositioner from '../lib/AliveBarPositioner';

export default class WindowBoundsCorePlugin extends CorePlugin {
public static id = '@ulixee/window-bounds-core-plugin';

configure(options: IBrowserEmulatorConfig): Promise<any> | void {
if ((options.viewport as any)?.isDefault) {
const maxChromeBounds = AliveBarPositioner.getMaxChromeBounds();
Object.assign(options.viewport, {
width: 0,
height: 0,
deviceScaleFactor: 0,
positionX: maxChromeBounds?.left,
positionY: maxChromeBounds?.top,
screenWidth: maxChromeBounds?.width,
screenHeight: maxChromeBounds?.height,
mobile: undefined,
});
}
}

onNewPuppetPage(page: IPuppetPage, sessionSummary: ISessionSummary): Promise<any> {
if (!sessionSummary.options.showBrowser) return;
return Promise.all([
Expand Down
55 changes: 45 additions & 10 deletions apps/chromealive-core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ import { ChildProcess } from 'child_process';
import launchChromeAlive from '@ulixee/apps-chromealive/index';
import type Puppet from '@ulixee/hero-puppet';
import IDevtoolsSession from '@ulixee/hero-interfaces/IDevtoolsSession';
import { bindFunctions } from '@ulixee/commons/lib/utils';
import * as util from 'util';
import FocusedWindowCorePlugin from './hero-plugins/FocusedWindowCorePlugin';
import WindowBoundsCorePlugin from './hero-plugins/WindowBoundsCorePlugin';
import SessionObserver from './lib/SessionObserver';
import ConnectionToClient from './lib/ConnectionToClient';
import activateChromeExtension from './lib/activateChromeExtension';
import AliveBarPositioner from './lib/AliveBarPositioner';

util.inspect.defaultOptions.depth = 10;

const debug = Debug('ulixee:chromealive');

export default class ChromeAliveCore {
Expand All @@ -24,6 +28,9 @@ export default class ChromeAliveCore {

public static setCoreServerAddress(address: Promise<string>) {
this.coreServerAddress = address;
this.launchApp().catch(err => {
console.error('Cannot launch ChromeAlive app', err);
});
}

public static getConnection(): ConnectionToClient {
Expand All @@ -37,6 +44,7 @@ export default class ChromeAliveCore {
HeroGlobalPool.events.off('browser-launched', this.launchApp);
HeroGlobalPool.events.off('all-browsers-closed', this.closeApp);
HeroGlobalPool.events.off('session-created', this.onHeroSessionCreated);
HeroGlobalPool.events.off('browser-has-no-open-windows', this.onBrowserHasNoWindows);
FocusedWindowCorePlugin.onVisibilityChange = null;
AliveBarPositioner.getSessionDevtools = null;
this.getConnection().close();
Expand All @@ -52,17 +60,17 @@ export default class ChromeAliveCore {
this.shouldAutoShowBrowser = true;
}

this.launchApp = this.launchApp.bind(this);
this.closeApp = this.closeApp.bind(this);
this.onHeroSessionCreated = this.onHeroSessionCreated.bind(this);
bindFunctions(this);

const connection = this.getConnection();
connection.on('connected', this.onWsConnected.bind(this));
HeroGlobalPool.events.on('browser-launched', this.launchApp);
connection.on('connected', this.onWsConnected);
HeroGlobalPool.events.on('browser-launched', this.onNewBrowser);
HeroGlobalPool.events.on('all-browsers-closed', this.closeApp);
HeroGlobalPool.events.on('session-created', this.onHeroSessionCreated);
HeroGlobalPool.events.on('browser-has-no-open-windows', this.onBrowserHasNoWindows);

FocusedWindowCorePlugin.onVisibilityChange = this.changeActiveSessions.bind(this);
AliveBarPositioner.getSessionDevtools = this.getSessionDevtools.bind(this);
FocusedWindowCorePlugin.onVisibilityChange = this.changeActiveSessions;
AliveBarPositioner.getSessionDevtools = this.getSessionDevtools;

HeroCore.use(FocusedWindowCorePlugin);
HeroCore.use(WindowBoundsCorePlugin);
Expand All @@ -73,6 +81,7 @@ export default class ChromeAliveCore {
if (this.shouldAutoShowBrowser) {
heroSession.options.showBrowser = true;
heroSession.options.showBrowserInteractions = true;
heroSession.options.viewport ??= { width: 0, height: 0 };
}

// if not auto-registered, check if browser is showing
Expand All @@ -88,7 +97,10 @@ export default class ChromeAliveCore {
this.sessionObserversById.set(heroSession.id, sessionObserver);
sessionObserver.on('session:updated', this.sendActiveSession.bind(this, heroSession.id));
sessionObserver.on('output:updated', this.sendOutput.bind(this, heroSession.id));
this.activeHeroSessionId ??= heroSession.id;
if (!this.activeHeroSessionId) {
this.sendEvent('App.show');
this.activeHeroSessionId = heroSession.id;
}
this.sendActiveSession(heroSession.id);
}

Expand Down Expand Up @@ -131,17 +143,39 @@ export default class ChromeAliveCore {
return page.devtoolsSession;
}

private static async launchApp(event: { puppet: Puppet }): Promise<void> {
const args: string[] = [];
private static async launchApp(): Promise<void> {
if (this.app && !this.app.killed) return;

const args: string[] = ['--hide'];
if (this.coreServerAddress) {
args.push(`--coreServerAddress=${await this.coreServerAddress}`);
}

this.app = launchChromeAlive(...args);
this.app.once('exit', () => (this.app = null));
this.app.once('close', () => (this.app = null));
debug('Launched Electron App', {
file: this.app?.spawnfile,
args: this.app?.spawnargs,
});
}

private static onBrowserHasNoWindows(event: { puppet: Puppet }) {
const browserId = event.puppet.browserId;
setTimeout(
(p: Puppet) => {
const sessionsUsingEngine = HeroSession.sessionsWithBrowserId(browserId);
const hasWindows = sessionsUsingEngine.some(x => x.tabsById.size > 0);
if (!hasWindows) {
return event.puppet.close();
}
},
2e3,
event.puppet,
).unref();
}

private static async onNewBrowser(event: { puppet: Puppet }): Promise<void> {
await activateChromeExtension(event.puppet);
}

Expand All @@ -165,6 +199,7 @@ export default class ChromeAliveCore {
eventType: T,
data: IChromeAliveEvents[T] = null,
) {
console.log('SendEvent', { eventType, data });
this.getConnection().sendEvent({ eventType, data });
}
}
29 changes: 22 additions & 7 deletions apps/chromealive-core/lib/AliveBarPositioner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,27 @@ const debug = Debug('ulixee:chromealive');
export default class AliveBarPositioner {
public static getSessionDevtools: (sessionId: string) => IDevtoolsSession;

private static lastToolbarBounds: IBounds;
private static workarea: IBounds;
private static lastToolbarBounds: IBounds;
private static isFirstAdjustment = true;

private static lastWindowBoundsBySessionId: {
[sessionId: string]: IBounds & { windowId: number };
} = {};

public static getMaxChromeBounds(): IBounds | null {
if (!this.workarea || !this.lastToolbarBounds) return null;

const { top, height } = this.lastToolbarBounds;
const toolbarBottom = top + height + 1;
return {
top: toolbarBottom,
left: this.lastToolbarBounds.left,
width: this.lastToolbarBounds.width,
height: this.workarea.height - this.lastToolbarBounds.height,
};
}

public static onChromeWindowBoundsChanged(
sessionId: string,
windowId: number,
Expand All @@ -39,19 +53,20 @@ export default class AliveBarPositioner {
if (!this.lastToolbarBounds) return;

const chromeBounds = this.lastWindowBoundsBySessionId[sessionId];
const { top, height } = this.lastToolbarBounds;
const toolbarBottom = top + height + 2;
let newBounds = this.getMaxChromeBounds();

if (chromeBounds.top < toolbarBottom) {
if (chromeBounds.top < newBounds.top) {
const devtools = this.getSessionDevtools(sessionId);
if (!devtools) return;

if (!this.isFirstAdjustment) {
newBounds = { top: newBounds.top } as any;
}
this.isFirstAdjustment = false;
devtools
.send('Browser.setWindowBounds', {
windowId: chromeBounds.windowId,
bounds: {
top: toolbarBottom,
},
bounds: newBounds,
})
.catch(() => null);
}
Expand Down
8 changes: 6 additions & 2 deletions apps/chromealive-core/lib/SessionObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ export default class SessionObserver extends TypedEventEmitter<{
const runCommands = this.heroSession.sessionState.commands.filter(
x => x.run === this.heroSession.resumeCounter,
);
const thisRunUrls = new Set<string>(runCommands.map(x => x.url));
const thisRunUrls = new Set<string>();
for (const command of runCommands) {
thisRunUrls.add(command.url);
if (command.result?.url) thisRunUrls.add(command.result.url);
}
const runStart = runCommands[0]?.runStartDate;

const loadedUrls = this.loadedUrls.filter(
Expand Down Expand Up @@ -170,7 +174,7 @@ export default class SessionObserver extends TypedEventEmitter<{
// update url in case it changed
sessionUrl.url = status.url;

if (status.newStatus === 'ContentPaint') {
if (status.newStatus === 'ContentPaint' || !sessionUrl.screenshotBase64) {
try {
const screenshot = await tab.puppetPage.screenshot('jpeg', undefined, 50);
sessionUrl.screenshotBase64 = screenshot.toString('base64');
Expand Down
21 changes: 16 additions & 5 deletions apps/chromealive-ui/src/pages/app/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
class="output app-button"
ref="outputButton"
@click.prevent="toggleOutput()"
:class="{ selected: isShowingOutput }"
:class="{ selected: !!outputWindow }"
>
<span class="label">Output</span>
<span class="size">({{ outputSize }})</span>
Expand Down Expand Up @@ -126,8 +126,6 @@ import flattenJson, { FlatJson } from '@/utils/flattenJson';
})
export default class ChromeAliveApp extends Vue {
private client = Client;
private isPlaying = false;
private isShowingOutput = false;
private isShowingInput = false;
private scriptTimeAgo = '';
private timeAgoTimeout: number;
Expand Down Expand Up @@ -162,6 +160,10 @@ export default class ChromeAliveApp extends Vue {

private screenshotsByNavigationId = new Map<number, string>();

private get isPlaying() {
return this.session?.state === 'play';
}

canPlay(): boolean {
if (!this.session.heroSessionId) return false;
return this.session.state === 'paused';
Expand Down Expand Up @@ -228,15 +230,21 @@ export default class ChromeAliveApp extends Vue {
}

toggleOutput() {
this.isShowingOutput = !this.isShowingOutput;
if (!this.isShowingOutput) {
if (this.outputWindow) {
this.outputWindow.close();
this.outputWindow = null;
} else {
const { left } = (this.$refs.outputButton as HTMLElement).getBoundingClientRect();
const { bottom } = (this.$refs.toolbar as HTMLElement).getBoundingClientRect();
const features = `top=${bottom},left=${left},width=300,height=500,frame=true,nodeIntegration=no`;
this.outputWindow = window.open('/output.html', '_blank', features);
this.outputWindow.addEventListener('blur', () => {
this.outputWindow?.close()
this.outputWindow = null;
});
this.outputWindow.addEventListener('close', () => {
this.outputWindow = null;
})
}
}

Expand Down Expand Up @@ -335,6 +343,9 @@ export default class ChromeAliveApp extends Vue {
return;
}

this.lastAppBounds = appBounds;
this.lastToolbarBounds = toolbarBounds

await this.client.connect();
await this.client.send('App.boundsChanged', {
workarea: (window as any).workarea,
Expand Down
Loading