mobile/android/modules/Home.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial