Added SkinFeedPlugin in place of SkinSvc, simplifying things greatly.

This commit is contained in:
satyr 2010-03-18 05:41:03 +09:00
Родитель 670de04680
Коммит af727561cf
9 изменённых файлов: 315 добавлений и 505 удалений

Просмотреть файл

@ -159,7 +159,7 @@ CmdUtils.CreateCommand({
argument: noun_type_skin,
execute: function chskin_execute({object: {data: skin}}) {
if (skin) {
UbiquitySetup.createServices().skinService.changeSkin(skin.localUrl);
skin.pick();
Utils.tabs.reload(/^about:ubiquity\?settings\b/);
}
else Utils.focusUrlInBrowser(Settings);

Просмотреть файл

@ -66,6 +66,10 @@ var {escapeHtml} = Utils;
var {feedManager, commandSource, messageService} = (
UbiquitySetup.createServices());
function getFeeds(type) [
feed for each (feed in feedManager["get" + type + "Feeds"]())
if ("commands" in feed)];
function A(url, text, className, attrs) {
var a = document.createElement("a");
a.href = url;
@ -231,12 +235,11 @@ function fillTableRowForCmd(row, cmd, className) {
function updateSubscribedCount() {
$("#num-commands").html(commandSource.commandNames.length);
$("#num-subscribed-feeds").html(feedManager.getSubscribedFeeds().length);
$("#num-subscribed-feeds").text(getFeeds("Subscribed").length);
}
function updateUnsubscribedCount() {
$("#num-unsubscribed-feeds").html(
feedManager.getUnsubscribedFeeds().length);
$("#num-unsubscribed-feeds").text(getFeeds("Unsubscribed").length);
}
function buildTable() {
@ -267,10 +270,8 @@ function buildTable() {
function addCmdToTable(cmd) {
let aRow = $("<tr></tr>");
let feedCell = $("<td></td>");
let feed = getFeedForCommand(feedManager, cmd);
if (feed) {
fillTableCellForFeed(feedCell, feed);
}
let feed = feedManager.getFeedForUrl(cmd.feedUri);
if (feed) fillTableCellForFeed(feedCell, feed);
aRow.append(feedCell);
fillTableRowForCmd(aRow, cmd);
table.append(aRow);
@ -279,7 +280,7 @@ function buildTable() {
updateSubscribedCount();
if (/^feed/.test(sortMode))
(feedManager.getSubscribedFeeds()
(getFeeds("Subscribed")
.sort(/date$/.test(sortMode) ? byDate : byTitle)
.forEach(addFeedToTable));
else
@ -315,14 +316,6 @@ function sortCmdListBy(cmdList, key) {
return cmdList.sort(key === "enabled" ? checksort : alphasort);
}
function getFeedForCommand(feedManager, cmd) {
// This is a really hacky implementation -- it involves going through
// all feeds looking for one containing a command with a matching name.
for each (let feed in feedManager.getSubscribedFeeds())
if (cmd.id in (feed.commands || {})) return feed;
return null;
}
// Bind this to checkbox "change".
function onDisableOrEnableCmd() {
// update the preferences, when the user toggles the active

Просмотреть файл

@ -45,6 +45,7 @@ Cu.import("resource://ubiquity/modules/localization_utils.js");
var L = LocalizationUtils.propertySelector(
"chrome://ubiquity/locale/aboutubiquity.properties");
var H = Utils.escapeHtml;
var gPrefs = Utils.prefs;

Просмотреть файл

@ -42,7 +42,6 @@ Cu.import("resource://ubiquity/modules/prefkeys.js")
const PREF_NFE = "extensions.ubiquity.doNounFirstExternals";
var {skinService, messageService} = UbiquitySetup.createServices();
var {escapeHtml} = Utils;
$(onDocumentLoad);
@ -117,107 +116,76 @@ function changeExternalCallSettings() {
}
function loadSkinList() {
var {CUSTOM_SKIN, currentSkin, skinList} = skinService;
var $list = $("#skin-list").empty();
var i = 0;
for each (let skin in skinList)
if (skin.localUrl === CUSTOM_SKIN)
var customSkin = skin;
else
$list.append(createSkinElement(skin, i++));
$list.append(createSkinElement(customSkin, i));
checkSkin(currentSkin);
// If current skin is custom skin, auto-open the editor
if (currentSkin === CUSTOM_SKIN)
openSkinEditor();
var {skins, currentSkin} = skinService;
var $list = $("#skin-list").empty(), id = -1;
for each (let skin in Utils.sortBy(skins, function(s) s.uri.spec))
$list.append(createSkinElement(skin, ++id, skin === currentSkin));
if (currentSkin === skinService.customSkin) openSkinEditor();
}
function createSkinElement(skin, id) {
var {localUrl: filepath, downloadUrl: origpath, metaData: skinMeta} = skin;
var skinId = "skin_" + id;
var skinEl = $(
'<div class="command" id="' + skinId + '">' +
('<input type="radio" name="skins" id="rad_' + skinId +
'" value="' + escapeHtml(filepath) + '"></input>') +
'<label class="label light" for="rad_'+ skinId + '">' +
'<a class="name"/><br/>' +
'<span class="author"></span><br/>' +
'<span class="license"></span></label>' +
'<div class="email light"></div>' +
'<div class="homepage light"></div></div>');
function createSkinElement(skin, id, current) {
var {metaData} = skin;
var $skin = $(
'<div class="command light" id="skin_' + id + '">' +
('<input type="radio" name="skins" id="rad_skin_' + id + '"' +
(current ? ' checked="checked"' : '') + '/>') +
'<label class="label" for="rad_skin_'+ id +
'"><a class="name"/></label></div>');
//Add the name and onchange event
skinEl.find(".name").text(skinMeta.name);
skinEl.find("input").change(function onRadioChange() {
skinService.changeSkin(filepath);
});
$skin.find(".name").text(metaData.name);
$skin.find("input").change(function onPick() { skin.pick() });
if ("author" in skinMeta)
skinEl.find(".author").text(L("ubiquity.settings.skinauthor",
skinMeta.author));
if ("email" in skinMeta) {
let ee = escapeHtml(skinMeta.email);
skinEl.find(".email")[0].innerHTML = "email: " + ee.link("mailto:" + ee);
}
if ("license" in skinMeta)
skinEl.find(".license").text(L("ubiquity.settings.skinlicense",
skinMeta.license));
if ("homepage" in skinMeta) {
let eh = escapeHtml(skinMeta.homepage);
skinEl.find(".homepage")[0].innerHTML = eh.link(eh);
}
"author" in metaData && $("<div>", {
class: "author",
text: L("ubiquity.settings.skinauthor", metaData.author),
}).appendTo($skin);
"license" in metaData && $("<div>", {
class: "license",
text: L("ubiquity.settings.skinlicense", metaData.license),
}).appendTo($skin);
"email" in metaData && $("<div>", {
class: "email",
html: let (ee = H(metaData.email)) "email: " + ee.link("mailto:" + ee),
}).appendTo($skin);
"homepage" in metaData && $("<div>", {
class: "homepage",
html: let (eh = H(metaData.homepage)) eh.link(eh),
}).appendTo($skin);
($('<a class="action" target="_blank"></a>')
.attr("href", "view-source:" + filepath)
.attr("href", "view-source:" + skin.viewSourceUri.spec)
.text(L("ubiquity.settings.viewskinsource"))
.appendTo(skinEl));
if (filepath !== origpath) (
.appendTo($skin));
skin.isBuiltIn || (
$('<a class="action"></a>')
.text(L("ubiquity.settings.uninstallskin"))
.click(function uninstall() {
var before = skinService.currentSkin;
skinService.uninstall(filepath);
var after = skinService.currentSkin;
if (before !== after) checkSkin(after);
skinEl.slideUp();
if (skin === skinService.currentSkin) skinService.defaultSkin.pick();
skin.purge();
$skin.slideUp();
})
.appendTo(skinEl.append(" ")));
.appendTo($skin.append(" ")));
return skinEl;
}
function checkSkin(url) {
$("#skin-list input:radio").each(function radio() {
if (this.value === url) {
this.checked = true;
return false;
}
});
return $skin;
}
function openSkinEditor() {
$("#editor-div").show();
$("#skin-editor").val(Utils.getLocalUrl(skinService.CUSTOM_SKIN)).focus();
$("#skin-editor").val(skinService.customSkin.css).focus();
$("#edit-button").hide();
}
function saveCustomSkin() {
try {
skinService.saveCustomSkin($("#skin-editor").val());
} catch (e) {
messageService.displayMessage(L("ubiquity.settings.skinerror"));
Cu.reportError(e);
return;
}
var {customSkin} = skinService;
customSkin.css = $("#skin-editor").val();
messageService.displayMessage(L("ubiquity.settings.skinsaved"));
loadSkinList();
if (skinService.currentSkin === skinService.CUSTOM_SKIN)
skinService.loadCurrentSkin();
if (customSkin === skinService.currentSkin) customSkin.pick();
}
function saveAs() {
try {
skinService.saveAs($("#skin-editor").val(), "custom");
skinService.saveAs($("#skin-editor").val(), "custom.css");
} catch (e) {
messageService.displayMessage(L("ubiquity.settings.skinerror"));
Cu.reportError(e);
@ -228,6 +196,6 @@ function saveAs() {
function shareSkin() {
var data = $("#skin-editor").val()
var name = Utils.trim((/@name[ \t]+(.+)/(data) || [, "ubiquity-skin"])[1]);
var name = ((/@name[ \t]+(.+)/(data) || [, "ubiquity-skin"])[1]).trim();
pasteToGist(name, data, "css");
}

Просмотреть файл

@ -42,6 +42,8 @@
<li><a class="intra-wiki"
href="#modules/locked_down_feed_plugin.js">Locked-Down Feed
Plugin</a></li>
<li><a class="intra-wiki"
href="#modules/skin_feed_plugin.js">SkinFeedPlugin</a></li>
<li><a class="intra-wiki"
href="#modules/parser/new/parser.js">Parser 2</a></li>
<li><a class="intra-wiki"

Просмотреть файл

@ -528,9 +528,7 @@ var noun_type_disabled_command = {
// === {{{ noun_type_skin }}} ===
// Suggests each installed skin whose name matches the input.
// * {{{text, html}}} : skin name
// * {{{data.downloadUrl}}}
// * {{{data.localUrl}}}
// * {{{data.metaData}}} : meta data dictionary
// * {{{data}}} : [[#modules/skin_feed_plugin.js|SkinFeed]] instance
var noun_type_skin = {
label: "name",
@ -538,7 +536,7 @@ var noun_type_skin = {
cacheTime: 0,
suggest: function nt_skin_suggest(text, html, cb, selected) {
var suggs = [CmdUtils.makeSugg(skin.metaData.name, null, skin)
for each (skin in skinService.skinList)];
for each (skin in skinService.skins)];
return CmdUtils.grepSuggs(text, suggs);
},
};

Просмотреть файл

@ -54,7 +54,7 @@ Cu.import("resource://ubiquity/modules/suggestion_memory.js");
Cu.import("resource://ubiquity/modules/feedaggregator.js");
Cu.import("resource://ubiquity/modules/webjsm.js");
Cu.import("resource://ubiquity/modules/prefcommands.js");
Cu.import("resource://ubiquity/modules/skinsvc.js");
Cu.import("resource://ubiquity/modules/skin_feed_plugin.js");
var gServices, gWebJsModule, gPrefs = Utils.prefs;
@ -158,9 +158,6 @@ var UbiquitySetup = {
if (annDb.exists())
annDb.remove(false);
// Reset all skins.
SkinSvc.reset();
// We'll reset the preferences for our extension here. Unfortunately,
// there doesn't seem to be an easy way to get this from FUEL, so
// we'll have to use XPCOM directly.
@ -187,23 +184,15 @@ var UbiquitySetup = {
}
},
getBaseUri: function getBaseUri() {
let ioSvc = (Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService));
let extDir = this.__getExtDir();
let baseUri = ioSvc.newFileURI(extDir).spec;
return baseUri;
},
getBaseUri: function getBaseUri()
Utils.IOService.newFileURI(this.__getExtDir()).spec,
isInstalledAsXpi: function isInstalledAsXpi() {
let profileDir = (Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties)
.get("ProfD", Ci.nsIFile));
let extDir = this.__getExtDir();
if (profileDir.contains(extDir, false))
return true;
return false;
return profileDir.contains(extDir, false);
},
preload: function preload(callback) {
@ -216,13 +205,10 @@ var UbiquitySetup = {
gWebJsModule = new WebJsModule(callback);
},
get isResetScheduled getIsResetScheduled() {
return gPrefs.getValue(RESET_SCHEDULED_PREF, false);
},
set isResetScheduled setIsResetScheduled(value) {
gPrefs.setValue(RESET_SCHEDULED_PREF, value);
},
get isResetScheduled getIsResetScheduled()
gPrefs.get(RESET_SCHEDULED_PREF, false),
set isResetScheduled setIsResetScheduled(value)
gPrefs.set(RESET_SCHEDULED_PREF, value),
__removeExtinctStandardFeeds: function __rmExtinctStdFeeds(feedManager) {
var OLD_STD_FEED_URIS = [
@ -287,14 +273,12 @@ var UbiquitySetup = {
);
disabledStorage.attach(cmdSource);
var skinService = new SkinSvc(gWebJsModule, msgService);
skinService.updateAllSkins();
skinService.loadCurrentSkin();
gServices = {commandSource: cmdSource,
feedManager: feedManager,
messageService: msgService,
skinService: skinService};
gServices = {
commandSource: cmdSource,
feedManager: feedManager,
messageService: msgService,
skinService: SkinFeedPlugin(feedManager, msgService, gWebJsModule),
};
this.__setupFinalizer();
@ -321,7 +305,6 @@ var UbiquitySetup = {
setupWindow: function setupWindow(window) {
gServices.feedManager.installToWindow(window);
gServices.skinService.installToWindow(window);
const PAGE_LOAD_PREF = "extensions.ubiquity.enablePageLoadHandlers";

Просмотреть файл

@ -0,0 +1,240 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Ubiquity.
*
* The Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Satoshi Murakami <murky.satyr@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// = SkinFeedPlugin =
// The boss of {{{SkinFeed}}}s, aka {{{skinService}}}.
var EXPORTED_SYMBOLS = ["SkinFeedPlugin"];
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://ubiquity/modules/utils.js");
Cu.import("resource://ubiquity/modules/codesource.js");
Cu.import("resource://ubiquity/modules/localization_utils.js");
const L = LocalizationUtils.propertySelector(
"chrome://ubiquity/locale/coreubiquity.properties");
const SSS = (Cc["@mozilla.org/content/style-sheet-service;1"]
.getService(Ci.nsIStyleSheetService));
const PREF_SKIN = "extensions.ubiquity.skin";
const PREF_CUSTOM = "extensions.ubiquity.customCss";
const PREF_REMOTE_TIMEOUT = "extensions.ubiquity.remoteUriTimeout";
const URL_ROOT = "chrome://ubiquity/skin/skins/";
const URL_CUSTOM = "ubiquity://custom-skin-css";
const URL_DEFAULT = URL_ROOT + "experimental.css";
const RE_LEAFNAME = /[^/]*$/;
var gCurrentCssUri = Utils.uri("data:text/css,");
function SkinFeedPlugin(feedManager, msgService, webJsm) {
SFP._feedManager = feedManager;
SFP._msgService = msgService;
SFP._webJsm = webJsm;
feedManager.registerPlugin(SFP);
for each (let url in [
URL_CUSTOM, URL_DEFAULT, URL_ROOT + "default.css", URL_ROOT + "old.css"])
feedManager.addSubscribedFeed({
type: "ubiquity-skin",
url: url, sourceUrl: url, title: url,
isBuiltIn: true,
canAutoUpdate: true,
});
SFP.customSkin.__defineSetter__("css", function SF_setCustomCss(css) {
Utils.prefs.set(PREF_CUSTOM, css);
});
SFP.currentSkin.pick(true);
return SFP;
}
var SFP = Utils.extend(SkinFeedPlugin.prototype, {
type: "ubiquity-skin",
notifyMessage: L("ubiquity.skinsvc.newskinfound"),
makeFeed: function SFP_makeFeed(baseFeed, eventHub)
SkinFeed(baseFeed, eventHub, this._msgService),
onSubscribeClick: function SFP_onSubscribeClick(pageUrl, link) {
var cssUrl = link.href, me = this;
me._webJsm.jQuery.ajax({
url: link.href,
dataType: "text",
success: function yay(css) {
me._feedManager.addSubscribedFeed({
type: "ubiquity-skin",
url: cssUrl, sourceUrl: cssUrl,
title: url,
sourceCode: css,
canAutoUpdate: true,
}).getFeedForUrl(url).pick();
Utils.tabs.reload(/^about:ubiquity\?settings\b/);
},
error: Utils.log,
});
},
// === {{{ SkinFeedPlugin.skins }}} ===
// Installed {{{SkinFeed}}}s as array.
get skins SFP_getSkins() [
feed for each (feed in this._feedManager.getSubscribedFeeds())
if (feed.type === "ubiquity-skin")],
// === {{{ SkinFeedPlugin.customSkin }}} ===
get customSkin SFP_getCurrentSkin()
this._feedManager.getFeedForUrl(URL_CUSTOM),
// === {{{ SkinFeedPlugin.defaultSkin }}} ===
get defaultSkin SFP_getCurrentSkin()
this._feedManager.getFeedForUrl(URL_DEFAULT),
// === {{{ SkinFeedPlugin.currentSkin }}} ===
get currentSkin SFP_getCurrentSkin() (
this._feedManager.getFeedForUrl(Utils.prefs.get(PREF_SKIN, URL_DEFAULT)) ||
this.defaultSkin),
// === {{{ SkinFeedPlugin.saveAs(cssText, defaultName) }}} ===
// Saves {{{cssText}}} to a file and subscribes to it.
saveAs: function SFP_saveAs(cssText, defaultName) {
const {nsIFilePicker} = Ci;
var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
fp.init(Utils.currentChromeWindow,
L("ubiquity.skinsvc.saveyourskin"),
nsIFilePicker.modeSave);
fp.defaultString = defaultName || "";
fp.appendFilter("CSS (*.css)", "*.css");
var rv = fp.show();
if (rv !== nsIFilePicker.returnOK &&
rv !== nsIFilePicker.returnReplace) return "";
var {file, fileURL: {spec}} = fp;
try {
let fos = (Cc["@mozilla.org/network/file-output-stream;1"]
.createInstance(Ci.nsIFileOutputStream));
fos.init(file, 0x02 | 0x08 | 0x20, 0644, 0);
fos.write(cssText, cssText.length);
fos.close();
} catch (e) {
//errorToLocalize
e.message =
"Error writing Ubiquity skin to " + file.path + ": " + e.message;
Cu.reportError(e);
}
this.onSubscribeClick({title: RE_LEAFNAME(spec)[0], URL: spec}, spec);
this._feedManager.getFeedForUrl(spec).pick();
return file.path;
},
toString: function SFP_toString() "[object SkinFeedPlugin]",
});
// == SkinFeed ==
function SkinFeed(baseFeed, eventHub, msgService) Utils.extend({
__proto__: baseFeed,
_msgService: msgService,
_codeSource: (
RemoteUriCodeSource.isValidUri(baseFeed.uri)
? new RemoteUriCodeSource(baseFeed,
Utils.prefs.get(PREF_REMOTE_TIMEOUT, 3e5))
: new LocalUriCodeSource(baseFeed.uri.spec)),
_dataCache: null,
}, SkinFeed.prototype);
Utils.extend(SkinFeed.prototype, {
// === {{{ SkinFeed#css }}} ===
// CSS code of this skin. Settable if custom.
get css SF_getCss() {
var code = this._codeSource.getCode();
if (this._codeSource.updated) this._dataCache = null;
return code;
},
// === {{{ SkinFeed#dataUri }}} ===
// Data URI object used to register this skin.
get dataUri SF_getDataUri()
Utils.uri("data:text/css,/*ubiquity-skin*/" + encodeURI(this.css)),
// === {{{ SkinFeed#metaData }}} ===
// Contents of the meta data block ({{{ =skin= ~ =/skin= }}}).
get metaData SF_getMetaData() {
if (this._dataCache) return this._dataCache;
var {css} = this, data = {name: this.title};
var [, block] = /=skin=\s*([^]+)\s*=\/skin=/(css) || 0;
if (block) {
let re = /^[ \t]*@(\w+)[ \t]+(.+)/mg, m;
while ((m = re.exec(block))) data[m[1]] = m[2].trim();
}
if (!("homepage" in data)) data.homepage = this.title;
return this._dataCache = data;
},
// === {{{ SkinFeed#pick(silently = false) }}} ===
// Applies this skin. Won't notify user if {{{silently}}}.
pick: function SF_pick(silently) {
try {
(SSS.sheetRegistered(gCurrentCssUri, SSS.USER_SHEET) &&
SSS.unregisterSheet(gCurrentCssUri, SSS.USER_SHEET));
hackCssForBugs(gCurrentCssUri, SSS);
var {dataUri, uri} = this;
SSS.loadAndRegisterSheet(dataUri, SSS.USER_SHEET);
hackCssForBugs(dataUri, SSS, true);
gCurrentCssUri = dataUri;
Utils.prefs.set(PREF_SKIN, uri.spec);
} catch (e) {
this._msgService.displayMessage(
//errorToLocalize
"Error applying Ubiquity skin from " + uri.spec);
Cu.reportError(e);
}
silently ||
this._msgService.displayMessage(L("ubiquity.skinsvc.skinchanged"));
return this;
},
refresh: function SF_refresh() this,
toString: function SF_toString() "[object SkinFeed<" + this.uri.spec + ">]",
});
function hackCssForBugs(uri, registering) {
if (Utils.OS === "Darwin" &&
uri.spec === URL_ROOT + "experimental.css") {
let hackUri = Utils.uri(URL_ROOT + "experimental-466hack.css");
if (registering)
SSS.loadAndRegisterSheet(hackUri, SSS.USER_SHEET);
else if (SSS.sheetRegistered(hackUri, SSS.USER_SHEET))
SSS.unregisterSheet(hackUri, SSS.USER_SHEET);
}
}
Cu.import("resource://ubiquity/modules/ubiquity_protocol.js", null).setPath(
RE_LEAFNAME(URL_CUSTOM)[0], function customSkinUri() {
var css = Utils.prefs.get(PREF_CUSTOM);
if (!css) css = Utils.getLocalUrl(URL_ROOT + "custom.css");
return "data:text/css," + encodeURI(css);
});

Просмотреть файл

@ -1,375 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Ubiquity.
*
* The Initial Developer of the Original Code is Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Abimanyu Raja <abimanyuraja@gmail.com>
* Satoshi Murakami <murky.satyr@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
var EXPORTED_SYMBOLS = ["SkinSvc"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://ubiquity/modules/utils.js");
Cu.import("resource://ubiquity/modules/dbutils.js");
Cu.import("resource://ubiquity/modules/localization_utils.js");
const SKIN_ROOT = "chrome://ubiquity/skin/skins/";
const SKIN_PREF = "extensions.ubiquity.skin";
var L = LocalizationUtils.propertySelector(
"chrome://ubiquity/locale/coreubiquity.properties");
var gConnection = connect();
var gMetaDict = {};
function connect() DbUtils.connectLite(
"ubiquity_skin_memory",
{ download_uri: "VARCHAR(256)",
local_uri : "VARCHAR(256)" },
[let (path = SKIN_ROOT + name + ".css") [path, path]
for each (name in ["default", "experimental", "old", "custom"])]);
function SkinSvc(webJsm, msgService) {
this._webJsm = webJsm;
this._msgService = msgService;
}
SkinSvc.SkinProto = {
get metaData() SkinSvc.readMetaData(this),
};
SkinSvc.readMetaData = function SS_readMetaData(
{css, downloadUrl, localUrl, noCache}) {
if (!noCache && localUrl in gMetaDict) return gMetaDict[localUrl];
var metaData = gMetaDict[localUrl] = {name: localUrl};
css || (css = Utils.getLocalUrl(localUrl, "utf-8"));
//look for =skin= ~ =/skin= indicating metadata
var [, data] = /=skin=\s*([^]+)\s*=\/skin=/(css) || 0;
if (data) {
let re = /^[ \t]*@(\w+)[ \t]+(.+)/mg, m;
while ((m = re.exec(data))) metaData[m[1]] = m[2].trim();
}
if (!("homepage" in metaData) && /^https?:/.test(downloadUrl))
metaData.homepage = downloadUrl;
return metaData;
};
SkinSvc.reset = function SS_reset() {
var {databaseFile} = gConnection;
gConnection.close();
databaseFile.exists() && databaseFile.remove(false);
gConnection = connect();
};
SkinSvc.prototype = {
PREF: SKIN_PREF,
DEFAULT_SKIN: SKIN_ROOT + "experimental.css",
CUSTOM_SKIN : SKIN_ROOT + "custom.css",
_createStatement: function SS__createStatement(sql) {
try {
return gConnection.createStatement(sql);
} catch (e) {
throw new Error(gConnection.lastErrorString);
}
},
_isLocalUrl: function SS__isLocalUrl(skinUrl)
/^(?:file|chrome)$/.test(Utils.url(skinUrl).scheme),
//Navigate to chrome://ubiquity/skin/skins/ and get the folder
_getSkinFolder: function SS__getSkinFolder() {
var MY_ID = "ubiquity@labs.mozilla.com";
var em = (Cc["@mozilla.org/extensions/manager;1"]
.getService(Ci.nsIExtensionManager));
var file = (em.getInstallLocation(MY_ID)
.getItemFile(MY_ID, "chrome/skin/skins/default.css")
.parent);
return file;
},
// file: nsILocalFile / data: string
_writeToFile: function SS__writeToFile(file, data) {
try {
var foStream = (Cc["@mozilla.org/network/file-output-stream;1"]
.createInstance(Ci.nsIFileOutputStream));
foStream.init(file, 0x02 | 0x08 | 0x20, 0644, 0);
foStream.write(data, data.length);
foStream.close();
} catch (e) {
//errorToLocalize
Cu.reportError("Error writing Ubiquity skin to " + file.path +
"\n" + e);
}
},
_writeToLocalUrl: function SS__writeToLocalUrl(url, data) {
var file = this._getSkinFolder();
file.append(url.slice(url.lastIndexOf("/") + 1));
this._writeToFile(file, data);
SkinSvc.readMetaData({css: data, localUrl: url, noCache: true});
},
_randomKey: function SS__randomKey() Math.random().toString(36).slice(-8),
_hackCssForBug466: function SS__hackCssForBug466(cssPath, sss, action) {
if (cssPath.spec === "chrome://ubiquity/skin/skins/experimental.css" &&
Utils.OS === "Darwin") {
let hackCss =
Utils.url("chrome://ubiquity/skin/skins/experimental-466hack.css");
if (action === "register")
sss.loadAndRegisterSheet(hackCss, sss.USER_SHEET);
else if (sss.sheetRegistered(hackCss, sss.USER_SHEET))
sss.unregisterSheet(hackCss, sss.USER_SHEET);
}
},
_hackCssForBug717: function SS__hackCssForBug717(cssPath, sss, action) {
if (cssPath.spec === "chrome://ubiquity/skin/skins/default.css" &&
Utils.OS === "Darwin" &&
let (VC = (Cc["@mozilla.org/xpcom/version-comparator;1"]
.getService(Ci.nsIVersionComparator)),
XULAI = (Cc["@mozilla.org/xre/app-info;1"]
.getService(Ci.nsIXULAppInfo))
) VC.compare(XULAI.version, "3.1") < 0) {
let hackCss =
Utils.url("chrome://ubiquity/skin/skins/default-717hack.css");
if (action === "register")
sss.loadAndRegisterSheet(hackCss, sss.USER_SHEET);
else if (sss.sheetRegistered(hackCss, sss.USER_SHEET))
sss.unregisterSheet(hackCss, sss.USER_SHEET);
}
},
//Check if the skin from this URL has already been installed
isInstalled: function SS_isInstalled(url) {
var selStmt = this._createStatement(
"SELECT COUNT(*) FROM ubiquity_skin_memory " +
"WHERE download_uri = ?1");
selStmt.bindUTF8StringParameter(0, url);
var count = selStmt.executeStep() ? selStmt.getInt32(0) : 0;
selStmt.finalize();
return count !== 0;
},
//Add a new skin record into the database
addSkin: function SS_addSkin(downloadUrl, localUrl) {
var insStmt = this._createStatement(
"INSERT INTO ubiquity_skin_memory VALUES (?1, ?2)");
insStmt.bindUTF8StringParameter(0, downloadUrl);
insStmt.bindUTF8StringParameter(1, localUrl);
insStmt.execute();
insStmt.finalize();
},
deleteSkin: function SS_deleteSkin(url) {
var delStmt = this._createStatement(
"DELETE FROM ubiquity_skin_memory " +
"WHERE local_uri = ?1 OR download_uri = ?2");
delStmt.bindUTF8StringParameter(0, url);
delStmt.bindUTF8StringParameter(1, url);
delStmt.execute();
delStmt.finalize();
},
//Unregister any current skins
//And load this new skin
loadSkin: function SS_loadSkin(newSkinPath) {
var sss = (Cc["@mozilla.org/content/style-sheet-service;1"]
.getService(Ci.nsIStyleSheetService));
try {
// Remove the previous skin CSS
var oldCss = Utils.url(this.currentSkin);
if (sss.sheetRegistered(oldCss, sss.USER_SHEET))
sss.unregisterSheet(oldCss, sss.USER_SHEET);
this._hackCssForBug466(oldCss, sss, "unregister");
this._hackCssForBug717(oldCss, sss, "unregister");
} catch (e) {} // do nothing
//Load the new skin CSS
var newCss = Utils.url(newSkinPath);
sss.loadAndRegisterSheet(newCss, sss.USER_SHEET);
Utils.prefs.setValue(SKIN_PREF, newSkinPath);
this._hackCssForBug466(newCss, sss, "register");
this._hackCssForBug717(newCss, sss, "register");
},
//Change the skin and notify
changeSkin: function SS_changeSkin(newSkinPath) {
try {
this.loadSkin(newSkinPath);
this._msgService.displayMessage(L("ubiquity.skinsvc.skinchanged"));
} catch (e) {
this.loadSkin(this.DEFAULT_SKIN);
//errorToLocalize
var msg = "Error applying Ubiquity skin from " + newSkinPath;
this._msgService.displayMessage(msg);
Cu.reportError(msg + " : " + e);
}
},
updateSkin: function SS_updateSkin(downloadUrl, localUrl) {
var self = this;
this._webJsm.jQuery.get(downloadUrl, null, function onSuccess(data) {
self._writeToLocalUrl(localUrl, data);
}, "text");
},
updateAllSkins: function SS_updateAllSkins() {
//Only have to update/download remote skins
//Local skins are pointed at directly
for each (var skin in this.skinList)
if (skin.localUrl !== skin.downloadUrl)
this.updateSkin(skin.downloadUrl, skin.localUrl);
},
loadCurrentSkin: function SS_loadCurrentSkin() {
try {
this.loadSkin(this.currentSkin);
} catch (e) {
//If there's any error loading the current skin,
//load the default and tell the user about the failure
this.loadSkin(this.DEFAULT_SKIN);
//errorToLocalize
this._msgService.displayMessage(
"Loading your current skin failed. The default skin will be loaded.");
}
},
install: function SS_install(remote, local) {
this.addSkin(remote, local);
this.changeSkin(local);
Utils.tabs.reload(/^about:ubiquity\?settings\b/);
},
uninstall: function SS_uninstall(url) {
var {skinList} = this;
EACH_SKIN: {
for each (var {localUrl, downloadUrl} in skinList)
if (localUrl !== downloadUrl &&
localUrl === url || downloadUrl === url)
break EACH_SKIN;
return;
}
this.deleteSkin(url);
var file = (Cc["@mozilla.org/network/protocol;1?name=file"]
.createInstance(Ci.nsIFileProtocolHandler)
.getFileFromURLSpec(localUrl));
file.remove(false);
if (localUrl === this.currentSkin)
this.changeSkin(this.DEFAULT_SKIN);
},
saveCustomSkin: function SS_saveCustomSkin(cssText) {
this._writeToLocalUrl(this.CUSTOM_SKIN, cssText);
},
saveAs: function SS_saveAs(cssText, defaultName) {
const {nsIFilePicker} = Ci;
var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
fp.init(Utils.currentChromeWindow,
L("ubiquity.skinsvc.saveyourskin"),
nsIFilePicker.modeSave);
fp.defaultString = defaultName || "";
fp.appendFilter("CSS (*.css)", "*.css");
var rv = fp.show();
if (rv !== nsIFilePicker.returnOK &&
rv !== nsIFilePicker.returnReplace)
return null;
this._writeToFile(fp.file, cssText);
var {spec} = fp.fileURL;
this.addSkin("data:,dev/null/" + this._randomKey(), spec);
this.changeSkin(spec);
SkinSvc.readMetaData({css: cssText, localUrl: spec, noCache: true});
return fp.file.path;
},
get currentSkin SS_getCurrentSkin()
Utils.prefs.getValue(SKIN_PREF, this.DEFAULT_SKIN),
get skinList SS_getSkinList() {
var list = [];
var selStmt = this._createStatement(
"SELECT local_uri, download_uri FROM ubiquity_skin_memory");
while (selStmt.executeStep())
list.push({
localUrl: selStmt.getUTF8String(0),
downloadUrl: selStmt.getUTF8String(1),
__proto__: SkinSvc.SkinProto,
});
selStmt.finalize();
return list;
},
};
SkinSvc.prototype.installToWindow = function installToWindow(window) {
var self = this;
function showNotification(targetDoc, skinUrl) {
Utils.notify({
target: targetDoc,
label: L("ubiquity.skinsvc.newskinfound"),
value: "ubiquity_notify_skin_available",
priority: "INFO_MEDIUM",
buttons: [{
accessKey: "I",
callback: onSubscribeClick,
label: L("ubiquity.skinsvc.installskin"),
}]});
function onSubscribeClick(notification, button) {
if (self._isLocalUrl(skinUrl)) self.install(skinUrl, skinUrl);
else self._webJsm.jQuery.get(skinUrl, null, function onSuccess(data) {
//Navigate to chrome://ubiquity/skin/skins/
var file = self._getSkinFolder();
//Select a random name for the file
var filename = self._randomKey() + ".css";
//Create the new file
file.append(filename);
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0644);
//Write the downloaded CSS to the file
self._writeToFile(file, data);
var ios = (Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService));
var {spec} = ios.newFileURI(file);
//Add skin to DB and make it the current skin
self.install(skinUrl, spec);
SkinSvc.readMetaData({
css: data, downloadUrl: skinUrl, localUrl: spec, noCache: true});
}, "text");
}
}
// Watch for any tags of the form <link rel="ubiquity-skin">
// on pages and install the skin for them if they exist.
window.addEventListener("DOMLinkAdded", function onLinkAdded({target}) {
if (target.rel === "ubiquity-skin" && !self.isInstalled(target.href))
showNotification(target.ownerDocument, target.href);
}, false);
};