mobile/android/modules/Home.jsm

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 "use strict";
michael@0 7
michael@0 8 this.EXPORTED_SYMBOLS = ["Home"];
michael@0 9
michael@0 10 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
michael@0 11
michael@0 12 Cu.import("resource://gre/modules/Services.jsm");
michael@0 13 Cu.import("resource://gre/modules/SharedPreferences.jsm");
michael@0 14 Cu.import("resource://gre/modules/Messaging.jsm");
michael@0 15
michael@0 16 // Keep this in sync with the constant defined in PanelAuthCache.java
michael@0 17 const PREFS_PANEL_AUTH_PREFIX = "home_panels_auth_";
michael@0 18
michael@0 19 // See bug 915424
michael@0 20 function resolveGeckoURI(aURI) {
michael@0 21 if (!aURI)
michael@0 22 throw "Can't resolve an empty uri";
michael@0 23
michael@0 24 if (aURI.startsWith("chrome://")) {
michael@0 25 let registry = Cc['@mozilla.org/chrome/chrome-registry;1'].getService(Ci["nsIChromeRegistry"]);
michael@0 26 return registry.convertChromeURL(Services.io.newURI(aURI, null, null)).spec;
michael@0 27 } else if (aURI.startsWith("resource://")) {
michael@0 28 let handler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
michael@0 29 return handler.resolveURI(Services.io.newURI(aURI, null, null));
michael@0 30 }
michael@0 31 return aURI;
michael@0 32 }
michael@0 33
michael@0 34 function BannerMessage(options) {
michael@0 35 let uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
michael@0 36 this.id = uuidgen.generateUUID().toString();
michael@0 37
michael@0 38 if ("text" in options && options.text != null)
michael@0 39 this.text = options.text;
michael@0 40
michael@0 41 if ("icon" in options && options.icon != null)
michael@0 42 this.iconURI = resolveGeckoURI(options.icon);
michael@0 43
michael@0 44 if ("onshown" in options && typeof options.onshown === "function")
michael@0 45 this.onshown = options.onshown;
michael@0 46
michael@0 47 if ("onclick" in options && typeof options.onclick === "function")
michael@0 48 this.onclick = options.onclick;
michael@0 49
michael@0 50 if ("ondismiss" in options && typeof options.ondismiss === "function")
michael@0 51 this.ondismiss = options.ondismiss;
michael@0 52 }
michael@0 53
michael@0 54 // We need this object to have access to the HomeBanner
michael@0 55 // private members without leaking it outside Home.jsm.
michael@0 56 let HomeBannerMessageHandlers;
michael@0 57
michael@0 58 let HomeBanner = (function () {
michael@0 59 // Whether there is a "HomeBanner:Get" request we couldn't fulfill.
michael@0 60 let _pendingRequest = false;
michael@0 61
michael@0 62 // Functions used to handle messages sent from Java.
michael@0 63 HomeBannerMessageHandlers = {
michael@0 64 "HomeBanner:Get": function handleBannerGet(data) {
michael@0 65 if (!_sendBannerData()) {
michael@0 66 _pendingRequest = true;
michael@0 67 }
michael@0 68 }
michael@0 69 };
michael@0 70
michael@0 71 // Holds the messages that will rotate through the banner.
michael@0 72 let _messages = {};
michael@0 73
michael@0 74 let _sendBannerData = function() {
michael@0 75 let keys = Object.keys(_messages);
michael@0 76 if (!keys.length) {
michael@0 77 return false;
michael@0 78 }
michael@0 79
michael@0 80 // Choose a message at random.
michael@0 81 let randomId = keys[Math.floor(Math.random() * keys.length)];
michael@0 82 let message = _messages[randomId];
michael@0 83
michael@0 84 sendMessageToJava({
michael@0 85 type: "HomeBanner:Data",
michael@0 86 id: message.id,
michael@0 87 text: message.text,
michael@0 88 iconURI: message.iconURI
michael@0 89 });
michael@0 90 return true;
michael@0 91 };
michael@0 92
michael@0 93 let _handleShown = function(id) {
michael@0 94 let message = _messages[id];
michael@0 95 if (message.onshown)
michael@0 96 message.onshown();
michael@0 97 };
michael@0 98
michael@0 99 let _handleClick = function(id) {
michael@0 100 let message = _messages[id];
michael@0 101 if (message.onclick)
michael@0 102 message.onclick();
michael@0 103 };
michael@0 104
michael@0 105 let _handleDismiss = function(id) {
michael@0 106 let message = _messages[id];
michael@0 107 if (message.ondismiss)
michael@0 108 message.ondismiss();
michael@0 109 };
michael@0 110
michael@0 111 return Object.freeze({
michael@0 112 observe: function(subject, topic, data) {
michael@0 113 switch(topic) {
michael@0 114 case "HomeBanner:Shown":
michael@0 115 _handleShown(data);
michael@0 116 break;
michael@0 117
michael@0 118 case "HomeBanner:Click":
michael@0 119 _handleClick(data);
michael@0 120 break;
michael@0 121
michael@0 122 case "HomeBanner:Dismiss":
michael@0 123 _handleDismiss(data);
michael@0 124 break;
michael@0 125 }
michael@0 126 },
michael@0 127
michael@0 128 /**
michael@0 129 * Adds a new banner message to the rotation.
michael@0 130 *
michael@0 131 * @return id Unique identifer for the message.
michael@0 132 */
michael@0 133 add: function(options) {
michael@0 134 let message = new BannerMessage(options);
michael@0 135 _messages[message.id] = message;
michael@0 136
michael@0 137 // If this is the first message we're adding, add
michael@0 138 // observers to listen for requests from the Java UI.
michael@0 139 if (Object.keys(_messages).length == 1) {
michael@0 140 Services.obs.addObserver(this, "HomeBanner:Shown", false);
michael@0 141 Services.obs.addObserver(this, "HomeBanner:Click", false);
michael@0 142 Services.obs.addObserver(this, "HomeBanner:Dismiss", false);
michael@0 143
michael@0 144 // Send a message to Java if there's a pending "HomeBanner:Get" request.
michael@0 145 if (_pendingRequest) {
michael@0 146 _pendingRequest = false;
michael@0 147 _sendBannerData();
michael@0 148 }
michael@0 149 }
michael@0 150
michael@0 151 return message.id;
michael@0 152 },
michael@0 153
michael@0 154 /**
michael@0 155 * Removes a banner message from the rotation.
michael@0 156 *
michael@0 157 * @param id The id of the message to remove.
michael@0 158 */
michael@0 159 remove: function(id) {
michael@0 160 if (!(id in _messages)) {
michael@0 161 throw "Home.banner: Can't remove message that doesn't exist: id = " + id;
michael@0 162 }
michael@0 163
michael@0 164 delete _messages[id];
michael@0 165
michael@0 166 // If there are no more messages, remove the observers.
michael@0 167 if (Object.keys(_messages).length == 0) {
michael@0 168 Services.obs.removeObserver(this, "HomeBanner:Shown");
michael@0 169 Services.obs.removeObserver(this, "HomeBanner:Click");
michael@0 170 Services.obs.removeObserver(this, "HomeBanner:Dismiss");
michael@0 171 }
michael@0 172 }
michael@0 173 });
michael@0 174 })();
michael@0 175
michael@0 176 // We need this object to have access to the HomePanels
michael@0 177 // private members without leaking it outside Home.jsm.
michael@0 178 let HomePanelsMessageHandlers;
michael@0 179
michael@0 180 let HomePanels = (function () {
michael@0 181 // Functions used to handle messages sent from Java.
michael@0 182 HomePanelsMessageHandlers = {
michael@0 183
michael@0 184 "HomePanels:Get": function handlePanelsGet(data) {
michael@0 185 data = JSON.parse(data);
michael@0 186
michael@0 187 let requestId = data.requestId;
michael@0 188 let ids = data.ids || null;
michael@0 189
michael@0 190 let panels = [];
michael@0 191 for (let id in _registeredPanels) {
michael@0 192 // Null ids means we want to fetch all available panels
michael@0 193 if (ids == null || ids.indexOf(id) >= 0) {
michael@0 194 try {
michael@0 195 panels.push(_generatePanel(id));
michael@0 196 } catch(e) {
michael@0 197 Cu.reportError("Home.panels: Invalid options, panel.id = " + id + ": " + e);
michael@0 198 }
michael@0 199 }
michael@0 200 }
michael@0 201
michael@0 202 sendMessageToJava({
michael@0 203 type: "HomePanels:Data",
michael@0 204 panels: panels,
michael@0 205 requestId: requestId
michael@0 206 });
michael@0 207 },
michael@0 208
michael@0 209 "HomePanels:Authenticate": function handlePanelsAuthenticate(id) {
michael@0 210 // Generate panel options to get auth handler.
michael@0 211 let options = _registeredPanels[id]();
michael@0 212 if (!options.auth) {
michael@0 213 throw "Home.panels: Invalid auth for panel.id = " + id;
michael@0 214 }
michael@0 215 if (!options.auth.authenticate || typeof options.auth.authenticate !== "function") {
michael@0 216 throw "Home.panels: Invalid auth authenticate function: panel.id = " + this.id;
michael@0 217 }
michael@0 218 options.auth.authenticate();
michael@0 219 },
michael@0 220
michael@0 221 "HomePanels:RefreshView": function handlePanelsRefreshView(data) {
michael@0 222 data = JSON.parse(data);
michael@0 223
michael@0 224 let options = _registeredPanels[data.panelId]();
michael@0 225 let view = options.views[data.viewIndex];
michael@0 226
michael@0 227 if (!view) {
michael@0 228 throw "Home.panels: Invalid view for panel.id = " + data.panelId
michael@0 229 + ", view.index = " + data.viewIndex;
michael@0 230 }
michael@0 231
michael@0 232 if (!view.onrefresh || typeof view.onrefresh !== "function") {
michael@0 233 throw "Home.panels: Invalid onrefresh for panel.id = " + data.panelId
michael@0 234 + ", view.index = " + data.viewIndex;
michael@0 235 }
michael@0 236
michael@0 237 view.onrefresh();
michael@0 238 },
michael@0 239
michael@0 240 "HomePanels:Installed": function handlePanelsInstalled(id) {
michael@0 241 _assertPanelExists(id);
michael@0 242
michael@0 243 let options = _registeredPanels[id]();
michael@0 244 if (!options.oninstall) {
michael@0 245 return;
michael@0 246 }
michael@0 247 if (typeof options.oninstall !== "function") {
michael@0 248 throw "Home.panels: Invalid oninstall function: panel.id = " + this.id;
michael@0 249 }
michael@0 250 options.oninstall();
michael@0 251 },
michael@0 252
michael@0 253 "HomePanels:Uninstalled": function handlePanelsUninstalled(id) {
michael@0 254 _assertPanelExists(id);
michael@0 255
michael@0 256 let options = _registeredPanels[id]();
michael@0 257 if (!options.onuninstall) {
michael@0 258 return;
michael@0 259 }
michael@0 260 if (typeof options.onuninstall !== "function") {
michael@0 261 throw "Home.panels: Invalid onuninstall function: panel.id = " + this.id;
michael@0 262 }
michael@0 263 options.onuninstall();
michael@0 264 }
michael@0 265 };
michael@0 266
michael@0 267 // Holds the current set of registered panels that can be
michael@0 268 // installed, updated, uninstalled, or unregistered. It maps
michael@0 269 // panel ids with the functions that dynamically generate
michael@0 270 // their respective panel options. This is used to retrieve
michael@0 271 // the current list of available panels in the system.
michael@0 272 // See HomePanels:Get handler.
michael@0 273 let _registeredPanels = {};
michael@0 274
michael@0 275 // Valid layouts for a panel.
michael@0 276 let Layout = Object.freeze({
michael@0 277 FRAME: "frame"
michael@0 278 });
michael@0 279
michael@0 280 // Valid types of views for a dataset.
michael@0 281 let View = Object.freeze({
michael@0 282 LIST: "list",
michael@0 283 GRID: "grid"
michael@0 284 });
michael@0 285
michael@0 286 // Valid item types for a panel view.
michael@0 287 let Item = Object.freeze({
michael@0 288 ARTICLE: "article",
michael@0 289 IMAGE: "image"
michael@0 290 });
michael@0 291
michael@0 292 // Valid item handlers for a panel view.
michael@0 293 let ItemHandler = Object.freeze({
michael@0 294 BROWSER: "browser",
michael@0 295 INTENT: "intent"
michael@0 296 });
michael@0 297
michael@0 298 function Panel(id, options) {
michael@0 299 this.id = id;
michael@0 300 this.title = options.title;
michael@0 301 this.layout = options.layout;
michael@0 302 this.views = options.views;
michael@0 303
michael@0 304 if (!this.id || !this.title) {
michael@0 305 throw "Home.panels: Can't create a home panel without an id and title!";
michael@0 306 }
michael@0 307
michael@0 308 if (!this.layout) {
michael@0 309 // Use FRAME layout by default
michael@0 310 this.layout = Layout.FRAME;
michael@0 311 } else if (!_valueExists(Layout, this.layout)) {
michael@0 312 throw "Home.panels: Invalid layout for panel: panel.id = " + this.id + ", panel.layout =" + this.layout;
michael@0 313 }
michael@0 314
michael@0 315 for (let view of this.views) {
michael@0 316 if (!_valueExists(View, view.type)) {
michael@0 317 throw "Home.panels: Invalid view type: panel.id = " + this.id + ", view.type = " + view.type;
michael@0 318 }
michael@0 319
michael@0 320 if (!view.itemType) {
michael@0 321 if (view.type == View.LIST) {
michael@0 322 // Use ARTICLE item type by default in LIST views
michael@0 323 view.itemType = Item.ARTICLE;
michael@0 324 } else if (view.type == View.GRID) {
michael@0 325 // Use IMAGE item type by default in GRID views
michael@0 326 view.itemType = Item.IMAGE;
michael@0 327 }
michael@0 328 } else if (!_valueExists(Item, view.itemType)) {
michael@0 329 throw "Home.panels: Invalid item type: panel.id = " + this.id + ", view.itemType = " + view.itemType;
michael@0 330 }
michael@0 331
michael@0 332 if (!view.itemHandler) {
michael@0 333 // Use BROWSER item handler by default
michael@0 334 view.itemHandler = ItemHandler.BROWSER;
michael@0 335 } else if (!_valueExists(ItemHandler, view.itemHandler)) {
michael@0 336 throw "Home.panels: Invalid item handler: panel.id = " + this.id + ", view.itemHandler = " + view.itemHandler;
michael@0 337 }
michael@0 338
michael@0 339 if (!view.dataset) {
michael@0 340 throw "Home.panels: No dataset provided for view: panel.id = " + this.id + ", view.type = " + view.type;
michael@0 341 }
michael@0 342
michael@0 343 if (view.onrefresh) {
michael@0 344 view.refreshEnabled = true;
michael@0 345 }
michael@0 346 }
michael@0 347
michael@0 348 if (options.auth) {
michael@0 349 if (!options.auth.messageText) {
michael@0 350 throw "Home.panels: Invalid auth messageText: panel.id = " + this.id;
michael@0 351 }
michael@0 352 if (!options.auth.buttonText) {
michael@0 353 throw "Home.panels: Invalid auth buttonText: panel.id = " + this.id;
michael@0 354 }
michael@0 355
michael@0 356 this.authConfig = {
michael@0 357 messageText: options.auth.messageText,
michael@0 358 buttonText: options.auth.buttonText
michael@0 359 };
michael@0 360
michael@0 361 // Include optional image URL if it is specified.
michael@0 362 if (options.auth.imageUrl) {
michael@0 363 this.authConfig.imageUrl = options.auth.imageUrl;
michael@0 364 }
michael@0 365 }
michael@0 366 }
michael@0 367
michael@0 368 let _generatePanel = function(id) {
michael@0 369 let options = _registeredPanels[id]();
michael@0 370 return new Panel(id, options);
michael@0 371 };
michael@0 372
michael@0 373 // Helper function used to see if a value is in an object.
michael@0 374 let _valueExists = function(obj, value) {
michael@0 375 for (let key in obj) {
michael@0 376 if (obj[key] == value) {
michael@0 377 return true;
michael@0 378 }
michael@0 379 }
michael@0 380 return false;
michael@0 381 };
michael@0 382
michael@0 383 let _assertPanelExists = function(id) {
michael@0 384 if (!(id in _registeredPanels)) {
michael@0 385 throw "Home.panels: Panel doesn't exist: id = " + id;
michael@0 386 }
michael@0 387 };
michael@0 388
michael@0 389 return Object.freeze({
michael@0 390 Layout: Layout,
michael@0 391 View: View,
michael@0 392 Item: Item,
michael@0 393 ItemHandler: ItemHandler,
michael@0 394
michael@0 395 register: function(id, optionsCallback) {
michael@0 396 // Bail if the panel already exists
michael@0 397 if (id in _registeredPanels) {
michael@0 398 throw "Home.panels: Panel already exists: id = " + id;
michael@0 399 }
michael@0 400
michael@0 401 if (!optionsCallback || typeof optionsCallback !== "function") {
michael@0 402 throw "Home.panels: Panel callback must be a function: id = " + id;
michael@0 403 }
michael@0 404
michael@0 405 _registeredPanels[id] = optionsCallback;
michael@0 406 },
michael@0 407
michael@0 408 unregister: function(id) {
michael@0 409 _assertPanelExists(id);
michael@0 410
michael@0 411 delete _registeredPanels[id];
michael@0 412 },
michael@0 413
michael@0 414 install: function(id) {
michael@0 415 _assertPanelExists(id);
michael@0 416
michael@0 417 sendMessageToJava({
michael@0 418 type: "HomePanels:Install",
michael@0 419 panel: _generatePanel(id)
michael@0 420 });
michael@0 421 },
michael@0 422
michael@0 423 uninstall: function(id) {
michael@0 424 _assertPanelExists(id);
michael@0 425
michael@0 426 sendMessageToJava({
michael@0 427 type: "HomePanels:Uninstall",
michael@0 428 id: id
michael@0 429 });
michael@0 430 },
michael@0 431
michael@0 432 update: function(id) {
michael@0 433 _assertPanelExists(id);
michael@0 434
michael@0 435 sendMessageToJava({
michael@0 436 type: "HomePanels:Update",
michael@0 437 panel: _generatePanel(id)
michael@0 438 });
michael@0 439 },
michael@0 440
michael@0 441 setAuthenticated: function(id, isAuthenticated) {
michael@0 442 _assertPanelExists(id);
michael@0 443
michael@0 444 let authKey = PREFS_PANEL_AUTH_PREFIX + id;
michael@0 445 let sharedPrefs = new SharedPreferences();
michael@0 446 sharedPrefs.setBoolPref(authKey, isAuthenticated);
michael@0 447 }
michael@0 448 });
michael@0 449 })();
michael@0 450
michael@0 451 // Public API
michael@0 452 this.Home = Object.freeze({
michael@0 453 banner: HomeBanner,
michael@0 454 panels: HomePanels,
michael@0 455
michael@0 456 // Lazy notification observer registered in browser.js
michael@0 457 observe: function(subject, topic, data) {
michael@0 458 if (topic in HomeBannerMessageHandlers) {
michael@0 459 HomeBannerMessageHandlers[topic](data);
michael@0 460 } else if (topic in HomePanelsMessageHandlers) {
michael@0 461 HomePanelsMessageHandlers[topic](data);
michael@0 462 } else {
michael@0 463 Cu.reportError("Home.observe: message handler not found for topic: " + topic);
michael@0 464 }
michael@0 465 }
michael@0 466 });

mercurial