mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
4d6a633bba
The -*- file variable lines -*- establish per-file settings that Emacs will pick up. This patch makes the following changes to those lines (and touches nothing else): - Never set the buffer's mode. Years ago, Emacs did not have a good JavaScript mode, so it made sense to use Java or C++ mode in .js files. However, Emacs has had js-mode for years now; it's perfectly serviceable, and is available and enabled by default in all major Emacs packagings. Selecting a mode in the -*- file variable line -*- is almost always the wrong thing to do anyway. It overrides Emacs's default choice, which is (now) reasonable; and even worse, it overrides settings the user might have made in their '.emacs' file for that file extension. It's only useful when there's something specific about that particular file that makes a particular mode appropriate. - Correctly propagate settings that establish the correct indentation level for this file: c-basic-offset and js2-basic-offset should be js-indent-level. Whatever value they're given should be preserved; different parts of our tree use different indentation styles. - We don't use tabs in Mozilla JS code. Always set indent-tabs-mode: nil. Remove tab-width: settings, at least in files that don't contain tab characters. - Remove js2-mode settings that belong in the user's .emacs file, like js2-skip-preprocessor-directives.
482 lines
14 KiB
JavaScript
482 lines
14 KiB
JavaScript
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
|
/* 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");
|
|
Cu.import("resource://gre/modules/SharedPreferences.jsm");
|
|
Cu.import("resource://gre/modules/Messaging.jsm");
|
|
|
|
// Keep this in sync with the constant defined in PanelAuthCache.java
|
|
const PREFS_PANEL_AUTH_PREFIX = "home_panels_auth_";
|
|
|
|
// Default weight for a banner message.
|
|
const DEFAULT_WEIGHT = 100;
|
|
|
|
// 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);
|
|
|
|
if ("onshown" in options && typeof options.onshown === "function")
|
|
this.onshown = options.onshown;
|
|
|
|
if ("onclick" in options && typeof options.onclick === "function")
|
|
this.onclick = options.onclick;
|
|
|
|
if ("ondismiss" in options && typeof options.ondismiss === "function")
|
|
this.ondismiss = options.ondismiss;
|
|
|
|
let weight = parseInt(options.weight, 10);
|
|
this.weight = weight > 0 ? weight : DEFAULT_WEIGHT;
|
|
}
|
|
|
|
// We need this object to have access to the HomeBanner
|
|
// private members without leaking it outside Home.jsm.
|
|
let HomeBannerMessageHandlers;
|
|
|
|
let HomeBanner = (function () {
|
|
// Whether there is a "HomeBanner:Get" request we couldn't fulfill.
|
|
let _pendingRequest = false;
|
|
|
|
// Functions used to handle messages sent from Java.
|
|
HomeBannerMessageHandlers = {
|
|
"HomeBanner:Get": function handleBannerGet(data) {
|
|
if (Object.keys(_messages).length > 0) {
|
|
_sendBannerData();
|
|
} else {
|
|
_pendingRequest = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Holds the messages that will rotate through the banner.
|
|
let _messages = {};
|
|
|
|
// Choose a random message from the set of messages, biasing towards those with higher weight.
|
|
// Weight logic copied from desktop snippets:
|
|
// https://github.com/mozilla/snippets-service/blob/7d80edb8b1cddaed075275c2fc7cdf69a10f4003/snippets/base/templates/base/includes/snippet_js.html#L119
|
|
let _sendBannerData = function() {
|
|
let totalWeight = 0;
|
|
for (let key in _messages) {
|
|
let message = _messages[key];
|
|
totalWeight += message.weight;
|
|
message.totalWeight = totalWeight;
|
|
}
|
|
|
|
let threshold = Math.random() * totalWeight;
|
|
for (let key in _messages) {
|
|
let message = _messages[key];
|
|
if (threshold < message.totalWeight) {
|
|
sendMessageToJava({
|
|
type: "HomeBanner:Data",
|
|
id: message.id,
|
|
text: message.text,
|
|
iconURI: message.iconURI
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
let _handleShown = function(id) {
|
|
let message = _messages[id];
|
|
if (message.onshown)
|
|
message.onshown();
|
|
};
|
|
|
|
let _handleClick = function(id) {
|
|
let message = _messages[id];
|
|
if (message.onclick)
|
|
message.onclick();
|
|
};
|
|
|
|
let _handleDismiss = function(id) {
|
|
let message = _messages[id];
|
|
if (message.ondismiss)
|
|
message.ondismiss();
|
|
};
|
|
|
|
return Object.freeze({
|
|
observe: function(subject, topic, data) {
|
|
switch(topic) {
|
|
case "HomeBanner:Shown":
|
|
_handleShown(data);
|
|
break;
|
|
|
|
case "HomeBanner:Click":
|
|
_handleClick(data);
|
|
break;
|
|
|
|
case "HomeBanner:Dismiss":
|
|
_handleDismiss(data);
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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;
|
|
|
|
// 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:Shown", false);
|
|
Services.obs.addObserver(this, "HomeBanner:Click", false);
|
|
Services.obs.addObserver(this, "HomeBanner:Dismiss", false);
|
|
|
|
// Send a message to Java if there's a pending "HomeBanner:Get" request.
|
|
if (_pendingRequest) {
|
|
_pendingRequest = false;
|
|
_sendBannerData();
|
|
}
|
|
}
|
|
|
|
return message.id;
|
|
},
|
|
|
|
/**
|
|
* Removes a banner message from the rotation.
|
|
*
|
|
* @param id The id of the message to remove.
|
|
*/
|
|
remove: function(id) {
|
|
if (!(id in _messages)) {
|
|
throw "Home.banner: Can't remove message that doesn't exist: id = " + id;
|
|
}
|
|
|
|
delete _messages[id];
|
|
|
|
// If there are no more messages, remove the observers.
|
|
if (Object.keys(_messages).length == 0) {
|
|
Services.obs.removeObserver(this, "HomeBanner:Shown");
|
|
Services.obs.removeObserver(this, "HomeBanner:Click");
|
|
Services.obs.removeObserver(this, "HomeBanner:Dismiss");
|
|
}
|
|
}
|
|
});
|
|
})();
|
|
|
|
// We need this object to have access to the HomePanels
|
|
// private members without leaking it outside Home.jsm.
|
|
let HomePanelsMessageHandlers;
|
|
|
|
let HomePanels = (function () {
|
|
// Functions used to handle messages sent from Java.
|
|
HomePanelsMessageHandlers = {
|
|
|
|
"HomePanels:Get": function handlePanelsGet(data) {
|
|
data = JSON.parse(data);
|
|
|
|
let requestId = data.requestId;
|
|
let ids = data.ids || null;
|
|
|
|
let panels = [];
|
|
for (let id in _registeredPanels) {
|
|
// Null ids means we want to fetch all available panels
|
|
if (ids == null || ids.indexOf(id) >= 0) {
|
|
try {
|
|
panels.push(_generatePanel(id));
|
|
} catch(e) {
|
|
Cu.reportError("Home.panels: Invalid options, panel.id = " + id + ": " + e);
|
|
}
|
|
}
|
|
}
|
|
|
|
sendMessageToJava({
|
|
type: "HomePanels:Data",
|
|
panels: panels,
|
|
requestId: requestId
|
|
});
|
|
},
|
|
|
|
"HomePanels:Authenticate": function handlePanelsAuthenticate(id) {
|
|
// Generate panel options to get auth handler.
|
|
let options = _registeredPanels[id]();
|
|
if (!options.auth) {
|
|
throw "Home.panels: Invalid auth for panel.id = " + id;
|
|
}
|
|
if (!options.auth.authenticate || typeof options.auth.authenticate !== "function") {
|
|
throw "Home.panels: Invalid auth authenticate function: panel.id = " + this.id;
|
|
}
|
|
options.auth.authenticate();
|
|
},
|
|
|
|
"HomePanels:RefreshView": function handlePanelsRefreshView(data) {
|
|
data = JSON.parse(data);
|
|
|
|
let options = _registeredPanels[data.panelId]();
|
|
let view = options.views[data.viewIndex];
|
|
|
|
if (!view) {
|
|
throw "Home.panels: Invalid view for panel.id = " + data.panelId
|
|
+ ", view.index = " + data.viewIndex;
|
|
}
|
|
|
|
if (!view.onrefresh || typeof view.onrefresh !== "function") {
|
|
throw "Home.panels: Invalid onrefresh for panel.id = " + data.panelId
|
|
+ ", view.index = " + data.viewIndex;
|
|
}
|
|
|
|
view.onrefresh();
|
|
},
|
|
|
|
"HomePanels:Installed": function handlePanelsInstalled(id) {
|
|
_assertPanelExists(id);
|
|
|
|
let options = _registeredPanels[id]();
|
|
if (!options.oninstall) {
|
|
return;
|
|
}
|
|
if (typeof options.oninstall !== "function") {
|
|
throw "Home.panels: Invalid oninstall function: panel.id = " + this.id;
|
|
}
|
|
options.oninstall();
|
|
},
|
|
|
|
"HomePanels:Uninstalled": function handlePanelsUninstalled(id) {
|
|
_assertPanelExists(id);
|
|
|
|
let options = _registeredPanels[id]();
|
|
if (!options.onuninstall) {
|
|
return;
|
|
}
|
|
if (typeof options.onuninstall !== "function") {
|
|
throw "Home.panels: Invalid onuninstall function: panel.id = " + this.id;
|
|
}
|
|
options.onuninstall();
|
|
}
|
|
};
|
|
|
|
// Holds the current set of registered panels that can be
|
|
// installed, updated, uninstalled, or unregistered. It maps
|
|
// panel ids with the functions that dynamically generate
|
|
// their respective panel options. This is used to retrieve
|
|
// the current list of available panels in the system.
|
|
// See HomePanels:Get handler.
|
|
let _registeredPanels = {};
|
|
|
|
// Valid layouts for a panel.
|
|
let Layout = Object.freeze({
|
|
FRAME: "frame"
|
|
});
|
|
|
|
// Valid types of views for a dataset.
|
|
let View = Object.freeze({
|
|
LIST: "list",
|
|
GRID: "grid"
|
|
});
|
|
|
|
// Valid item types for a panel view.
|
|
let Item = Object.freeze({
|
|
ARTICLE: "article",
|
|
IMAGE: "image"
|
|
});
|
|
|
|
// Valid item handlers for a panel view.
|
|
let ItemHandler = Object.freeze({
|
|
BROWSER: "browser",
|
|
INTENT: "intent"
|
|
});
|
|
|
|
function Panel(id, options) {
|
|
this.id = id;
|
|
this.title = options.title;
|
|
this.layout = options.layout;
|
|
this.views = options.views;
|
|
|
|
if (!this.id || !this.title) {
|
|
throw "Home.panels: Can't create a home panel without an id and title!";
|
|
}
|
|
|
|
if (!this.layout) {
|
|
// Use FRAME layout by default
|
|
this.layout = Layout.FRAME;
|
|
} else if (!_valueExists(Layout, this.layout)) {
|
|
throw "Home.panels: Invalid layout for panel: panel.id = " + this.id + ", panel.layout =" + this.layout;
|
|
}
|
|
|
|
for (let view of this.views) {
|
|
if (!_valueExists(View, view.type)) {
|
|
throw "Home.panels: Invalid view type: panel.id = " + this.id + ", view.type = " + view.type;
|
|
}
|
|
|
|
if (!view.itemType) {
|
|
if (view.type == View.LIST) {
|
|
// Use ARTICLE item type by default in LIST views
|
|
view.itemType = Item.ARTICLE;
|
|
} else if (view.type == View.GRID) {
|
|
// Use IMAGE item type by default in GRID views
|
|
view.itemType = Item.IMAGE;
|
|
}
|
|
} else if (!_valueExists(Item, view.itemType)) {
|
|
throw "Home.panels: Invalid item type: panel.id = " + this.id + ", view.itemType = " + view.itemType;
|
|
}
|
|
|
|
if (!view.itemHandler) {
|
|
// Use BROWSER item handler by default
|
|
view.itemHandler = ItemHandler.BROWSER;
|
|
} else if (!_valueExists(ItemHandler, view.itemHandler)) {
|
|
throw "Home.panels: Invalid item handler: panel.id = " + this.id + ", view.itemHandler = " + view.itemHandler;
|
|
}
|
|
|
|
if (!view.dataset) {
|
|
throw "Home.panels: No dataset provided for view: panel.id = " + this.id + ", view.type = " + view.type;
|
|
}
|
|
|
|
if (view.onrefresh) {
|
|
view.refreshEnabled = true;
|
|
}
|
|
}
|
|
|
|
if (options.auth) {
|
|
if (!options.auth.messageText) {
|
|
throw "Home.panels: Invalid auth messageText: panel.id = " + this.id;
|
|
}
|
|
if (!options.auth.buttonText) {
|
|
throw "Home.panels: Invalid auth buttonText: panel.id = " + this.id;
|
|
}
|
|
|
|
this.authConfig = {
|
|
messageText: options.auth.messageText,
|
|
buttonText: options.auth.buttonText
|
|
};
|
|
|
|
// Include optional image URL if it is specified.
|
|
if (options.auth.imageUrl) {
|
|
this.authConfig.imageUrl = options.auth.imageUrl;
|
|
}
|
|
}
|
|
}
|
|
|
|
let _generatePanel = function(id) {
|
|
let options = _registeredPanels[id]();
|
|
return new Panel(id, options);
|
|
};
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
let _assertPanelExists = function(id) {
|
|
if (!(id in _registeredPanels)) {
|
|
throw "Home.panels: Panel doesn't exist: id = " + id;
|
|
}
|
|
};
|
|
|
|
return Object.freeze({
|
|
Layout: Layout,
|
|
View: View,
|
|
Item: Item,
|
|
ItemHandler: ItemHandler,
|
|
|
|
register: function(id, optionsCallback) {
|
|
// Bail if the panel already exists
|
|
if (id in _registeredPanels) {
|
|
throw "Home.panels: Panel already exists: id = " + id;
|
|
}
|
|
|
|
if (!optionsCallback || typeof optionsCallback !== "function") {
|
|
throw "Home.panels: Panel callback must be a function: id = " + id;
|
|
}
|
|
|
|
_registeredPanels[id] = optionsCallback;
|
|
},
|
|
|
|
unregister: function(id) {
|
|
_assertPanelExists(id);
|
|
|
|
delete _registeredPanels[id];
|
|
},
|
|
|
|
install: function(id) {
|
|
_assertPanelExists(id);
|
|
|
|
sendMessageToJava({
|
|
type: "HomePanels:Install",
|
|
panel: _generatePanel(id)
|
|
});
|
|
},
|
|
|
|
uninstall: function(id) {
|
|
_assertPanelExists(id);
|
|
|
|
sendMessageToJava({
|
|
type: "HomePanels:Uninstall",
|
|
id: id
|
|
});
|
|
},
|
|
|
|
update: function(id) {
|
|
_assertPanelExists(id);
|
|
|
|
sendMessageToJava({
|
|
type: "HomePanels:Update",
|
|
panel: _generatePanel(id)
|
|
});
|
|
},
|
|
|
|
setAuthenticated: function(id, isAuthenticated) {
|
|
_assertPanelExists(id);
|
|
|
|
let authKey = PREFS_PANEL_AUTH_PREFIX + id;
|
|
let sharedPrefs = SharedPreferences.forProfile();
|
|
sharedPrefs.setBoolPref(authKey, isAuthenticated);
|
|
}
|
|
});
|
|
})();
|
|
|
|
// Public API
|
|
this.Home = Object.freeze({
|
|
banner: HomeBanner,
|
|
panels: HomePanels,
|
|
|
|
// Lazy notification observer registered in browser.js
|
|
observe: function(subject, topic, data) {
|
|
if (topic in HomeBannerMessageHandlers) {
|
|
HomeBannerMessageHandlers[topic](data);
|
|
} else if (topic in HomePanelsMessageHandlers) {
|
|
HomePanelsMessageHandlers[topic](data);
|
|
} else {
|
|
Cu.reportError("Home.observe: message handler not found for topic: " + topic);
|
|
}
|
|
}
|
|
});
|