Thank you for the reply. I'm not sure I understand the instructions.
If I disable "secondary click" in the macOS Trackpad settings I can then re-enable it in BTT (Globally) to still open a contextual menu when I two-finger tap on my Mac, and can also two-finger tap on links within Safari to open them in a tab without the contextual menu being opened?
Or would I have to re-enable two-finger tapping to open a contextual menu in every app including the Finder except for Safari? Would I still be able to open contextual menus in Safari with a two-finger tap when not two-finger tapping on a link?
There was a Safari extension called Linkthing (downloadable extension) that was able to differentiate between clicking on links with a two-finger tap and clicking elsewhere within the Safari page to initiate a contextual menu. LinkThing also allowed me to hold down the control key and click on a link to bring up a contextual menu instead of opening the link in a new tab automatically. This all worked while still having secondary-click enabled in the Trackpad preferences.
I found an old copy of the extension and took a look at some of the code. Would it be possible for BTT to replicate that functionality?
Inside the extension file I found:
global.js
const defaults = {
kickOnLinks : 0,
kickOffLinks : 0,
newTabPosition : null,
newBgTabPosition : null,
focusLinkTarget : true,
preferWindows : false,
cmdClickIgnoresTarget : true,
rightClickForCmdClick : false,
useTPOverrideKeys : false,
tpOverrideKeyClicks : false,
rewriteGoogleLinks : false,
showLinkHrefs : true,
showMenuItems : true,
focusOriginalTab : false,
tpoKeyLeftmost : { keyCode: 65, keyIdentifier: 'U+0041' },
tpoKeyLeft : { keyCode: 83, keyIdentifier: 'U+0053' },
tpoKeyRight : { keyCode: 68, keyIdentifier: 'U+0044' },
tpoKeyRightmost : { keyCode: 70, keyIdentifier: 'U+0046' },
tpoKeyCurrent : { keyCode: 71, keyIdentifier: 'U+0047' },
lastPrefPane : 0,
specialSites : [],
hardBlacklist : [],
userBlacklist : [
'//www.example.com/',
'^https?:\\/\\/www\\.example(\\.[a-z]+)+'
],
};
const linkBlacklist = [
/\/\/www\.google(\.[a-z]+)+\/reader\/view\//,
/\/\/api\.flattr\.com\//
];
const downloadPatterns = [
/\.dmg$/, /\.dmg\?/,
/\.zip$/, /\.zip\?/,
/\.pkg$/, /\.pkg\?/,
/\.safariextz$/, /\.safariextz\?/,
/\.torrent$/, /\.torrent\?/,
/\.iso$/, /\.iso\?/,
/\.rar$/, /\.rar\?/,
/\.exe$/, /\.exe\?/,
/\.tgz$/, /\.tgz\?/,
/\.gz$/ , /\.gz\?/ ,
/\.tar$/, /\.tar\?/,
/\.xar$/, /\.xar\?/,
/\.odm$/, /\.odm\?/,
/\.acsm$/, /\.acsm\?/
];
var sa = safari.application;
var se = safari.extension;
var openedTabs = []; // to keep Safari from GCing tab references
var dummyLink = document.createElement('a');
var previousActiveTab;
if (navigator.appVersion.match('Version/5.0')) {
sa.activeBrowserWindow.openTab().url = se.baseURI + 'upgrade_notice.html';
} else {
initializeSettings();
sa.addEventListener("contextmenu", handleContextMenu, false);
sa.addEventListener("command", handleCommand, false);
sa.addEventListener("message", handleMessage, false);
se.settings.addEventListener("change", handleSettingChange, false);
se.addContentScriptFromURL(se.baseURI + 'injected.js', ['safari-reader://*/*'], null, true);
}
function SettingsObject(addedProps) {
for (var key in defaults) {
if (!(defaults[key] instanceof Array)) {
this[key] = se.settings[key]
}
}
for (var p in addedProps) {
this[p] = addedProps[p];
}
}
function addLinkToIp(info) {
var sourceTab = sa.activeBrowserWindow.activeTab;
var xhr = new XMLHttpRequest();
var ipUrl = 'https://www.instapaper.com/api/add';
ipUrl += '?username=' + encodeURIComponent(se.secureSettings.ipUsername);
ipUrl += '&password=' + encodeURIComponent(se.secureSettings.ipPassword);
ipUrl += '&url=' + encodeURIComponent(info.url);
ipUrl += '&selection=Added%20from%20"' + encodeURIComponent(info.tit);
ipUrl += '"%20(' + encodeURIComponent(info.ref) + ')';
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.responseText == 201) {
sourceTab.page.dispatchMessage('ipAddState','saved');
} else {
sourceTab.page.dispatchMessage('ipAddState','failed');
var message = 'LinkThing could not log in to your Instapaper account. ';
message += 'Please check your username and password.';
alert(message);
}
}
};
xhr.open('GET', ipUrl, true);
xhr.send(null);
sourceTab.page.dispatchMessage('ipAddState','saving');
}
function blurNextOpened(event) {
previousActiveTab.activate();
previousActiveTab = null;
sa.removeEventListener('open', blurNextOpened, true);
}
function getBlacklistStatus(url) {
function stringToRe(string) {
return new RegExp(string, 'i');
}
for (var i = 0; i < se.settings.userBlacklist.length; i++) {
if (stringToRe(se.settings.userBlacklist[i]).test(url)) {
return 2;
}
}
for (var i = 0; i < se.settings.hardBlacklist.length; i++) {
if (stringToRe(se.settings.hardBlacklist[i]).test(url)) {
return 1;
}
}
return 0;
}
function getSitePrefs(hostname) {
for (var i = 0; i < se.settings.specialSites.length; i++) {
if (hostname === se.settings.specialSites[i].hostname) {
return se.settings.specialSites[i];
}
}
return {
hostname : hostname,
kickOffLinks : null,
kickOnLinks : null,
newTabPosition : null,
newBgTabPosition : null,
focusLinkTarget : null,
preferWindows : null
};
}
function focusNextOpened(event) {
event.target.activate();
sa.removeEventListener('open', focusNextOpened, true);
}
function handleCommand(event) {
var sourceTab = sa.activeBrowserWindow.activeTab;
if (event.command === 'showPrefsBox') {
if (sourceTab.url == '' || sourceTab.url == undefined) {
var am = 'Sorry, LinkThing cannot open its settings here.';
am += 'Please load a web page in this tab and try again.';
alert(am);
} else {
sourceTab.page.dispatchMessage('showPrefsBox', event.userInfo);
}
}
else if (event.command === 'addLinkToIp') {
addLinkToIp(event.userInfo);
}
}
function handleContextMenu(event) {
if (event.userInfo && se.settings.showMenuItems) {
event.contextMenu.appendContextMenuItem('showPrefsBox','LinkThing Settings...');
}
if (event.userInfo.url && se.secureSettings.ipUsername) {
event.contextMenu.appendContextMenuItem('addLinkToIp','Add Link to Instapaper');
}
}
function handleLinkKick(event) {
console.log('link kick:', event.message);
var settings = event.message.settings;
var targetIsReader = event.target instanceof SafariReader;
var sourceTab = targetIsReader ? event.target.tab : event.target;
var thisWindow = sourceTab.browserWindow;
var srcTabIndex = thisWindow.tabs.indexOf(sourceTab);
var background = !(!event.message.shift !== !settings.focusLinkTarget);
var tpo = event.message.tpo;
var positionSetting = event.message.positionSetting;
var newTab;
if (positionSetting === null)
return;
if (tpo.fr)
background = !background;
if (targetIsReader)
background = true;
if (positionSetting === undefined)
positionSetting = 1;
if (event.message.option) {
newTab = sa.openBrowserWindow().activeTab;
newTab.url = event.message.href;
background && thisWindow.activate();
} else {
var newTabPosition = (
positionSetting == -2 ? 0 :
positionSetting == 0 ? srcTabIndex :
positionSetting == 1 ? srcTabIndex + 1 :
positionSetting == -1 ? thisWindow.tabs.length : null
);
newTab = thisWindow.openTab((background ? 'background' : 'foreground'), newTabPosition);
newTab.url = event.message.href;
}
newTab.sourceTab = sourceTab;
openedTabs.push(newTab);
newTab.addEventListener('close', function (closeEvent) {
var closedTab = closeEvent.target;
openedTabs.splice(openedTabs.indexOf(closedTab), 1);
if (!se.settings.focusOriginalTab)
return;
if (closedTab.sourceTab && closedTab.sourceTab.url) {
closedTab.sourceTab.activate();
}
}, false);
}
function handleMessage(event) {
switch (event.name) {
case 'handleLinkMouseOver':
event.target.page.dispatchMessage('showHrefBox', event.message);
break;
case 'handleLinkMouseOut':
event.target.page.dispatchMessage('hideHrefBox');
break;
case 'handleLinkKick':
handleLinkKick(event);
break;
case 'focusNextOpenedTab':
sa.addEventListener('open', focusNextOpened, true);
break;
case 'blurNextOpenedTab':
previousActiveTab = event.target;
sa.addEventListener('open', blurNextOpened, true);
break;
case 'loadInMyTab':
// message source must be a SafariReader instance
event.target.tab.url = event.message;
break;
case 'passSettings':
if (!event.message || !event.message.hostname)
break;
var settings = new SettingsObject({
hostname : event.message,
linkBlacklist : linkBlacklist,
downloadPatterns : downloadPatterns,
blacklistStatus : getBlacklistStatus(event.message.url),
});
var sitePrefs = getSitePrefs(event.message.hostname);
for (var key in sitePrefs) {
if (sitePrefs[key] !== null) {
settings[key] = sitePrefs[key];
}
}
var listener = (event.target instanceof SafariReader) ? event.target : event.target.page;
listener.dispatchMessage('receiveSettings', settings);
break;
case 'whoAreMyFellowFrames?':
event.target.page.dispatchMessage('giveMeYourWindowName', event.message);
break;
case 'myWindowNameIs':
event.target.page.dispatchMessage('youHaveAFellowFrameNamed:', event.message);
break;
case 'logThis':
console.log(event.message);
break;
case 'passPrefs':
try {
var hostname = event.target.url.split('//')[1].split('/')[0];
} catch (e) {
var hostname = 'nohost';
}
var settings = new SettingsObject({
hostname : hostname,
sitePrefs : getSitePrefs(hostname),
lastPrefPane : se.settings.lastPrefPane,
userBlacklist : se.settings.userBlacklist,
});
event.target.page.dispatchMessage('receivePrefs', settings);
break;
case 'saveLastPrefPane':
se.settings.lastPrefPane = event.message;
break;
case 'saveSitePrefs':
var specialSites = se.settings.specialSites;
var ssEntry = specialSites.filter(function (ss) {
return ss.hostname == event.message.hostname;
})[0];
if (!ssEntry) {
ssEntry = {};
specialSites.unshift(ssEntry);
}
for (var key in event.message) {
ssEntry[key] = event.message[key];
}
for (var key in ssEntry) {
if (ssEntry[key] == null) {
delete ssEntry[key];
}
}
if (specialSites.length > 300) {
specialSites.pop();
}
se.settings.specialSites = specialSites;
passNewSettingsToAllPages();
break;
case 'saveGlobalPrefs':
for (var key in event.message) {
se.settings[key] = event.message[key];
}
passNewSettingsToAllPages();
break;
case 'saveTPOverrideKey':
se.settings[event.message.which] = event.message.data;
passNewSettingsToAllPages();
break;
case 'saveUrlFilters':
var filterStrings = event.message.split('\n');
saveUserBlacklist(filterStrings);
passNewSettingsToAllPages();
break;
case 'requestClosePrefsBox':
event.target.page.dispatchMessage('prepareToClose');
break;
case 'closePrefsBox':
event.target.page.dispatchMessage('closePrefsBox');
break;
case 'consoleLog':
console.log.apply(window, event.message);
break;
}
}
function handleSettingChange(event) {
if (event.newValue !== event.oldValue) {
switch (event.key) {
default: {
}
}
}
}
function passNewSettingsToAllPages() {
var message = {};
var thisWindow = {};
var thisTab = {};
for (var j = 0; j < sa.browserWindows.length; j++) {
thisWindow = sa.browserWindows[j];
for (var k = 0; k < thisWindow.tabs.length; k++) {
thisTab = thisWindow.tabs[k];
if (thisTab.url && thisTab.url.match(/^http/)) {
console.log('Passing settings to page at ' + thisTab.url);
dummyLink.href = thisTab.url;
var settings = new SettingsObject({
hostname : dummyLink.hostname,
blacklistStatus : getBlacklistStatus(thisTab.url),
});
var sitePrefs = getSitePrefs(dummyLink.hostname);
for (var key in sitePrefs) {
if (sitePrefs[key] !== null) {
settings[key] = sitePrefs[key];
}
}
thisTab.page.dispatchMessage('receiveSettings', settings);
}
}
}
}
function saveUserBlacklist(filterStrings) {
function isNotEmpty(string) {
return string !== '';
}
se.settings.userBlacklist = filterStrings.filter(isNotEmpty);
}
function initializeSettings() {
var lastVersion = se.settings.lastVersion;
for (var key in defaults) {
if (se.settings[key] === undefined) {
se.settings[key] = defaults[key];
}
}
if (lastVersion < 1006) {
se.settings.cmdClickIgnoresTarget = false;
}
if (lastVersion < 1018) {
se.settings.rewriteGoogleLinks = false;
}
if (lastVersion < 2009) {
se.settings.userBlacklist = defaults.userBlackList;
}
if (lastVersion < 2042) {
if (se.settings.newTabPosition == undefined) se.settings.newTabPosition = 1;
if (se.settings.newBgTabPosition == undefined) se.settings.newBgTabPosition = 1;
}
if (lastVersion < 2051) {
if (se.settings.newTabPosition == 1) se.settings.newTabPosition = null;
if (se.settings.newBgTabPosition == 1) se.settings.newBgTabPosition = null;
}
se.settings.lastVersion = 2051;
}
And, injected.js
HTMLAnchorElement.prototype.handleKeyUp = function (e) {
switch (e.keyCode) {
case settings.tpoKeyLeftmost.keyCode : positionOverride = { ps: -2, fr: e.shiftKey }; break;
case settings.tpoKeyLeft.keyCode : positionOverride = { ps: 0, fr: e.shiftKey }; break;
case settings.tpoKeyRight.keyCode : positionOverride = { ps: 1, fr: e.shiftKey }; break;
case settings.tpoKeyRightmost.keyCode : positionOverride = { ps: -1, fr: e.shiftKey }; break;
case settings.tpoKeyCurrent.keyCode : positionOverride = { ps: null }; break;
default: return;
}
if (positionOverride.ps === undefined) {
return;
}
if (settings.tpOverrideKeyClicks) {
var ps = positionOverride.ps;
if (ps == 1) {
positionOverride.ps = null;
if (safariVersion >= 601) {
var message = {
href : this.href,
tpo : positionOverride || {},
shift : e.shiftKey,
option : e.altKey,
positionSetting : 1,
settings : settings
};
safari.self.tab.dispatchMessage('handleLinkKick', message);
} else {
var evt = new MouseEvent('click', {
button : 0,
metaKey : true,
shiftKey : e.shiftKey
});
this.dispatchEvent(evt);
}
}
else if (ps === null) {
window.location.href = this.href;
}
else {
var message = {
href : this.href,
tpo : positionOverride || {},
shift : e.shiftKey,
option : e.altKey,
positionSetting : positionOverride.ps,
settings : settings
};
safari.self.tab.dispatchMessage('handleLinkKick', message);
}
}
if (hrefRevealer) {
var statusText = this.href;
var targetText = (
positionOverride.ps == -2 ? 'new leftmost' :
positionOverride.ps == 0 ? 'new left' :
positionOverride.ps == 1 ? 'new right' :
positionOverride.ps == -1 ? 'new rightmost' :
positionOverride.ps == null ? 'this' : ''
);
var focusText = (positionOverride.ps == null || (!settings.focusLinkTarget !== !e.shiftKey))
? '' : ' background';
statusText += ' (opens in ' + targetText + focusText + ' tab)';
hrefRevealer.say(statusText);
}
}
HTMLAnchorElement.prototype.isBlacklisted = function () {
return settings.linkBlacklist.some(matches, this.href) || settings.downloadPatterns.some(matches, this.href);
};
HTMLAnchorElement.prototype.isEligible = function () {
var link = this;
var tgt = link.target;
if (link.getAttribute('href') == '#') return false;
if (!(/^http/).test(link.protocol)) return false;
if (tgt && contains(frameNames, tgt)) return false;
if (link.isBlacklisted()) return false;
if (link.isTargetingExempt()) return false;
return true;
};
HTMLAnchorElement.prototype.isTargetingExempt = function () {
var exemptClasses = [
'floatbox','greybox','highslide','lightwindow','lbOn','lightview',
'sexylightbox','smoothbox','submodal','thickbox'
];
var exemptRels = [
'lightbox','lytebox','lyteshow','lyteframe','facebox','gb_image','gb_page','imagezoom',
'thickbox','prettyPhoto','lighterbox','milkbox','shadowbox','rokbox','gallery'
];
var exemptRoles = [
'button','presentation'
];
this.role = this.getAttribute('role');
if (this.className && contains(exemptClasses, this.className)) return true;
if (this.rel && contains(exemptRels , this.rel )) return true;
if (this.role && contains(exemptRoles , this.role )) return true;
return false;
};
HTMLAnchorElement.prototype.isOffsite = function () {
if (this.host != window.location.host) {
return true;
} else if (gHostnameRe.test(location.hostname) && this.className == 'l') {
return true;
} else return false;
};
HTMLAnchorElement.prototype.processLinkClick = function (e) {
var link = this;
var modkeys = e.shiftKey * 1 + e.ctrlKey * 2 + e.altKey * 4 + e.metaKey * 8;
if (modkeys == 1 || modkeys == 4 || modkeys == 5)
return;
if (link.isEligible()) {
if (rewriteGoogleLinks) {
if (link.pathname == '/url') {
if ((/[?&]url=[^&]+/).test(link.search)) {
link.href = decodeURIComponent(link.search.split(/[?&]url=/)[1].split('&')[0]);
}
}
}
var willKick = link.willKick(e, true);
if (willKick) {
var background = !(!e.shiftKey !== !settings.focusLinkTarget);
var positionSetting = (positionOverride.ps !== undefined)
? positionOverride.ps
: (background ? settings.newBgTabPosition : settings.newTabPosition);
if (positionSetting !== null) {
e.stopPropagation();
e.preventDefault();
var message = {
href : link.href,
tpo : positionOverride || {},
shift : e.shiftKey,
option : e.altKey,
positionSetting : positionSetting,
settings : settings
};
safari.self.tab.dispatchMessage('handleLinkKick', message);
} else {
if (willKick == 'byPref')
link.setTempTarget('_blank', 100);
if (background)
safari.self.tab.dispatchMessage('blurNextOpenedTab');
else {
safari.self.tab.dispatchMessage('focusNextOpenedTab');
}
}
positionOverride = {};
handleMouseOutOfLink(e);
} else {
if (positionOverride.ps === null) {
link.setTempTarget('_top', 100);
} else
if (e.metaKey) {
e.preventDefault();
if (selfIsReader) {
safari.self.tab.dispatchMessage('loadInMyTab', link.href);
} else {
window.location.href = link.href;
}
} else {
if (['_self','_parent','_top'].indexOf(link.target) < 0) {
link.setTempTarget('_top', 100);
} else {
handleMouseOutOfLink(e);
}
}
}
} else {
if (settings.downloadPatterns.some(matches, link.href)) {
link.target = '';
}
}
}
HTMLAnchorElement.prototype.setTempTarget = function (target, duration) {
var link = this;
var oldTarget = link.target;
link.target = target;
setTimeout(function () {
link.target = oldTarget;
}, duration);
};
HTMLAnchorElement.prototype.willKick = function (e, testedEligible) {
var targetKick = !contains(['','_self','_parent','_top'], this.target);
if (testedEligible || this.isEligible()) {
if (e.metaKey && e.shiftKey)
return true;
else if (positionOverride.ps !== undefined)
return true;
var samePage = (this.href.split('#')[0] == location.href.split('#')[0]) && !(/^\#\!/).test(this.hash);
var kickPref = (this.isOffsite() ? settings.kickOffLinks : settings.kickOnLinks);
if (kickPref == 1 && samePage)
kickPref = 0;
var wouldKick = (
selfIsReader ? true :
kickPref == 1 ? true : // if not reversed, link opens in new tab
kickPref == -1 ? false : // if not reversed, link opens in this tab
targetKick // if not reversed, site will decide
);
var reversal = (
e.button == 0 ? e.metaKey :
e.button == 1 ? !e.metaKey :
e.button == 2 ? true :
e.metaKey
);
var conclusion = (settings.cmdClickIgnoresTarget) ? (wouldKick || reversal) : (!wouldKick !== !reversal);
if (conclusion && kickPref == 1)
return 'byPref';
return conclusion;
} else {
if (settings.downloadPatterns.some(matches, this.href))
return false;
return targetKick || (e.metaKey || e.button == 2);
}
};
function StatusBox(msg, mx, my) {
var sb = document.createElement('div');
sb.show = function () {
document.documentElement.appendChild(this);
setTimeout(function () { sb.style.opacity = '1'; }, 0);
if ((window.innerHeight - bottomOffset - my < this.offsetHeight + 5) && (mx < this.offsetWidth + 5)) {
this.style.top = '0';
this.style.bottom = 'auto';
this.style.borderStyle = 'none solid solid none';
this.style.borderRadius = '0 0 4px 0';
}
return this;
};
sb.say = function (msg) {
this.textContent = msg;
};
sb.destroy = function () {
document.documentElement.removeChild(sb);
hrefRevealer = null;
};
sb.style.display = 'block';
sb.style.position = 'fixed';
sb.style.zIndex = '2147483647';
sb.style.left = '0';
sb.style.top = 'auto';
sb.style.bottom = bottomOffset + 'px';
sb.style.width = 'auto';
sb.style.height = 'auto';
sb.style.maxWidth = window.innerWidth - 8 + 'px';
sb.style.overflow = 'hidden';
sb.style.margin = 0;
sb.style.borderStyle = 'solid solid none none';
sb.style.borderWidth = '1px';
sb.style.borderRadius = '0 4px 0 0';
sb.style.borderColor = '#999';
sb.style.backgroundColor = '#e3e3e3';
sb.style.padding = '1px 5px 3px 5px';
sb.style.whiteSpace = 'nowrap';
sb.style.textOverflow = 'ellipsis';
sb.style.color = 'black';
sb.style.font = 'normal normal normal 11px/normal "Lucida Grande", sans-serif';
sb.style.opacity = '0';
sb.style.transition = 'opacity 0.2s ease-in-out';
sb.textContent = msg;
return sb;
}
function clearScrolling() {
window.scrolling = false;
}
function contains(array, thing) {
return array.indexOf(thing) >= 0;
}
function handleClick(e) {
if (siteIsBlacklisted || e.ctrlKey) return;
var node = e.target;
if (node == document.documentElement) return;
while (node.href == undefined && node.parentNode) {
node = node.parentNode;
}
if (node.href) {
node.processLinkClick(e);
}
}
function handleContextMenu(e) {
var userInfo = {
x: e.clientX,
y: e.clientY
};
var node = e.target;
while (node.href == undefined && node.parentNode) {
node = node.parentNode;
}
if (!selfIsReader) {
if (node.href) {
userInfo.url = node.href;
userInfo.ref = document.location.href;
userInfo.tit = document.title;
}
safari.self.tab.setContextMenuEventUserInfo(event, userInfo);
}
if (!siteIsBlacklisted) {
if (node.href && !e.ctrlKey && settings.rightClickForCmdClick) {
window.getSelection().empty();
e.preventDefault();
var click = document.createEvent('MouseEvents');
click.initMouseEvent(
'click', true, true, window, 0, e.screenX, e.screenY, e.clientX, e.clientY,
false, false, e.shiftKey, true, 0, null
);
node.dispatchEvent(click);
}
}
}
function handleMessage(e) {
switch (e.name) {
case 'receiveSettings':
if (e.message.hostname == window.location.hostname || e.message.hostname == '*') {
// console.log('Received LinkThing settings for hostname ' + e.message.hostname, e.message);
for (var key in e.message) {
settings[key] = e.message[key];
}
if (settings.blacklistStatus !== undefined) {
siteIsBlacklisted = (settings.blacklistStatus > 0);
// document.removeEventListener('mouseover', handleMouseOver, false);
document.removeEventListener('click', handleClick, true);
if (!siteIsBlacklisted) {
// document.addEventListener('mouseover', handleMouseOver, false);
document.addEventListener('click', handleClick, true);
}
}
if (settings.rewriteGoogleLinks !== undefined) {
rewriteGoogleLinks = settings.rewriteGoogleLinks && gHostnameRe.test(location.hostname)
}
if (settings.showLinkHrefs !== undefined) {
document.removeEventListener('scroll', handleScroll, false);
if (settings.showLinkHrefs) {
showLinkHrefs = settings.showLinkHrefs && !window.statusbar.visible;
document.addEventListener('scroll', handleScroll, false);
}
}
} break;
case 'showHrefBox':
if (winIsTop) {
hrefRevealer = new StatusBox(e.message.msgStr, e.message.mouseX, e.message.mouseY).show();
} break;
case 'hideHrefBox':
winIsTop && hrefRevealer && hrefRevealer.destroy();
break;
case 'showPrefsBox':
if (winIsTop) {
showPrefsBox(e.message);
} break;
case 'closePrefsBox':
var prefsBox = document.getElementById('cksle_prefsBox');
winIsTop && prefsBox && document.documentElement.removeChild(prefsBox);
break;
case 'ipAddState':
if (winIsTop) {
setIpAddIndicator(e.message);
} break;
case 'giveMeYourWindowName':
if (window.name && window.name != e.message)
safari.self.tab.dispatchMessage('myWindowNameIs', {
framename : window.name,
requester : e.message,
});
break;
case 'youHaveAFellowFrameNamed:':
if (window.name == e.message.requester) {
if (frameNames.indexOf(e.message.framename) == -1) {
frameNames.push(e.message.framename);
// console.log('frames:', frameNames, 'this frame: "' + window.name + '"');
}
} break;
}
}
function handleMouseOutOfLink(e) {
if (revealWaiter) {
clearTimeout(revealWaiter);
revealWaiter = null;
}
e.target.removeEventListener(e.type, handleMouseOutOfLink, false);
if (e.type != 'mouseout')
e.target.removeEventListener('mouseout', handleMouseOutOfLink, false);
if (winIsTop) {
hrefRevealer && hrefRevealer.destroy();
} else {
safari.self.tab.dispatchMessage('handleLinkMouseOut');
}
}
function handleMouseOver(e) {
if (window.scrolling) return;
var et = e.target, lc = -1;
while (et && !(et instanceof HTMLAnchorElement) && (2 > ++lc))
et = et.parentElement;
if (!et || !et.href) return;
var link = et;
if (showLinkHrefs) {
link.addEventListener('mouseout', handleMouseOutOfLink, false);
document.addEventListener('click', handleMouseOutOfLink, false);
revealWaiter || (revealWaiter = setTimeout(revealHref, 100, e, link));
}
if (!siteIsBlacklisted) {
if (settings.useTPOverrideKeys) {
var handleLinkKeyDown = function (kd) {
if (/^www\.google\.[a-z]+$/.test(location.hostname)) {
kd.stopPropagation();
}
};
var handleLinkKeyUp = function (ku) {
if (['INPUT','BUTTON','SELECT','TEXTAREA'].indexOf(ku.target.nodeName) == -1) {
ku.stopPropagation();
link.handleKeyUp(ku);
}
};
var cancelLinkKeyListeners = function (me) {
positionOverride = {};
me.currentTarget.removeEventListener(me.type, cancelLinkKeyListeners, false);
document.removeEventListener('keydown', handleLinkKeyDown, true);
document.removeEventListener('keyup', handleLinkKeyUp, true);
}
document.addEventListener('keydown', handleLinkKeyDown, true);
document.addEventListener('keyup', handleLinkKeyUp, true);
link.addEventListener('mouseout', cancelLinkKeyListeners, false);
// link.addEventListener('mousedown', cancelLinkKeyListeners, false);
}
if (rewriteGoogleLinks && link.getAttribute('onmousedown')) {
link.removeAttribute('onmousedown');
if (link.pathname == '/url') {
var url = (/[?&]url=([^&]+)/.exec(link.search) || [])[1];
if (url) {
link.href = decodeURIComponent(url);
console.log('Link changed to', url);
}
}
}
}
}
function handleScroll(e) {
if (window.scrolling) return;
window.scrolling = true;
setTimeout(clearScrolling, 100);
}
function logGlobal(msg) {
safari.self.tab.dispatchMessage('logThis', msg);
}
function matches(re) {
return re.test(this);
}
function revealHref(evt, link) {
revealWaiter = null;
var modkeys = evt.shiftKey * 1 + evt.ctrlKey * 2 + evt.altKey * 4 + evt.metaKey * 8;
var statusText = '';
if (modkeys == 1) {
statusText = 'Add "' + link.href + '" to Reading List';
} else
if (modkeys == 4 || modkeys == 5) {
statusText = 'Download "' + link.href + '"';
} else {
statusText = link.href;
var background, kickTest;
if (siteIsBlacklisted) {
background = evt.shiftKey;
kickTest = evt.metaKey;
} else {
background = !(!evt.shiftKey !== !settings.focusLinkTarget) || selfIsReader;
kickTest = link.willKick(evt);
}
if (link.target && frameNames.indexOf(link.target) > -1) {
statusText += ' (opens in "' + link.target + '")';
} else if (kickTest) {
statusText += ' (opens in new' + (background ? ' background ' : ' ') + (evt.altKey ? 'window)' : 'tab)');
}
}
if (winIsTop) {
hrefRevealer = new StatusBox(statusText, evt.clientX, evt.clientY).show();
} else {
var message = { msgStr: statusText, mouseX: evt.clientX, mouseY: evt.clientY };
safari.self.tab.dispatchMessage('handleLinkMouseOver', message);
}
}
function setIpAddIndicator(state) {
if (state == 'saving') {
var ipaiBox = document.createElement('div');
ipaiBox.w = 96;
ipaiBox.h = 72;
ipaiBox.id = 'cksle_ipaiBox';
ipaiBox.setAttribute('style', '\
display: -webkit-box;\
-webkit-box-orient: vertical;\
-webkit-box-pack: center;\
position: fixed;\
z-index: 2147483647;\
margin: 0; padding: 0;\
border: 1px solid #444;\
border-radius: 5px;\
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(white), to(#F4F4F4));\
text-align: center;\
color: black;\
');
ipaiBox.style.left = window.innerWidth/2 - ipaiBox.w/2 + 'px';
ipaiBox.style.top = window.innerHeight/2 - ipaiBox.h/2 + 'px';
ipaiBox.style.width = ipaiBox.w + 'px';
ipaiBox.style.height = ipaiBox.h + 'px';
var ipaiIcon = document.createElement('img');
ipaiIcon.id = 'cksle_ipaiIcon';
ipaiIcon.src = safari.extension.baseURI + 'saving.gif';
ipaiBox.appendChild(ipaiIcon);
ipaiBox.appendChild(document.createElement('br'));
var ipaiText = document.createElement('span');
ipaiText.id = 'cksle_ipaiText';
ipaiText.setAttribute('style', '\
position: relative;\
top: 6px;\
font: normal 13px/normal "Helvetica Neue", Helvetica, sans-serif;\
');
ipaiText.textContent = 'Saving...';
ipaiBox.appendChild(ipaiText);
document.documentElement.appendChild(ipaiBox);
}
else if (state == 'saved') {
var ipaiBox = document.getElementById('cksle_ipaiBox');
var ipaiIcon = document.getElementById('cksle_ipaiIcon');
var ipaiText = document.getElementById('cksle_ipaiText');
ipaiIcon.src = safari.extension.baseURI + 'saved.png';
ipaiText.textContent = 'Saved!';
ipaiText.style.color = 'green';
setTimeout(function() {
var ibo = 10;
var iv = setInterval(function() {
ibo -= 2;
ipaiBox.style.opacity = ibo/10 + '';
if (ibo == 0) {
clearInterval(iv);
document.documentElement.removeChild(ipaiBox);
}
}, 50);
}, 1000);
}
else if (state == 'failed') {
var ipaiBox = document.getElementById('cksle_ipaiBox');
document.documentElement.removeChild(ipaiBox);
}
}
function showPrefsBox(coords) {
if (document.getElementById('cksle_prefsBox')) {
document.documentElement.removeChild(document.getElementById('cksle_prefsBox'));
}
var prefsBox = document.createElement('iframe');
prefsBox.width = 480;
prefsBox.height = 446;
prefsBox.left = window.innerWidth/2 - prefsBox.width/2;
prefsBox.top = 32;
if (coords) {
prefsBox.left = coords.x - prefsBox.width/2;
if (prefsBox.left < 20) {
prefsBox.left = 20;
} else if (prefsBox.left + prefsBox.width*1 + 20 > window.innerWidth) {
prefsBox.left = window.innerWidth - prefsBox.width - 20;
}
prefsBox.top = coords.y;
if (prefsBox.top < 20) {
prefsBox.top = 20;
} else if (prefsBox.top + prefsBox.height*1 + 20 > window.innerHeight) {
prefsBox.top = window.innerHeight - prefsBox.height - 20;
}
}
prefsBox.id = 'cksle_prefsBox';
prefsBox.name = 'cksle_prefsBox';
prefsBox.style.position = 'fixed';
prefsBox.style.zIndex = '2147483647';
prefsBox.style.left = prefsBox.left + 'px';
prefsBox.style.top = prefsBox.top + 'px';
prefsBox.style.width = prefsBox.width + 'px';
prefsBox.style.height = prefsBox.height + 'px';
prefsBox.style.opacity = '1';
prefsBox.style.borderStyle = 'none';
prefsBox.style.borderRadius = '5px';
prefsBox.style.boxShadow = '#222 0px 10px 32px';
prefsBox.scrolling = 'no';
prefsBox.src = safari.extension.baseURI + 'siteprefs.html';
document.documentElement.appendChild(prefsBox);
prefsBox.focus();
window.addEventListener('click', function requestClose(e) {
var prefsBox = document.getElementById('cksle_prefsBox');
prefsBox && document.documentElement.removeChild(prefsBox);
window.removeEventListener('click', requestClose, false);
}, false);
}
var settings = {};
var safariVersion = /\bSafari\/(\d+)\b/.exec(navigator.appVersion)[1];
var frameNames = (window.name) ? [window.name] : [];
var showLinkHrefs = false;
var hrefRevealer = null;
var revealWaiter = null;
var siteIsBlacklisted = false;
var selfIsReader = location.protocol == 'safari-reader:';
var winIsTop = (window == window.top);
var bottomOffset = (/^cksfe_fwin/).test(window.name) ? 22 : 0;
var positionOverride = {};
var rewriteGoogleLinks;
const tpoModChars = /[AaSsDdFfJjKkLl:;]/;
const gHostnameRe = /^(encrypted|www)\.google(\.[a-z]+)+$/;
safari.self.addEventListener('message', handleMessage, false);
safari.self.tab.dispatchMessage('passSettings', {
url : window.location.href,
hostname : window.location.hostname
});
if (window.name || /developer.apple.com\/.*\/#documentation/.test(location.href)) {
safari.self.tab.dispatchMessage('whoAreMyFellowFrames?', window.name);
}
window.addEventListener('contextmenu', handleContextMenu, false);
window.addEventListener('mouseover', handleMouseOver, false);
And, siteprefs.js
function initialize() {
var settings = {};
safari.self.addEventListener('message', handleMessage, false);
safari.self.tab.dispatchMessage('passPrefs');
document.addEventListener('keyup', function (e) {
if (e.which == 27) closeMe();
}, false);
document.getElementById('filterArea').addEventListener('keydown', function (e) {
if (e.which == 27) this.blur();
}, false);
}
function closeMe() {
safari.self.tab.dispatchMessage('closePrefsBox');
}
function focusTab(idx) {
var prefTabs = document.querySelectorAll('.prefTab');
for (var i=0; i < prefTabs.length; i++) {
prefTabs[i].className = prefTabs[i].className.replace(' active','');
}
prefTabs[idx].className += ' active';
var prefPanes = document.querySelectorAll('.prefPane');
for (var i=0; i < prefPanes.length; i++) {
prefPanes[i].className = prefPanes[i].className.replace(' active','');
}
prefPanes[idx].className += ' active';
safari.self.tab.dispatchMessage('saveLastPrefPane', idx);
}
function getSetting(cid) {
var control = document.getElementById(cid);
if (control.type === 'checkbox') {
return control.checked;
} else { // control is a <select>
var cval = control.options[control.selectedIndex].value;
return (
cval === '' ? null :
cval === '?' ? undefined :
cval * 1
);
}
}
function handleHotkeyDown(e) {
e.stopPropagation();
switch (e.which) {
case 27: // escape
e.target.blur();
break;
case 8: // backspace
case 13: // enter
case 32: // space
case 37: // left
case 38: // up
case 39: // right
case 40: // down
e.preventDefault();
break;
case 9: // tab
case 16: // shift
case 17: // ctrl
case 18: // option
case 91: // command-left
case 93: // command-right
break;
default:
e.preventDefault();
saveTPOverrideKey(e);
break;
}
}
function handleHotkeyFocus(e) {
setTimeout(function () {
e.target.select();
}, 10);
}
function handleMessage(msg) {
switch (msg.name) {
case 'receivePrefs': {
settings = msg.message;
console.log('settings:', settings);
if (settings.hostname === 'nohost') {
focusTab(0);
document.getElementById('localTab').style.display = 'none';
} else {
var siteLabel = document.getElementById('siteLabel');
siteLabel.textContent = settings.hostname;
focusTab(settings.lastPrefPane);
initLocalForm();
}
initGlobalForm();
initAdvancedForm();
initFilterForm();
document.getElementById('prefPaneBg').style.visibility = 'visible';
break;
}
case 'prepareToClose': {
document.getElementById('filterArea').blur();
closeMe();
break;
}
}
}
function initAdvancedForm() {
var hkInputs = document.querySelectorAll('input.hotkey');
for (var hki, i = 0; i < hkInputs.length; i++) {
hki = hkInputs[i];
hki.value = populateHotkeyInput(hki.id);
hki.onfocus = handleHotkeyFocus;
hki.onmouseup = handleHotkeyFocus;
hki.onkeydown = handleHotkeyDown;
}
}
function initFilterForm() {
document.getElementById('filterArea').value = settings.userBlacklist.join('\n');
}
function initGlobalForm() {
for (var key in settings) {
if (key !== 'hostname' && key !== 'sitePrefs' && key !== 'lastPrefPane' && key !== 'linkBlacklist') {
setControlValue(key, settings[key]);
}
}
}
function initLocalForm() {
for (var key in settings.sitePrefs) {
if (key !== 'hostname') {
setControlValue('site.' + key, settings.sitePrefs[key]);
}
}
}
function populateHotkeyInput(inputId) {
if (!settings[inputId]) return;
var cStr = String.fromCharCode(settings[inputId].keyCode);
if (!/[0-9A-Z]/.test(cStr))
cStr = String.fromCharCode(parseInt(settings[inputId].keyIdentifier.slice(2), 16));
if (cStr === ' ')
cStr = 'Space';
return cStr;
}
function saveTPOverrideKey(e) {
e.target.blur();
var hotkey = {};
var props = ['keyCode','keyIdentifier'];
for (var i = 0; i < props.length; i++)
hotkey[props[i]] = e[props[i]];
var message = {
which : e.target.id,
data : hotkey
};
settings[e.target.id] = hotkey;
safari.self.tab.dispatchMessage('saveTPOverrideKey', message);
e.target.value = populateHotkeyInput(e.target.id);
}
function setControlValue(cid, value) {
try {
var control = document.getElementById(cid);
if (control.type === 'checkbox') {
control.checked = value;
} else { // control is a <select>
var stringValue = (
value === null ? '' :
value === undefined ? '?' :
value + ''
);
for (var i=0; i < control.options.length; i++) {
if (control.options[i].value == stringValue) {
control.selectedIndex = i;
break;
}
}
}
} catch (e) {
// console.log(e);
}
};
function submitGlobalPref() {
var prefs = {};
var prefName = event.target.id;
prefs[prefName] = getSetting(prefName);
console.log('Submitting global pref: ', prefName, prefs[prefName]);
safari.self.tab.dispatchMessage('saveGlobalPrefs', prefs);
}
function submitSitePref() {
var prefs = { hostname: settings.hostname };
var prefName = event.target.id.replace(/^site\./, '');
prefs[prefName] = getSetting(event.target.id);
console.log('Submitting site pref: ', prefName, prefs[prefName]);
safari.self.tab.dispatchMessage('saveSitePrefs', prefs);
}
function submitUrlFilters() {
safari.self.tab.dispatchMessage('saveUrlFilters', document.getElementById('filterArea').value);
}
Are there any clues within this code in how this was accomplished?