Skip to content

No-frills light-weight Android web browser with support for Greasemonkey userscripts.

License

Notifications You must be signed in to change notification settings

warren-bank/Android-WebMonkey

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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
    • 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

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 uses GM_xmlhttpRequest to make network requests
      • 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
  • adds an additional Javascript API interface to expose Android-specific capabilities:
    • legacy:
      • GM_exit()
      • 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
        • 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 for window.top.location.href and window.parent.location.href as observed from within the iframe
          • [optional] proxyFrame is a boolean: a truthy value causes urlFrame to be downloaded in Java
            • urlParent 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 and urlFrame 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 if urlParent is sent in the Referer header
        • example:
          • ('http://example.com/iframe_window.html', 'http://example.com/parent_window.html')
        • 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
        • 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 = 'http://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 = 'http://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
        • example:
          • ('http://example.com/iframe_window.html', 'Referer', 'http://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 to urlBase
        • 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
        • examples:
          • ('video.mp4', 'http://example.com/iframe_window.html')
          • ('video.mp4')
      • 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
      • 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 of data
          • [optional] extras is a list of String name/value pairs
        • example:
          • ('android.intent.action.VIEW', 'http://example.com/video.mp4', 'video/mp4', 'referUrl', 'http://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

Settings

  • default browser home page
  • 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

Security

  1. closure
  2. 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
  3. 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
      1. group:
        • GM_setValue
        • GM_getValue
        • GM_deleteValue
        • GM_listValues
        • GM.setValue
        • GM.getValue
        • GM.deleteValue
        • GM.listValues
      2. 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
      3. group:
        • GM_removeAllCookies
        • GM.removeAllCookies
      4. group:
        • GM_setUserAgent
        • GM.setUserAgent

Caveats

  • userscripts only run in the top window
    • a mitmproxy script is required to load userscripts into iframes

Legal: