vapi-background.js 110 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532
  1. /*******************************************************************************
  2. ηMatrix - a browser extension to black/white list requests.
  3. Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors
  4. Copyright (C) 2019 Alessio Vanni
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see {http://www.gnu.org/licenses/}.
  15. Home: https://gitlab.com/vannilla/ematrix
  16. uMatrix Home: https://github.com/gorhill/uMatrix
  17. */
  18. /* jshint bitwise: false, esnext: true */
  19. /* global self, Components, punycode */
  20. // For background page
  21. 'use strict';
  22. /******************************************************************************/
  23. (function() {
  24. /******************************************************************************/
  25. // Useful links
  26. //
  27. // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface
  28. // https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Services.jsm
  29. /******************************************************************************/
  30. const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
  31. const {Services} = Cu.import('resource://gre/modules/Services.jsm', null);
  32. /******************************************************************************/
  33. var vAPI = self.vAPI = self.vAPI || {};
  34. vAPI.firefox = true;
  35. vAPI.modernFirefox =
  36. Services.appinfo.ID === '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}'
  37. && Services.vc.compare(Services.appinfo.version, '44') > 0;
  38. /******************************************************************************/
  39. var deferUntil = function(testFn, mainFn, details) {
  40. if ( typeof details !== 'object' ) {
  41. details = {};
  42. }
  43. var now = 0;
  44. var next = details.next || 200;
  45. var until = details.until || 2000;
  46. var check = function() {
  47. if ( testFn() === true || now >= until ) {
  48. mainFn();
  49. return;
  50. }
  51. now += next;
  52. vAPI.setTimeout(check, next);
  53. };
  54. if ( 'sync' in details && details.sync === true ) {
  55. check();
  56. } else {
  57. vAPI.setTimeout(check, 1);
  58. }
  59. };
  60. /******************************************************************************/
  61. vAPI.app = {
  62. name: 'eMatrix',
  63. version: location.hash.slice(1)
  64. };
  65. /******************************************************************************/
  66. vAPI.app.start = function() {
  67. };
  68. /******************************************************************************/
  69. vAPI.app.stop = function() {
  70. };
  71. /******************************************************************************/
  72. vAPI.app.restart = function() {
  73. // Listening in bootstrap.js
  74. Cc['@mozilla.org/childprocessmessagemanager;1']
  75. .getService(Ci.nsIMessageSender)
  76. .sendAsyncMessage(location.host + '-restart');
  77. };
  78. /******************************************************************************/
  79. // List of things that needs to be destroyed when disabling the extension
  80. // Only functions should be added to it
  81. var cleanupTasks = [];
  82. // This must be updated manually, every time a new task is added/removed
  83. // Fixed by github.com/AlexVallat:
  84. // https://github.com/AlexVallat/uBlock/commit/7b781248f00cbe3d61b1cc367c440db80fa06049
  85. // 7 instances of cleanupTasks.push, but one is unique to fennec, and one to desktop.
  86. var expectedNumberOfCleanups = 7;
  87. window.addEventListener('unload', function() {
  88. if ( typeof vAPI.app.onShutdown === 'function' ) {
  89. vAPI.app.onShutdown();
  90. }
  91. // IMPORTANT: cleanup tasks must be executed using LIFO order.
  92. var i = cleanupTasks.length;
  93. while ( i-- ) {
  94. cleanupTasks[i]();
  95. }
  96. if ( cleanupTasks.length < expectedNumberOfCleanups ) {
  97. console.error(
  98. 'eMatrix> Cleanup tasks performed: %s (out of %s)',
  99. cleanupTasks.length,
  100. expectedNumberOfCleanups
  101. );
  102. }
  103. // frameModule needs to be cleared too
  104. var frameModuleURL = vAPI.getURL('frameModule.js');
  105. var frameModule = {};
  106. Cu.import(frameModuleURL, frameModule);
  107. frameModule.contentObserver.unregister();
  108. Cu.unload(frameModuleURL);
  109. });
  110. /******************************************************************************/
  111. // For now, only booleans.
  112. vAPI.browserSettings = {
  113. originalValues: {},
  114. rememberOriginalValue: function(path, setting) {
  115. var key = path + '.' + setting;
  116. if ( this.originalValues.hasOwnProperty(key) ) {
  117. return;
  118. }
  119. var hasUserValue;
  120. var branch = Services.prefs.getBranch(path + '.');
  121. try {
  122. hasUserValue = branch.prefHasUserValue(setting);
  123. } catch (ex) {
  124. }
  125. if ( hasUserValue !== undefined ) {
  126. this.originalValues[key] = hasUserValue ? this.getValue(path, setting) : undefined;
  127. }
  128. },
  129. clear: function(path, setting) {
  130. var key = path + '.' + setting;
  131. // Value was not overriden -- nothing to restore
  132. if ( this.originalValues.hasOwnProperty(key) === false ) {
  133. return;
  134. }
  135. var value = this.originalValues[key];
  136. // https://github.com/gorhill/uBlock/issues/292#issuecomment-109621979
  137. // Forget the value immediately, it may change outside of
  138. // uBlock control.
  139. delete this.originalValues[key];
  140. // Original value was a default one
  141. if ( value === undefined ) {
  142. try {
  143. Services.prefs.getBranch(path + '.').clearUserPref(setting);
  144. } catch (ex) {
  145. }
  146. return;
  147. }
  148. // Reset to original value
  149. this.setValue(path, setting, value);
  150. },
  151. getValue: function(path, setting) {
  152. var branch = Services.prefs.getBranch(path + '.');
  153. var getMethod;
  154. // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPrefBranch#getPrefType%28%29
  155. switch ( branch.getPrefType(setting) ) {
  156. case 64: // PREF_INT
  157. getMethod = 'getIntPref';
  158. break;
  159. case 128: // PREF_BOOL
  160. getMethod = 'getBoolPref';
  161. break;
  162. default: // not supported
  163. return;
  164. }
  165. try {
  166. return branch[getMethod](setting);
  167. } catch (ex) {
  168. }
  169. },
  170. setValue: function(path, setting, value) {
  171. var setMethod;
  172. switch ( typeof value ) {
  173. case 'number':
  174. setMethod = 'setIntPref';
  175. break;
  176. case 'boolean':
  177. setMethod = 'setBoolPref';
  178. break;
  179. default: // not supported
  180. return;
  181. }
  182. try {
  183. Services.prefs.getBranch(path + '.')[setMethod](setting, value);
  184. } catch (ex) {
  185. }
  186. },
  187. setSetting: function(setting, value) {
  188. var prefName, prefVal;
  189. switch ( setting ) {
  190. case 'prefetching':
  191. this.rememberOriginalValue('network', 'prefetch-next');
  192. // http://betanews.com/2015/08/15/firefox-stealthily-loads-webpages-when-you-hover-over-links-heres-how-to-stop-it/
  193. // https://bugzilla.mozilla.org/show_bug.cgi?id=814169
  194. // Sigh.
  195. this.rememberOriginalValue('network.http', 'speculative-parallel-limit');
  196. // https://github.com/gorhill/uBlock/issues/292
  197. // "true" means "do not disable", i.e. leave entry alone
  198. if ( value ) {
  199. this.clear('network', 'prefetch-next');
  200. this.clear('network.http', 'speculative-parallel-limit');
  201. } else {
  202. this.setValue('network', 'prefetch-next', false);
  203. this.setValue('network.http', 'speculative-parallel-limit', 0);
  204. }
  205. break;
  206. case 'hyperlinkAuditing':
  207. this.rememberOriginalValue('browser', 'send_pings');
  208. this.rememberOriginalValue('beacon', 'enabled');
  209. // https://github.com/gorhill/uBlock/issues/292
  210. // "true" means "do not disable", i.e. leave entry alone
  211. if ( value ) {
  212. this.clear('browser', 'send_pings');
  213. this.clear('beacon', 'enabled');
  214. } else {
  215. this.setValue('browser', 'send_pings', false);
  216. this.setValue('beacon', 'enabled', false);
  217. }
  218. break;
  219. // https://github.com/gorhill/uBlock/issues/894
  220. // Do not disable completely WebRTC if it can be avoided. FF42+
  221. // has a `media.peerconnection.ice.default_address_only` pref which
  222. // purpose is to prevent local IP address leakage.
  223. case 'webrtcIPAddress':
  224. if ( this.getValue('media.peerconnection', 'ice.default_address_only') !== undefined ) {
  225. prefName = 'ice.default_address_only';
  226. prefVal = true;
  227. } else {
  228. prefName = 'enabled';
  229. prefVal = false;
  230. }
  231. this.rememberOriginalValue('media.peerconnection', prefName);
  232. if ( value ) {
  233. this.clear('media.peerconnection', prefName);
  234. } else {
  235. this.setValue('media.peerconnection', prefName, prefVal);
  236. }
  237. break;
  238. default:
  239. break;
  240. }
  241. },
  242. set: function(details) {
  243. for ( var setting in details ) {
  244. if ( details.hasOwnProperty(setting) === false ) {
  245. continue;
  246. }
  247. this.setSetting(setting, !!details[setting]);
  248. }
  249. },
  250. restoreAll: function() {
  251. var pos;
  252. for ( var key in this.originalValues ) {
  253. if ( this.originalValues.hasOwnProperty(key) === false ) {
  254. continue;
  255. }
  256. pos = key.lastIndexOf('.');
  257. this.clear(key.slice(0, pos), key.slice(pos + 1));
  258. }
  259. }
  260. };
  261. cleanupTasks.push(vAPI.browserSettings.restoreAll.bind(vAPI.browserSettings));
  262. /******************************************************************************/
  263. // API matches that of chrome.storage.local:
  264. // https://developer.chrome.com/extensions/storage
  265. vAPI.storage = (function() {
  266. var db = null;
  267. var vacuumTimer = null;
  268. var close = function() {
  269. if ( vacuumTimer !== null ) {
  270. clearTimeout(vacuumTimer);
  271. vacuumTimer = null;
  272. }
  273. if ( db === null ) {
  274. return;
  275. }
  276. db.asyncClose();
  277. db = null;
  278. };
  279. var open = function() {
  280. if ( db !== null ) {
  281. return db;
  282. }
  283. // Create path
  284. let path = Services.dirsvc.get('ProfD', Ci.nsIFile);
  285. path.append('ematrix-data');
  286. if ( !path.exists() ) {
  287. path.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0774', 8));
  288. }
  289. if ( !path.isDirectory() ) {
  290. throw Error('Should be a directory...');
  291. }
  292. let path2 = Services.dirsvc.get('ProfD', Ci.nsIFile);
  293. path2.append('extension-data');
  294. path2.append(location.host + '.sqlite');
  295. if (path2.exists()) {
  296. path2.moveTo(path, location.host+'.sqlite');
  297. }
  298. path.append(location.host + '.sqlite');
  299. // Open database
  300. try {
  301. db = Services.storage.openDatabase(path);
  302. if ( db.connectionReady === false ) {
  303. db.asyncClose();
  304. db = null;
  305. }
  306. } catch (ex) {
  307. }
  308. if ( db === null ) {
  309. return null;
  310. }
  311. // Database was opened, register cleanup task
  312. cleanupTasks.push(close);
  313. // Setup database
  314. db.createAsyncStatement('CREATE TABLE IF NOT EXISTS "settings" ("name" TEXT PRIMARY KEY NOT NULL, "value" TEXT);')
  315. .executeAsync();
  316. if ( vacuum !== null ) {
  317. vacuumTimer = vAPI.setTimeout(vacuum, 60000);
  318. }
  319. return db;
  320. };
  321. // https://developer.mozilla.org/en-US/docs/Storage/Performance#Vacuuming_and_zero-fill
  322. // Vacuum only once, and only while idle
  323. var vacuum = function() {
  324. vacuumTimer = null;
  325. if ( db === null ) {
  326. return;
  327. }
  328. var idleSvc = Cc['@mozilla.org/widget/idleservice;1']
  329. .getService(Ci.nsIIdleService);
  330. if ( idleSvc.idleTime < 60000 ) {
  331. vacuumTimer = vAPI.setTimeout(vacuum, 60000);
  332. return;
  333. }
  334. db.createAsyncStatement('VACUUM').executeAsync();
  335. vacuum = null;
  336. };
  337. // Execute a query
  338. var runStatement = function(stmt, callback) {
  339. var result = {};
  340. stmt.executeAsync({
  341. handleResult: function(rows) {
  342. if ( !rows || typeof callback !== 'function' ) {
  343. return;
  344. }
  345. var row;
  346. while ( (row = rows.getNextRow()) ) {
  347. // we assume that there will be two columns, since we're
  348. // using it only for preferences
  349. result[row.getResultByIndex(0)] = row.getResultByIndex(1);
  350. }
  351. },
  352. handleCompletion: function(reason) {
  353. if ( typeof callback === 'function' && reason === 0 ) {
  354. callback(result);
  355. }
  356. },
  357. handleError: function(error) {
  358. console.error('SQLite error ', error.result, error.message);
  359. // Caller expects an answer regardless of failure.
  360. if ( typeof callback === 'function' ) {
  361. callback(null);
  362. }
  363. }
  364. });
  365. };
  366. var bindNames = function(stmt, names) {
  367. if ( Array.isArray(names) === false || names.length === 0 ) {
  368. return;
  369. }
  370. var params = stmt.newBindingParamsArray();
  371. var i = names.length, bp;
  372. while ( i-- ) {
  373. bp = params.newBindingParams();
  374. bp.bindByName('name', names[i]);
  375. params.addParams(bp);
  376. }
  377. stmt.bindParameters(params);
  378. };
  379. var clear = function(callback) {
  380. if ( open() === null ) {
  381. if ( typeof callback === 'function' ) {
  382. callback();
  383. }
  384. return;
  385. }
  386. runStatement(db.createAsyncStatement('DELETE FROM "settings";'), callback);
  387. };
  388. var getBytesInUse = function(keys, callback) {
  389. if ( typeof callback !== 'function' ) {
  390. return;
  391. }
  392. if ( open() === null ) {
  393. callback(0);
  394. return;
  395. }
  396. var stmt;
  397. if ( Array.isArray(keys) ) {
  398. stmt = db.createAsyncStatement('SELECT "size" AS "size", SUM(LENGTH("value")) FROM "settings" WHERE "name" = :name');
  399. bindNames(keys);
  400. } else {
  401. stmt = db.createAsyncStatement('SELECT "size" AS "size", SUM(LENGTH("value")) FROM "settings"');
  402. }
  403. runStatement(stmt, function(result) {
  404. callback(result.size);
  405. });
  406. };
  407. var read = function(details, callback) {
  408. if ( typeof callback !== 'function' ) {
  409. return;
  410. }
  411. var prepareResult = function(result) {
  412. var key;
  413. for ( key in result ) {
  414. if ( result.hasOwnProperty(key) === false ) {
  415. continue;
  416. }
  417. result[key] = JSON.parse(result[key]);
  418. }
  419. if ( typeof details === 'object' && details !== null ) {
  420. for ( key in details ) {
  421. if ( result.hasOwnProperty(key) === false ) {
  422. result[key] = details[key];
  423. }
  424. }
  425. }
  426. callback(result);
  427. };
  428. if ( open() === null ) {
  429. prepareResult({});
  430. return;
  431. }
  432. var names = [];
  433. if ( details !== null ) {
  434. if ( Array.isArray(details) ) {
  435. names = details;
  436. } else if ( typeof details === 'object' ) {
  437. names = Object.keys(details);
  438. } else {
  439. names = [details.toString()];
  440. }
  441. }
  442. var stmt;
  443. if ( names.length === 0 ) {
  444. stmt = db.createAsyncStatement('SELECT * FROM "settings"');
  445. } else {
  446. stmt = db.createAsyncStatement('SELECT * FROM "settings" WHERE "name" = :name');
  447. bindNames(stmt, names);
  448. }
  449. runStatement(stmt, prepareResult);
  450. };
  451. var remove = function(keys, callback) {
  452. if ( open() === null ) {
  453. if ( typeof callback === 'function' ) {
  454. callback();
  455. }
  456. return;
  457. }
  458. var stmt = db.createAsyncStatement('DELETE FROM "settings" WHERE "name" = :name');
  459. bindNames(stmt, typeof keys === 'string' ? [keys] : keys);
  460. runStatement(stmt, callback);
  461. };
  462. var write = function(details, callback) {
  463. if ( open() === null ) {
  464. if ( typeof callback === 'function' ) {
  465. callback();
  466. }
  467. return;
  468. }
  469. var stmt = db.createAsyncStatement('INSERT OR REPLACE INTO "settings" ("name", "value") VALUES(:name, :value)');
  470. var params = stmt.newBindingParamsArray(), bp;
  471. for ( var key in details ) {
  472. if ( details.hasOwnProperty(key) === false ) {
  473. continue;
  474. }
  475. bp = params.newBindingParams();
  476. bp.bindByName('name', key);
  477. bp.bindByName('value', JSON.stringify(details[key]));
  478. params.addParams(bp);
  479. }
  480. if ( params.length === 0 ) {
  481. return;
  482. }
  483. stmt.bindParameters(params);
  484. runStatement(stmt, callback);
  485. };
  486. // Export API
  487. var api = {
  488. QUOTA_BYTES: 100 * 1024 * 1024,
  489. clear: clear,
  490. get: read,
  491. getBytesInUse: getBytesInUse,
  492. remove: remove,
  493. set: write
  494. };
  495. return api;
  496. })();
  497. vAPI.cacheStorage = vAPI.storage;
  498. /******************************************************************************/
  499. // This must be executed/setup early.
  500. var winWatcher = (function() {
  501. var windowToIdMap = new Map();
  502. var windowIdGenerator = 1;
  503. var api = {
  504. onOpenWindow: null,
  505. onCloseWindow: null
  506. };
  507. // https://github.com/gorhill/uMatrix/issues/586
  508. // This is necessary hack because on SeaMonkey 2.40, for unknown reasons
  509. // private windows do not have the attribute `windowtype` set to
  510. // `navigator:browser`. As a fallback, the code here will also test whether
  511. // the id attribute is `main-window`.
  512. api.toBrowserWindow = function(win) {
  513. var docElement = win && win.document && win.document.documentElement;
  514. if ( !docElement ) {
  515. return null;
  516. }
  517. if ( vAPI.thunderbird ) {
  518. return docElement.getAttribute('windowtype') === 'mail:3pane' ? win : null;
  519. }
  520. return docElement.getAttribute('windowtype') === 'navigator:browser' ||
  521. docElement.getAttribute('id') === 'main-window' ?
  522. win : null;
  523. };
  524. api.getWindows = function() {
  525. return windowToIdMap.keys();
  526. };
  527. api.idFromWindow = function(win) {
  528. return windowToIdMap.get(win) || 0;
  529. };
  530. api.getCurrentWindow = function() {
  531. return this.toBrowserWindow(Services.wm.getMostRecentWindow(null));
  532. };
  533. var addWindow = function(win) {
  534. if ( !win || windowToIdMap.has(win) ) {
  535. return;
  536. }
  537. windowToIdMap.set(win, windowIdGenerator++);
  538. if ( typeof api.onOpenWindow === 'function' ) {
  539. api.onOpenWindow(win);
  540. }
  541. };
  542. var removeWindow = function(win) {
  543. if ( !win || windowToIdMap.delete(win) !== true ) {
  544. return;
  545. }
  546. if ( typeof api.onCloseWindow === 'function' ) {
  547. api.onCloseWindow(win);
  548. }
  549. };
  550. // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIWindowMediator
  551. // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIWindowWatcher
  552. // https://github.com/gorhill/uMatrix/issues/357
  553. // Use nsIWindowMediator for being notified of opened/closed windows.
  554. var listeners = {
  555. onOpenWindow: function(aWindow) {
  556. var win;
  557. try {
  558. win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
  559. .getInterface(Ci.nsIDOMWindow);
  560. } catch (ex) {
  561. }
  562. addWindow(win);
  563. },
  564. onCloseWindow: function(aWindow) {
  565. var win;
  566. try {
  567. win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
  568. .getInterface(Ci.nsIDOMWindow);
  569. } catch (ex) {
  570. }
  571. removeWindow(win);
  572. },
  573. observe: function(aSubject, topic) {
  574. // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIWindowWatcher#registerNotification%28%29
  575. // "aSubject - the window being opened or closed, sent as an
  576. // "nsISupports which can be ... QueryInterfaced to an
  577. // "nsIDOMWindow."
  578. var win;
  579. try {
  580. win = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
  581. .getInterface(Ci.nsIDOMWindow);
  582. } catch (ex) {
  583. }
  584. if ( !win ) { return; }
  585. if ( topic === 'domwindowopened' ) {
  586. addWindow(win);
  587. return;
  588. }
  589. if ( topic === 'domwindowclosed' ) {
  590. removeWindow(win);
  591. return;
  592. }
  593. }
  594. };
  595. (function() {
  596. var winumerator, win;
  597. // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIWindowMediator#getEnumerator%28%29
  598. winumerator = Services.wm.getEnumerator(null);
  599. while ( winumerator.hasMoreElements() ) {
  600. win = winumerator.getNext();
  601. if ( !win.closed ) {
  602. windowToIdMap.set(win, windowIdGenerator++);
  603. }
  604. }
  605. // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIWindowWatcher#getWindowEnumerator%28%29
  606. winumerator = Services.ww.getWindowEnumerator();
  607. while ( winumerator.hasMoreElements() ) {
  608. win = winumerator.getNext()
  609. .QueryInterface(Ci.nsIInterfaceRequestor)
  610. .getInterface(Ci.nsIDOMWindow);
  611. if ( !win.closed ) {
  612. windowToIdMap.set(win, windowIdGenerator++);
  613. }
  614. }
  615. Services.wm.addListener(listeners);
  616. Services.ww.registerNotification(listeners);
  617. })();
  618. cleanupTasks.push(function() {
  619. Services.wm.removeListener(listeners);
  620. Services.ww.unregisterNotification(listeners);
  621. windowToIdMap.clear();
  622. });
  623. return api;
  624. })();
  625. /******************************************************************************/
  626. var getTabBrowser = function(win) {
  627. return win && win.gBrowser || null;
  628. };
  629. /******************************************************************************/
  630. var getOwnerWindow = function(target) {
  631. if ( target.ownerDocument ) {
  632. return target.ownerDocument.defaultView;
  633. }
  634. return null;
  635. };
  636. /******************************************************************************/
  637. vAPI.isBehindTheSceneTabId = function(tabId) {
  638. return tabId.toString() === '-1';
  639. };
  640. vAPI.noTabId = '-1';
  641. /******************************************************************************/
  642. vAPI.tabs = {};
  643. /******************************************************************************/
  644. vAPI.tabs.registerListeners = function() {
  645. tabWatcher.start();
  646. };
  647. /******************************************************************************/
  648. // Firefox:
  649. // https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Tabbed_browser
  650. //
  651. // browser --> ownerDocument --> defaultView --> gBrowser --> browsers --+
  652. // ^ |
  653. // | |
  654. // +-------------------------------------------------------------------
  655. //
  656. // browser (browser)
  657. // contentTitle
  658. // currentURI
  659. // ownerDocument (XULDocument)
  660. // defaultView (ChromeWindow)
  661. // gBrowser (tabbrowser OR browser)
  662. // browsers (browser)
  663. // selectedBrowser
  664. // selectedTab
  665. // tabs (tab.tabbrowser-tab)
  666. //
  667. // Fennec: (what I figured so far)
  668. //
  669. // tab --> browser windows --> window --> BrowserApp --> tabs --+
  670. // ^ window |
  671. // | |
  672. // +---------------------------------------------------------------+
  673. //
  674. // tab
  675. // browser
  676. // [manual search to go back to tab from list of windows]
  677. vAPI.tabs.get = function(tabId, callback) {
  678. var browser;
  679. if ( tabId === null ) {
  680. browser = tabWatcher.currentBrowser();
  681. tabId = tabWatcher.tabIdFromTarget(browser);
  682. } else {
  683. browser = tabWatcher.browserFromTabId(tabId);
  684. }
  685. // For internal use
  686. if ( typeof callback !== 'function' ) {
  687. return browser;
  688. }
  689. if ( !browser || !browser.currentURI ) {
  690. callback();
  691. return;
  692. }
  693. var win = getOwnerWindow(browser);
  694. var tabBrowser = getTabBrowser(win);
  695. // https://github.com/gorhill/uMatrix/issues/540
  696. // The `index` property is nowhere used by eMatrix at this point, so we
  697. // will refrain from returning this information for the time being.
  698. callback({
  699. id: tabId,
  700. index: undefined,
  701. windowId: winWatcher.idFromWindow(win),
  702. active: tabBrowser !== null && browser === tabBrowser.selectedBrowser,
  703. url: browser.currentURI.asciiSpec,
  704. title: browser.contentTitle
  705. });
  706. };
  707. /******************************************************************************/
  708. vAPI.tabs.getAllSync = function(window) {
  709. var win, tab;
  710. var tabs = [];
  711. for ( win of winWatcher.getWindows() ) {
  712. if ( window && window !== win ) {
  713. continue;
  714. }
  715. var tabBrowser = getTabBrowser(win);
  716. if ( tabBrowser === null ) {
  717. continue;
  718. }
  719. // This can happens if a tab-less window is currently opened.
  720. // Example of a tab-less window: one opened from clicking
  721. // "View Page Source".
  722. if ( !tabBrowser.tabs ) {
  723. continue;
  724. }
  725. for ( tab of tabBrowser.tabs ) {
  726. tabs.push(tab);
  727. }
  728. }
  729. return tabs;
  730. };
  731. /******************************************************************************/
  732. vAPI.tabs.getAll = function(callback) {
  733. var tabs = [], tab;
  734. for ( var browser of tabWatcher.browsers() ) {
  735. tab = tabWatcher.tabFromBrowser(browser);
  736. if ( tab === null ) {
  737. continue;
  738. }
  739. if ( tab.hasAttribute('pending') ) {
  740. continue;
  741. }
  742. tabs.push({
  743. id: tabWatcher.tabIdFromTarget(browser),
  744. url: browser.currentURI.asciiSpec
  745. });
  746. }
  747. callback(tabs);
  748. };
  749. /******************************************************************************/
  750. // properties of the details object:
  751. // url: 'URL', // the address that will be opened
  752. // tabId: 1, // the tab is used if set, instead of creating a new one
  753. // index: -1, // undefined: end of the list, -1: following tab, or after index
  754. // active: false, // opens the tab in background - true and undefined: foreground
  755. // select: true // if a tab is already opened with that url, then select it instead of opening a new one
  756. vAPI.tabs.open = function(details) {
  757. if ( !details.url ) {
  758. return null;
  759. }
  760. // extension pages
  761. if ( /^[\w-]{2,}:/.test(details.url) === false ) {
  762. details.url = vAPI.getURL(details.url);
  763. }
  764. var tab;
  765. if ( details.select ) {
  766. var URI = Services.io.newURI(details.url, null, null);
  767. for ( tab of this.getAllSync() ) {
  768. var browser = tabWatcher.browserFromTarget(tab);
  769. // https://github.com/gorhill/uBlock/issues/2558
  770. if ( browser === null ) { continue; }
  771. // Or simply .equals if we care about the fragment
  772. if ( URI.equalsExceptRef(browser.currentURI) === false ) {
  773. continue;
  774. }
  775. this.select(tab);
  776. // Update URL if fragment is different
  777. if ( URI.equals(browser.currentURI) === false ) {
  778. browser.loadURI(URI.asciiSpec);
  779. }
  780. return;
  781. }
  782. }
  783. if ( details.active === undefined ) {
  784. details.active = true;
  785. }
  786. if ( details.tabId ) {
  787. tab = tabWatcher.browserFromTabId(details.tabId);
  788. if ( tab ) {
  789. tabWatcher.browserFromTarget(tab).loadURI(details.url);
  790. return;
  791. }
  792. }
  793. // Open in a standalone window
  794. if ( details.popup === true ) {
  795. Services.ww.openWindow(
  796. self,
  797. details.url,
  798. null,
  799. 'location=1,menubar=1,personalbar=1,resizable=1,toolbar=1',
  800. null
  801. );
  802. return;
  803. }
  804. var win = winWatcher.getCurrentWindow();
  805. var tabBrowser = getTabBrowser(win);
  806. if ( tabBrowser === null ) {
  807. return;
  808. }
  809. if ( details.index === -1 ) {
  810. details.index = tabBrowser.browsers.indexOf(tabBrowser.selectedBrowser) + 1;
  811. }
  812. tab = tabBrowser.loadOneTab(details.url, { inBackground: !details.active });
  813. if ( details.index !== undefined ) {
  814. tabBrowser.moveTabTo(tab, details.index);
  815. }
  816. };
  817. /******************************************************************************/
  818. // Replace the URL of a tab. Noop if the tab does not exist.
  819. vAPI.tabs.replace = function(tabId, url) {
  820. var targetURL = url;
  821. // extension pages
  822. if ( /^[\w-]{2,}:/.test(targetURL) !== true ) {
  823. targetURL = vAPI.getURL(targetURL);
  824. }
  825. var browser = tabWatcher.browserFromTabId(tabId);
  826. if ( browser ) {
  827. browser.loadURI(targetURL);
  828. }
  829. };
  830. /******************************************************************************/
  831. vAPI.tabs._remove = function(tab, tabBrowser) {
  832. if ( tabBrowser ) {
  833. tabBrowser.removeTab(tab);
  834. }
  835. };
  836. /******************************************************************************/
  837. vAPI.tabs.remove = function(tabId) {
  838. var browser = tabWatcher.browserFromTabId(tabId);
  839. if ( !browser ) {
  840. return;
  841. }
  842. var tab = tabWatcher.tabFromBrowser(browser);
  843. if ( !tab ) {
  844. return;
  845. }
  846. this._remove(tab, getTabBrowser(getOwnerWindow(browser)));
  847. };
  848. /******************************************************************************/
  849. vAPI.tabs.reload = function(tabId) {
  850. var browser = tabWatcher.browserFromTabId(tabId);
  851. if ( !browser ) {
  852. return;
  853. }
  854. browser.webNavigation.reload(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
  855. };
  856. /******************************************************************************/
  857. vAPI.tabs.select = function(tab) {
  858. if ( typeof tab !== 'object' ) {
  859. tab = tabWatcher.tabFromBrowser(tabWatcher.browserFromTabId(tab));
  860. }
  861. if ( !tab ) {
  862. return;
  863. }
  864. // https://github.com/gorhill/uBlock/issues/470
  865. var win = getOwnerWindow(tab);
  866. win.focus();
  867. var tabBrowser = getTabBrowser(win);
  868. if ( tabBrowser ) {
  869. tabBrowser.selectedTab = tab;
  870. }
  871. };
  872. /******************************************************************************/
  873. vAPI.tabs.injectScript = function(tabId, details, callback) {
  874. var browser = tabWatcher.browserFromTabId(tabId);
  875. if ( !browser ) {
  876. return;
  877. }
  878. if ( typeof details.file !== 'string' ) {
  879. return;
  880. }
  881. details.file = vAPI.getURL(details.file);
  882. browser.messageManager.sendAsyncMessage(
  883. location.host + ':broadcast',
  884. JSON.stringify({
  885. broadcast: true,
  886. channelName: 'vAPI',
  887. msg: {
  888. cmd: 'injectScript',
  889. details: details
  890. }
  891. })
  892. );
  893. if ( typeof callback === 'function' ) {
  894. vAPI.setTimeout(callback, 13);
  895. }
  896. };
  897. /******************************************************************************/
  898. var tabWatcher = (function() {
  899. // TODO: find out whether we need a janitor to take care of stale entries.
  900. // https://github.com/gorhill/uMatrix/issues/540
  901. // Use only weak references to hold onto browser references.
  902. var browserToTabIdMap = new WeakMap();
  903. var tabIdToBrowserMap = new Map();
  904. var tabIdGenerator = 1;
  905. var indexFromBrowser = function(browser) {
  906. if ( !browser ) {
  907. return -1;
  908. }
  909. var win = getOwnerWindow(browser);
  910. if ( !win ) {
  911. return -1;
  912. }
  913. var tabBrowser = getTabBrowser(win);
  914. if ( tabBrowser === null ) {
  915. return -1;
  916. }
  917. // This can happen, for example, the `view-source:` window, there is
  918. // no tabbrowser object, the browser object sits directly in the
  919. // window.
  920. if ( tabBrowser === browser ) {
  921. return 0;
  922. }
  923. return tabBrowser.browsers.indexOf(browser);
  924. };
  925. var indexFromTarget = function(target) {
  926. return indexFromBrowser(browserFromTarget(target));
  927. };
  928. var tabFromBrowser = function(browser) {
  929. var i = indexFromBrowser(browser);
  930. if ( i === -1 ) {
  931. return null;
  932. }
  933. var win = getOwnerWindow(browser);
  934. if ( !win ) {
  935. return null;
  936. }
  937. var tabBrowser = getTabBrowser(win);
  938. if ( tabBrowser === null ) {
  939. return null;
  940. }
  941. if ( !tabBrowser.tabs || i >= tabBrowser.tabs.length ) {
  942. return null;
  943. }
  944. return tabBrowser.tabs[i];
  945. };
  946. var browserFromTarget = function(target) {
  947. if ( !target ) {
  948. return null;
  949. }
  950. if ( target.linkedPanel ) { // target is a tab
  951. target = target.linkedBrowser;
  952. }
  953. if ( target.localName !== 'browser' ) {
  954. return null;
  955. }
  956. return target;
  957. };
  958. var tabIdFromTarget = function(target) {
  959. var browser = browserFromTarget(target);
  960. if ( browser === null ) {
  961. return vAPI.noTabId;
  962. }
  963. var tabId = browserToTabIdMap.get(browser);
  964. if ( tabId === undefined ) {
  965. tabId = '' + tabIdGenerator++;
  966. browserToTabIdMap.set(browser, tabId);
  967. tabIdToBrowserMap.set(tabId, Cu.getWeakReference(browser));
  968. }
  969. return tabId;
  970. };
  971. var browserFromTabId = function(tabId) {
  972. var weakref = tabIdToBrowserMap.get(tabId);
  973. var browser = weakref && weakref.get();
  974. return browser || null;
  975. };
  976. var currentBrowser = function() {
  977. var win = winWatcher.getCurrentWindow();
  978. // https://github.com/gorhill/uBlock/issues/399
  979. // getTabBrowser() can return null at browser launch time.
  980. var tabBrowser = getTabBrowser(win);
  981. if ( tabBrowser === null ) {
  982. return null;
  983. }
  984. return browserFromTarget(tabBrowser.selectedTab);
  985. };
  986. var removeBrowserEntry = function(tabId, browser) {
  987. if ( tabId && tabId !== vAPI.noTabId ) {
  988. vAPI.tabs.onClosed(tabId);
  989. delete vAPI.toolbarButton.tabs[tabId];
  990. tabIdToBrowserMap.delete(tabId);
  991. }
  992. if ( browser ) {
  993. browserToTabIdMap.delete(browser);
  994. }
  995. };
  996. var removeTarget = function(target) {
  997. onClose({ target: target });
  998. };
  999. var getAllBrowsers = function() {
  1000. var browsers = [], browser;
  1001. for ( var [tabId, weakref] of tabIdToBrowserMap ) {
  1002. browser = weakref.get();
  1003. // TODO:
  1004. // Maybe call removeBrowserEntry() if the browser no longer exists?
  1005. if ( browser ) {
  1006. browsers.push(browser);
  1007. }
  1008. }
  1009. return browsers;
  1010. };
  1011. // https://developer.mozilla.org/en-US/docs/Web/Events/TabOpen
  1012. //var onOpen = function({target}) {
  1013. // var tabId = tabIdFromTarget(target);
  1014. // var browser = browserFromTabId(tabId);
  1015. // vAPI.tabs.onNavigation({
  1016. // frameId: 0,
  1017. // tabId: tabId,
  1018. // url: browser.currentURI.asciiSpec,
  1019. // });
  1020. //};
  1021. // https://developer.mozilla.org/en-US/docs/Web/Events/TabShow
  1022. var onShow = function({target}) {
  1023. tabIdFromTarget(target);
  1024. };
  1025. // https://developer.mozilla.org/en-US/docs/Web/Events/TabClose
  1026. var onClose = function({target}) {
  1027. // target is tab in Firefox, browser in Fennec
  1028. var browser = browserFromTarget(target);
  1029. var tabId = browserToTabIdMap.get(browser);
  1030. removeBrowserEntry(tabId, browser);
  1031. };
  1032. // https://developer.mozilla.org/en-US/docs/Web/Events/TabSelect
  1033. // This is an entry point: when creating a new tab, it is not always
  1034. // reported through onLocationChanged... Sigh. It is "reported" here
  1035. // however.
  1036. var onSelect = function({target}) {
  1037. var browser = browserFromTarget(target);
  1038. var tabId = browserToTabIdMap.get(browser);
  1039. if ( tabId === undefined ) {
  1040. tabId = tabIdFromTarget(target);
  1041. vAPI.tabs.onNavigation({
  1042. frameId: 0,
  1043. tabId: tabId,
  1044. url: browser.currentURI.asciiSpec
  1045. });
  1046. }
  1047. vAPI.setIcon(tabId, getOwnerWindow(target));
  1048. };
  1049. var locationChangedMessageName = location.host + ':locationChanged';
  1050. var onLocationChanged = function(e) {
  1051. var vapi = vAPI;
  1052. var details = e.data;
  1053. // Ignore notifications related to our popup
  1054. if ( details.url.lastIndexOf(vapi.getURL('popup.html'), 0) === 0 ) {
  1055. return;
  1056. }
  1057. var browser = e.target;
  1058. var tabId = tabIdFromTarget(browser);
  1059. if ( tabId === vapi.noTabId ) {
  1060. return;
  1061. }
  1062. // LOCATION_CHANGE_SAME_DOCUMENT = "did not load a new document"
  1063. if ( details.flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT ) {
  1064. vapi.tabs.onUpdated(tabId, {url: details.url}, {
  1065. frameId: 0,
  1066. tabId: tabId,
  1067. url: browser.currentURI.asciiSpec
  1068. });
  1069. return;
  1070. }
  1071. // https://github.com/chrisaljoudi/uBlock/issues/105
  1072. // Allow any kind of pages
  1073. vapi.tabs.onNavigation({
  1074. frameId: 0,
  1075. tabId: tabId,
  1076. url: details.url
  1077. });
  1078. };
  1079. var attachToTabBrowser = function(window) {
  1080. if ( typeof vAPI.toolbarButton.attachToNewWindow === 'function' ) {
  1081. vAPI.toolbarButton.attachToNewWindow(window);
  1082. }
  1083. var tabBrowser = getTabBrowser(window);
  1084. if ( tabBrowser === null ) {
  1085. return;
  1086. }
  1087. var tabContainer;
  1088. if ( tabBrowser.deck ) { // Fennec
  1089. tabContainer = tabBrowser.deck;
  1090. } else if ( tabBrowser.tabContainer ) { // Firefox
  1091. tabContainer = tabBrowser.tabContainer;
  1092. vAPI.contextMenu.register(document);
  1093. }
  1094. // https://github.com/gorhill/uBlock/issues/697
  1095. // Ignore `TabShow` events: unfortunately the `pending` attribute is
  1096. // not set when a tab is opened as a result of session restore -- it is
  1097. // set *after* the event is fired in such case.
  1098. if ( tabContainer ) {
  1099. tabContainer.addEventListener('TabShow', onShow);
  1100. tabContainer.addEventListener('TabClose', onClose);
  1101. // when new window is opened TabSelect doesn't run on the selected tab?
  1102. tabContainer.addEventListener('TabSelect', onSelect);
  1103. }
  1104. };
  1105. // https://github.com/gorhill/uBlock/issues/906
  1106. // Ensure the environment is ready before trying to attaching.
  1107. var canAttachToTabBrowser = function(window) {
  1108. var document = window && window.document;
  1109. if ( !document || document.readyState !== 'complete' ) {
  1110. return false;
  1111. }
  1112. // On some platforms, the tab browser isn't immediately available,
  1113. // try waiting a bit if this happens.
  1114. // https://github.com/gorhill/uBlock/issues/763
  1115. // Not getting a tab browser should not prevent from attaching ourself
  1116. // to the window.
  1117. var tabBrowser = getTabBrowser(window);
  1118. if ( tabBrowser === null ) {
  1119. return false;
  1120. }
  1121. return winWatcher.toBrowserWindow(window) !== null;
  1122. };
  1123. var onWindowLoad = function(win) {
  1124. deferUntil(
  1125. canAttachToTabBrowser.bind(null, win),
  1126. attachToTabBrowser.bind(null, win)
  1127. );
  1128. };
  1129. var onWindowUnload = function(win) {
  1130. vAPI.contextMenu.unregister(win.document);
  1131. var tabBrowser = getTabBrowser(win);
  1132. if ( tabBrowser === null ) {
  1133. return;
  1134. }
  1135. var tabContainer = tabBrowser.tabContainer;
  1136. if ( tabContainer ) {
  1137. tabContainer.removeEventListener('TabShow', onShow);
  1138. tabContainer.removeEventListener('TabClose', onClose);
  1139. tabContainer.removeEventListener('TabSelect', onSelect);
  1140. }
  1141. // https://github.com/gorhill/uBlock/issues/574
  1142. // To keep in mind: not all windows are tab containers,
  1143. // sometimes the window IS the tab.
  1144. var tabs;
  1145. if ( tabBrowser.tabs ) {
  1146. tabs = tabBrowser.tabs;
  1147. } else if ( tabBrowser.localName === 'browser' ) {
  1148. tabs = [tabBrowser];
  1149. } else {
  1150. tabs = [];
  1151. }
  1152. var browser, URI, tabId;
  1153. var tabindex = tabs.length, tab;
  1154. while ( tabindex-- ) {
  1155. tab = tabs[tabindex];
  1156. browser = browserFromTarget(tab);
  1157. if ( browser === null ) {
  1158. continue;
  1159. }
  1160. URI = browser.currentURI;
  1161. // Close extension tabs
  1162. if ( URI.schemeIs('chrome') && URI.host === location.host ) {
  1163. vAPI.tabs._remove(tab, getTabBrowser(win));
  1164. }
  1165. tabId = browserToTabIdMap.get(browser);
  1166. if ( tabId !== undefined ) {
  1167. removeBrowserEntry(tabId, browser);
  1168. tabIdToBrowserMap.delete(tabId);
  1169. }
  1170. browserToTabIdMap.delete(browser);
  1171. }
  1172. };
  1173. // Initialize map with existing active tabs
  1174. var start = function() {
  1175. var tabBrowser, tabs, tab;
  1176. for ( var win of winWatcher.getWindows() ) {
  1177. onWindowLoad(win);
  1178. tabBrowser = getTabBrowser(win);
  1179. if ( tabBrowser === null ) {
  1180. continue;
  1181. }
  1182. for ( tab of tabBrowser.tabs ) {
  1183. if ( !tab.hasAttribute('pending') ) {
  1184. tabIdFromTarget(tab);
  1185. }
  1186. }
  1187. }
  1188. winWatcher.onOpenWindow = onWindowLoad;
  1189. winWatcher.onCloseWindow = onWindowUnload;
  1190. vAPI.messaging.globalMessageManager.addMessageListener(
  1191. locationChangedMessageName,
  1192. onLocationChanged
  1193. );
  1194. };
  1195. var stop = function() {
  1196. winWatcher.onOpenWindow = null;
  1197. winWatcher.onCloseWindow = null;
  1198. vAPI.messaging.globalMessageManager.removeMessageListener(
  1199. locationChangedMessageName,
  1200. onLocationChanged
  1201. );
  1202. for ( var win of winWatcher.getWindows() ) {
  1203. onWindowUnload(win);
  1204. }
  1205. browserToTabIdMap = new WeakMap();
  1206. tabIdToBrowserMap.clear();
  1207. };
  1208. cleanupTasks.push(stop);
  1209. return {
  1210. browsers: getAllBrowsers,
  1211. browserFromTabId: browserFromTabId,
  1212. browserFromTarget: browserFromTarget,
  1213. currentBrowser: currentBrowser,
  1214. indexFromTarget: indexFromTarget,
  1215. removeTarget: removeTarget,
  1216. start: start,
  1217. tabFromBrowser: tabFromBrowser,
  1218. tabIdFromTarget: tabIdFromTarget
  1219. };
  1220. })();
  1221. /******************************************************************************/
  1222. vAPI.setIcon = function(tabId, iconId, badge) {
  1223. // If badge is undefined, then setIcon was called from the TabSelect event
  1224. var win;
  1225. if ( badge === undefined ) {
  1226. win = iconId;
  1227. } else {
  1228. win = winWatcher.getCurrentWindow();
  1229. }
  1230. var tabBrowser = getTabBrowser(win);
  1231. if ( tabBrowser === null ) {
  1232. return;
  1233. }
  1234. var curTabId = tabWatcher.tabIdFromTarget(tabBrowser.selectedTab);
  1235. var tb = vAPI.toolbarButton;
  1236. // from 'TabSelect' event
  1237. if ( tabId === undefined ) {
  1238. tabId = curTabId;
  1239. } else if ( badge !== undefined ) {
  1240. tb.tabs[tabId] = { badge: badge, img: iconId };
  1241. }
  1242. if ( tabId === curTabId ) {
  1243. tb.updateState(win, tabId);
  1244. }
  1245. };
  1246. /******************************************************************************/
  1247. vAPI.messaging = {
  1248. get globalMessageManager() {
  1249. return Cc['@mozilla.org/globalmessagemanager;1']
  1250. .getService(Ci.nsIMessageListenerManager);
  1251. },
  1252. frameScript: vAPI.getURL('frameScript.js'),
  1253. listeners: {},
  1254. defaultHandler: null,
  1255. NOOPFUNC: function(){},
  1256. UNHANDLED: 'vAPI.messaging.notHandled'
  1257. };
  1258. /******************************************************************************/
  1259. vAPI.messaging.listen = function(listenerName, callback) {
  1260. this.listeners[listenerName] = callback;
  1261. };
  1262. /******************************************************************************/
  1263. vAPI.messaging.onMessage = function({target, data}) {
  1264. var messageManager = target.messageManager;
  1265. if ( !messageManager ) {
  1266. // Message came from a popup, and its message manager is not usable.
  1267. // So instead we broadcast to the parent window.
  1268. messageManager = getOwnerWindow(
  1269. target.webNavigation.QueryInterface(Ci.nsIDocShell).chromeEventHandler
  1270. ).messageManager;
  1271. }
  1272. var channelNameRaw = data.channelName;
  1273. var pos = channelNameRaw.indexOf('|');
  1274. var channelName = channelNameRaw.slice(pos + 1);
  1275. var callback = vAPI.messaging.NOOPFUNC;
  1276. if ( data.requestId !== undefined ) {
  1277. callback = CallbackWrapper.factory(
  1278. messageManager,
  1279. channelName,
  1280. channelNameRaw.slice(0, pos),
  1281. data.requestId
  1282. ).callback;
  1283. }
  1284. var sender = {
  1285. tab: {
  1286. id: tabWatcher.tabIdFromTarget(target)
  1287. }
  1288. };
  1289. // Specific handler
  1290. var r = vAPI.messaging.UNHANDLED;
  1291. var listener = vAPI.messaging.listeners[channelName];
  1292. if ( typeof listener === 'function' ) {
  1293. r = listener(data.msg, sender, callback);
  1294. }
  1295. if ( r !== vAPI.messaging.UNHANDLED ) {
  1296. return;
  1297. }
  1298. // Default handler
  1299. r = vAPI.messaging.defaultHandler(data.msg, sender, callback);
  1300. if ( r !== vAPI.messaging.UNHANDLED ) {
  1301. return;
  1302. }
  1303. console.error('eMatrix> messaging > unknown request: %o', data);
  1304. // Unhandled:
  1305. // Need to callback anyways in case caller expected an answer, or
  1306. // else there is a memory leak on caller's side
  1307. callback();
  1308. };
  1309. /******************************************************************************/
  1310. vAPI.messaging.setup = function(defaultHandler) {
  1311. // Already setup?
  1312. if ( this.defaultHandler !== null ) {
  1313. return;
  1314. }
  1315. if ( typeof defaultHandler !== 'function' ) {
  1316. defaultHandler = function(){ return vAPI.messaging.UNHANDLED; };
  1317. }
  1318. this.defaultHandler = defaultHandler;
  1319. this.globalMessageManager.addMessageListener(
  1320. location.host + ':background',
  1321. this.onMessage
  1322. );
  1323. this.globalMessageManager.loadFrameScript(this.frameScript, true);
  1324. cleanupTasks.push(function() {
  1325. var gmm = vAPI.messaging.globalMessageManager;
  1326. gmm.removeDelayedFrameScript(vAPI.messaging.frameScript);
  1327. gmm.removeMessageListener(
  1328. location.host + ':background',
  1329. vAPI.messaging.onMessage
  1330. );
  1331. });
  1332. };
  1333. /******************************************************************************/
  1334. vAPI.messaging.broadcast = function(message) {
  1335. this.globalMessageManager.broadcastAsyncMessage(
  1336. location.host + ':broadcast',
  1337. JSON.stringify({broadcast: true, msg: message})
  1338. );
  1339. };
  1340. /******************************************************************************/
  1341. // This allows to avoid creating a closure for every single message which
  1342. // expects an answer. Having a closure created each time a message is processed
  1343. // has been always bothering me. Another benefit of the implementation here
  1344. // is to reuse the callback proxy object, so less memory churning.
  1345. //
  1346. // https://developers.google.com/speed/articles/optimizing-javascript
  1347. // "Creating a closure is significantly slower then creating an inner
  1348. // function without a closure, and much slower than reusing a static
  1349. // function"
  1350. //
  1351. // http://hacksoflife.blogspot.ca/2015/01/the-four-horsemen-of-performance.html
  1352. // "the dreaded 'uniformly slow code' case where every function takes 1%
  1353. // of CPU and you have to make one hundred separate performance optimizations
  1354. // to improve performance at all"
  1355. //
  1356. // http://jsperf.com/closure-no-closure/2
  1357. var CallbackWrapper = function(messageManager, channelName, listenerId, requestId) {
  1358. this.callback = this.proxy.bind(this); // bind once
  1359. this.init(messageManager, channelName, listenerId, requestId);
  1360. };
  1361. CallbackWrapper.junkyard = [];
  1362. CallbackWrapper.factory = function(messageManager, channelName, listenerId, requestId) {
  1363. var wrapper = CallbackWrapper.junkyard.pop();
  1364. if ( wrapper ) {
  1365. wrapper.init(messageManager, channelName, listenerId, requestId);
  1366. return wrapper;
  1367. }
  1368. return new CallbackWrapper(messageManager, channelName, listenerId, requestId);
  1369. };
  1370. CallbackWrapper.prototype.init = function(messageManager, channelName, listenerId, requestId) {
  1371. this.messageManager = messageManager;
  1372. this.channelName = channelName;
  1373. this.listenerId = listenerId;
  1374. this.requestId = requestId;
  1375. };
  1376. CallbackWrapper.prototype.proxy = function(response) {
  1377. var message = JSON.stringify({
  1378. requestId: this.requestId,
  1379. channelName: this.channelName,
  1380. msg: response !== undefined ? response : null
  1381. });
  1382. if ( this.messageManager.sendAsyncMessage ) {
  1383. this.messageManager.sendAsyncMessage(this.listenerId, message);
  1384. } else {
  1385. this.messageManager.broadcastAsyncMessage(this.listenerId, message);
  1386. }
  1387. // Mark for reuse
  1388. this.messageManager =
  1389. this.channelName =
  1390. this.requestId =
  1391. this.listenerId = null;
  1392. CallbackWrapper.junkyard.push(this);
  1393. };
  1394. /******************************************************************************/
  1395. var httpRequestHeadersFactory = function(channel) {
  1396. var entry = httpRequestHeadersFactory.junkyard.pop();
  1397. if ( entry ) {
  1398. return entry.init(channel);
  1399. }
  1400. return new HTTPRequestHeaders(channel);
  1401. };
  1402. httpRequestHeadersFactory.junkyard = [];
  1403. var HTTPRequestHeaders = function(channel) {
  1404. this.init(channel);
  1405. };
  1406. HTTPRequestHeaders.prototype.init = function(channel) {
  1407. this.channel = channel;
  1408. this.headers = new Array();
  1409. this.originalHeaderNames = new Array();
  1410. channel.visitRequestHeaders({visitHeader: function(name, value) {
  1411. this.headers.push({name: name, value: value});
  1412. this.originalHeaderNames.push(name);
  1413. }.bind(this)});
  1414. return this;
  1415. };
  1416. HTTPRequestHeaders.prototype.dispose = function() {
  1417. this.channel = null;
  1418. this.headers = null;
  1419. this.originalHeaderNames = null;
  1420. httpRequestHeadersFactory.junkyard.push(this);
  1421. };
  1422. HTTPRequestHeaders.prototype.update = function() {
  1423. var newHeaderNames = new Set();
  1424. for ( var header of this.headers ) {
  1425. this.setHeader(header.name, header.value, true);
  1426. newHeaderNames.add(header.name);
  1427. }
  1428. //Clear any headers that were removed
  1429. for ( var name of this.originalHeaderNames ) {
  1430. if ( !newHeaderNames.has(name) ) {
  1431. this.channel.setRequestHeader(name, '', false);
  1432. }
  1433. }
  1434. }
  1435. HTTPRequestHeaders.prototype.getHeader = function(name) {
  1436. try {
  1437. return this.channel.getRequestHeader(name);
  1438. } catch (e) {
  1439. }
  1440. return '';
  1441. };
  1442. HTTPRequestHeaders.prototype.setHeader = function(name, newValue, create) {
  1443. var oldValue = this.getHeader(name);
  1444. if ( newValue === oldValue ) {
  1445. return false;
  1446. }
  1447. if ( oldValue === '' && create !== true ) {
  1448. return false;
  1449. }
  1450. this.channel.setRequestHeader(name, newValue, false);
  1451. return true;
  1452. };
  1453. /******************************************************************************/
  1454. var httpObserver = {
  1455. classDescription: 'net-channel-event-sinks for ' + location.host,
  1456. classID: Components.ID('{5d2e2797-6d68-42e2-8aeb-81ce6ba16b95}'),
  1457. contractID: '@' + location.host + '/net-channel-event-sinks;1',
  1458. REQDATAKEY: location.host + 'reqdata',
  1459. ABORT: Components.results.NS_BINDING_ABORTED,
  1460. ACCEPT: Components.results.NS_SUCCEEDED,
  1461. // Request types:
  1462. // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIContentPolicy#Constants
  1463. frameTypeMap: {
  1464. 6: 'main_frame',
  1465. 7: 'sub_frame'
  1466. },
  1467. typeMap: {
  1468. 1: 'other',
  1469. 2: 'script',
  1470. 3: 'image',
  1471. 4: 'stylesheet',
  1472. 5: 'object',
  1473. 6: 'main_frame',
  1474. 7: 'sub_frame',
  1475. 9: 'xbl',
  1476. 10: 'ping',
  1477. 11: 'xmlhttprequest',
  1478. 12: 'object',
  1479. 13: 'xml_dtd',
  1480. 14: 'font',
  1481. 15: 'media',
  1482. 16: 'websocket',
  1483. 17: 'csp_report',
  1484. 18: 'xslt',
  1485. 19: 'beacon',
  1486. 20: 'xmlhttprequest',
  1487. 21: 'imageset',
  1488. 22: 'web_manifest'
  1489. },
  1490. mimeTypeMap: {
  1491. 'audio': 15,
  1492. 'video': 15
  1493. },
  1494. get componentRegistrar() {
  1495. return Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
  1496. },
  1497. get categoryManager() {
  1498. return Cc['@mozilla.org/categorymanager;1']
  1499. .getService(Ci.nsICategoryManager);
  1500. },
  1501. QueryInterface: (function() {
  1502. var {XPCOMUtils} = Cu.import('resource://gre/modules/XPCOMUtils.jsm', null);
  1503. return XPCOMUtils.generateQI([
  1504. Ci.nsIFactory,
  1505. Ci.nsIObserver,
  1506. Ci.nsIChannelEventSink,
  1507. Ci.nsISupportsWeakReference
  1508. ]);
  1509. })(),
  1510. createInstance: function(outer, iid) {
  1511. if ( outer ) {
  1512. throw Components.results.NS_ERROR_NO_AGGREGATION;
  1513. }
  1514. return this.QueryInterface(iid);
  1515. },
  1516. register: function() {
  1517. this.pendingRingBufferInit();
  1518. // https://developer.mozilla.org/en/docs/Observer_Notifications#HTTP_requests
  1519. Services.obs.addObserver(this, 'http-on-modify-request', true);
  1520. Services.obs.addObserver(this, 'http-on-examine-response', true);
  1521. Services.obs.addObserver(this, 'http-on-examine-cached-response', true);
  1522. // Guard against stale instances not having been unregistered
  1523. if ( this.componentRegistrar.isCIDRegistered(this.classID) ) {
  1524. try {
  1525. this.componentRegistrar.unregisterFactory(this.classID, Components.manager.getClassObject(this.classID, Ci.nsIFactory));
  1526. } catch (ex) {
  1527. console.error('eMatrix> httpObserver > unable to unregister stale instance: ', ex);
  1528. }
  1529. }
  1530. this.componentRegistrar.registerFactory(
  1531. this.classID,
  1532. this.classDescription,
  1533. this.contractID,
  1534. this
  1535. );
  1536. this.categoryManager.addCategoryEntry(
  1537. 'net-channel-event-sinks',
  1538. this.contractID,
  1539. this.contractID,
  1540. false,
  1541. true
  1542. );
  1543. },
  1544. unregister: function() {
  1545. Services.obs.removeObserver(this, 'http-on-modify-request');
  1546. Services.obs.removeObserver(this, 'http-on-examine-response');
  1547. Services.obs.removeObserver(this, 'http-on-examine-cached-response');
  1548. this.componentRegistrar.unregisterFactory(this.classID, this);
  1549. this.categoryManager.deleteCategoryEntry(
  1550. 'net-channel-event-sinks',
  1551. this.contractID,
  1552. false
  1553. );
  1554. },
  1555. PendingRequest: function() {
  1556. this.rawType = 0;
  1557. this.tabId = 0;
  1558. this._key = ''; // key is url, from URI.spec
  1559. },
  1560. // If all work fine, this map should not grow indefinitely. It can have
  1561. // stale items in it, but these will be taken care of when entries in
  1562. // the ring buffer are overwritten.
  1563. pendingURLToIndex: new Map(),
  1564. pendingWritePointer: 0,
  1565. pendingRingBuffer: new Array(256),
  1566. pendingRingBufferInit: function() {
  1567. // Use and reuse pre-allocated PendingRequest objects = less memory
  1568. // churning.
  1569. var i = this.pendingRingBuffer.length;
  1570. while ( i-- ) {
  1571. this.pendingRingBuffer[i] = new this.PendingRequest();
  1572. }
  1573. },
  1574. // Pending request ring buffer:
  1575. // +-------+-------+-------+-------+-------+-------+-------
  1576. // |0 |1 |2 |3 |4 |5 |...
  1577. // +-------+-------+-------+-------+-------+-------+-------
  1578. //
  1579. // URL to ring buffer index map:
  1580. // { k = URL, s = ring buffer indices }
  1581. //
  1582. // s is a string which character codes map to ring buffer indices -- for
  1583. // when the same URL is received multiple times by shouldLoadListener()
  1584. // before the existing one is serviced by the network request observer.
  1585. // I believe the use of a string in lieu of an array reduces memory
  1586. // churning.
  1587. createPendingRequest: function(url) {
  1588. var bucket;
  1589. var i = this.pendingWritePointer;
  1590. this.pendingWritePointer = i + 1 & 255;
  1591. var preq = this.pendingRingBuffer[i];
  1592. var si = String.fromCharCode(i);
  1593. // Cleanup unserviced pending request
  1594. if ( preq._key !== '' ) {
  1595. bucket = this.pendingURLToIndex.get(preq._key);
  1596. if ( bucket.length === 1 ) {
  1597. this.pendingURLToIndex.delete(preq._key);
  1598. } else {
  1599. var pos = bucket.indexOf(si);
  1600. this.pendingURLToIndex.set(preq._key, bucket.slice(0, pos) + bucket.slice(pos + 1));
  1601. }
  1602. }
  1603. bucket = this.pendingURLToIndex.get(url);
  1604. this.pendingURLToIndex.set(url, bucket === undefined ? si : bucket + si);
  1605. preq._key = url;
  1606. return preq;
  1607. },
  1608. lookupPendingRequest: function(url) {
  1609. var bucket = this.pendingURLToIndex.get(url);
  1610. if ( bucket === undefined ) {
  1611. return null;
  1612. }
  1613. var i = bucket.charCodeAt(0);
  1614. if ( bucket.length === 1 ) {
  1615. this.pendingURLToIndex.delete(url);
  1616. } else {
  1617. this.pendingURLToIndex.set(url, bucket.slice(1));
  1618. }
  1619. var preq = this.pendingRingBuffer[i];
  1620. preq._key = ''; // mark as "serviced"
  1621. return preq;
  1622. },
  1623. handleRequest: function(channel, URI, tabId, rawType) {
  1624. var type = this.typeMap[rawType] || 'other';
  1625. var onBeforeRequest = vAPI.net.onBeforeRequest;
  1626. if ( onBeforeRequest.types === null || onBeforeRequest.types.has(type) ) {
  1627. var result = onBeforeRequest.callback({
  1628. parentFrameId: type === 'main_frame' ? -1 : 0,
  1629. tabId: tabId,
  1630. type: type,
  1631. url: URI.asciiSpec
  1632. });
  1633. if ( typeof result === 'object' ) {
  1634. channel.cancel(this.ABORT);
  1635. return true;
  1636. }
  1637. }
  1638. var onBeforeSendHeaders = vAPI.net.onBeforeSendHeaders;
  1639. if ( onBeforeSendHeaders.types === null || onBeforeSendHeaders.types.has(type) ) {
  1640. var requestHeaders = httpRequestHeadersFactory(channel);
  1641. var newHeaders = onBeforeSendHeaders.callback({
  1642. parentFrameId: type === 'main_frame' ? -1 : 0,
  1643. requestHeaders: requestHeaders.headers,
  1644. tabId: tabId,
  1645. type: type,
  1646. url: URI.asciiSpec,
  1647. method: channel.requestMethod
  1648. });
  1649. if ( newHeaders ) {
  1650. requestHeaders.update();
  1651. }
  1652. requestHeaders.dispose();
  1653. }
  1654. return false;
  1655. },
  1656. channelDataFromChannel: function(channel) {
  1657. if ( channel instanceof Ci.nsIWritablePropertyBag ) {
  1658. try {
  1659. return channel.getProperty(this.REQDATAKEY) || null;
  1660. } catch (ex) {
  1661. }
  1662. }
  1663. return null;
  1664. },
  1665. // https://github.com/gorhill/uMatrix/issues/165
  1666. // https://developer.mozilla.org/en-US/Firefox/Releases/3.5/Updating_extensions#Getting_a_load_context_from_a_request
  1667. // Not sure `ematrix:shouldLoad` is still needed, eMatrix does not
  1668. // care about embedded frames topography.
  1669. // Also:
  1670. // https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Limitations_of_chrome_scripts
  1671. tabIdFromChannel: function(channel) {
  1672. var lc;
  1673. try {
  1674. lc = channel.notificationCallbacks.getInterface(Ci.nsILoadContext);
  1675. } catch(ex) {
  1676. }
  1677. if ( !lc ) {
  1678. try {
  1679. lc = channel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
  1680. } catch(ex) {
  1681. }
  1682. if ( !lc ) {
  1683. return vAPI.noTabId;
  1684. }
  1685. }
  1686. if ( lc.topFrameElement ) {
  1687. return tabWatcher.tabIdFromTarget(lc.topFrameElement);
  1688. }
  1689. var win;
  1690. try {
  1691. win = lc.associatedWindow;
  1692. } catch (ex) { }
  1693. if ( !win ) {
  1694. return vAPI.noTabId;
  1695. }
  1696. if ( win.top ) {
  1697. win = win.top;
  1698. }
  1699. var tabBrowser;
  1700. try {
  1701. tabBrowser = getTabBrowser(
  1702. win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
  1703. .QueryInterface(Ci.nsIDocShell).rootTreeItem
  1704. .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow)
  1705. );
  1706. } catch (ex) { }
  1707. if ( !tabBrowser ) {
  1708. return vAPI.noTabId;
  1709. }
  1710. if ( tabBrowser.getBrowserForContentWindow ) {
  1711. return tabWatcher.tabIdFromTarget(tabBrowser.getBrowserForContentWindow(win));
  1712. }
  1713. // Falling back onto _getTabForContentWindow to ensure older versions
  1714. // of Firefox work well.
  1715. return tabBrowser._getTabForContentWindow ?
  1716. tabWatcher.tabIdFromTarget(tabBrowser._getTabForContentWindow(win)) :
  1717. vAPI.noTabId;
  1718. },
  1719. rawtypeFromContentType: function(channel) {
  1720. var mime = channel.contentType;
  1721. if ( !mime ) {
  1722. return 0;
  1723. }
  1724. var pos = mime.indexOf('/');
  1725. if ( pos === -1 ) {
  1726. pos = mime.length;
  1727. }
  1728. return this.mimeTypeMap[mime.slice(0, pos)] || 0;
  1729. },
  1730. observe: function(channel, topic) {
  1731. if ( channel instanceof Ci.nsIHttpChannel === false ) {
  1732. return;
  1733. }
  1734. var URI = channel.URI;
  1735. var channelData = this.channelDataFromChannel(channel);
  1736. if ( topic.lastIndexOf('http-on-examine-', 0) === 0 ) {
  1737. if ( channelData === null ) {
  1738. return;
  1739. }
  1740. var type = this.frameTypeMap[channelData[1]];
  1741. if ( !type ) {
  1742. return;
  1743. }
  1744. //topic = ['Content-Security-Policy', 'Content-Security-Policy-Report-Only'];
  1745. // Can send empty responseHeaders as these headers are only added to and then merged.
  1746. // TODO: Find better place for this, needs to be set before onHeadersReceived.callback.
  1747. // Web workers not blocked in Pale Moon as child-src currently unavailable, see:
  1748. // https://github.com/MoonchildProductions/Pale-Moon/issues/949
  1749. if ( ηMatrix.cspNoWorker === undefined ) {
  1750. ηMatrix.cspNoWorker = "child-src 'none'; frame-src data: blob: *; report-uri about:blank";
  1751. }
  1752. var result = vAPI.net.onHeadersReceived.callback({
  1753. parentFrameId: type === 'main_frame' ? -1 : 0,
  1754. responseHeaders: [],
  1755. tabId: channelData[0],
  1756. type: type,
  1757. url: URI.asciiSpec
  1758. });
  1759. if ( result ) {
  1760. for ( let header of result.responseHeaders ) {
  1761. channel.setResponseHeader(
  1762. header.name,
  1763. header.value,
  1764. true
  1765. );
  1766. }
  1767. }
  1768. return;
  1769. }
  1770. // http-on-modify-request
  1771. // The channel was previously serviced.
  1772. if ( channelData !== null ) {
  1773. this.handleRequest(channel, URI, channelData[0], channelData[1]);
  1774. return;
  1775. }
  1776. // The channel was never serviced.
  1777. var tabId;
  1778. var pendingRequest = this.lookupPendingRequest(URI.asciiSpec);
  1779. var rawType = 1;
  1780. var loadInfo = channel.loadInfo;
  1781. // https://github.com/gorhill/uMatrix/issues/390#issuecomment-155717004
  1782. if ( loadInfo ) {
  1783. rawType = loadInfo.externalContentPolicyType !== undefined ?
  1784. loadInfo.externalContentPolicyType :
  1785. loadInfo.contentPolicyType;
  1786. if ( !rawType ) {
  1787. rawType = 1;
  1788. }
  1789. }
  1790. if ( pendingRequest !== null ) {
  1791. tabId = pendingRequest.tabId;
  1792. // https://github.com/gorhill/uBlock/issues/654
  1793. // Use the request type from the HTTP observer point of view.
  1794. if ( rawType !== 1 ) {
  1795. pendingRequest.rawType = rawType;
  1796. } else {
  1797. rawType = pendingRequest.rawType;
  1798. }
  1799. } else {
  1800. tabId = this.tabIdFromChannel(channel);
  1801. }
  1802. if ( this.handleRequest(channel, URI, tabId, rawType) ) {
  1803. return;
  1804. }
  1805. if ( channel instanceof Ci.nsIWritablePropertyBag === false ) {
  1806. return;
  1807. }
  1808. // Carry data for behind-the-scene redirects
  1809. channel.setProperty(this.REQDATAKEY, [tabId, rawType]);
  1810. },
  1811. // contentPolicy.shouldLoad doesn't detect redirects, this needs to be used
  1812. asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) {
  1813. // If error thrown, the redirect will fail
  1814. try {
  1815. var URI = newChannel.URI;
  1816. if ( !URI.schemeIs('http') && !URI.schemeIs('https') ) {
  1817. return;
  1818. }
  1819. if ( newChannel instanceof Ci.nsIWritablePropertyBag === false ) {
  1820. return;
  1821. }
  1822. var channelData = this.channelDataFromChannel(oldChannel);
  1823. if ( channelData === null ) {
  1824. return;
  1825. }
  1826. // Carry the data on in case of multiple redirects
  1827. newChannel.setProperty(this.REQDATAKEY, channelData);
  1828. } catch (ex) {
  1829. // console.error(ex);
  1830. } finally {
  1831. callback.onRedirectVerifyCallback(this.ACCEPT);
  1832. }
  1833. }
  1834. };
  1835. /******************************************************************************/
  1836. vAPI.net = {};
  1837. /******************************************************************************/
  1838. vAPI.net.registerListeners = function() {
  1839. this.onBeforeRequest.types = this.onBeforeRequest.types ?
  1840. new Set(this.onBeforeRequest.types) :
  1841. null;
  1842. this.onBeforeSendHeaders.types = this.onBeforeSendHeaders.types ?
  1843. new Set(this.onBeforeSendHeaders.types) :
  1844. null;
  1845. var shouldLoadListenerMessageName = location.host + ':shouldLoad';
  1846. var shouldLoadListener = function(e) {
  1847. var details = e.data;
  1848. var pendingReq = httpObserver.createPendingRequest(details.url);
  1849. pendingReq.rawType = details.rawType;
  1850. pendingReq.tabId = tabWatcher.tabIdFromTarget(e.target);
  1851. };
  1852. // https://github.com/gorhill/uMatrix/issues/200
  1853. // We need this only for Firefox 34 and less: the tab id is derived from
  1854. // the origin of the message.
  1855. if ( !vAPI.modernFirefox ) {
  1856. vAPI.messaging.globalMessageManager.addMessageListener(
  1857. shouldLoadListenerMessageName,
  1858. shouldLoadListener
  1859. );
  1860. }
  1861. httpObserver.register();
  1862. cleanupTasks.push(function() {
  1863. if ( !vAPI.modernFirefox ) {
  1864. vAPI.messaging.globalMessageManager.removeMessageListener(
  1865. shouldLoadListenerMessageName,
  1866. shouldLoadListener
  1867. );
  1868. }
  1869. httpObserver.unregister();
  1870. });
  1871. };
  1872. /******************************************************************************/
  1873. /******************************************************************************/
  1874. vAPI.toolbarButton = {
  1875. id: location.host + '-button',
  1876. type: 'view',
  1877. viewId: location.host + '-panel',
  1878. label: vAPI.app.name,
  1879. tooltiptext: vAPI.app.name,
  1880. tabs: {/*tabId: {badge: 0, img: boolean}*/},
  1881. init: null,
  1882. codePath: ''
  1883. };
  1884. /******************************************************************************/
  1885. // Non-Fennec: common code paths.
  1886. (function() {
  1887. if ( vAPI.fennec ) {
  1888. return;
  1889. }
  1890. var tbb = vAPI.toolbarButton;
  1891. var popupCommittedWidth = 0;
  1892. var popupCommittedHeight = 0;
  1893. tbb.onViewShowing = function({target}) {
  1894. popupCommittedWidth = popupCommittedHeight = 0;
  1895. target.firstChild.setAttribute('src', vAPI.getURL('popup.html'));
  1896. };
  1897. tbb.onViewHiding = function({target}) {
  1898. target.parentNode.style.maxWidth = '';
  1899. target.firstChild.setAttribute('src', 'about:blank');
  1900. };
  1901. tbb.updateState = function(win, tabId) {
  1902. var button = win.document.getElementById(this.id);
  1903. if ( !button ) {
  1904. return;
  1905. }
  1906. var icon = this.tabs[tabId];
  1907. button.setAttribute('badge', icon && icon.badge || '');
  1908. button.classList.toggle('off', !icon || !icon.img);
  1909. var iconId = icon && icon.img ? icon.img : 'off';
  1910. icon = 'url(' + vAPI.getURL('img/browsericons/icon19-' + iconId + '.png') + ')';
  1911. button.style.listStyleImage = icon;
  1912. };
  1913. tbb.populatePanel = function(doc, panel) {
  1914. panel.setAttribute('id', this.viewId);
  1915. var iframe = doc.createElement('iframe');
  1916. iframe.setAttribute('type', 'content');
  1917. panel.appendChild(iframe);
  1918. var toPx = function(pixels) {
  1919. return pixels.toString() + 'px';
  1920. };
  1921. var scrollBarWidth = 0;
  1922. var resizeTimer = null;
  1923. var resizePopupDelayed = function(attempts) {
  1924. if ( resizeTimer !== null ) {
  1925. return false;
  1926. }
  1927. // Sanity check
  1928. attempts = (attempts || 0) + 1;
  1929. if ( attempts > 1/*000*/ ) {
  1930. //console.error('eMatrix> resizePopupDelayed: giving up after too many attempts');
  1931. return false;
  1932. }
  1933. resizeTimer = vAPI.setTimeout(resizePopup, 10, attempts);
  1934. return true;
  1935. };
  1936. var resizePopup = function(attempts) {
  1937. resizeTimer = null;
  1938. panel.parentNode.style.maxWidth = 'none';
  1939. var body = iframe.contentDocument.body;
  1940. // https://github.com/gorhill/uMatrix/issues/301
  1941. // Don't resize if committed size did not change.
  1942. if (
  1943. popupCommittedWidth === body.clientWidth &&
  1944. popupCommittedHeight === body.clientHeight
  1945. ) {
  1946. return;
  1947. }
  1948. // We set a limit for height
  1949. var height = Math.min(body.clientHeight, 600);
  1950. // https://github.com/chrisaljoudi/uBlock/issues/730
  1951. // Voodoo programming: this recipe works
  1952. panel.style.setProperty('height', toPx(height));
  1953. iframe.style.setProperty('height', toPx(height));
  1954. // Adjust width for presence/absence of vertical scroll bar which may
  1955. // have appeared as a result of last operation.
  1956. var contentWindow = iframe.contentWindow;
  1957. var width = body.clientWidth;
  1958. if ( contentWindow.scrollMaxY !== 0 ) {
  1959. width += scrollBarWidth;
  1960. }
  1961. panel.style.setProperty('width', toPx(width));
  1962. // scrollMaxX should always be zero once we know the scrollbar width
  1963. if ( contentWindow.scrollMaxX !== 0 ) {
  1964. scrollBarWidth = contentWindow.scrollMaxX;
  1965. width += scrollBarWidth;
  1966. panel.style.setProperty('width', toPx(width));
  1967. }
  1968. if ( iframe.clientHeight !== height || panel.clientWidth !== width ) {
  1969. if ( resizePopupDelayed(attempts) ) {
  1970. return;
  1971. }
  1972. // resizePopupDelayed won't be called again, so commit
  1973. // dimentsions.
  1974. }
  1975. popupCommittedWidth = body.clientWidth;
  1976. popupCommittedHeight = body.clientHeight;
  1977. };
  1978. var onResizeRequested = function() {
  1979. var body = iframe.contentDocument.body;
  1980. if ( body.getAttribute('data-resize-popup') !== 'true' ) {
  1981. return;
  1982. }
  1983. body.removeAttribute('data-resize-popup');
  1984. resizePopupDelayed();
  1985. };
  1986. var onPopupReady = function() {
  1987. var win = this.contentWindow;
  1988. if ( !win || win.location.host !== location.host ) {
  1989. return;
  1990. }
  1991. if ( typeof tbb.onBeforePopupReady === 'function' ) {
  1992. tbb.onBeforePopupReady.call(this);
  1993. }
  1994. resizePopupDelayed();
  1995. var body = win.document.body;
  1996. body.removeAttribute('data-resize-popup');
  1997. var mutationObserver = new win.MutationObserver(onResizeRequested);
  1998. mutationObserver.observe(body, {
  1999. attributes: true,
  2000. attributeFilter: [ 'data-resize-popup' ]
  2001. });
  2002. };
  2003. iframe.addEventListener('load', onPopupReady, true);
  2004. };
  2005. })();
  2006. /******************************************************************************/
  2007. (function() {
  2008. // Add toolbar button for not-Basilisk
  2009. if (Services.appinfo.ID === "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") {
  2010. return;
  2011. }
  2012. var tbb = vAPI.toolbarButton;
  2013. if ( tbb.init !== null ) {
  2014. return;
  2015. }
  2016. tbb.codePath = 'legacy';
  2017. tbb.viewId = tbb.id + '-panel';
  2018. var styleSheetUri = null;
  2019. var createToolbarButton = function(window) {
  2020. var document = window.document;
  2021. var toolbarButton = document.createElement('toolbarbutton');
  2022. toolbarButton.setAttribute('id', tbb.id);
  2023. // type = panel would be more accurate, but doesn't look as good
  2024. toolbarButton.setAttribute('type', 'menu');
  2025. toolbarButton.setAttribute('removable', 'true');
  2026. toolbarButton.setAttribute('class', 'toolbarbutton-1 chromeclass-toolbar-additional');
  2027. toolbarButton.setAttribute('label', tbb.label);
  2028. toolbarButton.setAttribute('tooltiptext', tbb.tooltiptext);
  2029. var toolbarButtonPanel = document.createElement('panel');
  2030. // NOTE: Setting level to parent breaks the popup for PaleMoon under
  2031. // linux (mouse pointer misaligned with content). For some reason.
  2032. // toolbarButtonPanel.setAttribute('level', 'parent');
  2033. tbb.populatePanel(document, toolbarButtonPanel);
  2034. toolbarButtonPanel.addEventListener('popupshowing', tbb.onViewShowing);
  2035. toolbarButtonPanel.addEventListener('popuphiding', tbb.onViewHiding);
  2036. toolbarButton.appendChild(toolbarButtonPanel);
  2037. toolbarButtonPanel.setAttribute('tooltiptext', '');
  2038. return toolbarButton;
  2039. };
  2040. var addLegacyToolbarButton = function(window) {
  2041. // eMatrix's stylesheet lazily added.
  2042. if ( styleSheetUri === null ) {
  2043. var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
  2044. .getService(Ci.nsIStyleSheetService);
  2045. styleSheetUri = Services.io.newURI(vAPI.getURL("css/legacy-toolbar-button.css"), null, null);
  2046. // Register global so it works in all windows, including palette
  2047. if ( !sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET) ) {
  2048. sss.loadAndRegisterSheet(styleSheetUri, sss.AUTHOR_SHEET);
  2049. }
  2050. }
  2051. var document = window.document;
  2052. // https://github.com/gorhill/uMatrix/issues/357
  2053. // Already installed?
  2054. if ( document.getElementById(tbb.id) !== null ) {
  2055. return;
  2056. }
  2057. var toolbox = document.getElementById('navigator-toolbox') ||
  2058. document.getElementById('mail-toolbox');
  2059. if ( toolbox === null ) {
  2060. return;
  2061. }
  2062. var toolbarButton = createToolbarButton(window);
  2063. // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/toolbarpalette
  2064. var palette = toolbox.palette;
  2065. if ( palette && palette.querySelector('#' + tbb.id) === null ) {
  2066. palette.appendChild(toolbarButton);
  2067. }
  2068. // Find the place to put the button.
  2069. // Pale Moon: `toolbox.externalToolbars` can be undefined. Seen while
  2070. // testing popup test number 3:
  2071. // http://raymondhill.net/ublock/popup.html
  2072. var toolbars = toolbox.externalToolbars ? toolbox.externalToolbars.slice() : [];
  2073. for ( var child of toolbox.children ) {
  2074. if ( child.localName === 'toolbar' ) {
  2075. toolbars.push(child);
  2076. }
  2077. }
  2078. for ( var toolbar of toolbars ) {
  2079. var currentsetString = toolbar.getAttribute('currentset');
  2080. if ( !currentsetString ) {
  2081. continue;
  2082. }
  2083. var currentset = currentsetString.split(/\s*,\s*/);
  2084. var index = currentset.indexOf(tbb.id);
  2085. if ( index === -1 ) {
  2086. continue;
  2087. }
  2088. // This can occur with Pale Moon:
  2089. // "TypeError: toolbar.insertItem is not a function"
  2090. if ( typeof toolbar.insertItem !== 'function' ) {
  2091. continue;
  2092. }
  2093. // Found our button on this toolbar - but where on it?
  2094. var before = null;
  2095. for ( var i = index + 1; i < currentset.length; i++ ) {
  2096. // The [id=...] notation doesn't work on
  2097. // space elements as they get a random ID each session
  2098. // (or something like that)
  2099. // https://gitlab.com/vannilla/ematrix/issues/5
  2100. // https://gitlab.com/vannilla/ematrix/issues/6
  2101. // Based on JustOff's snippet from the Pale Moon forum.
  2102. // It was reorganized because I find it more readable like this,
  2103. // but he did most of the work.
  2104. let space = /^(spring|spacer|separator)$/.exec(currentset[i]);
  2105. if (space !== null) {
  2106. let elems = toolbar.querySelectorAll('toolbar'+space[1]);
  2107. let count = currentset.slice(i-currentset.length)
  2108. .filter(function (x) {return x == space[1];})
  2109. .length;
  2110. before =
  2111. toolbar.querySelector('[id="'
  2112. + elems[elems.length-count].id
  2113. + '"]');
  2114. } else {
  2115. before = toolbar.querySelector('[id="'+currentset[i]+'"]');
  2116. }
  2117. if ( before !== null ) {
  2118. break;
  2119. }
  2120. }
  2121. // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Method/insertItem
  2122. toolbar.insertItem(tbb.id, before);
  2123. break;
  2124. }
  2125. // https://github.com/gorhill/uBlock/issues/763
  2126. // We are done if our toolbar button is already installed in one of the
  2127. // toolbar.
  2128. if ( palette !== null && toolbarButton.parentElement !== palette ) {
  2129. return;
  2130. }
  2131. // No button yet so give it a default location. If forcing the button,
  2132. // just put in in the palette rather than on any specific toolbar (who
  2133. // knows what toolbars will be available or visible!)
  2134. var navbar = document.getElementById('nav-bar');
  2135. if ( navbar !== null && !vAPI.localStorage.getBool('legacyToolbarButtonAdded') ) {
  2136. // https://github.com/gorhill/uBlock/issues/264
  2137. // Find a child customizable palette, if any.
  2138. navbar = navbar.querySelector('.customization-target') || navbar;
  2139. navbar.appendChild(toolbarButton);
  2140. navbar.setAttribute('currentset', navbar.currentSet);
  2141. document.persist(navbar.id, 'currentset');
  2142. vAPI.localStorage.setBool('legacyToolbarButtonAdded', 'true');
  2143. }
  2144. };
  2145. var canAddLegacyToolbarButton = function(window) {
  2146. var document = window.document;
  2147. if (
  2148. !document ||
  2149. document.readyState !== 'complete' ||
  2150. document.getElementById('nav-bar') === null
  2151. ) {
  2152. return false;
  2153. }
  2154. var toolbox = document.getElementById('navigator-toolbox') ||
  2155. document.getElementById('mail-toolbox');
  2156. return toolbox !== null && !!toolbox.palette;
  2157. };
  2158. var onPopupCloseRequested = function({target}) {
  2159. var document = target.ownerDocument;
  2160. if ( !document ) {
  2161. return;
  2162. }
  2163. var toolbarButtonPanel = document.getElementById(tbb.viewId);
  2164. if ( toolbarButtonPanel === null ) {
  2165. return;
  2166. }
  2167. // `hidePopup` reported as not existing while testing legacy button
  2168. // on FF 41.0.2.
  2169. // https://bugzilla.mozilla.org/show_bug.cgi?id=1151796
  2170. if ( typeof toolbarButtonPanel.hidePopup === 'function' ) {
  2171. toolbarButtonPanel.hidePopup();
  2172. }
  2173. };
  2174. var shutdown = function() {
  2175. for ( var win of winWatcher.getWindows() ) {
  2176. var toolbarButton = win.document.getElementById(tbb.id);
  2177. if ( toolbarButton ) {
  2178. toolbarButton.parentNode.removeChild(toolbarButton);
  2179. }
  2180. }
  2181. if ( styleSheetUri !== null ) {
  2182. var sss = Cc["@mozilla.org/content/style-sheet-service;1"]
  2183. .getService(Ci.nsIStyleSheetService);
  2184. if ( sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET) ) {
  2185. sss.unregisterSheet(styleSheetUri, sss.AUTHOR_SHEET);
  2186. }
  2187. styleSheetUri = null;
  2188. }
  2189. vAPI.messaging.globalMessageManager.removeMessageListener(
  2190. location.host + ':closePopup',
  2191. onPopupCloseRequested
  2192. );
  2193. };
  2194. tbb.attachToNewWindow = function(win) {
  2195. deferUntil(
  2196. canAddLegacyToolbarButton.bind(null, win),
  2197. addLegacyToolbarButton.bind(null, win)
  2198. );
  2199. };
  2200. tbb.init = function() {
  2201. vAPI.messaging.globalMessageManager.addMessageListener(
  2202. location.host + ':closePopup',
  2203. onPopupCloseRequested
  2204. );
  2205. cleanupTasks.push(shutdown);
  2206. };
  2207. })();
  2208. /******************************************************************************/
  2209. /*
  2210. (function() {
  2211. // Add toolbar button for Basilisk
  2212. if (Services.appinfo.ID !== "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") {
  2213. return;
  2214. }
  2215. var tbb = vAPI.toolbarButton;
  2216. if ( tbb.init !== null ) {
  2217. return;
  2218. }
  2219. if ( Services.vc.compare(Services.appinfo.version, '36.0') >= 0 ) {
  2220. return null;
  2221. }
  2222. var CustomizableUI = null;
  2223. try {
  2224. CustomizableUI = Cu.import('resource:///modules/CustomizableUI.jsm', null).CustomizableUI;
  2225. } catch (ex) {
  2226. }
  2227. if ( CustomizableUI === null ) {
  2228. return;
  2229. }
  2230. tbb.codePath = 'australis';
  2231. tbb.CustomizableUI = CustomizableUI;
  2232. tbb.defaultArea = CustomizableUI.AREA_NAVBAR;
  2233. var styleURI = null;
  2234. var onPopupCloseRequested = function({target}) {
  2235. if ( typeof tbb.closePopup === 'function' ) {
  2236. tbb.closePopup(target);
  2237. }
  2238. };
  2239. var shutdown = function() {
  2240. CustomizableUI.destroyWidget(tbb.id);
  2241. for ( var win of winWatcher.getWindows() ) {
  2242. var panel = win.document.getElementById(tbb.viewId);
  2243. panel.parentNode.removeChild(panel);
  2244. win.QueryInterface(Ci.nsIInterfaceRequestor)
  2245. .getInterface(Ci.nsIDOMWindowUtils)
  2246. .removeSheet(styleURI, 1);
  2247. }
  2248. vAPI.messaging.globalMessageManager.removeMessageListener(
  2249. location.host + ':closePopup',
  2250. onPopupCloseRequested
  2251. );
  2252. };
  2253. tbb.onBeforeCreated = function(doc) {
  2254. var panel = doc.createElement('panelview');
  2255. this.populatePanel(doc, panel);
  2256. doc.getElementById('PanelUI-multiView').appendChild(panel);
  2257. doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
  2258. .getInterface(Ci.nsIDOMWindowUtils)
  2259. .loadSheet(styleURI, 1);
  2260. };
  2261. tbb.onBeforePopupReady = function() {
  2262. // https://github.com/gorhill/uBlock/issues/83
  2263. // Add `portrait` class if width is constrained.
  2264. try {
  2265. this.contentDocument.body.classList.toggle(
  2266. 'portrait',
  2267. CustomizableUI.getWidget(tbb.id).areaType === CustomizableUI.TYPE_MENU_PANEL
  2268. );
  2269. } catch (ex) {
  2270. // noop
  2271. }
  2272. };
  2273. tbb.init = function() {
  2274. vAPI.messaging.globalMessageManager.addMessageListener(
  2275. location.host + ':closePopup',
  2276. onPopupCloseRequested
  2277. );
  2278. var style = [
  2279. '#' + this.id + '.off {',
  2280. 'list-style-image: url(',
  2281. vAPI.getURL('img/browsericons/icon19-off.png'),
  2282. ');',
  2283. '}',
  2284. '#' + this.id + ' {',
  2285. 'list-style-image: url(',
  2286. vAPI.getURL('img/browsericons/icon19.png'),
  2287. ');',
  2288. '}',
  2289. '#' + this.viewId + ', #' + this.viewId + ' > iframe {',
  2290. 'width: 160px;',
  2291. 'height: 290px;',
  2292. 'overflow: hidden !important;',
  2293. '}',
  2294. '#' + this.id + '[badge]:not([badge=""])::after {',
  2295. 'position: absolute;',
  2296. 'margin-left: -16px;',
  2297. 'margin-top: 3px;',
  2298. 'padding: 1px 2px;',
  2299. 'font-size: 9px;',
  2300. 'font-weight: bold;',
  2301. 'color: #fff;',
  2302. 'background: #000;',
  2303. 'content: attr(badge);',
  2304. '}'
  2305. ];
  2306. styleURI = Services.io.newURI(
  2307. 'data:text/css,' + encodeURIComponent(style.join('')),
  2308. null,
  2309. null
  2310. );
  2311. this.closePopup = function(tabBrowser) {
  2312. CustomizableUI.hidePanelForNode(
  2313. tabBrowser.ownerDocument.getElementById(this.viewId)
  2314. );
  2315. };
  2316. CustomizableUI.createWidget(this);
  2317. cleanupTasks.push(shutdown);
  2318. };
  2319. })();
  2320. */
  2321. /******************************************************************************/
  2322. (function() {
  2323. // It appears that this branch actually works on the latest
  2324. // Basilisk. Maybe we can simply use this one directly instead of
  2325. // making checks like it's done now.
  2326. // It was decided to use this branch unconditionally. It's still
  2327. // experimental though.
  2328. // Add toolbar button for Basilisk
  2329. if (Services.appinfo.ID !== "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") {
  2330. return;
  2331. }
  2332. var tbb = vAPI.toolbarButton;
  2333. if ( tbb.init !== null ) {
  2334. return;
  2335. }
  2336. // if ( Services.vc.compare(Services.appinfo.version, '36.0') < 0 ) {
  2337. // return null;
  2338. // }
  2339. var CustomizableUI = null;
  2340. try {
  2341. CustomizableUI = Cu.import('resource:///modules/CustomizableUI.jsm', null).CustomizableUI;
  2342. } catch (ex) {
  2343. }
  2344. if ( CustomizableUI === null ) {
  2345. return null;
  2346. }
  2347. tbb.codePath = 'australis';
  2348. tbb.CustomizableUI = CustomizableUI;
  2349. tbb.defaultArea = CustomizableUI.AREA_NAVBAR;
  2350. var CUIEvents = {};
  2351. var badgeCSSRules = [
  2352. 'background: #000',
  2353. 'color: #fff'
  2354. ].join(';');
  2355. var updateBadgeStyle = function() {
  2356. for ( var win of winWatcher.getWindows() ) {
  2357. var button = win.document.getElementById(tbb.id);
  2358. if ( button === null ) {
  2359. continue;
  2360. }
  2361. var badge = button.ownerDocument.getAnonymousElementByAttribute(
  2362. button,
  2363. 'class',
  2364. 'toolbarbutton-badge'
  2365. );
  2366. if ( !badge ) {
  2367. continue;
  2368. }
  2369. badge.style.cssText = badgeCSSRules;
  2370. }
  2371. };
  2372. var updateBadge = function() {
  2373. var wId = tbb.id;
  2374. var buttonInPanel = CustomizableUI.getWidget(wId).areaType === CustomizableUI.TYPE_MENU_PANEL;
  2375. for ( var win of winWatcher.getWindows() ) {
  2376. var button = win.document.getElementById(wId);
  2377. if ( button === null ) {
  2378. continue;
  2379. }
  2380. if ( buttonInPanel ) {
  2381. button.classList.remove('badged-button');
  2382. continue;
  2383. }
  2384. button.classList.add('badged-button');
  2385. }
  2386. if ( buttonInPanel ) {
  2387. return;
  2388. }
  2389. // Anonymous elements need some time to be reachable
  2390. vAPI.setTimeout(updateBadgeStyle, 250);
  2391. }.bind(CUIEvents);
  2392. // https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/CustomizableUI.jsm#Listeners
  2393. CUIEvents.onCustomizeEnd = updateBadge;
  2394. CUIEvents.onWidgetAdded = updateBadge;
  2395. CUIEvents.onWidgetUnderflow = updateBadge;
  2396. var onPopupCloseRequested = function({target}) {
  2397. if ( typeof tbb.closePopup === 'function' ) {
  2398. tbb.closePopup(target);
  2399. }
  2400. };
  2401. var shutdown = function() {
  2402. for ( var win of winWatcher.getWindows() ) {
  2403. var panel = win.document.getElementById(tbb.viewId);
  2404. if ( panel !== null && panel.parentNode !== null ) {
  2405. panel.parentNode.removeChild(panel);
  2406. }
  2407. win.QueryInterface(Ci.nsIInterfaceRequestor)
  2408. .getInterface(Ci.nsIDOMWindowUtils)
  2409. .removeSheet(styleURI, 1);
  2410. }
  2411. CustomizableUI.removeListener(CUIEvents);
  2412. CustomizableUI.destroyWidget(tbb.id);
  2413. vAPI.messaging.globalMessageManager.removeMessageListener(
  2414. location.host + ':closePopup',
  2415. onPopupCloseRequested
  2416. );
  2417. };
  2418. var styleURI = null;
  2419. tbb.onBeforeCreated = function(doc) {
  2420. var panel = doc.createElement('panelview');
  2421. this.populatePanel(doc, panel);
  2422. doc.getElementById('PanelUI-multiView').appendChild(panel);
  2423. doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
  2424. .getInterface(Ci.nsIDOMWindowUtils)
  2425. .loadSheet(styleURI, 1);
  2426. };
  2427. tbb.onCreated = function(button) {
  2428. button.setAttribute('badge', '');
  2429. vAPI.setTimeout(updateBadge, 250);
  2430. };
  2431. tbb.onBeforePopupReady = function() {
  2432. // https://github.com/gorhill/uBlock/issues/83
  2433. // Add `portrait` class if width is constrained.
  2434. try {
  2435. this.contentDocument.body.classList.toggle(
  2436. 'portrait',
  2437. CustomizableUI.getWidget(tbb.id).areaType === CustomizableUI.TYPE_MENU_PANEL
  2438. );
  2439. } catch (ex) {
  2440. /* noop */
  2441. }
  2442. };
  2443. tbb.closePopup = function(tabBrowser) {
  2444. CustomizableUI.hidePanelForNode(
  2445. tabBrowser.ownerDocument.getElementById(tbb.viewId)
  2446. );
  2447. };
  2448. tbb.init = function() {
  2449. vAPI.messaging.globalMessageManager.addMessageListener(
  2450. location.host + ':closePopup',
  2451. onPopupCloseRequested
  2452. );
  2453. CustomizableUI.addListener(CUIEvents);
  2454. var style = [
  2455. '#' + this.id + '.off {',
  2456. 'list-style-image: url(',
  2457. vAPI.getURL('img/browsericons/icon19-off.png'),
  2458. ');',
  2459. '}',
  2460. '#' + this.id + ' {',
  2461. 'list-style-image: url(',
  2462. vAPI.getURL('img/browsericons/icon19-19.png'),
  2463. ');',
  2464. '}',
  2465. '#' + this.viewId + ', #' + this.viewId + ' > iframe {',
  2466. 'height: 290px;',
  2467. 'max-width: none !important;',
  2468. 'min-width: 0 !important;',
  2469. 'overflow: hidden !important;',
  2470. 'padding: 0 !important;',
  2471. 'width: 160px;',
  2472. '}'
  2473. ];
  2474. styleURI = Services.io.newURI(
  2475. 'data:text/css,' + encodeURIComponent(style.join('')),
  2476. null,
  2477. null
  2478. );
  2479. CustomizableUI.createWidget(this);
  2480. cleanupTasks.push(shutdown);
  2481. };
  2482. })();
  2483. /******************************************************************************/
  2484. // No toolbar button.
  2485. (function() {
  2486. // Just to ensure the number of cleanup tasks is as expected: toolbar
  2487. // button code is one single cleanup task regardless of platform.
  2488. if ( vAPI.toolbarButton.init === null ) {
  2489. cleanupTasks.push(function(){});
  2490. }
  2491. })();
  2492. /******************************************************************************/
  2493. if ( vAPI.toolbarButton.init !== null ) {
  2494. vAPI.toolbarButton.init();
  2495. }
  2496. /******************************************************************************/
  2497. /******************************************************************************/
  2498. vAPI.contextMenu = {
  2499. contextMap: {
  2500. frame: 'inFrame',
  2501. link: 'onLink',
  2502. image: 'onImage',
  2503. audio: 'onAudio',
  2504. video: 'onVideo',
  2505. editable: 'onEditableArea'
  2506. }
  2507. };
  2508. /******************************************************************************/
  2509. vAPI.contextMenu.displayMenuItem = function({target}) {
  2510. var doc = target.ownerDocument;
  2511. var gContextMenu = doc.defaultView.gContextMenu;
  2512. if ( !gContextMenu.browser ) {
  2513. return;
  2514. }
  2515. var menuitem = doc.getElementById(vAPI.contextMenu.menuItemId);
  2516. var currentURI = gContextMenu.browser.currentURI;
  2517. // https://github.com/chrisaljoudi/uBlock/issues/105
  2518. // TODO: Should the element picker works on any kind of pages?
  2519. if ( !currentURI.schemeIs('http') && !currentURI.schemeIs('https') ) {
  2520. menuitem.setAttribute('hidden', true);
  2521. return;
  2522. }
  2523. var ctx = vAPI.contextMenu.contexts;
  2524. if ( !ctx ) {
  2525. menuitem.setAttribute('hidden', false);
  2526. return;
  2527. }
  2528. var ctxMap = vAPI.contextMenu.contextMap;
  2529. for ( var context of ctx ) {
  2530. if (
  2531. context === 'page' &&
  2532. !gContextMenu.onLink &&
  2533. !gContextMenu.onImage &&
  2534. !gContextMenu.onEditableArea &&
  2535. !gContextMenu.inFrame &&
  2536. !gContextMenu.onVideo &&
  2537. !gContextMenu.onAudio
  2538. ) {
  2539. menuitem.setAttribute('hidden', false);
  2540. return;
  2541. }
  2542. if (
  2543. ctxMap.hasOwnProperty(context) &&
  2544. gContextMenu[ctxMap[context]]
  2545. ) {
  2546. menuitem.setAttribute('hidden', false);
  2547. return;
  2548. }
  2549. }
  2550. menuitem.setAttribute('hidden', true);
  2551. };
  2552. /******************************************************************************/
  2553. vAPI.contextMenu.register = (function() {
  2554. var register = function(doc) {
  2555. if ( !this.menuItemId ) {
  2556. return;
  2557. }
  2558. // Already installed?
  2559. if ( doc.getElementById(this.menuItemId) !== null ) {
  2560. return;
  2561. }
  2562. var contextMenu = doc.getElementById('contentAreaContextMenu');
  2563. var menuitem = doc.createElement('menuitem');
  2564. menuitem.setAttribute('id', this.menuItemId);
  2565. menuitem.setAttribute('label', this.menuLabel);
  2566. menuitem.setAttribute('image', vAPI.getURL('img/browsericons/icon19-19.png'));
  2567. menuitem.setAttribute('class', 'menuitem-iconic');
  2568. menuitem.addEventListener('command', this.onCommand);
  2569. contextMenu.addEventListener('popupshowing', this.displayMenuItem);
  2570. contextMenu.insertBefore(menuitem, doc.getElementById('inspect-separator'));
  2571. };
  2572. // https://github.com/gorhill/uBlock/issues/906
  2573. // Be sure document.readyState is 'complete': it could happen at launch
  2574. // time that we are called by vAPI.contextMenu.create() directly before
  2575. // the environment is properly initialized.
  2576. var registerSafely = function(doc, tryCount) {
  2577. if ( doc.readyState === 'complete' ) {
  2578. register.call(this, doc);
  2579. return;
  2580. }
  2581. if ( typeof tryCount !== 'number' ) {
  2582. tryCount = 0;
  2583. }
  2584. tryCount += 1;
  2585. if ( tryCount < 8 ) {
  2586. vAPI.setTimeout(registerSafely.bind(this, doc, tryCount), 200);
  2587. }
  2588. };
  2589. return registerSafely;
  2590. })();
  2591. /******************************************************************************/
  2592. vAPI.contextMenu.unregister = function(doc) {
  2593. if ( !this.menuItemId ) {
  2594. return;
  2595. }
  2596. var menuitem = doc.getElementById(this.menuItemId);
  2597. if ( menuitem === null ) {
  2598. return;
  2599. }
  2600. var contextMenu = menuitem.parentNode;
  2601. menuitem.removeEventListener('command', this.onCommand);
  2602. contextMenu.removeEventListener('popupshowing', this.displayMenuItem);
  2603. contextMenu.removeChild(menuitem);
  2604. };
  2605. /******************************************************************************/
  2606. vAPI.contextMenu.create = function(details, callback) {
  2607. this.menuItemId = details.id;
  2608. this.menuLabel = details.title;
  2609. this.contexts = details.contexts;
  2610. if ( Array.isArray(this.contexts) && this.contexts.length ) {
  2611. this.contexts = this.contexts.indexOf('all') === -1 ? this.contexts : null;
  2612. } else {
  2613. // default in Chrome
  2614. this.contexts = ['page'];
  2615. }
  2616. this.onCommand = function() {
  2617. var gContextMenu = getOwnerWindow(this).gContextMenu;
  2618. var details = {
  2619. menuItemId: this.id
  2620. };
  2621. if ( gContextMenu.inFrame ) {
  2622. details.tagName = 'iframe';
  2623. // Probably won't work with e10s
  2624. details.frameUrl = gContextMenu.focusedWindow.location.href;
  2625. } else if ( gContextMenu.onImage ) {
  2626. details.tagName = 'img';
  2627. details.srcUrl = gContextMenu.mediaURL;
  2628. } else if ( gContextMenu.onAudio ) {
  2629. details.tagName = 'audio';
  2630. details.srcUrl = gContextMenu.mediaURL;
  2631. } else if ( gContextMenu.onVideo ) {
  2632. details.tagName = 'video';
  2633. details.srcUrl = gContextMenu.mediaURL;
  2634. } else if ( gContextMenu.onLink ) {
  2635. details.tagName = 'a';
  2636. details.linkUrl = gContextMenu.linkURL;
  2637. }
  2638. callback(details, {
  2639. id: tabWatcher.tabIdFromTarget(gContextMenu.browser),
  2640. url: gContextMenu.browser.currentURI.asciiSpec
  2641. });
  2642. };
  2643. for ( var win of winWatcher.getWindows() ) {
  2644. this.register(win.document);
  2645. }
  2646. };
  2647. /******************************************************************************/
  2648. vAPI.contextMenu.remove = function() {
  2649. for ( var win of winWatcher.getWindows() ) {
  2650. this.unregister(win.document);
  2651. }
  2652. this.menuItemId = null;
  2653. this.menuLabel = null;
  2654. this.contexts = null;
  2655. this.onCommand = null;
  2656. };
  2657. /******************************************************************************/
  2658. /******************************************************************************/
  2659. var optionsObserver = (function() {
  2660. var addonId = 'eMatrix@vannilla.org';
  2661. var commandHandler = function() {
  2662. switch ( this.id ) {
  2663. case 'showDashboardButton':
  2664. vAPI.tabs.open({ url: 'dashboard.html', index: -1 });
  2665. break;
  2666. case 'showLoggerButton':
  2667. vAPI.tabs.open({ url: 'logger-ui.html', index: -1 });
  2668. break;
  2669. default:
  2670. break;
  2671. }
  2672. };
  2673. var setupOptionsButton = function(doc, id) {
  2674. var button = doc.getElementById(id);
  2675. if ( button === null ) {
  2676. return;
  2677. }
  2678. button.addEventListener('command', commandHandler);
  2679. button.label = vAPI.i18n(id);
  2680. };
  2681. var setupOptionsButtons = function(doc) {
  2682. setupOptionsButton(doc, 'showDashboardButton');
  2683. setupOptionsButton(doc, 'showLoggerButton');
  2684. };
  2685. var observer = {
  2686. observe: function(doc, topic, id) {
  2687. if ( id !== addonId ) {
  2688. return;
  2689. }
  2690. setupOptionsButtons(doc);
  2691. }
  2692. };
  2693. // https://github.com/gorhill/uBlock/issues/948
  2694. // Older versions of Firefox can throw here when looking up `currentURI`.
  2695. var canInit = function() {
  2696. try {
  2697. var tabBrowser = tabWatcher.currentBrowser();
  2698. return tabBrowser &&
  2699. tabBrowser.currentURI &&
  2700. tabBrowser.currentURI.spec === 'about:addons' &&
  2701. tabBrowser.contentDocument &&
  2702. tabBrowser.contentDocument.readyState === 'complete';
  2703. } catch (ex) {
  2704. }
  2705. };
  2706. // Manually add the buttons if the `about:addons` page is already opened.
  2707. var init = function() {
  2708. if ( canInit() ) {
  2709. setupOptionsButtons(tabWatcher.currentBrowser().contentDocument);
  2710. }
  2711. };
  2712. var unregister = function() {
  2713. Services.obs.removeObserver(observer, 'addon-options-displayed');
  2714. };
  2715. var register = function() {
  2716. Services.obs.addObserver(observer, 'addon-options-displayed', false);
  2717. cleanupTasks.push(unregister);
  2718. deferUntil(canInit, init, { next: 463 });
  2719. };
  2720. return {
  2721. register: register,
  2722. unregister: unregister
  2723. };
  2724. })();
  2725. optionsObserver.register();
  2726. /******************************************************************************/
  2727. /******************************************************************************/
  2728. vAPI.lastError = function() {
  2729. return null;
  2730. };
  2731. /******************************************************************************/
  2732. /******************************************************************************/
  2733. // This is called only once, when everything has been loaded in memory after
  2734. // the extension was launched. It can be used to inject content scripts
  2735. // in already opened web pages, to remove whatever nuisance could make it to
  2736. // the web pages before uBlock was ready.
  2737. vAPI.onLoadAllCompleted = function() {
  2738. for ( var browser of tabWatcher.browsers() ) {
  2739. browser.messageManager.sendAsyncMessage(
  2740. location.host + '-load-completed'
  2741. );
  2742. }
  2743. };
  2744. /******************************************************************************/
  2745. /******************************************************************************/
  2746. // Likelihood is that we do not have to punycode: given punycode overhead,
  2747. // it's faster to check and skip than do it unconditionally all the time.
  2748. var punycodeHostname = punycode.toASCII;
  2749. var isNotASCII = /[^\x21-\x7F]/;
  2750. vAPI.punycodeHostname = function(hostname) {
  2751. return isNotASCII.test(hostname) ? punycodeHostname(hostname) : hostname;
  2752. };
  2753. vAPI.punycodeURL = function(url) {
  2754. if ( isNotASCII.test(url) ) {
  2755. return Services.io.newURI(url, null, null).asciiSpec;
  2756. }
  2757. return url;
  2758. };
  2759. /******************************************************************************/
  2760. /******************************************************************************/
  2761. vAPI.cloud = (function() {
  2762. var extensionBranchPath = 'extensions.' + location.host;
  2763. var cloudBranchPath = extensionBranchPath + '.cloudStorage';
  2764. // https://github.com/gorhill/uBlock/issues/80#issuecomment-132081658
  2765. // We must use get/setComplexValue in order to properly handle strings
  2766. // with unicode characters.
  2767. var iss = Ci.nsISupportsString;
  2768. var argstr = Components.classes['@mozilla.org/supports-string;1']
  2769. .createInstance(iss);
  2770. var options = {
  2771. defaultDeviceName: '',
  2772. deviceName: ''
  2773. };
  2774. // User-supplied device name.
  2775. try {
  2776. options.deviceName = Services.prefs
  2777. .getBranch(extensionBranchPath + '.')
  2778. .getComplexValue('deviceName', iss)
  2779. .data;
  2780. } catch(ex) {
  2781. }
  2782. var getDefaultDeviceName = function() {
  2783. var name = '';
  2784. try {
  2785. name = Services.prefs
  2786. .getBranch('services.sync.client.')
  2787. .getComplexValue('name', iss)
  2788. .data;
  2789. } catch(ex) {
  2790. }
  2791. return name || window.navigator.platform || window.navigator.oscpu;
  2792. };
  2793. var start = function(dataKeys) {
  2794. var extensionBranch = Services.prefs.getBranch(extensionBranchPath + '.');
  2795. var syncBranch = Services.prefs.getBranch('services.sync.prefs.sync.');
  2796. // Mark config entries as syncable
  2797. argstr.data = '';
  2798. var dataKey;
  2799. for ( var i = 0; i < dataKeys.length; i++ ) {
  2800. dataKey = dataKeys[i];
  2801. if ( extensionBranch.prefHasUserValue('cloudStorage.' + dataKey) === false ) {
  2802. extensionBranch.setComplexValue('cloudStorage.' + dataKey, iss, argstr);
  2803. }
  2804. syncBranch.setBoolPref(cloudBranchPath + '.' + dataKey, true);
  2805. }
  2806. };
  2807. var push = function(datakey, data, callback) {
  2808. var branch = Services.prefs.getBranch(cloudBranchPath + '.');
  2809. var bin = {
  2810. 'source': options.deviceName || getDefaultDeviceName(),
  2811. 'tstamp': Date.now(),
  2812. 'data': data,
  2813. 'size': 0
  2814. };
  2815. bin.size = JSON.stringify(bin).length;
  2816. argstr.data = JSON.stringify(bin);
  2817. branch.setComplexValue(datakey, iss, argstr);
  2818. if ( typeof callback === 'function' ) {
  2819. callback();
  2820. }
  2821. };
  2822. var pull = function(datakey, callback) {
  2823. var result = null;
  2824. var branch = Services.prefs.getBranch(cloudBranchPath + '.');
  2825. try {
  2826. var json = branch.getComplexValue(datakey, iss).data;
  2827. if ( typeof json === 'string' ) {
  2828. result = JSON.parse(json);
  2829. }
  2830. } catch(ex) {
  2831. }
  2832. callback(result);
  2833. };
  2834. var getOptions = function(callback) {
  2835. if ( typeof callback !== 'function' ) {
  2836. return;
  2837. }
  2838. options.defaultDeviceName = getDefaultDeviceName();
  2839. callback(options);
  2840. };
  2841. var setOptions = function(details, callback) {
  2842. if ( typeof details !== 'object' || details === null ) {
  2843. return;
  2844. }
  2845. var branch = Services.prefs.getBranch(extensionBranchPath + '.');
  2846. if ( typeof details.deviceName === 'string' ) {
  2847. argstr.data = details.deviceName;
  2848. branch.setComplexValue('deviceName', iss, argstr);
  2849. options.deviceName = details.deviceName;
  2850. }
  2851. getOptions(callback);
  2852. };
  2853. return {
  2854. start: start,
  2855. push: push,
  2856. pull: pull,
  2857. getOptions: getOptions,
  2858. setOptions: setOptions
  2859. };
  2860. })();
  2861. /******************************************************************************/
  2862. /******************************************************************************/
  2863. vAPI.browserData = {};
  2864. /******************************************************************************/
  2865. // https://developer.mozilla.org/en-US/docs/HTTP_Cache
  2866. vAPI.browserData.clearCache = function(callback) {
  2867. // PURGE_DISK_DATA_ONLY:1
  2868. // PURGE_DISK_ALL:2
  2869. // PURGE_EVERYTHING:3
  2870. // However I verified that no argument does clear the cache data.
  2871. // There is no cache2 for older versions of Firefox.
  2872. if ( Services.cache2 ) {
  2873. Services.cache2.clear();
  2874. } else if ( Services.cache ) {
  2875. Services.cache.evictEntries(Services.cache.STORE_ON_DISK);
  2876. }
  2877. if ( typeof callback === 'function' ) {
  2878. callback();
  2879. }
  2880. };
  2881. /******************************************************************************/
  2882. vAPI.browserData.clearOrigin = function(/* domain */) {
  2883. // TODO
  2884. };
  2885. /******************************************************************************/
  2886. /******************************************************************************/
  2887. // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsICookieManager2
  2888. // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsICookie2
  2889. // https://developer.mozilla.org/en-US/docs/Observer_Notifications#Cookies
  2890. vAPI.cookies = {};
  2891. /******************************************************************************/
  2892. vAPI.cookies.CookieEntry = function(ffCookie) {
  2893. this.domain = ffCookie.host;
  2894. this.name = ffCookie.name;
  2895. this.path = ffCookie.path;
  2896. this.secure = ffCookie.isSecure === true;
  2897. this.session = ffCookie.expires === 0;
  2898. this.value = ffCookie.value;
  2899. };
  2900. /******************************************************************************/
  2901. vAPI.cookies.start = function() {
  2902. Services.obs.addObserver(this, 'cookie-changed', false);
  2903. Services.obs.addObserver(this, 'private-cookie-changed', false);
  2904. cleanupTasks.push(this.stop.bind(this));
  2905. };
  2906. /******************************************************************************/
  2907. vAPI.cookies.stop = function() {
  2908. Services.obs.removeObserver(this, 'cookie-changed');
  2909. Services.obs.removeObserver(this, 'private-cookie-changed');
  2910. };
  2911. /******************************************************************************/
  2912. vAPI.cookies.observe = function(subject, topic, reason) {
  2913. //if ( topic !== 'cookie-changed' && topic !== 'private-cookie-changed' ) {
  2914. // return;
  2915. //}
  2916. //
  2917. if ( reason === 'cleared' && typeof this.onAllRemoved === 'function' ) {
  2918. this.onAllRemoved();
  2919. return;
  2920. }
  2921. if ( subject === null ) {
  2922. return;
  2923. }
  2924. if ( subject instanceof Ci.nsICookie2 === false ) {
  2925. try {
  2926. subject = subject.QueryInterface(Ci.nsICookie2);
  2927. } catch (ex) {
  2928. return;
  2929. }
  2930. }
  2931. if ( reason === 'deleted' ) {
  2932. if ( typeof this.onRemoved === 'function' ) {
  2933. this.onRemoved(new this.CookieEntry(subject));
  2934. }
  2935. return;
  2936. }
  2937. if ( typeof this.onChanged === 'function' ) {
  2938. this.onChanged(new this.CookieEntry(subject));
  2939. }
  2940. };
  2941. /******************************************************************************/
  2942. // Meant and expected to be asynchronous.
  2943. vAPI.cookies.getAll = function(callback) {
  2944. if ( typeof callback !== 'function' ) {
  2945. return;
  2946. }
  2947. var onAsync = function() {
  2948. var out = [];
  2949. var enumerator = Services.cookies.enumerator;
  2950. var ffcookie;
  2951. while ( enumerator.hasMoreElements() ) {
  2952. ffcookie = enumerator.getNext();
  2953. if ( ffcookie instanceof Ci.nsICookie ) {
  2954. out.push(new this.CookieEntry(ffcookie));
  2955. }
  2956. }
  2957. callback(out);
  2958. };
  2959. vAPI.setTimeout(onAsync.bind(this), 0);
  2960. };
  2961. /******************************************************************************/
  2962. vAPI.cookies.remove = function(details, callback) {
  2963. var uri = Services.io.newURI(details.url, null, null);
  2964. var cookies = Services.cookies;
  2965. cookies.remove(uri.asciiHost, details.name, uri.path, false, {});
  2966. cookies.remove( '.' + uri.asciiHost, details.name, uri.path, false, {});
  2967. if ( typeof callback === 'function' ) {
  2968. callback({
  2969. domain: uri.asciiHost,
  2970. name: details.name,
  2971. path: uri.path
  2972. });
  2973. }
  2974. };
  2975. /******************************************************************************/
  2976. /******************************************************************************/
  2977. })();
  2978. /******************************************************************************/