From 76f9d8bfb9eed04270fc4ea74ef057e64cdcca46 Mon Sep 17 00:00:00 2001 From: aminomancer <33384265+aminomancer@users.noreply.github.com> Date: Wed, 21 Sep 2022 01:18:18 -0700 Subject: [PATCH] (JS) searchSelectionShortcut v1.7.3: add support for regular expression custom matches. see the readme or the script file description for instructions. --- JS/extensionStylesheetLoader.uc.js | 4 +- JS/searchSelectionShortcut.uc.js | 117 ++++++++++++++++++----------- README.md | 52 +++++++++++-- 3 files changed, 121 insertions(+), 52 deletions(-) diff --git a/JS/extensionStylesheetLoader.uc.js b/JS/extensionStylesheetLoader.uc.js index 235c430b..9e23a898 100644 --- a/JS/extensionStylesheetLoader.uc.js +++ b/JS/extensionStylesheetLoader.uc.js @@ -1,6 +1,6 @@ // ==UserScript== // @name Extension Stylesheet Loader -// @version 1.1.2 +// @version 1.1.3 // @author aminomancer // @homepage https://github.com/aminomancer // @description Allows users to share stylesheets for webextensions without @@ -48,7 +48,7 @@ class ExtensionStylesheetLoader { // create a manifest file that registers a URI for // chrome://uc-extensionstylesheetloader/content/ this.manifestFile = await this.createTempFile(`content uc-extensionstylesheetloader ./`, { - name: "ucsss", + name: "ucess", type: "manifest", }); this.childFile = await this.createTempFile( diff --git a/JS/searchSelectionShortcut.uc.js b/JS/searchSelectionShortcut.uc.js index 5bafb7de..12501f61 100644 --- a/JS/searchSelectionShortcut.uc.js +++ b/JS/searchSelectionShortcut.uc.js @@ -1,53 +1,86 @@ // ==UserScript== // @name Search Selection Keyboard Shortcut -// @version 1.7.2 +// @version 1.7.3 // @author aminomancer // @homepage https://github.com/aminomancer // @description Adds a new keyboard shortcut (Ctrl+Shift+F) that searches your default search // engine for whatever text you currently have highlighted. This does basically the same thing as // the context menu option "Search {Engine} for {Selection}" except that if you highlight a URL, // instead of searching for the selection it will navigate directly to the URL. Optionally, you can -// also configure the script to use your other (non-default) search engines as well. The preference -// "userChrome.searchSelectionShortcut.match-engine-to-current-tab" will add a second hotkey -// (Ctrl+Alt+F) that will look for an installed engine that matches the current webpage. So if your -// default search engine is Google but you use the hotkey on Wikipedia, and you have a search engine -// for Wikipedia installed, it will search Wikipedia for the selected text instead. This preference -// is disabled by default. But what if you have a non-default search engine that you want to use for -// a particular website? Let's say you're on about:config, browsing through preferences. You -// highlight a pref name and hit the hotkey to search for it and find out what it does. Normally, -// pressing the second hotkey will launch your default engine, since about:config doesn't correspond -// to any normal URL. But by setting the pref "userChrome.searchSelectionShortcut.custom-matches", -// you can "link" any website to any engine. This pref accepts a JSON formatted object containing -// zero or more name-value pairs, separated by commas. The object format is {: } +// also configure the script to use your other (non-default) search engines as well. + +// The preference `userChrome.searchSelectionShortcut.match-engine-to-current-tab` will add a second +// hotkey (Ctrl+Alt+F) that will look for an installed engine that matches the current webpage. So +// if your default search engine is Google but you use the hotkey on Wikipedia, and you have a +// search engine for Wikipedia installed, it will search Wikipedia for the selected text instead. +// This preference is disabled by default, since some extensions may use that key combination. You +// can toggle it in a popup that appears the first time you install the script, or in about:config. + +// But what if you have a non-default search engine that you want to use for a particular website? +// Let's say you're on about:config, browsing through preferences. You highlight a pref name and hit +// the hotkey to search for it and find out what it does. Normally, pressing the second hotkey will +// launch your default engine, since about:config doesn't correspond to any normal URL. But by +// setting the pref `userChrome.searchSelectionShortcut.custom-matches`, you can "link" any website +// to any engine you have installed. + +// This pref accepts a JSON-formatted object containing zero or more name-value pairs, separated by +// commas. This object can also include one reserved property called REG_EXPS, which uses regular +// expressions instead of URL strings. The object format is: +// { +// "REG_EXPS": { +// : , +// : +// }, +// : , +// : +// } + // Here's an example: -// {"about:config": "Searchfox", "bugzilla.mozilla.org": "searchfox.org", "raw.githubusercontent.com": "https://github.com/search?q=%s"} -// This should basically explain the options. represents a website you might visit, -// represents the engine to use when you press the hotkey while on the . So the first one -// means use Searchfox when the hotkey is activated on about:config. This is JSON, so all and -// values must be wrapped in quotes and the pairs must be separated by commas, or the pref -// won't work at all. A value must be some kind of valid URL. Ideally a host (domain) is -// best, but it doesn't have to be a host, because some types of URLs lack hosts. If you're unsure -// what the host is for a website you're trying to link to an engine, open the website in a browser -// tab, open the content toolbox, and type location.host. For pages that lack hosts or have very -// specific protocols (like moz-extension:// URLs) you can specify the full page URL, like -// moz-extension://blahblah/index.html An value can be either 1) an engine's name — that's -// the label that appears next to the search engine in the UI, e.g. "Google"; 2) the domain on which -// the search engine is hosted, e.g. "www.google.com"; or 3) the engine's full search template URL, -// or something close to it, e.g. "www.google.com/search?q=%s". Any of these values will work, but -// using the engine's name is most efficient. You can change the hotkey itself (though not the -// modifiers) by setting "userChrome.searchSelectionShortcut.keycode" to a valid KeyboardEvent code. -// The default value "KeyF" corresponds to the F key. The correct notation is different for numbers -// and special characters, so visit https://keycode.info and press the desired key to find its -// event.code. Then input that string into the pref editor in about:config. Since v1.3 this script -// supports Fission by using JSActors instead of Message Managers. Normally JSActors require -// multiple files — a parent script and a child script, to communicate between the content frame and -// the parent process. And to instantiate them would require a third file, the autoconfig script. An -// autoconfig script requiring multiple additional files doesn't make for a very user-friendly -// experience. So this script automatically generates its own subscript files in your chrome folder -// and cleans them up when you quit Firefox. I had a lot of fun figuring this out. If you're trying -// to learn how to make these kinds of mods, this is a good subject to research since JSActors are -// really powerful. It's also cool to see how a standalone autoconfig script can be made to create -// its own little network of temp files to work in a more vanilla-style manner. +// { +// "REG_EXPS": { +// "^https?://bugzilla\\.mozilla\\.org(/.*)?$": "https://bugzilla.mozilla.org/buglist.cgi?quicksearch=%s", +// "^https?://(.*\\.)?(github|githubusercontent)\\.com(/.*)?$": "https://github.com/search?q=%s" +// }, +// "about:config": "Searchfox", +// "mozilla.org": "searchfox.org", +// "google.com": "https://www.google.com/search?client=firefox-b-1-d&q=%s" +// } +// The example above showcases several different accepted formats. or represents a +// website you might visit, and represents the engine to use when you press the hotkey +// while on the . So, the "about:config" one tells the script to use Searchfox when the hotkey +// is activated on about:config. This is JSON, so all values must be wrapped in quotes and the pairs +// must be separated by commas, or the pref won't work at all. All forward slashes must be escaped, +// so when escaping characters in your regular expressions, use two forward slashes instead of one. + +// The current URL will be tested against each in the REG_EXPS object. If a match is found, +// the corresponding will be used. If no match is found (or if the REG_EXPS object does not +// exist), the URL will be tested against each in the pref. If a match is found, the +// corresponding will be used. If no match is found, the default engine will be used. + +// A value must be a valid regular expression, wrapped in double quotes and escaped. + +// A value must be some kind of valid URL. Ideally a host (domain) is best, but it +// doesn't have to be a host, because some types of URLs lack hosts. If you're unsure what the host +// is for a website you're trying to link to an engine, open the website in a browser tab, open the +// content toolbox, and type location.host. For pages that lack hosts or have very important +// protocols (like `moz-extension://` URLs) you can specify the full page URL, like +// `moz-extension://blahblah/index.html` — or better yet, use a regular expression instead. + +// An value can be either: +// 1) an engine's name — the label that appears next to the search engine in the UI, e.g. "Google" +// 2) the domain on which the search engine is hosted, e.g. "www.google.com" +// 3) the engine's full search URL, or something close to it, e.g. "www.google.com/search?q=%s". +// Any of these values will work, but using the engine's name is most efficient + +// If you already use these hotkeys for something else, e.g., an extension, you can change the +// hotkey (though not the modifiers) by setting `userChrome.searchSelectionShortcut.keycode` to a +// valid KeyboardEvent code. The default value "KeyF" corresponds to the F key. The correct notation +// is different for numbers and special characters, so visit https://keycode.info and press the +// desired key to find the event.code you need to input for the preference. + +// This script automatically generates its own subscript files in your chrome folder and cleans them +// up when you quit Firefox. This is unfortunately necessary to avoid requiring users to download +// multiple files just to make a single script work. // @license This Source Code Form is subject to the terms of the Creative Commons Attribution-NonCommercial-ShareAlike International License, v. 4.0. If a copy of the CC BY-NC-SA 4.0 was not distributed with this file, You can obtain one at http://creativecommons.org/licenses/by-nc-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. // @include main // @startup searchSelectionShortcut @@ -147,7 +180,7 @@ class SearchSelectionShortcut { // it assumes you don't want to keep an empty tab around, so it'll open the // search/link in the current tab. this.parentFile = await this.createTempFile( - `"use strict";import{XPCOMUtils}from"resource://gre/modules/XPCOMUtils.sys.mjs";const lazy={};XPCOMUtils.defineLazyModuleGetters(lazy,{BrowserWindowTracker:"resource:///modules/BrowserWindowTracker.jsm",PrivateBrowsingUtils:"resource://gre/modules/PrivateBrowsingUtils.jsm",E10SUtils:"resource://gre/modules/E10SUtils.jsm"});XPCOMUtils.defineLazyPreferenceGetter(lazy,"CUSTOM_MATCHES","userChrome.searchSelectionShortcut.custom-matches","{}",null,(val=>JSON.parse(val)));const{WebExtensionPolicy}=Cu.getGlobalForObject(Services);const schemes=/^http|https|ftp$/;const base=host=>{let domain;try{domain=Services.eTLD.getBaseDomainFromHost(host)}catch(e){}return domain};export class SearchSelectionShortcutParent extends JSWindowActorParent{get browser(){return this.browsingContext.top.embedderElement}getEngineTemplate(e){const engineURL=e._getURLOfType("text/html");return engineURL.params.length>0?e._searchForm:engineURL.template}async getMatchingEngine(match,url,host,check=true){if(!match)return null;let preferred;let uri=Services.io.newURI(url);if(check){if(url in lazy.CUSTOM_MATCHES)preferred=lazy.CUSTOM_MATCHES[url];if(!preferred&&host in lazy.CUSTOM_MATCHES)preferred=lazy.CUSTOM_MATCHES[host];if(!preferred&&!host){try{preferred=lazy.CUSTOM_MATCHES[uri.prePath+uri.filePath]}catch(e){}}if(preferred){const engine=Services.search.getEngineByName(preferred);if(engine&&!engine.hidden)return engine}}const visibleEngines=await Services.search.getVisibleEngines();let originalHost;if(preferred&&/.+\\..+/.test(preferred)){originalHost=host;host=preferred}let engines=visibleEngines.filter((engine=>engine.getResultDomain()==host));if(!engines.length){const baseHost=base(host);if(baseHost||!preferred)engines=visibleEngines.filter((engine=>base(engine.getResultDomain())==baseHost))}if(originalHost&&!engines.length){try{const fixup=Services.uriFixup.getFixupURIInfo(preferred,Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS);uri=fixup.fixedURI;engines=visibleEngines.filter((engine=>engine.getResultDomain()==uri.host))}catch(e){}if(!engines.length)return this.getMatchingEngine(match,url,originalHost,false)}if(engines.length>1){engines.sort(((a,b)=>{const uriA=Services.io.newURI(this.getEngineTemplate(a)),uriB=Services.io.newURI(this.getEngineTemplate(b)),cmnA=this.commonLength(uri,uriA),cmnB=this.commonLength(uri,uriB);return cmnB.host-cmnA.host||cmnB.path-cmnA.path||cmnB.query-cmnA.query}))}return engines[0]}commonLength(x,y){if(!(x?.spec&&y?.spec))return 0;let xh="",yh="";try{xh=x.host}catch(e){}try{yh=y.host}catch(e){}let xf=x.filePath,yf=y.filePath,xs=x.scheme,ys=y.scheme||"https",xq=x.query,yq=y.query,i=0,k=0,len=xh.length,sq="";if(xs!=ys&&!(schemes.test(xs)&&schemes.test(ys)))return 0;while(k{if(p.endsWith("{searchTerms}")){qp=p.replace(/{searchTerms}/,"");return}return true}));xa=xa.filter((p=>!(qp&&p.startsWith(qp))));sq=xa.filter((p=>ya.includes(p)))}return{host:xh.substring(len-k,len).length,path:xf.substring(0,i).length,query:sq.length}}stripURLPrefix(str){const match=/^[a-z]+:(?:\\/){0,2}/i.exec(str);if(!match)return["",str];let prefix=match[0];if(prefix.length{let domain;try{domain=Services.eTLD.getBaseDomainFromHost(host)}catch(e){}return domain};export class SearchSelectionShortcutParent extends JSWindowActorParent{get browser(){return this.browsingContext.top.embedderElement}getEngineTemplate(e){const engineURL=e._getURLOfType("text/html");return engineURL.params.length>0?e._searchForm:engineURL.template}async getMatchingEngine(match,url,host,check=true){if(!match)return null;let preferred;let uri=Services.io.newURI(url);if(check){let MATCHES=JSON.parse(lazy.CUSTOM_MATCHES);if(MATCHES.REG_EXPS){for(let[regExp,engineStr]of Object.entries(MATCHES.REG_EXPS)){if(new RegExp(regExp)?.test(url)){preferred=engineStr;break}}delete MATCHES.REG_EXPS}if(!preferred&&url in MATCHES)preferred=MATCHES[url];if(!preferred&&host in MATCHES)preferred=MATCHES[host];if(!preferred&&!host){try{preferred=MATCHES[uri.prePath+uri.filePath]}catch(e){}}if(preferred){const engine=Services.search.getEngineByName(preferred);if(engine&&!engine.hidden)return engine}}const visibleEngines=await Services.search.getVisibleEngines();let originalHost;if(preferred&&/.+\\..+/.test(preferred)){originalHost=host;host=preferred}let engines=visibleEngines.filter((engine=>engine.getResultDomain()==host));if(!engines.length){const baseHost=base(host);if(baseHost||!preferred)engines=visibleEngines.filter((engine=>base(engine.getResultDomain())==baseHost))}if(originalHost&&!engines.length){try{const fixup=Services.uriFixup.getFixupURIInfo(preferred,Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS);uri=fixup.fixedURI;engines=visibleEngines.filter((engine=>engine.getResultDomain()==uri.host))}catch(e){}if(!engines.length)return this.getMatchingEngine(match,url,originalHost,false)}if(engines.length>1){engines.sort(((a,b)=>{const uriA=Services.io.newURI(this.getEngineTemplate(a)),uriB=Services.io.newURI(this.getEngineTemplate(b)),cmnA=this.commonLength(uri,uriA),cmnB=this.commonLength(uri,uriB);return cmnB.host-cmnA.host||cmnB.path-cmnA.path||cmnB.query-cmnA.query}))}return engines[0]}commonLength(x,y){if(!(x?.spec&&y?.spec))return 0;let xh="",yh="";try{xh=x.host}catch(e){}try{yh=y.host}catch(e){}let xf=x.filePath,yf=y.filePath,xs=x.scheme,ys=y.scheme||"https",xq=x.query,yq=y.query,i=0,k=0,len=xh.length,sq="";if(xs!=ys&&!(schemes.test(xs)&&schemes.test(ys)))return 0;while(k{if(p.endsWith("{searchTerms}")){qp=p.replace(/{searchTerms}/,"");return}return true}));xa=xa.filter((p=>!(qp&&p.startsWith(qp))));sq=xa.filter((p=>ya.includes(p)))}return{host:xh.substring(len-k,len).length,path:xf.substring(0,i).length,query:sq.length}}stripURLPrefix(str){const match=/^[a-z]+:(?:\\/){0,2}/i.exec(str);if(!match)return["",str];let prefix=match[0];if(prefix.length💬 **_More details..._** +Adds a new keyboard shortcut (Ctrl+Shift+F) that searches for whatever text you currently have highlighted. This does basically the same thing as the context menu option "Search {Engine} for {Selection}" except that if you highlight a URL, (meaning text that is likely a URL, even if it's not a clickable hyperlink) instead of searching for the selection it will navigate directly to the URL. Optionally, you can also configure the script to use your other (non-default) search engines as well.
💬 **_More details..._** -Optionally, you can also configure the script to use your other (non-default) search engines as well. The preference `userChrome.searchSelectionShortcut.match-engine-to-current-tab` will add a second hotkey (Ctrl+Alt+F) that will look for an installed engine that matches the current webpage. So if your default search engine is Google but you use the hotkey on Wikipedia, and you have a search engine for Wikipedia installed, it will search Wikipedia for the selected text instead of Google. This preference is disabled by default, since some extensions may use that key combination. You can toggle it in a popup that appears the first time you install the script, or in about:config. +The preference `userChrome.searchSelectionShortcut.match-engine-to-current-tab` will add a second hotkey (Ctrl+Alt+F) that will look for an installed engine that matches the current webpage. So if your default search engine is Google but you use the hotkey on Wikipedia, and you have a search engine for Wikipedia installed, it will search Wikipedia for the selected text instead of Google. This preference is disabled by default, since some extensions may use that key combination. You can toggle it in a popup that appears the first time you install the script, or in about:config. -But what if you have a non-default search engine that you want to use for a particular website? Let's say you're on about:config, browsing through preferences. You highlight a pref name and hit the hotkey to search for it and find out what it does. Normally, pressing the second hotkey will launch your default engine, since about:config doesn't correspond to any normal URL. But by setting the pref `userChrome.searchSelectionShortcut.custom-matches`, you can "link" any website to any engine. This pref accepts a JSON formatted object containing zero or more name-value pairs, separated by commas. The object format is `{: }` +But what if you have a non-default search engine that you want to use for a particular website? Let's say you're on about:config, browsing through preferences. You highlight a pref name and hit the hotkey to search for it and find out what it does. Normally, pressing the second hotkey will launch your default engine, since about:config doesn't correspond to any normal URL. But by setting the pref `userChrome.searchSelectionShortcut.custom-matches`, you can "link" any website to any engine you have installed. -Here's an example: `{"about:config": "Searchfox", "bugzilla.mozilla.org": "searchfox.org", "raw.githubusercontent.com": "https://github.com/search?q=%s"}` +This pref accepts a JSON-formatted object containing zero or more name-value pairs, separated by commas. This object can also include one reserved property called `REG_EXPS`, which uses regular expressions instead of URL strings. The object format is: -The example above showcases several different accepted formats. `site` represents a website you might visit, and `engine` represents the engine to use when you press the hotkey while on the `site`. So the first one means _use Searchfox when the hotkey is activated on about:config_. This is JSON, so all `site` and `engine` values must be wrapped in quotes and the pairs must be separated by commas, or the pref won't work at all. +``` +{ + REG_EXPS: { + : , + : + }, + : , + : +} +``` + +Here's an example: + +```json +{ + "REG_EXPS": { + "^https?://bugzilla\\.mozilla\\.org(/.*)?$": "https://bugzilla.mozilla.org/buglist.cgi?quicksearch=%s", + "^https?://(.*\\.)?(github|githubusercontent)\\.com(/.*)?$": "https://github.com/search?q=%s" + }, + "about:config": "Searchfox", + "mozilla.org": "searchfox.org", + "google.com": "https://www.google.com/search?client=firefox-b-1-d&q=%s" +} +``` + +The example above showcases several different accepted formats. `` or `` represents a website you might visit, and `` represents the engine to use when you press the hotkey while on the ``. So, the "about:config" one tells the script to _use Searchfox when the hotkey is activated on about:config_. This is JSON, so all values must be wrapped in quotes and the pairs must be separated by commas, or the pref won't work at all. All forward slashes must be escaped, so when escaping characters in your regular expressions, use two forward slashes instead of one. + +The current URL will be tested against each `` in the `REG_EXPS` object. If a match is found, the corresponding `` will be used. If no match is found (or if the `REG_EXPS` object does not exist), the URL will be tested against each `` in the pref. If a match is found, the corresponding `` will be used. If no match is found, the default engine will be used. + +A `` value must be a valid regular expression, wrapped in double quotes and escaped. + +A `` value must be some kind of valid URL. Ideally a host (domain) is best, but it doesn't have to be a host, because some types of URLs lack hosts. If you're unsure what the host is for a website you're trying to link to an engine, open the website in a browser tab, open the content toolbox, and type `location.host`. For pages that lack hosts or have very important protocols (like `"moz-extension://"` URLs) you can specify the full page URL, like `"moz-extension://blahblah/index.html"` — or better yet, use a regular expression instead. + +An value can be either: +1. an engine's name — the label that appears next to the search engine in the UI, e.g. `"Google"` +2. the domain on which the search engine is hosted, e.g. `"www.google.com"` +3. the engine's full search URL, or something close to it, e.g. `"www.google.com/search?q=%s"` -A `site` value must be some kind of valid URL. Ideally a host (domain) is best, but it doesn't have to be a host, as some types of URLs lack hosts. If you're unsure what the host is for a website you're trying to link to an engine, open the website in a browser tab, open the content toolbox, and type `location.host`. For pages that lack hosts or have local protocols (like `moz-extension://` URLs) you can specify the page's complete URL, like `moz-extension://blahblah/index.html` +Any of these values will work, but using the engine's name is most efficient. -An `engine` value can be either 1) an engine's name — that's the label that appears next to the search engine in the UI, e.g. `Google`; 2) the domain on which the search engine is hosted, e.g. `www.google.com`; or 3) the engine's full search template URL, or something close to it, e.g. `www.google.com/search?q=%s`. Any of these values will work, but using the engine's name is most efficient. +If you already use these hotkeys for something else, e.g., an extension, you can change the hotkey (though not the modifiers) by setting `userChrome.searchSelectionShortcut.keycode` to a valid [KeyboardEvent code](https://developer.mozilla.org/docs/Web/API/KeyboardEvent/code). The default value `KeyF` corresponds to the F key. The correct notation is different for numbers and special characters, so visit [keycode.info](https://keycode.info) and press your desired key to find the `event.code` you need to input for the preference. -If you already use these hotkeys for something else, e.g., an extension, you can change the hotkey itself (though not the modifiers) by setting `userChrome.searchSelectionShortcut.keycode` to a valid [KeyboardEvent code](https://developer.mozilla.org/docs/Web/API/KeyboardEvent/code). The default value `KeyF` corresponds to the F key. The correct notation is different for numbers and special characters, so visit [keycode.info](https://keycode.info) and press your desired key to find the `event.code` you need to input for the preference. In the future I may add modifier customization support, if users request it. +This script automatically generates its own subscript files in your chrome folder and cleans them up when you quit Firefox. This is unfortunately necessary to avoid requiring users to download multiple files just to make a single script work.