Created
January 20, 2013 12:25
-
-
Save Griever/4578252 to your computer and use it in GitHub Desktop.
UCSS reload usercss
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
userContent.css と userChrome.css(以下 usercss)を @import を使って管理するための補助スクリプト。 | |
usercss や @import で読み込まれた .css のリロードや編集が可能。 | |
どうしても Stylish を使いたくない ucjs 信者か、ローカルの .css を利用したい人向け。 | |
思いつきで作ってみたものの、これ以上作りこむ気にならないのでここに放置。 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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