No-frills light-weight Android web browser with support for Greasemonkey userscripts.
Builds upon the WebView GM library demo application.
Background
- the WebView GM library enhances the native Android System WebView
- with userscript management:
- detecting and downloading
*.user.js
URLs - parsing and saving to a DB
- automatic updates
- detecting and downloading
- with userscript injection:
- on top-level HTML pages that match URL patterns
- with support for Greasemonkey API ( 1, 2, 3 ) functions:
GM_addStyle
GM_deleteValue
GM_getResourceText
GM_getResourceURL
GM_getValue
GM_listValues
GM_log
GM_setValue
GM_xmlhttpRequest
- with userscript management:
Improvements
- supplements the list of supported Greasemonkey API functions:
- legacy:
GM_addElement
GM_cookie.delete
GM_cookie.list
GM_cookie.set
GM_fetch
- drop-in replacement for
window.fetch
that usesGM_xmlhttpRequest
to make network requests
- drop-in replacement for
GM_info
GM_registerMenuCommand
GM_unregisterMenuCommand
- GM 4:
GM.addElement
GM.addStyle
GM.cookie.delete
GM.cookie.list
GM.cookie.set
GM.cookies.delete
GM.cookies.list
GM.cookies.set
GM.deleteValue
GM.fetch
GM.getResourceText
GM.getResourceUrl
GM.getValue
GM.info
GM.listValues
GM.log
GM.registerMenuCommand
GM.setValue
GM.unregisterMenuCommand
GM.xmlHttpRequest
- legacy:
- adds an additional Javascript API interface to expose Android-specific capabilities:
- legacy:
GM_exit()
- causes WebMonkey to close
GM_getUrl()
- returns a String containing the URL that is currently loaded in the WebView
- use case:
- allows the userscript to detect whether the page has been redirected
- server response status codes: 301, 302
- allows the userscript to detect whether the page has been redirected
- example:
var is_redirect = (GM_getUrl() !== unsafeWindow.location.href)
GM_getUserAgent()
- returns a String containing the User Agent that is currently configured in Settings for use by the WebView
GM_loadFrame(urlFrame, urlParent, proxyFrame)
- loads an iframe into the WebView
- where:
- [required]
urlFrame
is a String URL: the page loaded into the iframe - [required]
urlParent
is a String URL: value forwindow.top.location.href
andwindow.parent.location.href
as observed from within the iframe - [optional]
proxyFrame
is a boolean: a truthy value causesurlFrame
to be downloaded in JavaurlParent
is sent in the Referer header- a successful (200-299) response is dynamically loaded into iframe.srcdoc
- the benefit:
- same-origin policy does not apply
- when
urlParent
andurlFrame
belong to different domains, a userscript running in the top window can access the DOM within the iframe window
- special use case:
- when
urlFrame
only serves the desired web page content ifurlParent
is sent in the Referer header
- when
- [required]
- example:
- use case:
- "parent_window.html" contains:
- an iframe to display "iframe_window.html"
- other content that is not wanted
- though a userscript could easily do the necessary housekeeping:
- detach the iframe
- remove all other DOM elements from body
- reattach the iframe
- this method provides a better solution:
- removes all scripts that are loaded into the parent window
- handles all the css needed to resize the iframe to maximize its display within the parent window
- makes it easy to handle this common case
- "parent_window.html" contains:
- why this is a common case:
- "iframe_window.html" performs a check to verify that it is loaded in the proper parent window
- example 1:
const urlParent = 'example.com/parent_window.html' try { // will throw when either: // - `top` is loaded from a different domain // - `top` is loaded from the same origin, but the URL path does not match 'parent_window.html' if(window.top.location.href !== urlParent) throw '' } catch(e) { // will redirect `top` window to the proper parent window window.top.location = urlParent }
- example 2:
const urlParent = 'example.com/parent_window.html' { // will redirect to proper parent window when 'iframe_window.html' is loaded without a `top` window if(window === window.top) window.location = urlParent }
GM_loadUrl(url, ...headers)
- loads a URL into the WebView with additional HTTP request headers
- where:
- [required]
url
is a String URL - [optional]
headers
is a list of String name/value pairs
- [required]
- example:
('example.com/iframe_window.html', 'Referer', 'example.com/parent_window.html')
GM_removeAllCookies()
- completely removes all cookies for all web sites
GM_resolveUrl(urlRelative, urlBase)
- returns a String containing
urlRelative
resolved relative tourlBase
- where:
- [required]
urlRelative
is a String URL: relative path - [optional]
urlBase
is a String URL: absolute path- default value: the URL that is currently loaded in the WebView
- [required]
- examples:
('video.mp4', 'example.com/iframe_window.html')
('video.mp4')
- returns a String containing
GM_setUserAgent(value)
- changes the User Agent value that is configured in Settings
- where:
- [optional]
value
is a String- special cases:
WebView
(or falsy)Chrome
- special cases:
- [optional]
GM_startIntent(action, data, type, ...extras)
- starts an implicit Intent
- where:
- [required, can be empty]
action
is a String - [required, can be empty]
data
is a String URL - [required, can be empty]
type
is a String mime-type for format ofdata
- [optional]
extras
is a list of String name/value pairs
- [required, can be empty]
- example:
('android.intent.action.VIEW', 'example.com/video.mp4', 'video/mp4', 'referUrl', 'example.com/videos.html')
GM_toastLong(message)
GM_toastShort(message)
- GM 4:
GM.exit
GM.getUrl
GM.getUserAgent
GM.loadFrame
GM.loadUrl
GM.removeAllCookies
GM.resolveUrl
GM.setUserAgent
GM.startIntent
GM.toastLong
GM.toastShort
- legacy:
Settings
- default browser home page
- Continue where you left off
- Blank page
- Userscripts by developer
- Userscripts at Greasy Fork
- Custom URL
- User Agent
- WebView
- Chrome desktop
- Custom User Agent
- page load behavior on HTTPS certificate error
- cancel
- proceed
- ask
- script update interval
- number of days to wait between checks
- special case:
0
disables automatic script updates
- shared secret for JS to access low-level API method:
window.WebViewWM.getUserscriptJS(secret, url)
- specific use case: mitmproxy script to inject JS code to bootstrap userscripts in iframes
- enable remote debugger
- allows remote access over an adb connection, such as:
adb connect "${IP_of_phone_on_LAN}:5555"
- remote debugger is accessible in Chrome at:
chrome://inspect/#devices
- the interface uses Chrome DevTools
- allows remote access over an adb connection, such as:
Security
- closure
- by default, all JS code injected into web pages is wrapped by a closure
- the closure is implemented as a self-executing anonymous function, also known as an immediately invoked function expression
- this security feature can be disabled by a userscript by adding any of the following declarations to its header block:
// @unwrap // @flag noJsClosure // @flags noJsClosure
- SANDBOX.txt contains more details
- sandbox
- when a closure is disabled, a sandbox is also disabled
- when a closure is enabled, by default, all JS global variables saved to the
window
Object are stored in a sandbox - as such, JS code outside of the userscript cannot see or access these variables
- however, the JS code inside of the userscript can see and access all global variables… including its own
- the sandbox is implemented as an ES6
Proxy
- this security feature can be disabled by a userscript by adding any of the following declarations to its header block:
// @grant none // @flag noJsSandbox // @flags noJsSandbox
- SANDBOX.txt contains more details
- API-level permissions
// @grant <API>
is only required to use API methods that I would consider to be potentially dangerous- several of these API methods are grouped together,
and permission granted for any one…
also grants permission to use all other API methods in the same group- group:
GM_setValue
GM_getValue
GM_deleteValue
GM_listValues
GM.setValue
GM.getValue
GM.deleteValue
GM.listValues
- group:
GM_cookie
GM_cookie.list
GM_cookie.set
GM_cookie.delete
GM.cookie
GM.cookie.list
GM.cookie.set
GM.cookie.delete
GM.cookies
GM.cookies.list
GM.cookies.set
GM.cookies.delete
- group:
GM_removeAllCookies
GM.removeAllCookies
- group:
GM_setUserAgent
GM.setUserAgent
- group:
Caveats
- userscripts only run in the top window
- a mitmproxy script is required to load userscripts into iframes
Legal:
- copyright: Warren Bank
- license: GPL-2.0