Guest User

Untitled

a guest
Nov 6th, 2016
142
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.75 KB | None | 0 0
  1. !function(){
  2. // Avoid running repeatedly due to new `document.documentElement`
  3. if (window.VM) return;
  4. window.VM = 1;
  5.  
  6. function getUniqId() {
  7. return Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
  8. }
  9. function sendMessage(data) {
  10. return new Promise(function (resolve, reject) {
  11. chrome.runtime.sendMessage(data, function (res) {
  12. res && res.error ? reject(res.error) : resolve(res && res.data);
  13. });
  14. });
  15. }
  16. function includes(arr, item) {
  17. for (var i = arr.length; i --;) {
  18. if (arr[i] === item) return true;
  19. }
  20. return false;
  21. }
  22. function forEach(arr, func, context) {
  23. var length = arr.length;
  24. for (var i = 0; i < length; i ++) {
  25. if (func.call(context, arr[i], i, arr) === false) break;
  26. }
  27. }
  28.  
  29. /**
  30. * http://www.webtoolkit.info/javascript-utf8.html
  31. */
  32. function utf8decode (utftext) {
  33. var string = "";
  34. var i = 0;
  35. var c = 0, c2 = 0, c3 = 0;
  36. while ( i < utftext.length ) {
  37. c = utftext.charCodeAt(i);
  38. if (c < 128) {
  39. string += String.fromCharCode(c);
  40. i ++;
  41. } else if (c > 191 && c < 224) {
  42. c2 = utftext.charCodeAt(i + 1);
  43. string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
  44. i += 2;
  45. } else {
  46. c2 = utftext.charCodeAt(i + 1);
  47. c3 = utftext.charCodeAt(i + 2);
  48. string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
  49. i += 3;
  50. }
  51. }
  52. return string;
  53. }
  54.  
  55. function getPopup(){
  56. // XXX: only scripts run in top level window are counted
  57. top === window && sendMessage({
  58. cmd: 'SetPopup',
  59. data: {
  60. ids: ids,
  61. menus: menus,
  62. },
  63. });
  64. }
  65.  
  66. var badge = {
  67. number: 0,
  68. ready: false,
  69. willSet: false,
  70. };
  71. function getBadge(){
  72. badge.willSet = true;
  73. setBadge();
  74. }
  75. function setBadge(){
  76. if (badge.ready && badge.willSet) {
  77. // XXX: only scripts run in top level window are counted
  78. top === window && sendMessage({cmd: 'SetBadge', data: badge.number});
  79. }
  80. }
  81.  
  82. /**
  83. * @desc Wrap methods to prevent unexpected modifications.
  84. */
  85. function getWrapper() {
  86. // http://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects
  87. // http://developer.mozilla.org/docs/Web/API/Window
  88. var comm = this;
  89. var wrapper = {};
  90. // `eval` should be called directly so that it is run in current scope
  91. wrapper.eval = eval;
  92. // Wrap methods
  93. comm.forEach([
  94. // 'uneval',
  95. 'isFinite',
  96. 'isNaN',
  97. 'parseFloat',
  98. 'parseInt',
  99. 'decodeURI',
  100. 'decodeURIComponent',
  101. 'encodeURI',
  102. 'encodeURIComponent',
  103.  
  104. 'addEventListener',
  105. 'alert',
  106. 'atob',
  107. 'blur',
  108. 'btoa',
  109. 'clearInterval',
  110. 'clearTimeout',
  111. 'close',
  112. 'confirm',
  113. 'dispatchEvent',
  114. 'fetch',
  115. 'find',
  116. 'focus',
  117. 'getComputedStyle',
  118. 'getSelection',
  119. 'matchMedia',
  120. 'moveBy',
  121. 'moveTo',
  122. 'open',
  123. 'openDialog',
  124. 'postMessage',
  125. 'print',
  126. 'prompt',
  127. 'removeEventListener',
  128. 'requestAnimationFrame',
  129. 'resizeBy',
  130. 'resizeTo',
  131. 'scroll',
  132. 'scrollBy',
  133. 'scrollByLines',
  134. 'scrollByPages',
  135. 'scrollTo',
  136. 'setInterval',
  137. 'setTimeout',
  138. 'stop',
  139. ], function (name) {
  140. var method = window[name];
  141. if (method) wrapper[name] = function () {
  142. return method.apply(window, arguments);
  143. };
  144. });
  145. function defineProtectedProperty(name) {
  146. var modified = false;
  147. var value;
  148. Object.defineProperty(wrapper, name, {
  149. get: function () {
  150. if (!modified) value = window[name];
  151. return value === window ? wrapper : value;
  152. },
  153. set: function (val) {
  154. modified = true;
  155. value = val;
  156. },
  157. });
  158. }
  159. function defineReactedProperty(name) {
  160. Object.defineProperty(wrapper, name, {
  161. get: function () {
  162. var value = window[name];
  163. return value === window ? wrapper : value;
  164. },
  165. set: function (val) {
  166. window[name] = val;
  167. },
  168. });
  169. }
  170. // Wrap properties
  171. comm.forEach(comm.props, function (name) {
  172. if (wrapper[name]) return;
  173. if (name.slice(0, 2) === 'on') defineReactedProperty(name);
  174. else defineProtectedProperty(name);
  175. });
  176. return wrapper;
  177. }
  178. // Communicator
  179. var comm = {
  180. vmid: 'VM_' + getUniqId(),
  181. state: 0,
  182. utf8decode: utf8decode,
  183. getUniqId: getUniqId,
  184. props: Object.getOwnPropertyNames(window),
  185.  
  186. // Array functions
  187. // Notice: avoid using prototype functions since they may be changed by page scripts
  188. includes: includes,
  189. forEach: forEach,
  190.  
  191. init: function(srcId, destId) {
  192. var comm = this;
  193. comm.sid = comm.vmid + srcId;
  194. comm.did = comm.vmid + destId;
  195. document.addEventListener(comm.sid, comm['handle' + srcId].bind(comm), false);
  196. comm.load = comm.checkLoad = function () {};
  197. // check whether the page is injectable via <script>, whether limited by CSP
  198. try {
  199. comm.injectable = (0, eval)('true');
  200. } catch (e) {
  201. comm.injectable = false;
  202. }
  203. },
  204. post: function(data) {
  205. var e = new CustomEvent(this.did, {detail: data});
  206. document.dispatchEvent(e);
  207. },
  208. handleR: function(e) {
  209. var obj = e.detail;
  210. var comm = this;
  211. var maps = {
  212. LoadScript: comm.loadScript.bind(comm),
  213. Command: function (data) {
  214. var func = comm.command[data];
  215. func && func();
  216. },
  217. GotRequestId: function (id) {
  218. comm.qrequests.shift().start(id);
  219. },
  220. HttpRequested: function (r) {
  221. var req = comm.requests[r.id];
  222. if (req) req.callback(r);
  223. },
  224. UpdateValues: function (data) {
  225. var values = comm.values;
  226. if (values && values[data.uri]) values[data.uri] = data.values;
  227. },
  228. // advanced inject
  229. Injected: function (id) {
  230. var obj = comm.ainject[id];
  231. var func = window['VM_' + id];
  232. delete window['VM_' + id];
  233. delete comm.ainject[id];
  234. if (obj && func) comm.runCode(obj[0], func, obj[1]);
  235. },
  236. };
  237. var func = maps[obj.cmd];
  238. if (func) func(obj.data);
  239. },
  240. runCode: function(name, func, wrapper) {
  241. try {
  242. func.call(wrapper.window || wrapper, wrapper);
  243. } catch (e) {
  244. var msg = 'Error running script: ' + name + '\n' + e;
  245. if (e.message) msg += '\n' + e.message;
  246. console.error(msg);
  247. }
  248. },
  249. initRequest: function() {
  250. // request functions
  251. function reqAbort(){
  252. comm.post({cmd: 'AbortRequest', data: this.id});
  253. }
  254. function parseData(req, details) {
  255. if (req.resType) {
  256. // blob or arraybuffer
  257. var data = req.data.response.split(',');
  258. var mimetype = data[0].match(/^data:(.*?);base64$/);
  259. if (!mimetype) {
  260. // invalid
  261. req.data.response = null;
  262. } else {
  263. data = window.atob(data[1]);
  264. var arr = new window.Uint8Array(data.length);
  265. for (var i = 0; i < data.length; i ++) arr[i] = data.charCodeAt(i);
  266. if (details.responseType == 'blob') {
  267. // blob
  268. return new Blob([arr], {type: mimetype});
  269. } else {
  270. // arraybuffer
  271. return arr.buffer;
  272. }
  273. }
  274. } else if (details.responseType == 'json') {
  275. // json
  276. return JSON.parse(req.data.response);
  277. } else {
  278. // text
  279. return req.data.response;
  280. }
  281. }
  282. // request object functions
  283. function callback(req) {
  284. var t = this;
  285. var cb = t.details['on' + req.type];
  286. if (cb) {
  287. if (req.data.response) {
  288. if (!t.data) t.data = [parseData(req, t.details)];
  289. req.data.response = t.data[0];
  290. }
  291. cb(req.data);
  292. }
  293. if (req.type == 'loadend') delete comm.requests[t.id];
  294. }
  295. function start(id) {
  296. var t = this;
  297. var data = {
  298. id: id,
  299. method: t.details.method,
  300. url: t.details.url,
  301. data: t.details.data,
  302. //async: !t.details.synchronous,
  303. user: t.details.user,
  304. password: t.details.password,
  305. headers: t.details.headers,
  306. overrideMimeType: t.details.overrideMimeType,
  307. };
  308. t.id = id;
  309. comm.requests[id] = t;
  310. if (comm.includes(['arraybuffer', 'blob'], t.details.responseType)) {
  311. data.responseType = 'blob';
  312. }
  313. comm.post({cmd: 'HttpRequest', data: data});
  314. }
  315. function getFullUrl(url) {
  316. var a = document.createElement('a');
  317. a.setAttribute('href', url);
  318. return a.href;
  319. }
  320.  
  321. var comm = this;
  322. comm.requests = {};
  323. comm.qrequests = [];
  324. comm.Request = function(details) {
  325. var t = {
  326. details: details,
  327. callback: callback,
  328. start: start,
  329. req: {
  330. abort: reqAbort,
  331. },
  332. };
  333. details.url = getFullUrl(details.url);
  334. comm.qrequests.push(t);
  335. comm.post({cmd:'GetRequestId'});
  336. return t.req;
  337. };
  338. },
  339. getWrapper: getWrapper,
  340. wrapGM: function(script, cache) {
  341. function getValues() {
  342. return comm.values[script.uri];
  343. }
  344. function propertyToString() {
  345. return '[Violentmonkey property]';
  346. }
  347. function addProperty(name, prop, obj) {
  348. if ('value' in prop) prop.writable = false;
  349. prop.configurable = false;
  350. Object.defineProperty(obj, name, prop);
  351. if (typeof obj[name] == 'function') obj[name].toString = propertyToString;
  352. }
  353. function saveValues() {
  354. comm.post({
  355. cmd: 'SetValue',
  356. data: {
  357. uri: script.uri,
  358. values: getValues(),
  359. },
  360. });
  361. }
  362. // Add GM functions
  363. // Reference: http://wiki.greasespot.net/Greasemonkey_Manual:API
  364. var comm = this;
  365. var gm = {};
  366. var grant = script.meta.grant || [];
  367. var urls = {};
  368. if (!grant.length || grant.length == 1 && grant[0] == 'none') {
  369. // @grant none
  370. grant.pop();
  371. } else {
  372. gm['window'] = comm.getWrapper();
  373. }
  374. if (!comm.includes(grant, 'unsafeWindow')) grant.push('unsafeWindow');
  375. if (!comm.includes(grant, 'GM_info')) grant.push('GM_info');
  376. var resources = script.meta.resources || {};
  377. var gm_funcs = {
  378. unsafeWindow: {value: window},
  379. GM_info: {
  380. get: function () {
  381. var m = script.code.match(/\/\/\s+==UserScript==\s+([\s\S]*?)\/\/\s+==\/UserScript==\s/);
  382. var data = {
  383. description: script.meta.description || '',
  384. excludes: script.meta.exclude.concat(),
  385. includes: script.meta.include.concat(),
  386. matches: script.meta.match.concat(),
  387. name: script.meta.name || '',
  388. namespace: script.meta.namespace || '',
  389. resources: {},
  390. 'run-at': script.meta['run-at'] || '',
  391. unwrap: false,
  392. version: script.meta.version || '',
  393. };
  394. var obj = {};
  395. addProperty('scriptMetaStr', {value: m ? m[1] : ''}, obj);
  396.  
  397. // whether update is allowed
  398. addProperty('scriptWillUpdate', {value: !!script.update}, obj);
  399.  
  400. // Violentmonkey specific data
  401. addProperty('version', {value: comm.version}, obj);
  402. addProperty('scriptHandler', {value: 'Violentmonkey'}, obj);
  403.  
  404. // script object
  405. addProperty('script', {value:{}}, obj);
  406. var i;
  407. for (i in data) addProperty(i, {value: data[i]}, obj.script);
  408. for (i in script.meta.resources) {
  409. addProperty(i, {value: script.meta.resources[i]}, obj.script.resources);
  410. }
  411. return obj;
  412. },
  413. },
  414. GM_deleteValue: {
  415. value: function (key) {
  416. var values = getValues();
  417. delete values[key];
  418. saveValues();
  419. },
  420. },
  421. GM_getValue: {
  422. value: function(key, val) {
  423. var values = getValues();
  424. var v = values[key];
  425. if (v) {
  426. var type = v[0];
  427. v = v.slice(1);
  428. switch (type) {
  429. case 'n':
  430. val = Number(v);
  431. break;
  432. case 'b':
  433. val = v == 'true';
  434. break;
  435. case 'o':
  436. try {
  437. val = JSON.parse(v);
  438. } catch (e) {
  439. console.warn(e);
  440. }
  441. break;
  442. default:
  443. val = v;
  444. }
  445. }
  446. return val;
  447. },
  448. },
  449. GM_listValues: {
  450. value: function () {
  451. return Object.getOwnPropertyNames(getValues());
  452. },
  453. },
  454. GM_setValue: {
  455. value: function (key, val) {
  456. var type = (typeof val)[0];
  457. switch (type) {
  458. case 'o':
  459. val = type + JSON.stringify(val);
  460. break;
  461. default:
  462. val = type + val;
  463. }
  464. var values = getValues();
  465. values[key] = val;
  466. saveValues();
  467. },
  468. },
  469. GM_getResourceText: {
  470. value: function (name) {
  471. for (var i in resources) if (name == i) {
  472. var text = cache[resources[i]];
  473. if (text) text = comm.utf8decode(window.atob(text));
  474. return text;
  475. }
  476. },
  477. },
  478. GM_getResourceURL: {
  479. value: function (name) {
  480. for (var k in resources) if (name == k) {
  481. var key = resources[k];
  482. var url = urls[key];
  483. if (!url) {
  484. var cc = cache[key];
  485. if (cc) {
  486. // Binary string is not supported by blob constructor,
  487. // so we have to transform it into array buffer.
  488. var bin = window.atob(cc);
  489. var arr = new window.Uint8Array(bin.length);
  490. for (var i = 0; i < bin.length; i ++) {
  491. arr[i] = bin.charCodeAt(i);
  492. }
  493. var b = new Blob([arr]);
  494. urls[key] = url = URL.createObjectURL(b);
  495. } else {
  496. url = key;
  497. }
  498. }
  499. return url;
  500. }
  501. }
  502. },
  503. GM_addStyle: {
  504. value: function (css) {
  505. comm.post({cmd: 'AddStyle', data: css});
  506. },
  507. },
  508. GM_log: {
  509. /* eslint-disable no-console */
  510. value: function (data) {console.log(data);},
  511. /* eslint-enable no-console */
  512. },
  513. GM_openInTab: {
  514. value: function (url) {
  515. comm.post({cmd: 'NewTab', data: url});
  516. },
  517. },
  518. GM_registerMenuCommand: {
  519. value: function (cap, func, acc) {
  520. comm.command[cap] = func;
  521. comm.post({cmd: 'RegisterMenu', data: [cap, acc]});
  522. },
  523. },
  524. GM_xmlhttpRequest: {
  525. value: function (details) {
  526. if (!comm.Request) comm.initRequest();
  527. return comm.Request(details);
  528. },
  529. },
  530. };
  531. comm.forEach(grant, function (name) {
  532. var prop = gm_funcs[name];
  533. prop && addProperty(name, prop, gm);
  534. });
  535. return gm;
  536. },
  537. loadScript: function (data) {
  538. function buildCode(script) {
  539. var require = script.meta.require || [];
  540. var wrapper = comm.wrapGM(script, data.cache);
  541. var vars = [];
  542. comm.forEach(Object.getOwnPropertyNames(wrapper), function(name) {
  543. vars.push(name + '=this["' + name + '"]=g["' + name + '"]');
  544. });
  545. // vars should not be empty
  546. var code = ['var ' + vars.join(',') + ';delete g;with(this)!function(){'];
  547. comm.forEach(require, function (key) {
  548. var script = data.require[key];
  549. script && code.push(script);
  550. });
  551. // wrap code to make 'use strict' work
  552. code.push('!function(){' + script.code + '\n}.call(this)');
  553. code.push('}.call(this);');
  554. code = code.join('\n');
  555. var name = script.custom.name || script.meta.name || script.id;
  556. if (comm.injectable) {
  557. // normal injection
  558. try {
  559. var func = new Function('g', code);
  560. } catch (e) {
  561. console.error('Syntax error in script: ' + name + '\n' + e.message);
  562. return;
  563. }
  564. comm.runCode(name, func, wrapper);
  565. } else {
  566. // advanced injection
  567. var id = comm.getUniqId();
  568. comm.ainject[id] = [name, wrapper];
  569. comm.post({cmd: 'Inject', data: [id, code]});
  570. }
  571. }
  572. function run(list) {
  573. while (list.length) buildCode(list.shift());
  574. }
  575. var comm = this;
  576. var start = [];
  577. var idle = [];
  578. var end = [];
  579. comm.command = {};
  580. comm.ainject = {};
  581. comm.version = data.version;
  582. comm.values = {};
  583. // reset load and checkLoad
  584. comm.load = function() {
  585. run(end);
  586. setTimeout(function() {
  587. run(idle);
  588. }, 0);
  589. };
  590. comm.checkLoad = function() {
  591. if (!comm.state && comm.includes(['interactive', 'complete'], document.readyState))
  592. comm.state = 1;
  593. if (comm.state) comm.load();
  594. };
  595. var listMap = {
  596. 'document-start': start,
  597. 'document-idle': idle,
  598. 'document-end': end,
  599. };
  600. comm.forEach(data.scripts, function(script) {
  601. comm.values[script.uri] = data.values[script.uri] || {};
  602. if (script && script.enabled) {
  603. var list = listMap[script.custom['run-at'] || script.meta['run-at']] || end;
  604. list.push(script);
  605. }
  606. });
  607. run(start);
  608. comm.checkLoad();
  609. },
  610. };
  611.  
  612. var menus = [];
  613. var ids = [];
  614. function injectScript(data) {
  615. // data: [id, code]
  616. var func = function(id, did, cb) {
  617. Object.defineProperty(window, 'VM_' + id, {
  618. value: cb,
  619. configurable: true,
  620. });
  621. var e = new CustomEvent(did, {detail: {cmd: 'Injected', data: id}});
  622. document.dispatchEvent(e);
  623. };
  624. inject('!' + func.toString() + '(' + JSON.stringify(data[0]) + ',' + JSON.stringify(comm.did) + ',function(g){' + data[1] + '})');
  625. }
  626. function handleC(e) {
  627. var req = e.detail;
  628. if (!req) {
  629. console.error('[Violentmonkey] Invalid data! There might be unsupported data format.');
  630. return;
  631. }
  632. var maps = {
  633. GetRequestId: getRequestId,
  634. HttpRequest: httpRequest,
  635. AbortRequest: abortRequest,
  636. Inject: injectScript,
  637. NewTab: function (url) {
  638. window.open(url);
  639. },
  640. SetValue: function (data) {
  641. sendMessage({cmd: 'SetValue', data: data});
  642. },
  643. RegisterMenu: function (data) {
  644. if (window.top === window) menus.push(data);
  645. getPopup();
  646. },
  647. AddStyle: function (css) {
  648. if (document.head) {
  649. var style = document.createElement('style');
  650. style.innerHTML = css;
  651. document.head.appendChild(style);
  652. }
  653. },
  654. };
  655. var func = maps[req.cmd];
  656. if (func) func(req.data);
  657. }
  658.  
  659. // Messages
  660. chrome.runtime.onMessage.addListener(function (req, src) {
  661. var maps = {
  662. Command: function (data) {
  663. comm.post({cmd: 'Command', data: data});
  664. },
  665. GetPopup: getPopup,
  666. GetBadge: getBadge,
  667. HttpRequested: httpRequested,
  668. UpdateValues: function (data) {
  669. comm.post({cmd: 'UpdateValues', data: data});
  670. },
  671. };
  672. var func = maps[req.cmd];
  673. if (func) func(req.data, src);
  674. });
  675.  
  676. // Requests
  677. var requests = {};
  678. function getRequestId() {
  679. sendMessage({cmd: 'GetRequestId'}).then(function (id) {
  680. requests[id] = 1;
  681. comm.post({cmd: 'GotRequestId', data: id});
  682. });
  683. }
  684. function httpRequest(details) {
  685. sendMessage({cmd: 'HttpRequest', data: details});
  686. }
  687. function httpRequested(data) {
  688. if (requests[data.id]) {
  689. if (data.type == 'loadend') delete requests[data.id];
  690. comm.post({cmd: 'HttpRequested', data: data});
  691. }
  692. }
  693. function abortRequest(id) {
  694. sendMessage({cmd: 'AbortRequest', data: id});
  695. }
  696.  
  697. function objEncode(obj) {
  698. var list = [];
  699. for (var i in obj) {
  700. if (!obj.hasOwnProperty(i)) continue;
  701. if (typeof obj[i] == 'function') {
  702. list.push(i + ':' + obj[i].toString());
  703. } else {
  704. list.push(i + ':' + JSON.stringify(obj[i]));
  705. }
  706. }
  707. return '{' + list.join(',') + '}';
  708. }
  709. function inject(code) {
  710. var script = document.createElement('script')
  711. var doc = document.body || document.documentElement;
  712. script.innerHTML = code;
  713. doc.appendChild(script);
  714. doc.removeChild(script);
  715. }
  716. function loadScript(data) {
  717. data.scripts.forEach(function(script) {
  718. ids.push(script.id);
  719. if (script.enabled) badge.number ++;
  720. });
  721. comm.post({cmd: 'LoadScript', data: data});
  722. badge.ready = true;
  723. getPopup();
  724. setBadge();
  725. }
  726. function initCommunicator() {
  727. var C = 'C';
  728. var R = 'R';
  729. inject(
  730. '!function(c,R,C){c.init(R,C);document.addEventListener("DOMContentLoaded",function(e){c.state=1;c.load();},false);c.checkLoad();}(' +
  731. objEncode(comm) + ',"' + R + '","' + C + '")'
  732. );
  733. comm.handleC = handleC;
  734. comm.init(C, R);
  735. sendMessage({cmd: 'GetInjected', data: location.href}).then(loadScript);
  736. }
  737. initCommunicator();
  738. }();
Add Comment
Please, Sign In to add comment