This commit is contained in:
Babil Golam Sarwar 2018-05-21 03:01:01 +10:00
commit 3f205b5d7b
21 changed files with 220 additions and 96 deletions

View file

@ -11,7 +11,9 @@
- `native` support for Windows: just do what `installnative` tells you to
- you'll probably want to make sure `gvim` is on your path
- **Potentially breaking change**: pipes in `composite` now send return values to the following ex command. Use semi-colons if you want the old behaviour back (see `bind D`).
- **Potentially breaking changes**
- pipes in `composite` now send return values to the following ex command. Use semi-colons if you want the old behaviour back (see `bind D`).
- the `DocStart` now uses `String.prototype.search` for matching, so you can use regular expressions such as `/www\.amazon\.co.*/`
- Add internal functions for editing `user.js` - we'll probably add a nice interface to this some day.

View file

@ -1,6 +1,6 @@
## Control your browser with your keyboard *only*.
Replace Firefox's control mechanism with one modelled on VIM.
Replace Firefox's control mechanism with one modelled on VIM. This is a "Firefox Quantum" replacement for VimFX, Vimperator and Pentadactyl.
Most common tasks you want your browser to perform are bound to a single key
press:
@ -18,7 +18,7 @@ you have `:native` working.
- But how do you use your browser now? `Shift-Insert` again and we're back on.
The list could go on a bit here, but I guess you'll get the point. If you feel
lost sometimes `:help` might help you a lot.
lost sometimes `:help` might help you a lot, and there's always `:tutor`.
**Highlighted features:**

View file

@ -1,10 +1,23 @@
#!/bin/sh
#!/bin/bash
source ./scripts/common
jsfiles=$(cachedJS)
jsfiles=$(git diff --cached --name-only --diff-filter=ACM "*.js" "*.jsx" "*.ts" "*.tsx" | tr '\n' ' ')
[ -z "$jsfiles" ] && exit 0
# Prettify all staged .js files
echo "$jsfiles" | xargs ./node_modules/.bin/prettier --write
# Check if any of the files are ugly or contain a console.log call
consoleFiles=$(noisy $jsfiles)
uglyFiles=$(ugly $jsfiles)
# Add back the modified/prettified files to staging
echo "$jsfiles" | xargs git add
if [ -n "$consoleFiles" ]; then
echo "Warning: adding console.log calls in ${consoleFiles[@]}"
echo 'Did you mean to use logger.debug?'
echo
fi
if [ -n "$uglyFiles" ]; then
echo "Prettify your files first:"
echo '$(npm bin)/prettier --write' "${uglyFiles[@]}"
exit 1
fi

6
package-lock.json generated
View file

@ -2011,6 +2011,10 @@
"integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=",
"dev": true
},
"csp-serdes": {
"version": "github:cmcaine/csp-serdes#6c6fe34dbd138855e5c26f331b094e9a75358a64",
"from": "github:cmcaine/csp-serdes"
},
"css": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/css/-/css-2.2.1.tgz",
@ -9376,7 +9380,7 @@
}
},
"web-ext-types": {
"version": "github:kelseasy/web-ext-types#5f6e888318984c185413f8943d2616915e2af88f",
"version": "github:kelseasy/web-ext-types#53d82dcea599d34a23b5f4aa6a9c616613c7adc2",
"from": "github:kelseasy/web-ext-types",
"dev": true
},

View file

@ -5,6 +5,7 @@
"dependencies": {
"@types/css": "0.0.31",
"@types/nearley": "^2.11.0",
"csp-serdes": "github:cmcaine/csp-serdes",
"css": "^2.2.1",
"fuse.js": "^3.2.0",
"mark.js": "^8.11.1",
@ -34,11 +35,13 @@
},
"scripts": {
"build": "sh scripts/build.sh",
"run": "web-ext run --firefox=firefox -s build/ -u 'txti.es'",
"run": "web-ext run -s build/ -u 'txti.es'",
"watch": "echo 'watch is broken, use build instead'; exit 0; chokidar src scripts --initial --silent -i 'src/excmds_{background,content}.ts' -i 'src/static/docs' -c 'npm run build'",
"clean": "rm -rf build generated",
"test": "npm run build && jest --silent",
"update-buildsystem": "rm -rf src/node_modules; npm run clean"
"update-buildsystem": "rm -rf src/node_modules; npm run clean",
"lint": "bash hooks/pre-commit",
"pretty": "bash scripts/pretty"
},
"jest": {
"transform": {

View file

@ -102,6 +102,7 @@ Extended hint modes allow you to perform actions on page items:
- `;s`/`;a` — save/save-as the linked resource
- `;S`/`;A` — save/save-as the selected image
- `;p` — copy an element's text to the clipboard
- `;P` — copy an element's title/alt text to the clipboard
- `;y` — copy an element's link URL to the clipboard
- `;#` — copy an element's anchor URL to the clipboard
- `;r` — read the element's text with text-to-speech
@ -146,7 +147,7 @@ See `:help bind` for details about this command.
- Can I import/export settings, and does Tridactyl use an external configuration file just like Vimperator?
Yes, if you have `native` working, `$XDG_CONFIG_DIR/tridactyl/tridactylrc` or `~/.tridactylrc` will be read at startup via an `autocmd` and `source`. There is an (example file available on our repository)[https://github.com/cmcaine/tridactyl/blob/master/.tridactylrc].
Yes, if you have `native` working, `$XDG_CONFIG_DIR/tridactyl/tridactylrc` or `~/.tridactylrc` will be read at startup via an `autocmd` and `source`. There is an [example file available on our repository](https://github.com/cmcaine/tridactyl/blob/master/.tridactylrc).
If you can't use the native messenger for some reason, there is a workaround: if you do `set storageloc local`, a JSON file will appear at `<your firefox profile>\browser-extension-data\tridactyl.vim@cmcaine.co.uk\storage.js`. You can find your profile folder by going to `about:support`. You can edit this file to your heart's content.

23
scripts/common Executable file
View file

@ -0,0 +1,23 @@
#!/bin/bash
cachedJS() {
git diff --cached --name-only --diff-filter=ACM "*.js" "*.jsx" "*.ts" "*.tsx" | tr '\n' ' '
}
ugly() {
local acc=()
for jsfile in "$@"; do
diff "$jsfile" <($(npm bin)/prettier "$jsfile") >/dev/null || acc+=("$jsfile")
done
echo $acc
}
noisy() {
local acc=()
for jsfile in "$@"; do
if [ "$(git diff --cached "$jsfile" | grep '^+.*console.log' -c)" -gt '0' ] ; then
acc+=("jsfile")
fi
done
echo $acc
}

9
scripts/pretty Executable file
View file

@ -0,0 +1,9 @@
#!/bin/bash
source ./scripts/common
uglyFiles=$(ugly $(cachedJS))
if [ -n "$uglyFiles" ]; then
prettier --write "$uglyFiles"
fi

View file

@ -55,28 +55,35 @@ import * as webext from "./lib/webext"
l: prom => prom.then(console.log).catch(console.error),
})
// {{{ Clobber CSP
// This should be removed once https://bugzilla.mozilla.org/show_bug.cgi?id=1267027 is fixed
let cspListener
if (config.get("csp") == "clobber") {
cspListener = browser.webRequest.onHeadersReceived.addListener(
request.addurltocsp,
function addCSPListener() {
browser.webRequest.onHeadersReceived.addListener(
request.clobberCSP,
{ urls: ["<all_urls>"], types: ["main_frame"] },
["blocking", "responseHeaders"],
)
}
function removeCSPListener() {
browser.webRequest.onHeadersReceived.removeListener(request.clobberCSP)
}
config.getAsync("csp").then(csp => csp === "clobber" && addCSPListener())
browser.storage.onChanged.addListener((changes, areaname) => {
if (config.get("csp") == "clobber") {
cspListener = browser.webRequest.onHeadersReceived.addListener(
request.addurltocsp,
{ urls: ["<all_urls>"], types: ["main_frame"] },
["blocking", "responseHeaders"],
)
} else {
// This doesn't work. :(
// browser.webRequest.onHeadersReceived.removeListener(cspListener)
if ("userconfig" in changes) {
if (changes.userconfig.newValue.csp === "clobber") {
addCSPListener()
} else {
removeCSPListener()
}
}
})
// }}}
// Prevent Tridactyl from being updated while it is running in the hope of fixing #290
browser.runtime.onUpdateAvailable.addListener(_ => {})

View file

@ -34,18 +34,18 @@ async function init() {
)
cmdline_iframe.setAttribute("id", "cmdline_iframe")
hide()
window.document.documentElement.appendChild(cmdline_iframe)
document.documentElement.appendChild(cmdline_iframe)
} catch (e) {
logger.error("Couldn't initialise cmdline_iframe!", e)
}
}
}
// Load the iframe immediately if the document is already complete (happens if tridactyl is reloaded)
// Load the iframe immediately if we can (happens if tridactyl is reloaded or on ImageDocument)
// Else load lazily to avoid upsetting page JS that hates foreign iframes.
if (document.readyState === "complete") {
try {
init()
} else {
} catch (e) {
// Surrender event loop with setTimeout() to page JS in case it's still doing stuff.
document.addEventListener("DOMContentLoaded", () => setTimeout(init, 0))
}

View file

@ -114,6 +114,7 @@ const DEFAULTS = o({
";k": "hint -k",
";y": "hint -y",
";p": "hint -p",
";P": "hint -P",
";r": "hint -r",
";s": "hint -s",
";S": "hint -S",
@ -132,6 +133,8 @@ const DEFAULTS = o({
zo: "zoom -0.1 true",
zz: "zoom 1",
".": "repeat",
gow: "open http://www.bbc.co.uk/news/live/uk-44167290",
gnw: "tabopen http://www.bbc.co.uk/news/live/uk-44167290",
"<SA-ArrowUp><SA-ArrowUp><SA-ArrowDown><SA-ArrowDown><SA-ArrowLeft><SA-ArrowRight><SA-ArrowLeft><SA-ArrowRight>ba":
"open https://www.youtube.com/watch?v=M3iOROuTuMA",
}),
@ -174,8 +177,11 @@ const DEFAULTS = o({
openwith: "hint -W",
"!": "exclaim",
"!s": "exclaim_quiet",
colorscheme: "set theme",
colors: "set theme",
colourscheme: "set theme",
colours: "colourscheme",
colorscheme: "colourscheme",
colors: "colourscheme",
man: "help",
"!js": "js",
"!jsb": "jsb",
current_url: "composite get_current_url | fillcmdline_notrail ",

View file

@ -98,19 +98,9 @@ config.getAsync("modeindicator").then(mode => {
: ""
statusIndicator.className =
"cleanslate TridactylStatusIndicator " + privateMode
try {
// On quick loading pages, the document is already loaded
statusIndicator.textContent = state.mode || "normal"
document.body.appendChild(statusIndicator)
document.head.appendChild(style)
} catch (e) {
// But on slower pages we wait for the document to load
window.addEventListener("DOMContentLoaded", () => {
statusIndicator.textContent = state.mode || "normal"
document.body.appendChild(statusIndicator)
document.head.appendChild(style)
})
}
statusIndicator.textContent = state.mode
dom.appendTo(document.body, statusIndicator)
dom.appendTo(document.head, style)
browser.storage.onChanged.addListener((changes, areaname) => {
if (areaname === "local" && "state" in changes) {

View file

@ -83,7 +83,11 @@ function* ParserController() {
acceptExCmd(exstr)
} catch (e) {
// Rumsfeldian errors are caught here
logger.error("An error occurred in the controller: ", e)
logger.error(
"An error occurred in the controller: ",
e,
" ¯\\_(ツ)_/¯",
)
}
}
}

View file

@ -492,3 +492,15 @@ export function setupFocusHandler(): void {
// Handles when the page tries to select an input
hijackPageFocusFunction()
}
export function appendTo(parent, child) {
try {
// On quick loading pages, the document is already loaded
parent.appendChild(child)
} catch (e) {
// But on slower pages we wait for the document to load
window.addEventListener("DOMContentLoaded", () =>
parent.appendChild(child),
)
}
}

View file

@ -49,7 +49,7 @@
- [[bind]] new commands with e.g. `:bind J tabnext`
- Type `:help` to see a list of available excmds
- Use `yy` to copy the current page URL to your clipboard
- `]]` and `[[` to navigate through the pages of comics, paginated
- `[[`and `]]` to navigate through the pages of comics, paginated
articles, etc
- Pressing `ZZ` will close all tabs and windows, but it will only "save"
them if your about:preferences are set to "show your tabs and windows
@ -57,11 +57,8 @@
There are some caveats common to all webextension vimperator-alikes:
- To make Tridactyl work on addons.mozilla.org, about:\*, some file:\*
URIs, view-source:\*, or data:\*, you need to open `about:config`, add a
new boolean `privacy.resistFingerprinting.block_mozAddonManager` with the
value `true`, and remove the above domains from
`extensions.webextensions.restrictedDomains`.
- To make Tridactyl work on addons.mozilla.org and some other Mozilla domains, you need to open `about:config`, run [[fixamo]] or add a new boolean `privacy.resistFingerprinting.block_mozAddonManager` with the value `true`, and remove the above domains from `extensions.webextensions.restrictedDomains`.
- Tridactyl can't run on about:\*, some file:\* URIs, view-source:\*, or data:\*, URIs.
- To change/hide the GUI of Firefox from Tridactyl, you can use [[guiset]]
with the native messenger installed (see [[native]] and
[[installnative]]). Alternatively, you can edit your userChrome yourself.
@ -762,7 +759,7 @@ export function home(all: "false" | "true" = "false") {
*/
//#background
export async function help(excmd?: string) {
const docpage = browser.extension.getURL("static/docs/modules/_excmds_.html")
const docpage = browser.extension.getURL("static/docs/modules/_src_excmds_.html")
if (excmd === undefined) excmd = ""
if ((await activeTab()).url.startsWith(docpage)) {
open(docpage + "#" + excmd)
@ -1049,7 +1046,7 @@ export async function loadaucmds() {
let aucmds = await config.getAsync("autocmds", "DocStart")
const ausites = Object.keys(aucmds)
// yes, this is lazy
const aukey = ausites.find(e => window.document.location.href.includes(e))
const aukey = ausites.find(e => window.document.location.href.search(e) >= 0)
if (aukey !== undefined) {
Messaging.message("commandline_background", "recvExStr", [aucmds[aukey]])
}
@ -1897,8 +1894,11 @@ export function searchsetkeyword(keyword: string, url: string) {
*/
//#background
export function set(key: string, ...values: string[]) {
if (!key || !values[0]) {
throw "Both key and value must be provided!"
if (!key) {
throw "Key must be provided!"
} else if (!values[0]) {
get(key)
return
}
const target = key.split(".")
@ -1935,8 +1935,9 @@ export function set(key: string, ...values: string[]) {
@param event Curently, only 'TriStart' and 'DocStart' are supported.
@param url For DocStart: the URL on which the events will trigger (currently
just uses "contains").
@param url For DocStart: a fragment of the URL on which the events will trigger, or a JavaScript regex (e.g, `/www\.amazon\.co.*\/`)
We just use [URL.search](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search).
For TriStart: A regular expression that matches the hostname of the computer
the autocmd should be run on. This requires the native messenger to be
@ -2189,6 +2190,7 @@ import * as hinting from "./hinting_background"
- -b open in background
- -y copy (yank) link's target to clipboard
- -p copy an element's text to the clipboard
- -P copy an element's title/alt text to the clipboard
- -r read an element's text with text-to-speech
- -i view an image
- -I view an image in a new tab
@ -2217,10 +2219,11 @@ import * as hinting from "./hinting_background"
"relatedopenpos": "related" | "next" | "last"
*/
//#background
export function hint(option?: string, selectors = "", ...rest: string[]) {
export function hint(option?: string, selectors?: string, ...rest: string[]) {
if (option === "-b") hinting.hintPageOpenInBackground()
else if (option === "-y") hinting.hintPageYank()
else if (option === "-p") hinting.hintPageTextYank()
else if (option === "-P") hinting.hintPageTitleAltTextYank()
else if (option === "-i") hinting.hintImage(false)
else if (option === "-I") hinting.hintImage(true)
else if (option === "-k") hinting.hintKill()
@ -2228,7 +2231,7 @@ export function hint(option?: string, selectors = "", ...rest: string[]) {
else if (option === "-S") hinting.hintSave("img", false)
else if (option === "-a") hinting.hintSave("link", true)
else if (option === "-A") hinting.hintSave("img", true)
else if (option === "-;") hinting.hintFocus()
else if (option === "-;") hinting.hintFocus(selectors)
else if (option === "-#") hinting.hintPageAnchorYank()
else if (option === "-c") hinting.hintPageSimple(selectors)
else if (option === "-r") hinting.hintRead()

View file

@ -450,6 +450,10 @@ function elementswithtext() {
])
}
function titleAltTextElements() {
return DOM.getElemsBySelector("[title], [alt]", [DOM.isVisible])
}
/** Returns elements that point to a saveable resource
*/
function saveableElements() {
@ -642,6 +646,14 @@ function hintPageTextYank() {
})
}
function hintPageTitleAltTextYank() {
hintPage(titleAltTextElements(), hint => {
messageActiveTab("commandline_frame", "setClipboard", [
hint.target.title ? hint.target.title : hint.target.alt,
])
})
}
function hintPageYank() {
hintPage(hintables(), hint => {
messageActiveTab("commandline_frame", "setClipboard", [
@ -683,8 +695,8 @@ function hintImage(inBackground) {
}
/** Hint elements to focus */
function hintFocus() {
hintPage(hintables(), hint => {
function hintFocus(selectors?) {
hintPage(hintables(selectors), hint => {
hint.target.focus()
})
}
@ -751,6 +763,7 @@ addListener(
hintPageExStr,
hintPageYank,
hintPageTextYank,
hintPageTitleAltTextYank,
hintPageAnchorYank,
hintPageOpenInBackground,
hintPageWindow,

View file

@ -16,6 +16,10 @@ export async function hintPageYank() {
return await messageActiveTab("hinting_content", "hintPageYank")
}
export async function hintPageTitleAltTextYank() {
return await messageActiveTab("hinting_content", "hintPageTitleAltTextYank")
}
export async function hintPageTextYank() {
return await messageActiveTab("hinting_content", "hintPageTextYank")
}
@ -54,8 +58,8 @@ export async function hintImage(inBackground) {
])
}
export async function hintFocus() {
return await messageActiveTab("hinting_content", "hintFocus")
export async function hintFocus(selectors?) {
return await messageActiveTab("hinting_content", "hintFocus", [selectors])
}
export async function hintRead() {

View file

@ -1,32 +1,53 @@
import * as config from "./config"
import * as csp from "csp-serdes"
export function addurltocsp(response) {
let headers = response["responseHeaders"]
let cspind = headers.findIndex(
header => header.name == "Content-Security-Policy",
)
// if it's found
if (cspind > -1) {
// Split the csp header up so we can manage it individually.
let csparr = [headers[cspind]["value"].split("; ")][0]
for (let i = 0; i < csparr.length; i++) {
// Add 'unsafe-inline' as a directive since we use it
if (csparr[i].indexOf("style-src") > -1) {
if (csparr[i].indexOf("'self'") > -1) {
csparr[i] = csparr[i].replace(
"'self'",
"'self' 'unsafe-inline'",
)
}
}
// Remove the element if it's a sandbox directive
if (csparr[i] === "sandbox") {
csparr.splice(i, 1)
}
}
// Join the header up after clobberin'
headers[cspind]["value"] = csparr.join("; ")
class DefaultMap extends Map {
constructor(private defaultFactory, ...args) {
super(...args)
}
get(key) {
let ans = super.get(key)
if (ans === undefined) {
ans = this.defaultFactory(key)
super.set(key, ans)
}
return ans
}
}
/**
* Reduce CSP safety to permit tridactyl to run correctly
*
* style-src needs 'unsafe-inline' (hinting styles) and 'self' (mode indicator hiding)
* script-src needs 'unsafe-eval' (event hijacking)
* - but that's pretty dangerous, so maybe we shouldn't just clobber it?
* sandbox must not be set
*
* This only needs to happen because of a Firefox bug and we should stop doing
* it when they fix the bug.
*/
export function clobberCSP(response) {
const headers = response["responseHeaders"]
const cspHeader = headers.find(
header => header.name.toLowerCase() === "content-security-policy",
)
if (cspHeader !== undefined) {
const policy = new DefaultMap(
() => new Set(),
csp.parse(cspHeader.value),
)
policy.delete("sandbox")
policy
.get("style-src")
.add("'unsafe-inline'")
.add("'self'")
// policy.get("script-src").add("'unsafe-eval'")
// Replace old CSP
cspHeader.value = csp.serialize(policy)
return { responseHeaders: headers }
} else {
return {}
}
return { responseHeaders: headers }
}

View file

@ -4,7 +4,7 @@
--background: #111;
}
:root.dark input {
:root.dark input, :root.dark #command-line-holder {
color: var(--foreground-bright);
background: var(--background);
}

View file

@ -7,17 +7,26 @@ body {
width: 100%;
}
#command-line-holder {
background:white;
font-family: monospace;
font-size: 9pt;
/* reduce the padding added by the colon so that the command line shows up roughly where it used to be */
padding-left: 0.125ex;
}
input {
width: 100%;
width: 97%;
font-family: monospace;
font-size: 9pt;
line-height: 1.5;
color: black;
background: white;
border: unset;
/* reduce the padding from the colon */
margin-left: -0.25ex;
/* we currently have a border from the completions */
/* border-top: solid 1px lightgray; */
padding-left: 0.5ex;
}
/* COMPLETIONS */

View file

@ -7,7 +7,7 @@
</head>
<body>
<div id="completions"></div>
<input id="tridactyl-input"></input>
<div id="command-line-holder">:<input id="tridactyl-input"></input></div>
<script src="../commandline_frame.js"></script>
</body>
</html>