Merge branch 'master' of github.com:cmcaine/tridactyl into glacambre-js_pipes

This commit is contained in:
Oliver Blanthorn 2018-05-28 21:45:38 +01:00
commit 61a01efeab
No known key found for this signature in database
GPG key ID: 2BB8C36BB504BFF3
20 changed files with 835 additions and 99 deletions

95
.appveyor.yml Normal file
View file

@ -0,0 +1,95 @@
# Build worker image (VM template)
image: Visual Studio 2017
# Build platform, i.e. x86, x64, Any CPU.
platform:
- Any CPU
# Set `git clone` directory
clone_folder: 'C:\Tridactyl'
init:
# Verify %PATH%
- ps: Write-Host "[+] Current PATH contains ..."
- ps: Write-Host $env:PATH.Replace(";", "`r`n")
# Verify Bash
- ps: Write-Host "[+] Location of Bash ..."
- ps: Get-Command -Name 'bash'
# Verify NPM
- ps: Write-Host "[+] Location of NPM ..."
- ps: Get-Command -Name 'npm'
# Verify software versions
- ps: Write-Host "[+] Verifying software verisons ..."
- sh --version
- bash --version
- node --version
- npm --version
#
# Python version will show "2.7" below, which is required to keep
# NPM's 'bunyan' > 'dtrace-provider' modules happy.
# 'Dtrace-provider' needs Python 'gyp' module, which is only
# available for Python-2. We will prepend Python-3.6 to $PATH,
# under 'build_script'.
#
- python --version
install:
#
# If there is a newer build queued for the same PR, cancel this
# one. The AppVeyor 'rollout builds' option is supposed to serve
# the same purpose but it is problematic because it tends to
# cancel builds pushed directly to master instead of just PR
# builds (or the converse).
#
# Credits: JuliaLang developers.
#
- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER `
-and $env:APPVEYOR_BUILD_NUMBER `
-ne ((Invoke-RestMethod `
https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds `
| Where-Object pullRequestId `
-eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) `
{ throw "Newer build in progress, giving this one up ..." }
# Change to build directory
- ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER
# Verify CWD
- ps: Write-Host "[+] Current working directory is ..."
- ps: Get-Location
- bash -e -l -c "cd $APPVEYOR_BUILD_FOLDER && ls -alh"
# Install Python modules
- ps: python -m pip install --upgrade pip
# - ps: python -m pip install --upgrade packager
- ps: python -m pip install --upgrade pyinstaller
# Install NPM modules
# - bash -e -l -c "cd /C/Tridactyl && npm install -g windows-build-tools"
- bash -e -l -c "cd $APPVEYOR_BUILD_FOLDER && npm install"
build_script:
# Add Python-3.6 to %PATH%
- ps: $env:PATH = "C:\Python36-x64\Scripts;$env:PATH"
- ps: $env:PATH = "C:\Python36-x64;$env:PATH"
- ps: Copy-Item -Path "C:\Python36-x64\Python.exe" -Destination "C:\Python36-x64\Python3.exe"
# Change to build directory and verify CWD
- ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER
- ps: Write-Host "[+] Current working directory is ..."
- ps: Get-Location
# Start build
- ps: Write-Host "[+] Current %PATH% under Bash ..."
- bash -e -l -c "echo $PATH"
- ps: Write-Host "[+] Current directory under Bash ..."
- bash -e -l -c "cd $APPVEYOR_BUILD_FOLDER && ls -alh"
- ps: Write-Host "[+] Starting 'npm run build' ..."
- bash -e -l -c "cd $APPVEYOR_BUILD_FOLDER && export PYINSTALLER=1 && npm run build"

5
.gitignore vendored
View file

@ -8,3 +8,8 @@ generated
web-ext-artifacts
yarn.lock
.vscode/
native/__pycache__
native/native_main
native_main.spec
.wine-pyinstaller/
tags

View file

51
native/gen_native_message.py Executable file
View file

@ -0,0 +1,51 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import json
import struct
def usage():
"""Show usage and exit with non-zero status."""
sys.stderr.write(
"\n[+] Usage: %s cmd [command] | %s\n"
% (os.path.basename(__file__), "native_main.py")
)
sys.stderr.write("\n - Note: Use '..' as key-value separator")
sys.stderr.write(
"\n - Example: %s %s %s %s | %s\n"
% (
os.path.basename(__file__),
"cmd..win_restart_firefox",
"profiledir..auto",
"browser..firefox",
"native_main.py",
)
)
exit(-1)
if __name__ == "__main__":
"""Main functionalities are here for now."""
separator = ".."
msg = dict()
if len(sys.argv) > 1:
for i in range(1, len(sys.argv)):
key = sys.argv[i].strip().split(separator)[0]
val = sys.argv[i].strip().split(separator)[1]
msg[key] = val
if len(sys.argv) == 1:
usage()
msg = json.dumps(msg)
msg = "\r\n" + msg + "\r\n"
msg = msg.encode("utf-8")
packed_len = struct.pack("@I", len(msg))
sys.stdout.buffer.write(packed_len + msg)
sys.stdout.flush()

View file

@ -1,5 +1,6 @@
Param (
[switch]$Uninstall = $false,
[switch]$NoPython= $false,
[string]$DebugDirBase = "",
[string]$InstallDirBase = ""
)
@ -8,26 +9,44 @@ Param (
# Global constants
#
$global:InstallDirName = ".tridactyl"
$global:MessengerBinName = "native_main.py"
$global:MessengerBinPyName = "native_main.py"
$global:MessengerBinExeName = "native_main.exe"
$global:MessengerBinWrapperFilename = "native_main.bat"
$global:MessengerManifestFilename = "tridactyl.json"
$global:PythonVersionStr = "Python 3"
$global:WinPython3Command = "py -3 -u"
$global:MessengerManifestReplaceStr = "REPLACE_ME_WITH_SED"
$global:MessengerFilesHttpUriBase = [string]::Format("{0}{1}",
"https://raw.githubusercontent.com",
"/cmcaine/tridactyl/master/native")
# $git_repo_owner should be "cmcaine" in final release
$git_repo_owner = "cmcaine"
# $git_repo_branch should be "master" in final release
$git_repo_branch = "cmcaine/master"
$git_repo_proto = "https"
$git_repo_host = "raw.githubusercontent.com"
$git_repo_name = "tridactyl"
$git_repo_dir = "native"
$global:MessengerFilesHttpUriBase = `
[string]::Format("{0}://{1}/{2}/{3}/{4}/{5}",
$git_repo_proto,
$git_repo_host,
$git_repo_owner,
$git_repo_name,
$git_repo_branch,
$git_repo_dir
)
$global:MessengerExeHttpUriBase = "https://tridactyl.cmcaine.co.uk/betas"
$global:MessengerManifestRegistryPath = `
"HKCU:\Software\Mozilla\NativeMessagingHosts\tridactyl"
$global:Uninstall = $Uninstall
$global:NoPython= $NoPython
$global:InstallDirBase = $InstallDirBase.Trim()
$global:DebugDirBase = $DebugDirBase.Trim()
function Get-PythonVersionStatus() {
try {
$pythonVersion = py -3 -u --version
$pythonVersion = Invoke-Expression `
"$global:WinPython3Command --version"
} catch {
$pythonVersion = ""
}
@ -150,12 +169,22 @@ function Set-InstallDir() {
}
}
function Get-MessengerBinName() {
$messengerBinName = $global:MessengerBinPyName
if ($global:NoPython -eq $true) { # system doesn't have python3
$messengerBinName = $global:MessengerBinExeName
}
Return $messengerBinName
}
function Get-MessengerBinPath() {
$messengerInstallDir = Get-MessengerInstallDir
$messengerBinName = Get-MessengerBinName
$native_messenger_binary_path = [string]::Format("{0}\{1}",
$messengerInstallDir,
$global:MessengerBinName)
$messengerBinName)
Return $native_messenger_binary_path.Trim()
}
@ -165,10 +194,18 @@ function Get-MessengerBinUri() {
-Date ((Get-Date).ToUniversalTime()) `
-UFormat %s)
$messengerBinUri = [string]::Format("{0}/{1}?{2}",
$global:MessengerFilesHttpUriBase,
$global:MessengerBinName,
$downloadStartTime)
$messengerBinName = Get-MessengerBinName
if ($global:NoPython -eq $true) { # system doesn't have python3
$messengerBinUri = [string]::Format("{0}/{1}",
$global:MessengerExeHttpUriBase,
$messengerBinName
)
} else {
$messengerBinUri = [string]::Format("{0}/{1}?{2}",
$global:MessengerFilesHttpUriBase,
$messengerBinName,
$downloadStartTime)
}
Return $messengerBinUri.Trim()
}
@ -178,15 +215,18 @@ function Set-MessengerBin() {
$messengerBinUri = Get-MessengerBinUri
if ($global:DebugDirBase.Length -gt 0) {
$messengerBinName = Get-MessengerBinName
$srcPath = [string]::Format("{0}\{1}",
$global:DebugDirBase,
$global:MessengerBinName)
$messengerBinName)
Write-Host "[+] Copying $srcPath ..."
Copy-Item `
-Path $srcPath `
-Destination $messengerBinPath
-Destination $messengerBinPath `
-Force `
} else {
Write-Host "[+] Downloading $messengerBinUri ..."
@ -233,10 +273,18 @@ function Get-MessengerBinWrapperPath() {
function Set-MessengerBinWrapper() {
$messengerBinPath = Get-MessengerBinPath
$messengerBinWrapperPath = Get-MessengerBinWrapperPath
if ($global:NoPython -eq $false) { # system has python3
$messengerWrapperContent = @"
@echo off
call py -3 -u $messengerBinPath
call $global:WinPython3Command $messengerBinPath
"@
} else { ## system does _not_ have python3
$messengerWrapperContent = @"
@echo off
call $messengerBinPath
"@
}
Write-Host "[+] Preparing $messengerBinWrapperPath ..."
@ -298,7 +346,8 @@ function Set-MessengerManifest() {
Copy-Item `
-Path $srcPath `
-Destination $messengerManifestPath
-Destination $messengerManifestPath `
-Force `
} else {
Write-Host "[+] Downloading $messengerManifestUri ..."
@ -437,23 +486,26 @@ function Set-MessengerManifestRegistry() {
}
function Set-MessengerInstall() {
# Check for Python 3
Write-Host "[+] Looking for Python 3 ..."
$pythonVersionStatus = Get-PythonVersionStatus
if (! $pythonVersionStatus) {
Write-Host " - Python 3 not found, quitting ..."
exit -1
} else {
$pythonPath = Get-Command "py" `
# Check if system has Python 3, unless user set # the
# `-NoPython` flag
if ($global:NoPython -eq $false) {
Write-Host "[+] Looking for Python 3 ..."
$pythonVersionStatus = Get-PythonVersionStatus
if (! $pythonVersionStatus) {
Write-Host " - Python 3 not found, will use EXE ..."
$global:NoPython = $true
} else {
$pythonPath = Get-Command "py" `
| Select-Object -ExpandProperty "Source"
Write-Host " - Python 3 found at: $pythonPath"
Write-Host " - Python 3 found at: $pythonPath"
}
}
# Prepare `.tridactyl` directory
$result = Set-InstallDir
# Prepare `native_main.py`
# Prepare `native_main.{py,exe}`
if ($result -eq $true) {
$result = Set-MessengerBin
}
@ -503,4 +555,3 @@ if ($global:Uninstall) {
}
Set-MessengerInstall

39
package-lock.json generated
View file

@ -2245,7 +2245,8 @@
"discontinuous-range": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
"integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo="
"integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=",
"dev": true
},
"dispensary": {
"version": "0.10.10",
@ -5781,9 +5782,9 @@
"integrity": "sha1-GA8fnr74sOY45BZq1S24eb6y/8U="
},
"marked": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz",
"integrity": "sha1-ssbGGPzOzk74bE/Gy4p8v1rtqNc=",
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.4.0.tgz",
"integrity": "sha512-tMsdNBgOsrUophCAFQl0XPe6Zqk/uy9gnue+jIIKhykO51hxyu6uNx7zBPy0+y/WKYVZZMspV9YeXLNdKk+iYw==",
"dev": true
},
"md5.js": {
@ -6096,13 +6097,15 @@
"optional": true
},
"nearley": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/nearley/-/nearley-2.11.0.tgz",
"integrity": "sha512-clqqhEuP0ZCJQ85Xv2I/4o2Gs/fvSR6fCg5ZHVE2c8evWyNk2G++ih4JOO3lMb/k/09x6ihQ2nzKUlB/APCWjg==",
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/nearley/-/nearley-2.13.0.tgz",
"integrity": "sha512-ioYYogSaZhFlCpRizQgY3UT3G1qFXmHGY/5ozoFE3dMfiCRAeJfh+IPE3/eh9gCZvqLhPCWb4bLt7Bqzo+1mLQ==",
"dev": true,
"requires": {
"nomnom": "~1.6.2",
"railroad-diagrams": "^1.0.0",
"randexp": "^0.4.2"
"randexp": "0.4.6",
"semver": "^5.4.1"
}
},
"node-dir": {
@ -6193,6 +6196,7 @@
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.6.2.tgz",
"integrity": "sha1-hKZqJgF0QI/Ft3oY+IjszET7aXE=",
"dev": true,
"requires": {
"colors": "0.5.x",
"underscore": "~1.4.4"
@ -6201,7 +6205,8 @@
"colors": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz",
"integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q="
"integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=",
"dev": true
}
}
},
@ -6941,12 +6946,14 @@
"railroad-diagrams": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
"integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234="
"integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=",
"dev": true
},
"randexp": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
"integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==",
"dev": true,
"requires": {
"discontinuous-range": "1.0.0",
"ret": "~0.1.10"
@ -7300,7 +7307,8 @@
"ret": {
"version": "0.1.15",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
"dev": true
},
"right-align": {
"version": "0.1.3",
@ -8786,6 +8794,12 @@
"typescript": "2.4.1"
},
"dependencies": {
"marked": {
"version": "0.3.19",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz",
"integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==",
"dev": true
},
"progress": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz",
@ -8855,7 +8869,8 @@
"underscore": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz",
"integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ="
"integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=",
"dev": true
},
"union-value": {
"version": "1.0.0",

View file

@ -9,7 +9,6 @@
"css": "^2.2.1",
"fuse.js": "^3.2.0",
"mark.js": "^8.11.1",
"nearley": "^2.11.0",
"semver-compare": "^1.0.0"
},
"devDependencies": {
@ -20,6 +19,8 @@
"cleanslate": "^0.10.1",
"copy-webpack-plugin": "^4.2.0",
"jest": "^21.2.1",
"marked": "^0.4.0",
"nearley": "^2.13.0",
"prettier": "^1.11.1",
"shared-git-hooks": "^1.2.1",
"source-map-loader": "^0.2.2",

View file

@ -95,7 +95,7 @@ Extended hint modes allow you to perform actions on page items:
* `;k` — delete an element from the page
* `;;` — focus an element
Additionally, you can bind to a custom CSS selector with `:hint -c [selector]` which is useful for site-specific versions of the standard `f` hint mode.
Additionally, you can hint elements matching a custom CSS selector with `:hint -c [selector]` which is useful for site-specific versions of the standard `f` hint mode.
### Binding custom commands
@ -230,7 +230,7 @@ $(npm bin)/web-ext build -s build
If you want to build a signed copy (e.g. for the non-developer release), you can do that with `web-ext sign`. You'll need some keys for AMO and to edit the application id in `src/manifest.json`. There's a helper script in `scripts/sign` that's used by our build bot and for manual releases.
#### Building on Windows
### Building on Windows
* Install [Git for Windows][win-git]
@ -243,6 +243,67 @@ If you want to build a signed copy (e.g. for the non-developer release), you can
[win-git]: https://git-scm.com/download/win
[win-nodejs]: https://nodejs.org/dist/v8.11.1/node-v8.11.1-x64.msi
[pyinstaller]: https://www.pyinstaller.org
[gpg4win]: https://www.gpg4win.org
<!--- ## Disable GPG signing for now, until decided otherwise later
### Cryptographically Verifying the Compiled Native Binary on Windows
- `native_main.py` is compiled to `native_main.exe` for Windows using [PyInstaller][pyinstaller]. The goal is to relieve Tridactyl users on Windows from having to install the whole Python 3 distribution.
- Due to `native_main.exe` being a binary-blob and difficult to easily review like the plain-text `native_main.py` counterpart, it is **strongly** recommended the users verify the SHA-256 hash and GPG signatures using the following commands on Powershell.
**Verifying SHA-256 Hash**
```
## Change directory to Tridactyl's native-messanger directory
PS C:\> cd "$env:HOME\.tridactyl"
## Run `dir` and check `native_main.exe` is found
PS C:\Users\{USERNAME}\.tridactyl> dir
## Download `native_main.exe.sha256` containing the SHA-256 sum
PS C:\Users\{USERNAME}\.tridactyl> iwr https://raw.githubusercontent.com/gsbabil/tridactyl/master/native/native_main.exe.sha256 -OutFile native_main.exe.sha256
## Print the SHA-256 sum from `native_main.exe.sha256`
PS C:\Users\{USERNAME}\.tridactyl> (Get-FileHash native_main.exe -Algorithm SHA256).Hash.ToLower()
## Compute SHA-256 sum from `native_main.exe`
PS C:\Users\{USERNAME}\.tridactyl> Get-Content -Path native_main.exe.sha256 | %{$_ .Split(' ')[0]}
## Compare results of the of the last two commands ...
```
**Verifying OpenPGP Signature***
- First, download [`GPG4Win`][gpg4win] from this website and
install in on your system
- Once `gpg2` is on your path, go to Powershell and run the
following commands to verify OpenPGP signature:
```
## Change directory to Tridactyl's native-messanger directory
PS C:\> cd "$env:HOME\.tridactyl"
## Run `dir` and check `native_main.exe` is found
PS C:\Users\{USERNAME}\.tridactyl> dir
## Download `native_main.exe.sig` containing the OpenPGP signature
PS C:\Users\{USERNAME}\.tridactyl> iwr https://raw.githubusercontent.com/gsbabil/tridactyl/master/native/native_main.exe.sig -OutFile native_main.exe.sig
## Download `gsbabil-pub.asc` to verify the signature
PS C:\Users\{USERNAME}\.tridactyl> iwr https://raw.githubusercontent.com/gsbabil/tridactyl/master/native/gsbabil-pub.asc -OutFile gsbabil-pub.asc
## Import `gsbabil-pub.asc` into your GPG key-ring
PS C:\Users\{USERNAME}\.tridactyl> gpg2 --armor --import gsbabil-pub.asc
## Verify signature using `gpg2`
PS C:\Users\{USERNAME}\.tridactyl> gpg2 --verify .\native_main.exe.sig .\native_main.exe
```
--->
### Development loop

View file

@ -3,4 +3,4 @@
# Put the AMO flavour text in your clipboard for easy pasting.
# AMO doesn't support all HTML in markdown so we strip it out.
marked doc/amo.md | sed -r "s/<.?p>//g" | sed -r "s/<.?h.*>//g" | xclip -selection "clipboard"
$(npm bin)/marked doc/amo.md | sed -r "s/<.?p>//g" | sed -r "s/<.?h.*>//g" | xclip -selection "clipboard"

View file

@ -6,7 +6,8 @@ CLEANSLATE="node_modules/cleanslate/docs/files/cleanslate.css"
isWindowsMinGW() {
local is_mingw="False"
if [ "$(uname | cut -c 1-5)" = "MINGW" ]; then
if [ "$(uname | cut -c 1-5)" = "MINGW" ] \
|| [ "$(uname | cut -c 1-4)" = "MSYS" ]; then
is_mingw="True"
fi
@ -37,7 +38,7 @@ scripts/newtab.md.sh
scripts/make_tutorial.sh
scripts/make_docs.sh &
nearleyc src/grammars/bracketexpr.ne \
$(npm bin)/nearleyc src/grammars/bracketexpr.ne \
> src/grammars/.bracketexpr.generated.ts
if [ "$(isWindowsMinGW)" = "True" ]; then

View file

@ -1,4 +1,4 @@
#!/bin/sh
dest=generated/static/docs
typedoc --theme src/static/typedoc/ --out $dest src --ignoreCompilerErrors
$(npm bin)/typedoc --theme src/static/typedoc/ --out $dest src --ignoreCompilerErrors
cp -r $dest build/static/

View file

@ -11,6 +11,6 @@ for page in $pages
do
fileroot=$(echo $page | cut -d'.' -f-1)
sed "/REPLACETHIS/,$ d" tutor.template.html > "$dest$fileroot.html"
marked $page >> "$dest$fileroot.html"
$(npm bin)/marked $page >> "$dest$fileroot.html"
sed "1,/REPLACETHIS/ d" tutor.template.html >> "$dest$fileroot.html"
done

View file

@ -8,7 +8,7 @@ newtab="../../generated/static/newtab.html"
newtabtemp="../../generated/static/newtab.temp.html"
sed "/REPLACETHIS/,$ d" newtab.template.html > "$newtabtemp"
marked newtab.md >> "$newtabtemp"
$(npm bin)/marked newtab.md >> "$newtabtemp"
sed "1,/REPLACETHIS/ d" newtab.template.html >> "$newtabtemp"
# Why think when you can pattern match?
@ -19,7 +19,7 @@ echo """
<label for="spoilerbutton" onclick="">Changelog</label>
<div class="spoiler">
""" >> "$newtab"
marked ../../CHANGELOG.md >> "$newtab"
$(npm bin)/marked ../../CHANGELOG.md >> "$newtab"
echo """
</div>
""" >> "$newtab"

167
scripts/wine-pyinstaller.sh Executable file
View file

@ -0,0 +1,167 @@
#!/bin/bash -e
# This script must be run from the root Tridactyl directory
TRIDIR="$(pwd)"
BUILDROOT="${TRIDIR}/.wine-pyinstaller"
OUTDIR="${BUILDROOT}/dist"
DLDIR="${BUILDROOT}/downloads"
PYVER="3.5.4"
PYDIR="${BUILDROOT}/python-${PYVER}"
WINPY_HASH="b5d90c5252a624117ccec8678862d6144710219737f06cd01deb1df963f639fd"
WINPY_EXE="${DLDIR}/winpython-${PYVER}.exe"
WINEDIR="${BUILDROOT}/wine"
WINEARCH="win32"
export WINEDEBUG="fixme-all"
# stop wine whining
export DISPLAY=
PREREQUISITES="tput printf 7z wine"
MIN_WINE_VER="3"
MIN_7ZIP_VER="16"
checkRequiredVersions() {
if [ -z "$(7z \
| awk '/Version/{print $3}' \
| grep "${MIN_7ZIP_VER}")" ]; then
colorEcho \
"[-] p7zip minimum version ${MIN_7ZIP_VER} required\n" \
"alert"
exit -1
fi
if [ -z "$(wine --version 2> /dev/null \
| grep "wine-${MIN_WINE_VER}")" ]; then
colorEcho \
"[-] wine minimum version ${MIN_WINE_VER} required\n" \
"alert"
exit -1
fi
}
stripWhitespace() {
local input="$@"
printf "${input}\n" | tr -d "[:space:]"
}
colorEcho() {
local COLOR_RESET=$(tput sgr0 2>/dev/null)
local COLOR_BOLD=$(tput bold 2>/dev/null)
local COLOR_BAD=$(tput setaf 1 2>/dev/null)
local COLOR_GOOD=$(tput setaf 2 2>/dev/null)
local str="$1"
local color="${COLOR_GOOD}${COLOR_BOLD}"
if [ ! -z "$2" ] \
&& [ "$(stripWhitespace "$2")" = "alert" ]; then
color="${COLOR_BAD}${COLOR_BOLD}"
fi
printf "${color}${str}${COLOR_RESET}"
}
checkPrerequisite() {
local bin_name="$1"
local bin_loc=$(which "${bin_name}" 2>/dev/null)
if [ -z "${bin_loc}" ] \
|| [ ! -f "${bin_loc}" ]; then
printf " - '$1' not found, quitting ...\n"
exit -1
else
printf " - '${bin_name}' found at ${bin_loc}\n"
fi
}
mainFunction() {
export "WINEARCH=${WINEARCH}"
export "WINEPREFIX=${WINEDIR}"
## Check prerequisites
colorEcho "[+] Checking prerequisites ...\n"
for bin in ${PREREQUISITES}; do
checkPrerequisite "${bin}"
done
checkRequiredVersions
## Create required directories
mkdir -pv "${BUILDROOT}"
mkdir -pv "${DLDIR}"
mkdir -pv "${OUTDIR}"
mkdir -pv "$TRIDIR"/web-ext-artifacts/
## Download Python and Pip
colorEcho "[+] Downloading necessary files ...\n"
if [ ! -f "${WINPY_EXE}" ]; then
wget \
"https://github.com/winpython/winpython/releases/download/1.10.20180404/WinPython32-${PYVER}.2Zero.exe" \
-O "${WINPY_EXE}"
fi
if [ ! "$(sha256sum "${WINPY_EXE}" \
| cut -d" " -f1)" = ${WINPY_HASH} ]; then
colorEcho "[-] ${WINPY_EXE} has incorrect hash, quitting ...\n"
exit 1
fi
## Extract Python-3.5.4 from WinPython if required
rm -rf "${WINEDIR}"
local winepython="wine $PYDIR/python.exe"
if [ ! -f "$PYDIR/python.exe" ]; then
colorEcho "[+] Extract Python-${PYVER}\n"
7z x "${DLDIR}/winpython-${PYVER}.exe" "python-$PYVER" -o"$BUILDROOT"
$winepython -m pip install --upgrade pip
colorEcho "[+] Installing PyInstaller ...\n"
$winepython -m pip install pyinstaller
fi
## Compile with PyInstaller
colorEcho "[+] Compiling with PyInstaller under Wine ...\n"
rm -rf "${OUTDIR}"
PYTHONHASHSEED=1 wine "$PYDIR"/Scripts/pyinstaller.exe \
--clean \
--console \
--onefile \
--noupx \
--noconfir \
--log-level=ERROR \
--workpath "${OUTDIR}" \
--distpath "${OUTDIR}" \
"$TRIDIR/native/native_main.py"
## Test the compiled EXE
colorEcho "[+] Checking compiled binary ...\n"
OUTFILE="${OUTDIR}/native_main.exe"
cp "$OUTFILE" "$TRIDIR"/web-ext-artifacts/
if [ -f "${OUTFILE}" ]; then
python3 \
"$TRIDIR/native/gen_native_message.py" cmd..version \
| wine "$TRIDIR"/web-ext-artifacts/native_main.exe
printf "\n"
colorEcho "[+] PyInstaller with Wine was successful!\n"
else
colorEcho \
"[-] PyInstaller compilation failed, quitting ...\n" \
"alert"
exit -1
fi
}
mainFunction "$@"

View file

@ -73,8 +73,8 @@ const DEFAULTS = o({
"<c-6>": "buffer #",
H: "back",
L: "forward",
"<c-o>": "back",
"<c-i>": "forward",
"<c-o>": "jumpprev",
"<c-i>": "jumpnext",
d: "tabclose",
D: "composite tabprev; sleep 100; tabclose #",
gx0: "tabclosealltoleft",
@ -233,6 +233,11 @@ const DEFAULTS = o({
// Maybe have a nice user-vicible message when the setting is changed?
allowautofocus: "true",
// These two options will fall back to user's preferences and then to a
// default value set in scrolling.ts if left undefined.
smoothscroll: undefined, // "false" | "true"
scrollduration: undefined, // number
tabopenpos: "next",
relatedopenpos: "related",
ttsvoice: "default", // chosen from the listvoices list, or "default"
@ -252,6 +257,8 @@ const DEFAULTS = o({
theme: "default", // currently available: "default", "dark"
modeindicator: "true",
jumpdelay: 3000, // Milliseconds before registering a scroll in the jumplist
// Default logging levels - 2 === WARNING
logging: o({
messaging: 2,
@ -274,9 +281,9 @@ const DEFAULTS = o({
nativeinstallcmd:
"curl -fsSl https://raw.githubusercontent.com/cmcaine/tridactyl/master/native/install.sh | bash",
win_powershell_nativeinstallcmd:
"Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/gsbabil/tridactyl/master/native/win_install.ps1'))",
"Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/cmcaine/tridactyl/master/native/win_install.ps1'))",
win_cmdexe_nativeinstallcmd:
'@"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -NoProfile -InputFormat None -Command "iex ((New-Object System.Net.WebClient).DownloadString(\'https://raw.githubusercontent.com/gsbabil/tridactyl/master/native/win_install.ps1\'))"',
'@"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -NoProfile -InputFormat None -Command "iex ((New-Object System.Net.WebClient).DownloadString(\'https://raw.githubusercontent.com/cmcaine/tridactyl/master/native/win_install.ps1\'))"',
profiledir: "auto",
// Container settings

View file

@ -105,6 +105,7 @@ import * as SELF from "./.excmds_content.generated"
Messaging.addListener("excmd_content", Messaging.attributeCaller(SELF))
import * as DOM from "./dom"
import { executeWithoutCommandLine } from "./commandline_content"
import * as scrolling from "./scrolling"
// }
//#background_helper
@ -514,6 +515,110 @@ export function loggingsetlevel(logModule: string, level: string) {
// {{{ PAGE CONTEXT
//#content_helper
export let JUMPED: boolean
//#content_helper
export function getJumpPageId() {
return document.location.href
}
//#content_helper
export async function saveJumps(jumps) {
browserBg.sessions.setTabValue(await activeTabId(), "jumps", jumps)
}
//#content_helper
export async function curJumps() {
let tabid = await activeTabId()
let jumps = await browserBg.sessions.getTabValue(tabid, "jumps")
if (!jumps) jumps = {}
let ensure = (obj, key, def) => {
if (obj[key] === null || obj[key] === undefined) obj[key] = def
}
let page = getJumpPageId()
ensure(jumps, page, {})
ensure(jumps[page], "list", [{ x: 0, y: 0 }])
ensure(jumps[page], "cur", 0)
saveJumps(jumps)
return jumps
}
//#content
export function jumpnext(n = 1) {
jumpprev(-n)
}
/** Similar to Pentadactyl or vim's jump list.
Should be bound to <C-o> when modifiers are implemented
*/
//#content
export function jumpprev(n = 1) {
curJumps().then(alljumps => {
let jumps = alljumps[getJumpPageId()]
let current = jumps.cur - n
if (current < 0) {
jumps.cur = 0
saveJumps(alljumps)
return back(-current)
} else if (current >= jumps.list.length) {
jumps.cur = jumps.list.length - 1
saveJumps(alljumps)
return forward(current - jumps.list.length + 1)
}
jumps.cur = current
let p = jumps.list[jumps.cur]
saveJumps(alljumps)
JUMPED = true
window.scrollTo(p.x, p.y)
})
}
/** Called on 'scroll' events.
If you want to have a function that moves within the page but doesn't add a
location to the jumplist, make sure to set JUMPED to true before moving
around.
The setTimeout call is required because sometimes a user wants to move
somewhere by pressing 'j' multiple times and we don't want to add the
in-between locations to the jump list
*/
//#content_helper
export function addJump(scrollEvent: UIEvent) {
if (JUMPED) {
JUMPED = false
return
}
let pageX = scrollEvent.pageX
let pageY = scrollEvent.pageY
// Get config for current page
curJumps().then(alljumps => {
let jumps = alljumps[getJumpPageId()]
// Prevent pending jump from being registered
clearTimeout(jumps.timeoutid)
// Schedule the registering of the current jump
jumps.timeoutid = setTimeout(() => {
let list = jumps.list
// if the page hasn't moved, stop
if (list[jumps.cur].x == pageX && list[jumps.cur].y == pageY) return
// Store the new jump
// Could removing all jumps from list[cur] to list[list.length] be
// a better/more intuitive behavior?
list.push({ x: pageX, y: pageY })
jumps.cur = jumps.list.length - 1
saveJumps(alljumps)
}, config.get("jumpdelay"))
})
}
//#content_helper
document.addEventListener("scroll", addJump)
// Try to restore the previous jump position every time a page is loaded
//#content_helper
curJumps().then(() => {
jumpprev(0)
})
/** Blur (unfocus) the active element */
//#content
export function unfocus() {
@ -522,10 +627,8 @@ export function unfocus() {
}
//#content
export function scrollpx(a: number, b: number) {
let top = document.body.getClientRects()[0].top
window.scrollBy(a, b)
if (top == document.body.getClientRects()[0].top) recursiveScroll(a, b, [document.body])
export async function scrollpx(a: number, b: number) {
if (!await scrolling.scroll(a, b, document.documentElement)) scrolling.recursiveScroll(a, b, [document.documentElement])
}
/** If two numbers are given, treat as x and y values to give to window.scrollTo
@ -542,54 +645,27 @@ export function scrollto(a: number, b: number | "x" | "y" = "y") {
window.scrollTo(window.scrollX, percentage * elem.scrollHeight / 100)
if (top == elem.getClientRects()[0].top && (percentage == 0 || percentage == 100)) {
// scrollTo failed, if the user wants to go to the top/bottom of
// the page try recursiveScroll instead
recursiveScroll(window.scrollX, 1073741824 * (percentage == 0 ? -1 : 1), [window.document.body])
// the page try scrolling.recursiveScroll instead
scrolling.recursiveScroll(window.scrollX, 1073741824 * (percentage == 0 ? -1 : 1), [document.documentElement])
}
} else if (b === "x") {
let left = elem.getClientRects()[0].left
window.scrollTo(percentage * elem.scrollWidth / 100, window.scrollY)
if (left == elem.getClientRects()[0].left && (percentage == 0 || percentage == 100)) {
recursiveScroll(1073741824 * (percentage == 0 ? -1 : 1), window.scrollX, [window.document.body])
scrolling.recursiveScroll(1073741824 * (percentage == 0 ? -1 : 1), window.scrollX, [document.documentElement])
}
} else {
window.scrollTo(a, Number(b)) // a,b numbers
}
}
/** Tries to find a node which can be scrolled either x pixels to the right or
* y pixels down among the Elements in {nodes} and children of these Elements.
*
* This function used to be recursive but isn't anymore due to various
* attempts at optimizing the function in order to reduce GC pressure.
*/
//#content_helper
function recursiveScroll(x: number, y: number, nodes: Element[]) {
let index = 0
do {
let node = nodes[index++] as any
// Save the node's position
let top = node.scrollTop
let left = node.scrollLeft
node.scrollBy(x, y)
// if the node moved, stop
if (top != node.scrollTop || left != node.scrollLeft) return
// Otherwise, add its children to the nodes that could be scrolled
nodes = nodes.concat(Array.from(node.children))
if (node.contentDocument) nodes.push(node.contentDocument.body)
} while (index < nodes.length)
}
//#content
export function scrollline(n = 1) {
let top = document.body.getClientRects()[0].top
window.scrollByLines(n)
if (top == document.body.getClientRects()[0].top) {
const cssHeight = window.getComputedStyle(document.body).getPropertyValue("line-height")
// Remove the "px" at the end
const lineHeight = parseInt(cssHeight.substr(0, cssHeight.length - 2))
// lineHeight probably can't be NaN but let's make sure
if (lineHeight) recursiveScroll(0, lineHeight * n, [window.document.body])
}
const cssHeight = window.getComputedStyle(document.body).getPropertyValue("line-height")
// Remove the "px" at the end
const lineHeight = parseInt(cssHeight.substr(0, cssHeight.length - 2))
// lineHeight probably can't be NaN but let's make sure
if (lineHeight) scrolling.recursiveScroll(0, lineHeight * n, [document.documentElement])
}
//#content

View file

@ -56,9 +56,7 @@ export class Logger {
return browser.runtime.sendMessage({
type: "commandline_background",
command: "recvExStr",
args: [
"fillcmdline # Error: " + message.join(" "),
],
args: ["fillcmdline # " + message.join(" ")],
})
else
return browser.tabs.sendMessage(
@ -69,7 +67,7 @@ export class Logger {
{
type: "commandline_frame",
command: "fillcmdline",
args: ["# Error: " + message.join(" ")],
args: ["# " + message.join(" ")],
},
)
}

View file

@ -361,12 +361,15 @@ export async function loadPrefs(filename): Promise<{ [key: string]: string }> {
return parsePrefs(result.content)
}
let cached_prefs = null
/** Returns a promise for an object that should contain every about:config
* setting. If performance is slow, it might be a good idea to cache the
* results of this function: the preference files do not change while firefox
* is running.
* setting.
*
* Performance is slow so we need to cache the results.
*/
export async function getPrefs(): Promise<{ [key: string]: string }> {
if (cached_prefs != null) return cached_prefs
const profile = (await getProfileDir()) + "/"
const prefFiles = [
// Debian has these
@ -396,7 +399,10 @@ export async function getPrefs(): Promise<{ [key: string]: string }> {
for (let file of prefFiles) {
promises.push(loadPrefs(file))
}
return promises.reduce(async (a, b) => Object.assign(await a, await b))
cached_prefs = promises.reduce(async (a, b) =>
Object.assign(await a, await b),
)
return cached_prefs
}
/** Returns the value for the corresponding about:config setting */
@ -404,8 +410,42 @@ export async function getPref(name: string): Promise<string> {
return (await getPrefs())[name]
}
/** Fetches a config option from the config. If the option is undefined, fetch
* a preference from preferences. It would make more sense for this function to
* be in config.ts but this would require importing this file in config.ts and
* Webpack doesn't like circular dependencies.
*/
export async function getConfElsePref(
confName: string,
prefName: string,
): Promise<any> {
let option = await config.getAsync(confName)
if (option === undefined) {
try {
option = await getPref(prefName)
} catch (e) {}
}
return option
}
/** Fetches a config option from the config. If the option is undefined, fetch
* prefName from the preferences. If prefName is undefined too, return a
* default.
*/
export async function getConfElsePrefElseDefault(
confName: string,
prefName: string,
def: any,
): Promise<any> {
let option = await getConfElsePref(confName, prefName)
if (option === undefined) return def
return option
}
/** Writes a preference to user.js */
export async function writePref(name: string, value: any) {
if (cached_prefs) cached_prefs[name] = value
if (typeof value == "string") value = `"${value}"`
const file = (await getProfileDir()) + "/user.js"
// No need to check the return code because read returns "" when failing to

162
src/scrolling.ts Normal file
View file

@ -0,0 +1,162 @@
import * as Native from "./native_background"
type scrollingDirection = "scrollLeft" | "scrollTop"
// Stores elements that are currently being horizontally scrolled
let horizontallyScrolling = new Map()
// Stores elements that are currently being vertically scrolled
let verticallyScrolling = new Map()
async function getSmooth() {
return await Native.getConfElsePrefElseDefault(
"smoothscroll",
"general.smoothScroll",
"false",
)
}
async function getDuration() {
return await Native.getConfElsePrefElseDefault(
"scrollduration",
"general.smoothScroll.lines.durationMinMs",
100,
)
}
class ScrollingData {
// time at which the scrolling animation started
startTime: number
// Starting position of the element. This shouldn't ever change.
startPos: number
// Where the element should end up. This can change if .scroll() is called
// while a scrolling animation is already running
endPos: number
// Whether the element is being scrolled
scrolling = false
// Duration of the scrolling animation
duration = 0
/** elem: The element that should be scrolled
* pos: "scrollLeft" if the element should be scrolled on the horizontal axis, "scrollTop" otherwise
*/
constructor(
private elem: HTMLElement,
private pos: scrollingDirection = "scrollTop",
) {}
/** Computes where the element should be.
* This changes depending on how long ago the first scrolling attempt was
* made.
* It might be useful to make this function more configurable by making it
* accept an argument instead of using performance.now()
*/
getStep() {
if (this.startTime === undefined) {
this.startTime = performance.now()
}
let elapsed = performance.now() - this.startTime
// If the animation should be done, return the position the element should have
if (elapsed > this.duration || this.elem[this.pos] == this.endPos)
return this.endPos
let result = (this.endPos - this.startPos) * elapsed / this.duration
if (result >= 1 || result <= -1) return this.startPos + result
return this.elem[this.pos] + (this.startPos < this.endPos ? 1 : -1)
}
/** Updates the position of this.elem */
scrollStep() {
let val = this.elem[this.pos]
this.elem[this.pos] = this.getStep()
return val != this.elem[this.pos]
}
/** Calls this.scrollStep() until the element has been completely scrolled
* or the scrolling animation is complete */
scheduleStep() {
// If scrollStep() scrolled the element, reschedule a step
// Otherwise, register that the element stopped scrolling
window.requestAnimationFrame(
() =>
this.scrollStep()
? this.scheduleStep()
: (this.scrolling = false),
)
}
scroll(distance: number, duration: number) {
this.startTime = performance.now()
this.startPos = this.elem[this.pos]
this.endPos = this.elem[this.pos] + distance
this.duration = duration
// If we're already scrolling we don't need to try to scroll
if (this.scrolling) return true
this.scrolling = this.scrollStep()
if (this.scrolling)
// If the element can be scrolled, scroll until animation completion
this.scheduleStep()
return this.scrolling
}
}
/** Tries to scroll e by x and y pixel, make the smooth scrolling animation
* last duration milliseconds
*/
export async function scroll(
x: number,
y: number,
e: HTMLElement,
duration: number = undefined,
) {
let smooth = await getSmooth()
if (smooth == "false") duration = 0
else if (duration === undefined) duration = await getDuration()
let result = false
if (x != 0) {
// Don't create a new ScrollingData object if the element is already
// being scrolled
let scrollData = horizontallyScrolling.get(e)
if (!scrollData || !scrollData.scrolling)
scrollData = new ScrollingData(e, "scrollLeft")
horizontallyScrolling.set(e, scrollData)
result = result || scrollData.scroll(x, duration)
}
if (y != 0) {
let scrollData = verticallyScrolling.get(e)
if (!scrollData || !scrollData.scrolling)
scrollData = new ScrollingData(e, "scrollTop")
verticallyScrolling.set(e, scrollData)
result = result || scrollData.scroll(y, duration)
}
return result
}
/** Tries to find a node which can be scrolled either x pixels to the right or
* y pixels down among the Elements in {nodes} and children of these Elements.
*
* This function used to be recursive but isn't anymore due to various
* attempts at optimizing the function in order to reduce GC pressure.
*/
export async function recursiveScroll(
x: number,
y: number,
nodes: Element[],
duration: number = undefined,
) {
// Determine whether to smoothscroll or not
let smooth = await getSmooth()
if (smooth == "false") duration = 0
else if (duration === undefined) duration = await getDuration()
let index = 0
let now = performance.now()
do {
let node = nodes[index++] as any
// Save the node's position
if (await scroll(x, y, node, duration)) return
// Otherwise, add its children to the nodes that could be scrolled
nodes = nodes.concat(Array.from(node.children))
if (node.contentDocument) nodes.push(node.contentDocument.body)
} while (index < nodes.length)
}

6
src/tridactyl.d.ts vendored
View file

@ -16,6 +16,12 @@ interface Window {
eval(str: string): any
}
// Again, firefox-specific
interface UIEvent {
pageX: number
pageY: number
}
interface HTMLElement {
// Let's be future proof:
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus