background.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. var db,port=null,pos=0;
  2. function initDb(callback) {
  3. var request=indexedDB.open('Violentmonkey',1);
  4. request.onsuccess=function(e){db=request.result;if(callback) callback();};
  5. request.onerror=function(e){console.log('IndexedDB error: '+e.target.webkitErrorMessage);};
  6. request.onupgradeneeded=function(e){
  7. var r=e.currentTarget.result,o,i;
  8. // settings: key value
  9. o=r.createObjectStore('settings',{keyPath:'key'});
  10. [
  11. {key:'showDetails',value:false},
  12. {key:'lastUpdate',value:0},
  13. {key:'maxPosition',value:0},
  14. {key:'withData',value:true},
  15. {key:'showButton',value:true},
  16. {key:'isApplied',value:true},
  17. {key:'autoUpdate',value:true},
  18. {key:'search',value:_('defaultSearch')},
  19. ].forEach(function(i){o.add(i);});
  20. // scripts: id uri custom meta enabled update code position
  21. o=r.createObjectStore('scripts',{keyPath:'id',autoIncrement:true});
  22. o.createIndex('uri','uri',{unique:true});
  23. o.createIndex('update','update',{unique:false});
  24. o.createIndex('position','position',{unique:false}); // should be unique at last
  25. // require: uri code
  26. o=r.createObjectStore('require',{keyPath:'uri'});
  27. // cache: uri data
  28. o=r.createObjectStore('cache',{keyPath:'uri'});
  29. // values: uri values
  30. o=r.createObjectStore('values',{keyPath:'uri'});
  31. };
  32. }
  33. function newPosition(){
  34. setOption({key:'maxPosition',value:++pos});
  35. return pos;
  36. }
  37. function getNameURI(i) {
  38. var ns=i.meta.namespace||'',n=i.meta.name||'',k=escape(ns)+':'+escape(n)+':';
  39. if(!ns&&!n) k+=i.id;
  40. return k;
  41. }
  42. function getMeta(j){return {id:j.id,custom:j.custom,meta:j.meta,enabled:j.enabled,update:j.update};}
  43. function parseMeta(d){
  44. var o=-1,meta={include:[],exclude:[],match:[],require:[],resources:{}};
  45. meta.resource=[];
  46. d.replace(/(?:^|\n)\/\/\s*([@=]\S+)(.*)/g,function(m,k,v){
  47. if(o<0&&k=='==UserScript==') o=1;
  48. else if(k=='==/UserScript==') o=0;
  49. if(o==1&&k[0]=='@') k=k.slice(1); else return;
  50. v=v.replace(/^\s+|\s+$/g,'');
  51. if(meta[k]&&meta[k].push) meta[k].push(v); // multiple values allowed
  52. else if(!(k in meta)) meta[k]=v; // only first value will be stored
  53. });
  54. meta.resource.forEach(function(i){
  55. o=i.match(/^(\w+)\s+(.*)/);
  56. if(o) meta.resources[o[1]]=o[2];
  57. });
  58. delete meta.resource;
  59. return meta;
  60. }
  61. function newScript() {
  62. var r={
  63. custom: {},
  64. enabled: 1,
  65. update: 1,
  66. code: '// ==UserScript==\n// @name New Script\n// ==/UserScript==\n'
  67. };
  68. r.meta=parseMeta(r.code);
  69. return r;
  70. }
  71. function removeScript(i,src,callback) {
  72. var o=db.transaction('scripts','readwrite').objectStore('scripts');
  73. o.delete(i);
  74. if(callback) callback();
  75. }
  76. function saveScript(i,src,callback) {
  77. var o=db.transaction('scripts','readwrite').objectStore('scripts');
  78. i.enabled=i.enabled?1:0;
  79. i.update=i.update?1:0;
  80. if(!i.position) i.position=newPosition();
  81. if(callback) callback();
  82. return o.put(i);
  83. }
  84. function vacuum(o,src,callback) {
  85. var cc={},rq={},vl={},w=0;
  86. function vacuumPosition(){
  87. var o=db.transaction('scripts','readwrite').objectStore('scripts'),p=0;
  88. o.index('position').openCursor().onsuccess=function(e){
  89. var r=e.target.result,v,i;
  90. if(r) {
  91. v=r.value;p++;
  92. if(v.position!=p) {v.position=p;o.put(v);}
  93. v.meta.require.forEach(function(i){rq[i]=1;});
  94. for(i in v.meta.resources) cc[i]=1;
  95. if(v.meta.icon) cc[v.meta.icon]=1;vl[v.uri]=1;
  96. r.continue();
  97. } else {
  98. vacuumDB('require',rq);
  99. vacuumDB('cache',cc);
  100. vacuumDB('values',vl);
  101. }
  102. };
  103. }
  104. function vacuumDB(dbName,dic){
  105. w++;
  106. var o=db.transaction(dbName,'readwrite').objectStore(dbName);
  107. o.openCursor().onsuccess=function(e){
  108. var r=e.target.result,v;
  109. if(r) {
  110. v=r.value;
  111. if(!dic[v.uri]) o.delete(v.uri);
  112. r.continue();
  113. } else finish();
  114. };
  115. }
  116. function finish(){
  117. if(!--w) chrome.tabs.sendMessage(src.tab.id,{cmd:'Vacuumed'});
  118. }
  119. vacuumPosition();
  120. if(callback) callback();
  121. }
  122. function move(data,src,callback){
  123. var o=db.transaction('scripts','readwrite').objectStore('scripts');
  124. o.get(data.id).onsuccess=function(e){
  125. var r=e.target.result,k,s,x=r.position;
  126. if(data.offset<0) {
  127. k=IDBKeyRange.upperBound(x,true);
  128. s='prev';
  129. data.offset=-data.offset;
  130. } else {
  131. k=IDBKeyRange.lowerBound(x,true);
  132. s='next';
  133. }
  134. o.index('position').openCursor(k,s).onsuccess=function(e){
  135. var p=e.target.result,v,y;
  136. if(p) {
  137. data.offset--;
  138. v=p.value;y=v.position;
  139. v.position=x;o.put(v);x=y;
  140. if(data.offset) p.continue();
  141. else {r.position=x;o.put(r);}
  142. }
  143. };
  144. };
  145. if(callback) callback();
  146. }
  147. function str2RE(s){return s.replace(/(\.|\?|\/)/g,'\\$1').replace(/\*/g,'.*?');}
  148. function autoReg(s, w) {
  149. if(!w&&s[0]=='/'&&s.slice(-1)=='/') return RegExp(s.slice(1,-1));
  150. return RegExp('^'+str2RE(s)+'$');
  151. }
  152. var match_reg=/(.*?):\/\/([^\/]*)\/(.*)/;
  153. function matchTest(s,u) {
  154. var m=s.match(match_reg);
  155. if(!m) return false;
  156. if(m[1]=='*') {
  157. if(u[1]!='http'&&u[1]!='https') return false;
  158. } else if(m[1]!=u[1]) return false;
  159. if(m[2]!='*') {
  160. if(m[2].slice(0,2)=='*.') {
  161. if(u[2]!=m[2].slice(2)&&u[2].slice(1-m[2].length)!=m[2].slice(1)) return false;
  162. } else if(m[2]!=u[2]) return false;
  163. }
  164. if(!autoReg(m[3],1).test(u[3])) return false;
  165. return true;
  166. }
  167. function testURL(url,e) {
  168. var f=true,i,inc=[],exc=[],mat=[],u=url.match(match_reg);
  169. if(e.custom._match!=false&&e.meta.match) mat=mat.concat(e.meta.match);
  170. if(e.custom.match) mat=mat.concat(e.custom.match);
  171. if(e.custom._include!=false&&e.meta.include) inc=inc.concat(e.meta.include);
  172. if(e.custom.include) inc=inc.concat(e.custom.include);
  173. if(e.custom._exclude!=false&&e.meta.exclude) exc=exc.concat(e.meta.exclude);
  174. if(e.custom.exclude) exc=exc.concat(e.custom.exclude);
  175. if(mat.length) {
  176. for(i=0;i<mat.length;i++) if(f=matchTest(mat[i],u)) break;
  177. } else for(i=0;i<inc.length;i++) if(f=autoReg(inc[i]).test(url)) break;
  178. if(f) for(i=0;i<exc.length;i++) if(!(f=!autoReg(exc[i]).test(url))) break;
  179. return f;
  180. }
  181. function getScript(id,src,callback) { // for user edit
  182. var o=db.transaction('scripts').objectStore('scripts');
  183. o.get(id).onsuccess=function(e){
  184. var r=e.target.result,v;
  185. if(r) {
  186. v=getMeta(r);
  187. v.code=r.code;
  188. if(callback) callback(v);
  189. }
  190. };
  191. }
  192. function getMetas(ids,src,callback) { // for popup menu
  193. var o=db.transaction('scripts').objectStore('scripts'),data=[],id;
  194. function getOne(){
  195. var id=ids.shift();
  196. if(id) o.get(id).onsuccess=function(e){
  197. var r=e.target.result;
  198. if(r) data.push(getMeta(r));
  199. getOne();
  200. }; else callback(data);
  201. }
  202. getOne();
  203. }
  204. function findScript(o,src,callback) { // for injected
  205. function getSettings(){
  206. var o=db.transaction('settings').objectStore('settings');
  207. o.get('isApplied').onsuccess=function(e){
  208. var r=e.target.result;
  209. if(data.isApplied=r.value) getScripts();
  210. else finish();
  211. };
  212. }
  213. function getScripts(){
  214. function addCache(i,c,d){
  215. if(!(i in d)) {c.push(i);d[i]=null;}
  216. }
  217. var o=db.transaction('scripts').objectStore('scripts'),n=0;
  218. o.index('position').openCursor().onsuccess=function(e){
  219. var i,r=e.target.result,v;
  220. if(r) {
  221. v=r.value;
  222. if(testURL(src.tab.url,v)) {
  223. data.scripts.push(v);if(v.enabled) n++;
  224. values.push(v.uri);
  225. v.meta.require.forEach(function(i){addCache(i,require,data.require);});
  226. for(i in v.meta.resources) addCache(v.meta.resources[i],cache,data.cache);
  227. }
  228. r.continue();
  229. } else {
  230. if(n) {
  231. chrome.browserAction.setBadgeBackgroundColor({color:'#808',tabId:src.tab.id});
  232. chrome.browserAction.setBadgeText({text:n.toString(),tabId:src.tab.id});
  233. }
  234. getRequire();
  235. }
  236. };
  237. }
  238. function getRequire(){
  239. function loop(){
  240. var i=require.pop();
  241. if(i) o.get(i).onsuccess=function(e){
  242. var r=e.target.result;
  243. if(r) data.require[i]=r.code;
  244. loop();
  245. }; else getCache();
  246. }
  247. var o=db.transaction('require').objectStore('require');
  248. loop();
  249. }
  250. function getCache(){
  251. function loop(){
  252. var i=cache.pop();
  253. if(i) o.get(i).onsuccess=function(e){
  254. var r=e.target.result;
  255. if(r) data.cache[i]=new Int8Array(r.data);
  256. loop();
  257. }; else getValues();
  258. }
  259. var o=db.transaction('cache').objectStore('cache');
  260. loop();
  261. }
  262. function getValues(){
  263. function loop(){
  264. var i=values.pop();
  265. if(i) o.get(i).onsuccess=function(e){
  266. var r=e.target.result;
  267. if(r) data.values[i]=r.values;
  268. loop();
  269. }; else finish();
  270. }
  271. var o=db.transaction('values').objectStore('values');
  272. loop();
  273. }
  274. function finish(){callback(data);}
  275. var data={isApplied:true,scripts:[],require:{},cache:{},values:{}},cache=[],values=[],require=[];
  276. getSettings();
  277. }
  278. function fetchURL(url, cb, type) {
  279. var req = new XMLHttpRequest();
  280. req.open('GET', url, true);
  281. if (type) req.responseType = type;
  282. if (cb) req.onloadend = cb;
  283. req.send();
  284. }
  285. function fetchCache(url) {
  286. fetchURL(url, function() {
  287. if (this.status!=200) return;
  288. var o=db.transaction('cache','readwrite').objectStore('cache');
  289. o.put({uri:url,data:this.response});
  290. }, 'arraybuffer');
  291. }
  292. function fetchRequire(url) {
  293. fetchURL(url, function() {
  294. if (this.status!=200) return;
  295. var o=db.transaction('require','readwrite').objectStore('require');
  296. o.put({uri:url,code:this.responseText});
  297. });
  298. }
  299. function updateItem(r){
  300. if(port) try{
  301. port.postMessage(r);
  302. }catch(e){
  303. port=null;
  304. console.log(e);
  305. }
  306. }
  307. function queryScript(id,meta,callback){
  308. var o=db.transaction('scripts').objectStore('scripts');
  309. function queryMeta() {
  310. var uri=getNameURI({id:'',meta:meta});
  311. if(uri!='::') o.index('uri').get(uri).onsuccess=function(e){
  312. var r=e.target.result;
  313. if(r) callback(r); else callback(newScript());
  314. }; else callback(newScript());
  315. }
  316. function queryId() {
  317. if(id) o.get(id).onsuccess=function(e){
  318. var r=e.target.result;
  319. if(r) callback(r); else queryMeta();
  320. }; else queryMeta();
  321. }
  322. queryId();
  323. }
  324. function parseScript(o,src,callback) {
  325. var i,r={status:0,message:'message' in o?o.message:_('msgUpdated')};
  326. function finish(){
  327. if(src) chrome.tabs.sendMessage(src.tab.id,{cmd:'ShowMessage',data:r});
  328. updateItem(r);
  329. }
  330. if(o.status&&o.status!=200||o.code=='') { // net error
  331. r.status=-1;r.message=_('msgErrorFetchingScript');finish();
  332. } else { // store script
  333. var meta=parseMeta(o.code);
  334. queryScript(o.id,meta,function(c){
  335. if(!c.id){r.status=1;r.message=_('msgInstalled');}
  336. if(o.more) for(i in o.more) c[i]=o.more[i]; // for import and user edit
  337. c.meta=meta;c.code=o.code;r.obj=getMeta(c);c.uri=getNameURI(c);
  338. if(o.from&&!c.meta.homepage&&!c.custom.homepage&&!/^(file|data):/.test(o.from)) c.custom.homepage=o.from;
  339. if(o.url&&!c.meta.downloadURL&&!c.custom.downloadURL) c.custom.downloadURL=o.url;
  340. saveScript(c,src).onsuccess=function(e){
  341. r.id=c.id=e.target.result;finish();
  342. };
  343. });
  344. meta.require.forEach(fetchRequire); // @require
  345. for(d in meta.resources) fetchCache(meta.resources[d]); // @resource
  346. if(meta.icon) fetchCache(meta.icon); // @icon
  347. }
  348. if(callback) callback();
  349. }
  350. function canUpdate(o,n){
  351. o=(o||'').split('.');
  352. n=(n||'').split('.');
  353. var r=/(\d*)([a-z]*)(\d*)([a-z]*)/i;
  354. while(o.length&&n.length){
  355. var vo=o.shift().match(r),vn=n.shift().match(r);
  356. vo.shift();vn.shift();
  357. vo[0]=parseInt(vo[0]||0,10);
  358. vo[2]=parseInt(vo[2]||0,10);
  359. vn[0]=parseInt(vn[0]||0,10);
  360. vn[2]=parseInt(vn[2]||0,10);
  361. while(vo.length&&vn.length){
  362. var eo=vo.shift(),en=vn.shift();
  363. if(eo!=en) return eo<en;
  364. }
  365. }
  366. return n.length;
  367. }
  368. function setValue(data,src,callback){
  369. var o=db.transaction('values','readwrite').objectStore('values');
  370. o.put({uri:data.uri,values:data.values});
  371. if(callback) callback(); // it seems that CALLBACK does not work with READWRITE transaction
  372. }
  373. function getOption(data,src,callback){
  374. var o=db.transaction('settings').objectStore('settings');
  375. o.get(data).onsuccess=function(e){
  376. var r=e.target.result;
  377. if(callback) callback(r.value);
  378. };
  379. }
  380. function setOption(data,src,callback){
  381. var o=db.transaction('settings','readwrite').objectStore('settings');
  382. o.put({key:data.key,value:data.value});
  383. if(callback) callback(data.value);
  384. }
  385. function enableScript(d,src,callback) {
  386. var o=db.transaction('scripts','readwrite').objectStore('scripts');
  387. o.get(d.id).onsuccess=function(e){
  388. var r=e.target.result;
  389. if(!r) return;
  390. r.enabled=d.data?1:0;
  391. o.put(r).onsuccess=function(e){ // store script without another transaction
  392. //saveScript(r,src).onsuccess=function(e){
  393. updateItem({id:d.id,obj:getMeta(r),status:0});
  394. };
  395. };
  396. if(callback) callback();
  397. }
  398. function checkUpdateO(o) {
  399. var r={id: o.id, hideUpdate: 1, status: 2};
  400. function update() {
  401. var u = o.custom.downloadURL || o.meta.downloadURL;
  402. if (u) {
  403. r.message = _('msgUpdating');
  404. fetchURL(u, function() {
  405. parseScript({
  406. id: o.id,
  407. status: this.status,
  408. code: this.responseText
  409. });
  410. });
  411. } else r.message = '<span class=new>' + _('msgNewVersion') + '</span>';
  412. updateItem(r);
  413. }
  414. var u = o.custom.updateURL || o.meta.updateURL;
  415. if (u) {
  416. r.message = _('msgCheckingForUpdate');
  417. updateItem(r);
  418. fetchURL(u, function() {
  419. r.message = _('msgErrorFetchingUpdateInfo');
  420. if (this.status == 200) try {
  421. var m = parseMeta(this.responseText);
  422. if (canUpdate(o.meta.version, m.version)) return update();
  423. r.message = _('msgNoUpdate');
  424. } catch (e) {}
  425. delete r.hideUpdate;
  426. updateItem(r);
  427. });
  428. }
  429. }
  430. function checkUpdate(id,src,callback) {
  431. var o=db.transaction('scripts').objectStore('scripts');
  432. o.get(id).onsuccess=function(e){
  433. var r=e.target.result;
  434. if(r) checkUpdateO(r);
  435. if(callback) callback();
  436. };
  437. }
  438. function checkUpdateAll(e,src,callback) {
  439. setOption({key:'lastUpdate',value:Date.now()});
  440. var o=db.transaction('scripts').objectStore('scripts');
  441. o.index('update').openCursor(1).onsuccess=function(e){
  442. var r=e.target.result;
  443. if(!r) {
  444. if(callback) callback();
  445. return;
  446. }
  447. checkUpdateO(r.value);
  448. r.continue();
  449. };
  450. }
  451. var checking=false;
  452. function autoCheck() {
  453. function check() {
  454. getOption('autoUpdate',null,function(a){
  455. if(a) getOption('lastUpdate',null,function(l){
  456. if(Date.now()-l>=864e5) checkUpdateAll();
  457. setTimeout(check,36e5);
  458. }); else checking=false;
  459. });
  460. }
  461. if(!checking) {checking=true;check();}
  462. }
  463. function autoUpdate(o,src,callback){
  464. o=!!o;
  465. setOption({key:'autoUpdate',value:o},src,autoCheck);
  466. if(callback) callback(o);
  467. }
  468. function getData(data,src,callback) {
  469. function getSettings(){
  470. var o=db.transaction('settings').objectStore('settings');
  471. o.openCursor().onsuccess=function(e){
  472. var r=e.target.result,v;
  473. if(r) {
  474. v=r.value;
  475. settings[v.key]=v.value;
  476. r.continue();
  477. } else getScripts();
  478. };
  479. }
  480. function getScripts(){
  481. var o=db.transaction('scripts').objectStore('scripts');
  482. o.index('position').openCursor().onsuccess=function(e){
  483. var r=e.target.result,v;
  484. if(r) {
  485. v=r.value;
  486. if(v.meta.icon&&!(v.meta.icon in settings.cache)) {
  487. cache.push(v.meta.icon);
  488. settings.cache[v.meta.icon]=null;
  489. }
  490. settings.scripts.push(getMeta(v));
  491. r.continue();
  492. } else getCache();
  493. };
  494. }
  495. function getCache(){
  496. var o=db.transaction('cache').objectStore('cache');
  497. function loop(){
  498. var i=cache.pop();
  499. if(i) {
  500. o.get(i).onsuccess=function(e){
  501. var r=e.target.result;
  502. if(r) {
  503. var b=new Blob([r.data],{type:'image/png'});
  504. settings.cache[i]=URL.createObjectURL(b);
  505. URL.revokeObjectURL(b);
  506. }
  507. loop();
  508. };
  509. } else callback(settings);
  510. }
  511. loop();
  512. }
  513. var settings={scripts:[],cache:{}},cache=[];
  514. getSettings();
  515. }
  516. function exportZip(z,src,callback){
  517. function getScripts(){
  518. function loop(){
  519. var i=z.data.shift();
  520. if(i) o.get(i).onsuccess=function(e){
  521. var r=e.target.result;
  522. if(r) {
  523. d.data.push(r);
  524. if(z.values) values.push(r.uri);
  525. }
  526. loop();
  527. }; else getValues();
  528. }
  529. var o=db.transaction('scripts').objectStore('scripts');
  530. loop();
  531. }
  532. function getValues(){
  533. function loop(){
  534. var i=values.shift();
  535. if(i) o.get(i).onsuccess=function(e){
  536. var r=e.target.result;
  537. if(r) d.values[i]=r.values;
  538. loop();
  539. }; else finish();
  540. }
  541. if(z.values) {
  542. var o=db.transaction('values').objectStore('values');
  543. d.values={};loop();
  544. } else finish();
  545. }
  546. function finish(){callback(d);}
  547. var d={data:[]},values=[];
  548. getScripts();
  549. }
  550. chrome.runtime.onConnect.addListener(function(p){
  551. port=p;
  552. p.onDisconnect.addListener(function(){port=null;});
  553. });
  554. chrome.runtime.onMessage.addListener(function(req,src,callback) {
  555. var maps={
  556. NewScript:function(o,src,callback){
  557. o=newScript();
  558. saveScript(o,src).onsuccess=function(e){
  559. o.id=e.target.result;
  560. chrome.tabs.sendMessage(src.tab.id,{cmd:'NewScript',data:o});
  561. };
  562. if(callback) callback();
  563. },
  564. RemoveScript: removeScript,
  565. GetData: getData,
  566. FindScript: findScript,
  567. CheckUpdate: checkUpdate,
  568. CheckUpdateAll: checkUpdateAll,
  569. SaveScript: saveScript,
  570. EnableScript: enableScript,
  571. SetValue: setValue,
  572. GetOption: getOption,
  573. SetOption: setOption,
  574. ExportZip: exportZip,
  575. ParseScript: parseScript,
  576. GetScript: getScript, // for user edit
  577. GetMetas: getMetas, // for popup menu
  578. AutoUpdate: autoUpdate,
  579. Vacuum: vacuum,
  580. Move: move,
  581. LoadDirectly:loadDirectly,
  582. },f=maps[req.cmd];
  583. if(f) f(req.data,src,callback);
  584. return true;
  585. });
  586. initDb(function(){
  587. getOption('maxPosition',function(p){
  588. if(p) pos=p;
  589. });
  590. getOption('isApplied',null,function(o){
  591. chrome.browserAction.setIcon({path:'images/icon19'+(o?'':'w')+'.png'});
  592. });
  593. setTimeout(autoCheck,2e3);
  594. });
  595. var directUrls={};
  596. function loadDirectly(o,src,callback){
  597. directUrls[o]=1;
  598. if(callback) callback();
  599. }
  600. chrome.webRequest.onBeforeRequest.addListener(function(o){
  601. if(directUrls[o.url]) delete directUrls[o.url];
  602. else if(/\.user\.js(\?|$)/.test(o.url)) {
  603. if(o.tabId<0) chrome.tabs.create({url:chrome.extension.getURL('/confirm.html')+'?url='+encodeURIComponent(o.url)});
  604. else chrome.tabs.get(o.tabId,function(t){
  605. chrome.tabs.create({url:chrome.extension.getURL('/confirm.html')+'?url='+encodeURIComponent(o.url)+'&from='+encodeURIComponent(t.url)});
  606. });
  607. return {redirectUrl:'javascript:history.back()'};
  608. }
  609. },{
  610. urls:['*://*/*','file://*/*'],types:['main_frame']
  611. },['blocking']);