IntroductionQuick StartExtensionsAPIsSupport
 

Snappy: Mobile browser with Chrome-like extensions

Introduction

Snappy is an extendable web browser for geeks on Android. It supports javascript extensions similar to those used in Chromium-based desktop browsers.

New Tab Tabs/Windows List Empty Left Side Panel Settings: Extensions List History extension in the Left Side Panel Bookmarks extension in the Right Side Panel Google and browser action buttons Popup window of DomainCage extension Ad blocking by DomainCage extension Custom New Tab extension

 

Quick Start

Snappy is ready to work just after installation, but it provides only minimal functionality with its core: you can search through the web and browse sites, save pages or images and download links. All the extended functionality, including navigation history, bookmarks, customized start pages and many other aspects, are exposed via APIs for javascript extensions, which can be added into the browser in a snap.

To install an extension:

  • download a zip file with the extention's files
  • unpack all files to an empty folder on your Android device
  • in Snappy, open the Settings dialog, swipe to the Extensions list at the right, press Add button (add) and pick manifest.json file from the extension folder

This is much like the process how unpacked extensions are installed in desktop Chrome. Nevertheless, please note, that Snappy extensions are similar to but not fully compatible with Chrome extensions. You can not install Chrome extensions to Snappy. Every Chrome extension should be ported by a developer to Snappy, before it can be used in Snappy.

Android 4.4+ or higher is required for Snappy to run, but it's strongly recommended to use Android 5+ and WebView version 48+. Disregard of these requirements may lead to failures and incorrect browser operation.

Please note that large number of installed extensions may affect performance and functionality. Script injection for numerous extensions may take quite some time and interfere with each other. The rule of thumb is to have no more than 3 extensions with content scripts. Also only 1 extension with webrequest interception is advisable. After all, this is just a mobile browser.

Google Play

Main window controls

  • add - open a new window; long touch shows a list of windows
  • zoom - switch on the full-screen mode; to switch it off - swipe down from the top and click the button again
  • settings - open the settings dialog - some settings changes take effect only after browser window is closed and reopened
  • back - navigate back; visible only if there is a previous page in the navigation history
  • next - navigate forward; visible only if there is a next page in the navigation history
  • reload - reload current page

Main window context menu/action menu

  • Open in new tab (available for links)
  • Download URL (available for links)
  • Copy URL to clipboard (available for links)
  • Save current page (create a webarchive .mht in Download folder, can be viewed in the standard Android's HTML Viewer)
  • View image (available for images)
  • Bookmark current page
  • Search on page
  • Select all (available for text selection)
  • Copy (available for text selection)
  • Share (available for text selection)
  • Web-search (available for text selection)

Extensions

Extensions are distributed in source codes. If an extension is downloaded from a page other than this, please, verify its source code to make sure it does not contain malware - this is where Snappy acts as a browser for geeks.

The following extensions are available for download:

  • Force Zoom (0.1) - Re-enables zooming functionality for users on those unfriendly sites which disable zooming intentionally (utilizes content script injection)
  • View Raw HTML (0.1) - Shows raw html codes of current page (demonstrates browser action and extension options page - to open the options perform a long touch on the browser action button in the main window or on the extension record in the Settings dialog)
  • Domain Cage (0.7.1m3 for Snappy 1.7+, obsolete) - Prevents web-requests to external domains and specific URLs, please visit the site of the extension for desktop browsers for details (utilizes browser action, popup dialog, content scripts, context menus, custom pages, sending messages and web-request blocking)
  • Domain Cage with declarative rules (0.7.1 R 4 for Snappy 1.9+ updated 31.03.2016) - Improved blocker of ads and trackers (demonstrates declarative API)
  • Navigator (0.1) - Shows back and forward navigation history (works with the history API)
  • History (0.1) - Provides a UI for long term history of web browsing (demonstrates usage of the history API and side panel)
  • Bookmarks (0.1) - Simple bookmark manager (uses a side panel)
  • Yandex (0.1) - Search through Web from omnibox using Yandex (demonstrates search engine override)
  • Custom Start Page (0.1) - New tab replacement example (demonstrates new tab override)
  • Cookie Viewer (0.1) - View cookies for current document (provides a popup for reading and removing cookies)
  • MediaSaver (0.3) - Saves streaming media content into local files (demonstrates downloads API)

APIs

Here is the list of the supported extensions APIs. They mimic the desktop Chrome extensions APIs as much as possible, but differ in some aspects due to limitations imposed by mobile WebView available on Android.

The root interface is android (this is an analogue for chrome). It's used to access all other specific interfaces, such as tabs, contextMenus, storage and others. Most importat change in comparison to the desktop APIs is that all specific interfaces should be obtained by corresponding methods accepting a single integer parameter - an extension ID. For example, a desktop extension code chrome.tabs.get(tabId, ...) should be replaced with android.getTabs(extId).get(tabId). The extension ID can be obtained in extension code either from current custom page URL or from 'exid' parameter of current content script tag. Please use the source codes of available extensions as a reference for the APIs usage.

Main development rules to follow:

  • No background pages - only content scripts and scripts linked to custom pages are available for algorithmic processing
  • No page actions - they are fused into browser actions
  • No function callbacks - instead of them, function names or complete function bodies as strings should be used (with all possible side-effects such as missing wrapping contexts and closures)
  • Tabs are turned into windows - there is no a 'tab' look and feel in UI, every page is hosted by a window
  • Two side panels - left and right - are new placeholders in UI ready for overriding

android

  • Tabs getTabs(int extId)
  • LocalStorage getLocalStorage(int extId)
  • BrowserAction getBrowserAction(int extId)
  • ContextMenus getContextMenus(int extId)
  • WebRequest getWebRequest(int extId)
  • History getHistory(int extId)
  • Bookmarks getBookmarks(int extId)
  • Cookies getCookies(int extId)
  • Downloads getDownloads(int extId)

Tabs (chrome.tabs)

  • Tab get(int tabId)
  • Tab getCurrent()
  • boolean sendRequest(int tabId, int port, String data, String callback): returns true on success; data can be json;
  • Tab getSelected()
  • Tab create(String url [optional], String callback [optional])
  • TabCollection query()
  • update(int tabId, String json): tabId - 0 by default, means current tab; json - {url: String [optional], title: String [optional], active: boolean [optional]}
  • reload(int tabId)
  • remove(int tabId)
  • executeScript(int targetId, final String json): json - {code: String [javascript] | file: String [filename] | inline: String [filename for inline insertion]}
  • setOnCreatedListener(String callback)
  • setOnUpdatedListener(String callback)
  • setOnActivatedListener(String callback)
  • setOnRemovedListener(String callback)
  • setOnRequestListener(String callback, int port)
  • setOnSuspendedListener(String callback): chrome.runtime.onSuspend
  • openOptionsPage(): chrome.runtime.openOptionsPage
  • String getURL(String filename): chrome.extension.getURL
  • notifyCallback(int senderId, String callback, String data [optional]): notify a sender about received request; senderId - whom notify; callback - how notify
  • TabCollection
    • int getSize()
    • Tab getTab(int index)
    Tab
    • int getTabId()
    • String getTitle()
    • String getUrl()
    • boolean getStatus(): returns true if ready
    • boolean getActive()
    • historyGo(int steps)

Storage (chrome.storage)

  • set(String key, String value)
  • get(String key, String callback): async
  • String get(String key): sync
  • remove(String key)
  • clear()

BrowserAction (chrome.browserAction)

  • setTitle(String text)
  • String getTitle()
  • setIcon(String filename)
  • setPopup(String filename)
  • String getPopup()
  • setBadgeText(String text)
  • String getBadgeText()
  • setBadgeBackgroundColor(String c)
  • String getBadgeBackgroundColor()
  • enable()
  • disable()
  • setOnClickedListener(String callback)

ContextMenus (chrome.contextMenus)

  • int create(String json, String onClickedCallback): json - {type: String ["normal" | "checkbox" | "radio"], title: String, checked: [true | false], contexts: String ["all" | "page" | "frame" | "selection" | "link" | "editable" | "image" | "video" | "audio" | "browser_action"], parentId: int} not implemented; returns ID of new item; onClickedCallback receives a single String parameter with json: {menuItemId: int, checked: [true | false], wasChecked: [true | false], title: String, extra: String [link URL]}
  • update(int id, String json, String onClickedCallback [optional])
  • remove(int id)
  • removeAll()

WebRequest (chrome.webRequest)

  • setOnBeforeRequestListener(String callback, String urlPattern): blocking
  • setOnErrorOccurredListener(String callback)
  • boolean JSONResult(String json): for the callback to pass results to the browser core; obsolete, since Snappy 1.8 where object can be returned "as is"): json - {cancel: [true | false], redirectUrl: String, requestId: long}
  • int addRules(String jsonArray): adds processing rules for declarative APIs; returns number of added rules; if a rule with specified ID does already exist, it will be replaced with the new one;
  • int removeRules(String jsonArrayOfIDs): deletes processing rules by their IDs; returns number of deleted rules; if null is passed, removes all rules;
  • boolean getRules(String jsonArrayOfIDs): returns true if at least one of rules with specified IDs exist;
  • Rule getRule(String id): returns rule object by its ID (with methods: String getId(), TagList getTags(), int getPriority());
  • setOnMessageListener(String callback): assigns a callback to be called from "sendMessageToExtension" and other actions as callback(ruleId, actionType, parameter or url);

History (chrome.history)

  • WebLogCollection search(String json): json - {text: String, startTime: long (timestamp in milliseconds, defaults to 24h back), endTime: long, maxResults: int (defaults to 100), startResult: int}
  • addUrl(String url)
  • addUrl(String url, String title)
  • deleteUrl(String url)
  • deleteRange(long startTime, long endTime)
  • deleteAll()
  • BackForwardList getBackForwardList
  • WebLogCollection
    • int getSize()
    • WebLogItem get(int index)
    WebLogItem
    • String getUrl()
    • String getTitle()
    • long getFirstVisitTime()
    • long getLastVisitTime()
    • int getVisitCount()
    • String getIcon()
    BackForwardList
    • int getCurrentIndex()
    • HistoryItem getCurrentItem
    • HistoryItem getItemAtIndex
    • int getSize()
    HistoryItem
    • String getFavicon()
    • String getOriginalUrl()
    • String getTitle()
    • String getUrl()

Bookmarks (chrome.bookmarks)

  • long create(long parentId, long index, String url, String title): if parentId = 0, places new bookmark in the root folder, if index = 0, adds new bookmarks at the end of the folder, if url is empty - creates a sub-folder; returns ID of the new bookmark
  • remove(long id)
  • update(long id, String url, String title)
  • move(long id, long parentId, long index): either moves a bookmark from current folder into another (if parentId differs from ID of the current folder where the bookmark id resides), or reorders bookmarks in the current folder (if parentId equals to ID of the current folder, and index differs from current position of the bookmark inside this folder)
  • BookmarkTreeNodeList search(String text)
  • BookmarkTreeNode get(long id)
  • BookmarkTreeNodeList getChildren(long parentId)
  • BookmarkTreeNodeList getRecent(int number)
  • BookmarkTreeNodeList
    • int getSize()
    • BookmarkTreeNode get(int index)
    BookmarkTreeNode
    • long getId()
    • long getParentId()
    • long getPosition()
    • String getUrl()
    • String getTitle()
    • long getTimeAdded()

Cookies (chrome.cookies)

  • WebCookieList get(String url)
  • WebCookie get(String url, String name)
  • set(String url, String rawValue)
  • remove(String url, String rawValue)
  • WebCookieList
    • int getSize()
    • WebCookie get(int index)
    WebCookie
    • String getName()
    • String getValue()

Downloads (chrome.downloads)

  • long download(String url [, String filename]): starts download, returns unique ID
  • int cancel(String jsonArray): removes or cancels downloads with specified IDs; returns number of affected items
  • int removeFiles(String jsonArray); the same as cancel()
  • DownloadCollection search(String jsonObjectWithCriteria): returns a list of items sorted by time, jsonObjectWithCriteria - {query: ["string"...], exists: boolean, error: boolean, paused: boolean, limit: integer, mime: "string", id: [long...], state: "in_progress" | "interrupted" | "complete", endedAfter | startedAfter: timestamp, endedBefore | startedBefore: timestamp, totalBytesGreater: long, totalBytesLess: long, filenameRegex: "string", urlRegex: "string"}
  • open(long id): opens downloaded file
  • show(): opens system Downloads app
  • DownloadCollection
    • int getSize()
    • DownloadItem get(int index)
    • String getJSON(int index): returns stringified DownloadItem {id: long, url: "string", filename: "string", mime: "string", status: "in_progress" | "interrupted" | "complete", error: int, paused: boolean, endTime: timestamp, totalBytes: long, bytesReceived: long}
    DownloadItem
    • long getId()
    • String getUrl()
    • String getFilename()
    • String getMime()
    • String getState()
    • boolean getPaused()
    • int getError()
    • long getStartTime()/getEndTime()
    • long getBytesReceived()
    • long getTotalBytes()
    • String getObject()

Support

The application is still under development. Some parts of the Chrome extensions APIs do not yet have mobile counterparts. Most obvious APIs and UI features which are currently missing and remain in TODO list:

  • Permissions in manifest
  • Localization API (i18n)
  • Alarms API
  • Notifications API
  • Sessions API

For security reasons it's recommended to wipe out extension IDs from content scripts as soons as the scripts read this data into internal variables. For example:


    function getID()
    {
      // obtain ID provided by the browser core     
      var idstr = document.currentScript.exid;
      
      // remove exid attribute
      delete document.currentScript.exid;
      
      // empty src attribute
      document.currentScript.src = "";
      
      return parseInt(idstr);
    }
      

manifest.json reference


  {
    "manifest_version": "1m",
    "name": "text",
    "version": "text",
    "description": "text",
    "icons":
    {
      "128" | "48" | "16": "imagefile.*"
    },
    "browser_action" | "page_action":
    {
      "default_icon": "imagefile.*",
      "default_title": "text",
      "default_popup": "htmlfile.*" // if not specified onClicked is fired  
      "options_long_click": true,   // optinal, false by default
    },
    "options_page": "htmlfile.*",
    "overrides":
    {
      "newtab": "htmlfile.*",
      "sidepanel": "htmlfile.*",
      "searchengine": "url"
    },
    "content_scripts":
    [{
      "matches": ["<all_urls>"],
      "js": ["scriptfile.js"],
      "run_at": "document_start", "document_end", "document_idle"
    }]
  }
      

logo

For support enquiries, questions, comments, suggestions, and extensions development requests, please use this e-mail.

Copyright © 2016 Stan Korotky