Skip to content

Instantly share code, notes, and snippets.

@knu
Last active March 10, 2024 06:21
Show Gist options
  • Save knu/80c703b06bf048251da7200eb26c89e6 to your computer and use it in GitHub Desktop.
Save knu/80c703b06bf048251da7200eb26c89e6 to your computer and use it in GitHub Desktop.
JXA script to open a URL in a specified Chromium-based browser
#!/usr/bin/osascript -l JavaScript
// -*- javascript -*-
//
// open-chromium: Open a URL in a Chromium-based browser in a specified profile
//
// usage: open-chromium [(-a | --application) <app>] [(-p | --profile) <profile>] <url>
//
// <app>: "Chromium" (default), "Google Chrome", "Microsoft Edge", etc.
// <profile>: "Default", "Profile 1", etc.
//
// If the URL is already opened in a tab, it is activated.
function first(collection) {
return collection.length > 0 ? collection[0] : null;
}
function activateWindowById(app, id) {
const window = app.windows.byId(id);
if (!window) {
console.log("window is gone!");
return false;
}
if (window.index() !== 1) window.index = 1;
return true;
}
function activateTabById(window, id) {
Array.from(window.tabs()).forEach((tab, i) => {
if (tab.id() === id) {
window.activeTabIndex = i + 1;
return true;
}
});
}
function selectProfile(app, profile) {
const system = Application("System Events");
const browserAppProc = system.applicationProcesses.byName(app.name());
const menuBar = browserAppProc.menuBars[0];
const menu = first(
menuBar.menus.whose(
{
_or: [
{ name: "Profiles" },
// other languages
{ name: "プロファイル" },
],
},
{ considering: "case" },
),
);
const menuName = menu.name();
const menuItem = first(
menu.menuItems.whose(
{
_or: [
// Chrome, Chromium
{ name: { _endsWith: ` (${profile})` } },
// Microsoft Edge
{ name: profile },
],
},
{ considering: "case" },
),
);
if (!menuItem) return false;
const menuItemName = menuItem.name();
menuItem.click();
const isSelected = () =>
menuBar.menus
.byName(menuName)
.menuItems.byName(menuItemName)
.attributes.byName("AXMenuItemMarkChar")
.value() === "✓";
if (isSelected()) return true;
for (const id of Array.from(app.windows())
.slice(1)
.map((window) => window.id())) {
activateWindowById(app, id);
if (isSelected()) return true;
}
return false;
}
function openUrlInWindow(app, window, url) {
const tab = first(window.tabs.whose({ url }, { considering: "case" }));
if (tab) {
activateTabById(window, tab.id());
} else {
const tab = first(
window.tabs.whose(
{
_or: [
// Chrome, Chromium
{ url: "chrome://newtab/" },
// Microsoft Edge
{ url: "edge://newtab/" },
],
},
{ considering: "case" },
),
);
if (tab) {
tab.url = url;
} else {
window.tabs.push(app.Tab({ url }));
}
}
}
function openUrl(app, url, { profile }) {
if (profile) {
if (!selectProfile(app, profile)) console.log("profile not found");
if (!url) return;
openUrlInWindow(
app,
Array.from(app.windows()).find(
(window) => window.mode() !== "incognito",
) || app.Window().make(),
url,
);
} else {
if (!url) return;
Array.from(app.windows()).find((window, i) => {
if (window.mode() === "incognito") return false;
const tab = first(window.tabs.whose({ url }, { considering: "case" }));
if (tab) {
activateWindowById(app, window.id());
activateTabById(window, tab.id());
return true;
}
return false;
}) ||
openUrlInWindow(
app,
Array.from(app.windows()).find(
(window) => window.mode() !== "incognito",
) || app.Window().make(),
url,
);
}
}
function run(argv) {
let appName = "Chromium",
profile,
urls = [];
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
let m;
if ((m = /^(?:-p(.+)?|--profile(?:=(.*))?)$/.exec(arg))) {
profile = m[1] ?? m[2] ?? argv[++i];
} else if ((m = /^(?:-a(.+)?|--application(?:=(.*))?)$/.exec(arg))) {
appName = m[1] ?? m[2] ?? argv[++i];
} else {
urls.push(arg);
}
}
const app = Application(appName);
app.activate();
for (const url of urls) {
openUrl(app, url, { profile });
}
app.activate();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment