NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @namespace VA_i
// @version 8.0.0.20171116
// @grant GM.getValue
// @grant GM.setValue
// @grant GM_getValue
// @grant GM_setValue
// @grant unsafeWindow
// @include /^https?://(?:www|encrypted|ipv[46])\.google\.[^/]+/(?:$|[#?]|search|webhp|imgres)/
// @match https://news.google.com/*
// @match https://cse.google.com/cse/*
// @run-at document-start
// @name Google: Bypass Result Page Redirect
// @name:zh-CN Google:绕过搜索结果网页链接重定向
// @name:zh-TW Google:繞過搜尋結果網頁鏈接重定向
// @description Avoid Google redirect for search result pages.
// @description:zh-CN 令 Google 直接链接至搜索结果网页,无须重定向。
// @description:zh-TW 令 Google 直接鏈接至搜尋結果網頁,無須重定向。
// @license AGPL-3.0-or-later
// ==/UserScript==
var M = (typeof GM !== 'undefined') ? GM : {
getValue: function (name, alt) {
var value = GM_getValue(name, alt);
return { then: function (callback) { callback(value); } };
},
setValue: function (name, value) {
GM_setValue(name, value);
return { then: function (callback) { callback(); } };
}
};
function getOption() {
var opt_noopen = false;
// For example: open https://ipv4.google.com/#x-option:open-inplace
switch (location.hash) {
// Open links in the current tab.
case '#x-option:open-inplace': opt_noopen = true; break;
// Do not ...
case '#x-option:no-open-inplace': opt_noopen = false; break;
default: return M.getValue('opt_noopen', opt_noopen);
}
M.setValue('opt_noopen', opt_noopen);
return { then: function (callback) { callback(opt_noopen); } };
}
function unsafeEval(func, opt) {
let body = 'return (' + func + ').apply(this, arguments)';
unsafeWindow.Function(body).call(unsafeWindow, opt);
}
getOption().then(function run(opt_noopen) {
unsafeEval(function (opt_noopen) {
var debug = false;
var count = 0;
var options = {opt_noopen: opt_noopen};
debug && console.log('Options:', options);
// web pages: url?url=
// custom search engine: url?q=
// malware: interstitial?url=
var re = /\burl\?.*?\b(?:url|q)=(https?\b[^&#]+)/i;
var restore = function (link, url) {
var oldUrl = link.getAttribute('href') || '';
var newUrl = url || oldUrl;
var matches = newUrl.match(re);
if (matches) {
debug && console.log('restoring', link._x_id, newUrl);
link.setAttribute('href', decodeURIComponent(matches[1]));
enhanceLink(link);
} else if (url != null) {
link.setAttribute('href', newUrl);
}
};
var purifyLink = function (a) {
if (/\brwt\(/.test(a.getAttribute('onmousedown'))) {
a.removeAttribute('onmousedown');
}
if (a.parentElement &&
/\bclick\b/.test(a.parentElement.getAttribute('jsaction') || '')) {
a.addEventListener('click', function (e) {
e.stopImmediatePropagation();
e.stopPropagation();
}, true);
}
};
var enhanceLink = function (a) {
purifyLink(a);
a.setAttribute('rel', 'noreferrer');
a.setAttribute('referrerpolicy', 'no-referrer');
if (options.opt_noopen) {
a.setAttribute('target', '_self');
a.addEventListener('click', function (event) {
event.stopImmediatePropagation();
event.stopPropagation();
}, true);
}
};
var fakeLink = document.createElement('a');
var normalizeUrl = function (url) {
fakeLink.href = url;
return fakeLink.href;
};
var setter = function (v) {
v = String(v); // in case an object is passed by clever Google
debug && console.log('State:', document.readyState);
debug && console.log('set', this._x_id, this.getAttribute('href'), v);
restore(this, v);
};
var getter = function () {
debug && console.log('get', this._x_id, this.getAttribute('href'));
return normalizeUrl(this.getAttribute('href'));
};
var blocker = function (event) {
event.stopPropagation();
restore(this);
debug && console.log('block', this._x_id, this.getAttribute('href'));
};
var handler = function (a) {
if (a._x_id) {
restore(a);
return;
}
a._x_id = ++count;
debug && a.setAttribute('x-id', a._x_id);
if (Object.defineProperty) {
debug && console.log('define property', a._x_id);
Object.defineProperty(a, 'href', {get: getter, set: setter});
} else if (a.__defineSetter__) {
debug && console.log('define getter', a._x_id);
a.__defineSetter__('href', setter);
a.__defineGetter__('href', getter);
} else {
debug && console.log('define listener', a._x_id);
a.onmouseenter = a.onmousemove = a.onmouseup = a.onmousedown =
a.ondbclick = a.onclick = a.oncontextmenu = blocker;
}
if (/^_(?:blank|self)$/.test(a.getAttribute('target')) ||
/\brwt\(/.test(a.getAttribute('onmousedown')) ||
/\bmouse/.test(a.getAttribute('jsaction')) ||
/\bclick\b/.test(a.parentElement.getAttribute('jsaction'))) {
enhanceLink(a);
}
restore(a);
};
var checkNewNodes = function (mutations) {
debug && console.log('State:', document.readyState);
if (mutations.target) {
checkAttribute(mutations);
} else {
mutations.forEach && mutations.forEach(checkAttribute);
}
};
var checkAttribute = function (mutation) {
var target = mutation.target;
if (target && target.nodeName.toUpperCase() === 'A') {
if ((mutation.attributeName || mutation.attrName) === 'href') {
debug && console.log('restore attribute', target._x_id, target.getAttribute('href'));
}
handler(target);
} else if (target instanceof Element) {
[].slice.call(target.querySelectorAll('a')).forEach(handler);
}
};
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
if (MutationObserver) {
debug && console.log('MutationObserver: true');
new MutationObserver(checkNewNodes).observe(document.documentElement, {
childList: true,
attributes: true,
attributeFilter: ['href'],
subtree: true
});
} else {
debug && console.log('MutationEvent: true');
document.addEventListener('DOMAttrModified', checkAttribute, false);
document.addEventListener('DOMNodeInserted', checkNewNodes, false);
}
}, opt_noopen);
});