Repository: andy-portmen/open-in Branch: master Commit: b1981c98d2d5 Files: 19 Total size: 29.3 KB Directory structure: gitextract_pmrctrx2/ ├── README.md ├── brave/ │ ├── config.js │ └── manifest.json ├── chrome/ │ └── README ├── chromium/ │ └── README ├── common/ │ ├── common.js │ ├── data/ │ │ ├── inject.js │ │ └── options/ │ │ ├── index.html │ │ └── index.js │ └── schema.json ├── edge/ │ └── README ├── palemoon/ │ ├── config.js │ └── manifest.json ├── vivaldi/ │ ├── config.js │ └── manifest.json ├── waterfox/ │ ├── config.js │ └── manifest.json └── yandex/ ├── config.js └── manifest.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ # open-in Send URLs from one browser to another one ## browser-specific projects * [open-in-chromium](https://github.com/andy-portmen/open-in-chromium) * [open-in-edge](https://github.com/andy-portmen/open-in-edge) * [open-in-ie](https://github.com/andy-portmen/open-in-ie) * [open-in-opera](https://github.com/andy-portmen/open-in-opera) * [open-in-firefox](https://github.com/andy-portmen/open-in-firefox) ## general-purpose connector * https://github.com/andy-portmen/native-client ================================================ FILE: brave/config.js ================================================ 'use strict'; var app = { id: 'com.add0n.node', tag: 'brave', multiple: true }; app.locale = { name: 'brave', current: 'Open Link in Brave Browser', all: 'Open all Tabs in Brave Browser', call: 'Open all Tabs in Brave Browser (Current window)', example: 'example D:\\Brave\\Application\\Brave.exe' }; app.runtime = { mac: { args: ['-a', 'Brave Browser'] }, linux: { name: 'brave-browser' }, windows: { name: 'cmd', args: ['/s/c', 'start', 'brave "%url;"'], prgfiles: '%ProgramFiles(x86)%\\BraveSoftware\\Brave-Browser\\Application\\brave.exe' } }; ================================================ FILE: brave/manifest.json ================================================ { "name": "Open in Brave Browser", "description": "Open current page, link, or all tabs in the Brave browser", "version": "0.1.3", "manifest_version": 2, "permissions": [ "storage", "tabs", "contextMenus", "nativeMessaging" ], "optional_permissions": [ "downloads" ], "background": { "persistent": false, "scripts": [ "config.js", "common.js" ] }, "storage": { "managed_schema": "schema.json" }, "homepage_url": "https://add0n.com/open-in.html?from=brave", "icons": { "16": "data/icons/16.png", "32": "data/icons/32.png", "48": "data/icons/48.png", "64": "data/icons/64.png", "128": "data/icons/128.png", "256": "data/icons/256.png", "512": "data/icons/512.png" }, "browser_action": {}, "content_scripts": [{ "matches": [""], "js": ["data/inject.js"], "run_at": "document_start", "all_frames": true, "match_about_blank": true }], "options_ui": { "page": "data/options/index.html", "chrome_style": true, "open_in_tab": true } } ================================================ FILE: chrome/README ================================================ moved to https://github.com/andy-portmen/open-in-chrome/ ================================================ FILE: chromium/README ================================================ moved to https://github.com/andy-portmen/open-in-chromium/ ================================================ FILE: common/common.js ================================================ /* globals app */ 'use strict'; const os = { mac: navigator.userAgent.indexOf('Mac') !== -1, linux: navigator.userAgent.indexOf('Linux') !== -1 }; const isFirefox = navigator.userAgent.indexOf('Firefox') !== -1; function error(response) { window.alert(`Something went wrong! ----- Code: ${response.code} Output: ${response.stdout} Error: ${response.stderr}`); } function response(res, success = () => {}) { // windows batch file returns 1 if (res && (res.code !== 0 && (res.code !== 1 || res.stderr !== ''))) { error(res); } else if (!res) { chrome.tabs.query({ url: chrome.runtime.getURL('data/helper/index.html') }, tabs => { if (tabs && tabs.length) { chrome.tabs.update(tabs[0].id, { active: true }, () => { chrome.windows.update(tabs[0].windowId, { focused: true }); }); } else { chrome.tabs.create({ url: 'data/helper/index.html' }); } }); } else { success(); } } function exec(command, args, callback, properties = {}) { if (command) { chrome.runtime.sendNativeMessage(app.id, { cmd: 'exec', command, arguments: args, properties }, res => (callback || response)(res)); } else { window.alert(`Please set the "${app.locale.name}" executable path in the options page`); chrome.runtime.openOptionsPage(); } } function find(callback) { chrome.runtime.sendNativeMessage(app.id, { cmd: 'env' }, res => { if (res && res.env && res.env.ProgramFiles) { chrome.storage.local.set({ path: app.runtime.windows.prgfiles .replace('%LOCALAPPDATA%', res.env.LOCALAPPDATA) .replace('%ProgramFiles(x86)%', res.env['ProgramFiles(x86)']) .replace('%ProgramFiles%', res.env.ProgramFiles) }, callback); } else { response(res); } }); } const open = (urls, closeIDs = []) => { chrome.storage.local.get({ path: null, closeme: false }, prefs => { const close = () => { if (prefs.closeme && closeIDs.length) { chrome.tabs.remove(closeIDs); } }; if (os.mac) { if (prefs.path) { const length = app.runtime.mac.args.length; app.runtime.mac.args[length - 1] = prefs.path; } exec('open', [...app.runtime.mac.args, ...urls], r => response(r, close)); } else if (os.linux) { exec(prefs.path || app.runtime.linux.name, urls, r => response(r, close)); } else { if (prefs.path) { exec(prefs.path, [...(app.runtime.windows.args2 || []), ...urls], r => response(r, close)); } else { const args = app.runtime.windows.args .map(a => a.replace('%url;', urls.join(' '))) // Firefox is not detaching the process on Windows .map(s => s.replace('start', isFirefox ? 'start /WAIT' : 'start')); exec(app.runtime.windows.name, args, res => { // use old method if (res && res.code !== 0) { find(() => open(urls, closeIDs)); } else { response(res, close); } }, {windowsVerbatimArguments: true}); } } }); }; function delayOpen(tabs) { chrome.storage.local.get({ multiple: app.multiple }, prefs => { if (prefs.multiple) { return open(tabs.map(t => t.url), tabs.map(t => t.id)); } const tab = tabs.shift(); if (tab) { open([tab.url], [tab.id]); window.setTimeout(delayOpen, 1000, tabs); } }); } chrome.browserAction.onClicked.addListener(() => { chrome.tabs.query({ active: true, currentWindow: true }, tabs => open(tabs.map(t => t.url), tabs.map(t => t.id))); }); // context menu { const callback = () => { chrome.contextMenus.create({ id: 'open-current', title: app.locale.current, contexts: ['link'], documentUrlPatterns: ['*://*/*'] }); chrome.contextMenus.create({ id: 'open-all', title: app.locale.all, contexts: ['browser_action'] }); chrome.contextMenus.create({ id: 'open-call', title: app.locale.call, contexts: ['browser_action'] }); }; chrome.runtime.onInstalled.addListener(callback); chrome.runtime.onStartup.addListener(callback); } chrome.contextMenus.onClicked.addListener(info => { if (info.menuItemId === 'open-current') { open([info.linkUrl || info.pageUrl], []); } else if (info.menuItemId === 'open-all') { chrome.tabs.query({ url: ['*://*/*'] }, delayOpen); } else if (info.menuItemId === 'open-call') { chrome.tabs.query({ url: ['*://*/*'], currentWindow: true }, delayOpen); } }); chrome.runtime.onMessage.addListener((request, sender) => { if (request.cmd === 'open-in') { open([request.url], [sender.tab.id]); } }); // FAQs & Feedback/* FAQs & Feedback */ { const {management, runtime: {onInstalled, setUninstallURL, getManifest}, storage, tabs} = chrome; if (navigator.webdriver !== true) { const page = getManifest().homepage_url; const {name, version} = getManifest(); onInstalled.addListener(({reason, previousVersion}) => { management.getSelf(({installType}) => installType === 'normal' && storage.local.get({ 'faqs': true, 'last-update': 0 }, prefs => { if (reason === 'install' || (prefs.faqs && reason === 'update')) { const doUpdate = (Date.now() - prefs['last-update']) / 1000 / 60 / 60 / 24 > 45; if (doUpdate && previousVersion !== version) { tabs.create({ url: page + '&version=' + version + (previousVersion ? '&p=' + previousVersion : '') + '&type=' + reason, active: reason === 'install' }); storage.local.set({'last-update': Date.now()}); } } })); }); setUninstallURL(page + '&rd=feedback&name=' + encodeURIComponent(name) + '&version=' + version); } } ================================================ FILE: common/data/inject.js ================================================ 'use strict'; const config = { button: 0, altKey: true, ctrlKey: false, shiftKey: true, metaKey: false, enabled: false, hosts: [], urls: [], reverse: false, topRedict: false }; const validate = (a, callback, isTop = false) => { if (config.hosts.length) { const host = a.hostname; if (host) { if (config.hosts.some(h => h.endsWith(host) || host.endsWith(h))) { return config.reverse ? '' : callback(a.href); } } } else { const href = a.href; if (href) { if (config.urls.some(h => href.startsWith(h))) { return config.reverse ? '' : callback(a.href); } } } // reverse mode if (config.reverse && a.href && (a.href.indexOf('#') === -1 || isTop)) { if (a.href.startsWith('http') || a.href.startsWith('file')) { return callback(a.href); } } }; chrome.storage.local.get(config, prefs => { Object.assign(config, prefs); // managed chrome.storage.managed.get({ hosts: [], urls: [], reverse: false }, prefs => { if (!chrome.runtime.lastError) { config.hosts.push(...prefs.hosts); config.urls.push(...prefs.urls); config.reverse = config.reverse || prefs.reverse; } // top level redirect if (window.top === window && config.topRedict) { validate(location, url => { if (history.length) { history.back(); } else { window.stop(); } chrome.runtime.sendMessage({ cmd: 'open-in', url }); }, true); } // Gmail attachments // https://github.com/andy-portmen/open-in/issues/42 if (window.top === window && location.hostname === 'mail.google.com') { validate(location, () => { const script = document.createElement('script'); script.textContent = `{ const script = document.currentScript; const hps = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'src'); Object.defineProperty(HTMLIFrameElement.prototype, 'src', { set(v) { if (v && v.indexOf('&view=att&') !== -1) { script.dispatchEvent(new CustomEvent('open-request', { detail: v })); } else { hps.set.call(this, v); } } }); }`; script.addEventListener('open-request', e => { e.stopPropagation(); chrome.runtime.sendMessage({ cmd: 'open-in', url: e.detail }); }); document.documentElement.appendChild(script); script.remove(); }, true); } }); }); chrome.storage.onChanged.addListener(e => { Object.keys(e).forEach(n => { config[n] = e[n].newValue; }); }); document.addEventListener('click', e => { const redirect = url => { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); chrome.runtime.sendMessage({ cmd: 'open-in', url }); return false; }; // hostname on left-click if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) { if (config.hosts.length || config.urls.length || config.reverse) { let a = e.target.closest('a'); if (a) { if (a.href.startsWith('https://www.google') && a.href.indexOf('&url=') !== -1) { const link = decodeURIComponent(a.href.split('&url=')[1].split('&')[0]); a = new URL(link); } validate(a, redirect); } } } // click + modifier if ( config.enabled && e.button === config.button && e.altKey === config.altKey && e.ctrlKey === config.ctrlKey && e.metaKey === config.metaKey && e.shiftKey === config.shiftKey ) { const a = e.target.closest('a'); if (a && a.href) { return redirect(a.href); } } }, true); ================================================ FILE: common/data/options/index.html ================================================ Open In Options

Open With Keyboard-Mouse-Click Combinations

Mouse click4  +   +   +   + 

Open with Left-Click

Comma-separated list of domains to open with the external browser when the link is opened with left-click1
Comma-separated list of URLs to open with the external browser when the link is opened with left-click1

Misc

Path to the executable2:
This extension supports managed storage. Some of the preferences can be pre-configured by the domain administrator

-


  1. It is possible to use "managed storage" to set the default host and URL list. This is useful for administrators to force opening a set of URLs or hostnames in another browser. Instruction on how to setup the managed storage can be found in the FAQs page.
  2. On Windows, if the path to the executable is not provided (is empty) is used. On Linux, if the path is not set, it is assumed binary can be found in the global PATH environment. On Mac OS, open -a command is used (with as the application or whatever is in the input).
  3. By activating this option all the requested URLs are sent to the external executable at once. Only activate this option if the external executable is capable of handling this type of requests.
  4. Not all key combinations are allowed in all operating systems.
  5. When enabled, all left-click links except the ones that match with at least one condition will be sent to the external executable.
  6. If checked, the extension validates top-level navigation with the list of left-click hostnames and URLs. If matched, the URL is sent to the external executable, and the navigation is blocked.
================================================ FILE: common/data/options/index.js ================================================ /* globals app */ 'use strict'; document.title = 'Open In ' + app.locale.name + ' :: Options'; document.getElementById('path').placeholder = app.locale.example; document.getElementById('l2').textContent = app.runtime.windows.prgfiles; document.getElementById('l3').textContent = app.runtime.linux.name; document.getElementById('l4').textContent = app.runtime.mac.args[1]; function restore() { // Use default value color = 'red' and likesColor = true. chrome.storage.local.get({ path: '', enabled: false, altKey: true, shiftKey: true, ctrlKey: false, metaKey: false, button: 0, faqs: true, closeme: false, multiple: app.multiple, hosts: [], urls: [], reverse: false, topRedict: false }, prefs => { document.getElementById('path').value = prefs.path; document.getElementById('enabled').checked = prefs.enabled; document.getElementById('altKey').checked = prefs.altKey; document.getElementById('shiftKey').checked = prefs.shiftKey; document.getElementById('ctrlKey').checked = prefs.ctrlKey; document.getElementById('metaKey').checked = prefs.metaKey; document.getElementById('button').selectedIndex = prefs.button; document.getElementById('faqs').checked = prefs.faqs; document.getElementById('closeme').checked = prefs.closeme; document.getElementById('multiple').checked = prefs.multiple; document.getElementById('hosts').value = prefs.hosts.join(', '); document.getElementById('urls').value = prefs.urls.join(', '); document.getElementById('reverse').checked = prefs.reverse; document.getElementById('topRedict').checked = prefs.topRedict; }); } function save() { const path = document.getElementById('path').value; const enabled = document.getElementById('enabled').checked; const altKey = document.getElementById('altKey').checked; const shiftKey = document.getElementById('shiftKey').checked; const ctrlKey = document.getElementById('ctrlKey').checked; const metaKey = document.getElementById('metaKey').checked; const button = document.getElementById('button').selectedIndex; const faqs = document.getElementById('faqs').checked; const closeme = document.getElementById('closeme').checked; const multiple = document.getElementById('multiple').checked; const hosts = document.getElementById('hosts').value; const urls = document.getElementById('urls').value; const reverse = document.getElementById('reverse').checked; const topRedict = document.getElementById('topRedict').checked; chrome.storage.local.set({ path, enabled, altKey, shiftKey, ctrlKey, metaKey, button, faqs, closeme, multiple, hosts: hosts.split(/\s*,\s*/).map(s => s.replace('http://', '') .replace('https://', '') .split('/')[0].trim()) .filter((h, i, l) => h && l.indexOf(h) === i), urls: urls.split(/\s*,\s*/).filter(s => s.startsWith('http') || s.startsWith('file')) .filter((h, i, l) => h && l.indexOf(h) === i), reverse, topRedict }, () => { restore(); const status = document.getElementById('status'); status.textContent = 'Options saved.'; setTimeout(() => status.textContent = '', 750); }); } document.addEventListener('DOMContentLoaded', restore); document.getElementById('save').addEventListener('click', save); document.getElementById('support').addEventListener('click', () => chrome.tabs.create({ url: chrome.runtime.getManifest().homepage_url + '&rd=donate' })); document.getElementById('save').addEventListener('click', save); document.getElementById('reset').addEventListener('click', e => { if (e.detail === 1) { const status = document.getElementById('status'); window.setTimeout(() => status.textContent = '', 750); status.textContent = 'Double-click to reset!'; } else { localStorage.clear(); chrome.storage.local.clear(() => { chrome.runtime.reload(); window.close(); }); } }); ================================================ FILE: common/schema.json ================================================ { "type": "object", "properties": { "hosts": { "title": "List of hosts", "description": "List of domains to open with the external browser when the link is opened with left-click", "type": "array", "items": { "type": "string" } }, "urls": { "title": "List of URLs", "description": "List of URLs to open with the external browser when the link is opened with left-click", "type": "array", "items": { "type": "string" } }, "faqs": { "title": "Open FAQs page on updates", "type": "boolean" }, "reverse": { "title": "Enable reversed left-click mode", "type": "boolean" } } } ================================================ FILE: edge/README ================================================ moved to https://github.com/Rynu/open-in-edge/tree/master ================================================ FILE: palemoon/config.js ================================================ 'use strict'; var app = { id: 'com.add0n.node', tag: 'palemoon', multiple: true }; app.locale = { name: 'Pale Moon', current: 'Open Link in Pale Moon Browser', all: 'Open all Tabs in Pale Moon Browser', call: 'Open all Tabs in Pale Moon Browser (Current window)', example: 'example D:\\Pale Moon\\browser.exe' }; app.runtime = { mac: { args: ['-a', 'Palemoon'] }, linux: { name: 'palemoon' }, windows: { name: 'cmd', args: ['/s/c', 'start', 'palemoon "%url;"'], prgfiles: '%ProgramFiles(x86)%\\Pale Moon\\palemoon.exe' } }; ================================================ FILE: palemoon/manifest.json ================================================ { "name": "Open in Pale Moon", "description": "Open current page, link, or all tabs in the Pale Moon browser.", "version": "0.1.1", "manifest_version": 2, "permissions": [ "storage", "tabs", "contextMenus", "nativeMessaging" ], "optional_permissions": [ "downloads" ], "background": { "persistent": false, "scripts": [ "config.js", "common.js" ] }, "storage": { "managed_schema": "schema.json" }, "homepage_url": "https://add0n.com/open-in.html?from=palemoon", "icons": { "16": "data/icons/16.png", "32": "data/icons/32.png", "48": "data/icons/48.png", "64": "data/icons/64.png", "128": "data/icons/128.png", "256": "data/icons/256.png", "512": "data/icons/512.png" }, "browser_action": {}, "content_scripts": [{ "matches": [""], "js": ["data/inject.js"], "run_at": "document_start", "all_frames": true, "match_about_blank": true }], "options_ui": { "page": "data/options/index.html", "chrome_style": true, "open_in_tab": true } } ================================================ FILE: vivaldi/config.js ================================================ 'use strict'; var app = { id: 'com.add0n.node', tag: 'vivaldi', multiple: true }; app.locale = { name: 'Vivaldi', current: 'Open Link in Vivaldi Browser', all: 'Open all Tabs in Vivaldi Browser', call: 'Open all Tabs in Vivaldi Browser (Current window)', example: 'example D:\\Vivaldi\\vivaldi.exe' }; app.runtime = { mac: { args: ['-a', 'vivaldi'] }, linux: { name: 'vivaldi' }, windows: { name: 'cmd', args: ['/s/c', 'start', 'vivaldi "%url;"'], prgfiles: '%LOCALAPPDATA%\\Vivaldi\\Application\\vivaldi.exe' } }; ================================================ FILE: vivaldi/manifest.json ================================================ { "name": "Open in Vivaldi", "description": "Open current page, link, or all tabs in the Vivaldi browser.", "version": "0.2.0", "manifest_version": 2, "permissions": [ "storage", "tabs", "contextMenus", "nativeMessaging" ], "optional_permissions": [ "downloads" ], "background": { "persistent": false, "scripts": [ "config.js", "common.js" ] }, "storage": { "managed_schema": "schema.json" }, "homepage_url": "https://add0n.com/open-in.html?from=vivaldi", "icons": { "16": "data/icons/16.png", "32": "data/icons/32.png", "48": "data/icons/48.png", "64": "data/icons/64.png", "128": "data/icons/128.png", "256": "data/icons/256.png", "512": "data/icons/512.png" }, "browser_action": {}, "content_scripts": [{ "matches": [""], "js": ["data/inject.js"], "run_at": "document_start", "all_frames": true, "match_about_blank": true }], "options_ui": { "page": "data/options/index.html", "chrome_style": true, "open_in_tab": true } } ================================================ FILE: waterfox/config.js ================================================ 'use strict'; var app = { id: 'com.add0n.node', tag: 'waterfox', multiple: true }; app.locale = { name: 'Waterfox', current: 'Open Link in Waterfox Browser', all: 'Open all Tabs in Waterfox Browser', call: 'Open all Tabs in Waterfox Browser (Current window)', example: 'example D:\\Waterfox\\browser.exe' }; app.runtime = { mac: { args: ['-a', 'Waterfox'] }, linux: { name: 'waterfox' }, windows: { name: 'cmd', args: ['/s/c', 'start', 'waterfox "%url;"'], prgfiles: '%ProgramFiles(x86)%\\Waterfox\\waterfox.exe' } }; ================================================ FILE: waterfox/manifest.json ================================================ { "name": "Open in Waterfox", "description": "Open current page, link, or all tabs in the Waterfox browser.", "version": "0.1.1", "manifest_version": 2, "permissions": [ "storage", "tabs", "contextMenus", "nativeMessaging" ], "optional_permissions": [ "downloads" ], "background": { "persistent": false, "scripts": [ "config.js", "common.js" ] }, "storage": { "managed_schema": "schema.json" }, "homepage_url": "https://add0n.com/open-in.html?from=waterfox", "icons": { "16": "data/icons/16.png", "32": "data/icons/32.png", "48": "data/icons/48.png", "64": "data/icons/64.png", "128": "data/icons/128.png", "256": "data/icons/256.png", "512": "data/icons/512.png" }, "browser_action": {}, "content_scripts": [{ "matches": [""], "js": ["data/inject.js"], "run_at": "document_start", "all_frames": true, "match_about_blank": true }], "options_ui": { "page": "data/options/index.html", "chrome_style": true, "open_in_tab": true } } ================================================ FILE: yandex/config.js ================================================ 'use strict'; var app = { id: 'com.add0n.node', tag: 'yandex', multiple: true }; app.locale = { name: 'Yandex', current: 'Open Link in Yandex Browser', all: 'Open all Tabs in Yandex Browser', call: 'Open all Tabs in Yandex Browser (Current window)', example: 'example D:\\Yandex\\browser.exe' }; app.runtime = { mac: { args: ['-a', 'yandex'] }, linux: { name: 'yandex' }, windows: { name: 'cmd', args: ['/s/c', 'start', 'yandex "%url;"'], prgfiles: '%LOCALAPPDATA%\\Yandex\\YandexBrowser\\Application\\browser.exe' } }; ================================================ FILE: yandex/manifest.json ================================================ { "name": "Open in Yandex browser", "description": "Open current page, link, or all tabs in the Yandex browser.", "version": "0.1.6", "manifest_version": 2, "permissions": [ "storage", "tabs", "contextMenus", "nativeMessaging" ], "optional_permissions": [ "downloads" ], "background": { "persistent": false, "scripts": [ "config.js", "common.js" ] }, "storage": { "managed_schema": "schema.json" }, "homepage_url": "https://add0n.com/open-in.html?from=yandex", "icons": { "16": "data/icons/16.png", "32": "data/icons/32.png", "48": "data/icons/48.png", "64": "data/icons/64.png", "128": "data/icons/128.png", "256": "data/icons/256.png", "512": "data/icons/512.png" }, "browser_action": {}, "content_scripts": [{ "matches": [""], "js": ["data/inject.js"], "run_at": "document_start", "all_frames": true, "match_about_blank": true }], "options_ui": { "page": "data/options/index.html", "chrome_style": true, "open_in_tab": true } }