2013-08-19 17:22:47 -07:00
|
|
|
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
this.EXPORTED_SYMBOLS = ["Home"];
|
|
|
|
|
|
|
|
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
|
|
|
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
2013-12-19 14:51:10 -08:00
|
|
|
Cu.import("resource://gre/modules/SharedPreferences.jsm");
|
2014-02-11 09:16:00 -08:00
|
|
|
Cu.import("resource://gre/modules/Messaging.jsm");
|
2013-08-19 17:22:47 -07:00
|
|
|
|
|
|
|
// See bug 915424
|
|
|
|
function resolveGeckoURI(aURI) {
|
|
|
|
if (!aURI)
|
|
|
|
throw "Can't resolve an empty uri";
|
|
|
|
|
|
|
|
if (aURI.startsWith("chrome://")) {
|
|
|
|
let registry = Cc['@mozilla.org/chrome/chrome-registry;1'].getService(Ci["nsIChromeRegistry"]);
|
|
|
|
return registry.convertChromeURL(Services.io.newURI(aURI, null, null)).spec;
|
|
|
|
} else if (aURI.startsWith("resource://")) {
|
|
|
|
let handler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
|
|
|
|
return handler.resolveURI(Services.io.newURI(aURI, null, null));
|
|
|
|
}
|
|
|
|
return aURI;
|
|
|
|
}
|
|
|
|
|
|
|
|
function BannerMessage(options) {
|
|
|
|
let uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
|
|
|
this.id = uuidgen.generateUUID().toString();
|
|
|
|
|
|
|
|
if ("text" in options && options.text != null)
|
|
|
|
this.text = options.text;
|
|
|
|
|
|
|
|
if ("icon" in options && options.icon != null)
|
|
|
|
this.iconURI = resolveGeckoURI(options.icon);
|
|
|
|
|
2013-11-04 14:39:00 -08:00
|
|
|
if ("onshown" in options && typeof options.onshown === "function")
|
|
|
|
this.onshown = options.onshown;
|
|
|
|
|
2013-08-19 17:22:47 -07:00
|
|
|
if ("onclick" in options && typeof options.onclick === "function")
|
|
|
|
this.onclick = options.onclick;
|
2014-02-14 16:51:43 -08:00
|
|
|
|
|
|
|
if ("ondismiss" in options && typeof options.ondismiss === "function")
|
|
|
|
this.ondismiss = options.ondismiss;
|
2013-08-19 17:22:47 -07:00
|
|
|
}
|
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
let HomeBanner = (function () {
|
2013-08-19 17:22:47 -07:00
|
|
|
// Holds the messages that will rotate through the banner.
|
2014-02-14 08:52:14 -08:00
|
|
|
let _messages = {};
|
2013-08-19 17:22:47 -07:00
|
|
|
|
|
|
|
// A queue used to keep track of which message to show next.
|
2014-02-14 08:52:14 -08:00
|
|
|
let _queue = [];
|
2013-08-19 17:22:47 -07:00
|
|
|
|
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
let _handleGet = function() {
|
2013-08-19 17:22:47 -07:00
|
|
|
// Get the message from the front of the queue, then add it back
|
|
|
|
// to the end of the queue to show it again later.
|
2014-02-14 08:52:14 -08:00
|
|
|
let id = _queue.shift();
|
|
|
|
_queue.push(id);
|
2013-08-19 17:22:47 -07:00
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
let message = _messages[id];
|
2013-08-19 17:22:47 -07:00
|
|
|
sendMessageToJava({
|
|
|
|
type: "HomeBanner:Data",
|
|
|
|
id: message.id,
|
|
|
|
text: message.text,
|
|
|
|
iconURI: message.iconURI
|
|
|
|
});
|
2013-11-04 14:39:00 -08:00
|
|
|
|
|
|
|
if (message.onshown)
|
|
|
|
message.onshown();
|
2014-02-14 08:52:14 -08:00
|
|
|
};
|
2013-08-19 17:22:47 -07:00
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
let _handleClick = function(id) {
|
|
|
|
let message = _messages[id];
|
2013-08-19 17:22:47 -07:00
|
|
|
if (message.onclick)
|
|
|
|
message.onclick();
|
2014-02-14 08:52:14 -08:00
|
|
|
};
|
|
|
|
|
2014-02-14 16:51:43 -08:00
|
|
|
let _handleDismiss = function(id) {
|
|
|
|
let message = _messages[id];
|
|
|
|
if (message.ondismiss)
|
|
|
|
message.ondismiss();
|
|
|
|
};
|
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
return Object.freeze({
|
|
|
|
observe: function(subject, topic, data) {
|
|
|
|
switch(topic) {
|
|
|
|
case "HomeBanner:Get":
|
|
|
|
_handleGet();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "HomeBanner:Click":
|
|
|
|
_handleClick(data);
|
|
|
|
break;
|
2014-02-14 16:51:43 -08:00
|
|
|
|
|
|
|
case "HomeBanner:Dismiss":
|
|
|
|
_handleDismiss(data);
|
|
|
|
break;
|
2014-02-14 08:52:14 -08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a new banner message to the rotation.
|
|
|
|
*
|
|
|
|
* @return id Unique identifer for the message.
|
|
|
|
*/
|
|
|
|
add: function(options) {
|
|
|
|
let message = new BannerMessage(options);
|
|
|
|
_messages[message.id] = message;
|
|
|
|
|
|
|
|
// Add the new message to the end of the queue.
|
|
|
|
_queue.push(message.id);
|
|
|
|
|
|
|
|
// If this is the first message we're adding, add
|
|
|
|
// observers to listen for requests from the Java UI.
|
|
|
|
if (Object.keys(_messages).length == 1) {
|
|
|
|
Services.obs.addObserver(this, "HomeBanner:Get", false);
|
|
|
|
Services.obs.addObserver(this, "HomeBanner:Click", false);
|
2014-02-14 16:51:43 -08:00
|
|
|
Services.obs.addObserver(this, "HomeBanner:Dismiss", false);
|
2014-02-14 08:52:14 -08:00
|
|
|
|
|
|
|
// Send a message to Java, in case there's an active HomeBanner
|
|
|
|
// waiting for a response.
|
|
|
|
_handleGet();
|
|
|
|
}
|
2013-08-19 17:22:47 -07:00
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
return message.id;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes a banner message from the rotation.
|
|
|
|
*
|
|
|
|
* @param id The id of the message to remove.
|
|
|
|
*/
|
|
|
|
remove: function(id) {
|
2014-02-22 09:41:31 -08:00
|
|
|
if (!(id in _messages)) {
|
|
|
|
throw "Home.banner: Can't remove message that doesn't exist: id = " + id;
|
|
|
|
}
|
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
delete _messages[id];
|
|
|
|
|
|
|
|
// Remove the message from the queue.
|
|
|
|
let index = _queue.indexOf(id);
|
|
|
|
_queue.splice(index, 1);
|
|
|
|
|
|
|
|
// If there are no more messages, remove the observers.
|
|
|
|
if (Object.keys(_messages).length == 0) {
|
|
|
|
Services.obs.removeObserver(this, "HomeBanner:Get");
|
|
|
|
Services.obs.removeObserver(this, "HomeBanner:Click");
|
2014-02-14 16:51:43 -08:00
|
|
|
Services.obs.removeObserver(this, "HomeBanner:Dismiss");
|
2014-02-14 08:52:14 -08:00
|
|
|
}
|
2013-08-19 17:22:47 -07:00
|
|
|
}
|
2014-02-14 08:52:14 -08:00
|
|
|
});
|
|
|
|
})();
|
2013-08-19 17:22:47 -07:00
|
|
|
|
2014-01-10 12:57:23 -08:00
|
|
|
function Panel(options) {
|
2013-12-19 14:51:10 -08:00
|
|
|
if ("id" in options)
|
|
|
|
this.id = options.id;
|
|
|
|
|
|
|
|
if ("title" in options)
|
|
|
|
this.title = options.title;
|
2014-01-17 09:27:07 -08:00
|
|
|
|
|
|
|
if ("layout" in options)
|
|
|
|
this.layout = options.layout;
|
|
|
|
|
|
|
|
if ("views" in options)
|
|
|
|
this.views = options.views;
|
2013-12-19 14:51:10 -08:00
|
|
|
}
|
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
let HomePanels = (function () {
|
2014-01-14 15:08:10 -08:00
|
|
|
// Holds the currrent set of registered panels.
|
2014-02-14 08:52:14 -08:00
|
|
|
let _panels = {};
|
2013-12-19 14:51:10 -08:00
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
let _panelToJSON = function(panel) {
|
2014-02-05 06:14:51 -08:00
|
|
|
return {
|
|
|
|
id: panel.id,
|
|
|
|
title: panel.title,
|
|
|
|
layout: panel.layout,
|
|
|
|
views: panel.views
|
|
|
|
};
|
2014-02-14 08:52:14 -08:00
|
|
|
};
|
2014-02-05 06:14:51 -08:00
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
let _handleGet = function(data) {
|
2014-02-05 06:14:52 -08:00
|
|
|
let requestId = data.requestId;
|
|
|
|
let ids = data.ids || null;
|
|
|
|
|
2014-01-14 15:08:10 -08:00
|
|
|
let panels = [];
|
2014-02-14 08:52:14 -08:00
|
|
|
for (let id in _panels) {
|
|
|
|
let panel = _panels[id];
|
2014-02-05 06:14:52 -08:00
|
|
|
|
|
|
|
// Null ids means we want to fetch all available panels
|
|
|
|
if (ids == null || ids.indexOf(panel.id) >= 0) {
|
2014-02-14 08:52:14 -08:00
|
|
|
panels.push(_panelToJSON(panel));
|
2014-02-05 06:14:52 -08:00
|
|
|
}
|
2014-01-14 15:08:10 -08:00
|
|
|
}
|
2013-12-19 14:51:10 -08:00
|
|
|
|
2014-01-14 15:08:10 -08:00
|
|
|
sendMessageToJava({
|
|
|
|
type: "HomePanels:Data",
|
|
|
|
panels: panels,
|
|
|
|
requestId: requestId
|
|
|
|
});
|
2014-02-14 08:52:14 -08:00
|
|
|
};
|
2014-02-13 03:19:57 -08:00
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
// Helper function used to see if a value is in an object.
|
|
|
|
let _valueExists = function(obj, value) {
|
|
|
|
for (let key in obj) {
|
|
|
|
if (obj[key] == value) {
|
|
|
|
return true;
|
|
|
|
}
|
2014-01-17 09:27:07 -08:00
|
|
|
}
|
2014-02-14 08:52:14 -08:00
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2014-02-21 02:33:59 -08:00
|
|
|
let _assertPanelExists = function(id) {
|
|
|
|
if (!(id in _panels)) {
|
|
|
|
throw "Home.panels: Panel doesn't exist: id = " + id;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
return Object.freeze({
|
|
|
|
// Valid layouts for a panel.
|
|
|
|
Layout: Object.freeze({
|
|
|
|
FRAME: "frame"
|
|
|
|
}),
|
|
|
|
|
|
|
|
// Valid types of views for a dataset.
|
|
|
|
View: Object.freeze({
|
|
|
|
LIST: "list",
|
|
|
|
GRID: "grid"
|
|
|
|
}),
|
|
|
|
|
2014-02-19 09:36:59 -08:00
|
|
|
// Valid item types for a panel view.
|
|
|
|
Item: Object.freeze({
|
|
|
|
ARTICLE: "article",
|
|
|
|
IMAGE: "image"
|
|
|
|
}),
|
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
// Valid item handlers for a panel view.
|
|
|
|
ItemHandler: Object.freeze({
|
|
|
|
BROWSER: "browser",
|
|
|
|
INTENT: "intent"
|
|
|
|
}),
|
|
|
|
|
2014-02-21 02:33:59 -08:00
|
|
|
register: function(options) {
|
2014-02-14 08:52:14 -08:00
|
|
|
let panel = new Panel(options);
|
2014-01-17 09:27:07 -08:00
|
|
|
|
2014-02-21 02:33:59 -08:00
|
|
|
// Bail if the panel already exists
|
|
|
|
if (panel.id in _panels) {
|
2014-02-14 08:52:14 -08:00
|
|
|
throw "Home.panels: Panel already exists: id = " + panel.id;
|
2014-01-17 09:27:07 -08:00
|
|
|
}
|
|
|
|
|
2014-02-21 02:33:59 -08:00
|
|
|
if (!panel.id || !panel.title) {
|
|
|
|
throw "Home.panels: Can't create a home panel without an id and title!";
|
|
|
|
}
|
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
if (!_valueExists(this.Layout, panel.layout)) {
|
|
|
|
throw "Home.panels: Invalid layout for panel: panel.id = " + panel.id + ", panel.layout =" + panel.layout;
|
2014-02-13 10:42:33 -08:00
|
|
|
}
|
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
for (let view of panel.views) {
|
|
|
|
if (!_valueExists(this.View, view.type)) {
|
|
|
|
throw "Home.panels: Invalid view type: panel.id = " + panel.id + ", view.type = " + view.type;
|
|
|
|
}
|
|
|
|
|
2014-02-19 09:36:59 -08:00
|
|
|
if (!view.itemType) {
|
|
|
|
if (view.type == this.View.LIST) {
|
|
|
|
// Use ARTICLE item type by default in LIST views
|
|
|
|
view.itemType = this.Item.ARTICLE;
|
|
|
|
} else if (view.type == this.View.GRID) {
|
|
|
|
// Use IMAGE item type by default in GRID views
|
|
|
|
view.itemType = this.Item.IMAGE;
|
|
|
|
}
|
|
|
|
} else if (!_valueExists(this.Item, view.itemType)) {
|
|
|
|
throw "Home.panels: Invalid item type: panel.id = " + panel.id + ", view.itemType = " + view.itemType;
|
|
|
|
}
|
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
if (!view.itemHandler) {
|
|
|
|
// Use BROWSER item handler by default
|
|
|
|
view.itemHandler = this.ItemHandler.BROWSER;
|
|
|
|
} else if (!_valueExists(this.ItemHandler, view.itemHandler)) {
|
|
|
|
throw "Home.panels: Invalid item handler: panel.id = " + panel.id + ", view.itemHandler = " + view.itemHandler;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!view.dataset) {
|
|
|
|
throw "Home.panels: No dataset provided for view: panel.id = " + panel.id + ", view.type = " + view.type;
|
|
|
|
}
|
2014-01-17 09:27:07 -08:00
|
|
|
}
|
2013-12-19 14:51:10 -08:00
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
_panels[panel.id] = panel;
|
2014-02-21 02:33:59 -08:00
|
|
|
},
|
2014-02-05 06:14:51 -08:00
|
|
|
|
2014-02-21 02:33:59 -08:00
|
|
|
unregister: function(id) {
|
|
|
|
_assertPanelExists(id);
|
2014-02-13 03:19:57 -08:00
|
|
|
|
2014-02-21 02:33:59 -08:00
|
|
|
delete _panels[id];
|
|
|
|
},
|
2014-02-13 03:19:57 -08:00
|
|
|
|
2014-02-21 02:33:59 -08:00
|
|
|
install: function(id) {
|
|
|
|
_assertPanelExists(id);
|
2014-02-13 03:19:57 -08:00
|
|
|
|
2014-02-21 02:33:59 -08:00
|
|
|
sendMessageToJava({
|
|
|
|
type: "HomePanels:Install",
|
|
|
|
panel: _panelToJSON(_panels[id])
|
|
|
|
});
|
2014-02-14 08:52:14 -08:00
|
|
|
},
|
2013-12-19 14:51:10 -08:00
|
|
|
|
2014-02-21 02:33:59 -08:00
|
|
|
uninstall: function(id) {
|
|
|
|
_assertPanelExists(id);
|
2013-12-19 14:51:10 -08:00
|
|
|
|
2014-02-14 08:52:14 -08:00
|
|
|
sendMessageToJava({
|
2014-02-21 02:33:58 -08:00
|
|
|
type: "HomePanels:Uninstall",
|
2014-02-21 02:33:59 -08:00
|
|
|
id: id
|
2014-02-14 08:52:14 -08:00
|
|
|
});
|
2014-02-21 02:33:59 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
update: function(id) {
|
|
|
|
_assertPanelExists(id);
|
|
|
|
|
|
|
|
sendMessageToJava({
|
|
|
|
type: "HomePanels:Update",
|
|
|
|
panel: _panelToJSON(_panels[id])
|
|
|
|
});
|
2014-01-17 09:27:07 -08:00
|
|
|
}
|
2014-02-14 08:52:14 -08:00
|
|
|
});
|
|
|
|
})();
|
2013-12-19 14:51:10 -08:00
|
|
|
|
2013-08-19 17:22:47 -07:00
|
|
|
// Public API
|
2014-02-08 18:02:27 -08:00
|
|
|
this.Home = Object.freeze({
|
2013-12-19 14:51:10 -08:00
|
|
|
banner: HomeBanner,
|
2014-01-14 15:08:10 -08:00
|
|
|
panels: HomePanels,
|
|
|
|
|
|
|
|
// Lazy notification observer registered in browser.js
|
|
|
|
observe: function(subject, topic, data) {
|
|
|
|
switch(topic) {
|
|
|
|
case "HomePanels:Get":
|
2014-02-05 06:14:52 -08:00
|
|
|
HomePanels._handleGet(JSON.parse(data));
|
2014-01-14 15:08:10 -08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-02-08 18:02:27 -08:00
|
|
|
});
|