如果您只想监视响应而不进行任何交互,则可以将 XHR 构造函数替换为包装函数,并为每个新创建的实例添加一个事件侦听器。
// ==UserScript==
// @name ajaxobserver
// @namespace http://example.com
// @description observes ajax responses
// @include http://project-gc.com/Statistics/TopFavWilson
// @include http://project-gc.com/Statistics/TopFavWilson/*
// @version 1
// @grant none
// @run-at document-start
// ==/UserScript==
// scope encapsulation in newer GM versions not necessary, nevertheless...
(function()
{
// save constructor
let XMLHttpRequest = window.XMLHttpRequest;
// replace constructor function
window.XMLHttpRequest = function()
{
// new instance
let obj = new XMLHttpRequest();
// register a listener
obj.addEventListener('load', function (event)
{
console.log('EVENT LISTENER: ajax load: responseText =', event.target.responseText);
});
//return the created instance instead of `this`
return obj;
};
})();
如果您还想操作结果,您将需要一个代理,或者您已经手动重建整个 XHR 对象作为包装器。这是一个代理示例:
// ==UserScript==
// @name ajaxmanipulator
// @namespace http://example.com
// @description observes & manipulate ajax responses
// @include http://project-gc.com/Statistics/TopFavWilson
// @include http://project-gc.com/Statistics/TopFavWilson/*
// @version 1
// @grant none
// @run-at document-start
// ==/UserScript==
(function()
{
let
// a function call handler for onevent functions
applyEventHandler =
{
apply: function(targetFunc, thisArg, [event])
{
if
(
'readystatechange' === event.type && 4 === event.target.readyState && 200 === event.target.status
||
'load' === event.type
)
console.log('EVENT', event.type + ':', event.target.responseText);
return targetFunc.call(thisArg, event);
}
},
// a function call handler for onevent functions
applyOpenHandler =
{ // destructuring arguments array into individual named arguments
apply: function(targetFunc, thisArg, [method, url, async, user, password])
{
console.log('open handler:', 'target =', targetFunc, ', thisArg =', thisArg );
console.log
( 'XMLHttpRequest.open\n',
'method:' , method,
'url:' , url,
'async:' , async,
'user:' , user,
'password:', password
);
// let's manipulate some argument
url += '?get-parameter-added-by-proxy';
// finally call the trapped function in context of thisArg passing our manipulated arguments
return targetFunc.call(thisArg, method, url, async, user, password);
}
},
// a property handler for XMLHttpRequest instances
xmlHttpReq_PropertiesHandler =
{
// target : the proxied object (native XMLHttpRequest instance)
// property: name of the property
// value : the new value to assign to the property (only in setter trap)
// receiver: the Proxy instance
get:
function (target, property /*, receiver*/)
{
console.log('%cget handler: ', 'color:green', property);
switch (property)
{
case 'responseText':
// let's return a manipulated string when the property `responseText` is read
return '<div style="color:red;border:1px solid red">response manipulated by proxy'
+ target.responseText + '</div>';
case 'open':
// All we can detect here is a get access to the *property*, which
// usually returns a function object. The apply trap does not work at this
// point. Only a *proxied function* can be trapped by the apply trap.
// Thus we return a proxy of the open function using the apply trap.
// (A simple wrapper function would do the trick as well, but could be easier
// detected by the site's script.)
// We use bind to set the this-context to the native `XMLHttpRequest` instance.
// It will be passed as `thisArg` to the trap handler.
return new Proxy(target.open, applyOpenHandler).bind(target);
default:
return 'function' === typeof target[property]
// function returned by proxy must be executed in slave context (target)
? target[property].bind(target)
// non-function properties are just returned
: target[property]
;
}
},
set:
function (target, property, value, receiver)
{
try
{
console.log('%cset handler: ', 'color:orange', property, '=', value);
switch (property)
{
// Event handlers assigned to the proxy must be stored into the proxied object (slave),
// so that its prototype can access them (in slave context). Such an access is not trapped by
// the proxy's get trap. We need to store proxied functions to get ready to observe invokations.
// Old ajax style was checking the readystate,
// newer scripts use the onload event handler. Both can still be found.
case 'onreadystatechange':
case 'onload':
// only create proxy if `value` is a function
target[property] = 'function' === typeof value
? new Proxy(value, applyEventHandler).bind(receiver)
: value
;
break;
default:
target[property] = value;
}
return true; // success
}
catch (e)
{
console.error(e);
return false; // indicate setter error
}
}
},
oldXMLHttpRequest = window.XMLHttpRequest
; // end of local variable declaration
window.XMLHttpRequest = function(...argv)
{
return new Proxy(new oldXMLHttpRequest(...argv), xmlHttpReq_PropertiesHandler);
}
})();
请注意,此直接访问仅在您不授予任何特权 GM_functions 时才有效。当您被封闭到沙箱中时,您将不得不将函数作为 sting 注入站点的范围,例如 via setTimeout
。