This commit is contained in:
Mike Sawka 2024-09-10 12:50:55 -07:00 committed by GitHub
parent a9486852f9
commit 5fe0cae244
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 120 additions and 7 deletions

View File

@ -18,18 +18,38 @@ var webCmd = &cobra.Command{
PersistentPreRunE: preRunSetupRpcClient,
}
var webOpenCommand = &cobra.Command{
var webOpenCmd = &cobra.Command{
Use: "open url",
Short: "open a url a web widget",
Args: cobra.ExactArgs(1),
RunE: webOpenRun,
}
var webGetCmd = &cobra.Command{
Use: "get [--inner] [--all] css-selector",
Short: "get the html for a css selector",
Args: cobra.ExactArgs(1),
Hidden: true,
RunE: webGetRun,
}
var webGetInner bool
var webGetAll bool
var webOpenMagnified bool
func init() {
webCmd.AddCommand(webOpenCommand)
webOpenCmd.Flags().BoolVarP(&webOpenMagnified, "magnified", "m", false, "open view in magnified mode")
webCmd.AddCommand(webOpenCmd)
webGetCmd.Flags().BoolVarP(&webGetInner, "inner", "", false, "get inner html (instead of outer)")
webGetCmd.Flags().BoolVarP(&webGetAll, "all", "", false, "get all matches (querySelectorAll)")
webCmd.AddCommand(webGetCmd)
rootCmd.AddCommand(webCmd)
}
func webGetRun(cmd *cobra.Command, args []string) error {
return nil
}
func webOpenRun(cmd *cobra.Command, args []string) error {
wshCmd := wshrpc.CommandCreateBlockData{
BlockDef: &waveobj.BlockDef{
@ -38,7 +58,7 @@ func webOpenRun(cmd *cobra.Command, args []string) error {
waveobj.MetaKey_Url: args[0],
},
},
Magnified: viewMagnified,
Magnified: webOpenMagnified,
}
oref, err := wshclient.CreateBlockCommand(RpcClient, wshCmd, nil)
if err != nil {

63
emain/emain-web.ts Normal file
View File

@ -0,0 +1,63 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { BrowserWindow, ipcMain, webContents, WebContents } from "electron";
export async function getWebContentsByBlockId(
win: BrowserWindow,
tabId: string,
blockId: string
): Promise<WebContents> {
const prtn = new Promise<WebContents>((resolve, reject) => {
const randId = Math.floor(Math.random() * 1000000000).toString();
const respCh = `getWebContentsByBlockId-${randId}`;
win.webContents.send("webcontentsid-from-blockid", blockId, respCh);
ipcMain.once(respCh, (event, webContentsId) => {
if (webContentsId == null) {
resolve(null);
return;
}
const wc = webContents.fromId(parseInt(webContentsId));
resolve(wc);
});
setTimeout(() => {
reject(new Error("timeout waiting for response"));
}, 2000);
});
return prtn;
}
function escapeSelector(selector: string): string {
return selector
.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')
.replace(/'/g, "\\'")
.replace(/\n/g, "\\n")
.replace(/\r/g, "\\r")
.replace(/\t/g, "\\t");
}
export type WebGetOpts = {
all?: boolean;
inner?: boolean;
};
export async function webGetSelector(wc: WebContents, selector: string, opts?: WebGetOpts): Promise<string[]> {
if (!wc || !selector) {
return null;
}
try {
const escapedSelector = escapeSelector(selector);
const queryMethod = opts?.all ? "querySelectorAll" : "querySelector";
const prop = opts?.inner ? "innerHTML" : "outerHTML";
const execExpr = `
Array.from(document.${queryMethod}("${escapedSelector}") || []).map(el => el.${prop});
`;
const results = await wc.executeJavaScript(execExpr);
return results;
} catch (e) {
console.error("webGetSelector error", e);
return null;
}
}

View File

@ -824,6 +824,12 @@ async function relaunchBrowserWindows(): Promise<void> {
}
}
process.on("uncaughtException", (error) => {
console.error("Uncaught Exception:", error);
console.error("Stack Trace:", error.stack);
electron.app.quit();
});
async function appMain() {
const startTs = Date.now();
const instanceLock = electronApp.requestSingleInstanceLock();

View File

@ -1,7 +1,7 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
const { contextBridge, ipcRenderer } = require("electron");
import { contextBridge, ipcRenderer, WebviewTag } from "electron";
contextBridge.exposeInMainWorld("api", {
getAuthKey: () => ipcRenderer.sendSync("get-auth-key"),
@ -42,3 +42,9 @@ ipcRenderer.on("webview-new-window", (e, webContentsId, details) => {
const event = new CustomEvent("new-window", { detail: details });
document.getElementById("webview").dispatchEvent(event);
});
ipcRenderer.on("webcontentsid-from-blockid", (e, blockId, responseCh) => {
const webviewElem: WebviewTag = document.querySelector("div[data-blockid='" + blockId + "'] webview");
const wcId = webviewElem?.dataset?.webcontentsid;
ipcRenderer.send(responseCh, wcId);
});

View File

@ -362,6 +362,18 @@ const WebView = memo(({ model }: WebViewProps) => {
// The initial value of the block metadata URL when the component first renders. Used to set the starting src value for the webview.
const [metaUrlInitial] = useState(metaUrl);
const [webContentsId, setWebContentsId] = useState(null);
const [domReady, setDomReady] = useState(false);
useEffect(() => {
if (model.webviewRef.current && domReady) {
const wcId = model.webviewRef.current.getWebContentsId?.();
if (wcId) {
setWebContentsId(wcId);
}
}
}, [model.webviewRef.current, domReady]);
// Load a new URL if the block metadata is updated.
useEffect(() => {
if (metaUrlRef.current != metaUrl) {
@ -409,6 +421,9 @@ const WebView = memo(({ model }: WebViewProps) => {
const webviewBlur = () => {
getApi().setWebviewFocus(null);
};
const handleDomReady = () => {
setDomReady(true);
};
webview.addEventListener("did-navigate-in-page", navigateListener);
webview.addEventListener("did-navigate", navigateListener);
@ -416,9 +431,9 @@ const WebView = memo(({ model }: WebViewProps) => {
webview.addEventListener("did-stop-loading", stopLoadingHandler);
webview.addEventListener("new-window", newWindowHandler);
webview.addEventListener("did-fail-load", failLoadHandler);
webview.addEventListener("focus", webviewFocus);
webview.addEventListener("blur", webviewBlur);
webview.addEventListener("dom-ready", handleDomReady);
// Clean up event listeners on component unmount
return () => {
@ -428,8 +443,9 @@ const WebView = memo(({ model }: WebViewProps) => {
webview.removeEventListener("did-fail-load", failLoadHandler);
webview.removeEventListener("did-start-loading", startLoadingHandler);
webview.removeEventListener("did-stop-loading", stopLoadingHandler);
webview.addEventListener("focus", webviewFocus);
webview.addEventListener("blur", webviewBlur);
webview.removeEventListener("focus", webviewFocus);
webview.removeEventListener("blur", webviewBlur);
webview.removeEventListener("dom-ready", handleDomReady);
};
}
}, []);
@ -440,6 +456,8 @@ const WebView = memo(({ model }: WebViewProps) => {
className="webview"
ref={model.webviewRef}
src={metaUrlInitial}
data-blockid={model.blockId}
data-webcontentsid={webContentsId} // needed for emain
// @ts-ignore This is a discrepancy between the React typing and the Chromium impl for webviewTag. Chrome webviewTag expects a string, while React expects a boolean.
allowpopups="true"
></webview>