midori
Table of Contents
Since some time I’m a proud user of Midori because it is much faster than firefox and also faster at starting than chromium. Unfortunately there are some bugs but hey it’s a beta.
Main #
Something I really like is the adaptability using javascript and css.
Scripts I’m using are:
BlockFlash2
blocking google ads1
blocking google ads2
Because I’m a vim user I also have changed the bunch of shortcuts of midori to suit my wishes and I’m using this script for navigation:
Arch Linux Forum Post - VimNav
Old Vim-like vimkeybinding
Then a friend and I, we’ve improved this keynav-script.
We’ve changed the binding and added code to deny execution while an input or textarea field has focus.
Scripts #
Script to hint and open a link using ‘f’ in current tab.
// KeyNav
// version 0.1.1 beta
// Itamar Benzaken
// modified by Andrwe and Seiichiro0185
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// To install, you need Greasemonkey: http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// To uninstall, go to Tools/Manage User Scripts,
// select "KeyNav", and click Uninstall.
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name KeyNav
// @description Enables keyboard navigation
// @namespace tag:itamar.benzaken@gmail.com,2008-09-10:KeyNav
// @include *
// ==/UserScript==
/*
@what
The script enables keyboard navigation.
@how
Pressing a hotkey displays a (or hides the) label near each clickable element.
Pressing the shortcut triggers the "click" for the associated element.
@structure
- A Map object: enables mapping of a String (the shortcut) to an Object (the DOM element).
- A Configuration object: contains anything that can be "externalized" (preferences, actual label generation, etc).
- A KeyNav object: the main object that handles all the messy stuff.
@changelog
0.1
initial
0.1.1
- added labels index for faster looking up of labels
- moved creation of labels to initialization
- added event handler for invalidating labels when document changes
*/
//javascript.options.strict = true;
//browser.dom.window.dump.enabled = true;
function Map() {
function LOG(message) {
//window.dump( "[Map] " + message + "\n");
}
// internal map
var map;
var bEmpty;
/* returns the value associated with the given key
*
* @param key
* @return the associated value, or null if none
*/
this.getValue = function (key) {
if (key in map)
return map[key];
return null;
}
/* returns a set of values whose keys pass a certain filter
*
* @param predicate a filter Function [key->Boolean] that returns True
* iff the key's value should be included in the result set
* @return a values Array
*/
this.getValuesByPredicate = function (predicate) {
var values = new Array(0);
for (key in map) {
if ( predicate(key) ) {
values.push( map[key] );
}
}
return values;
}
/* add a key=value binding
*
* @param val
* @param key
*/
this.setValue = function (key, val) {
bEmpty = false;
map[key] = val;
LOG( "bound \"" + key + "\" to " + val);
}
this.clear = function () {
map = new Object();
bEmpty = true;
}
this.isEmpty = function() {
return bEmpty;
}
this.keys = function () {
var keys = new Array();
for ( var key in map ) {
keys.push(key);
}
return keys;
}
this.values = function () {
var values = new Array();
for ( var key in map ) {
values.push( map[key] );
}
return values;
}
this.clear();
}
/* Configuration object */
function Configuration() {
function LOG(message) {
//window.dump( "[Configuration] " + message + "\n");
}
this.getPreference = function (key) {
if ( key=="activationKeyCode" ) {
return 70; //f
}
else if ( key=="deactivationKeyCode" ) {
return 70; //f
}
else
return null;
}
/* returns a UNIQUE shortcut calculated using `ndx`
*
* @param ndx a non negative integer Number
* @return a unique (in terms of ndx) String
*/
this.generateShortcut = function (ndx) {
return ndx;
}
this.createLabel = function(element,shortcut) {
var overlay = getElementOverlay(element);
var overlayId = "keynav.shortcut["+shortcut+"]";
// no overlay at all yet? create one
if (!overlay) {
LOG("creating a new empty, hidden overlay");
overlay = document.createElement("span");
overlay.style.position = "absolute";
overlay.style.background = "lightyellow";
overlay.style.fontSize = "small";
overlay.style.fontColor = "black";
overlay.style.border = "1px dashed darkgray";
overlay.style.fontColor = "black";
overlay.style.visibility = "hidden";
overlay.style.padding = "1px";
// insert as a sibling, because the element itself might not be able
// to have children (like a Button, for example)
element.parentNode.insertBefore(overlay,element);
}
// new/wrong shortcut? fix shortcut
if ( (!overlay.id) | (overlay.id!=overlayId) ) {
LOG("changing overlay id from '" + overlay.id + "' to '" + overlayId + "'");
overlay.id = overlayId;
overlay.innerHTML = "<font color=\"black\">" + shortcut + "</font>";
element.setAttribute( "keynav:shortcut", shortcut );
}
else {
LOG("overlay already exists for id: " + overlayId);
}
return overlay;
}
/** Returns the shortcut overlay of the specified element
*
* @param element
* @return
*/
function getElementOverlay(element) {
if ( element.hasAttribute("keynav:shortcut") ) {
var shortcutElementId = "keynav.shortcut[" +
element.getAttribute("keynav:shortcut") + "]";
var shortcutElement = document.getElementById(shortcutElementId);
return shortcutElement;
} else {
return null;
}
}
/** highlights (or dims) the element's shortcut.
*
* @param element the Element whose shortcut is to be highlighted/dimmed
* @param on a Boolean - true to highlight, false to dim.
*/
this.highlightShortcut = function (element,on) {
var overlay = getElementOverlay(element);
if (overlay) {
overlay.style.background = on ? "lightgreen" : "lightyellow";
overlay.style.border = on ? "1px solid black" : "1px dashed darkgray";
}
}
/** Selects the elements of the document to assign shortcuts to
*
* @return an array of Elements
*/
this.getClickableElements = function() {
var ems = new Array(0);
function addClickableElementsIn( parent ) {
for (var i=0; i<parent.childNodes.length; ++i) {
var node = parent.childNodes[i];
if (node.nodeType!=1) {
continue;
}
var clickable = node.nodeName.toLowerCase()=="input" |
node.nodeName.toLowerCase()=="select" |
node.hasAttribute("href") |
node.hasAttribute("onclick");
if ( clickable ) {
ems.push(node);
}
addClickableElementsIn(node);
}
}
addClickableElementsIn(document);
return ems;
}
/** Simulates a user-click on an element
*
* @param element the element that should be "clicked"
*/
this.simulateClick = function (element, wind) {
if ( element.nodeName.toLowerCase()=="input" ) {
// only INPUT elements implement the click() method
element.focus();
element.click();
}
if ( element.nodeName.toLowerCase()=="select" ) {
element.focus();
}
else if (element.hasAttribute("onclick")) {
// must come before checking "href" because links may have a
// pseudo "href" but a real "onclick"
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window, 0,
0, 0, 0, 0, false, false, false, false, 0, null);
element.dispatchEvent(evt);
}
else if (element.hasAttribute("href")) {
window.location = element.getAttribute("href");
}
}
}
function KeyNav() {
function LOG(message) {
//window.dump( "[KeyNav] " + message + "\n");
}
/** displays information that most users will find useful
*
* @param message a String to display to the user
*/
var displayOverlay;
function display(message) {
if (!displayOverlay) {
displayOverlay = document.createElement("div");
displayOverlay.style.position = "fixed";
displayOverlay.style.display = "block";
displayOverlay.style.left = "40%";
displayOverlay.style.top = "40%";
displayOverlay.style.background = "lightgreen";
displayOverlay.style.border = "1px dashed black";
displayOverlay.style.padding = "2px";
displayOverlay.style.zorder = 500;
displayOverlay.style.opacity = 0.8;
displayOverlay.setAttribute("align","center");
document.getElementsByTagName("body")[0].appendChild( displayOverlay );
}
if (message=="") {
displayOverlay.style.visibility = "hidden";
} else {
displayOverlay.style.visibility = "visible";
displayOverlay.innerHTML = message;
}
}
var conf = new Configuration();
var bindings = new Map(); //maps Strings (shortcuts) to Elements
var acceptInput = false; //off by default
// last searched shortcut.
// the shortcut is searched incrementally
var lastShortcut = "";
/** highlights the overlays for the specified elements. Dims all other
* elements
*
* @param targets an Elements array whose overlays should be highlighted
*/
function highlightOverlays( targets ) {
//TODO use _labels instead of bindings.values()
// trivial implementation: dims all elements and highlights the
// specified ones
// dim all elements
var allTargets = bindings.values();
for (var i=0; i<allTargets.length; ++i) {
conf.highlightShortcut( allTargets[i], false );
}
// highlight specified elements
for (var i=0; i<targets.length; ++i) {
conf.highlightShortcut( targets[i], true );
}
}
var _labels = new Array(0);
var _labelsValid = false;
function invalidate() {
_labelsValid = false;
LOG("invalidated");
}
function isValid() {
return _labelsValid;
}
function createLabels() {
_labels = new Array(0);
bindings.clear();
var clickableElements = conf.getClickableElements();
for (var i=0; i<clickableElements.length; ++i) {
var element = clickableElements[i];
var shortcut = conf.generateShortcut(i);
var label = conf.createLabel(element,shortcut);
bindings.setValue( shortcut, element );
_labels.push( label );
}
_labelsValid = true;
LOG("labels are now valid");
}
function showLabels() {
for (var i=0; i<_labels.length; ++i) {
_labels[i].style.visibility = "visible";
}
}
function hideLabels() {
for (var i=0; i<_labels.length; ++i) {
_labels[i].style.visibility = "hidden";
}
}
/* toggles keyboard navigation on/off for the current document
*
* @param en a Boolean
*/
function toggle(en) {
if (en) {
// suspend the event handler until all labels are created (and
// inserted to the document)
document.removeEventListener("DOMNodeInserted",invalidate,true);
if (!isValid()) {
createLabels();
}
showLabels();
acceptInput = true;
display("");
// whenever the document changes, invalidate the labels
document.addEventListener("DOMNodeInserted",invalidate,true);
LOG("enabled");
}
else {
acceptInput = false;
hideLabels();
display("");
LOG("disabled");
}
}
function onKeyPress(evt) {
if (!acceptInput) return;
// append pressed character to the shortcut we are going to lookup
var ch = String.fromCharCode(evt.charCode).toLowerCase();
lastShortcut += ch;
// lookup targets starting with the shortcut
var targets = bindings.getValuesByPredicate( function(key){
if ( key.substring(0,lastShortcut.length)==lastShortcut )
return true;
else
return false;
} );
highlightOverlays( targets );
if ( targets.length==0 ) {
// no targets at all - start a new search next time
display("");
LOG(lastShortcut + " is unbound. clearing");
lastShortcut = "";
}
else if ( targets.length==1 ) {
// exactly one match - navigate to target, and start a new search
// next time
display("");
LOG(lastShortcut + " bound to 1 target. clearing. navigating");
if (targets[0]) {
toggle(false);
conf.simulateClick(targets[0]);
}
else {
LOG("no target for sequence: " + lastShortcut);
}
lastShortcut = "";
}
else {
// more than one match - do nothing. next search will be appended
display("Keep typing.. " + targets.length +
" elements match so far<br>(or press Enter to \"click\" " +
lastShortcut + ")");
LOG(lastShortcut + " bound to " + targets.length + " targets");
}
}
function onKeyDown(evt) {
var currEle = document.activeElement;
if ( currEle.tagName == "input" | currEle.tagName == "INPUT" | currEle.tagName == "textarea" | currEle.tagName == "TEXTAREA" )
{
toggle(false);
return;
}
// activation
if (!acceptInput & evt.keyCode==conf.getPreference("activationKeyCode")) {
toggle(true);
}
// deactivation
else if (acceptInput & evt.keyCode==conf.getPreference("deactivationKeyCode")) {
toggle(false);
return;
}
if (!acceptInput) return;
switch (evt.keyCode) {
//case KeyEvent.DOM_VK_RETURN:
case 0x0D: //KeyEvent.DOM_VK_ENTER:
// match the exact shortcut typed so far
var target = bindings.getValue(lastShortcut);
LOG("match for exact shortcut " + lastShortcut + ": " + target);
if (target) {
toggle(false);
conf.simulateClick(target);
}
lastShortcut = "";
break;
}
}
function installEventListeners() {
window.addEventListener("keydown",onKeyDown,true);
window.addEventListener("keypress",onKeyPress,true);
}
createLabels();
installEventListeners();
}
var keynav = new KeyNav();
Script to hint and open a link in a new window/tab.
// KeyNavNT
// version 0.1.1 beta
// Itamar Benzaken
// modified by Andrwe and Seiichiro0185
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// To install, you need Greasemonkey: http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// To uninstall, go to Tools/Manage User Scripts,
// select "KeyNav", and click Uninstall.
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name KeyNavNT
// @description Enables keyboard navigation
// @namespace tag:itamar.benzaken@gmail.com,2008-09-10:KeyNav
// @include *
// ==/UserScript==
/*
@what
The script enables keyboard navigation.
@how
Pressing a hotkey displays a (or hides the) label near each clickable element.
Pressing the shortcut triggers the "click" for the associated element.
@structure
- A Map object: enables mapping of a String (the shortcut) to an Object (the DOM element).
- A Configuration object: contains anything that can be "externalized" (preferences, actual label generation, etc).
- A KeyNav object: the main object that handles all the messy stuff.
@changelog
0.1
initial
0.1.1
- added labels index for faster looking up of labels
- moved creation of labels to initialization
- added event handler for invalidating labels when document changes
*/
//javascript.options.strict = true;
//browser.dom.window.dump.enabled = true;
function Map() {
function LOG(message) {
//window.dump( "[Map] " + message + "\n");
}
// internal map
var map;
var bEmpty;
/* returns the value associated with the given key
*
* @param key
* @return the associated value, or null if none
*/
this.getValue = function (key) {
if (key in map)
return map[key];
return null;
}
/* returns a set of values whose keys pass a certain filter
*
* @param predicate a filter Function [key->Boolean] that returns True
* iff the key's value should be included in the result set
* @return a values Array
*/
this.getValuesByPredicate = function (predicate) {
var values = new Array(0);
for (key in map) {
if ( predicate(key) ) {
values.push( map[key] );
}
}
return values;
}
/* add a key=value binding
*
* @param val
* @param key
*/
this.setValue = function (key, val) {
bEmpty = false;
map[key] = val;
LOG( "bound \"" + key + "\" to " + val);
}
this.clear = function () {
map = new Object();
bEmpty = true;
}
this.isEmpty = function() {
return bEmpty;
}
this.keys = function () {
var keys = new Array();
for ( var key in map ) {
keys.push(key);
}
return keys;
}
this.values = function () {
var values = new Array();
for ( var key in map ) {
values.push( map[key] );
}
return values;
}
this.clear();
}
/* Configuration object */
function Configuration() {
function LOG(message) {
//window.dump( "[Configuration] " + message + "\n");
}
this.getPreference = function (key) {
if ( key=="activationKeyCode" ) {
return 78; //F12
}
else if ( key=="deactivationKeyCode" ) {
return 78; //F12
}
else
return null;
}
/* returns a UNIQUE shortcut calculated using `ndx`
*
* @param ndx a non negative integer Number
* @return a unique (in terms of ndx) String
*/
this.generateShortcut = function (ndx) {
return ndx;
}
this.createLabel = function(element,shortcut) {
var overlay = getElementOverlay(element);
var overlayId = "keynav.shortcut["+shortcut+"]";
// no overlay at all yet? create one
if (!overlay) {
LOG("creating a new empty, hidden overlay");
overlay = document.createElement("span");
overlay.style.position = "absolute";
overlay.style.background = "lightyellow";
overlay.style.fontSize = "small";
overlay.style.fontColor = "black";
overlay.style.border = "1px dashed darkgray";
overlay.style.fontColor = "black";
overlay.style.visibility = "hidden";
overlay.style.padding = "1px";
// insert as a sibling, because the element itself might not be able
// to have children (like a Button, for example)
element.parentNode.insertBefore(overlay,element);
}
// new/wrong shortcut? fix shortcut
if ( (!overlay.id) | (overlay.id!=overlayId) ) {
LOG("changing overlay id from '" + overlay.id + "' to '" + overlayId + "'");
overlay.id = overlayId;
overlay.innerHTML = "<font color=\"black\">" + shortcut + "</font>";
element.setAttribute( "keynav:shortcut", shortcut );
}
else {
LOG("overlay already exists for id: " + overlayId);
}
return overlay;
}
/** Returns the shortcut overlay of the specified element
*
* @param element
* @return
*/
function getElementOverlay(element) {
if ( element.hasAttribute("keynav:shortcut") ) {
var shortcutElementId = "keynav.shortcut[" +
element.getAttribute("keynav:shortcut") + "]";
var shortcutElement = document.getElementById(shortcutElementId);
return shortcutElement;
} else {
return null;
}
}
/** highlights (or dims) the element's shortcut.
*
* @param element the Element whose shortcut is to be highlighted/dimmed
* @param on a Boolean - true to highlight, false to dim.
*/
this.highlightShortcut = function (element,on) {
var overlay = getElementOverlay(element);
if (overlay) {
overlay.style.background = on ? "lightgreen" : "lightyellow";
overlay.style.border = on ? "1px solid black" : "1px dashed darkgray";
}
}
/** Selects the elements of the document to assign shortcuts to
*
* @return an array of Elements
*/
this.getClickableElements = function() {
var ems = new Array(0);
function addClickableElementsIn( parent ) {
for (var i=0; i<parent.childNodes.length; ++i) {
var node = parent.childNodes[i];
if (node.nodeType!=1) {
continue;
}
var clickable = node.nodeName.toLowerCase()=="input" |
node.nodeName.toLowerCase()=="select" |
node.hasAttribute("href") |
node.hasAttribute("onclick");
if ( clickable ) {
ems.push(node);
}
addClickableElementsIn(node);
}
}
addClickableElementsIn(document);
return ems;
}
/** Simulates a user-click on an element
*
* @param element the element that should be "clicked"
*/
this.simulateClick = function (element) {
if ( element.nodeName.toLowerCase()=="input" ) {
// only INPUT elements implement the click() method
element.focus();
element.click();
}
if ( element.nodeName.toLowerCase()=="select" ) {
element.focus();
}
else if (element.hasAttribute("onclick")) {
// must come before checking "href" because links may have a
// pseudo "href" but a real "onclick"
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window, 0,
0, 0, 0, 0, false, false, false, false, 0, null);
element.dispatchEvent(evt);
}
else if (element.hasAttribute("href")) {
//window.location = element.getAttribute("href");
var win = window.open(element.getAttribute("href"), "", "");
// win.focus();
}
}
}
function KeyNav() {
function LOG(message) {
//window.dump( "[KeyNav] " + message + "\n");
}
/** displays information that most users will find useful
*
* @param message a String to display to the user
*/
var displayOverlay;
function display(message) {
if (!displayOverlay) {
displayOverlay = document.createElement("div");
displayOverlay.style.position = "fixed";
displayOverlay.style.display = "block";
displayOverlay.style.left = "40%";
displayOverlay.style.top = "40%";
displayOverlay.style.background = "lightgreen";
displayOverlay.style.border = "1px dashed black";
displayOverlay.style.padding = "2px";
displayOverlay.style.zorder = 500;
displayOverlay.style.opacity = 0.8;
displayOverlay.setAttribute("align","center");
document.getElementsByTagName("body")[0].appendChild( displayOverlay );
}
if (message=="") {
displayOverlay.style.visibility = "hidden";
} else {
displayOverlay.style.visibility = "visible";
displayOverlay.innerHTML = message;
}
}
var conf = new Configuration();
var bindings = new Map(); //maps Strings (shortcuts) to Elements
var acceptInput = false; //off by default
// last searched shortcut.
// the shortcut is searched incrementally
var lastShortcut = "";
/** highlights the overlays for the specified elements. Dims all other
* elements
*
* @param targets an Elements array whose overlays should be highlighted
*/
function highlightOverlays( targets ) {
//TODO use _labels instead of bindings.values()
// trivial implementation: dims all elements and highlights the
// specified ones
// dim all elements
var allTargets = bindings.values();
for (var i=0; i<allTargets.length; ++i) {
conf.highlightShortcut( allTargets[i], false );
}
// highlight specified elements
for (var i=0; i<targets.length; ++i) {
conf.highlightShortcut( targets[i], true );
}
}
var _labels = new Array(0);
var _labelsValid = false;
function invalidate() {
_labelsValid = false;
LOG("invalidated");
}
function isValid() {
return _labelsValid;
}
function createLabels() {
_labels = new Array(0);
bindings.clear();
var clickableElements = conf.getClickableElements();
for (var i=0; i<clickableElements.length; ++i) {
var element = clickableElements[i];
var shortcut = conf.generateShortcut(i);
var label = conf.createLabel(element,shortcut);
bindings.setValue( shortcut, element );
_labels.push( label );
}
_labelsValid = true;
LOG("labels are now valid");
}
function showLabels() {
for (var i=0; i<_labels.length; ++i) {
_labels[i].style.visibility = "visible";
}
}
function hideLabels() {
for (var i=0; i<_labels.length; ++i) {
_labels[i].style.visibility = "hidden";
}
}
/* toggles keyboard navigation on/off for the current document
*
* @param en a Boolean
*/
function toggle(en) {
if (en) {
// suspend the event handler until all labels are created (and
// inserted to the document)
document.removeEventListener("DOMNodeInserted",invalidate,true);
if (!isValid()) {
createLabels();
}
showLabels();
acceptInput = true;
display("");
// whenever the document changes, invalidate the labels
document.addEventListener("DOMNodeInserted",invalidate,true);
LOG("enabled");
}
else {
acceptInput = false;
hideLabels();
display("");
LOG("disabled");
}
}
function onKeyPress(evt) {
if (!acceptInput) return;
// append pressed character to the shortcut we are going to lookup
var ch = String.fromCharCode(evt.charCode).toLowerCase();
lastShortcut += ch;
// lookup targets starting with the shortcut
var targets = bindings.getValuesByPredicate( function(key){
if ( key.substring(0,lastShortcut.length)==lastShortcut )
return true;
else
return false;
} );
highlightOverlays( targets );
if ( targets.length==0 ) {
// no targets at all - start a new search next time
display("");
LOG(lastShortcut + " is unbound. clearing");
lastShortcut = "";
}
else if ( targets.length==1 ) {
// exactly one match - navigate to target, and start a new search
// next time
display("");
LOG(lastShortcut + " bound to 1 target. clearing. navigating");
if (targets[0]) {
toggle(false);
conf.simulateClick(targets[0]);
}
else {
LOG("no target for sequence: " + lastShortcut);
}
lastShortcut = "";
}
else {
// more than one match - do nothing. next search will be appended
display("Keep typing.. " + targets.length +
" elements match so far<br>(or press Enter to \"click\" " +
lastShortcut + ")");
LOG(lastShortcut + " bound to " + targets.length + " targets");
}
}
function onKeyDown(evt) {
var currEle = document.activeElement;
if ( currEle.tagName == "input" | currEle.tagName == "INPUT" | currEle.tagName == "textarea" | currEle.tagName == "TEXTAREA" )
{
toggle(false);
return;
}
// activation
if (!acceptInput & evt.keyCode==conf.getPreference("activationKeyCode")) {
toggle(true);
}
// deactivation
else if (acceptInput & evt.keyCode==conf.getPreference("deactivationKeyCode")) {
toggle(false);
return;
}
if (!acceptInput) return;
switch (evt.keyCode) {
//case KeyEvent.DOM_VK_RETURN:
case 0x0D: //KeyEvent.DOM_VK_ENTER:
// match the exact shortcut typed so far
var target = bindings.getValue(lastShortcut);
LOG("match for exact shortcut " + lastShortcut + ": " + target);
if (target) {
toggle(false);
conf.simulateClick(target);
}
lastShortcut = "";
break;
}
}
function installEventListeners() {
window.addEventListener("keydown",onKeyDown,true);
window.addEventListener("keypress",onKeyPress,true);
}
createLabels();
installEventListeners();
}
var keynav = new KeyNav();