Skip to content

Instantly share code, notes, and snippets.

@Griever Griever/UCSS.uc.js
Created Jan 20, 2013

Embed
What would you like to do?
UCSS reload usercss
userContent.css と userChrome.css(以下 usercss)を @import を使って管理するための補助スクリプト。
usercss や @import で読み込まれた .css のリロードや編集が可能。
どうしても Stylish を使いたくない ucjs 信者か、ローカルの .css を利用したい人向け。
思いつきで作ってみたものの、これ以上作りこむ気にならないのでここに放置。
// ==UserScript==
// @name UCSS
// @description reload usercss
// @namespace http://d.hatena.ne.jp/Griever/
// @author Griever
// @license MIT License
// @compatibility Firefox 18
// @charset UTF-8
// @include main
// @version Beta 1
// ==/UserScript==
/*
◆ これは何? ◆
userContent.css と userChrome.css(以下 usercss)を @import を使って管理するための補助スクリプト。
usercss や @import で読み込まれた .css のリロードや編集が可能。
どうしても Stylish を使いたくない ucjs 信者か、ローカルの .css を利用したい人向け。
◆ 使い方 ◆
usercss に :root{} と記述しておく。
.css の編集機能はスクラッチパッドを利用。
Ctrl+S で保存と同時に CSS がリロードされる。
■メモ
chrome の再描画は一度背面にする。
content は markup を利用。背面のタブの内容が再描画されないのはご愛嬌。
usercss の @import は insertRule, deleteRule しても効果がない
 insertRule は loadText + parseStyleSheet で対処
 deleteRule は disabled + リフレッシュで対処
usercss をリロードしても @import はリロードされず無効化される
 CSSImportRule.styleSheet はあるが cssRules が 0 個
 @import の追加・削除をチェックして自前でロードすることで対処
usercss を再読込すると @import 内の XBL がセキュリティエラーになる
 セキュリティエラー: moz-nullprincipal:{...} のコンテンツが file:///.../xbl.xml#id を読み込みまたはリンクすることは禁止されています。
  usercss > import > XBL←これがNG
  usercss > import > import が起動時ですら実行されてなかったorz
 ※対処法 - XBL は usercss 本体に書く
■今後
CSSEdit 関数は要改善
UI を考える
やる気が出ないので当分放置かも
*/
(function(CSS){
"use strict";
const DEBUG = false;
if (window.UCSS) {
window.UCSS.destroy();
delete window.UCSS;
}
var MyCSSObject = function () {
this.init.apply(this, arguments);
};
MyCSSObject.prototype = {
sheet: null,
url: "",
lastLoadedTime: 0,
mDOMUtils: Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils),
get disabled() this.sheet.disabled,
set disabled(bool) {
this.sheet.disabled = !!bool;
this.refresh();
return bool;
},
init: function(sheet, file, isContent) {
if (sheet) {
this.sheet = sheet;
this.url = sheet.href;
}
if (file) {
this.file = file;
}
if (!this.file && this.url) {
this.file = Services.io.getProtocolHandler("file")
.QueryInterface(Ci.nsIFileProtocolHandler)
.getFileFromURLSpec(this.url);
}
if (!this.url) {
this.url = Services.io.getProtocolHandler("file")
.QueryInterface(Ci.nsIFileProtocolHandler)
.getURLSpecFromFile(this.file);
}
this.name = this.file.leafName;
this.isContent = !!isContent;
this.lastLoadedTime = new Date().getTime();
},
edit: function() {
debug(this.name + " edit");
var self = this;
CSSEdit(this.file, {
onSave: function(spWin, pad) {
spWin.setTimeout(function() {
self.reload();
self.refresh(spWin);
}, 500);
}
});
},
reload: function(hasRefresh, useCache) {
// useCache じゃなくて lastModifiedTimeOfLink のチェックのがいいかも
debug(this.name + " reload" +
(useCache ? (this.cache ? " from cache" : " nocache") : ""));
try {
var text;
if (useCache && this.cache) {
text = this.cache;
} else {
text = this.cache = loadText(this.file);
}
if (!text)
throw this.name + " is not found.";
this.mDOMUtils.parseStyleSheet(this.sheet, text);
this.lastLoadedTime = new Date().getTime();
log("Reload: " + this.name);
// imported オブジェクトは userContent/userChrome.css だけ
if (this.imported)
this.getImports();
if (hasRefresh)
this.refresh();
return true;
} catch (e) {
Cu.reportError(e);
log("Reload Error:" + this.name);
}
},
refresh: function(spWin) {
debug(this.name + " refresh");
if (this.isContent) {
var s = gBrowser.markupDocumentViewer;
s.authorStyleDisabled = !s.authorStyleDisabled;
s.authorStyleDisabled = !s.authorStyleDisabled;
} else {
if (spWin) {
window.focus();
spWin.focus();
} else {
alert("refresh");
}
}
},
getImports: function() {
debug(this.name + " getImports");
if (!this.sheet)
return log("this.sheet not found.");
if (!this.imported)
this.imported = {};
$A(this.sheet.cssRules).forEach(function(rule){
if (rule.type != rule.IMPORT_RULE) return;
if (!rule.styleSheet) return debug(rule.href + " rule.styelSheet is not found.");
var obj = this.imported[rule.href];
if (obj) {
rule.styleSheet.disabled = obj.sheet.disabled;
obj.sheet = rule.styleSheet; // styelSheet を新しいものに入れ替える
} else {
obj = this.imported[rule.href] = new MyCSSObject(rule.styleSheet, null, this.isContent);
}
obj.nowload = true; // @import された
}, this);
Object.keys(this.imported).forEach(function(href){
var obj = this.imported[href];
// ファイルがない or インポートされなかったら削除
if (!obj.nowload || !obj.file.exists()) {
debug(obj.name + (!obj.nowload ?
" は @import されなくなりました" :
" はファイルが見つかりませんでした") );
obj.disabled = true;
delete this.imported[href];
return;
}
delete obj.nowload;
// ファイルが更新されていたらリロード
if (obj.lastLoadedTime < obj.file.lastModifiedTimeOfLink) {
obj.reload(false, false);
return;
}
obj.reload(false, true); // cache から更新
}, this);
},
};
window.UCSS = {
get DEBUG() DEBUG,
set DEBUG(value) DEBUG = value,
getUserChromeSheet: function() {
return this.getCCStyleSheet(document.documentElement, this.userChrome.url);
},
getUserContentSheet: function() {
var sheet = null;
gBrowser.browsers.some(function(browser){
sheet = this.getCCStyleSheet(browser.contentDocument.documentElement, this.userContent.url);
if (sheet) return true;
}, this);
return sheet;
},
init: function() {
if (CSS) this.xulstyle = addStyle(CSS);
var obj = $DOM({
$: "menu",
label: "UCSS",
id: "ucss-menu",
accesskey: "U",
_: [{
$: "menupopup",
_: [{
$: "menu",
label: "userChrome.css",
class: "ucss-userChrome",
_: [{
$: "menupopup",
_onpopupshowing: "UCSS.onPopupShowing(event);",
css_type: "userChrome",
_: [{
$: "menuitem",
label: "有効 / 無効",
accesskey: "T",
type: "checkbox",
checked: "true",
autoCheck: "false",
oncommand: [
"var o = UCSS.userChrome;"
,"var bool = o.disabled = !o.disabled;"
,"this.setAttribute('checked', !bool);"
].join("\n"),
}, {
$: "menuitem",
label: "再読み込み",
accesskey: "R",
oncommand: "UCSS.userChrome.reload(true); UCSS.rebuildMenu(this.parentNode);",
}, {
$: "menuitem",
label: "編集",
accesskey: "E",
oncommand: "UCSS.userChrome.edit();",
}, {
$: "menuseparator",
class: "sep",
}]
},]
}, {
$: "menu",
label: "userContent.css",
class: "ucss-userContent",
_: [{
$: "menupopup",
_onpopupshowing: "UCSS.onPopupShowing(event);",
css_type: "userContent",
_: [{
$: "menuitem",
label: "有効 / 無効",
accesskey: "T",
type: "checkbox",
checked: "true",
autoCheck: "false",
oncommand: [
"var o = UCSS.userContent;"
,"var bool = o.disabled = !o.disabled;"
,"this.setAttribute('checked', !bool);"
].join("\n"),
}, {
$: "menuitem",
label: "再読み込み",
accesskey: "R",
oncommand: "UCSS.userContent.reload(true); UCSS.rebuildMenu(this.parentNode);",
}, {
$: "menuitem",
label: "編集",
accesskey: "E",
oncommand: "UCSS.userContent.edit();",
}, {
$: "menuseparator",
class: "sep",
}]
},]
}]
}]
});
$("main-menubar").appendChild(obj);
var file = Services.dirsvc.get("UChrm", Ci.nsILocalFile);
file.appendRelativePath("userChrome.css");
var c = this.userChrome = new MyCSSObject(null, file, false);
c.sheet = this.getUserChromeSheet();
if (!c.sheet) {
$$('#ucss-menu .ucss-userChrome').forEach(function(elem){
elem.setAttribute("disabled", "true");
}, this);
} else {
c.getImports();
this.rebuildMenu($("ucss-menu").querySelector('menupopup[css_type="userChrome"]'));
}
var file = Services.dirsvc.get("UChrm", Ci.nsILocalFile);
file.appendRelativePath("userContent.css");
var c = this.userContent = new MyCSSObject(null, file, true);
c.sheet = this.getUserContentSheet();
if (!c.sheet) {
$$('#ucss-menu .ucss-userContent').forEach(function(elem){
elem.setAttribute("disabled", "true");
}, this);
} else {
c.getImports();
this.rebuildMenu($("ucss-menu").querySelector('menupopup[css_type="userContent"]'));
}
//window.addEventListener("unload", this, false);
},
uninit: function() {
//window.removeEventListener("unload", this, false);
},
destroy: function() {
["ucss-menu"].forEach(function(id){
var elem = $(id);
if (elem) elem.parentNode.removeChild(elem);
}, this);
this.uninit();
if (this.xulstyle) this.xulstyle.parentNode.removeChild(this.xulstyle);
},
handleEvent: function(event) {
switch(event.type){
case "unload":
this.uninit();
break;
}
},
onPopupShowing: function(event) {
var popup = event.target;
var sep = popup.querySelector('.sep');
if (sep && sep.nextSibling) {
var range = document.createRange();
range.setStartAfter(sep);
range.setEndAfter(popup.lastChild);
range.deleteContents();
}
var css_type = popup.getAttribute("css_type");
if (!css_type) return;
},
rebuildMenu: function(popup) {
var css_type = popup.getAttribute("css_type");
var obj = this[css_type];
if (!css_type || !obj) return;
var sep = popup.querySelector('.sep');
if (sep && sep.nextSibling) {
var range = document.createRange();
range.setStartAfter(sep);
range.setEndAfter(popup.lastChild);
range.deleteContents();
}
var tooltiptext = [
"左クリックで ON/OFF"
,"中クリックでリロード"
,"右クリックで編集"
].join("\n");
Object.keys(obj.imported).forEach(function(href) {
var o = obj.imported[href];
var menuitem = document.createElement("menuitem");
menuitem.setAttribute("label", href);
menuitem.setAttribute("tooltiptext", tooltiptext);
menuitem.setAttribute("type", "checkbox");
menuitem.setAttribute("checked", !o.disabled);
menuitem.setAttribute("autoCheck", "false");
menuitem.setAttribute("closemenu", "none");
menuitem.addEventListener("command", function(event) {
var bool = o.disabled = !o.disabled;
menuitem.setAttribute("checked", !bool);
}, false);
menuitem.addEventListener("click", function(event){
if (event.button === 0) return;
event.preventDefault();
event.stopPropagation();
if (event.button === 1) {
o.reload(true);
} else if (event.button === 2) {
o.edit();
closeMenus(menuitem);
}
}, false);
popup.appendChild(menuitem);
}, this);
},
get mDOMUtils() {
delete this.mDOMUtils;
return this.mDOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
},
getCCStyleSheet: function(aElement, cssURL) {
var rules = this.mDOMUtils.getCSSStyleRules(aElement);
var count = rules.Count();
if (!count) return null;
for (var i = 0; i < count; ++i) {
var rule = rules.GetElementAt(i).parentStyleSheet;
if (rule && rule.href === cssURL)
return rule;
};
},
loadFile: loadFile,
loadText: loadText,
saveFile: saveFile,
saveText: saveText,
log: log,
};
window.UCSS.init();
function CSSEdit(aFile, option){
//if (!aFile.exists() || !aFile.isFile()) return;
option || (option = {});
var spWin = Scratchpad.openScratchpad();
spWin.addEventListener("load", onload, false);
function onload(event) {
event.currentTarget.removeEventListener(event.type, onload, false);
var Scratchpad = spWin.Scratchpad;
Scratchpad.addObserver({"onReady": function(){
let spEditor = Scratchpad.editor;
spEditor.setMode("css");
Scratchpad.setFilename(aFile.path);
if (aFile.exists() && aFile.isFile()) {
Scratchpad.importFromFile(aFile, false);
} else {
Scratchpad.setText('');
Scratchpad.insertTextAtCaret([
'@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);'
,'@namespace html url(http://www.w3.org/1999/xhtml);'
].join('\n') + '\n');
}
Scratchpad.setRecentFile(aFile);
if (option.onLoad) option.onLoad(spWin, Scratchpad);
if (option.onSave) {
Scratchpad.saveFile_org = Scratchpad.saveFile;
Scratchpad.saveFile = function(){
if (!spEditor.dirty) return
Scratchpad.saveFile_org.apply(Scratchpad, arguments);
option.onSave(spWin, Scratchpad);
}
}
Scratchpad.openFile = function() {
spWin.alert("CSSEdit 中は利用できません。");
};
}});
};
};
function loadFile(aLeafName) {
var aFile = Services.dirsvc.get("UChrm", Ci.nsILocalFile);
aFile.appendRelativePath(aLeafName);
return loadText(aFile);
}
function loadText(aFile) {
if (!aFile.exists() || !aFile.isFile()) return null;
var fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
var sstream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
fstream.init(aFile, -1, 0, 0);
sstream.init(fstream);
var data = sstream.read(sstream.available());
try { data = decodeURIComponent(escape(data)); } catch(e) {}
sstream.close();
fstream.close();
return data;
}
function saveFile(aLeafName, data) {
var aFile = Services.dirsvc.get("UChrm", Ci.nsILocalFile);
aFile.appendRelativePath(aLeafName);
saveText(aFile);
};
function saveText(aFile, data) {
var suConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
suConverter.charset = "UTF-8";
data = suConverter.ConvertFromUnicode(data);
var foStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
foStream.init(aFile, 0x02 | 0x08 | 0x20, parseInt("0664", 8), 0);
foStream.write(data, data.length);
foStream.close();
};
function $(id) { return document.getElementById(id); }
function $$(exp, doc) { return $A((doc || document).querySelectorAll(exp)); }
function $A(args) { return Array.prototype.slice.call(args); }
function log() { Services.console.logStringMessage($A(arguments).join(', ')); }
function debug() { if (DEBUG) log("debug: " + $A(arguments).join(', ')); }
function addStyle(css) {
var pi = document.createProcessingInstruction(
'xml-stylesheet',
'type="text/css" href="data:text/css;utf-8,' + encodeURIComponent(css) + '"'
);
return document.insertBefore(pi, document.documentElement);
}
function $DOM(tree, doc) {
doc || (doc = document);
var func = function(obj) {
if (!obj.$) return doc.createTextNode(obj);
var el = doc.createElement(obj.$);
Object.keys(obj).forEach(function(an){
if (an === "$") return;
if (an === "_") return funcArr(obj[an], el);
el.setAttribute(an, obj[an]);
});
return el;
};
var funcArr = function(arr, par) {
arr.forEach(function(o) par.appendChild(func(o)));
return par;
};
return tree instanceof Array ?
funcArr(tree, doc.createDocumentFragment()) :
func(tree);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.