您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Detect url change on Chrome/Firefox (Greasemonkey, Firemonkey, Violentmonkey, Tampermonkey)
当前为
// ==UserScript== // @name Detect-Nav // @description Detect url change on Chrome/Firefox (Greasemonkey, Firemonkey, Violentmonkey, Tampermonkey) // @author GTK // @namespace https://greasyfork.org/en/users/1352961-gtk // @version 0.1 // @match *://*/* // @grant none // @run-at document-start // @noframes // ==/UserScript== // ============================================================ // `GMCompat` Compatibility shim // adapted from `https://github.com/chocolateboy/gm-compat` // ============================================================ const $unsafeWindow = (typeof unsafeWindow !== 'undefined') ? unsafeWindow.wrappedJSObject || unsafeWindow : window; const GMCompat = Object.freeze({ unsafeWindow: $unsafeWindow, CLONE_INTO_OPTIONS: { cloneFunctions: true, target: $unsafeWindow, wrapReflectors: true }, EXPORT_FUNCTION_OPTIONS: { target: $unsafeWindow, }, apply: function($this, fn, _args) { const args = [].slice.call(_args) return fn.apply($this, this.cloneInto(args)) }, call: function($this, fn, ..._args) { const args = this.cloneInto(_args) return fn.call($this, ...args) }, cloneInto: function(object, _options) { const options = Object.assign({}, this.CLONE_INTO_OPTIONS, _options) const _cloneInto = (typeof cloneInto === 'function') ? cloneInto : object => object return _cloneInto(object, options.target, options) }, export: function(value, options) { return (typeof value === 'function') ? this.exportFunction(value, options) : this.cloneInto(value, options) }, exportFunction: function(fn, _options) { const options = Object.assign({}, this.EXPORT_FUNCTION_OPTIONS, _options) const _exportFunction = (typeof exportFunction === 'function') ? exportFunction : (fn, { defineAs, target = $unsafeWindow } = {}) => { return defineAs ? (target[defineAs] = fn) : fn } return _exportFunction(fn, options.target, options) }, unwrap: function(value) { return value ? (value.wrappedJSObject || value) : value } }); // ============================================================ // Navigation detection & CustomEvent dispatch // ============================================================ if (window.navigation) { console.info('Using Navigation API.'); window.navigation.addEventListener('navigatesuccess', e => { notify(e.type, e.currentTarget.currentEntry.url); }); } else if (window.onurlchange === null) { console.info('Using window.onurlchange.'); window.addEventListener('urlchange', e => { notify('urlchange', e.url); }); } else { console.info('Using patch.'); (window.location.hostname === 'www.youtube.com' ? handleYoutube : patchHistory)(); } let oldUrl; function notify(method, url){ const absUrl = new URL(url || window.location.href, window.location.origin).href; const detail = GMCompat.export({ method: method, oldUrl: oldUrl, newUrl: absUrl }); const event = new CustomEvent('detectnavigate', { bubbles: true, detail: detail }); document.dispatchEvent(event); oldUrl = absUrl; } function patchHistory(){ ['pushState', 'replaceState'].forEach(method => { const original = GMCompat.unsafeWindow.history[method]; const patched = function(){ GMCompat.apply(this, original, arguments); notify(method, arguments[2]); }; GMCompat.unsafeWindow.history[method] = GMCompat.export(patched); }); window.addEventListener('popstate', e => { notify(e.type); }); } function handleYoutube(){ window.addEventListener('yt-navigate-finish', e => { notify(e.type, e.detail.response.url); }); }