223 lines
8.1 KiB
JavaScript
223 lines
8.1 KiB
JavaScript
|
let EXPORTED_SYMBOLS = [];
|
||
|
|
||
|
const Services = globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
|
||
|
const { xPref } = ChromeUtils.import('chrome://userchromejs/content/xPref.jsm');
|
||
|
const { Management } = ChromeUtils.import('resource://gre/modules/Extension.jsm');
|
||
|
const { AppConstants } = ChromeUtils.import('resource://gre/modules/AppConstants.jsm');
|
||
|
|
||
|
let UC = {
|
||
|
webExts: new Map(),
|
||
|
sidebar: new Map()
|
||
|
};
|
||
|
|
||
|
let _uc = {
|
||
|
ALWAYSEXECUTE: 'rebuild_userChrome.uc.js',
|
||
|
BROWSERCHROME: AppConstants.MOZ_APP_NAME == 'thunderbird' ? 'chrome://messenger/content/messenger.xhtml' : 'chrome://browser/content/browser.xhtml',
|
||
|
BROWSERTYPE: AppConstants.MOZ_APP_NAME == 'thunderbird' ? 'mail:3pane' : 'navigator:browser',
|
||
|
BROWSERNAME: AppConstants.MOZ_APP_NAME.charAt(0).toUpperCase() + AppConstants.MOZ_APP_NAME.slice(1),
|
||
|
PREF_ENABLED: 'userChromeJS.enabled',
|
||
|
PREF_SCRIPTSDISABLED: 'userChromeJS.scriptsDisabled',
|
||
|
|
||
|
chromedir: Services.dirsvc.get('UChrm', Ci.nsIFile),
|
||
|
scriptsDir: '',
|
||
|
|
||
|
sss: Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService),
|
||
|
|
||
|
getScripts: function () {
|
||
|
this.scripts = {};
|
||
|
let files = this.chromedir.directoryEntries.QueryInterface(Ci.nsISimpleEnumerator);
|
||
|
while (files.hasMoreElements()) {
|
||
|
let file = files.getNext().QueryInterface(Ci.nsIFile);
|
||
|
if (/\.uc\.js$/i.test(file.leafName)) {
|
||
|
_uc.getScriptData(file);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
getScriptData: function (aFile) {
|
||
|
let aContent = this.readFile(aFile);
|
||
|
let header = (aContent.match(/^\/\/ ==UserScript==\s*\n(?:.*\n)*?\/\/ ==\/UserScript==\s*\n/m) || [''])[0];
|
||
|
let match, rex = {
|
||
|
include: [],
|
||
|
exclude: []
|
||
|
};
|
||
|
let findNextRe = /^\/\/ @(include|exclude)\s+(.+)\s*$/gm;
|
||
|
while ((match = findNextRe.exec(header))) {
|
||
|
rex[match[1]].push(match[2].replace(/^main$/i, _uc.BROWSERCHROME).replace(/\*/g, '.*?'));
|
||
|
}
|
||
|
if (!rex.include.length) {
|
||
|
rex.include.push(_uc.BROWSERCHROME);
|
||
|
}
|
||
|
let exclude = rex.exclude.length ? '(?!' + rex.exclude.join('$|') + '$)' : '';
|
||
|
|
||
|
let def = ['', ''];
|
||
|
let author = (header.match(/\/\/ @author\s+(.+)\s*$/im) || def)[1];
|
||
|
let filename = aFile.leafName || '';
|
||
|
|
||
|
return this.scripts[filename] = {
|
||
|
filename: filename,
|
||
|
file: aFile,
|
||
|
url: Services.io.getProtocolHandler('file').QueryInterface(Ci.nsIFileProtocolHandler).getURLSpecFromDir(this.chromedir) + filename,
|
||
|
name: (header.match(/\/\/ @name\s+(.+)\s*$/im) || def)[1],
|
||
|
description: (header.match(/\/\/ @description\s+(.+)\s*$/im) || def)[1],
|
||
|
version: (header.match(/\/\/ @version\s+(.+)\s*$/im) || def)[1],
|
||
|
author: (header.match(/\/\/ @author\s+(.+)\s*$/im) || def)[1],
|
||
|
regex: new RegExp('^' + exclude + '(' + (rex.include.join('|') || '.*') + ')$', 'i'),
|
||
|
id: (header.match(/\/\/ @id\s+(.+)\s*$/im) || ['', filename.split('.uc.js')[0] + '@' + (author || 'userChromeJS')])[1],
|
||
|
homepageURL: (header.match(/\/\/ @homepageURL\s+(.+)\s*$/im) || def)[1],
|
||
|
downloadURL: (header.match(/\/\/ @downloadURL\s+(.+)\s*$/im) || def)[1],
|
||
|
updateURL: (header.match(/\/\/ @updateURL\s+(.+)\s*$/im) || def)[1],
|
||
|
optionsURL: (header.match(/\/\/ @optionsURL\s+(.+)\s*$/im) || def)[1],
|
||
|
startup: (header.match(/\/\/ @startup\s+(.+)\s*$/im) || def)[1],
|
||
|
shutdown: (header.match(/\/\/ @shutdown\s+(.+)\s*$/im) || def)[1],
|
||
|
onlyonce: /\/\/ @onlyonce\b/.test(header),
|
||
|
isRunning: false,
|
||
|
get isEnabled() {
|
||
|
return (xPref.get(_uc.PREF_SCRIPTSDISABLED) || '').split(',').indexOf(this.filename) == -1;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
readFile: function (aFile, metaOnly = false) {
|
||
|
let stream = Cc['@mozilla.org/network/file-input-stream;1'].createInstance(Ci.nsIFileInputStream);
|
||
|
stream.init(aFile, 0x01, 0, 0);
|
||
|
let cvstream = Cc['@mozilla.org/intl/converter-input-stream;1'].createInstance(Ci.nsIConverterInputStream);
|
||
|
cvstream.init(stream, 'UTF-8', 1024, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
|
||
|
let content = '',
|
||
|
data = {};
|
||
|
while (cvstream.readString(4096, data)) {
|
||
|
content += data.value;
|
||
|
if (metaOnly && content.indexOf('// ==/UserScript==') > 0) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
cvstream.close();
|
||
|
return content.replace(/\r\n?/g, '\n');
|
||
|
},
|
||
|
|
||
|
everLoaded: [],
|
||
|
|
||
|
loadScript: function (script, win) {
|
||
|
if (!script.regex.test(win.location.href) || (script.filename != this.ALWAYSEXECUTE && !script.isEnabled)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (script.onlyonce && script.isRunning) {
|
||
|
if (script.startup) {
|
||
|
eval(script.startup);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
Services.scriptloader.loadSubScript(script.url + '?' + script.file.lastModifiedTime,
|
||
|
script.onlyonce ? { window: win } : win);
|
||
|
script.isRunning = true;
|
||
|
if (script.startup) {
|
||
|
eval(script.startup);
|
||
|
}
|
||
|
if (!script.shutdown) {
|
||
|
this.everLoaded.push(script.id);
|
||
|
}
|
||
|
} catch (ex) {
|
||
|
Cu.reportError(ex);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
windows: function (fun, onlyBrowsers = true) {
|
||
|
let windows = Services.wm.getEnumerator(onlyBrowsers ? this.BROWSERTYPE : null);
|
||
|
while (windows.hasMoreElements()) {
|
||
|
let win = windows.getNext();
|
||
|
if (!win._uc)
|
||
|
continue;
|
||
|
if (!onlyBrowsers) {
|
||
|
let frames = win.docShell.getAllDocShellsInSubtree(Ci.nsIDocShellTreeItem.typeAll, Ci.nsIDocShell.ENUMERATE_FORWARDS);
|
||
|
let res = frames.some(frame => {
|
||
|
let fWin = frame.domWindow;
|
||
|
let {document, location} = fWin;
|
||
|
if (fun(document, fWin, location))
|
||
|
return true;
|
||
|
});
|
||
|
if (res)
|
||
|
break;
|
||
|
} else {
|
||
|
let {document, location} = win;
|
||
|
if (fun(document, win, location))
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
createElement: function (doc, tag, atts, XUL = true) {
|
||
|
let el = XUL ? doc.createXULElement(tag) : doc.createElement(tag);
|
||
|
for (let att in atts) {
|
||
|
el.setAttribute(att, atts[att]);
|
||
|
}
|
||
|
return el
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if (xPref.get(_uc.PREF_ENABLED) === undefined) {
|
||
|
xPref.set(_uc.PREF_ENABLED, true, true);
|
||
|
}
|
||
|
|
||
|
if (xPref.get(_uc.PREF_SCRIPTSDISABLED) === undefined) {
|
||
|
xPref.set(_uc.PREF_SCRIPTSDISABLED, '', true);
|
||
|
}
|
||
|
|
||
|
let UserChrome_js = {
|
||
|
observe: function (aSubject) {
|
||
|
aSubject.addEventListener('DOMContentLoaded', this, {once: true});
|
||
|
},
|
||
|
|
||
|
handleEvent: function (aEvent) {
|
||
|
let document = aEvent.originalTarget;
|
||
|
let window = document.defaultView;
|
||
|
let location = window.location;
|
||
|
|
||
|
if (!this.sharedWindowOpened && location.href == 'chrome://extensions/content/dummy.xhtml') {
|
||
|
this.sharedWindowOpened = true;
|
||
|
|
||
|
Management.on('extension-browser-inserted', function (topic, browser) {
|
||
|
browser.messageManager.addMessageListener('Extension:ExtensionViewLoaded', this.messageListener.bind(this));
|
||
|
}.bind(this));
|
||
|
} else if (/^(chrome:(?!\/\/global\/content\/commonDialog\.x?html)|about:(?!blank))/i.test(location.href)) {
|
||
|
window.UC = UC;
|
||
|
window._uc = _uc;
|
||
|
window.xPref = xPref;
|
||
|
if (window._gBrowser) // bug 1443849
|
||
|
window.gBrowser = window._gBrowser;
|
||
|
|
||
|
if (xPref.get(_uc.PREF_ENABLED)) {
|
||
|
Object.values(_uc.scripts).forEach(script => {
|
||
|
_uc.loadScript(script, window);
|
||
|
});
|
||
|
} else if (!UC.rebuild) {
|
||
|
_uc.loadScript(_uc.scripts[_uc.ALWAYSEXECUTE], window);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
messageListener: function (msg) {
|
||
|
const browser = msg.target;
|
||
|
const { addonId } = browser._contentPrincipal;
|
||
|
|
||
|
browser.messageManager.removeMessageListener('Extension:ExtensionViewLoaded', this.messageListener);
|
||
|
|
||
|
if (browser.ownerGlobal.location.href == 'chrome://extensions/content/dummy.xhtml') {
|
||
|
UC.webExts.set(addonId, browser);
|
||
|
Services.obs.notifyObservers(null, 'UCJS:WebExtLoaded', addonId);
|
||
|
} else {
|
||
|
let win = browser.ownerGlobal.windowRoot.ownerGlobal;
|
||
|
UC.sidebar.get(addonId)?.set(win, browser) || UC.sidebar.set(addonId, new Map([[win, browser]]));
|
||
|
Services.obs.notifyObservers(win, 'UCJS:SidebarLoaded', addonId);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if (!Services.appinfo.inSafeMode) {
|
||
|
_uc.chromedir.append(_uc.scriptsDir);
|
||
|
_uc.getScripts();
|
||
|
Services.obs.addObserver(UserChrome_js, 'chrome-document-global-created', false);
|
||
|
}
|