installXHook

install XHook

当前为 2015-12-22 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/15379/96218/installXHook.js

  1. // installXHook - v0.0.1
  2. ////////// Original copyright notice //////////
  3. // XHook - v1.3.3 - https://github.com/jpillora/xhook
  4. // Jaime Pillora <dev@jpillora.com> - MIT Copyright 2015
  5. function installXHook(window) {
  6. 'use strict';
  7. var
  8. AFTER,
  9. BEFORE,
  10. COMMON_EVENTS,
  11. FIRE,
  12. FormData,
  13. NativeFormData,
  14. NativeXMLHttp,
  15. OFF,
  16. ON,
  17. READY_STATE,
  18. UPLOAD_EVENTS,
  19. XMLHTTP,
  20. document,
  21. msie,
  22. xhook;
  23.  
  24. //for compression
  25. document = window.document;
  26. BEFORE = 'before';
  27. AFTER = 'after';
  28. READY_STATE = 'readyState';
  29. ON = 'addEventListener';
  30. OFF = 'removeEventListener';
  31. FIRE = 'dispatchEvent';
  32. XMLHTTP = 'XMLHttpRequest';
  33. FormData = 'FormData';
  34.  
  35. //parse IE version
  36. UPLOAD_EVENTS = ['load', 'loadend', 'loadstart'];
  37. COMMON_EVENTS = ['progress', 'abort', 'error', 'timeout'];
  38.  
  39. msie = parseInt((/msie (\d+)/.exec(navigator.userAgent.toLowerCase()) || [])[1]);
  40. if (isNaN(msie)) {
  41. msie = parseInt((/trident\/.*; rv:(\d+)/.exec(navigator.userAgent.toLowerCase()) || [])[1]);
  42. }
  43.  
  44. //if required, add 'indexOf' method to Array
  45. if (!('indexOf' in Array.prototype)) {
  46. Array.prototype.indexOf = function(item) {
  47. for (var i = 0, l = this.length; i < l; i++) {
  48. if (i in this && this[i] === item) {
  49. return i;
  50. }
  51. }
  52. return -1;
  53. };
  54. }
  55.  
  56. function slice(o, n) {
  57. return Array.prototype.slice.call(o, n);
  58. }
  59.  
  60. function depricatedProp(p) {
  61. return p === 'returnValue' || p === 'totalSize' || p === 'position';
  62. }
  63.  
  64. function mergeObjects(src, dst) {
  65. var k;
  66. for (k in src) {
  67. if (depricatedProp(k)) {
  68. continue;
  69. }
  70. try {
  71. dst[k] = src[k];
  72. } catch (_error) {}
  73. }
  74. return dst;
  75. }
  76.  
  77. //proxy events from one emitter to another
  78. function proxyEvents(events, src, dst) {
  79. var event, i, len;
  80. function p(event) {
  81. return function(e) {
  82. var clone, k, val;
  83. clone = {};
  84. //copies event, with dst emitter inplace of src
  85. for (k in e) {
  86. if (depricatedProp(k)) {
  87. continue;
  88. }
  89. val = e[k];
  90. clone[k] = val === src ? dst : val;
  91. }
  92. //emits out the dst
  93. return dst[FIRE](event, clone);
  94. };
  95. }
  96. //dont proxy manual events
  97. for (i = 0, len = events.length; i < len; i++) {
  98. event = events[i];
  99. if (dst._has(event)) {
  100. src['on' + event] = p(event);
  101. }
  102. }
  103. }
  104.  
  105. //create fake event
  106. function fakeEvent(type) {
  107. var msieEventObject;
  108. if (document.createEventObject != null) {
  109. msieEventObject = document.createEventObject();
  110. msieEventObject.type = type;
  111. return msieEventObject;
  112. } else {
  113. // on some platforms like android 4.1.2 and safari on windows, it appears
  114. // that new Event is not allowed
  115. try {
  116. return new Event(type);
  117. } catch (_error) {
  118. return {
  119. type: type
  120. };
  121. }
  122. }
  123. }
  124.  
  125. //tiny event emitter
  126. function EventEmitter(nodeStyle) {
  127. var emitter, events;
  128. //private
  129. events = {};
  130. function listeners(event) {
  131. return events[event] || [];
  132. }
  133. //public
  134. emitter = {};
  135. emitter[ON] = function(event, callback, i) {
  136. events[event] = listeners(event);
  137. if (events[event].indexOf(callback) >= 0) {
  138. return;
  139. }
  140. if (i === void 0) {
  141. i = events[event].length;
  142. }
  143. events[event].splice(i, 0, callback);
  144. };
  145. emitter[OFF] = function(event, callback) {
  146. var i;
  147. //remove all
  148. if (event === void 0) {
  149. events = {};
  150. return;
  151. }
  152. //remove all of type event
  153. if (callback === void 0) {
  154. events[event] = [];
  155. }
  156. //remove particular handler
  157. i = listeners(event).indexOf(callback);
  158. if (i === -1) {
  159. return;
  160. }
  161. listeners(event).splice(i, 1);
  162. };
  163. emitter[FIRE] = function() {
  164. var args, event, i, legacylistener, listener, _i, _len, _ref;
  165. args = slice(arguments);
  166. event = args.shift();
  167. if (!nodeStyle) {
  168. args[0] = mergeObjects(args[0], fakeEvent(event));
  169. }
  170. legacylistener = emitter['on' + event];
  171. if (legacylistener) {
  172. legacylistener.apply(void 0, args);
  173. }
  174. _ref = listeners(event).concat(listeners('*'));
  175. for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
  176. listener = _ref[i];
  177. listener.apply(void 0, args);
  178. }
  179. };
  180. emitter._has = function(event) {
  181. return !!(events[event] || emitter['on' + event]);
  182. };
  183. //add extra aliases
  184. if (nodeStyle) {
  185. emitter.listeners = function(event) {
  186. return slice(listeners(event));
  187. };
  188. emitter.on = emitter[ON];
  189. emitter.off = emitter[OFF];
  190. emitter.fire = emitter[FIRE];
  191. emitter.once = function(e, fn) {
  192. function fire() {
  193. emitter.off(e, fire);
  194. return fn.apply(null, arguments);
  195. }
  196. return emitter.on(e, fire);
  197. };
  198. emitter.destroy = function() {
  199. events = {};
  200. };
  201. }
  202. return emitter;
  203. }
  204.  
  205. //use event emitter to store hooks
  206. xhook = EventEmitter(true);
  207. xhook.EventEmitter = EventEmitter;
  208. xhook[BEFORE] = function(handler, i) {
  209. if (handler.length < 1 || handler.length > 2) {
  210. throw 'invalid hook';
  211. }
  212. return xhook[ON](BEFORE, handler, i);
  213. };
  214. xhook[AFTER] = function(handler, i) {
  215. if (handler.length < 2 || handler.length > 3) {
  216. throw 'invalid hook';
  217. }
  218. return xhook[ON](AFTER, handler, i);
  219. };
  220. xhook.enable = function() {
  221. window[XMLHTTP] = XHookHttpRequest;
  222. if (NativeFormData) {
  223. window[FormData] = XHookFormData;
  224. }
  225. return xhook;
  226. };
  227. xhook.disable = function() {
  228. window[XMLHTTP] = xhook[XMLHTTP];
  229. if (NativeFormData) {
  230. window[FormData] = NativeFormData;
  231. }
  232. return xhook;
  233. };
  234.  
  235. //helper
  236. function convertHeaders(h, dest) {
  237. var header, headers, k, name, v, value, _i, _len, _ref;
  238. if (dest == null) {
  239. dest = {};
  240. }
  241. switch (typeof h) {
  242. case 'object':
  243. headers = [];
  244. for (k in h) {
  245. v = h[k];
  246. name = k.toLowerCase();
  247. headers.push('' + name + ':\t' + v);
  248. }
  249. return headers.join('\n');
  250. case 'string':
  251. headers = h.split('\n');
  252. for (_i = 0, _len = headers.length; _i < _len; _i++) {
  253. header = headers[_i];
  254. if (/([^:]+):\s*(.+)/.test(header)) {
  255. name = RegExp.$1.toLowerCase();
  256. value = RegExp.$2;
  257. if (dest[name] == null) {
  258. dest[name] = value;
  259. }
  260. }
  261. }
  262. return dest;
  263. }
  264. }
  265. xhook.headers = convertHeaders;
  266.  
  267. //patch FormData
  268. // we can do this safely because all XHR
  269. // is hooked, so we can ensure the real FormData
  270. // object is used on send
  271. NativeFormData = window[FormData];
  272. function XHookFormData(form) {
  273. var entries;
  274. this.fd = form ? new NativeFormData(form) : new NativeFormData();
  275. this.form = form;
  276. entries = [];
  277. Object.defineProperty(this, 'entries', {
  278. get: function() {
  279. var fentries;
  280. //extract form entries
  281. fentries = !form ? [] : slice(form.querySelectorAll('input,select')).filter(function(e) {
  282. var _ref;
  283. return ((_ref = e.type) !== 'checkbox' && _ref !== 'radio') || e.checked;
  284. }).map(function(e) {
  285. return [e.name, e.type === 'file' ? e.files : e.value];
  286. });
  287. //combine with js entries
  288. return fentries.concat(entries);
  289. }
  290. });
  291. this.append = (function(_this) {
  292. return function() {
  293. var args;
  294. args = slice(arguments);
  295. entries.push(args);
  296. return _this.fd.append.apply(_this.fd, args);
  297. };
  298. })(this);
  299. }
  300.  
  301. if (NativeFormData) {
  302. //expose native formdata as xhook.FormData incase its needed
  303. xhook[FormData] = NativeFormData;
  304. }
  305.  
  306. //patch XHR
  307. NativeXMLHttp = window[XMLHTTP];
  308. xhook[XMLHTTP] = NativeXMLHttp;
  309. function XHookHttpRequest() {
  310. var
  311. ABORTED,
  312. currentState,
  313. facade,
  314. hasError,
  315. request,
  316. response,
  317. status,
  318. transiting,
  319. xhr;
  320. ABORTED = -1;
  321. xhr = new xhook[XMLHTTP]();
  322.  
  323. //==========================
  324. // Extra state
  325. request = {};
  326. status = null;
  327. hasError = void 0;
  328. transiting = void 0;
  329. response = void 0;
  330.  
  331. //==========================
  332. // Private API
  333.  
  334. //read results from real xhr into response
  335. function readHead() {
  336. var key, name, val, _ref;
  337. // Accessing attributes on an aborted xhr object will
  338. // throw an 'c00c023f error' in IE9 and lower, don't touch it.
  339. response.status = status || xhr.status;
  340. if (!(status === ABORTED && msie < 10)) {
  341. response.statusText = xhr.statusText;
  342. }
  343. if (status !== ABORTED) {
  344. _ref = convertHeaders(xhr.getAllResponseHeaders());
  345. for (key in _ref) {
  346. val = _ref[key];
  347. if (!response.headers[key]) {
  348. name = key.toLowerCase();
  349. response.headers[name] = val;
  350. }
  351. }
  352. }
  353. }
  354.  
  355. function readBody() {
  356. //https://xhr.spec.whatwg.org/
  357. if (!xhr.responseType || xhr.responseType === 'text') {
  358. response.text = xhr.responseText;
  359. response.data = xhr.responseText;
  360. } else if (xhr.responseType === 'document') {
  361. response.xml = xhr.responseXML;
  362. response.data = xhr.responseXML;
  363. } else {
  364. response.data = xhr.response;
  365. }
  366. //new in some browsers
  367. if ('responseURL' in xhr) {
  368. response.finalUrl = xhr.responseURL;
  369. }
  370. }
  371.  
  372. //write response into facade xhr
  373. function writeHead() {
  374. facade.status = response.status;
  375. facade.statusText = response.statusText;
  376. }
  377.  
  378. function writeBody() {
  379. if ('text' in response) {
  380. facade.responseText = response.text;
  381. }
  382. if ('xml' in response) {
  383. facade.responseXML = response.xml;
  384. }
  385. if ('data' in response) {
  386. facade.response = response.data;
  387. }
  388. if ('finalUrl' in response) {
  389. facade.responseURL = response.finalUrl;
  390. }
  391. }
  392.  
  393. //ensure ready state 0 through 4 is handled
  394. function emitReadyState(n) {
  395. while (n > currentState && currentState < 4) {
  396. facade[READY_STATE] = ++currentState;
  397. // make fake events for libraries that actually check the type on
  398. // the event object
  399. if (currentState === 1) {
  400. facade[FIRE]('loadstart', {});
  401. }
  402. if (currentState === 2) {
  403. writeHead();
  404. }
  405. if (currentState === 4) {
  406. writeHead();
  407. writeBody();
  408. }
  409. facade[FIRE]('readystatechange', {});
  410. //delay final events incase of error
  411. if (currentState === 4) {
  412. setTimeout(emitFinal, 0);
  413. }
  414. }
  415. }
  416.  
  417. function emitFinal() {
  418. if (!hasError) {
  419. facade[FIRE]('load', {});
  420. }
  421. facade[FIRE]('loadend', {});
  422. if (hasError) {
  423. facade[READY_STATE] = 0;
  424. }
  425. }
  426.  
  427. //control facade ready state
  428. currentState = 0;
  429. function setReadyState(n) {
  430. var hooks;
  431. //emit events until readyState reaches 4
  432. if (n !== 4) {
  433. emitReadyState(n);
  434. return;
  435. }
  436. //before emitting 4, run all 'after' hooks in sequence
  437. hooks = xhook.listeners(AFTER);
  438. function process() {
  439. var hook;
  440. if (!hooks.length) {
  441. emitReadyState(4);
  442. return;
  443. }
  444. hook = hooks.shift();
  445. if (hook.length === 2) {
  446. hook(request, response);
  447. process();
  448. } else if (hook.length === 3 && request.async) {
  449. hook(request, response, process);
  450. } else {
  451. process();
  452. }
  453. }
  454. process();
  455. }
  456.  
  457. //==========================
  458. // Facade XHR
  459. facade = request.xhr = EventEmitter();
  460.  
  461. //==========================
  462. // Handle the underlying ready state
  463. xhr.onreadystatechange = function(event) {
  464. //pull status and headers
  465. try {
  466. if (xhr[READY_STATE] === 2) {
  467. readHead();
  468. }
  469. } catch (_error) {}
  470. //pull response data
  471. if (xhr[READY_STATE] === 4) {
  472. transiting = false;
  473. readHead();
  474. readBody();
  475. }
  476.  
  477. setReadyState(xhr[READY_STATE]);
  478. };
  479.  
  480. //mark this xhr as errored
  481. function hasErrorHandler() {
  482. hasError = true;
  483. }
  484. facade[ON]('error', hasErrorHandler);
  485. facade[ON]('timeout', hasErrorHandler);
  486. facade[ON]('abort', hasErrorHandler);
  487. // progress means we're current downloading...
  488. facade[ON]('progress', function() {
  489. //progress events are followed by readystatechange for some reason...
  490. if (currentState < 3) {
  491. setReadyState(3);
  492. } else {
  493. facade[FIRE]('readystatechange', {}); //TODO fake an XHR event
  494. }
  495. });
  496.  
  497. // initialise 'withCredentials' on facade xhr in browsers with it
  498. // or if explicitly told to do so
  499. if ('withCredentials' in xhr || xhook.addWithCredentials) {
  500. facade.withCredentials = false;
  501. }
  502. facade.status = 0;
  503. facade.open = function(method, url, async, user, pass) {
  504. // Initailize empty XHR facade
  505. currentState = 0;
  506. hasError = false;
  507. transiting = false;
  508. request.headers = {};
  509. request.headerNames = {};
  510. request.status = 0;
  511. response = {};
  512. response.headers = {};
  513.  
  514. request.method = method;
  515. request.url = url;
  516. request.async = async !== false;
  517. request.user = user;
  518. request.pass = pass;
  519. // openned facade xhr (not real xhr)
  520. setReadyState(1);
  521. };
  522.  
  523. facade.send = function(body) {
  524. var hooks, k, modk, _i, _len, _ref;
  525. //read xhr settings before hooking
  526. _ref = ['type', 'timeout', 'withCredentials'];
  527. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  528. k = _ref[_i];
  529. modk = k === 'type' ? 'responseType' : k;
  530. if (modk in facade) {
  531. request[k] = facade[modk];
  532. }
  533. }
  534.  
  535. request.body = body;
  536. function send() {
  537. var header, value, _i, _len, _ref;
  538. //proxy all events from real xhr to facade
  539. proxyEvents(COMMON_EVENTS, xhr, facade);
  540. if (facade.upload) {
  541. proxyEvents(COMMON_EVENTS.concat(UPLOAD_EVENTS), xhr.upload, facade.upload);
  542. }
  543.  
  544. //prepare request all at once
  545. transiting = true;
  546. //perform open
  547. xhr.open(request.method, request.url, request.async, request.user, request.pass);
  548.  
  549. //write xhr settings
  550. _ref = ['type', 'timeout', 'withCredentials'];
  551. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  552. k = _ref[_i];
  553. modk = k === 'type' ? 'responseType' : k;
  554. if (k in request) {
  555. xhr[modk] = request[k];
  556. }
  557. }
  558.  
  559. //insert headers
  560. _ref = request.headers;
  561. for (header in _ref) {
  562. value = _ref[header];
  563. xhr.setRequestHeader(header, value);
  564. }
  565. //extract real formdata
  566. if (request.body instanceof XHookFormData) {
  567. request.body = request.body.fd;
  568. }
  569. //real send!
  570. xhr.send(request.body);
  571. }
  572.  
  573. hooks = xhook.listeners(BEFORE);
  574. //process hooks sequentially
  575. function process() {
  576. var hook;
  577. if (!hooks.length) {
  578. return send();
  579. }
  580. //go to next hook OR optionally provide response
  581. function done(userResponse) {
  582. //break chain - provide dummy response (readyState 4)
  583. if (typeof userResponse === 'object' && (typeof userResponse.status === 'number' || typeof response.status === 'number')) {
  584. if (!('data' in userResponse)) {
  585. userResponse.data = userResponse.response || userResponse.text;
  586. }
  587. mergeObjects(userResponse, response);
  588. setReadyState(4);
  589. return;
  590. }
  591. //continue processing until no hooks left
  592. process();
  593. }
  594. //specifically provide headers (readyState 2)
  595. done.head = function(userResponse) {
  596. mergeObjects(userResponse, response);
  597. return setReadyState(2);
  598. };
  599. //specifically provide partial text (responseText readyState 3)
  600. done.progress = function(userResponse) {
  601. mergeObjects(userResponse, response);
  602. return setReadyState(3);
  603. };
  604.  
  605. hook = hooks.shift();
  606. //async or sync?
  607. if (hook.length === 1) {
  608. done(hook(request));
  609. } else if (hook.length === 2 && request.async) {
  610. //async handlers must use an async xhr
  611. hook(request, done);
  612. } else {
  613. //skip async hook on sync requests
  614. done();
  615. }
  616. }
  617. //kick off
  618. process();
  619. };
  620.  
  621. facade.abort = function() {
  622. status = ABORTED;
  623. if (transiting) {
  624. xhr.abort(); //this will emit an 'abort' for us
  625. } else {
  626. facade[FIRE]('abort', {});
  627. }
  628. };
  629. facade.setRequestHeader = function(header, value) {
  630. var lName, name;
  631. //the first header set is used for all future case-alternatives of 'name'
  632. lName = header != null ? header.toLowerCase() : void 0;
  633. name = request.headerNames[lName] = request.headerNames[lName] || header;
  634. //append header to any previous values
  635. if (request.headers[name]) {
  636. value = request.headers[name] + ', ' + value;
  637. }
  638. request.headers[name] = value;
  639. };
  640. facade.getResponseHeader = function(header) {
  641. var name;
  642. name = header != null ? header.toLowerCase() : void 0;
  643. return response.headers[name];
  644. };
  645. facade.getAllResponseHeaders = function() {
  646. return convertHeaders(response.headers);
  647. };
  648.  
  649. //proxy call only when supported
  650. if (xhr.overrideMimeType) {
  651. facade.overrideMimeType = function() {
  652. return xhr.overrideMimeType.apply(xhr, arguments);
  653. };
  654. }
  655.  
  656. //create emitter when supported
  657. if (xhr.upload) {
  658. facade.upload = request.upload = EventEmitter();
  659. }
  660. this._facade = facade;
  661. }
  662. [
  663. 'readyState', 'open', 'setRequestHeader', 'timeout', 'withCredentials',
  664. 'upload', 'send', 'abort', 'status', 'statusText', 'getResponseHeader',
  665. 'getAllResponseHeaders', 'overrideMimeType', 'responseType', 'response',
  666. 'responseText', 'responseXML',
  667. ON, OFF, FIRE,
  668. 'onreadystatechange', 'onloadstart', 'onprogress', 'onabort', 'onerror',
  669. 'onload', 'ontimeout', 'onloadend'
  670. ].forEach(function(k) {
  671. Object.defineProperty(XHookHttpRequest.prototype, k, {
  672. configurable: true,
  673. enumerable: true,
  674. get: function() {
  675. return this._facade[k];
  676. },
  677. set: function(v) {
  678. this._facade[k] = v;
  679. }
  680. });
  681. });
  682.  
  683. return xhook;
  684. }