injected.js 23 KB

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