Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | this.EXPORTED_SYMBOLS = []; |
michael@0 | 8 | |
michael@0 | 9 | const DEBUG = false; |
michael@0 | 10 | function debug(s) { dump("-*- NotificationDB component: " + s + "\n"); } |
michael@0 | 11 | |
michael@0 | 12 | const Cu = Components.utils; |
michael@0 | 13 | const Cc = Components.classes; |
michael@0 | 14 | const Ci = Components.interfaces; |
michael@0 | 15 | |
michael@0 | 16 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 17 | Cu.import("resource://gre/modules/osfile.jsm"); |
michael@0 | 18 | |
michael@0 | 19 | XPCOMUtils.defineLazyServiceGetter(this, "ppmm", |
michael@0 | 20 | "@mozilla.org/parentprocessmessagemanager;1", |
michael@0 | 21 | "nsIMessageListenerManager"); |
michael@0 | 22 | |
michael@0 | 23 | XPCOMUtils.defineLazyGetter(this, "gEncoder", function() { |
michael@0 | 24 | return new TextEncoder(); |
michael@0 | 25 | }); |
michael@0 | 26 | |
michael@0 | 27 | XPCOMUtils.defineLazyGetter(this, "gDecoder", function() { |
michael@0 | 28 | return new TextDecoder(); |
michael@0 | 29 | }); |
michael@0 | 30 | |
michael@0 | 31 | |
michael@0 | 32 | const NOTIFICATION_STORE_DIR = OS.Constants.Path.profileDir; |
michael@0 | 33 | const NOTIFICATION_STORE_PATH = |
michael@0 | 34 | OS.Path.join(NOTIFICATION_STORE_DIR, "notificationstore.json"); |
michael@0 | 35 | |
michael@0 | 36 | let NotificationDB = { |
michael@0 | 37 | init: function() { |
michael@0 | 38 | this.notifications = {}; |
michael@0 | 39 | this.byTag = {}; |
michael@0 | 40 | this.loaded = false; |
michael@0 | 41 | |
michael@0 | 42 | this.tasks = []; // read/write operation queue |
michael@0 | 43 | this.runningTask = false; |
michael@0 | 44 | |
michael@0 | 45 | ppmm.addMessageListener("Notification:Save", this); |
michael@0 | 46 | ppmm.addMessageListener("Notification:Delete", this); |
michael@0 | 47 | ppmm.addMessageListener("Notification:GetAll", this); |
michael@0 | 48 | }, |
michael@0 | 49 | |
michael@0 | 50 | // Attempt to read notification file, if it's not there we will create it. |
michael@0 | 51 | load: function(callback) { |
michael@0 | 52 | var promise = OS.File.read(NOTIFICATION_STORE_PATH); |
michael@0 | 53 | promise.then( |
michael@0 | 54 | function onSuccess(data) { |
michael@0 | 55 | try { |
michael@0 | 56 | this.notifications = JSON.parse(gDecoder.decode(data)); |
michael@0 | 57 | } catch (e) { |
michael@0 | 58 | if (DEBUG) { debug("Unable to parse file data " + e); } |
michael@0 | 59 | } |
michael@0 | 60 | // populate the list of notifications by tag |
michael@0 | 61 | if (this.notifications) { |
michael@0 | 62 | for (var origin in this.notifications) { |
michael@0 | 63 | this.byTag[origin] = {}; |
michael@0 | 64 | for (var id in this.notifications[origin]) { |
michael@0 | 65 | var curNotification = this.notifications[origin][id]; |
michael@0 | 66 | if (curNotification.tag) { |
michael@0 | 67 | this.byTag[origin][curNotification.tag] = curNotification; |
michael@0 | 68 | } |
michael@0 | 69 | } |
michael@0 | 70 | } |
michael@0 | 71 | } |
michael@0 | 72 | this.loaded = true; |
michael@0 | 73 | callback && callback(); |
michael@0 | 74 | }.bind(this), |
michael@0 | 75 | |
michael@0 | 76 | // If read failed, we assume we have no notifications to load. |
michael@0 | 77 | function onFailure(reason) { |
michael@0 | 78 | this.loaded = true; |
michael@0 | 79 | this.createStore(callback); |
michael@0 | 80 | }.bind(this) |
michael@0 | 81 | ); |
michael@0 | 82 | }, |
michael@0 | 83 | |
michael@0 | 84 | // Creates the notification directory. |
michael@0 | 85 | createStore: function(callback) { |
michael@0 | 86 | var promise = OS.File.makeDir(NOTIFICATION_STORE_DIR, { |
michael@0 | 87 | ignoreExisting: true |
michael@0 | 88 | }); |
michael@0 | 89 | promise.then( |
michael@0 | 90 | function onSuccess() { |
michael@0 | 91 | this.createFile(callback); |
michael@0 | 92 | }.bind(this), |
michael@0 | 93 | |
michael@0 | 94 | function onFailure(reason) { |
michael@0 | 95 | if (DEBUG) { debug("Directory creation failed:" + reason); } |
michael@0 | 96 | callback && callback(); |
michael@0 | 97 | } |
michael@0 | 98 | ); |
michael@0 | 99 | }, |
michael@0 | 100 | |
michael@0 | 101 | // Creates the notification file once the directory is created. |
michael@0 | 102 | createFile: function(callback) { |
michael@0 | 103 | var promise = OS.File.open(NOTIFICATION_STORE_PATH, {create: true}); |
michael@0 | 104 | promise.then( |
michael@0 | 105 | function onSuccess(handle) { |
michael@0 | 106 | handle.close(); |
michael@0 | 107 | callback && callback(); |
michael@0 | 108 | }, |
michael@0 | 109 | function onFailure(reason) { |
michael@0 | 110 | if (DEBUG) { debug("File creation failed:" + reason); } |
michael@0 | 111 | callback && callback(); |
michael@0 | 112 | } |
michael@0 | 113 | ); |
michael@0 | 114 | }, |
michael@0 | 115 | |
michael@0 | 116 | // Save current notifications to the file. |
michael@0 | 117 | save: function(callback) { |
michael@0 | 118 | var data = gEncoder.encode(JSON.stringify(this.notifications)); |
michael@0 | 119 | var promise = OS.File.writeAtomic(NOTIFICATION_STORE_PATH, data); |
michael@0 | 120 | promise.then( |
michael@0 | 121 | function onSuccess() { |
michael@0 | 122 | callback && callback(); |
michael@0 | 123 | }, |
michael@0 | 124 | function onFailure(reason) { |
michael@0 | 125 | if (DEBUG) { debug("Save failed:" + reason); } |
michael@0 | 126 | callback && callback(); |
michael@0 | 127 | } |
michael@0 | 128 | ); |
michael@0 | 129 | }, |
michael@0 | 130 | |
michael@0 | 131 | // Helper function: callback will be called once file exists and/or is loaded. |
michael@0 | 132 | ensureLoaded: function(callback) { |
michael@0 | 133 | if (!this.loaded) { |
michael@0 | 134 | this.load(callback); |
michael@0 | 135 | } else { |
michael@0 | 136 | callback(); |
michael@0 | 137 | } |
michael@0 | 138 | }, |
michael@0 | 139 | |
michael@0 | 140 | receiveMessage: function(message) { |
michael@0 | 141 | if (DEBUG) { debug("Received message:" + message.name); } |
michael@0 | 142 | |
michael@0 | 143 | // sendAsyncMessage can fail if the child process exits during a |
michael@0 | 144 | // notification storage operation, so always wrap it in a try/catch. |
michael@0 | 145 | function returnMessage(name, data) { |
michael@0 | 146 | try { |
michael@0 | 147 | message.target.sendAsyncMessage(name, data); |
michael@0 | 148 | } catch (e) { |
michael@0 | 149 | if (DEBUG) { debug("Return message failed, " + name); } |
michael@0 | 150 | } |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | switch (message.name) { |
michael@0 | 154 | case "Notification:GetAll": |
michael@0 | 155 | this.queueTask("getall", message.data, function(notifications) { |
michael@0 | 156 | returnMessage("Notification:GetAll:Return:OK", { |
michael@0 | 157 | requestID: message.data.requestID, |
michael@0 | 158 | origin: message.data.origin, |
michael@0 | 159 | notifications: notifications |
michael@0 | 160 | }); |
michael@0 | 161 | }); |
michael@0 | 162 | break; |
michael@0 | 163 | |
michael@0 | 164 | case "Notification:Save": |
michael@0 | 165 | this.queueTask("save", message.data, function() { |
michael@0 | 166 | returnMessage("Notification:Save:Return:OK", { |
michael@0 | 167 | requestID: message.data.requestID |
michael@0 | 168 | }); |
michael@0 | 169 | }); |
michael@0 | 170 | break; |
michael@0 | 171 | |
michael@0 | 172 | case "Notification:Delete": |
michael@0 | 173 | this.queueTask("delete", message.data, function() { |
michael@0 | 174 | returnMessage("Notification:Delete:Return:OK", { |
michael@0 | 175 | requestID: message.data.requestID |
michael@0 | 176 | }); |
michael@0 | 177 | }); |
michael@0 | 178 | break; |
michael@0 | 179 | |
michael@0 | 180 | default: |
michael@0 | 181 | if (DEBUG) { debug("Invalid message name" + message.name); } |
michael@0 | 182 | } |
michael@0 | 183 | }, |
michael@0 | 184 | |
michael@0 | 185 | // We need to make sure any read/write operations are atomic, |
michael@0 | 186 | // so use a queue to run each operation sequentially. |
michael@0 | 187 | queueTask: function(operation, data, callback) { |
michael@0 | 188 | if (DEBUG) { debug("Queueing task: " + operation); } |
michael@0 | 189 | this.tasks.push({ |
michael@0 | 190 | operation: operation, |
michael@0 | 191 | data: data, |
michael@0 | 192 | callback: callback |
michael@0 | 193 | }); |
michael@0 | 194 | |
michael@0 | 195 | // Only run immediately if we aren't currently running another task. |
michael@0 | 196 | if (!this.runningTask) { |
michael@0 | 197 | if (DEBUG) { dump("Task queue was not running, starting now..."); } |
michael@0 | 198 | this.runNextTask(); |
michael@0 | 199 | } |
michael@0 | 200 | }, |
michael@0 | 201 | |
michael@0 | 202 | runNextTask: function() { |
michael@0 | 203 | if (this.tasks.length === 0) { |
michael@0 | 204 | if (DEBUG) { dump("No more tasks to run, queue depleted"); } |
michael@0 | 205 | this.runningTask = false; |
michael@0 | 206 | return; |
michael@0 | 207 | } |
michael@0 | 208 | this.runningTask = true; |
michael@0 | 209 | |
michael@0 | 210 | // Always make sure we are loaded before performing any read/write tasks. |
michael@0 | 211 | this.ensureLoaded(function() { |
michael@0 | 212 | var task = this.tasks.shift(); |
michael@0 | 213 | |
michael@0 | 214 | // Wrap the task callback to make sure we immediately |
michael@0 | 215 | // run the next task after running the original callback. |
michael@0 | 216 | var wrappedCallback = function() { |
michael@0 | 217 | if (DEBUG) { debug("Finishing task: " + task.operation); } |
michael@0 | 218 | task.callback.apply(this, arguments); |
michael@0 | 219 | this.runNextTask(); |
michael@0 | 220 | }.bind(this); |
michael@0 | 221 | |
michael@0 | 222 | switch (task.operation) { |
michael@0 | 223 | case "getall": |
michael@0 | 224 | this.taskGetAll(task.data, wrappedCallback); |
michael@0 | 225 | break; |
michael@0 | 226 | |
michael@0 | 227 | case "save": |
michael@0 | 228 | this.taskSave(task.data, wrappedCallback); |
michael@0 | 229 | break; |
michael@0 | 230 | |
michael@0 | 231 | case "delete": |
michael@0 | 232 | this.taskDelete(task.data, wrappedCallback); |
michael@0 | 233 | break; |
michael@0 | 234 | } |
michael@0 | 235 | }.bind(this)); |
michael@0 | 236 | }, |
michael@0 | 237 | |
michael@0 | 238 | taskGetAll: function(data, callback) { |
michael@0 | 239 | if (DEBUG) { debug("Task, getting all"); } |
michael@0 | 240 | var origin = data.origin; |
michael@0 | 241 | var notifications = []; |
michael@0 | 242 | // Grab only the notifications for specified origin. |
michael@0 | 243 | for (var i in this.notifications[origin]) { |
michael@0 | 244 | notifications.push(this.notifications[origin][i]); |
michael@0 | 245 | } |
michael@0 | 246 | callback(notifications); |
michael@0 | 247 | }, |
michael@0 | 248 | |
michael@0 | 249 | taskSave: function(data, callback) { |
michael@0 | 250 | if (DEBUG) { debug("Task, saving"); } |
michael@0 | 251 | var origin = data.origin; |
michael@0 | 252 | var notification = data.notification; |
michael@0 | 253 | if (!this.notifications[origin]) { |
michael@0 | 254 | this.notifications[origin] = {}; |
michael@0 | 255 | this.byTag[origin] = {}; |
michael@0 | 256 | } |
michael@0 | 257 | |
michael@0 | 258 | // We might have existing notification with this tag, |
michael@0 | 259 | // if so we need to remove it before saving the new one. |
michael@0 | 260 | if (notification.tag && this.byTag[origin][notification.tag]) { |
michael@0 | 261 | var oldNotification = this.byTag[origin][notification.tag]; |
michael@0 | 262 | delete this.notifications[origin][oldNotification.id]; |
michael@0 | 263 | this.byTag[origin][notification.tag] = notification; |
michael@0 | 264 | } |
michael@0 | 265 | |
michael@0 | 266 | this.notifications[origin][notification.id] = notification; |
michael@0 | 267 | this.save(callback); |
michael@0 | 268 | }, |
michael@0 | 269 | |
michael@0 | 270 | taskDelete: function(data, callback) { |
michael@0 | 271 | if (DEBUG) { debug("Task, deleting"); } |
michael@0 | 272 | var origin = data.origin; |
michael@0 | 273 | var id = data.id; |
michael@0 | 274 | if (!this.notifications[origin]) { |
michael@0 | 275 | if (DEBUG) { debug("No notifications found for origin: " + origin); } |
michael@0 | 276 | return; |
michael@0 | 277 | } |
michael@0 | 278 | |
michael@0 | 279 | // Make sure we can find the notification to delete. |
michael@0 | 280 | var oldNotification = this.notifications[origin][id]; |
michael@0 | 281 | if (!oldNotification) { |
michael@0 | 282 | if (DEBUG) { debug("No notification found with id: " + id); } |
michael@0 | 283 | return; |
michael@0 | 284 | } |
michael@0 | 285 | |
michael@0 | 286 | if (oldNotification.tag) { |
michael@0 | 287 | delete this.byTag[origin][oldNotification.tag]; |
michael@0 | 288 | } |
michael@0 | 289 | delete this.notifications[origin][id]; |
michael@0 | 290 | this.save(callback); |
michael@0 | 291 | } |
michael@0 | 292 | }; |
michael@0 | 293 | |
michael@0 | 294 | NotificationDB.init(); |