You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

359 lines
10 KiB

  1. /*******************************************************************************
  2. µBlock - a Chromium browser extension to block requests.
  3. Copyright (C) 2014 The µBlock authors
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see {http://www.gnu.org/licenses/}.
  14. Home: https://github.com/gorhill/uBlock
  15. */
  16. /* global Services */
  17. // For background page
  18. /******************************************************************************/
  19. (function() {
  20. 'use strict';
  21. /******************************************************************************/
  22. const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
  23. Cu['import']('resource://gre/modules/Services.jsm');
  24. /******************************************************************************/
  25. self.vAPI = self.vAPI || {};
  26. vAPI.firefox = true;
  27. /******************************************************************************/
  28. var SQLite = {
  29. open: function() {
  30. var path = Services.dirsvc.get('ProfD', Ci.nsIFile);
  31. path.append('extension-data');
  32. if (!path.exists()) {
  33. path.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0774', 8));
  34. }
  35. if (!path.isDirectory()) {
  36. throw Error('Should be a directory...');
  37. }
  38. path.append('uBlock.sqlite');
  39. this.db = Services.storage.openDatabase(path);
  40. this.db.executeSimpleSQL(
  41. 'CREATE TABLE IF NOT EXISTS settings' +
  42. '(name TEXT PRIMARY KEY NOT NULL, value TEXT);'
  43. );
  44. },
  45. close: function() {
  46. this.run('VACUUM');
  47. this.db.asyncClose();
  48. },
  49. run: function(query, values, callback) {
  50. if (!this.db) {
  51. this.open();
  52. }
  53. var result = {};
  54. query = this.db.createAsyncStatement(query);
  55. if (Array.isArray(values) && values.length) {
  56. var i = values.length;
  57. while (i--) {
  58. query.bindByIndex(i, values[i]);
  59. }
  60. }
  61. query.executeAsync({
  62. handleResult: function(rows) {
  63. if (!rows || typeof callback !== 'function') {
  64. return;
  65. }
  66. var row;
  67. while (row = rows.getNextRow()) {
  68. // we assume that there will be two columns, since we're
  69. // using it only for preferences
  70. result[row.getResultByIndex(0)] = row.getResultByIndex(1);
  71. }
  72. },
  73. handleCompletion: function(reason) {
  74. if (typeof callback === 'function' && reason === 0) {
  75. callback(result);
  76. }
  77. },
  78. handleError: function(error) {
  79. console.error('SQLite error ', error.result, error.message);
  80. }
  81. });
  82. }
  83. };
  84. /******************************************************************************/
  85. vAPI.storage = {
  86. QUOTA_BYTES: 100 * 1024 * 1024,
  87. sqlWhere: function(col, valNum) {
  88. if (valNum > 0) {
  89. valNum = Array(valNum + 1).join('?, ').slice(0, -2);
  90. return ' WHERE ' + col + ' IN (' + valNum + ')';
  91. }
  92. return '';
  93. },
  94. get: function(details, callback) {
  95. if (typeof callback !== 'function') {
  96. return;
  97. }
  98. var values = [], defaults = false;
  99. if (details !== null) {
  100. if (Array.isArray(details)) {
  101. values = details;
  102. }
  103. else if (typeof details === 'object') {
  104. defaults = true;
  105. values = Object.keys(details);
  106. }
  107. else {
  108. values = [details.toString()];
  109. }
  110. }
  111. SQLite.run(
  112. 'SELECT * FROM settings' + this.sqlWhere('name', values.length),
  113. values,
  114. function(result) {
  115. var key;
  116. for (key in result) {
  117. result[key] = JSON.parse(result[key]);
  118. }
  119. if (defaults) {
  120. for (key in details) {
  121. if (!result[key]) {
  122. result[key] = details[key];
  123. }
  124. }
  125. }
  126. callback(result);
  127. }
  128. );
  129. },
  130. set: function(details, callback) {
  131. var key, values = [], questionmarks = [];
  132. for (key in details) {
  133. values.push(key);
  134. values.push(JSON.stringify(details[key]));
  135. questionmarks.push('?, ?');
  136. }
  137. if (!values.length) {
  138. return;
  139. }
  140. SQLite.run(
  141. 'INSERT OR REPLACE INTO settings (name, value) SELECT ' +
  142. questionmarks.join(' UNION SELECT '),
  143. values,
  144. callback
  145. );
  146. },
  147. remove: function(keys, callback) {
  148. if (typeof keys === 'string') {
  149. keys = [keys];
  150. }
  151. SQLite.run(
  152. 'DELETE FROM settings' + this.sqlWhere('name', keys.length),
  153. keys,
  154. callback
  155. );
  156. },
  157. clear: function(callback) {
  158. SQLite.run('DELETE FROM settings', null, callback);
  159. SQLite.run('VACUUM');
  160. },
  161. getBytesInUse: function(keys, callback) {
  162. if (typeof callback !== 'function') {
  163. return;
  164. }
  165. SQLite.run(
  166. "SELECT 'size' AS size, SUM(LENGTH(value)) FROM settings" +
  167. this.sqlWhere('name', Array.isArray(keys) ? keys.length : 0),
  168. keys,
  169. function(result) {
  170. callback(result.size);
  171. }
  172. );
  173. }
  174. };
  175. /******************************************************************************/
  176. vAPI.messaging = {
  177. gmm: Cc['@mozilla.org/globalmessagemanager;1'].getService(Ci.nsIMessageListenerManager),
  178. frameScript: 'chrome://ublock/content/frameScript.js',
  179. listeners: {},
  180. defaultHandler: null,
  181. NOOPFUNC: function(){},
  182. UNHANDLED: 'vAPI.messaging.notHandled'
  183. };
  184. /******************************************************************************/
  185. vAPI.messaging.gmm.loadFrameScript(vAPI.messaging.frameScript, true);
  186. /******************************************************************************/
  187. vAPI.messaging.listen = function(listenerName, callback) {
  188. this.listeners[listenerName] = callback;
  189. };
  190. /******************************************************************************/
  191. vAPI.messaging.onMessage = function(request) {
  192. var messageManager = request.target.messageManager;
  193. var listenerId = request.data.portName.split('|');
  194. var portName = listenerId[1];
  195. listenerId = listenerId[0];
  196. var callback = vAPI.messaging.NOOPFUNC;
  197. if ( request.data.requestId !== undefined ) {
  198. callback = function(response) {
  199. messageManager.sendAsyncMessage(
  200. listenerId,
  201. JSON.stringify({
  202. requestId: request.data.requestId,
  203. portName: portName,
  204. msg: response !== undefined ? response : null
  205. })
  206. );
  207. };
  208. }
  209. // TODO:
  210. var sender = {
  211. tab: {
  212. id: 0
  213. }
  214. };
  215. // Specific handler
  216. var r = vAPI.messaging.UNHANDLED;
  217. var listener = vAPI.messaging.listeners[portName];
  218. if ( typeof listener === 'function' ) {
  219. r = listener(request.data.msg, sender, callback);
  220. }
  221. if ( r !== vAPI.messaging.UNHANDLED ) {
  222. return;
  223. }
  224. // Default handler
  225. r = vAPI.messaging.defaultHandler(request.data.msg, sender, callback);
  226. if ( r !== vAPI.messaging.UNHANDLED ) {
  227. return;
  228. }
  229. console.error('µBlock> messaging > unknown request: %o', request.data);
  230. // Unhandled:
  231. // Need to callback anyways in case caller expected an answer, or
  232. // else there is a memory leak on caller's side
  233. callback();
  234. };
  235. /******************************************************************************/
  236. vAPI.messaging.setup = function(defaultHandler) {
  237. // Already setup?
  238. if ( this.defaultHandler !== null ) {
  239. return;
  240. }
  241. if ( typeof defaultHandler !== 'function' ) {
  242. defaultHandler = function(){ return vAPI.messaging.UNHANDLED; };
  243. }
  244. this.defaultHandler = defaultHandler;
  245. this.gmm.addMessageListener(vAPI.app.name + ':background', this.onMessage);
  246. };
  247. /******************************************************************************/
  248. vAPI.messaging.broadcast = function(message) {
  249. this.gmm.broadcastAsyncMessage(
  250. vAPI.app.name + ':broadcast',
  251. JSON.stringify({broadcast: true, msg: message})
  252. );
  253. };
  254. /******************************************************************************/
  255. vAPI.lastError = function() {
  256. return null;
  257. };
  258. /******************************************************************************/
  259. // clean up when the extension is disabled
  260. window.addEventListener('unload', function() {
  261. SQLite.close();
  262. vAPI.messaging.gmm.removeMessageListener(
  263. vAPI.app.name + ':background',
  264. vAPI.messaging.postMessage
  265. );
  266. vAPI.messaging.gmm.removeDelayedFrameScript(vAPI.messaging.frameScript);
  267. // close extension tabs
  268. var enumerator = Services.wm.getEnumerator('navigator:browser');
  269. var host = 'ublock';
  270. var gBrowser, tabs, i, extURI;
  271. while (enumerator.hasMoreElements()) {
  272. gBrowser = enumerator.getNext().gBrowser;
  273. tabs = gBrowser.tabs;
  274. i = tabs.length;
  275. while (i--) {
  276. extURI = tabs[i].linkedBrowser.currentURI;
  277. if (extURI.scheme === 'chrome' && extURI.host === host) {
  278. gBrowser.removeTab(tabs[i]);
  279. }
  280. }
  281. }
  282. });
  283. /******************************************************************************/
  284. })();
  285. /******************************************************************************/