the huge 117+ fixes update
80
README.md
|
@ -1,36 +1,64 @@
|
||||||
# MSFX
|
# MSFX
|
||||||
|
|
||||||
Fork of [the MSFX Firefox userChrome](https://github.com/WinClassic/MSFX).
|
Fork of [the MSFX Firefox userChrome](https://github.com/WinClassic/MSFX).
|
||||||
|
|
||||||
![Screenshot of MSFX](.assets/screenshot.png)
|
![Screenshot of MSFX](.assets/screenshot.png)
|
||||||
|
|
||||||
## Changes
|
## Changes
|
||||||
* Uses normal [firefox-scripts](https://github.com/xiaoxiaoflood/firefox-scripts) instead of a stripped down version for greater userscript compatibility
|
|
||||||
* More accurate styles in some places
|
- Uses normal [firefox-scripts](https://github.com/xiaoxiaoflood/firefox-scripts) instead of a stripped down version for greater userscript compatibility
|
||||||
* Bookmarks bar folder menus
|
- Fixed up for 117+ and now bundled with again, as the upstream repo's maintainer is MIA.
|
||||||
* All tabs button
|
- More accurate styles in some places
|
||||||
* Replaced with overflow arrows icon
|
- Bookmarks bar folder menus
|
||||||
* Scrollbars forced to light theme
|
- All tabs button
|
||||||
* Selected address bar forced to light theme
|
- Replaced with overflow arrows icon
|
||||||
* Resize grabber no longer appears when maximized
|
- Scrollbars forced to light theme
|
||||||
* Autoplay Pending indicator icon replaced
|
- Selected address bar forced to light theme
|
||||||
* Pinned tabs no longer have extra padding
|
- Resize grabber no longer appears when maximized
|
||||||
* Internet status bar widget is its own component
|
- Autoplay Pending indicator icon replaced
|
||||||
* Empty boxes have been removed in favor of putting extensions on the status bar
|
- Accurate menus (117+)
|
||||||
* Address bar is no longer forced to the Address toolbar when opening a new window
|
- Unlocked toolbar style has proper bottom resizer (visual only)
|
||||||
|
- Properly styled address bar dropdown
|
||||||
|
- Proper hover/active states for menu bar items
|
||||||
|
- Scrollbar dithering
|
||||||
|
- Internet status bar widget is its own component
|
||||||
|
- Empty boxes have been removed in favor of putting extensions on the status bar
|
||||||
|
- Address bar is no longer forced to the Address toolbar (configurable, enabled by default)
|
||||||
|
- Works with Firefox 117+ (fixes developed for on 122.0b3)
|
||||||
|
- A lot of legacy styling was removed to where the `appearance` property does nothing, everything that used this to achieve the classic style has been accurately recreated as best as possible.
|
||||||
|
|
||||||
|
## What's missing?
|
||||||
|
|
||||||
|
- The stock extensions menu, the main menu and the overflow tabs menu are not styled, as I do not personally use them.
|
||||||
|
- More configuration options probably. There might've been some things I commented out for my own liking that you may not agree with in terms of accuracy to IE5.
|
||||||
|
- Default favicons for bookmark items, as these cannot be changed in pure CSS. (needs investigation)
|
||||||
|
- Extension button tooltips don't seem to be standard tooltips.
|
||||||
|
|
||||||
|
## Known issues
|
||||||
|
|
||||||
|
- Opening "Customize Toolbar" resets the access key for the Favorites (bookmarks) menu item back to "B", resulting in "Favorites (B)"
|
||||||
|
|
||||||
|
## What can be improved?
|
||||||
|
|
||||||
|
- Stop using base64 URLs everywhere.
|
||||||
|
- More configuration.
|
||||||
|
- [Nested rulesets](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting), since Firefox itself uses them everywhere. (117+)
|
||||||
|
- Some of the 117+ fixes have been using them (e.g. context menus)
|
||||||
|
- Multiple files.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
* Set the following keys in `about:config`:
|
|
||||||
* `svg.context-properties.content.enabled` to `true`
|
- Set the following keys in `about:config`:
|
||||||
* `ui.prefersReducedMotion` to `1`
|
- `toolkit.legacyUserProfileCustomizations.stylesheets` to `true`
|
||||||
* `browser.display.windows.non_native_menus` to `0`
|
- `svg.context-properties.content.enabled` to `true`
|
||||||
* Follow [firefox-scripts](https://github.com/xiaoxiaoflood/firefox-scripts) installation.
|
- `ui.prefersReducedMotion` to `1`
|
||||||
* Install [Status Bar userscript](https://raw.githubusercontent.com/xiaoxiaoflood/firefox-scripts/master/chrome/status-bar.uc.js).
|
- ~~`browser.display.windows.non_native_menus` to `0`~~ (removed in 117+)
|
||||||
* Merge `chrome` folder from the repo into your profile's `chrome` folder.
|
- Copy the contents of `ffroot` into your Firefox install folder (typically `C:\Program Files\Mozilla Firefox`)
|
||||||
|
- There is an extra set of folders added (`browser\chrome\icons\default`) to change the main window icon. The default is the IE5 page icon, but the 2001-2003 Mozilla icon is included as well, just needs to be renamed from `_main-window.ico` to `main-window.ico`.
|
||||||
|
- Merge `chrome` folder from the repo into your profile's `chrome` folder. (path can be found via `about:profiles` or `about:support`)
|
||||||
|
|
||||||
## Recommendations
|
## Recommendations
|
||||||
* [Extensions Options Menu](https://raw.githubusercontent.com/xiaoxiaoflood/firefox-scripts/master/chrome/extensionOptionsMenu.uc.js)
|
|
||||||
* More lightweight than the stock extensions menu, which is disabled by the theme anyways
|
- [Extensions Options Menu](https://raw.githubusercontent.com/xiaoxiaoflood/firefox-scripts/master/chrome/extensionOptionsMenu.uc.js)
|
||||||
* [userChromeJS Manager](https://raw.githubusercontent.com/xiaoxiaoflood/firefox-scripts/master/chrome/rebuild_userChrome.uc.js)
|
- More lightweight than the stock extensions menu, which is "hidden" by the theme anyways
|
||||||
* [Save File to](https://raw.githubusercontent.com/xiaoxiaoflood/firefox-scripts/master/extensions/savefileto/savefileto.xpi)
|
- [userChromeJS Manager](https://raw.githubusercontent.com/xiaoxiaoflood/firefox-scripts/master/chrome/rebuild_userChrome.uc.js)
|
||||||
* **Requires extensions support in firefox-scripts**
|
|
||||||
* "Replaces" save dialog for hybrid classic theme setup with Windhawk until investigation is done to fix the save dialog on classic enabled programs.
|
|
||||||
|
|
|
@ -1,52 +1,53 @@
|
||||||
// 'Activity throbber' script for Firefox 60+ by Aris
|
// 'Activity throbber' script for Firefox 60+ by Aris
|
||||||
|
(function () {
|
||||||
|
Components.utils.import("resource:///modules/CustomizableUI.jsm");
|
||||||
|
var Services =
|
||||||
|
globalThis.Services ||
|
||||||
|
ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
|
||||||
|
var sss = Components.classes[
|
||||||
|
"@mozilla.org/content/style-sheet-service;1"
|
||||||
|
].getService(Components.interfaces.nsIStyleSheetService);
|
||||||
|
|
||||||
Components.utils.import("resource:///modules/CustomizableUI.jsm");
|
var at_label = "Activity Throbber";
|
||||||
var { Services } = Components.utils.import(
|
|
||||||
"resource://gre/modules/Services.jsm",
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
var sss = Components.classes[
|
|
||||||
"@mozilla.org/content/style-sheet-service;1"
|
|
||||||
].getService(Components.interfaces.nsIStyleSheetService);
|
|
||||||
|
|
||||||
var at_label = "Activity Throbber";
|
var ActivityThrobber = {
|
||||||
|
init: function () {
|
||||||
|
try {
|
||||||
|
document.addEventListener("TabAttrModified", _ActivityThrobber, false);
|
||||||
|
document.addEventListener("TabSelect", _ActivityThrobber, false);
|
||||||
|
document.addEventListener("TabOpen", _ActivityThrobber, false);
|
||||||
|
document.addEventListener("TabClose", _ActivityThrobber, false);
|
||||||
|
document.addEventListener("load", _ActivityThrobber, false);
|
||||||
|
|
||||||
var ActivityThrobber = {
|
// add or remove 'busy' tab from activity item
|
||||||
init: function () {
|
function _ActivityThrobber() {
|
||||||
try {
|
if (gBrowser.selectedTab.hasAttribute("busy")) {
|
||||||
document.addEventListener("TabAttrModified", _ActivityThrobber, false);
|
document
|
||||||
document.addEventListener("TabSelect", _ActivityThrobber, false);
|
.querySelector("#activity_throbber")
|
||||||
document.addEventListener("TabOpen", _ActivityThrobber, false);
|
.setAttribute("busy", "true");
|
||||||
document.addEventListener("TabClose", _ActivityThrobber, false);
|
} else
|
||||||
document.addEventListener("load", _ActivityThrobber, false);
|
document
|
||||||
|
.querySelector("#activity_throbber")
|
||||||
|
.removeAttribute("busy");
|
||||||
|
}
|
||||||
|
|
||||||
// add or remove 'busy' tab from activity item
|
// create a default toolbar button
|
||||||
function _ActivityThrobber() {
|
CustomizableUI.createWidget({
|
||||||
if (gBrowser.selectedTab.hasAttribute("busy")) {
|
id: "activity_throbber", // button id
|
||||||
document
|
defaultArea: CustomizableUI.AREA_NAVBAR,
|
||||||
.querySelector("#activity_throbber")
|
removable: true,
|
||||||
.setAttribute("busy", "true");
|
label: at_label, // button title
|
||||||
} else
|
tooltiptext: at_label, // tooltip title
|
||||||
document.querySelector("#activity_throbber").removeAttribute("busy");
|
onCreated: function (button) {
|
||||||
}
|
return button;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// create a default toolbar button
|
// style button icon / embedded non-animated icon, because there is no image for then inside Fx anymore
|
||||||
CustomizableUI.createWidget({
|
var uri = Services.io.newURI(
|
||||||
id: "activity_throbber", // button id
|
"data:text/css;charset=utf-8," +
|
||||||
defaultArea: CustomizableUI.AREA_NAVBAR,
|
encodeURIComponent(
|
||||||
removable: true,
|
`#activity_throbber {
|
||||||
label: at_label, // button title
|
|
||||||
tooltiptext: at_label, // tooltip title
|
|
||||||
onCreated: function (button) {
|
|
||||||
return button;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// style button icon / embedded non-animated icon, because there is no image for then inside Fx anymore
|
|
||||||
var uri = Services.io.newURI(
|
|
||||||
"data:text/css;charset=utf-8," +
|
|
||||||
encodeURIComponent(
|
|
||||||
`#activity_throbber {
|
|
||||||
-moz-appearance: none !important;
|
-moz-appearance: none !important;
|
||||||
list-style-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJ1BMVEUAAAC0tLS0tLSysrK0tLS0tLS0tLS1tbW0tLS6urq0tLS3t7eurq4SF2bYAAAADXRSTlMA2oILm3RnVEEF0y4TZ0HrPwAAAE5JREFUCNdjAAIjZQYISBQDU1uUA0WNvIEMR/FDOoUiQIbiCgaGLiEgY3oDAwNHJQPDtGCQQtNMBkWQKJCEM+BSMMVw7XAD4VYgLIU7AwA5fBJ3rMaMkwAAAABJRU5ErkJggg==);
|
list-style-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJ1BMVEUAAAC0tLS0tLSysrK0tLS0tLS0tLS1tbW0tLS6urq0tLS3t7eurq4SF2bYAAAADXRSTlMA2oILm3RnVEEF0y4TZ0HrPwAAAE5JREFUCNdjAAIjZQYISBQDU1uUA0WNvIEMR/FDOoUiQIbiCgaGLiEgY3oDAwNHJQPDtGCQQtNMBkWQKJCEM+BSMMVw7XAD4VYgLIU7AwA5fBJ3rMaMkwAAAABJRU5ErkJggg==);
|
||||||
width: 16px !important;
|
width: 16px !important;
|
||||||
|
@ -63,20 +64,21 @@ var ActivityThrobber = {
|
||||||
list-style-image: url("chrome://global/skin/media/throbber.png");
|
list-style-image: url("chrome://global/skin/media/throbber.png");
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
// remove old style sheet, before registering the new one
|
// remove old style sheet, before registering the new one
|
||||||
if (sss.sheetRegistered(uri, sss.AGENT_SHEET)) {
|
if (sss.sheetRegistered(uri, sss.AGENT_SHEET)) {
|
||||||
sss.unregisterSheet(uri, sss.AGENT_SHEET);
|
sss.unregisterSheet(uri, sss.AGENT_SHEET);
|
||||||
|
}
|
||||||
|
sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
|
||||||
|
} catch (e) {
|
||||||
|
Components.utils.reportError(e);
|
||||||
}
|
}
|
||||||
sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
|
},
|
||||||
} catch (e) {
|
};
|
||||||
Components.utils.reportError(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", ActivityThrobber.init(), false);
|
document.addEventListener("DOMContentLoaded", ActivityThrobber.init(), false);
|
||||||
|
})();
|
||||||
|
|
|
@ -7,212 +7,223 @@
|
||||||
//
|
//
|
||||||
// workaround on Fx 71 to save/restore toolbar visibility
|
// workaround on Fx 71 to save/restore toolbar visibility
|
||||||
// creating an observer array always fails, so observers are created manually atm.
|
// creating an observer array always fails, so observers are created manually atm.
|
||||||
|
(function () {
|
||||||
|
Components.utils.import("resource:///modules/CustomizableUI.jsm");
|
||||||
|
var Services =
|
||||||
|
globalThis.Services ||
|
||||||
|
ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
|
||||||
|
var appversion = parseInt(Services.appinfo.version);
|
||||||
|
|
||||||
Components.utils.import("resource:///modules/CustomizableUI.jsm");
|
var AdditionalTopToolbars = {
|
||||||
var { Services } = Components.utils.import(
|
init: function () {
|
||||||
"resource://gre/modules/Services.jsm",
|
/* blank tab workaround */
|
||||||
{}
|
try {
|
||||||
);
|
if (gBrowser.selectedBrowser.getAttribute("blank"))
|
||||||
var appversion = parseInt(Services.appinfo.version);
|
gBrowser.selectedBrowser.removeAttribute("blank");
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
var AdditionalTopToolbars = {
|
var number_of_additional_top_toolbars = 1; // max 5 to save toolbar state on Fx 71+ (add additional code at the bottom for more)
|
||||||
init: function () {
|
var tb_label = "Address";
|
||||||
/* blank tab workaround */
|
|
||||||
try {
|
|
||||||
if (gBrowser.selectedBrowser.getAttribute("blank"))
|
|
||||||
gBrowser.selectedBrowser.removeAttribute("blank");
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
var number_of_additional_top_toolbars = 1; // max 5 to save toolbar state on Fx 71+ (add additional code at the bottom for more)
|
try {
|
||||||
var tb_label = "Address";
|
if (document.getElementById("additional_top_toolbar1") == null) {
|
||||||
|
if (
|
||||||
|
number_of_additional_top_toolbars > 0 &&
|
||||||
|
number_of_additional_top_toolbars < 6
|
||||||
|
) {
|
||||||
|
var i = 1;
|
||||||
|
|
||||||
try {
|
while (i <= number_of_additional_top_toolbars) {
|
||||||
if (document.getElementById("additional_top_toolbar1") == null) {
|
if (appversion <= 62)
|
||||||
if (
|
var toptoolbar = document.createElement("toolbar");
|
||||||
number_of_additional_top_toolbars > 0 &&
|
else var toptoolbar = document.createXULElement("toolbar");
|
||||||
number_of_additional_top_toolbars < 6
|
|
||||||
) {
|
|
||||||
var i = 1;
|
|
||||||
|
|
||||||
while (i <= number_of_additional_top_toolbars) {
|
toptoolbar.setAttribute("id", "additional_top_toolbar" + i + "");
|
||||||
if (appversion <= 62)
|
toptoolbar.setAttribute("toolbarname", tb_label + "");
|
||||||
var toptoolbar = document.createElement("toolbar");
|
toptoolbar.setAttribute("toolbarname", tb_label + "");
|
||||||
else var toptoolbar = document.createXULElement("toolbar");
|
toptoolbar.setAttribute("customizable", "true");
|
||||||
|
toptoolbar.setAttribute(
|
||||||
toptoolbar.setAttribute("id", "additional_top_toolbar" + i + "");
|
"class",
|
||||||
toptoolbar.setAttribute("toolbarname", tb_label + "");
|
"toolbar-primary chromeclass-toolbar browser-toolbar customization-target"
|
||||||
toptoolbar.setAttribute("toolbarname", tb_label + "");
|
|
||||||
toptoolbar.setAttribute("customizable", "true");
|
|
||||||
toptoolbar.setAttribute(
|
|
||||||
"class",
|
|
||||||
"toolbar-primary chromeclass-toolbar browser-toolbar customization-target"
|
|
||||||
);
|
|
||||||
toptoolbar.setAttribute("mode", "icons");
|
|
||||||
toptoolbar.setAttribute("iconsize", "small");
|
|
||||||
toptoolbar.setAttribute("toolboxid", "navigator-toolbox");
|
|
||||||
toptoolbar.setAttribute("context", "toolbar-context-menu");
|
|
||||||
toptoolbar.setAttribute("lockiconsize", "true");
|
|
||||||
toptoolbar.setAttribute("defaultset", "spring");
|
|
||||||
|
|
||||||
document
|
|
||||||
.querySelector("#navigator-toolbox")
|
|
||||||
.appendChild(toptoolbar);
|
|
||||||
|
|
||||||
CustomizableUI.registerArea("additional_top_toolbar" + i + "", {
|
|
||||||
legacy: true,
|
|
||||||
});
|
|
||||||
if (appversion >= 65)
|
|
||||||
CustomizableUI.registerToolbarNode(toptoolbar);
|
|
||||||
|
|
||||||
// top toolbars 'collapsed' on startup
|
|
||||||
//setToolbarVisibility(toptoolbar, false);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Services.prefs
|
|
||||||
.getDefaultBranch("browser.additional_top_toolbar" + i + ".")
|
|
||||||
.setBoolPref("enabled", true);
|
|
||||||
setToolbarVisibility(
|
|
||||||
document.getElementById("additional_top_toolbar" + i + ""),
|
|
||||||
Services.prefs
|
|
||||||
.getBranch("browser.additional_top_toolbar" + i + ".")
|
|
||||||
.getBoolPref("enabled")
|
|
||||||
);
|
);
|
||||||
} catch (e) {}
|
toptoolbar.setAttribute("mode", "icons");
|
||||||
|
toptoolbar.setAttribute("iconsize", "small");
|
||||||
|
toptoolbar.setAttribute("toolboxid", "navigator-toolbox");
|
||||||
|
toptoolbar.setAttribute("context", "toolbar-context-menu");
|
||||||
|
toptoolbar.setAttribute("lockiconsize", "true");
|
||||||
|
toptoolbar.setAttribute("defaultset", "spring");
|
||||||
|
|
||||||
i++;
|
document
|
||||||
|
.querySelector("#navigator-toolbox")
|
||||||
|
.appendChild(toptoolbar);
|
||||||
|
|
||||||
|
CustomizableUI.registerArea("additional_top_toolbar" + i + "", {
|
||||||
|
legacy: true,
|
||||||
|
});
|
||||||
|
if (appversion >= 65)
|
||||||
|
CustomizableUI.registerToolbarNode(toptoolbar);
|
||||||
|
|
||||||
|
// top toolbars 'collapsed' on startup
|
||||||
|
//setToolbarVisibility(toptoolbar, false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Services.prefs
|
||||||
|
.getDefaultBranch("browser.additional_top_toolbar" + i + ".")
|
||||||
|
.setBoolPref("enabled", true);
|
||||||
|
setToolbarVisibility(
|
||||||
|
document.getElementById("additional_top_toolbar" + i + ""),
|
||||||
|
Services.prefs
|
||||||
|
.getBranch("browser.additional_top_toolbar" + i + ".")
|
||||||
|
.getBoolPref("enabled")
|
||||||
|
);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CSS
|
||||||
|
var sss = Components.classes[
|
||||||
|
"@mozilla.org/content/style-sheet-service;1"
|
||||||
|
].getService(Components.interfaces.nsIStyleSheetService);
|
||||||
|
|
||||||
|
var uri = Services.io.newURI(
|
||||||
|
"data:text/css;charset=utf-8," +
|
||||||
|
encodeURIComponent(
|
||||||
|
`toolbar[id^="additional_top_toolbar"] {
|
||||||
|
-moz-appearance: none !important;
|
||||||
|
background-color: var(--toolbar-bgcolor);
|
||||||
|
background-image: var(--toolbar-bgimage);
|
||||||
|
background-clip: padding-box;
|
||||||
|
color: var(--toolbar-color, inherit);
|
||||||
|
}
|
||||||
|
#main-window[customizing] toolbar[id^="additional_top_toolbar"] {
|
||||||
|
outline: 1px dashed !important;
|
||||||
|
outline-offset: -2px !important;
|
||||||
|
}`
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSS
|
if (number_of_additional_top_toolbars >= 1) {
|
||||||
var sss = Components.classes[
|
var observer1 = new MutationObserver(function (mutations) {
|
||||||
"@mozilla.org/content/style-sheet-service;1"
|
mutations.forEach(function (mutation) {
|
||||||
].getService(Components.interfaces.nsIStyleSheetService);
|
Services.prefs
|
||||||
|
.getBranch("browser.additional_top_toolbar1.")
|
||||||
var uri = Services.io.newURI(
|
.setBoolPref(
|
||||||
"data:text/css;charset=utf-8," +
|
"enabled",
|
||||||
encodeURIComponent(
|
!document.querySelector("#additional_top_toolbar1").collapsed
|
||||||
'\
|
);
|
||||||
\
|
});
|
||||||
toolbar[id^="additional_top_toolbar"] { \
|
|
||||||
-moz-appearance: none !important; \
|
|
||||||
background-color: var(--toolbar-bgcolor); \
|
|
||||||
background-image: var(--toolbar-bgimage); \
|
|
||||||
background-clip: padding-box; \
|
|
||||||
color: var(--toolbar-color, inherit); \
|
|
||||||
} \
|
|
||||||
#main-window[customizing] toolbar[id^="additional_top_toolbar"] { \
|
|
||||||
outline: 1px dashed !important; \
|
|
||||||
outline-offset: -2px !important; \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
'
|
|
||||||
),
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (number_of_additional_top_toolbars >= 1) {
|
|
||||||
var observer1 = new MutationObserver(function (mutations) {
|
|
||||||
mutations.forEach(function (mutation) {
|
|
||||||
Services.prefs
|
|
||||||
.getBranch("browser.additional_top_toolbar1.")
|
|
||||||
.setBoolPref(
|
|
||||||
"enabled",
|
|
||||||
!document.querySelector("#additional_top_toolbar1").collapsed
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
observer1.observe(
|
||||||
observer1.observe(document.querySelector("#additional_top_toolbar1"), {
|
document.querySelector("#additional_top_toolbar1"),
|
||||||
attributes: true,
|
{
|
||||||
childList: true,
|
attributes: true,
|
||||||
characterData: true,
|
childList: true,
|
||||||
});
|
characterData: true,
|
||||||
}
|
}
|
||||||
if (number_of_additional_top_toolbars >= 2) {
|
);
|
||||||
var observer2 = new MutationObserver(function (mutations) {
|
}
|
||||||
mutations.forEach(function (mutation) {
|
if (number_of_additional_top_toolbars >= 2) {
|
||||||
Services.prefs
|
var observer2 = new MutationObserver(function (mutations) {
|
||||||
.getBranch("browser.additional_top_toolbar2.")
|
mutations.forEach(function (mutation) {
|
||||||
.setBoolPref(
|
Services.prefs
|
||||||
"enabled",
|
.getBranch("browser.additional_top_toolbar2.")
|
||||||
!document.querySelector("#additional_top_toolbar2").collapsed
|
.setBoolPref(
|
||||||
);
|
"enabled",
|
||||||
|
!document.querySelector("#additional_top_toolbar2").collapsed
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
observer2.observe(
|
||||||
observer2.observe(document.querySelector("#additional_top_toolbar2"), {
|
document.querySelector("#additional_top_toolbar2"),
|
||||||
attributes: true,
|
{
|
||||||
childList: true,
|
attributes: true,
|
||||||
characterData: true,
|
childList: true,
|
||||||
});
|
characterData: true,
|
||||||
}
|
}
|
||||||
if (number_of_additional_top_toolbars >= 3) {
|
);
|
||||||
var observer3 = new MutationObserver(function (mutations) {
|
}
|
||||||
mutations.forEach(function (mutation) {
|
if (number_of_additional_top_toolbars >= 3) {
|
||||||
Services.prefs
|
var observer3 = new MutationObserver(function (mutations) {
|
||||||
.getBranch("browser.additional_top_toolbar3.")
|
mutations.forEach(function (mutation) {
|
||||||
.setBoolPref(
|
Services.prefs
|
||||||
"enabled",
|
.getBranch("browser.additional_top_toolbar3.")
|
||||||
!document.querySelector("#additional_top_toolbar3").collapsed
|
.setBoolPref(
|
||||||
);
|
"enabled",
|
||||||
|
!document.querySelector("#additional_top_toolbar3").collapsed
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
observer3.observe(
|
||||||
observer3.observe(document.querySelector("#additional_top_toolbar3"), {
|
document.querySelector("#additional_top_toolbar3"),
|
||||||
attributes: true,
|
{
|
||||||
childList: true,
|
attributes: true,
|
||||||
characterData: true,
|
childList: true,
|
||||||
});
|
characterData: true,
|
||||||
}
|
}
|
||||||
if (number_of_additional_top_toolbars >= 4) {
|
);
|
||||||
var observer4 = new MutationObserver(function (mutations) {
|
}
|
||||||
mutations.forEach(function (mutation) {
|
if (number_of_additional_top_toolbars >= 4) {
|
||||||
Services.prefs
|
var observer4 = new MutationObserver(function (mutations) {
|
||||||
.getBranch("browser.additional_top_toolbar4.")
|
mutations.forEach(function (mutation) {
|
||||||
.setBoolPref(
|
Services.prefs
|
||||||
"enabled",
|
.getBranch("browser.additional_top_toolbar4.")
|
||||||
!document.querySelector("#additional_top_toolbar4").collapsed
|
.setBoolPref(
|
||||||
);
|
"enabled",
|
||||||
|
!document.querySelector("#additional_top_toolbar4").collapsed
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
observer4.observe(
|
||||||
observer4.observe(document.querySelector("#additional_top_toolbar4"), {
|
document.querySelector("#additional_top_toolbar4"),
|
||||||
attributes: true,
|
{
|
||||||
childList: true,
|
attributes: true,
|
||||||
characterData: true,
|
childList: true,
|
||||||
});
|
characterData: true,
|
||||||
}
|
}
|
||||||
if (number_of_additional_top_toolbars >= 5) {
|
);
|
||||||
var observer5 = new MutationObserver(function (mutations) {
|
}
|
||||||
mutations.forEach(function (mutation) {
|
if (number_of_additional_top_toolbars >= 5) {
|
||||||
Services.prefs
|
var observer5 = new MutationObserver(function (mutations) {
|
||||||
.getBranch("browser.additional_top_toolbar5.")
|
mutations.forEach(function (mutation) {
|
||||||
.setBoolPref(
|
Services.prefs
|
||||||
"enabled",
|
.getBranch("browser.additional_top_toolbar5.")
|
||||||
!document.querySelector("#additional_top_toolbar5").collapsed
|
.setBoolPref(
|
||||||
);
|
"enabled",
|
||||||
|
!document.querySelector("#additional_top_toolbar5").collapsed
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
observer5.observe(
|
||||||
observer5.observe(document.querySelector("#additional_top_toolbar5"), {
|
document.querySelector("#additional_top_toolbar5"),
|
||||||
attributes: true,
|
{
|
||||||
childList: true,
|
attributes: true,
|
||||||
characterData: true,
|
childList: true,
|
||||||
});
|
characterData: true,
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
);
|
||||||
},
|
}
|
||||||
};
|
} catch (e) {}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/* initialization delay workaround */
|
/* initialization delay workaround */
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
"DOMContentLoaded",
|
"DOMContentLoaded",
|
||||||
AdditionalTopToolbars.init(),
|
AdditionalTopToolbars.init(),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
// not needed anymore, but just in case someone prefers initialization that way
|
// not needed anymore, but just in case someone prefers initialization that way
|
||||||
/*
|
/*
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
AdditionalTopToolbars.init();
|
AdditionalTopToolbars.init();
|
||||||
},500);
|
},500);
|
||||||
*/
|
*/
|
||||||
|
})();
|
||||||
|
|
|
@ -1,42 +1,52 @@
|
||||||
function waitForElm(selector) {
|
(function () {
|
||||||
return new Promise(resolve => {
|
function waitForElm(selector) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (document.querySelector(selector)) {
|
||||||
|
return resolve(document.querySelector(selector));
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
if (document.querySelector(selector)) {
|
if (document.querySelector(selector)) {
|
||||||
return resolve(document.querySelector(selector));
|
resolve(document.querySelector(selector));
|
||||||
|
observer.disconnect();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const observer = new MutationObserver(mutations => {
|
observer.observe(document.body, {
|
||||||
if (document.querySelector(selector)) {
|
childList: true,
|
||||||
resolve(document.querySelector(selector));
|
subtree: true,
|
||||||
observer.disconnect();
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(document.body, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForElm('#find-button').then((elm) => {
|
waitForElm("#find-button").then((elm) => {
|
||||||
var find = document.querySelector("#find-button label.toolbarbutton-text");
|
var find = document.querySelector("#find-button label.toolbarbutton-text");
|
||||||
find.setAttribute("value", "Search");
|
if (find)
|
||||||
});
|
find.setAttribute("value", "Search");
|
||||||
|
});
|
||||||
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
waitForElm('#nav-bar').then((elm) => {
|
waitForElm("#nav-bar").then((elm) => {
|
||||||
var library = document.querySelector("#bookmarks-menu-button label.toolbarbutton-text");
|
var library = document.querySelector(
|
||||||
library.setAttribute("value", "Favorites");
|
"#bookmarks-menu-button label.toolbarbutton-text"
|
||||||
});
|
);
|
||||||
}, 0);
|
if (library)
|
||||||
|
library.setAttribute("value", "Favorites");
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
|
||||||
waitForElm('#bookmarksMenu').then((elm) => {
|
waitForElm("#bookmarksMenu").then((elm) => {
|
||||||
var bookmarksmenu = document.querySelector("#bookmarksMenu label.menubar-text");
|
var bookmarksmenu = document.querySelector(
|
||||||
bookmarksmenu.setAttribute("value", "Favorites");
|
"#bookmarksMenu label.menubar-text"
|
||||||
bookmarksmenu.setAttribute("accesskey", "a");
|
);
|
||||||
});
|
if (bookmarksmenu)
|
||||||
|
bookmarksmenu.setAttribute("value", "Favorites");
|
||||||
|
//bookmarksmenu.setAttribute("accesskey", "a");
|
||||||
|
});
|
||||||
|
|
||||||
waitForElm('#bookmarksMenu').then((elm) => {
|
waitForElm("#bookmarksMenu").then((elm) => {
|
||||||
var bookmarksmenu = document.querySelector("#bookmarksMenu");
|
var bookmarksmenu = document.querySelector("#bookmarksMenu");
|
||||||
bookmarksmenu.setAttribute("accesskey", "a");
|
if (bookmarksmenu)
|
||||||
});
|
bookmarksmenu.removeAttribute("accesskey");
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
|
@ -1,32 +1,32 @@
|
||||||
Components.utils.import("resource:///modules/CustomizableUI.jsm");
|
(function () {
|
||||||
var { Services } = Components.utils.import(
|
Components.utils.import("resource:///modules/CustomizableUI.jsm");
|
||||||
"resource://gre/modules/Services.jsm",
|
var Services =
|
||||||
{}
|
globalThis.Services ||
|
||||||
);
|
ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
|
||||||
var sss = Components.classes[
|
var sss = Components.classes[
|
||||||
"@mozilla.org/content/style-sheet-service;1"
|
"@mozilla.org/content/style-sheet-service;1"
|
||||||
].getService(Components.interfaces.nsIStyleSheetService);
|
].getService(Components.interfaces.nsIStyleSheetService);
|
||||||
|
|
||||||
var IE6StatusBar = {
|
var IE6StatusBar = {
|
||||||
init: function () {
|
init: function () {
|
||||||
try {
|
try {
|
||||||
// create a default toolbar button
|
// create a default toolbar button
|
||||||
CustomizableUI.createWidget({
|
CustomizableUI.createWidget({
|
||||||
id: "ie6_statusbar", // button id
|
id: "ie6_statusbar", // button id
|
||||||
defaultArea: CustomizableUI.AREA_NAVBAR,
|
defaultArea: CustomizableUI.AREA_NAVBAR,
|
||||||
removable: true,
|
removable: true,
|
||||||
label: "Internet", // button title
|
label: "Internet", // button title
|
||||||
tooltiptext: "", // tooltip title
|
tooltiptext: "", // tooltip title
|
||||||
onCreated: function (button) {
|
onCreated: function (button) {
|
||||||
return button;
|
return button;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// style button icon / embedded non-animated icon, because there is no image for then inside Fx anymore
|
// style button icon / embedded non-animated icon, because there is no image for then inside Fx anymore
|
||||||
var uri = Services.io.newURI(
|
var uri = Services.io.newURI(
|
||||||
"data:text/css;charset=utf-8," +
|
"data:text/css;charset=utf-8," +
|
||||||
encodeURIComponent(
|
encodeURIComponent(
|
||||||
`#ie6_statusbar {
|
`#ie6_statusbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 151px;
|
width: 151px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
|
@ -51,20 +51,21 @@ var IE6StatusBar = {
|
||||||
#ie6_statusbar > .toolbarbutton-icon {
|
#ie6_statusbar > .toolbarbutton-icon {
|
||||||
display: none;
|
display: none;
|
||||||
}`
|
}`
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
// remove old style sheet, before registering the new one
|
// remove old style sheet, before registering the new one
|
||||||
if (sss.sheetRegistered(uri, sss.AGENT_SHEET)) {
|
if (sss.sheetRegistered(uri, sss.AGENT_SHEET)) {
|
||||||
sss.unregisterSheet(uri, sss.AGENT_SHEET);
|
sss.unregisterSheet(uri, sss.AGENT_SHEET);
|
||||||
|
}
|
||||||
|
sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
|
||||||
|
} catch (e) {
|
||||||
|
Components.utils.reportError(e);
|
||||||
}
|
}
|
||||||
sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
|
},
|
||||||
} catch (e) {
|
};
|
||||||
Components.utils.reportError(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", IE6StatusBar.init(), false);
|
document.addEventListener("DOMContentLoaded", IE6StatusBar.init(), false);
|
||||||
|
})();
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
(function () {
|
(function () {
|
||||||
try {
|
try {
|
||||||
Components.utils.import("resource:///modules/CustomizableUI.jsm");
|
Components.utils.import("resource:///modules/CustomizableUI.jsm");
|
||||||
var { Services } = Components.utils.import(
|
var Services =
|
||||||
"resource://gre/modules/Services.jsm",
|
globalThis.Services ||
|
||||||
{}
|
ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
|
||||||
);
|
|
||||||
var sss = Components.classes[
|
var sss = Components.classes[
|
||||||
"@mozilla.org/content/style-sheet-service;1"
|
"@mozilla.org/content/style-sheet-service;1"
|
||||||
].getService(Components.interfaces.nsIStyleSheetService);
|
].getService(Components.interfaces.nsIStyleSheetService);
|
||||||
|
|
|
@ -2,82 +2,114 @@
|
||||||
// option: place urlbar on a different toolbar
|
// option: place urlbar on a different toolbar
|
||||||
// option: place back button on a different toolbar
|
// option: place back button on a different toolbar
|
||||||
// option: place back button on a different toolbar
|
// option: place back button on a different toolbar
|
||||||
|
(function () {
|
||||||
|
var {CustomizableUI} = Components.utils.import(
|
||||||
|
"resource:///modules/CustomizableUI.jsm",
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
var { CustomizableUI } = Components.utils.import(
|
var navigation = CustomizableUI.AREA_NAVBAR;
|
||||||
"resource:///modules/CustomizableUI.jsm",
|
var tabs = CustomizableUI.AREA_TABSTRIP;
|
||||||
{}
|
var menu = CustomizableUI.AREA_MENUBAR;
|
||||||
);
|
var bookmarks = CustomizableUI.AREA_BOOKMARKS;
|
||||||
|
|
||||||
var navigation = CustomizableUI.AREA_NAVBAR;
|
/* [target toolbar of item]
|
||||||
var tabs = CustomizableUI.AREA_TABSTRIP;
|
menu = 'menubar'
|
||||||
var menu = CustomizableUI.AREA_MENUBAR;
|
tabs = 'tabs toolbar'
|
||||||
var bookmarks = CustomizableUI.AREA_BOOKMARKS;
|
bookmarks = 'bookmarks toolbar'
|
||||||
|
navigation='navigation toolbar' */
|
||||||
|
var backbutton_on_toolbar = navigation;
|
||||||
|
var forwardbutton_on_toolbar = navigation;
|
||||||
|
var urlbar_on_toolbar = navigation;
|
||||||
|
|
||||||
/* [target toolbar of item]
|
/* [target position of item]
|
||||||
menu = 'menubar'
|
0 = 1st
|
||||||
tabs = 'tabs toolbar'
|
1 = 2nd
|
||||||
bookmarks = 'bookmarks toolbar'
|
2 = 3rd
|
||||||
navigation='navigation toolbar' */
|
...
|
||||||
var backbutton_on_toolbar = navigation;
|
x = xth */
|
||||||
var forwardbutton_on_toolbar = navigation;
|
var backbutton_on_toolbar_position = 0;
|
||||||
var urlbar_on_toolbar = navigation;
|
var forwardbutton_on_toolbar_position = 1;
|
||||||
|
var urlbar_on_toolbar_position = 0;
|
||||||
|
|
||||||
/* [target position of item]
|
const separateAddressBar = true;
|
||||||
0 = 1st
|
const moveNavigation = true;
|
||||||
1 = 2nd
|
const bottomTabs = true;
|
||||||
2 = 3rd
|
const menuToTop = true;
|
||||||
...
|
|
||||||
x = xth */
|
|
||||||
var backbutton_on_toolbar_position = 0;
|
|
||||||
var forwardbutton_on_toolbar_position = 1;
|
|
||||||
var urlbar_on_toolbar_position = 0;
|
|
||||||
|
|
||||||
var MoveUrlbar = {
|
var MoveUrlbar = {
|
||||||
init: function () {
|
init: function () {
|
||||||
try {
|
try {
|
||||||
document.getElementById("back-button").setAttribute("removable", "true");
|
document
|
||||||
document
|
.getElementById("back-button")
|
||||||
.getElementById("forward-button")
|
.setAttribute("removable", "true");
|
||||||
.setAttribute("removable", "true");
|
document
|
||||||
document
|
.getElementById("forward-button")
|
||||||
.getElementById("urlbar-container")
|
.setAttribute("removable", "true");
|
||||||
.setAttribute("removable", "true");
|
document
|
||||||
document
|
.getElementById("urlbar-container")
|
||||||
.getElementById("additional_top_toolbar1")
|
.setAttribute("removable", "true");
|
||||||
.setAttribute("fullscreentoolbar", "true");
|
document
|
||||||
} catch (e) {}
|
.getElementById("unified-extensions-button")
|
||||||
|
.setAttribute("removable", "true");
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
/*CustomizableUI.addWidgetToArea("back-button", backbutton_on_toolbar);
|
// Forces back/forward to navigation bar
|
||||||
CustomizableUI.moveWidgetWithinArea(
|
if (moveNavigation) {
|
||||||
"back-button",
|
CustomizableUI.addWidgetToArea("back-button", backbutton_on_toolbar);
|
||||||
backbutton_on_toolbar_position
|
CustomizableUI.moveWidgetWithinArea(
|
||||||
);
|
"back-button",
|
||||||
CustomizableUI.addWidgetToArea("forward-button", forwardbutton_on_toolbar);
|
backbutton_on_toolbar_position
|
||||||
CustomizableUI.moveWidgetWithinArea(
|
);
|
||||||
"forward-button",
|
CustomizableUI.addWidgetToArea(
|
||||||
forwardbutton_on_toolbar_position
|
"forward-button",
|
||||||
);
|
forwardbutton_on_toolbar
|
||||||
CustomizableUI.addWidgetToArea(
|
);
|
||||||
"urlbar-container",
|
CustomizableUI.moveWidgetWithinArea(
|
||||||
"additional_top_toolbar1"
|
"forward-button",
|
||||||
);
|
forwardbutton_on_toolbar_position
|
||||||
CustomizableUI.moveWidgetWithinArea(
|
);
|
||||||
"urlbar-container",
|
}
|
||||||
urlbar_on_toolbar_position
|
|
||||||
);*/
|
|
||||||
|
|
||||||
document
|
// Forces address bar to additional_top_toolbar1 (Address)
|
||||||
.getElementById("navigator-toolbox")
|
// THIS WILL SOFTBRICK YOUR FIREFOX IF ADDITIONAL TOP TOOLBARS AREN'T
|
||||||
.appendChild(document.getElementById("TabsToolbar"));
|
// ENABLED BUT THIS IS
|
||||||
|
if (separateAddressBar) {
|
||||||
|
CustomizableUI.addWidgetToArea(
|
||||||
|
"urlbar-container",
|
||||||
|
"additional_top_toolbar1"
|
||||||
|
);
|
||||||
|
CustomizableUI.moveWidgetWithinArea(
|
||||||
|
"urlbar-container",
|
||||||
|
urlbar_on_toolbar_position
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
// Moves tabs to the bottom of the toolbar stack
|
||||||
try {
|
if (bottomTabs) {
|
||||||
document.getElementById('back-button').setAttribute('removable','false');
|
document
|
||||||
document.getElementById('forward-button').setAttribute('removable','false');
|
.getElementById("navigator-toolbox")
|
||||||
document.getElementById('urlbar-container').setAttribute('removable','false');
|
.appendChild(document.getElementById("TabsToolbar"));
|
||||||
} catch(e){}
|
}
|
||||||
*/
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", MoveUrlbar.init(), false);
|
// Moves the main menu button to the top row, before the last item
|
||||||
|
// (activity throbber). This is because the icon is hidden but not the
|
||||||
|
// button and creates bad padding if you're using the navigation bar as
|
||||||
|
// an address bar
|
||||||
|
if (menuToTop) {
|
||||||
|
setTimeout(() => {
|
||||||
|
document
|
||||||
|
.getElementById("PanelUI-button")
|
||||||
|
.setAttribute("removable", "true");
|
||||||
|
const menubar = document.getElementById(menu);
|
||||||
|
menubar.insertBefore(
|
||||||
|
document.getElementById("PanelUI-button"),
|
||||||
|
menubar.lastChild
|
||||||
|
);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", MoveUrlbar.init(), false);
|
||||||
|
})();
|
||||||
|
|
Before Width: | Height: | Size: 257 B After Width: | Height: | Size: 257 B |
2503
chrome/msfx/msfx.css
Normal file
BIN
chrome/msfx/toolbar.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
chrome/msfx/toolbar_large.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
|
@ -1,146 +1,149 @@
|
||||||
(function () {
|
(function () {
|
||||||
var css = `@media all and (-moz-overlay-scrollbars) {
|
var css = `@media all and (-moz-overlay-scrollbars) {
|
||||||
scrollbar[root="true"] {
|
scrollbar[root="true"] {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2147483647;
|
z-index: 2147483647;
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollbar:not([active="true"]),
|
scrollbar:not([active="true"]),
|
||||||
scrollbar[disabled="true"] {
|
scrollbar[disabled="true"] {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollbar
|
scrollbar
|
||||||
{
|
{
|
||||||
color-scheme: light !important;
|
color-scheme: light !important;
|
||||||
-moz-default-appearance: none;
|
-moz-default-appearance: none;
|
||||||
-moz-binding: url("chrome://global/content/bindings/scrollbar.xml#scrollbar");
|
-moz-binding: url("chrome://global/content/bindings/scrollbar.xml#scrollbar");
|
||||||
cursor: default;
|
cursor: default;
|
||||||
min-width: 16px !important;
|
min-width: 16px !important;
|
||||||
background-repeat: repeat !important;
|
background-color: ThreeDHighlight !important;
|
||||||
background-color: ThreeDHighlight !important;
|
opacity: 1 !important;
|
||||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdib3g9IjAgMCAyIDIiIHdpZHRoPSIyIiBoZWlnaHQ9IjIiPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIGZpbGw9Ii1tb3otZGlhbG9nIi8+PHJlY3QgZmlsbD0iVGhyZWVESGlnaGxpZ2h0IiB4PSIxIiB5PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIi8+PHJlY3QgeD0iMSIgeT0iMSIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0iLW1vei1kaWFsb2ciLz48cmVjdCBmaWxsPSJUaHJlZURIaWdobGlnaHQiIHg9IjAiIHk9IjEiIHdpZHRoPSIxIiBoZWlnaHQ9IjEiLz48L3N2Zz4=") !important;
|
}
|
||||||
opacity: 1 !important;
|
|
||||||
}
|
thumb
|
||||||
|
{
|
||||||
thumb
|
color-scheme: light !important;
|
||||||
{
|
-moz-default-appearance: none !important;
|
||||||
color-scheme: light !important;
|
position: relative !important;
|
||||||
-moz-default-appearance: none !important;
|
background-color: -moz-Dialog !important;
|
||||||
position: relative !important;
|
min-height: 8px !important;
|
||||||
background-color: -moz-Dialog !important;
|
pointer-events: auto !important;
|
||||||
min-height: 8px !important;
|
border: 0 !important;
|
||||||
pointer-events: auto !important;
|
box-shadow: inset -1px -1px 0 WindowFrame, inset 1px 1px 0 -moz-Dialog, inset -2px -2px 0 ThreeDShadow, inset 2px 2px 0 ThreeDHighlight !important;
|
||||||
border: 0 !important;
|
}
|
||||||
box-shadow: inset -1px -1px 0 WindowFrame, inset 1px 1px 0 -moz-Dialog, inset -2px -2px 0 ThreeDShadow, inset 2px 2px 0 ThreeDHighlight !important;
|
|
||||||
}
|
thumb[orient="horizontal"] {
|
||||||
|
-moz-default-appearance: none;
|
||||||
thumb[orient="horizontal"] {
|
min-height: 16px !important;
|
||||||
-moz-default-appearance: none;
|
min-width: 8px !important;
|
||||||
min-height: 16px !important;
|
max-width: 100% !important;
|
||||||
min-width: 8px !important;
|
background-repeat: no-repeat !important;
|
||||||
max-width: 100% !important;
|
}
|
||||||
background-repeat: no-repeat !important;
|
|
||||||
}
|
scrollbarbutton
|
||||||
|
{
|
||||||
scrollbarbutton
|
color-scheme: light !important;
|
||||||
{
|
min-width: 16px !important;
|
||||||
color-scheme: light !important;
|
min-height: 16px !important;
|
||||||
min-width: 16px !important;
|
-moz-default-appearance: none !important;
|
||||||
min-height: 16px !important;
|
background-color: -moz-Dialog !important;
|
||||||
-moz-default-appearance: none !important;
|
box-shadow: inset -1px -1px 0 WindowFrame, inset 1px 1px 0 -moz-Dialog, inset -2px -2px 0 ThreeDShadow, inset 2px 2px 0 ThreeDHighlight !important;
|
||||||
background-color: -moz-Dialog !important;
|
}
|
||||||
box-shadow: inset -1px -1px 0 WindowFrame, inset 1px 1px 0 -moz-Dialog, inset -2px -2px 0 ThreeDShadow, inset 2px 2px 0 ThreeDHighlight !important;
|
|
||||||
}
|
scrollbarbutton:not(.disabled):hover:active
|
||||||
|
{
|
||||||
scrollbarbutton:not(.disabled):hover:active
|
background-color: -moz-dialog !important;
|
||||||
{
|
border: 1px solid ThreeDShadow !important;
|
||||||
background-color: -moz-dialog !important;
|
box-shadow: none !important;
|
||||||
border: 1px solid ThreeDShadow !important;
|
}
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
slider,
|
||||||
|
slider[orient="vertical"]
|
||||||
slider,
|
{
|
||||||
slider[orient="vertical"]
|
color-scheme: light !important;
|
||||||
{
|
-moz-default-appearance: none;
|
||||||
color-scheme: light !important;
|
background-color: ThreeDHighlight !important;
|
||||||
-moz-default-appearance: none;
|
background-repeat: repeat !important;
|
||||||
}
|
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMiIgaGVpZ2h0PSIyIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAuNTI5MTcgLjUyOTE3IiB4bWw6c3BhY2U9InByZXNlcnZlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnPjxnPjxyZWN0IHg9Ii4yNjQ1OCIgeT0iMC4wMDAwIiB3aWR0aD0iLjI2NDU4IiBoZWlnaHQ9Ii4yNjQ1OCIgZmlsbD0iVGhyZWVEU2hhZG93Ii8+PHJlY3QgeD0iMC4wMDAwIiB5PSIuMjY0NTgiIHdpZHRoPSIuMjY0NTgiIGhlaWdodD0iLjI2NDU4IiBmaWxsPSJUaHJlZURTaGFkb3ciLz48cmVjdCB4PSIwLjAwMDAiIHk9IjAuMDAwMCIgd2lkdGg9Ii4yNjQ1OCIgaGVpZ2h0PSIuMjY0NTgiIGZpbGw9IlRocmVlREhpZ2hsaWdodCIvPjxyZWN0IHg9Ii4yNjQ1OCIgeT0iLjI2NDU4IiB3aWR0aD0iLjI2NDU4IiBoZWlnaHQ9Ii4yNjQ1OCIgZmlsbD0iVGhyZWVESGlnaGxpZ2h0Ii8+PC9nPjwvZz48L3N2Zz4=") !important;
|
||||||
|
}
|
||||||
scrollcorner
|
|
||||||
{
|
scrollcorner
|
||||||
color-scheme: light !important;
|
{
|
||||||
-moz-default-appearance: none !important;
|
color-scheme: light !important;
|
||||||
-moz-binding: url(chrome://global/content/bindings/scrollbar.xml#scrollbar-base);
|
-moz-default-appearance: none !important;
|
||||||
width: 16px;
|
-moz-binding: url(chrome://global/content/bindings/scrollbar.xml#scrollbar-base);
|
||||||
cursor: default;
|
width: 16px;
|
||||||
background-color: -moz-dialog; !important;
|
cursor: default;
|
||||||
}
|
background-color: -moz-dialog; !important;
|
||||||
|
}
|
||||||
scrollbarbutton[type="increment"]
|
|
||||||
{
|
scrollbarbutton[type="increment"]
|
||||||
-moz-default-appearance: none;
|
{
|
||||||
background-repeat: no-repeat !important;
|
-moz-default-appearance: none;
|
||||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSI3IiB5PSI0IiB4PSI1IiBmaWxsPSJtZW51dGV4dCIvPjxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjUiIHk9IjUiIHg9IjYiIGZpbGw9Im1lbnV0ZXh0Ii8+PHJlY3Qgd2lkdGg9IjEiIGhlaWdodD0iMyIgeT0iNiIgeD0iNyIgZmlsbD0ibWVudXRleHQiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB5PSI3IiB4PSI4IiBmaWxsPSJtZW51dGV4dCIvPg0KCQ0KCQ0KPC9zdmc+") !important;
|
background-repeat: no-repeat !important;
|
||||||
background-position: center center !important;
|
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4KICA8cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSI3IiB5PSI0IiB4PSI1IiBmaWxsPSJDYW52YXNUZXh0Ii8+PHJlY3Qgd2lkdGg9IjEiIGhlaWdodD0iNSIgeT0iNSIgeD0iNiIgZmlsbD0iQ2FudmFzVGV4dCIvPjxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjMiIHk9IjYiIHg9IjciIGZpbGw9IkNhbnZhc1RleHQiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB5PSI3IiB4PSI4IiBmaWxsPSJDYW52YXNUZXh0Ii8+CiAgCiAgCjwvc3ZnPg==") !important;
|
||||||
}
|
background-position: center center !important;
|
||||||
|
}
|
||||||
scrollbar[orient="vertical"] > scrollbarbutton[type="increment"]
|
|
||||||
{
|
scrollbar[orient="vertical"] > scrollbarbutton[type="increment"]
|
||||||
-moz-default-appearance: none;
|
{
|
||||||
background-repeat: no-repeat !important;
|
-moz-default-appearance: none;
|
||||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTUiIHdpZHRoPSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCBoZWlnaHQ9IjEiIHdpZHRoPSI3IiB4PSIzIiB5PSI1IiBmaWxsPSJtZW51dGV4dCIvPjxyZWN0IGhlaWdodD0iMSIgd2lkdGg9IjUiIHk9IjYiIHg9IjQiIGZpbGw9Im1lbnV0ZXh0Ii8+PHJlY3QgaGVpZ2h0PSIxIiB3aWR0aD0iMyIgeD0iNSIgeT0iNyIgZmlsbD0ibWVudXRleHQiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4PSI2IiB5PSI4IiBmaWxsPSJtZW51dGV4dCIvPg0KCQ0KCQ0KPC9zdmc+") !important;
|
background-repeat: no-repeat !important;
|
||||||
background-position: center center !important;
|
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTUiIHdpZHRoPSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4KICA8cmVjdCBoZWlnaHQ9IjEiIHdpZHRoPSI3IiB4PSIzIiB5PSI1IiBmaWxsPSJDYW52YXNUZXh0Ii8+PHJlY3QgaGVpZ2h0PSIxIiB3aWR0aD0iNSIgeT0iNiIgeD0iNCIgZmlsbD0iQ2FudmFzVGV4dCIvPjxyZWN0IGhlaWdodD0iMSIgd2lkdGg9IjMiIHg9IjUiIHk9IjciIGZpbGw9IkNhbnZhc1RleHQiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4PSI2IiB5PSI4IiBmaWxsPSJDYW52YXNUZXh0Ii8+CiAgCiAgCjwvc3ZnPg==") !important;
|
||||||
}
|
background-position: center center !important;
|
||||||
|
}
|
||||||
scrollbarbutton[type="decrement"]
|
|
||||||
{
|
scrollbarbutton[type="decrement"]
|
||||||
-moz-default-appearance: none;
|
{
|
||||||
background-repeat: no-repeat !important;
|
-moz-default-appearance: none;
|
||||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTUiIHdpZHRoPSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSI3IiB5PSI0IiB4PSI4IiBmaWxsPSJtZW51dGV4dCIvPjxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjUiIHk9IjUiIHg9IjciIGZpbGw9Im1lbnV0ZXh0Ii8+PHJlY3Qgd2lkdGg9IjEiIGhlaWdodD0iMyIgeT0iNiIgeD0iNiIgZmlsbD0ibWVudXRleHQiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB5PSI3IiB4PSI1IiBmaWxsPSJtZW51dGV4dCIvPg0KCQ0KCQ0KPC9zdmc+") !important;
|
background-repeat: no-repeat !important;
|
||||||
background-position: center center !important;
|
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTUiIHdpZHRoPSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4KICA8cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSI3IiB5PSI0IiB4PSI4IiBmaWxsPSJDYW52YXNUZXh0Ii8+PHJlY3Qgd2lkdGg9IjEiIGhlaWdodD0iNSIgeT0iNSIgeD0iNyIgZmlsbD0iQ2FudmFzVGV4dCIvPjxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjMiIHk9IjYiIHg9IjYiIGZpbGw9IkNhbnZhc1RleHQiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB5PSI3IiB4PSI1IiBmaWxsPSJDYW52YXNUZXh0Ii8+CiAgCiAgCjwvc3ZnPg==") !important;
|
||||||
}
|
background-position: center center !important;
|
||||||
|
}
|
||||||
scrollbar[orient="vertical"] > scrollbarbutton[type="decrement"]
|
|
||||||
{
|
scrollbar[orient="vertical"] > scrollbarbutton[type="decrement"]
|
||||||
-moz-default-appearance: none;
|
{
|
||||||
background-repeat: no-repeat !important;
|
-moz-default-appearance: none;
|
||||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCBoZWlnaHQ9IjEiIHdpZHRoPSI3IiB5PSI4IiB4PSIzIiBmaWxsPSJtZW51dGV4dCIvPjxyZWN0IGhlaWdodD0iMSIgd2lkdGg9IjUiIHg9IjQiIHk9IjciIGZpbGw9Im1lbnV0ZXh0Ii8+PHJlY3QgaGVpZ2h0PSIxIiB3aWR0aD0iMyIgeT0iNiIgeD0iNSIgZmlsbD0ibWVudXRleHQiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB5PSI1IiB4PSI2IiBmaWxsPSJtZW51dGV4dCIvPg0KCQ0KCQ0KPC9zdmc+") !important;
|
background-repeat: no-repeat !important;
|
||||||
background-position: center center !important;
|
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4KCTxyZWN0IGhlaWdodD0iMSIgd2lkdGg9IjciIHk9IjgiIHg9IjMiIGZpbGw9IkNhbnZhc1RleHQiLz48cmVjdCBoZWlnaHQ9IjEiIHdpZHRoPSI1IiB4PSI0IiB5PSI3IiBmaWxsPSJDYW52YXNUZXh0Ii8+PHJlY3QgaGVpZ2h0PSIxIiB3aWR0aD0iMyIgeT0iNiIgeD0iNSIgZmlsbD0iQ2FudmFzVGV4dCIvPjxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjEiIHk9IjUiIHg9IjYiIGZpbGw9IkNhbnZhc1RleHQiLz4KCQoJCjwvc3ZnPg==") !important;
|
||||||
}
|
background-position: center center !important;
|
||||||
|
}
|
||||||
scrollbarbutton[type="increment"][disabled="true"]
|
|
||||||
{
|
scrollbarbutton[type="increment"][disabled="true"]
|
||||||
background-repeat: no-repeat !important;
|
{
|
||||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIHdpZHRoPSIxIiBoZWlnaHQ9IjciIHk9IjQiIHg9IjUiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIyIiBmaWxsPSJUaHJlZURIaWdobGlnaHQiIHg9IjYiIHk9IjEwIi8+PHJlY3Qgd2lkdGg9IjEiIGhlaWdodD0iMiIgZmlsbD0iVGhyZWVESGlnaGxpZ2h0IiB4PSI3IiB5PSI5Ii8+PHJlY3Qgd2lkdGg9IjEiIGhlaWdodD0iMiIgZmlsbD0iVGhyZWVESGlnaGxpZ2h0IiB4PSI4IiB5PSI4Ii8+PHJlY3Qgd2lkdGg9IjEiIGZpbGw9IlRocmVlREhpZ2hsaWdodCIgaGVpZ2h0PSIxIiB5PSI4IiB4PSI5Ii8+PHJlY3QgZmlsbD0iVGhyZWVEU2hhZG93IiB3aWR0aD0iMSIgaGVpZ2h0PSI1IiB5PSI1IiB4PSI2Ii8+PHJlY3QgZmlsbD0iVGhyZWVEU2hhZG93IiB3aWR0aD0iMSIgaGVpZ2h0PSIzIiB5PSI2IiB4PSI3Ii8+PHJlY3QgZmlsbD0iVGhyZWVEU2hhZG93IiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB5PSI3IiB4PSI4Ii8+DQoJDQoJDQo8L3N2Zz4=");
|
background-repeat: no-repeat !important;
|
||||||
background-position: center center !important;
|
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIHdpZHRoPSIxIiBoZWlnaHQ9IjciIHk9IjQiIHg9IjUiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIyIiBmaWxsPSJUaHJlZURIaWdobGlnaHQiIHg9IjYiIHk9IjEwIi8+PHJlY3Qgd2lkdGg9IjEiIGhlaWdodD0iMiIgZmlsbD0iVGhyZWVESGlnaGxpZ2h0IiB4PSI3IiB5PSI5Ii8+PHJlY3Qgd2lkdGg9IjEiIGhlaWdodD0iMiIgZmlsbD0iVGhyZWVESGlnaGxpZ2h0IiB4PSI4IiB5PSI4Ii8+PHJlY3Qgd2lkdGg9IjEiIGZpbGw9IlRocmVlREhpZ2hsaWdodCIgaGVpZ2h0PSIxIiB5PSI4IiB4PSI5Ii8+PHJlY3QgZmlsbD0iVGhyZWVEU2hhZG93IiB3aWR0aD0iMSIgaGVpZ2h0PSI1IiB5PSI1IiB4PSI2Ii8+PHJlY3QgZmlsbD0iVGhyZWVEU2hhZG93IiB3aWR0aD0iMSIgaGVpZ2h0PSIzIiB5PSI2IiB4PSI3Ii8+PHJlY3QgZmlsbD0iVGhyZWVEU2hhZG93IiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB5PSI3IiB4PSI4Ii8+DQoJDQoJDQo8L3N2Zz4=");
|
||||||
}
|
background-position: center center !important;
|
||||||
|
}
|
||||||
scrollbar[orient="vertical"] > scrollbarbutton[type="increment"][disabled="true"]
|
|
||||||
{
|
scrollbar[orient="vertical"] > scrollbarbutton[type="increment"][disabled="true"]
|
||||||
background-repeat: no-repeat !important;
|
{
|
||||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTUiIHdpZHRoPSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIGhlaWdodD0iMSIgd2lkdGg9IjciIHg9IjMiIHk9IjUiLz48cmVjdCBmaWxsPSJUaHJlZURIaWdobGlnaHQiIGhlaWdodD0iMSIgd2lkdGg9IjIiIHk9IjYiIHg9IjkiLz48cmVjdCBmaWxsPSJUaHJlZURIaWdobGlnaHQiIGhlaWdodD0iMSIgd2lkdGg9IjIiIHk9IjciIHg9IjgiLz48cmVjdCBmaWxsPSJUaHJlZURIaWdobGlnaHQiIGhlaWdodD0iMSIgd2lkdGg9IjIiIHg9IjciIHk9IjgiLz48cmVjdCBmaWxsPSJUaHJlZURIaWdobGlnaHQiIGhlaWdodD0iMSIgd2lkdGg9IjEiIHg9IjciIHk9IjkiLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIGhlaWdodD0iMSIgd2lkdGg9IjUiIHk9IjYiIHg9IjQiLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIGhlaWdodD0iMSIgd2lkdGg9IjMiIHg9IjUiIHk9IjciLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIHg9IjYiIHk9IjgiLz4NCgkNCgkNCjwvc3ZnPg==");
|
background-repeat: no-repeat !important;
|
||||||
background-position: center center !important;
|
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTUiIHdpZHRoPSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIGhlaWdodD0iMSIgd2lkdGg9IjciIHg9IjMiIHk9IjUiLz48cmVjdCBmaWxsPSJUaHJlZURIaWdobGlnaHQiIGhlaWdodD0iMSIgd2lkdGg9IjIiIHk9IjYiIHg9IjkiLz48cmVjdCBmaWxsPSJUaHJlZURIaWdobGlnaHQiIGhlaWdodD0iMSIgd2lkdGg9IjIiIHk9IjciIHg9IjgiLz48cmVjdCBmaWxsPSJUaHJlZURIaWdobGlnaHQiIGhlaWdodD0iMSIgd2lkdGg9IjIiIHg9IjciIHk9IjgiLz48cmVjdCBmaWxsPSJUaHJlZURIaWdobGlnaHQiIGhlaWdodD0iMSIgd2lkdGg9IjEiIHg9IjciIHk9IjkiLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIGhlaWdodD0iMSIgd2lkdGg9IjUiIHk9IjYiIHg9IjQiLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIGhlaWdodD0iMSIgd2lkdGg9IjMiIHg9IjUiIHk9IjciLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIHg9IjYiIHk9IjgiLz4NCgkNCgkNCjwvc3ZnPg==");
|
||||||
}
|
background-position: center center !important;
|
||||||
|
}
|
||||||
scrollbarbutton[type="decrement"][disabled="true"]
|
|
||||||
{
|
scrollbarbutton[type="decrement"][disabled="true"]
|
||||||
background-repeat: no-repeat !important;
|
{
|
||||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTUiIHdpZHRoPSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIHdpZHRoPSIxIiBoZWlnaHQ9IjciIHk9IjQiIHg9IjgiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSI3IiBmaWxsPSJUaHJlZURIaWdobGlnaHQiIHk9IjUiIHg9IjkiLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIHdpZHRoPSIxIiBoZWlnaHQ9IjUiIHk9IjUiIHg9IjciLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIHdpZHRoPSIxIiBoZWlnaHQ9IjMiIHk9IjYiIHg9IjYiLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIHk9IjciIHg9IjUiLz4NCgkNCgkNCjwvc3ZnPg==");
|
background-repeat: no-repeat !important;
|
||||||
background-position: center center !important;
|
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTUiIHdpZHRoPSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIHdpZHRoPSIxIiBoZWlnaHQ9IjciIHk9IjQiIHg9IjgiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSI3IiBmaWxsPSJUaHJlZURIaWdobGlnaHQiIHk9IjUiIHg9IjkiLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIHdpZHRoPSIxIiBoZWlnaHQ9IjUiIHk9IjUiIHg9IjciLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIHdpZHRoPSIxIiBoZWlnaHQ9IjMiIHk9IjYiIHg9IjYiLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIHk9IjciIHg9IjUiLz4NCgkNCgkNCjwvc3ZnPg==");
|
||||||
}
|
background-position: center center !important;
|
||||||
|
}
|
||||||
scrollbar[orient="vertical"] > scrollbarbutton[type="decrement"][disabled="true"]
|
|
||||||
{
|
scrollbar[orient="vertical"] > scrollbarbutton[type="decrement"][disabled="true"]
|
||||||
background-repeat: no-repeat !important;
|
{
|
||||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIGhlaWdodD0iMSIgd2lkdGg9IjciIHk9IjgiIHg9IjMiLz48cmVjdCBmaWxsPSJUaHJlZURIaWdobGlnaHQiIHdpZHRoPSI3IiBoZWlnaHQ9IjEiIHk9IjkiIHg9IjQiLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIGhlaWdodD0iMSIgd2lkdGg9IjUiIHg9IjQiIHk9IjciLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIGhlaWdodD0iMSIgd2lkdGg9IjMiIHk9IjYiIHg9IjUiLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIHk9IjUiIHg9IjYiLz4NCgkNCgkNCjwvc3ZnPg==") !important;
|
background-repeat: no-repeat !important;
|
||||||
background-position: center !important;
|
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIGhlaWdodD0iMSIgd2lkdGg9IjciIHk9IjgiIHg9IjMiLz48cmVjdCBmaWxsPSJUaHJlZURIaWdobGlnaHQiIHdpZHRoPSI3IiBoZWlnaHQ9IjEiIHk9IjkiIHg9IjQiLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIGhlaWdodD0iMSIgd2lkdGg9IjUiIHg9IjQiIHk9IjciLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIGhlaWdodD0iMSIgd2lkdGg9IjMiIHk9IjYiIHg9IjUiLz48cmVjdCBmaWxsPSJUaHJlZURTaGFkb3ciIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIHk9IjUiIHg9IjYiLz4NCgkNCgkNCjwvc3ZnPg==") !important;
|
||||||
}`;
|
background-position: center !important;
|
||||||
|
}`;
|
||||||
var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(
|
var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(
|
||||||
Ci.nsIStyleSheetService
|
Ci.nsIStyleSheetService
|
||||||
);
|
);
|
||||||
var uri = makeURI("data:text/css;charset=UTF=8," + encodeURIComponent(css));
|
var uri = Services.io.newURI(
|
||||||
|
"data:text/css;charset=UTF=8," + encodeURIComponent(css)
|
||||||
|
);
|
||||||
|
|
||||||
sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
|
sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -12,95 +12,118 @@
|
||||||
// [!] BUG: do not move main 'space'-item to palette or it will be hidden until customizing mode gets reopened
|
// [!] BUG: do not move main 'space'-item to palette or it will be hidden until customizing mode gets reopened
|
||||||
|
|
||||||
// [!] Fix for WebExtensions with own windows by 黒仪大螃蟹 (for 1-N scripts)
|
// [!] Fix for WebExtensions with own windows by 黒仪大螃蟹 (for 1-N scripts)
|
||||||
|
(function () {
|
||||||
|
Components.utils.import("resource:///modules/CustomizableUI.jsm");
|
||||||
|
var appversion = parseInt(Services.appinfo.version);
|
||||||
|
|
||||||
|
var AddSeparator = {
|
||||||
|
init: function () {
|
||||||
|
if (
|
||||||
|
appversion >= 76 &&
|
||||||
|
location != "chrome://browser/content/browser.xhtml"
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
Components.utils.import("resource:///modules/CustomizableUI.jsm");
|
try {
|
||||||
var {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
|
document
|
||||||
var appversion = parseInt(Services.appinfo.version);
|
.getElementById("back-button")
|
||||||
|
.setAttribute("removable", "true");
|
||||||
|
document
|
||||||
|
.getElementById("forward-button")
|
||||||
|
.setAttribute("removable", "true");
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
var AddSeparator = {
|
/* blank tab workaround */
|
||||||
init: function() {
|
try {
|
||||||
|
if (gBrowser.selectedBrowser.getAttribute("blank"))
|
||||||
|
gBrowser.selectedBrowser.removeAttribute("blank");
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
if (appversion >= 76 && location != 'chrome://browser/content/browser.xhtml')
|
var tb_config_label = "Configuration Toolbar";
|
||||||
return;
|
var tb_spacer_label = "Space";
|
||||||
|
var tb_sep_label = "Separator";
|
||||||
|
var tb_spring_label = "Flexible Space";
|
||||||
|
|
||||||
/* blank tab workaround */
|
try {
|
||||||
try {
|
if (document.getElementById("configuration_toolbar") == null) {
|
||||||
if(gBrowser.selectedBrowser.getAttribute('blank')) gBrowser.selectedBrowser.removeAttribute('blank');
|
if (appversion <= 62)
|
||||||
} catch(e) {}
|
var tb_config = document.createElement("toolbar");
|
||||||
|
else var tb_config = document.createXULElement("toolbar");
|
||||||
var tb_config_label = "Configuration Toolbar";
|
tb_config.setAttribute("id", "configuration_toolbar");
|
||||||
var tb_spacer_label = "Space";
|
tb_config.setAttribute("customizable", "true");
|
||||||
var tb_sep_label = "Separator";
|
tb_config.setAttribute(
|
||||||
var tb_spring_label = "Flexible Space";
|
"class",
|
||||||
|
"toolbar-primary chromeclass-toolbar browser-toolbar customization-target"
|
||||||
try {
|
);
|
||||||
if(document.getElementById('configuration_toolbar') == null) {
|
tb_config.setAttribute("mode", "icons");
|
||||||
|
tb_config.setAttribute("iconsize", "small");
|
||||||
if(appversion <= 62) var tb_config = document.createElement("toolbar");
|
tb_config.setAttribute("toolboxid", "navigator-toolbox");
|
||||||
else var tb_config = document.createXULElement("toolbar");
|
tb_config.setAttribute("lockiconsize", "true");
|
||||||
tb_config.setAttribute("id","configuration_toolbar");
|
tb_config.setAttribute("ordinal", "1005");
|
||||||
tb_config.setAttribute("customizable","true");
|
tb_config.setAttribute(
|
||||||
tb_config.setAttribute("class","toolbar-primary chromeclass-toolbar browser-toolbar customization-target");
|
"defaultset",
|
||||||
tb_config.setAttribute("mode","icons");
|
"toolbarspacer,toolbarseparator"
|
||||||
tb_config.setAttribute("iconsize","small");
|
);
|
||||||
tb_config.setAttribute("toolboxid","navigator-toolbox");
|
|
||||||
tb_config.setAttribute("lockiconsize","true");
|
|
||||||
tb_config.setAttribute("ordinal","1005");
|
|
||||||
tb_config.setAttribute("defaultset","toolbarspacer,toolbarseparator");
|
|
||||||
|
|
||||||
document.querySelector('#navigator-toolbox').appendChild(tb_config);
|
|
||||||
|
|
||||||
CustomizableUI.registerArea("configuration_toolbar", {legacy: true});
|
|
||||||
if(appversion >= 65) CustomizableUI.registerToolbarNode(tb_config);
|
|
||||||
|
|
||||||
if(appversion <= 62) var tb_label = document.createElement("label");
|
|
||||||
else var tb_label = document.createXULElement("label");
|
|
||||||
tb_label.setAttribute("label", tb_config_label+": ");
|
|
||||||
tb_label.setAttribute("value", tb_config_label+": ");
|
|
||||||
tb_label.setAttribute("id","tb_config_tb_label");
|
|
||||||
tb_label.setAttribute("removable","false");
|
|
||||||
|
|
||||||
tb_config.appendChild(tb_label);
|
|
||||||
|
|
||||||
|
|
||||||
if(appversion <= 62) var tb_spacer = document.createElement("toolbarspacer");
|
|
||||||
else var tb_spacer = document.createXULElement("toolbarspacer");
|
|
||||||
tb_spacer.setAttribute("id","spacer");
|
|
||||||
tb_spacer.setAttribute("class","chromeclass-toolbar-additional");
|
|
||||||
tb_spacer.setAttribute("customizableui-areatype","toolbar");
|
|
||||||
tb_spacer.setAttribute("removable","false");
|
|
||||||
tb_spacer.setAttribute("label", tb_spacer_label);
|
|
||||||
|
|
||||||
tb_config.appendChild(tb_spacer);
|
|
||||||
|
|
||||||
|
|
||||||
if(appversion <= 62) var tb_sep = document.createElement("toolbarseparator");
|
|
||||||
else var tb_sep = document.createXULElement("toolbarseparator");
|
|
||||||
tb_sep.setAttribute("id","separator");
|
|
||||||
tb_sep.setAttribute("class","chromeclass-toolbar-additional");
|
|
||||||
tb_sep.setAttribute("customizableui-areatype","toolbar");
|
|
||||||
tb_sep.setAttribute("removable","false");
|
|
||||||
tb_sep.setAttribute("label", tb_sep_label);
|
|
||||||
|
|
||||||
tb_config.appendChild(tb_sep);
|
|
||||||
|
|
||||||
|
|
||||||
if(appversion <= 62) var tb_spring = document.createElement("toolbarspring");
|
|
||||||
else var tb_spring = document.createXULElement("toolbarspring");
|
|
||||||
tb_spring.setAttribute("id","spring");
|
|
||||||
tb_spring.setAttribute("class","chromeclass-toolbar-additional");
|
|
||||||
tb_spring.setAttribute("customizableui-areatype","toolbar");
|
|
||||||
tb_spring.setAttribute("removable","false");
|
|
||||||
tb_spring.setAttribute("flex","1");
|
|
||||||
tb_spring.setAttribute("label", tb_spring_label);
|
|
||||||
|
|
||||||
tb_config.appendChild(tb_spring);
|
|
||||||
|
|
||||||
// CSS
|
|
||||||
var sss = Components.classes["@mozilla.org/content/style-sheet-service;1"].getService(Components.interfaces.nsIStyleSheetService);
|
|
||||||
|
|
||||||
var uri = Services.io.newURI("data:text/css;charset=utf-8," + encodeURIComponent('\
|
document.querySelector("#navigator-toolbox").appendChild(tb_config);
|
||||||
|
|
||||||
|
CustomizableUI.registerArea("configuration_toolbar", {
|
||||||
|
legacy: true,
|
||||||
|
});
|
||||||
|
if (appversion >= 65) CustomizableUI.registerToolbarNode(tb_config);
|
||||||
|
|
||||||
|
if (appversion <= 62) var tb_label = document.createElement("label");
|
||||||
|
else var tb_label = document.createXULElement("label");
|
||||||
|
tb_label.setAttribute("label", tb_config_label + ": ");
|
||||||
|
tb_label.setAttribute("value", tb_config_label + ": ");
|
||||||
|
tb_label.setAttribute("id", "tb_config_tb_label");
|
||||||
|
tb_label.setAttribute("removable", "false");
|
||||||
|
|
||||||
|
tb_config.appendChild(tb_label);
|
||||||
|
|
||||||
|
if (appversion <= 62)
|
||||||
|
var tb_spacer = document.createElement("toolbarspacer");
|
||||||
|
else var tb_spacer = document.createXULElement("toolbarspacer");
|
||||||
|
tb_spacer.setAttribute("id", "spacer");
|
||||||
|
tb_spacer.setAttribute("class", "chromeclass-toolbar-additional");
|
||||||
|
tb_spacer.setAttribute("customizableui-areatype", "toolbar");
|
||||||
|
tb_spacer.setAttribute("removable", "false");
|
||||||
|
tb_spacer.setAttribute("label", tb_spacer_label);
|
||||||
|
|
||||||
|
tb_config.appendChild(tb_spacer);
|
||||||
|
|
||||||
|
if (appversion <= 62)
|
||||||
|
var tb_sep = document.createElement("toolbarseparator");
|
||||||
|
else var tb_sep = document.createXULElement("toolbarseparator");
|
||||||
|
tb_sep.setAttribute("id", "separator");
|
||||||
|
tb_sep.setAttribute("class", "chromeclass-toolbar-additional");
|
||||||
|
tb_sep.setAttribute("customizableui-areatype", "toolbar");
|
||||||
|
tb_sep.setAttribute("removable", "false");
|
||||||
|
tb_sep.setAttribute("label", tb_sep_label);
|
||||||
|
|
||||||
|
tb_config.appendChild(tb_sep);
|
||||||
|
|
||||||
|
if (appversion <= 62)
|
||||||
|
var tb_spring = document.createElement("toolbarspring");
|
||||||
|
else var tb_spring = document.createXULElement("toolbarspring");
|
||||||
|
tb_spring.setAttribute("id", "spring");
|
||||||
|
tb_spring.setAttribute("class", "chromeclass-toolbar-additional");
|
||||||
|
tb_spring.setAttribute("customizableui-areatype", "toolbar");
|
||||||
|
tb_spring.setAttribute("removable", "false");
|
||||||
|
tb_spring.setAttribute("flex", "1");
|
||||||
|
tb_spring.setAttribute("label", tb_spring_label);
|
||||||
|
|
||||||
|
tb_config.appendChild(tb_spring);
|
||||||
|
|
||||||
|
// CSS
|
||||||
|
var sss = Components.classes[
|
||||||
|
"@mozilla.org/content/style-sheet-service;1"
|
||||||
|
].getService(Components.interfaces.nsIStyleSheetService);
|
||||||
|
|
||||||
|
var uri = Services.io.newURI(
|
||||||
|
"data:text/css;charset=utf-8," +
|
||||||
|
encodeURIComponent(
|
||||||
|
'\
|
||||||
\
|
\
|
||||||
#configuration_toolbar { \
|
#configuration_toolbar { \
|
||||||
-moz-appearance: none !important; \
|
-moz-appearance: none !important; \
|
||||||
|
@ -161,21 +184,24 @@ var AddSeparator = {
|
||||||
max-width: 100% !important; \
|
max-width: 100% !important; \
|
||||||
}\
|
}\
|
||||||
\
|
\
|
||||||
'), null, null);
|
'
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
|
sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
|
||||||
}
|
}
|
||||||
} catch(e){}
|
} catch (e) {}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
}
|
/* initialization delay workaround */
|
||||||
|
document.addEventListener("DOMContentLoaded", AddSeparator.init(), false);
|
||||||
}
|
/* Use the below code instead of the one above this line, if issues occur */
|
||||||
|
/*
|
||||||
/* initialization delay workaround */
|
|
||||||
document.addEventListener("DOMContentLoaded", AddSeparator.init(), false);
|
|
||||||
/* Use the below code instead of the one above this line, if issues occur */
|
|
||||||
/*
|
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
AddSeparator.init();
|
AddSeparator.init();
|
||||||
},2000);
|
},2000);
|
||||||
*/
|
*/
|
||||||
|
})();
|
||||||
|
|
207
chrome/status-bar.uc.js
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
// ==UserScript==
|
||||||
|
// @name Status Bar
|
||||||
|
// @author xiaoxiaoflood
|
||||||
|
// @include main
|
||||||
|
// @startup UC.statusBar.exec(win);
|
||||||
|
// @shutdown UC.statusBar.destroy();
|
||||||
|
// @onlyonce
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
const { CustomizableUI, StatusPanel } = window;
|
||||||
|
|
||||||
|
UC.statusBar = {
|
||||||
|
PREF_ENABLED: "userChromeJS.statusbar.enabled",
|
||||||
|
PREF_STATUSTEXT: "userChromeJS.statusbar.appendStatusText",
|
||||||
|
|
||||||
|
get enabled() {
|
||||||
|
return xPref.get(this.PREF_ENABLED);
|
||||||
|
},
|
||||||
|
|
||||||
|
get textInBar() {
|
||||||
|
return this.enabled && xPref.get(this.PREF_STATUSTEXT);
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function () {
|
||||||
|
xPref.set(this.PREF_ENABLED, true, true);
|
||||||
|
xPref.set(this.PREF_STATUSTEXT, true, true);
|
||||||
|
this.enabledListener = xPref.addListener(this.PREF_ENABLED, (isEnabled) => {
|
||||||
|
CustomizableUI.getWidget("status-dummybar").instances.forEach(
|
||||||
|
(dummyBar) => {
|
||||||
|
dummyBar.node.setAttribute("collapsed", !isEnabled);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.textListener = xPref.addListener(this.PREF_STATUSTEXT, (isEnabled) => {
|
||||||
|
if (!UC.statusBar.enabled) return;
|
||||||
|
|
||||||
|
_uc.windows((doc, win) => {
|
||||||
|
let StatusPanel = win.StatusPanel;
|
||||||
|
if (isEnabled)
|
||||||
|
win.statusbar.textNode.appendChild(StatusPanel._labelElement);
|
||||||
|
else StatusPanel.panel.appendChild(StatusPanel._labelElement);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setStyle();
|
||||||
|
_uc.sss.loadAndRegisterSheet(this.STYLE.url, this.STYLE.type);
|
||||||
|
|
||||||
|
CustomizableUI.registerArea("status-bar", {});
|
||||||
|
|
||||||
|
Services.obs.addObserver(this, "browser-delayed-startup-finished");
|
||||||
|
},
|
||||||
|
|
||||||
|
exec: function (win) {
|
||||||
|
let document = win.document;
|
||||||
|
let StatusPanel = win.StatusPanel;
|
||||||
|
|
||||||
|
let dummystatusbar = _uc.createElement(document, "toolbar", {
|
||||||
|
id: "status-dummybar",
|
||||||
|
toolbarname: "Status Bar",
|
||||||
|
hidden: "true",
|
||||||
|
});
|
||||||
|
dummystatusbar.collapsed = !this.enabled;
|
||||||
|
dummystatusbar.setAttribute = function (att, value) {
|
||||||
|
let result = Element.prototype.setAttribute.apply(this, arguments);
|
||||||
|
|
||||||
|
if (att == "collapsed") {
|
||||||
|
let StatusPanel = win.StatusPanel;
|
||||||
|
if (value === true) {
|
||||||
|
xPref.set(UC.statusBar.PREF_ENABLED, false);
|
||||||
|
win.statusbar.node.setAttribute("collapsed", true);
|
||||||
|
StatusPanel.panel.appendChild(StatusPanel._labelElement);
|
||||||
|
win.statusbar.node.parentNode.collapsed = true;
|
||||||
|
} else {
|
||||||
|
xPref.set(UC.statusBar.PREF_ENABLED, true);
|
||||||
|
win.statusbar.node.setAttribute("collapsed", false);
|
||||||
|
if (UC.statusBar.textInBar)
|
||||||
|
win.statusbar.textNode.appendChild(StatusPanel._labelElement);
|
||||||
|
win.statusbar.node.parentNode.collapsed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
win.gNavToolbox.appendChild(dummystatusbar);
|
||||||
|
|
||||||
|
win.statusbar.node = _uc.createElement(document, "toolbar", {
|
||||||
|
id: "status-bar",
|
||||||
|
customizable: "true",
|
||||||
|
context: "toolbar-context-menu",
|
||||||
|
mode: "icons",
|
||||||
|
});
|
||||||
|
|
||||||
|
win.statusbar.textNode = _uc.createElement(document, "toolbaritem", {
|
||||||
|
id: "status-text",
|
||||||
|
flex: "1",
|
||||||
|
width: "100",
|
||||||
|
});
|
||||||
|
if (this.textInBar)
|
||||||
|
win.statusbar.textNode.appendChild(StatusPanel._labelElement);
|
||||||
|
win.statusbar.node.appendChild(win.statusbar.textNode);
|
||||||
|
|
||||||
|
win.eval(
|
||||||
|
'Object.defineProperty(StatusPanel, "_label", {' +
|
||||||
|
Object.getOwnPropertyDescriptor(StatusPanel, "_label")
|
||||||
|
.set.toString()
|
||||||
|
.replace(/^set _label/, "set")
|
||||||
|
.replace(
|
||||||
|
/((\s+)this\.panel\.setAttribute\("inactive", "true"\);)/,
|
||||||
|
"$2this._labelElement.value = val;$1"
|
||||||
|
) +
|
||||||
|
", enumerable: true, configurable: true});"
|
||||||
|
);
|
||||||
|
|
||||||
|
let bottomBox = document.createElement("vbox");
|
||||||
|
bottomBox.id = "browser-bottombox";
|
||||||
|
bottomBox.append(win.statusbar.node);
|
||||||
|
|
||||||
|
if (!this.enabled) bottomBox.collapsed = true;
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("fullscreen-and-pointerlock-wrapper")
|
||||||
|
.insertAdjacentElement("afterend", bottomBox);
|
||||||
|
|
||||||
|
win.addEventListener("fullscreen", this.fsEvent);
|
||||||
|
|
||||||
|
if (document.readyState === "complete") this.observe(win);
|
||||||
|
},
|
||||||
|
|
||||||
|
fsEvent: function (ev) {
|
||||||
|
const { StatusPanel, fullScreen, statusbar } = ev.target;
|
||||||
|
if (fullScreen) StatusPanel.panel.appendChild(StatusPanel._labelElement);
|
||||||
|
else statusbar.textNode.appendChild(StatusPanel._labelElement);
|
||||||
|
},
|
||||||
|
|
||||||
|
observe: function (win) {
|
||||||
|
CustomizableUI.registerToolbarNode(win.statusbar.node);
|
||||||
|
},
|
||||||
|
|
||||||
|
orig: Object.getOwnPropertyDescriptor(StatusPanel, "_label").set.toString(),
|
||||||
|
|
||||||
|
setStyle: function () {
|
||||||
|
this.STYLE = {
|
||||||
|
url: Services.io.newURI(
|
||||||
|
"data:text/css;charset=UTF-8," +
|
||||||
|
encodeURIComponent(`
|
||||||
|
@-moz-document url('${_uc.BROWSERCHROME}') {
|
||||||
|
#status-bar {
|
||||||
|
color: initial !important;
|
||||||
|
/*background-color: var(--toolbar-non-lwt-bgcolor);*/
|
||||||
|
}
|
||||||
|
#status-text > #statuspanel-label {
|
||||||
|
border-top: 0 !important;
|
||||||
|
/*background-color: unset !important;
|
||||||
|
color: #444;*/
|
||||||
|
}
|
||||||
|
#status-bar > #status-text {
|
||||||
|
display: flex !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
align-content: center !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
-moz-window-dragging: drag;
|
||||||
|
}
|
||||||
|
toolbarpaletteitem #status-text:before {
|
||||||
|
content: "Status text";
|
||||||
|
color: red;
|
||||||
|
border: 1px #aaa solid;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
/*#browser-bottombox:not([collapsed]) {
|
||||||
|
border-top: 1px solid #BCBEBF !important;
|
||||||
|
}*/
|
||||||
|
:root[inFullscreen]:not([macOSNativeFullscreen]) #browser-bottombox {
|
||||||
|
visibility: collapse !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
),
|
||||||
|
type: _uc.sss.USER_SHEET,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function () {
|
||||||
|
const { CustomizableUI } = Services.wm.getMostRecentBrowserWindow();
|
||||||
|
|
||||||
|
xPref.removeListener(this.enabledListener);
|
||||||
|
xPref.removeListener(this.textListener);
|
||||||
|
CustomizableUI.unregisterArea("status-bar");
|
||||||
|
_uc.sss.unregisterSheet(this.STYLE.url, this.STYLE.type);
|
||||||
|
_uc.windows((doc, win) => {
|
||||||
|
const { eval, statusbar, StatusPanel } = win;
|
||||||
|
eval(
|
||||||
|
'Object.defineProperty(StatusPanel, "_label", {' +
|
||||||
|
this.orig.replace(/^set _label/, "set") +
|
||||||
|
", enumerable: true, configurable: true});"
|
||||||
|
);
|
||||||
|
StatusPanel.panel.appendChild(StatusPanel._labelElement);
|
||||||
|
doc.getElementById("status-dummybar").remove();
|
||||||
|
statusbar.node.remove();
|
||||||
|
win.removeEventListener("fullscreen", this.fsEvent);
|
||||||
|
});
|
||||||
|
Services.obs.removeObserver(this, "browser-delayed-startup-finished");
|
||||||
|
delete UC.statusBar;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
UC.statusBar.init();
|
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 197 B |
Before Width: | Height: | Size: 281 B |
Before Width: | Height: | Size: 262 B |
Before Width: | Height: | Size: 180 B |
Before Width: | Height: | Size: 350 B |
Before Width: | Height: | Size: 317 B |
Before Width: | Height: | Size: 432 B |
Before Width: | Height: | Size: 198 B |
Before Width: | Height: | Size: 243 B |
Before Width: | Height: | Size: 417 B |
Before Width: | Height: | Size: 326 B |
Before Width: | Height: | Size: 187 B |
Before Width: | Height: | Size: 261 B |
Before Width: | Height: | Size: 267 B |
Before Width: | Height: | Size: 173 B |
Before Width: | Height: | Size: 316 B |
Before Width: | Height: | Size: 322 B |
Before Width: | Height: | Size: 426 B |
Before Width: | Height: | Size: 190 B |
Before Width: | Height: | Size: 235 B |
Before Width: | Height: | Size: 422 B |
Before Width: | Height: | Size: 298 B |
Before Width: | Height: | Size: 200 B |
Before Width: | Height: | Size: 329 B |
Before Width: | Height: | Size: 301 B |
Before Width: | Height: | Size: 288 B |
Before Width: | Height: | Size: 191 B |
Before Width: | Height: | Size: 264 B |
Before Width: | Height: | Size: 227 B |
Before Width: | Height: | Size: 321 B |
Before Width: | Height: | Size: 326 B |
Before Width: | Height: | Size: 340 B |
Before Width: | Height: | Size: 469 B |
Before Width: | Height: | Size: 509 B |
Before Width: | Height: | Size: 524 B |
Before Width: | Height: | Size: 221 B |
Before Width: | Height: | Size: 276 B |
Before Width: | Height: | Size: 496 B |
Before Width: | Height: | Size: 389 B |
Before Width: | Height: | Size: 218 B |
Before Width: | Height: | Size: 326 B |
Before Width: | Height: | Size: 337 B |
Before Width: | Height: | Size: 329 B |
Before Width: | Height: | Size: 420 B |
Before Width: | Height: | Size: 437 B |
Before Width: | Height: | Size: 527 B |
Before Width: | Height: | Size: 221 B |
Before Width: | Height: | Size: 269 B |
Before Width: | Height: | Size: 487 B |
Before Width: | Height: | Size: 370 B |
Before Width: | Height: | Size: 231 B |
Before Width: | Height: | Size: 378 B |
Before Width: | Height: | Size: 357 B |
Before Width: | Height: | Size: 327 B |
Before Width: | Height: | Size: 204 B |
Before Width: | Height: | Size: 337 B |
Before Width: | Height: | Size: 248 B |
Before Width: | Height: | Size: 399 B |
Before Width: | Height: | Size: 350 B |
Before Width: | Height: | Size: 349 B |
Before Width: | Height: | Size: 210 B |
Before Width: | Height: | Size: 340 B |
Before Width: | Height: | Size: 225 B |
Before Width: | Height: | Size: 334 B |
Before Width: | Height: | Size: 289 B |
Before Width: | Height: | Size: 297 B |
Before Width: | Height: | Size: 190 B |
Before Width: | Height: | Size: 285 B |
Before Width: | Height: | Size: 4.4 KiB |
477
chrome/utils/BootstrapLoader.jsm
Normal file
|
@ -0,0 +1,477 @@
|
||||||
|
/* 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";
|
||||||
|
|
||||||
|
let EXPORTED_SYMBOLS = [];
|
||||||
|
|
||||||
|
const { XPCOMUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/XPCOMUtils.jsm"
|
||||||
|
);
|
||||||
|
const Services =
|
||||||
|
globalThis.Services ||
|
||||||
|
ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
|
Blocklist: "resource://gre/modules/Blocklist.jsm",
|
||||||
|
ConsoleAPI: "resource://gre/modules/Console.jsm",
|
||||||
|
InstallRDF: "chrome://userchromejs/content/RDFManifestConverter.jsm",
|
||||||
|
});
|
||||||
|
|
||||||
|
Services.obs.addObserver((doc) => {
|
||||||
|
if (
|
||||||
|
doc.location.protocol + doc.location.pathname === "about:addons" ||
|
||||||
|
doc.location.protocol + doc.location.pathname ===
|
||||||
|
"chrome:/content/extensions/aboutaddons.html"
|
||||||
|
) {
|
||||||
|
const win = doc.defaultView;
|
||||||
|
let handleEvent_orig =
|
||||||
|
win.customElements.get("addon-card").prototype.handleEvent;
|
||||||
|
win.customElements.get("addon-card").prototype.handleEvent = function (e) {
|
||||||
|
if (
|
||||||
|
e.type === "click" &&
|
||||||
|
e.target.getAttribute("action") === "preferences" &&
|
||||||
|
this.addon.optionsType == 1 /*AddonManager.OPTIONS_TYPE_DIALOG*/
|
||||||
|
) {
|
||||||
|
var windows = Services.wm.getEnumerator(null);
|
||||||
|
while (windows.hasMoreElements()) {
|
||||||
|
var win2 = windows.getNext();
|
||||||
|
if (win2.closed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (win2.document.documentURI == this.addon.optionsURL) {
|
||||||
|
win2.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var features = "chrome,titlebar,toolbar,centerscreen";
|
||||||
|
win.docShell.rootTreeItem.domWindow.openDialog(
|
||||||
|
this.addon.optionsURL,
|
||||||
|
this.addon.id,
|
||||||
|
features
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
handleEvent_orig.apply(this, arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let update_orig = win.customElements.get("addon-options").prototype.update;
|
||||||
|
win.customElements.get("addon-options").prototype.update = function (
|
||||||
|
card,
|
||||||
|
addon
|
||||||
|
) {
|
||||||
|
update_orig.apply(this, arguments);
|
||||||
|
if (addon.optionsType == 1 /*AddonManager.OPTIONS_TYPE_DIALOG*/)
|
||||||
|
this.querySelector(
|
||||||
|
'panel-item[data-l10n-id="preferences-addon-button"]'
|
||||||
|
).hidden = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, "chrome-document-loaded");
|
||||||
|
|
||||||
|
const { AddonManager } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/AddonManager.jsm"
|
||||||
|
);
|
||||||
|
const { XPIDatabase, AddonInternal } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/addons/XPIDatabase.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
const { defineAddonWrapperProperty } = Cu.import(
|
||||||
|
"resource://gre/modules/addons/XPIDatabase.jsm"
|
||||||
|
);
|
||||||
|
defineAddonWrapperProperty("optionsType", function optionsType() {
|
||||||
|
if (!this.isActive) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let addon = this.__AddonInternal__;
|
||||||
|
let hasOptionsURL = !!this.optionsURL;
|
||||||
|
|
||||||
|
if (addon.optionsType) {
|
||||||
|
switch (parseInt(addon.optionsType, 10)) {
|
||||||
|
case 1 /*AddonManager.OPTIONS_TYPE_DIALOG*/:
|
||||||
|
case AddonManager.OPTIONS_TYPE_TAB:
|
||||||
|
case AddonManager.OPTIONS_TYPE_INLINE_BROWSER:
|
||||||
|
return hasOptionsURL ? addon.optionsType : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
XPIDatabase.isDisabledLegacy = () => false;
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "BOOTSTRAP_REASONS", () => {
|
||||||
|
const { XPIProvider } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/addons/XPIProvider.jsm"
|
||||||
|
);
|
||||||
|
return XPIProvider.BOOTSTRAP_REASONS;
|
||||||
|
});
|
||||||
|
|
||||||
|
const { Log } = ChromeUtils.import("resource://gre/modules/Log.jsm");
|
||||||
|
var logger = Log.repository.getLogger("addons.bootstrap");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid IDs fit this pattern.
|
||||||
|
*/
|
||||||
|
var gIDTest =
|
||||||
|
/^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
|
||||||
|
|
||||||
|
// Properties that exist in the install manifest
|
||||||
|
const PROP_METADATA = [
|
||||||
|
"id",
|
||||||
|
"version",
|
||||||
|
"type",
|
||||||
|
"internalName",
|
||||||
|
"updateURL",
|
||||||
|
"optionsURL",
|
||||||
|
"optionsType",
|
||||||
|
"aboutURL",
|
||||||
|
"iconURL",
|
||||||
|
];
|
||||||
|
const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
|
||||||
|
const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"];
|
||||||
|
|
||||||
|
// Map new string type identifiers to old style nsIUpdateItem types.
|
||||||
|
// Retired values:
|
||||||
|
// 32 = multipackage xpi file
|
||||||
|
// 8 = locale
|
||||||
|
// 256 = apiextension
|
||||||
|
// 128 = experiment
|
||||||
|
// theme = 4
|
||||||
|
const TYPES = {
|
||||||
|
extension: 2,
|
||||||
|
dictionary: 64,
|
||||||
|
};
|
||||||
|
|
||||||
|
const COMPATIBLE_BY_DEFAULT_TYPES = {
|
||||||
|
extension: true,
|
||||||
|
dictionary: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
|
||||||
|
|
||||||
|
function isXPI(filename) {
|
||||||
|
let ext = filename.slice(-4).toLowerCase();
|
||||||
|
return ext === ".xpi" || ext === ".zip";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an nsIURI for a file within another file, either a directory or an XPI
|
||||||
|
* file. If aFile is a directory then this will return a file: URI, if it is an
|
||||||
|
* XPI file then it will return a jar: URI.
|
||||||
|
*
|
||||||
|
* @param {nsIFile} aFile
|
||||||
|
* The file containing the resources, must be either a directory or an
|
||||||
|
* XPI file
|
||||||
|
* @param {string} aPath
|
||||||
|
* The path to find the resource at, '/' separated. If aPath is empty
|
||||||
|
* then the uri to the root of the contained files will be returned
|
||||||
|
* @returns {nsIURI}
|
||||||
|
* An nsIURI pointing at the resource
|
||||||
|
*/
|
||||||
|
function getURIForResourceInFile(aFile, aPath) {
|
||||||
|
if (!isXPI(aFile.leafName)) {
|
||||||
|
let resource = aFile.clone();
|
||||||
|
if (aPath) aPath.split("/").forEach((part) => resource.append(part));
|
||||||
|
|
||||||
|
return Services.io.newFileURI(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildJarURI(aFile, aPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a jar: URI for a file inside a ZIP file.
|
||||||
|
*
|
||||||
|
* @param {nsIFile} aJarfile
|
||||||
|
* The ZIP file as an nsIFile
|
||||||
|
* @param {string} aPath
|
||||||
|
* The path inside the ZIP file
|
||||||
|
* @returns {nsIURI}
|
||||||
|
* An nsIURI for the file
|
||||||
|
*/
|
||||||
|
function buildJarURI(aJarfile, aPath) {
|
||||||
|
let uri = Services.io.newFileURI(aJarfile);
|
||||||
|
uri = "jar:" + uri.spec + "!/" + aPath;
|
||||||
|
return Services.io.newURI(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
var BootstrapLoader = {
|
||||||
|
name: "bootstrap",
|
||||||
|
manifestFile: "install.rdf",
|
||||||
|
async loadManifest(pkg) {
|
||||||
|
/**
|
||||||
|
* Reads locale properties from either the main install manifest root or
|
||||||
|
* an em:localized section in the install manifest.
|
||||||
|
*
|
||||||
|
* @param {Object} aSource
|
||||||
|
* The resource to read the properties from.
|
||||||
|
* @param {boolean} isDefault
|
||||||
|
* True if the locale is to be read from the main install manifest
|
||||||
|
* root
|
||||||
|
* @param {string[]} aSeenLocales
|
||||||
|
* An array of locale names already seen for this install manifest.
|
||||||
|
* Any locale names seen as a part of this function will be added to
|
||||||
|
* this array
|
||||||
|
* @returns {Object}
|
||||||
|
* an object containing the locale properties
|
||||||
|
*/
|
||||||
|
function readLocale(aSource, isDefault, aSeenLocales) {
|
||||||
|
let locale = {};
|
||||||
|
if (!isDefault) {
|
||||||
|
locale.locales = [];
|
||||||
|
for (let localeName of aSource.locales || []) {
|
||||||
|
if (!localeName) {
|
||||||
|
logger.warn("Ignoring empty locale in localized properties");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (aSeenLocales.includes(localeName)) {
|
||||||
|
logger.warn("Ignoring duplicate locale in localized properties");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
aSeenLocales.push(localeName);
|
||||||
|
locale.locales.push(localeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locale.locales.length == 0) {
|
||||||
|
logger.warn("Ignoring localized properties with no listed locales");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let prop of [...PROP_LOCALE_SINGLE, ...PROP_LOCALE_MULTI]) {
|
||||||
|
if (hasOwnProperty(aSource, prop)) {
|
||||||
|
locale[prop] = aSource[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
let manifestData = await pkg.readString("install.rdf");
|
||||||
|
let manifest = InstallRDF.loadFromString(manifestData).decode();
|
||||||
|
|
||||||
|
let addon = new AddonInternal();
|
||||||
|
for (let prop of PROP_METADATA) {
|
||||||
|
if (hasOwnProperty(manifest, prop)) {
|
||||||
|
addon[prop] = manifest[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!addon.type) {
|
||||||
|
addon.type = "extension";
|
||||||
|
} else {
|
||||||
|
let type = addon.type;
|
||||||
|
addon.type = null;
|
||||||
|
for (let name in TYPES) {
|
||||||
|
if (TYPES[name] == type) {
|
||||||
|
addon.type = name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(addon.type in TYPES))
|
||||||
|
throw new Error("Install manifest specifies unknown type: " + addon.type);
|
||||||
|
|
||||||
|
if (!addon.id) throw new Error("No ID in install manifest");
|
||||||
|
if (!gIDTest.test(addon.id))
|
||||||
|
throw new Error("Illegal add-on ID " + addon.id);
|
||||||
|
if (!addon.version) throw new Error("No version in install manifest");
|
||||||
|
|
||||||
|
addon.strictCompatibility =
|
||||||
|
!(addon.type in COMPATIBLE_BY_DEFAULT_TYPES) ||
|
||||||
|
manifest.strictCompatibility == "true";
|
||||||
|
|
||||||
|
// Only read these properties for extensions.
|
||||||
|
if (addon.type == "extension") {
|
||||||
|
if (manifest.bootstrap != "true") {
|
||||||
|
throw new Error("Non-restartless extensions no longer supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
addon.optionsType &&
|
||||||
|
addon.optionsType != 1 /*AddonManager.OPTIONS_TYPE_DIALOG*/ &&
|
||||||
|
addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE_BROWSER &&
|
||||||
|
addon.optionsType != AddonManager.OPTIONS_TYPE_TAB
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"Install manifest specifies unknown optionsType: " + addon.optionsType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addon.optionsType) addon.optionsType = parseInt(addon.optionsType);
|
||||||
|
}
|
||||||
|
|
||||||
|
addon.defaultLocale = readLocale(manifest, true);
|
||||||
|
|
||||||
|
let seenLocales = [];
|
||||||
|
addon.locales = [];
|
||||||
|
for (let localeData of manifest.localized || []) {
|
||||||
|
let locale = readLocale(localeData, false, seenLocales);
|
||||||
|
if (locale) addon.locales.push(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dependencies = new Set(manifest.dependencies);
|
||||||
|
addon.dependencies = Object.freeze(Array.from(dependencies));
|
||||||
|
|
||||||
|
let seenApplications = [];
|
||||||
|
addon.targetApplications = [];
|
||||||
|
for (let targetApp of manifest.targetApplications || []) {
|
||||||
|
if (!targetApp.id || !targetApp.minVersion || !targetApp.maxVersion) {
|
||||||
|
logger.warn(
|
||||||
|
"Ignoring invalid targetApplication entry in install manifest"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (seenApplications.includes(targetApp.id)) {
|
||||||
|
logger.warn(
|
||||||
|
"Ignoring duplicate targetApplication entry for " +
|
||||||
|
targetApp.id +
|
||||||
|
" in install manifest"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seenApplications.push(targetApp.id);
|
||||||
|
addon.targetApplications.push(targetApp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that we don't need to check for duplicate targetPlatform entries since
|
||||||
|
// the RDF service coalesces them for us.
|
||||||
|
addon.targetPlatforms = [];
|
||||||
|
for (let targetPlatform of manifest.targetPlatforms || []) {
|
||||||
|
let platform = {
|
||||||
|
os: null,
|
||||||
|
abi: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
let pos = targetPlatform.indexOf("_");
|
||||||
|
if (pos != -1) {
|
||||||
|
platform.os = targetPlatform.substring(0, pos);
|
||||||
|
platform.abi = targetPlatform.substring(pos + 1);
|
||||||
|
} else {
|
||||||
|
platform.os = targetPlatform;
|
||||||
|
}
|
||||||
|
|
||||||
|
addon.targetPlatforms.push(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
addon.userDisabled = false;
|
||||||
|
addon.softDisabled = addon.blocklistState == Blocklist.STATE_SOFTBLOCKED;
|
||||||
|
addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
|
||||||
|
|
||||||
|
addon.userPermissions = null;
|
||||||
|
|
||||||
|
addon.icons = {};
|
||||||
|
if (await pkg.hasResource("icon.png")) {
|
||||||
|
addon.icons[32] = "icon.png";
|
||||||
|
addon.icons[48] = "icon.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await pkg.hasResource("icon64.png")) {
|
||||||
|
addon.icons[64] = "icon64.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
return addon;
|
||||||
|
},
|
||||||
|
|
||||||
|
loadScope(addon) {
|
||||||
|
let file = addon.file || addon._sourceBundle;
|
||||||
|
let uri = getURIForResourceInFile(file, "bootstrap.js").spec;
|
||||||
|
let principal = Services.scriptSecurityManager.getSystemPrincipal();
|
||||||
|
|
||||||
|
let sandbox = new Cu.Sandbox(principal, {
|
||||||
|
sandboxName: uri,
|
||||||
|
addonId: addon.id,
|
||||||
|
wantGlobalProperties: ["ChromeUtils"],
|
||||||
|
metadata: { addonID: addon.id, URI: uri },
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object.assign(sandbox, BOOTSTRAP_REASONS);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(
|
||||||
|
sandbox,
|
||||||
|
"console",
|
||||||
|
() => new ConsoleAPI({ consoleID: `addon/${addon.id}` })
|
||||||
|
);
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(uri, sandbox);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`Error loading bootstrap.js for ${addon.id}`, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findMethod(name) {
|
||||||
|
if (sandbox[name]) {
|
||||||
|
return sandbox[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let method = Cu.evalInSandbox(name, sandbox);
|
||||||
|
return method;
|
||||||
|
} catch (err) {}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
logger.warn(`Add-on ${addon.id} is missing bootstrap method ${name}`);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let install = findMethod("install");
|
||||||
|
let uninstall = findMethod("uninstall");
|
||||||
|
let startup = findMethod("startup");
|
||||||
|
let shutdown = findMethod("shutdown");
|
||||||
|
|
||||||
|
return {
|
||||||
|
install(...args) {
|
||||||
|
install(...args);
|
||||||
|
// Forget any cached files we might've had from this extension.
|
||||||
|
Services.obs.notifyObservers(null, "startupcache-invalidate");
|
||||||
|
},
|
||||||
|
|
||||||
|
uninstall(...args) {
|
||||||
|
uninstall(...args);
|
||||||
|
// Forget any cached files we might've had from this extension.
|
||||||
|
Services.obs.notifyObservers(null, "startupcache-invalidate");
|
||||||
|
},
|
||||||
|
|
||||||
|
startup(...args) {
|
||||||
|
if (addon.type == "extension") {
|
||||||
|
logger.debug(`Registering manifest for ${file.path}\n`);
|
||||||
|
Components.manager.addBootstrappedManifestLocation(file);
|
||||||
|
}
|
||||||
|
return startup(...args);
|
||||||
|
},
|
||||||
|
|
||||||
|
shutdown(data, reason) {
|
||||||
|
try {
|
||||||
|
return shutdown(data, reason);
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
if (reason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
|
||||||
|
logger.debug(`Removing manifest for ${file.path}\n`);
|
||||||
|
Components.manager.removeBootstrappedManifestLocation(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
AddonManager.addExternalExtensionLoader(BootstrapLoader);
|
||||||
|
|
||||||
|
if (AddonManager.isReady) {
|
||||||
|
AddonManager.getAllAddons().then((addons) => {
|
||||||
|
addons.forEach((addon) => {
|
||||||
|
if (
|
||||||
|
addon.type == "extension" &&
|
||||||
|
!addon.isWebExtension &&
|
||||||
|
!addon.userDisabled
|
||||||
|
) {
|
||||||
|
addon.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
441
chrome/utils/RDFDataSource.jsm
Normal file
|
@ -0,0 +1,441 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module creates a new API for accessing and modifying RDF graphs. The
|
||||||
|
* goal is to be able to serialise the graph in a human readable form. Also
|
||||||
|
* if the graph was originally loaded from an RDF/XML the serialisation should
|
||||||
|
* closely match the original with any new data closely following the existing
|
||||||
|
* layout. The output should always be compatible with Mozilla's RDF parser.
|
||||||
|
*
|
||||||
|
* This is all achieved by using a DOM Document to hold the current state of the
|
||||||
|
* graph in XML form. This can be initially loaded and parsed from disk or
|
||||||
|
* a blank document used for an empty graph. As assertions are added to the
|
||||||
|
* graph, appropriate DOM nodes are added to the document to represent them
|
||||||
|
* along with any necessary whitespace to properly layout the XML.
|
||||||
|
*
|
||||||
|
* In general the order of adding assertions to the graph will impact the form
|
||||||
|
* the serialisation takes. If a resource is first added as the object of an
|
||||||
|
* assertion then it will eventually be serialised inside the assertion's
|
||||||
|
* property element. If a resource is first added as the subject of an assertion
|
||||||
|
* then it will be serialised at the top level of the XML.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const NS_XML = "http://www.w3.org/XML/1998/namespace";
|
||||||
|
const NS_XMLNS = "http://www.w3.org/2000/xmlns/";
|
||||||
|
const NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
|
||||||
|
const NS_NC = "http://home.netscape.com/NC-rdf#";
|
||||||
|
|
||||||
|
/* eslint prefer-template: 1 */
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["RDFLiteral", "RDFBlankNode", "RDFResource", "RDFDataSource"];
|
||||||
|
|
||||||
|
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
const Services = globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGlobalGetters(this, ["DOMParser", "Element", "fetch"]);
|
||||||
|
|
||||||
|
function isElement(obj) {
|
||||||
|
return Element.isInstance(obj);
|
||||||
|
}
|
||||||
|
function isText(obj) {
|
||||||
|
return obj && typeof obj == "object" && ChromeUtils.getClassName(obj) == "Text";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns either an rdf namespaced attribute or an un-namespaced attribute
|
||||||
|
* value. Returns null if neither exists,
|
||||||
|
*/
|
||||||
|
function getRDFAttribute(element, name) {
|
||||||
|
if (element.hasAttributeNS(NS_RDF, name))
|
||||||
|
return element.getAttributeNS(NS_RDF, name);
|
||||||
|
if (element.hasAttribute(name))
|
||||||
|
return element.getAttribute(name);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an assertion in the datasource
|
||||||
|
*/
|
||||||
|
class RDFAssertion {
|
||||||
|
constructor(subject, predicate, object) {
|
||||||
|
// The subject on this assertion, an RDFSubject
|
||||||
|
this._subject = subject;
|
||||||
|
// The predicate, a string
|
||||||
|
this._predicate = predicate;
|
||||||
|
// The object, an RDFNode
|
||||||
|
this._object = object;
|
||||||
|
// The datasource this assertion exists in
|
||||||
|
this._ds = this._subject._ds;
|
||||||
|
// Marks that _DOMnode is the subject's element
|
||||||
|
this._isSubjectElement = false;
|
||||||
|
// The DOM node that represents this assertion. Could be a property element,
|
||||||
|
// a property attribute or the subject's element for rdf:type
|
||||||
|
this._DOMNode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPredicate() {
|
||||||
|
return this._predicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
getObject() {
|
||||||
|
return this._object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RDFNode {
|
||||||
|
equals(rdfnode) {
|
||||||
|
return (rdfnode.constructor === this.constructor &&
|
||||||
|
rdfnode._value == this._value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple literal value
|
||||||
|
*/
|
||||||
|
class RDFLiteral extends RDFNode {
|
||||||
|
constructor(value) {
|
||||||
|
super();
|
||||||
|
this._value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue() {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an RDF node that can be a subject so a resource or a blank node
|
||||||
|
*/
|
||||||
|
class RDFSubject extends RDFNode {
|
||||||
|
constructor(ds) {
|
||||||
|
super();
|
||||||
|
// A lookup of the assertions with this as the subject. Keyed on predicate
|
||||||
|
this._assertions = {};
|
||||||
|
// A lookup of the assertions with this as the object. Keyed on predicate
|
||||||
|
this._backwards = {};
|
||||||
|
// The datasource this subject belongs to
|
||||||
|
this._ds = ds;
|
||||||
|
// The DOM elements in the document that represent this subject. Array of Element
|
||||||
|
this._elements = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the given Element from the DOM document
|
||||||
|
*/
|
||||||
|
/* eslint-disable complexity */
|
||||||
|
_parseElement(element) {
|
||||||
|
this._elements.push(element);
|
||||||
|
|
||||||
|
// There might be an inferred rdf:type assertion in the element name
|
||||||
|
if (element.namespaceURI != NS_RDF ||
|
||||||
|
element.localName != "Description") {
|
||||||
|
var assertion = new RDFAssertion(this, RDF_R("type"),
|
||||||
|
this._ds.getResource(element.namespaceURI + element.localName));
|
||||||
|
assertion._DOMnode = element;
|
||||||
|
assertion._isSubjectElement = true;
|
||||||
|
this._addAssertion(assertion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certain attributes can be literal properties
|
||||||
|
for (let attr of element.attributes) {
|
||||||
|
if (attr.namespaceURI == NS_XML || attr.namespaceURI == NS_XMLNS ||
|
||||||
|
attr.nodeName == "xmlns")
|
||||||
|
continue;
|
||||||
|
if ((attr.namespaceURI == NS_RDF || !attr.namespaceURI) &&
|
||||||
|
(["nodeID", "about", "resource", "ID", "parseType"].includes(attr.localName)))
|
||||||
|
continue;
|
||||||
|
var object = null;
|
||||||
|
if (attr.namespaceURI == NS_RDF) {
|
||||||
|
if (attr.localName == "type")
|
||||||
|
object = this._ds.getResource(attr.nodeValue);
|
||||||
|
}
|
||||||
|
if (!object)
|
||||||
|
object = new RDFLiteral(attr.nodeValue);
|
||||||
|
assertion = new RDFAssertion(this, attr.namespaceURI + attr.localName, object);
|
||||||
|
assertion._DOMnode = attr;
|
||||||
|
this._addAssertion(assertion);
|
||||||
|
}
|
||||||
|
|
||||||
|
var child = element.firstChild;
|
||||||
|
element.listCounter = 1;
|
||||||
|
while (child) {
|
||||||
|
if (isElement(child)) {
|
||||||
|
object = null;
|
||||||
|
var predicate = child.namespaceURI + child.localName;
|
||||||
|
if (child.namespaceURI == NS_RDF) {
|
||||||
|
if (child.localName == "li") {
|
||||||
|
predicate = RDF_R(`_${element.listCounter}`);
|
||||||
|
element.listCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for and bail out on unknown attributes on the property element
|
||||||
|
for (let attr of child.attributes) {
|
||||||
|
// Ignore XML namespaced attributes
|
||||||
|
if (attr.namespaceURI == NS_XML)
|
||||||
|
continue;
|
||||||
|
// These are reserved by XML for future use
|
||||||
|
if (attr.localName.substring(0, 3).toLowerCase() == "xml")
|
||||||
|
continue;
|
||||||
|
// We can handle these RDF attributes
|
||||||
|
if ((!attr.namespaceURI || attr.namespaceURI == NS_RDF) &&
|
||||||
|
["resource", "nodeID"].includes(attr.localName))
|
||||||
|
continue;
|
||||||
|
// This is a special attribute we handle for compatibility with Mozilla RDF
|
||||||
|
if (attr.namespaceURI == NS_NC &&
|
||||||
|
attr.localName == "parseType")
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parseType = child.getAttributeNS(NS_NC, "parseType");
|
||||||
|
|
||||||
|
var resource = getRDFAttribute(child, "resource");
|
||||||
|
var nodeID = getRDFAttribute(child, "nodeID");
|
||||||
|
|
||||||
|
if (resource !== undefined) {
|
||||||
|
var base = Services.io.newURI(element.baseURI);
|
||||||
|
object = this._ds.getResource(base.resolve(resource));
|
||||||
|
} else if (nodeID !== undefined) {
|
||||||
|
object = this._ds.getBlankNode(nodeID);
|
||||||
|
} else {
|
||||||
|
var hasText = false;
|
||||||
|
var childElement = null;
|
||||||
|
var subchild = child.firstChild;
|
||||||
|
while (subchild) {
|
||||||
|
if (isText(subchild) && /\S/.test(subchild.nodeValue)) {
|
||||||
|
hasText = true;
|
||||||
|
} else if (isElement(subchild)) {
|
||||||
|
childElement = subchild;
|
||||||
|
}
|
||||||
|
subchild = subchild.nextSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (childElement) {
|
||||||
|
object = this._ds._getSubjectForElement(childElement);
|
||||||
|
object._parseElement(childElement);
|
||||||
|
} else
|
||||||
|
object = new RDFLiteral(child.textContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertion = new RDFAssertion(this, predicate, object);
|
||||||
|
this._addAssertion(assertion);
|
||||||
|
assertion._DOMnode = child;
|
||||||
|
}
|
||||||
|
child = child.nextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* eslint-enable complexity */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new assertion to the internal hashes. Should be called for every
|
||||||
|
* new assertion parsed or created programmatically.
|
||||||
|
*/
|
||||||
|
_addAssertion(assertion) {
|
||||||
|
var predicate = assertion.getPredicate();
|
||||||
|
if (predicate in this._assertions)
|
||||||
|
this._assertions[predicate].push(assertion);
|
||||||
|
else
|
||||||
|
this._assertions[predicate] = [ assertion ];
|
||||||
|
|
||||||
|
var object = assertion.getObject();
|
||||||
|
if (object instanceof RDFSubject) {
|
||||||
|
// Create reverse assertion
|
||||||
|
if (predicate in object._backwards)
|
||||||
|
object._backwards[predicate].push(assertion);
|
||||||
|
else
|
||||||
|
object._backwards[predicate] = [ assertion ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all objects in assertions with this subject and the given predicate.
|
||||||
|
*/
|
||||||
|
getObjects(predicate) {
|
||||||
|
if (predicate in this._assertions)
|
||||||
|
return Array.from(this._assertions[predicate],
|
||||||
|
i => i.getObject());
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the first property value for the given predicate.
|
||||||
|
*/
|
||||||
|
getProperty(predicate) {
|
||||||
|
if (predicate in this._assertions)
|
||||||
|
return this._assertions[predicate][0].getObject();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new RDFResource for the datasource. Private.
|
||||||
|
*/
|
||||||
|
class RDFResource extends RDFSubject {
|
||||||
|
constructor(ds, uri) {
|
||||||
|
super(ds);
|
||||||
|
// This is the uri that the resource represents.
|
||||||
|
this._uri = uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new blank node. Private.
|
||||||
|
*/
|
||||||
|
class RDFBlankNode extends RDFSubject {
|
||||||
|
constructor(ds, nodeID) {
|
||||||
|
super(ds);
|
||||||
|
// The nodeID of this node. May be null if there is no ID.
|
||||||
|
this._nodeID = nodeID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets attributes on the DOM element to mark it as representing this node
|
||||||
|
*/
|
||||||
|
_applyToElement(element) {
|
||||||
|
if (!this._nodeID)
|
||||||
|
return;
|
||||||
|
if (USE_RDFNS_ATTR) {
|
||||||
|
var prefix = this._ds._resolvePrefix(element, RDF_R("nodeID"));
|
||||||
|
element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._nodeID);
|
||||||
|
} else {
|
||||||
|
element.setAttribute("nodeID", this._nodeID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Element in the document for holding assertions about this
|
||||||
|
* subject. The URI controls what tagname to use.
|
||||||
|
*/
|
||||||
|
_createNewElement(uri) {
|
||||||
|
// If there are already nodes representing this in the document then we need
|
||||||
|
// a nodeID to match them
|
||||||
|
if (!this._nodeID && this._elements.length > 0) {
|
||||||
|
this._ds._createNodeID(this);
|
||||||
|
for (let element of this._elements)
|
||||||
|
this._applyToElement(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super._createNewElement.call(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a reference to this node to the given property Element.
|
||||||
|
*/
|
||||||
|
_addReferenceToElement(element) {
|
||||||
|
if (this._elements.length > 0 && !this._nodeID) {
|
||||||
|
// In document elsewhere already
|
||||||
|
// Create a node ID and update the other nodes referencing
|
||||||
|
this._ds._createNodeID(this);
|
||||||
|
for (let element of this._elements)
|
||||||
|
this._applyToElement(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._nodeID) {
|
||||||
|
if (USE_RDFNS_ATTR) {
|
||||||
|
let prefix = this._ds._resolvePrefix(element, RDF_R("nodeID"));
|
||||||
|
element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._nodeID);
|
||||||
|
} else {
|
||||||
|
element.setAttribute("nodeID", this._nodeID);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Add the empty blank node, this is generally right since further
|
||||||
|
// assertions will be added to fill this out
|
||||||
|
var newelement = this._ds._addElement(element, RDF_R("Description"));
|
||||||
|
newelement.listCounter = 1;
|
||||||
|
this._elements.push(newelement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes any reference to this node from the given property Element.
|
||||||
|
*/
|
||||||
|
_removeReferenceFromElement(element) {
|
||||||
|
if (element.hasAttributeNS(NS_RDF, "nodeID"))
|
||||||
|
element.removeAttributeNS(NS_RDF, "nodeID");
|
||||||
|
if (element.hasAttribute("nodeID"))
|
||||||
|
element.removeAttribute("nodeID");
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeID() {
|
||||||
|
return this._nodeID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new RDFDataSource from the given document. The document will be
|
||||||
|
* changed as assertions are added and removed to the RDF. Pass a null document
|
||||||
|
* to start with an empty graph.
|
||||||
|
*/
|
||||||
|
class RDFDataSource {
|
||||||
|
constructor(document) {
|
||||||
|
// All known resources, indexed on URI
|
||||||
|
this._resources = {};
|
||||||
|
// All blank nodes
|
||||||
|
this._allBlankNodes = [];
|
||||||
|
|
||||||
|
// The underlying DOM document for this datasource
|
||||||
|
this._document = document;
|
||||||
|
this._parseDocument();
|
||||||
|
}
|
||||||
|
|
||||||
|
static loadFromString(text) {
|
||||||
|
let parser = new DOMParser();
|
||||||
|
let document = parser.parseFromString(text, "application/xml");
|
||||||
|
|
||||||
|
return new this(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an rdf subject for the given DOM Element. If the subject has not
|
||||||
|
* been seen before a new one is created.
|
||||||
|
*/
|
||||||
|
_getSubjectForElement(element) {
|
||||||
|
var about = getRDFAttribute(element, "about");
|
||||||
|
|
||||||
|
if (about !== undefined) {
|
||||||
|
let base = Services.io.newURI(element.baseURI);
|
||||||
|
return this.getResource(base.resolve(about));
|
||||||
|
}
|
||||||
|
return this.getBlankNode(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the document for subjects at the top level.
|
||||||
|
*/
|
||||||
|
_parseDocument() {
|
||||||
|
var domnode = this._document.documentElement.firstChild;
|
||||||
|
while (domnode) {
|
||||||
|
if (isElement(domnode)) {
|
||||||
|
var subject = this._getSubjectForElement(domnode);
|
||||||
|
subject._parseElement(domnode);
|
||||||
|
}
|
||||||
|
domnode = domnode.nextSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a blank node. nodeID may be null and if so a new blank node is created.
|
||||||
|
* If a nodeID is given then the blank node with that ID is returned or created.
|
||||||
|
*/
|
||||||
|
getBlankNode(nodeID) {
|
||||||
|
var rdfnode = new RDFBlankNode(this, nodeID);
|
||||||
|
this._allBlankNodes.push(rdfnode);
|
||||||
|
return rdfnode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the resource for the URI. The resource is created if it has not been
|
||||||
|
* used already.
|
||||||
|
*/
|
||||||
|
getResource(uri) {
|
||||||
|
if (uri in this._resources)
|
||||||
|
return this._resources[uri];
|
||||||
|
|
||||||
|
var resource = new RDFResource(this, uri);
|
||||||
|
this._resources[uri] = resource;
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
}
|
102
chrome/utils/RDFManifestConverter.jsm
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
/* 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";
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["InstallRDF"];
|
||||||
|
|
||||||
|
ChromeUtils.defineModuleGetter(this, "RDFDataSource",
|
||||||
|
"chrome://userchromejs/content/RDFDataSource.jsm");
|
||||||
|
|
||||||
|
const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest";
|
||||||
|
|
||||||
|
function EM_R(aProperty) {
|
||||||
|
return `http://www.mozilla.org/2004/em-rdf#${aProperty}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue(literal) {
|
||||||
|
return literal && literal.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProperty(resource, property) {
|
||||||
|
return getValue(resource.getProperty(EM_R(property)));
|
||||||
|
}
|
||||||
|
|
||||||
|
class Manifest {
|
||||||
|
constructor(ds) {
|
||||||
|
this.ds = ds;
|
||||||
|
}
|
||||||
|
|
||||||
|
static loadFromString(text) {
|
||||||
|
return new this(RDFDataSource.loadFromString(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InstallRDF extends Manifest {
|
||||||
|
_readProps(source, obj, props) {
|
||||||
|
for (let prop of props) {
|
||||||
|
let val = getProperty(source, prop);
|
||||||
|
if (val != null) {
|
||||||
|
obj[prop] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_readArrayProp(source, obj, prop, target, decode = getValue) {
|
||||||
|
let result = Array.from(source.getObjects(EM_R(prop)),
|
||||||
|
target => decode(target));
|
||||||
|
if (result.length) {
|
||||||
|
obj[target] = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_readArrayProps(source, obj, props, decode = getValue) {
|
||||||
|
for (let [prop, target] of Object.entries(props)) {
|
||||||
|
this._readArrayProp(source, obj, prop, target, decode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_readLocaleStrings(source, obj) {
|
||||||
|
this._readProps(source, obj, ["name", "description", "creator", "homepageURL"]);
|
||||||
|
this._readArrayProps(source, obj, {
|
||||||
|
locale: "locales",
|
||||||
|
developer: "developers",
|
||||||
|
translator: "translators",
|
||||||
|
contributor: "contributors",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
decode() {
|
||||||
|
let root = this.ds.getResource(RDFURI_INSTALL_MANIFEST_ROOT);
|
||||||
|
let result = {};
|
||||||
|
|
||||||
|
let props = ["id", "version", "type", "updateURL", "optionsURL",
|
||||||
|
"optionsType", "aboutURL", "iconURL",
|
||||||
|
"bootstrap", "unpack", "strictCompatibility"];
|
||||||
|
this._readProps(root, result, props);
|
||||||
|
|
||||||
|
let decodeTargetApplication = source => {
|
||||||
|
let app = {};
|
||||||
|
this._readProps(source, app, ["id", "minVersion", "maxVersion"]);
|
||||||
|
return app;
|
||||||
|
};
|
||||||
|
|
||||||
|
let decodeLocale = source => {
|
||||||
|
let localized = {};
|
||||||
|
this._readLocaleStrings(source, localized);
|
||||||
|
return localized;
|
||||||
|
};
|
||||||
|
|
||||||
|
this._readLocaleStrings(root, result);
|
||||||
|
|
||||||
|
this._readArrayProps(root, result, {"targetPlatform": "targetPlatforms"});
|
||||||
|
this._readArrayProps(root, result, {"targetApplication": "targetApplications"},
|
||||||
|
decodeTargetApplication);
|
||||||
|
this._readArrayProps(root, result, {"localized": "localized"},
|
||||||
|
decodeLocale);
|
||||||
|
this._readArrayProps(root, result, {"dependency": "dependencies"},
|
||||||
|
source => getProperty(source, "id"));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
102
chrome/utils/aboutconfig/aboutconfig.xhtml
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
|
||||||
|
<!-- 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/. -->
|
||||||
|
|
||||||
|
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
|
||||||
|
<?xml-stylesheet href="chrome://global/skin/in-content/info-pages.css" type="text/css"?>
|
||||||
|
<?xml-stylesheet href="chrome://userchromejs/content/aboutconfig/config.css" type="text/css"?>
|
||||||
|
|
||||||
|
<window id="config"
|
||||||
|
title="about:config"
|
||||||
|
csp="default-src chrome:; object-src 'none'"
|
||||||
|
data-l10n-id="config-window"
|
||||||
|
aria-describedby="warningTitle warningText"
|
||||||
|
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||||
|
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||||
|
windowtype="Preferences:ConfigManager"
|
||||||
|
role="application"
|
||||||
|
width="750"
|
||||||
|
height="500">
|
||||||
|
|
||||||
|
<script src="chrome://userchromejs/content/aboutconfig/config.js"/>
|
||||||
|
<script src="chrome://global/content/editMenuOverlay.js"/>
|
||||||
|
<script src="chrome://global/content/globalOverlay.js"/>
|
||||||
|
|
||||||
|
<menupopup id="configContext">
|
||||||
|
<menuitem id="toggleSelected" label="Toggle" accesskey="T" default="true"/>
|
||||||
|
<menuitem id="modifySelected" label="Modify" accesskey="M" default="true"/>
|
||||||
|
<menuseparator/>
|
||||||
|
<menuitem id="copyPref" label="Copy" accesskey="C"/>
|
||||||
|
<menuitem id="copyName" label="Copy Name" accesskey="N"/>
|
||||||
|
<menuitem id="copyValue" label="Copy Value" accesskey="V"/>
|
||||||
|
<menu label="New" accesskey="w">
|
||||||
|
<menupopup>
|
||||||
|
<menuitem id="configString" label="String" accesskey="S"/>
|
||||||
|
<menuitem id="configInt" label="Integer" accesskey="I"/>
|
||||||
|
<menuitem id="configBool" label="Boolean" accesskey="B"/>
|
||||||
|
</menupopup>
|
||||||
|
</menu>
|
||||||
|
<menuitem id="resetSelected" label="Reset" accesskey="R"/>
|
||||||
|
</menupopup>
|
||||||
|
|
||||||
|
<keyset id="configTreeKeyset" disabled="true">
|
||||||
|
<key id="keyVKReturn" keycode="VK_RETURN"/>
|
||||||
|
<key id="configFocuSearch" modifiers="accel" key="r"/>
|
||||||
|
<key id="configFocuSearch2" modifiers="accel" key="f"/>
|
||||||
|
</keyset>
|
||||||
|
<deck id="configDeck" flex="1">
|
||||||
|
<vbox id="warningScreen" flex="1" align="center" style="overflow: auto;">
|
||||||
|
<spacer flex="1"/>
|
||||||
|
<vbox id="warningBox" class="container">
|
||||||
|
<hbox class="title" flex="1">
|
||||||
|
<label id="warningTitle" value="This might void your warranty!" class="title-text" flex="1"></label>
|
||||||
|
</hbox>
|
||||||
|
<vbox class="description" flex="1">
|
||||||
|
<label id="warningText">Changing these advanced settings can be harmful to the stability, security, and performance of this application. You should only continue if you are sure of what you are doing.</label>
|
||||||
|
<checkbox id="showWarningNextTime" label="Show this warning next time" checked="true"/>
|
||||||
|
<hbox class="button-container">
|
||||||
|
<button id="warningButton" label="I accept the risk!" class="primary"/>
|
||||||
|
</hbox>
|
||||||
|
</vbox>
|
||||||
|
</vbox>
|
||||||
|
<spacer style="-moz-box-flex: 2"/>
|
||||||
|
</vbox>
|
||||||
|
<vbox flex="1">
|
||||||
|
<hbox id="filterRow" align="center">
|
||||||
|
<label value="Search:" accesskey="r" control="textbox"/>
|
||||||
|
<search-textbox id="textbox" flex="1"
|
||||||
|
aria-controls="configTree"/>
|
||||||
|
</hbox>
|
||||||
|
<tree id="configTree" flex="1" seltype="single"
|
||||||
|
enableColumnDrag="true" context="configContext">
|
||||||
|
<treecols>
|
||||||
|
<!--
|
||||||
|
The below code may suggest that 'ordinal' is still a supported XUL
|
||||||
|
attribute. It is not. This is a crutch so that we can continue
|
||||||
|
persisting the CSS -moz-box-ordinal-group attribute, which is the
|
||||||
|
appropriate replacement for the ordinal attribute but cannot yet
|
||||||
|
be easily persisted. The code that synchronizes the attribute with
|
||||||
|
the CSS lives in toolkit/content/widget/tree.js and is specific to
|
||||||
|
tree elements.
|
||||||
|
-->
|
||||||
|
<treecol id="prefCol" label="Preference Name" style="-moz-box-flex: 7"
|
||||||
|
ignoreincolumnpicker="true"
|
||||||
|
persist="hidden width ordinal sortDirection"/>
|
||||||
|
<splitter class="tree-splitter" />
|
||||||
|
<treecol id="lockCol" label="Status" flex="1"
|
||||||
|
persist="hidden width ordinal sortDirection"/>
|
||||||
|
<splitter class="tree-splitter" />
|
||||||
|
<treecol id="typeCol" label="Type" flex="1"
|
||||||
|
persist="hidden width ordinal sortDirection"/>
|
||||||
|
<splitter class="tree-splitter" />
|
||||||
|
<treecol id="valueCol" label="Value" style="-moz-box-flex: 10"
|
||||||
|
persist="hidden width ordinal sortDirection"/>
|
||||||
|
</treecols>
|
||||||
|
|
||||||
|
<treechildren id="configTreeBody"/>
|
||||||
|
</tree>
|
||||||
|
</vbox>
|
||||||
|
</deck>
|
||||||
|
</window>
|
49
chrome/utils/aboutconfig/config.css
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
#warningScreen {
|
||||||
|
font-size: 15px;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-inline-start: calc(48px + 4.6em);
|
||||||
|
padding-inline-end: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
background-image: url("chrome://global/skin/icons/warning.svg");
|
||||||
|
fill: #fcd100;
|
||||||
|
}
|
||||||
|
|
||||||
|
#warningTitle {
|
||||||
|
font-weight: lighter;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin: 0;
|
||||||
|
margin-bottom: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#warningText {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#warningButton {
|
||||||
|
margin-top: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#filterRow {
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-inline-start: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#configTree {
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#configTreeBody::-moz-tree-cell-text(user) {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#configTreeBody::-moz-tree-cell-text(locked) {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
781
chrome/utils/aboutconfig/config.js
Normal file
|
@ -0,0 +1,781 @@
|
||||||
|
// -*- 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/. */
|
||||||
|
|
||||||
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
const nsIPrefLocalizedString = Ci.nsIPrefLocalizedString;
|
||||||
|
const nsISupportsString = Ci.nsISupportsString;
|
||||||
|
const nsIPrefBranch = Ci.nsIPrefBranch;
|
||||||
|
const nsIClipboardHelper = Ci.nsIClipboardHelper;
|
||||||
|
|
||||||
|
const nsClipboardHelper_CONTRACTID = "@mozilla.org/widget/clipboardhelper;1";
|
||||||
|
|
||||||
|
const gPrefBranch = Services.prefs;
|
||||||
|
const gClipboardHelper = Cc[nsClipboardHelper_CONTRACTID].getService(
|
||||||
|
nsIClipboardHelper
|
||||||
|
);
|
||||||
|
|
||||||
|
var gLockProps = ["default", "user", "locked"];
|
||||||
|
// we get these from a string bundle
|
||||||
|
var gLockStrs = [];
|
||||||
|
var gTypeStrs = [];
|
||||||
|
|
||||||
|
const PREF_IS_DEFAULT_VALUE = 0;
|
||||||
|
const PREF_IS_MODIFIED = 1;
|
||||||
|
const PREF_IS_LOCKED = 2;
|
||||||
|
|
||||||
|
var gPrefHash = {};
|
||||||
|
var gPrefArray = [];
|
||||||
|
var gPrefView = gPrefArray; // share the JS array
|
||||||
|
var gSortedColumn = "prefCol";
|
||||||
|
var gSortFunction = null;
|
||||||
|
var gSortDirection = 1; // 1 is ascending; -1 is descending
|
||||||
|
var gFilter = null;
|
||||||
|
|
||||||
|
var view = {
|
||||||
|
get rowCount() {
|
||||||
|
return gPrefView.length;
|
||||||
|
},
|
||||||
|
getCellText(index, col) {
|
||||||
|
if (!(index in gPrefView)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = gPrefView[index][col.id];
|
||||||
|
|
||||||
|
switch (col.id) {
|
||||||
|
case "lockCol":
|
||||||
|
return gLockStrs[value];
|
||||||
|
case "typeCol":
|
||||||
|
return gTypeStrs[value];
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getRowProperties(index) {
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
getCellProperties(index, col) {
|
||||||
|
if (index in gPrefView) {
|
||||||
|
return gLockProps[gPrefView[index].lockCol];
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
getColumnProperties(col) {
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
treebox: null,
|
||||||
|
selection: null,
|
||||||
|
isContainer(index) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isContainerOpen(index) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isContainerEmpty(index) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isSorted() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
canDrop(index, orientation) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
drop(row, orientation) {},
|
||||||
|
setTree(out) {
|
||||||
|
this.treebox = out;
|
||||||
|
},
|
||||||
|
getParentIndex(rowIndex) {
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
hasNextSibling(rowIndex, afterIndex) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
getLevel(index) {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
getImageSrc(row, col) {
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
toggleOpenState(index) {},
|
||||||
|
cycleHeader(col) {
|
||||||
|
var index = this.selection.currentIndex;
|
||||||
|
if (col.id == gSortedColumn) {
|
||||||
|
gSortDirection = -gSortDirection;
|
||||||
|
gPrefArray.reverse();
|
||||||
|
if (gPrefView != gPrefArray) {
|
||||||
|
gPrefView.reverse();
|
||||||
|
}
|
||||||
|
if (index >= 0) {
|
||||||
|
index = gPrefView.length - index - 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var pref = null;
|
||||||
|
if (index >= 0) {
|
||||||
|
pref = gPrefView[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
var old = document.getElementById(gSortedColumn);
|
||||||
|
old.removeAttribute("sortDirection");
|
||||||
|
gPrefArray.sort((gSortFunction = gSortFunctions[col.id]));
|
||||||
|
if (gPrefView != gPrefArray) {
|
||||||
|
gPrefView.sort(gSortFunction);
|
||||||
|
}
|
||||||
|
gSortedColumn = col.id;
|
||||||
|
if (pref) {
|
||||||
|
index = getViewIndexOfPref(pref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
col.element.setAttribute(
|
||||||
|
"sortDirection",
|
||||||
|
gSortDirection > 0 ? "ascending" : "descending"
|
||||||
|
);
|
||||||
|
this.treebox.invalidate();
|
||||||
|
if (index >= 0) {
|
||||||
|
this.selection.select(index);
|
||||||
|
this.treebox.ensureRowIsVisible(index);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectionChanged() {},
|
||||||
|
cycleCell(row, col) {},
|
||||||
|
isEditable(row, col) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
setCellValue(row, col, value) {},
|
||||||
|
setCellText(row, col, value) {},
|
||||||
|
isSeparator(index) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// find the index in gPrefView of a pref object
|
||||||
|
// or -1 if it does not exist in the filtered view
|
||||||
|
function getViewIndexOfPref(pref) {
|
||||||
|
var low = -1,
|
||||||
|
high = gPrefView.length;
|
||||||
|
var index = (low + high) >> 1;
|
||||||
|
while (index > low) {
|
||||||
|
var mid = gPrefView[index];
|
||||||
|
if (mid == pref) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
if (gSortFunction(mid, pref) < 0) {
|
||||||
|
low = index;
|
||||||
|
} else {
|
||||||
|
high = index;
|
||||||
|
}
|
||||||
|
index = (low + high) >> 1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the index in gPrefView where a pref object belongs
|
||||||
|
function getNearestViewIndexOfPref(pref) {
|
||||||
|
var low = -1,
|
||||||
|
high = gPrefView.length;
|
||||||
|
var index = (low + high) >> 1;
|
||||||
|
while (index > low) {
|
||||||
|
if (gSortFunction(gPrefView[index], pref) < 0) {
|
||||||
|
low = index;
|
||||||
|
} else {
|
||||||
|
high = index;
|
||||||
|
}
|
||||||
|
index = (low + high) >> 1;
|
||||||
|
}
|
||||||
|
return high;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the index in gPrefArray of a pref object
|
||||||
|
function getIndexOfPref(pref) {
|
||||||
|
var low = -1,
|
||||||
|
high = gPrefArray.length;
|
||||||
|
var index = (low + high) >> 1;
|
||||||
|
while (index > low) {
|
||||||
|
var mid = gPrefArray[index];
|
||||||
|
if (mid == pref) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
if (gSortFunction(mid, pref) < 0) {
|
||||||
|
low = index;
|
||||||
|
} else {
|
||||||
|
high = index;
|
||||||
|
}
|
||||||
|
index = (low + high) >> 1;
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNearestIndexOfPref(pref) {
|
||||||
|
var low = -1,
|
||||||
|
high = gPrefArray.length;
|
||||||
|
var index = (low + high) >> 1;
|
||||||
|
while (index > low) {
|
||||||
|
if (gSortFunction(gPrefArray[index], pref) < 0) {
|
||||||
|
low = index;
|
||||||
|
} else {
|
||||||
|
high = index;
|
||||||
|
}
|
||||||
|
index = (low + high) >> 1;
|
||||||
|
}
|
||||||
|
return high;
|
||||||
|
}
|
||||||
|
|
||||||
|
var gPrefListener = {
|
||||||
|
observe(subject, topic, prefName) {
|
||||||
|
if (topic != "nsPref:changed") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var arrayIndex = gPrefArray.length;
|
||||||
|
var viewIndex = arrayIndex;
|
||||||
|
var selectedIndex = view.selection.currentIndex;
|
||||||
|
var pref;
|
||||||
|
var updateView = false;
|
||||||
|
var updateArray = false;
|
||||||
|
var addedRow = false;
|
||||||
|
if (prefName in gPrefHash) {
|
||||||
|
pref = gPrefHash[prefName];
|
||||||
|
viewIndex = getViewIndexOfPref(pref);
|
||||||
|
arrayIndex = getIndexOfPref(pref);
|
||||||
|
fetchPref(prefName, arrayIndex);
|
||||||
|
// fetchPref replaces the existing pref object
|
||||||
|
pref = gPrefHash[prefName];
|
||||||
|
if (viewIndex >= 0) {
|
||||||
|
// Might need to update the filtered view
|
||||||
|
gPrefView[viewIndex] = gPrefHash[prefName];
|
||||||
|
view.treebox.invalidateRow(viewIndex);
|
||||||
|
}
|
||||||
|
if (gSortedColumn == "lockCol" || gSortedColumn == "valueCol") {
|
||||||
|
updateArray = true;
|
||||||
|
gPrefArray.splice(arrayIndex, 1);
|
||||||
|
if (gFilter && gFilter.test(pref.prefCol + ";" + pref.valueCol)) {
|
||||||
|
updateView = true;
|
||||||
|
gPrefView.splice(viewIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fetchPref(prefName, arrayIndex);
|
||||||
|
pref = gPrefArray.pop();
|
||||||
|
updateArray = true;
|
||||||
|
addedRow = true;
|
||||||
|
if (gFilter && gFilter.test(pref.prefCol + ";" + pref.valueCol)) {
|
||||||
|
updateView = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateArray) {
|
||||||
|
// Reinsert in the data array
|
||||||
|
var newIndex = getNearestIndexOfPref(pref);
|
||||||
|
gPrefArray.splice(newIndex, 0, pref);
|
||||||
|
|
||||||
|
if (updateView) {
|
||||||
|
// View is filtered, reinsert in the view separately
|
||||||
|
newIndex = getNearestViewIndexOfPref(pref);
|
||||||
|
gPrefView.splice(newIndex, 0, pref);
|
||||||
|
} else if (gFilter) {
|
||||||
|
// View is filtered, but nothing to update
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addedRow) {
|
||||||
|
view.treebox.rowCountChanged(newIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate the changed range in the view
|
||||||
|
var low = Math.min(viewIndex, newIndex);
|
||||||
|
var high = Math.max(viewIndex, newIndex);
|
||||||
|
view.treebox.invalidateRange(low, high);
|
||||||
|
|
||||||
|
if (selectedIndex == viewIndex) {
|
||||||
|
selectedIndex = newIndex;
|
||||||
|
} else if (selectedIndex >= low && selectedIndex <= high) {
|
||||||
|
selectedIndex += newIndex > viewIndex ? -1 : 1;
|
||||||
|
}
|
||||||
|
if (selectedIndex >= 0) {
|
||||||
|
view.selection.select(selectedIndex);
|
||||||
|
if (selectedIndex == newIndex) {
|
||||||
|
view.treebox.ensureRowIsVisible(selectedIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function prefObject(prefName, prefIndex) {
|
||||||
|
this.prefCol = prefName;
|
||||||
|
}
|
||||||
|
|
||||||
|
prefObject.prototype = {
|
||||||
|
lockCol: PREF_IS_DEFAULT_VALUE,
|
||||||
|
typeCol: nsIPrefBranch.PREF_STRING,
|
||||||
|
valueCol: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
function fetchPref(prefName, prefIndex) {
|
||||||
|
var pref = new prefObject(prefName);
|
||||||
|
|
||||||
|
gPrefHash[prefName] = pref;
|
||||||
|
gPrefArray[prefIndex] = pref;
|
||||||
|
|
||||||
|
if (gPrefBranch.prefIsLocked(prefName)) {
|
||||||
|
pref.lockCol = PREF_IS_LOCKED;
|
||||||
|
} else if (gPrefBranch.prefHasUserValue(prefName)) {
|
||||||
|
pref.lockCol = PREF_IS_MODIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (gPrefBranch.getPrefType(prefName)) {
|
||||||
|
case gPrefBranch.PREF_BOOL:
|
||||||
|
pref.typeCol = gPrefBranch.PREF_BOOL;
|
||||||
|
// convert to a string
|
||||||
|
pref.valueCol = gPrefBranch.getBoolPref(prefName).toString();
|
||||||
|
break;
|
||||||
|
case gPrefBranch.PREF_INT:
|
||||||
|
pref.typeCol = gPrefBranch.PREF_INT;
|
||||||
|
// convert to a string
|
||||||
|
pref.valueCol = gPrefBranch.getIntPref(prefName).toString();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case gPrefBranch.PREF_STRING:
|
||||||
|
pref.valueCol = gPrefBranch.getStringPref(prefName);
|
||||||
|
// Try in case it's a localized string (will throw an exception if not)
|
||||||
|
if (
|
||||||
|
pref.lockCol == PREF_IS_DEFAULT_VALUE &&
|
||||||
|
/^chrome:\/\/.+\/locale\/.+\.properties/.test(pref.valueCol)
|
||||||
|
) {
|
||||||
|
pref.valueCol = gPrefBranch.getComplexValue(
|
||||||
|
prefName,
|
||||||
|
nsIPrefLocalizedString
|
||||||
|
).data;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Also catch obscure cases in which you can't tell in advance
|
||||||
|
// that the pref exists but has no user or default value...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onConfigLoad() {
|
||||||
|
let configContext = document.getElementById("configContext");
|
||||||
|
configContext.addEventListener("popupshowing", function(event) {
|
||||||
|
if (event.target == this) {
|
||||||
|
updateContextMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let commandListeners = {
|
||||||
|
toggleSelected: ModifySelected,
|
||||||
|
modifySelected: ModifySelected,
|
||||||
|
copyPref,
|
||||||
|
copyName,
|
||||||
|
copyValue,
|
||||||
|
resetSelected: ResetSelected,
|
||||||
|
};
|
||||||
|
|
||||||
|
configContext.addEventListener("command", e => {
|
||||||
|
if (e.target.id in commandListeners) {
|
||||||
|
commandListeners[e.target.id]();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let configString = document.getElementById("configString");
|
||||||
|
configString.addEventListener("command", function() {
|
||||||
|
NewPref(nsIPrefBranch.PREF_STRING);
|
||||||
|
});
|
||||||
|
|
||||||
|
let configInt = document.getElementById("configInt");
|
||||||
|
configInt.addEventListener("command", function() {
|
||||||
|
NewPref(nsIPrefBranch.PREF_INT);
|
||||||
|
});
|
||||||
|
|
||||||
|
let configBool = document.getElementById("configBool");
|
||||||
|
configBool.addEventListener("command", function() {
|
||||||
|
NewPref(nsIPrefBranch.PREF_BOOL);
|
||||||
|
});
|
||||||
|
|
||||||
|
let keyVKReturn = document.getElementById("keyVKReturn");
|
||||||
|
keyVKReturn.addEventListener("command", ModifySelected);
|
||||||
|
|
||||||
|
let textBox = document.getElementById("textbox");
|
||||||
|
textBox.addEventListener("command", FilterPrefs);
|
||||||
|
|
||||||
|
let configFocuSearch = document.getElementById("configFocuSearch");
|
||||||
|
configFocuSearch.addEventListener("command", function() {
|
||||||
|
textBox.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
let configFocuSearch2 = document.getElementById("configFocuSearch2");
|
||||||
|
configFocuSearch2.addEventListener("command", function() {
|
||||||
|
textBox.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
let warningButton = document.getElementById("warningButton");
|
||||||
|
warningButton.addEventListener("command", ShowPrefs);
|
||||||
|
|
||||||
|
let configTree = document.getElementById("configTree");
|
||||||
|
configTree.addEventListener("select", function() {
|
||||||
|
window.updateCommands("select");
|
||||||
|
});
|
||||||
|
|
||||||
|
let configTreeBody = document.getElementById("configTreeBody");
|
||||||
|
configTreeBody.addEventListener("dblclick", function(event) {
|
||||||
|
if (event.button == 0) {
|
||||||
|
ModifySelected();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gLockStrs[PREF_IS_DEFAULT_VALUE] = 'default';
|
||||||
|
gLockStrs[PREF_IS_MODIFIED] = 'modified';
|
||||||
|
gLockStrs[PREF_IS_LOCKED] = 'locked';
|
||||||
|
gTypeStrs[nsIPrefBranch.PREF_STRING] = 'string';
|
||||||
|
gTypeStrs[nsIPrefBranch.PREF_INT] = 'integer';
|
||||||
|
gTypeStrs[nsIPrefBranch.PREF_BOOL] = 'boolean';
|
||||||
|
|
||||||
|
var showWarning = gPrefBranch.getBoolPref("general.warnOnAboutConfig");
|
||||||
|
|
||||||
|
if (showWarning) {
|
||||||
|
document.getElementById("warningButton").focus();
|
||||||
|
} else {
|
||||||
|
ShowPrefs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unhide the warning message
|
||||||
|
function ShowPrefs() {
|
||||||
|
document.getElementById('configDeck').lastElementChild.style.visibility = 'visible';
|
||||||
|
gPrefBranch.getChildList("").forEach(fetchPref);
|
||||||
|
|
||||||
|
var descending = document.getElementsByAttribute(
|
||||||
|
"sortDirection",
|
||||||
|
"descending"
|
||||||
|
);
|
||||||
|
if (descending.item(0)) {
|
||||||
|
gSortedColumn = descending[0].id;
|
||||||
|
gSortDirection = -1;
|
||||||
|
} else {
|
||||||
|
var ascending = document.getElementsByAttribute(
|
||||||
|
"sortDirection",
|
||||||
|
"ascending"
|
||||||
|
);
|
||||||
|
if (ascending.item(0)) {
|
||||||
|
gSortedColumn = ascending[0].id;
|
||||||
|
} else {
|
||||||
|
document
|
||||||
|
.getElementById(gSortedColumn)
|
||||||
|
.setAttribute("sortDirection", "ascending");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gSortFunction = gSortFunctions[gSortedColumn];
|
||||||
|
gPrefArray.sort(gSortFunction);
|
||||||
|
|
||||||
|
gPrefBranch.addObserver("", gPrefListener);
|
||||||
|
|
||||||
|
var configTree = document.getElementById("configTree");
|
||||||
|
configTree.view = view;
|
||||||
|
configTree.controllers.insertControllerAt(0, configController);
|
||||||
|
|
||||||
|
document.getElementById("configDeck").setAttribute("selectedIndex", 1);
|
||||||
|
document.getElementById("configTreeKeyset").removeAttribute("disabled");
|
||||||
|
if (!document.getElementById("showWarningNextTime").checked) {
|
||||||
|
gPrefBranch.setBoolPref("general.warnOnAboutConfig", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process about:config?filter=<string>
|
||||||
|
var textbox = document.getElementById("textbox");
|
||||||
|
// About URIs don't support query params, so do this manually
|
||||||
|
var loc = document.location.href;
|
||||||
|
var matches = /[?&]filter\=([^&]+)/i.exec(loc);
|
||||||
|
if (matches) {
|
||||||
|
textbox.value = decodeURIComponent(matches[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even if we did not set the filter string via the URL query,
|
||||||
|
// textbox might have been set via some other mechanism
|
||||||
|
if (textbox.value) {
|
||||||
|
FilterPrefs();
|
||||||
|
}
|
||||||
|
textbox.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConfigUnload() {
|
||||||
|
if (
|
||||||
|
document.getElementById("configDeck").getAttribute("selectedIndex") == 1
|
||||||
|
) {
|
||||||
|
gPrefBranch.removeObserver("", gPrefListener);
|
||||||
|
var configTree = document.getElementById("configTree");
|
||||||
|
configTree.view = null;
|
||||||
|
configTree.controllers.removeController(configController);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function FilterPrefs() {
|
||||||
|
if (
|
||||||
|
document.getElementById("configDeck").getAttribute("selectedIndex") != 1
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var substring = document.getElementById("textbox").value;
|
||||||
|
// Check for "/regex/[i]"
|
||||||
|
if (substring.charAt(0) == "/") {
|
||||||
|
var r = substring.match(/^\/(.*)\/(i?)$/);
|
||||||
|
try {
|
||||||
|
gFilter = RegExp(r[1], r[2]);
|
||||||
|
} catch (e) {
|
||||||
|
return; // Do nothing on incomplete or bad RegExp
|
||||||
|
}
|
||||||
|
} else if (substring) {
|
||||||
|
gFilter = RegExp(
|
||||||
|
substring
|
||||||
|
.replace(/([^* \w])/g, "\\$1")
|
||||||
|
.replace(/^\*+/, "")
|
||||||
|
.replace(/\*+/g, ".*"),
|
||||||
|
"i"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
gFilter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefCol =
|
||||||
|
view.selection && view.selection.currentIndex < 0
|
||||||
|
? null
|
||||||
|
: gPrefView[view.selection.currentIndex].prefCol;
|
||||||
|
var oldlen = gPrefView.length;
|
||||||
|
gPrefView = gPrefArray;
|
||||||
|
if (gFilter) {
|
||||||
|
gPrefView = [];
|
||||||
|
for (var i = 0; i < gPrefArray.length; ++i) {
|
||||||
|
if (gFilter.test(gPrefArray[i].prefCol + ";" + gPrefArray[i].valueCol)) {
|
||||||
|
gPrefView.push(gPrefArray[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view.treebox.invalidate();
|
||||||
|
view.treebox.rowCountChanged(oldlen, gPrefView.length - oldlen);
|
||||||
|
gotoPref(prefCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
function prefColSortFunction(x, y) {
|
||||||
|
if (x.prefCol > y.prefCol) {
|
||||||
|
return gSortDirection;
|
||||||
|
}
|
||||||
|
if (x.prefCol < y.prefCol) {
|
||||||
|
return -gSortDirection;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lockColSortFunction(x, y) {
|
||||||
|
if (x.lockCol != y.lockCol) {
|
||||||
|
return gSortDirection * (y.lockCol - x.lockCol);
|
||||||
|
}
|
||||||
|
return prefColSortFunction(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
function typeColSortFunction(x, y) {
|
||||||
|
if (x.typeCol != y.typeCol) {
|
||||||
|
return gSortDirection * (y.typeCol - x.typeCol);
|
||||||
|
}
|
||||||
|
return prefColSortFunction(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
function valueColSortFunction(x, y) {
|
||||||
|
if (x.valueCol > y.valueCol) {
|
||||||
|
return gSortDirection;
|
||||||
|
}
|
||||||
|
if (x.valueCol < y.valueCol) {
|
||||||
|
return -gSortDirection;
|
||||||
|
}
|
||||||
|
return prefColSortFunction(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gSortFunctions = {
|
||||||
|
prefCol: prefColSortFunction,
|
||||||
|
lockCol: lockColSortFunction,
|
||||||
|
typeCol: typeColSortFunction,
|
||||||
|
valueCol: valueColSortFunction,
|
||||||
|
};
|
||||||
|
|
||||||
|
const gCategoryLabelForSortColumn = {
|
||||||
|
prefCol: "SortByName",
|
||||||
|
lockCol: "SortByStatus",
|
||||||
|
typeCol: "SortByType",
|
||||||
|
valueCol: "SortByValue",
|
||||||
|
};
|
||||||
|
|
||||||
|
const configController = {
|
||||||
|
supportsCommand: function supportsCommand(command) {
|
||||||
|
return command == "cmd_copy";
|
||||||
|
},
|
||||||
|
isCommandEnabled: function isCommandEnabled(command) {
|
||||||
|
return view.selection && view.selection.currentIndex >= 0;
|
||||||
|
},
|
||||||
|
doCommand: function doCommand(command) {
|
||||||
|
copyPref();
|
||||||
|
},
|
||||||
|
onEvent: function onEvent(event) {},
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateContextMenu() {
|
||||||
|
var lockCol = PREF_IS_LOCKED;
|
||||||
|
var typeCol = nsIPrefBranch.PREF_STRING;
|
||||||
|
var valueCol = "";
|
||||||
|
var copyDisabled = true;
|
||||||
|
var prefSelected = view.selection.currentIndex >= 0;
|
||||||
|
|
||||||
|
if (prefSelected) {
|
||||||
|
var prefRow = gPrefView[view.selection.currentIndex];
|
||||||
|
lockCol = prefRow.lockCol;
|
||||||
|
typeCol = prefRow.typeCol;
|
||||||
|
valueCol = prefRow.valueCol;
|
||||||
|
copyDisabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var copyPref = document.getElementById("copyPref");
|
||||||
|
copyPref.setAttribute("disabled", copyDisabled);
|
||||||
|
|
||||||
|
var copyName = document.getElementById("copyName");
|
||||||
|
copyName.setAttribute("disabled", copyDisabled);
|
||||||
|
|
||||||
|
var copyValue = document.getElementById("copyValue");
|
||||||
|
copyValue.setAttribute("disabled", copyDisabled);
|
||||||
|
|
||||||
|
var resetSelected = document.getElementById("resetSelected");
|
||||||
|
resetSelected.setAttribute("disabled", lockCol != PREF_IS_MODIFIED);
|
||||||
|
|
||||||
|
var canToggle = typeCol == nsIPrefBranch.PREF_BOOL && valueCol != "";
|
||||||
|
// indicates that a pref is locked or no pref is selected at all
|
||||||
|
var isLocked = lockCol == PREF_IS_LOCKED;
|
||||||
|
|
||||||
|
var modifySelected = document.getElementById("modifySelected");
|
||||||
|
modifySelected.setAttribute("disabled", isLocked);
|
||||||
|
modifySelected.hidden = canToggle;
|
||||||
|
|
||||||
|
var toggleSelected = document.getElementById("toggleSelected");
|
||||||
|
toggleSelected.setAttribute("disabled", isLocked);
|
||||||
|
toggleSelected.hidden = !canToggle;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyPref() {
|
||||||
|
var pref = gPrefView[view.selection.currentIndex];
|
||||||
|
gClipboardHelper.copyString(pref.prefCol + ";" + pref.valueCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyName() {
|
||||||
|
gClipboardHelper.copyString(gPrefView[view.selection.currentIndex].prefCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyValue() {
|
||||||
|
gClipboardHelper.copyString(gPrefView[view.selection.currentIndex].valueCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ModifySelected() {
|
||||||
|
if (view.selection.currentIndex >= 0) {
|
||||||
|
ModifyPref(gPrefView[view.selection.currentIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResetSelected() {
|
||||||
|
var entry = gPrefView[view.selection.currentIndex];
|
||||||
|
gPrefBranch.clearUserPref(entry.prefCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function NewPref(type) {
|
||||||
|
var result = { value: "" };
|
||||||
|
var dummy = { value: 0 };
|
||||||
|
|
||||||
|
let [newTitle, newPrompt] = [`New ${gTypeStrs[type]} value`, 'Enter the preference name'];
|
||||||
|
|
||||||
|
if (
|
||||||
|
Services.prompt.prompt(window, newTitle, newPrompt, result, null, dummy)
|
||||||
|
) {
|
||||||
|
result.value = result.value.trim();
|
||||||
|
if (!result.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pref;
|
||||||
|
if (result.value in gPrefHash) {
|
||||||
|
pref = gPrefHash[result.value];
|
||||||
|
} else {
|
||||||
|
pref = {
|
||||||
|
prefCol: result.value,
|
||||||
|
lockCol: PREF_IS_DEFAULT_VALUE,
|
||||||
|
typeCol: type,
|
||||||
|
valueCol: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (ModifyPref(pref)) {
|
||||||
|
setTimeout(gotoPref, 0, result.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function gotoPref(pref) {
|
||||||
|
// make sure the pref exists and is displayed in the current view
|
||||||
|
var index = pref in gPrefHash ? getViewIndexOfPref(gPrefHash[pref]) : -1;
|
||||||
|
if (index >= 0) {
|
||||||
|
view.selection.select(index);
|
||||||
|
view.treebox.ensureRowIsVisible(index);
|
||||||
|
} else {
|
||||||
|
view.selection.clearSelection();
|
||||||
|
view.selection.currentIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ModifyPref(entry) {
|
||||||
|
if (entry.lockCol == PREF_IS_LOCKED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let [title] = [`Enter ${gTypeStrs[entry.typeCol]} value`];
|
||||||
|
|
||||||
|
if (entry.typeCol == nsIPrefBranch.PREF_BOOL) {
|
||||||
|
var check = { value: entry.valueCol == "false" };
|
||||||
|
if (
|
||||||
|
!entry.valueCol &&
|
||||||
|
!Services.prompt.select(
|
||||||
|
window,
|
||||||
|
title,
|
||||||
|
entry.prefCol,
|
||||||
|
[false, true],
|
||||||
|
check
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
gPrefBranch.setBoolPref(entry.prefCol, check.value);
|
||||||
|
} else {
|
||||||
|
var result = { value: entry.valueCol };
|
||||||
|
var dummy = { value: 0 };
|
||||||
|
if (
|
||||||
|
!Services.prompt.prompt(window, title, entry.prefCol, result, null, dummy)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (entry.typeCol == nsIPrefBranch.PREF_INT) {
|
||||||
|
// | 0 converts to integer or 0; - 0 to float or NaN.
|
||||||
|
// Thus, this check should catch all cases.
|
||||||
|
var val = result.value | 0;
|
||||||
|
if (val != result.value - 0) {
|
||||||
|
const [err_title, err_text] = ['Invalid value', 'The text you entered is not a number.'];
|
||||||
|
|
||||||
|
Services.prompt.alert(window, err_title, err_text);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
gPrefBranch.setIntPref(entry.prefCol, val);
|
||||||
|
} else {
|
||||||
|
gPrefBranch.setStringPref(entry.prefCol, result.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Services.prefs.savePrefFile(null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = onConfigLoad;
|
||||||
|
window.onunload = onConfigUnload;
|
2
chrome/utils/chrome.manifest
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
content userchromejs ./
|
||||||
|
resource userchromejs ../
|
42
chrome/utils/hookFunction.jsm
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
let EXPORTED_SYMBOLS = ['hookFunction'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add hooks to a function to execute before and after it. The function to modify is functionContext[functionName]. Call only once per function - modification is not supported.
|
||||||
|
*
|
||||||
|
* Other addons wishing to access the original function may do so using the .originalFunction member of the replacement function. This member can also be set if required, to insert a new function replacement into the chain rather than appending.
|
||||||
|
*
|
||||||
|
* @param functionContext The object on which the function is a property
|
||||||
|
* @param functionName The name of the property containing the function (on functionContext)
|
||||||
|
* @param onBeforeFunction A function to be called before the hooked function is executed. It will be passed the same parameters as the hooked function. It's return value will be passed on to onAfterFunction
|
||||||
|
* @param onAfterFunction A function to be called after the hooked function is executed. The parameters passed to it are: onBeforeFunction return value, arguments object from original hooked function, return value from original hooked function. It's return value will be returned in place of that of the original function.
|
||||||
|
* @returns A function which can be called to safely un-hook the hook
|
||||||
|
*/
|
||||||
|
function hookFunction(functionContext, functionName, onBeforeFunction, onAfterFunction) {
|
||||||
|
let originalFunction = functionContext[functionName];
|
||||||
|
|
||||||
|
if (!originalFunction) {
|
||||||
|
throw new Error("Could not find function " + functionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
let replacementFunction = function() {
|
||||||
|
let onBeforeResult = null;
|
||||||
|
if (onBeforeFunction) {
|
||||||
|
onBeforeResult = onBeforeFunction.apply(this, arguments);
|
||||||
|
}
|
||||||
|
let originalResult = replacementFunction.originalFunction.apply(this, arguments);
|
||||||
|
if (onAfterFunction) {
|
||||||
|
return onAfterFunction.call(this, onBeforeResult, arguments, originalResult);
|
||||||
|
} else {
|
||||||
|
return originalResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replacementFunction.originalFunction = originalFunction;
|
||||||
|
functionContext[functionName] = replacementFunction;
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
// Not safe to simply assign originalFunction back again, as something else might have chained onto this function, which would then break the chain
|
||||||
|
// Unassigning these variables prevent any effects of the hook, though the function itself remains in place.
|
||||||
|
onBeforeFunction = null;
|
||||||
|
onAfterFunction = null;
|
||||||
|
};
|
||||||
|
}
|
845
chrome/utils/passwordmgr/passwordManager.js
Normal file
|
@ -0,0 +1,845 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
/** * =================== SAVED SIGNONS CODE =================== ***/
|
||||||
|
/* eslint-disable-next-line no-var */
|
||||||
|
var { AppConstants } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/AppConstants.jsm"
|
||||||
|
);
|
||||||
|
/* eslint-disable-next-line no-var */
|
||||||
|
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
ChromeUtils.defineModuleGetter(
|
||||||
|
this,
|
||||||
|
"DeferredTask",
|
||||||
|
"resource://gre/modules/DeferredTask.jsm"
|
||||||
|
);
|
||||||
|
ChromeUtils.defineModuleGetter(
|
||||||
|
this,
|
||||||
|
"PlacesUtils",
|
||||||
|
"resource://gre/modules/PlacesUtils.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Default value for signon table sorting
|
||||||
|
let lastSignonSortColumn = "origin";
|
||||||
|
let lastSignonSortAscending = true;
|
||||||
|
|
||||||
|
let showingPasswords = false;
|
||||||
|
|
||||||
|
// password-manager lists
|
||||||
|
let signons = [];
|
||||||
|
let deletedSignons = [];
|
||||||
|
|
||||||
|
// Elements that would be used frequently
|
||||||
|
let filterField;
|
||||||
|
let togglePasswordsButton;
|
||||||
|
let signonsIntro;
|
||||||
|
let removeButton;
|
||||||
|
let removeAllButton;
|
||||||
|
let signonsTree;
|
||||||
|
|
||||||
|
let signonReloadDisplay = {
|
||||||
|
observe(subject, topic, data) {
|
||||||
|
if (topic == "passwordmgr-storage-changed") {
|
||||||
|
switch (data) {
|
||||||
|
case "addLogin":
|
||||||
|
case "modifyLogin":
|
||||||
|
case "removeLogin":
|
||||||
|
case "removeAllLogins":
|
||||||
|
if (!signonsTree) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
signons.length = 0;
|
||||||
|
LoadSignons();
|
||||||
|
// apply the filter if needed
|
||||||
|
if (filterField && filterField.value != "") {
|
||||||
|
FilterPasswords();
|
||||||
|
}
|
||||||
|
signonsTree.ensureRowIsVisible(
|
||||||
|
signonsTree.view.selection.currentIndex
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Services.obs.notifyObservers(null, "passwordmgr-dialog-updated");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Formatter for localization.
|
||||||
|
let dateFormatter = new Services.intl.DateTimeFormat(undefined, {
|
||||||
|
dateStyle: "medium",
|
||||||
|
});
|
||||||
|
let dateAndTimeFormatter = new Services.intl.DateTimeFormat(undefined, {
|
||||||
|
dateStyle: "medium",
|
||||||
|
timeStyle: "short",
|
||||||
|
});
|
||||||
|
|
||||||
|
function Startup() {
|
||||||
|
// be prepared to reload the display if anything changes
|
||||||
|
Services.obs.addObserver(signonReloadDisplay, "passwordmgr-storage-changed");
|
||||||
|
|
||||||
|
signonsTree = document.getElementById("signonsTree");
|
||||||
|
filterField = document.getElementById("filter");
|
||||||
|
togglePasswordsButton = document.getElementById("togglePasswords");
|
||||||
|
signonsIntro = document.getElementById("signonsIntro");
|
||||||
|
removeButton = document.getElementById("removeSignon");
|
||||||
|
removeAllButton = document.getElementById("removeAllSignons");
|
||||||
|
|
||||||
|
togglePasswordsButton.label = "Show Passwords";
|
||||||
|
togglePasswordsButton.accessKey = "P";
|
||||||
|
signonsIntro.textContent = "Logins for the following sites are stored on your computer";
|
||||||
|
removeAllButton.label = "Remove All";
|
||||||
|
removeAllButton.accessKey = "A";
|
||||||
|
|
||||||
|
if (Services.policies && !Services.policies.isAllowed("passwordReveal")) {
|
||||||
|
togglePasswordsButton.hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementsByTagName("treecols")[0]
|
||||||
|
.addEventListener("click", event => {
|
||||||
|
let { target, button } = event;
|
||||||
|
let sortField = target.getAttribute("data-field-name");
|
||||||
|
|
||||||
|
if (target.nodeName != "treecol" || button != 0 || !sortField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignonColumnSort(sortField);
|
||||||
|
Services.telemetry
|
||||||
|
.getKeyedHistogramById("PWMGR_MANAGE_SORTED")
|
||||||
|
.add(sortField);
|
||||||
|
});
|
||||||
|
|
||||||
|
LoadSignons();
|
||||||
|
|
||||||
|
// filter the table if requested by caller
|
||||||
|
if (
|
||||||
|
window.arguments &&
|
||||||
|
window.arguments[0] &&
|
||||||
|
window.arguments[0].filterString
|
||||||
|
) {
|
||||||
|
setFilter(window.arguments[0].filterString);
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusFilterBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
function Shutdown() {
|
||||||
|
Services.obs.removeObserver(
|
||||||
|
signonReloadDisplay,
|
||||||
|
"passwordmgr-storage-changed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFilter(aFilterString) {
|
||||||
|
filterField.value = aFilterString;
|
||||||
|
FilterPasswords();
|
||||||
|
}
|
||||||
|
|
||||||
|
let signonsTreeView = {
|
||||||
|
_filterSet: [],
|
||||||
|
_lastSelectedRanges: [],
|
||||||
|
selection: null,
|
||||||
|
|
||||||
|
rowCount: 0,
|
||||||
|
setTree(tree) {},
|
||||||
|
getImageSrc(row, column) {
|
||||||
|
if (column.element.getAttribute("id") !== "siteCol") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const signon = GetVisibleLogins()[row];
|
||||||
|
|
||||||
|
return PlacesUtils.urlWithSizeRef(window, "page-icon:" + signon.origin, 16);
|
||||||
|
},
|
||||||
|
getCellValue(row, column) {},
|
||||||
|
getCellText(row, column) {
|
||||||
|
let time;
|
||||||
|
let signon = GetVisibleLogins()[row];
|
||||||
|
switch (column.id) {
|
||||||
|
case "siteCol":
|
||||||
|
return signon.httpRealm
|
||||||
|
? signon.origin + " (" + signon.httpRealm + ")"
|
||||||
|
: signon.origin;
|
||||||
|
case "userCol":
|
||||||
|
return signon.username || "";
|
||||||
|
case "passwordCol":
|
||||||
|
return signon.password || "";
|
||||||
|
case "timeCreatedCol":
|
||||||
|
time = new Date(signon.timeCreated);
|
||||||
|
return dateFormatter.format(time);
|
||||||
|
case "timeLastUsedCol":
|
||||||
|
time = new Date(signon.timeLastUsed);
|
||||||
|
return dateAndTimeFormatter.format(time);
|
||||||
|
case "timePasswordChangedCol":
|
||||||
|
time = new Date(signon.timePasswordChanged);
|
||||||
|
return dateFormatter.format(time);
|
||||||
|
case "timesUsedCol":
|
||||||
|
return signon.timesUsed;
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isEditable(row, col) {
|
||||||
|
if (col.id == "userCol" || col.id == "passwordCol") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isSeparator(index) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isSorted() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isContainer(index) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
cycleHeader(column) {},
|
||||||
|
getRowProperties(row) {
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
getColumnProperties(column) {
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
getCellProperties(row, column) {
|
||||||
|
if (column.element.getAttribute("id") == "siteCol") {
|
||||||
|
return "ltr";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
setCellText(row, col, value) {
|
||||||
|
let table = GetVisibleLogins();
|
||||||
|
function _editLogin(field) {
|
||||||
|
if (value == table[row][field]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let existingLogin = table[row].clone();
|
||||||
|
table[row][field] = value;
|
||||||
|
table[row].timePasswordChanged = Date.now();
|
||||||
|
Services.logins.modifyLogin(existingLogin, table[row]);
|
||||||
|
signonsTree.invalidateRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (col.id == "userCol") {
|
||||||
|
_editLogin("username");
|
||||||
|
} else if (col.id == "passwordCol") {
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_editLogin("password");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function SortTree(column, ascending) {
|
||||||
|
let table = GetVisibleLogins();
|
||||||
|
// remember which item was selected so we can restore it after the sort
|
||||||
|
let selections = GetTreeSelections();
|
||||||
|
let selectedNumber = selections.length ? table[selections[0]].number : -1;
|
||||||
|
function compareFunc(a, b) {
|
||||||
|
let valA, valB;
|
||||||
|
switch (column) {
|
||||||
|
case "origin":
|
||||||
|
let realmA = a.httpRealm;
|
||||||
|
let realmB = b.httpRealm;
|
||||||
|
realmA = realmA == null ? "" : realmA.toLowerCase();
|
||||||
|
realmB = realmB == null ? "" : realmB.toLowerCase();
|
||||||
|
|
||||||
|
valA = a[column].toLowerCase() + realmA;
|
||||||
|
valB = b[column].toLowerCase() + realmB;
|
||||||
|
break;
|
||||||
|
case "username":
|
||||||
|
case "password":
|
||||||
|
valA = a[column].toLowerCase();
|
||||||
|
valB = b[column].toLowerCase();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
valA = a[column];
|
||||||
|
valB = b[column];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valA < valB) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (valA > valB) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// do the sort
|
||||||
|
table.sort(compareFunc);
|
||||||
|
if (!ascending) {
|
||||||
|
table.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore the selection
|
||||||
|
let selectedRow = -1;
|
||||||
|
if (selectedNumber >= 0 && false) {
|
||||||
|
for (let s = 0; s < table.length; s++) {
|
||||||
|
if (table[s].number == selectedNumber) {
|
||||||
|
// update selection
|
||||||
|
// note: we need to deselect before reselecting in order to trigger ...Selected()
|
||||||
|
signonsTree.view.selection.select(-1);
|
||||||
|
signonsTree.view.selection.select(s);
|
||||||
|
selectedRow = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// display the results
|
||||||
|
signonsTree.invalidate();
|
||||||
|
if (selectedRow >= 0) {
|
||||||
|
signonsTree.ensureRowIsVisible(selectedRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function LoadSignons() {
|
||||||
|
// loads signons into table
|
||||||
|
try {
|
||||||
|
signons = Services.logins.getAllLogins();
|
||||||
|
} catch (e) {
|
||||||
|
signons = [];
|
||||||
|
}
|
||||||
|
signons.forEach(login => login.QueryInterface(Ci.nsILoginMetaInfo));
|
||||||
|
signonsTreeView.rowCount = signons.length;
|
||||||
|
|
||||||
|
// sort and display the table
|
||||||
|
signonsTree.view = signonsTreeView;
|
||||||
|
// The sort column didn't change. SortTree (called by
|
||||||
|
// SignonColumnSort) assumes we want to toggle the sort
|
||||||
|
// direction but here we don't so we have to trick it
|
||||||
|
lastSignonSortAscending = !lastSignonSortAscending;
|
||||||
|
SignonColumnSort(lastSignonSortColumn);
|
||||||
|
|
||||||
|
// disable "remove all signons" button if there are no signons
|
||||||
|
if (!signons.length) {
|
||||||
|
removeAllButton.setAttribute("disabled", "true");
|
||||||
|
togglePasswordsButton.setAttribute("disabled", "true");
|
||||||
|
} else {
|
||||||
|
removeAllButton.removeAttribute("disabled");
|
||||||
|
togglePasswordsButton.removeAttribute("disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetVisibleLogins() {
|
||||||
|
return signonsTreeView._filterSet.length
|
||||||
|
? signonsTreeView._filterSet
|
||||||
|
: signons;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetTreeSelections() {
|
||||||
|
let selections = [];
|
||||||
|
let select = signonsTree.view.selection;
|
||||||
|
if (select) {
|
||||||
|
let count = select.getRangeCount();
|
||||||
|
let min = {};
|
||||||
|
let max = {};
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
select.getRangeAt(i, min, max);
|
||||||
|
for (let k = min.value; k <= max.value; k++) {
|
||||||
|
if (k != -1) {
|
||||||
|
selections[selections.length] = k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selections;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SignonSelected() {
|
||||||
|
let selections = GetTreeSelections();
|
||||||
|
if (selections.length) {
|
||||||
|
removeButton.removeAttribute("disabled");
|
||||||
|
} else {
|
||||||
|
removeButton.setAttribute("disabled", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function DeleteSignon() {
|
||||||
|
let syncNeeded = !!signonsTreeView._filterSet.length;
|
||||||
|
let tree = signonsTree;
|
||||||
|
let view = signonsTreeView;
|
||||||
|
let table = GetVisibleLogins();
|
||||||
|
|
||||||
|
// Turn off tree selection notifications during the deletion
|
||||||
|
tree.view.selection.selectEventsSuppressed = true;
|
||||||
|
|
||||||
|
// remove selected items from list (by setting them to null) and place in deleted list
|
||||||
|
let selections = GetTreeSelections();
|
||||||
|
for (let s = selections.length - 1; s >= 0; s--) {
|
||||||
|
let i = selections[s];
|
||||||
|
deletedSignons.push(table[i]);
|
||||||
|
table[i] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// collapse list by removing all the null entries
|
||||||
|
for (let j = 0; j < table.length; j++) {
|
||||||
|
if (table[j] == null) {
|
||||||
|
let k = j;
|
||||||
|
while (k < table.length && table[k] == null) {
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
table.splice(j, k - j);
|
||||||
|
view.rowCount -= k - j;
|
||||||
|
tree.rowCountChanged(j, j - k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update selection and/or buttons
|
||||||
|
if (table.length) {
|
||||||
|
// update selection
|
||||||
|
let nextSelection =
|
||||||
|
selections[0] < table.length ? selections[0] : table.length - 1;
|
||||||
|
tree.view.selection.select(nextSelection);
|
||||||
|
} else {
|
||||||
|
// disable buttons
|
||||||
|
removeButton.setAttribute("disabled", "true");
|
||||||
|
removeAllButton.setAttribute("disabled", "true");
|
||||||
|
}
|
||||||
|
tree.view.selection.selectEventsSuppressed = false;
|
||||||
|
FinalizeSignonDeletions(syncNeeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function DeleteAllSignons() {
|
||||||
|
// Confirm the user wants to remove all passwords
|
||||||
|
let dummy = { value: false };
|
||||||
|
if (
|
||||||
|
Services.prompt.confirmEx(
|
||||||
|
window,
|
||||||
|
"Remove all passwords",
|
||||||
|
"Are you sure you wish to remove all passwords?",
|
||||||
|
Services.prompt.STD_YES_NO_BUTTONS + Services.prompt.BUTTON_POS_1_DEFAULT,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
dummy
|
||||||
|
) == 1
|
||||||
|
) {
|
||||||
|
// 1 == "No" button
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let syncNeeded = !!signonsTreeView._filterSet.length;
|
||||||
|
let view = signonsTreeView;
|
||||||
|
let table = GetVisibleLogins();
|
||||||
|
|
||||||
|
// remove all items from table and place in deleted table
|
||||||
|
for (let i = 0; i < table.length; i++) {
|
||||||
|
deletedSignons.push(table[i]);
|
||||||
|
}
|
||||||
|
table.length = 0;
|
||||||
|
|
||||||
|
// clear out selections
|
||||||
|
view.selection.select(-1);
|
||||||
|
|
||||||
|
// update the tree view and notify the tree
|
||||||
|
view.rowCount = 0;
|
||||||
|
|
||||||
|
signonsTree.rowCountChanged(0, -deletedSignons.length);
|
||||||
|
signonsTree.invalidate();
|
||||||
|
|
||||||
|
// disable buttons
|
||||||
|
removeButton.setAttribute("disabled", "true");
|
||||||
|
removeAllButton.setAttribute("disabled", "true");
|
||||||
|
FinalizeSignonDeletions(syncNeeded);
|
||||||
|
Services.telemetry.getHistogramById("PWMGR_MANAGE_DELETED_ALL").add(1);
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
null,
|
||||||
|
"weave:telemetry:histogram",
|
||||||
|
"PWMGR_MANAGE_DELETED_ALL"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function TogglePasswordVisible() {
|
||||||
|
if (showingPasswords || (await masterPasswordLogin(AskUserShowPasswords))) {
|
||||||
|
showingPasswords = !showingPasswords;
|
||||||
|
togglePasswordsButton.label = showingPasswords ? "Hide Passwords" : "Show Passwords";
|
||||||
|
togglePasswordsButton.accessKey = "P";
|
||||||
|
document.getElementById("passwordCol").hidden = !showingPasswords;
|
||||||
|
FilterPasswords();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify observers that the password visibility toggling is
|
||||||
|
// completed. (Mostly useful for tests)
|
||||||
|
Services.obs.notifyObservers(null, "passwordmgr-password-toggle-complete");
|
||||||
|
Services.telemetry
|
||||||
|
.getHistogramById("PWMGR_MANAGE_VISIBILITY_TOGGLED")
|
||||||
|
.add(showingPasswords);
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
null,
|
||||||
|
"weave:telemetry:histogram",
|
||||||
|
"PWMGR_MANAGE_VISIBILITY_TOGGLED"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function AskUserShowPasswords() {
|
||||||
|
let dummy = { value: false };
|
||||||
|
|
||||||
|
// Confirm the user wants to display passwords
|
||||||
|
return (
|
||||||
|
Services.prompt.confirmEx(
|
||||||
|
window,
|
||||||
|
null,
|
||||||
|
"Are you sure you wish to show your passwords?",
|
||||||
|
Services.prompt.STD_YES_NO_BUTTONS,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
dummy
|
||||||
|
) == 0
|
||||||
|
); // 0=="Yes" button
|
||||||
|
}
|
||||||
|
|
||||||
|
function FinalizeSignonDeletions(syncNeeded) {
|
||||||
|
for (let s = 0; s < deletedSignons.length; s++) {
|
||||||
|
Services.logins.removeLogin(deletedSignons[s]);
|
||||||
|
Services.telemetry.getHistogramById("PWMGR_MANAGE_DELETED").add(1);
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
null,
|
||||||
|
"weave:telemetry:histogram",
|
||||||
|
"PWMGR_MANAGE_DELETED"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// If the deletion has been performed in a filtered view, reflect the deletion in the unfiltered table.
|
||||||
|
// See bug 405389.
|
||||||
|
if (syncNeeded) {
|
||||||
|
try {
|
||||||
|
signons = Services.logins.getAllLogins();
|
||||||
|
} catch (e) {
|
||||||
|
signons = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deletedSignons.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function HandleSignonKeyPress(e) {
|
||||||
|
// If editing is currently performed, don't do anything.
|
||||||
|
if (signonsTree.getAttribute("editing")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
e.keyCode == KeyboardEvent.DOM_VK_DELETE ||
|
||||||
|
(AppConstants.platform == "macosx" &&
|
||||||
|
e.keyCode == KeyboardEvent.DOM_VK_BACK_SPACE)
|
||||||
|
) {
|
||||||
|
DeleteSignon();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColumnByName(column) {
|
||||||
|
switch (column) {
|
||||||
|
case "origin":
|
||||||
|
return document.getElementById("siteCol");
|
||||||
|
case "username":
|
||||||
|
return document.getElementById("userCol");
|
||||||
|
case "password":
|
||||||
|
return document.getElementById("passwordCol");
|
||||||
|
case "timeCreated":
|
||||||
|
return document.getElementById("timeCreatedCol");
|
||||||
|
case "timeLastUsed":
|
||||||
|
return document.getElementById("timeLastUsedCol");
|
||||||
|
case "timePasswordChanged":
|
||||||
|
return document.getElementById("timePasswordChangedCol");
|
||||||
|
case "timesUsed":
|
||||||
|
return document.getElementById("timesUsedCol");
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SignonColumnSort(column) {
|
||||||
|
let sortedCol = getColumnByName(column);
|
||||||
|
let lastSortedCol = getColumnByName(lastSignonSortColumn);
|
||||||
|
|
||||||
|
// clear out the sortDirection attribute on the old column
|
||||||
|
lastSortedCol.removeAttribute("sortDirection");
|
||||||
|
|
||||||
|
// determine if sort is to be ascending or descending
|
||||||
|
lastSignonSortAscending =
|
||||||
|
column == lastSignonSortColumn ? !lastSignonSortAscending : true;
|
||||||
|
|
||||||
|
// sort
|
||||||
|
lastSignonSortColumn = column;
|
||||||
|
SortTree(lastSignonSortColumn, lastSignonSortAscending);
|
||||||
|
|
||||||
|
// set the sortDirection attribute to get the styling going
|
||||||
|
// first we need to get the right element
|
||||||
|
sortedCol.setAttribute(
|
||||||
|
"sortDirection",
|
||||||
|
lastSignonSortAscending ? "ascending" : "descending"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SignonClearFilter() {
|
||||||
|
let singleSelection = signonsTreeView.selection.count == 1;
|
||||||
|
|
||||||
|
// Clear the Tree Display
|
||||||
|
signonsTreeView.rowCount = 0;
|
||||||
|
signonsTree.rowCountChanged(0, -signonsTreeView._filterSet.length);
|
||||||
|
signonsTreeView._filterSet = [];
|
||||||
|
|
||||||
|
// Just reload the list to make sure deletions are respected
|
||||||
|
LoadSignons();
|
||||||
|
|
||||||
|
// Restore selection
|
||||||
|
if (singleSelection) {
|
||||||
|
signonsTreeView.selection.clearSelection();
|
||||||
|
for (let i = 0; i < signonsTreeView._lastSelectedRanges.length; ++i) {
|
||||||
|
let range = signonsTreeView._lastSelectedRanges[i];
|
||||||
|
signonsTreeView.selection.rangedSelect(range.min, range.max, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
signonsTreeView.selection.select(0);
|
||||||
|
}
|
||||||
|
signonsTreeView._lastSelectedRanges = [];
|
||||||
|
|
||||||
|
signonsIntro.textContent = "Logins for the following sites are stored on your computer";
|
||||||
|
removeAllButton.label = "Remove All";
|
||||||
|
removeAllButton.accessKey = "A";
|
||||||
|
}
|
||||||
|
|
||||||
|
function FocusFilterBox() {
|
||||||
|
if (filterField.getAttribute("focused") != "true") {
|
||||||
|
filterField.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function SignonMatchesFilter(aSignon, aFilterValue) {
|
||||||
|
if (aSignon.origin.toLowerCase().includes(aFilterValue)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
aSignon.username &&
|
||||||
|
aSignon.username.toLowerCase().includes(aFilterValue)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
aSignon.httpRealm &&
|
||||||
|
aSignon.httpRealm.toLowerCase().includes(aFilterValue)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
showingPasswords &&
|
||||||
|
aSignon.password &&
|
||||||
|
aSignon.password.toLowerCase().includes(aFilterValue)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _filterPasswords(aFilterValue, view) {
|
||||||
|
aFilterValue = aFilterValue.toLowerCase();
|
||||||
|
return signons.filter(s => SignonMatchesFilter(s, aFilterValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
function SignonSaveState() {
|
||||||
|
// Save selection
|
||||||
|
let seln = signonsTreeView.selection;
|
||||||
|
signonsTreeView._lastSelectedRanges = [];
|
||||||
|
let rangeCount = seln.getRangeCount();
|
||||||
|
for (let i = 0; i < rangeCount; ++i) {
|
||||||
|
let min = {};
|
||||||
|
let max = {};
|
||||||
|
seln.getRangeAt(i, min, max);
|
||||||
|
signonsTreeView._lastSelectedRanges.push({
|
||||||
|
min: min.value,
|
||||||
|
max: max.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function FilterPasswords() {
|
||||||
|
if (filterField.value == "") {
|
||||||
|
SignonClearFilter();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newFilterSet = _filterPasswords(filterField.value, signonsTreeView);
|
||||||
|
if (!signonsTreeView._filterSet.length) {
|
||||||
|
// Save Display Info for the Non-Filtered mode when we first
|
||||||
|
// enter Filtered mode.
|
||||||
|
SignonSaveState();
|
||||||
|
}
|
||||||
|
signonsTreeView._filterSet = newFilterSet;
|
||||||
|
|
||||||
|
// Clear the display
|
||||||
|
let oldRowCount = signonsTreeView.rowCount;
|
||||||
|
signonsTreeView.rowCount = 0;
|
||||||
|
signonsTree.rowCountChanged(0, -oldRowCount);
|
||||||
|
// Set up the filtered display
|
||||||
|
signonsTreeView.rowCount = signonsTreeView._filterSet.length;
|
||||||
|
signonsTree.rowCountChanged(0, signonsTreeView.rowCount);
|
||||||
|
|
||||||
|
// if the view is not empty then select the first item
|
||||||
|
if (signonsTreeView.rowCount > 0) {
|
||||||
|
signonsTreeView.selection.select(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
signonsIntro.textContent = "The following logins match your search:";
|
||||||
|
removeAllButton.label = "Remove All Shown";
|
||||||
|
removeAllButton.accessKey = "A";
|
||||||
|
}
|
||||||
|
|
||||||
|
function CopySiteUrl() {
|
||||||
|
// Copy selected site url to clipboard
|
||||||
|
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
|
||||||
|
Ci.nsIClipboardHelper
|
||||||
|
);
|
||||||
|
let row = signonsTree.currentIndex;
|
||||||
|
let url = signonsTreeView.getCellText(row, { id: "siteCol" });
|
||||||
|
clipboard.copyString(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function CopyPassword() {
|
||||||
|
// Don't copy passwords if we aren't already showing the passwords & a master
|
||||||
|
// password hasn't been entered.
|
||||||
|
if (!showingPasswords && !(await masterPasswordLogin())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Copy selected signon's password to clipboard
|
||||||
|
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
|
||||||
|
Ci.nsIClipboardHelper
|
||||||
|
);
|
||||||
|
let row = signonsTree.currentIndex;
|
||||||
|
let password = signonsTreeView.getCellText(row, { id: "passwordCol" });
|
||||||
|
clipboard.copyString(password);
|
||||||
|
Services.telemetry.getHistogramById("PWMGR_MANAGE_COPIED_PASSWORD").add(1);
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
null,
|
||||||
|
"weave:telemetry:histogram",
|
||||||
|
"PWMGR_MANAGE_COPIED_PASSWORD"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CopyUsername() {
|
||||||
|
// Copy selected signon's username to clipboard
|
||||||
|
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
|
||||||
|
Ci.nsIClipboardHelper
|
||||||
|
);
|
||||||
|
let row = signonsTree.currentIndex;
|
||||||
|
let username = signonsTreeView.getCellText(row, { id: "userCol" });
|
||||||
|
clipboard.copyString(username);
|
||||||
|
Services.telemetry.getHistogramById("PWMGR_MANAGE_COPIED_USERNAME").add(1);
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
null,
|
||||||
|
"weave:telemetry:histogram",
|
||||||
|
"PWMGR_MANAGE_COPIED_USERNAME"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditCellInSelectedRow(columnName) {
|
||||||
|
let row = signonsTree.currentIndex;
|
||||||
|
let columnElement = getColumnByName(columnName);
|
||||||
|
signonsTree.startEditing(
|
||||||
|
row,
|
||||||
|
signonsTree.columns.getColumnFor(columnElement)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LaunchSiteUrl() {
|
||||||
|
let row = signonsTree.currentIndex;
|
||||||
|
let url = signonsTreeView.getCellText(row, { id: "siteCol" });
|
||||||
|
window.openWebLinkIn(url, "tab");
|
||||||
|
}
|
||||||
|
|
||||||
|
function UpdateContextMenu() {
|
||||||
|
let singleSelection = signonsTreeView.selection.count == 1;
|
||||||
|
let menuItems = new Map();
|
||||||
|
let menupopup = document.getElementById("signonsTreeContextMenu");
|
||||||
|
for (let menuItem of menupopup.querySelectorAll("menuitem")) {
|
||||||
|
menuItems.set(menuItem.id, menuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!singleSelection) {
|
||||||
|
for (let menuItem of menuItems.values()) {
|
||||||
|
menuItem.setAttribute("disabled", "true");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedRow = signonsTree.currentIndex;
|
||||||
|
|
||||||
|
// Don't display "Launch Site URL" if we're not a browser.
|
||||||
|
if (window.openWebLinkIn) {
|
||||||
|
menuItems.get("context-launchsiteurl").removeAttribute("disabled");
|
||||||
|
} else {
|
||||||
|
menuItems.get("context-launchsiteurl").setAttribute("disabled", "true");
|
||||||
|
menuItems.get("context-launchsiteurl").setAttribute("hidden", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable "Copy Username" if the username is empty.
|
||||||
|
if (signonsTreeView.getCellText(selectedRow, { id: "userCol" }) != "") {
|
||||||
|
menuItems.get("context-copyusername").removeAttribute("disabled");
|
||||||
|
} else {
|
||||||
|
menuItems.get("context-copyusername").setAttribute("disabled", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItems.get("context-copysiteurl").removeAttribute("disabled");
|
||||||
|
menuItems.get("context-editusername").removeAttribute("disabled");
|
||||||
|
menuItems.get("context-copypassword").removeAttribute("disabled");
|
||||||
|
|
||||||
|
// Disable "Edit Password" if the password column isn't showing.
|
||||||
|
if (!document.getElementById("passwordCol").hidden) {
|
||||||
|
menuItems.get("context-editpassword").removeAttribute("disabled");
|
||||||
|
} else {
|
||||||
|
menuItems.get("context-editpassword").setAttribute("disabled", "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function masterPasswordLogin(noPasswordCallback) {
|
||||||
|
// This does no harm if master password isn't set.
|
||||||
|
let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].createInstance(
|
||||||
|
Ci.nsIPK11TokenDB
|
||||||
|
);
|
||||||
|
let token = tokendb.getInternalKeyToken();
|
||||||
|
|
||||||
|
// If there is no master password, still give the user a chance to opt-out of displaying passwords
|
||||||
|
if (token.checkPassword("")) {
|
||||||
|
return noPasswordCallback ? noPasswordCallback() : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// So there's a master password. But since checkPassword didn't succeed, we're logged out (per nsIPK11Token.idl).
|
||||||
|
try {
|
||||||
|
// Relogin and ask for the master password.
|
||||||
|
token.login(true); // 'true' means always prompt for token password. User will be prompted until
|
||||||
|
// clicking 'Cancel' or entering the correct password.
|
||||||
|
} catch (e) {
|
||||||
|
// An exception will be thrown if the user cancels the login prompt dialog.
|
||||||
|
// User is also logged out of Software Security Device.
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.isLoggedIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeKeyHandler() {
|
||||||
|
// If editing is currently performed, don't do anything.
|
||||||
|
if (signonsTree.getAttribute("editing")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function OpenMigrator() {
|
||||||
|
const { MigrationUtils } = ChromeUtils.import(
|
||||||
|
"resource:///modules/MigrationUtils.jsm"
|
||||||
|
);
|
||||||
|
// We pass in the type of source we're using for use in telemetry:
|
||||||
|
MigrationUtils.showMigrationWizard(window, [
|
||||||
|
MigrationUtils.MIGRATION_ENTRYPOINT_PASSWORDS,
|
||||||
|
]);
|
||||||
|
}
|
135
chrome/utils/passwordmgr/passwordManager.xhtml
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
<?xml version="1.0"?> <!-- -*- Mode: SGML; indent-tabs-mode: nil -*- -->
|
||||||
|
|
||||||
|
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||||
|
<?xml-stylesheet href="chrome://global/skin/passwordmgr.css" type="text/css"?>
|
||||||
|
|
||||||
|
<window id="SignonViewerDialog"
|
||||||
|
windowtype="Toolkit:PasswordManager"
|
||||||
|
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||||
|
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||||
|
onload="Startup();"
|
||||||
|
onunload="Shutdown();"
|
||||||
|
title="Saved Logins"
|
||||||
|
style="min-width: 45em;"
|
||||||
|
persist="width height screenX screenY">
|
||||||
|
|
||||||
|
<script src="chrome://browser/content/utilityOverlay.js"/>
|
||||||
|
<script src="chrome://userchromejs/content/passwordmgr/passwordManager.js"/>
|
||||||
|
|
||||||
|
<keyset>
|
||||||
|
<key keycode="VK_ESCAPE" oncommand="escapeKeyHandler();"/>
|
||||||
|
<key key="w" modifiers="accel" oncommand="escapeKeyHandler();"/>
|
||||||
|
<key key="f" modifiers="accel" oncommand="FocusFilterBox();"/>
|
||||||
|
<key key="k" modifiers="accel" oncommand="FocusFilterBox();"/>
|
||||||
|
</keyset>
|
||||||
|
|
||||||
|
<popupset id="signonsTreeContextSet">
|
||||||
|
<menupopup id="signonsTreeContextMenu"
|
||||||
|
onpopupshowing="UpdateContextMenu()">
|
||||||
|
<menuitem id="context-copysiteurl"
|
||||||
|
label="Copy URL"
|
||||||
|
accesskey="y"
|
||||||
|
oncommand="CopySiteUrl()"/>
|
||||||
|
<menuitem id="context-launchsiteurl"
|
||||||
|
label="Visit URL"
|
||||||
|
accesskey="V"
|
||||||
|
oncommand="LaunchSiteUrl()"/>
|
||||||
|
<menuseparator/>
|
||||||
|
<menuitem id="context-copyusername"
|
||||||
|
label="Copy Username"
|
||||||
|
accesskey="U"
|
||||||
|
oncommand="CopyUsername()"/>
|
||||||
|
<menuitem id="context-editusername"
|
||||||
|
label="Edit Username"
|
||||||
|
accesskey="d"
|
||||||
|
oncommand="EditCellInSelectedRow('username')"/>
|
||||||
|
<menuseparator/>
|
||||||
|
<menuitem id="context-copypassword"
|
||||||
|
label="Copy Password"
|
||||||
|
accesskey="C"
|
||||||
|
oncommand="CopyPassword()"/>
|
||||||
|
<menuitem id="context-editpassword"
|
||||||
|
label="Edit Password"
|
||||||
|
accesskey="E"
|
||||||
|
oncommand="EditCellInSelectedRow('password')"/>
|
||||||
|
</menupopup>
|
||||||
|
</popupset>
|
||||||
|
|
||||||
|
<!-- saved signons -->
|
||||||
|
<vbox id="savedsignons" class="contentPane" flex="1">
|
||||||
|
<!-- filter -->
|
||||||
|
<hbox align="center">
|
||||||
|
<search-textbox id="filter" flex="1"
|
||||||
|
aria-controls="signonsTree"
|
||||||
|
oncommand="FilterPasswords();"
|
||||||
|
accesskey="S"
|
||||||
|
placeholder="Search"/>
|
||||||
|
</hbox>
|
||||||
|
|
||||||
|
<label control="signonsTree" id="signonsIntro"/>
|
||||||
|
<separator class="thin"/>
|
||||||
|
<tree id="signonsTree" flex="1"
|
||||||
|
width="750"
|
||||||
|
style="height: 20em;"
|
||||||
|
onkeypress="HandleSignonKeyPress(event)"
|
||||||
|
onselect="SignonSelected();"
|
||||||
|
editable="true"
|
||||||
|
context="signonsTreeContextMenu">
|
||||||
|
<treecols>
|
||||||
|
<treecol id="siteCol" label="Site" style="-moz-box-flex: 40"
|
||||||
|
data-field-name="origin" persist="width"
|
||||||
|
ignoreincolumnpicker="true"
|
||||||
|
sortDirection="ascending"/>
|
||||||
|
<splitter class="tree-splitter"/>
|
||||||
|
<treecol id="userCol" label="Username" style="-moz-box-flex: 25"
|
||||||
|
ignoreincolumnpicker="true"
|
||||||
|
data-field-name="username" persist="width"/>
|
||||||
|
<splitter class="tree-splitter"/>
|
||||||
|
<treecol id="passwordCol" label="Password" style="-moz-box-flex: 15"
|
||||||
|
ignoreincolumnpicker="true"
|
||||||
|
data-field-name="password" persist="width"
|
||||||
|
hidden="true"/>
|
||||||
|
<splitter class="tree-splitter"/>
|
||||||
|
<treecol id="timeCreatedCol" label="First Used" style="-moz-box-flex: 10"
|
||||||
|
data-field-name="timeCreated" persist="width hidden"
|
||||||
|
hidden="true"/>
|
||||||
|
<splitter class="tree-splitter"/>
|
||||||
|
<treecol id="timeLastUsedCol" label="Last Used" style="-moz-box-flex: 20"
|
||||||
|
data-field-name="timeLastUsed" persist="width hidden"
|
||||||
|
hidden="true"/>
|
||||||
|
<splitter class="tree-splitter"/>
|
||||||
|
<treecol id="timePasswordChangedCol" label="Last Changed" style="-moz-box-flex: 10"
|
||||||
|
data-field-name="timePasswordChanged" persist="width hidden"/>
|
||||||
|
<splitter class="tree-splitter"/>
|
||||||
|
<treecol id="timesUsedCol" label="Times Used" flex="1"
|
||||||
|
data-field-name="timesUsed" persist="width hidden"
|
||||||
|
hidden="true"/>
|
||||||
|
<splitter class="tree-splitter"/>
|
||||||
|
</treecols>
|
||||||
|
<treechildren/>
|
||||||
|
</tree>
|
||||||
|
<separator class="thin"/>
|
||||||
|
<hbox id="SignonViewerButtons">
|
||||||
|
<button id="removeSignon" disabled="true"
|
||||||
|
label="Remove"
|
||||||
|
accesskey="R"
|
||||||
|
oncommand="DeleteSignon();"/>
|
||||||
|
<button id="removeAllSignons"
|
||||||
|
oncommand="DeleteAllSignons();"/>
|
||||||
|
<spacer flex="1"/>
|
||||||
|
<button label="Import"
|
||||||
|
accesskey="I"
|
||||||
|
oncommand="OpenMigrator();"/>
|
||||||
|
<button id="togglePasswords"
|
||||||
|
oncommand="TogglePasswordVisible();"/>
|
||||||
|
</hbox>
|
||||||
|
</vbox>
|
||||||
|
<hbox align="end">
|
||||||
|
<hbox class="actionButtons">
|
||||||
|
<spacer flex="1"/>
|
||||||
|
<button oncommand="window.close();"
|
||||||
|
label="Close"
|
||||||
|
accesskey="C"/>
|
||||||
|
</hbox>
|
||||||
|
</hbox>
|
||||||
|
</window>
|
22
chrome/utils/passwordmgr/passwordmgr.css
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||||
|
|
||||||
|
.contentPane {
|
||||||
|
margin: 9px 8px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionButtons {
|
||||||
|
margin: 0 3px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
treechildren::-moz-tree-image(siteCol) {
|
||||||
|
list-style-image: url(chrome://mozapps/skin/places/defaultFavicon.svg);
|
||||||
|
-moz-context-properties: fill;
|
||||||
|
fill: currentColor;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin-inline-end: 5px;
|
||||||
|
}
|
BIN
chrome/utils/styloaix/16.png
Normal file
After Width: | Height: | Size: 661 B |
BIN
chrome/utils/styloaix/16w.png
Normal file
After Width: | Height: | Size: 589 B |
320
chrome/utils/styloaix/autocomplete.js
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
/* 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";
|
||||||
|
|
||||||
|
const AutocompletePopup = require('devtools/client/shared/autocomplete-popup');
|
||||||
|
|
||||||
|
let loader;
|
||||||
|
try {
|
||||||
|
({ loader } = ChromeUtils.import('resource://devtools/shared/loader/Loader.jsm'));
|
||||||
|
} catch (e) {
|
||||||
|
// tb91
|
||||||
|
({ loader } = ChromeUtils.import('resource://devtools/shared/Loader.jsm'));
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.lazyRequireGetter(
|
||||||
|
this,
|
||||||
|
"KeyCodes",
|
||||||
|
"devtools/client/shared/keycodes",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
loader.lazyRequireGetter(
|
||||||
|
this,
|
||||||
|
"CSSCompleter",
|
||||||
|
"devtools/client/shared/sourceeditor/css-autocompleter"
|
||||||
|
);
|
||||||
|
|
||||||
|
const autocompleteMap = new WeakMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares an editor instance for autocompletion.
|
||||||
|
*/
|
||||||
|
function initializeAutoCompletion(ctx, options = {}) {
|
||||||
|
const { cm, ed, Editor } = ctx;
|
||||||
|
if (autocompleteMap.has(ed)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const win = ed.container.contentWindow.wrappedJSObject;
|
||||||
|
const { CodeMirror } = win;
|
||||||
|
|
||||||
|
let completer = null;
|
||||||
|
const autocompleteKey =
|
||||||
|
"Ctrl-" + Editor.keyFor("autocompletion", { noaccel: true });
|
||||||
|
if (ed.config.mode == Editor.modes.css) {
|
||||||
|
completer = new CSSCompleter({
|
||||||
|
walker: options.walker,
|
||||||
|
cssProperties: options.cssProperties,
|
||||||
|
maxEntries: 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertSelectedPopupItem() {
|
||||||
|
const autocompleteState = autocompleteMap.get(ed);
|
||||||
|
if (!popup || !popup.isOpen || !autocompleteState) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!autocompleteState.suggestionInsertedOnce && popup.selectedItem) {
|
||||||
|
autocompleteMap.get(ed).insertingSuggestion = true;
|
||||||
|
insertPopupItem(ed, popup.selectedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
popup.once("popup-closed", () => {
|
||||||
|
// This event is used in tests.
|
||||||
|
ed.emit("popup-hidden");
|
||||||
|
});
|
||||||
|
popup.hidePopup();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give each popup a new name to avoid sharing the elements.
|
||||||
|
|
||||||
|
let popup = new AutocompletePopup(win.parent.document, {
|
||||||
|
position: "bottom",
|
||||||
|
autoSelect: true,
|
||||||
|
onClick: insertSelectedPopupItem,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cycle = reverse => {
|
||||||
|
if (popup?.isOpen) {
|
||||||
|
// eslint-disable-next-line mozilla/no-compare-against-boolean-literals
|
||||||
|
cycleSuggestions(ed, reverse == true);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CodeMirror.Pass;
|
||||||
|
};
|
||||||
|
|
||||||
|
let keyMap = {
|
||||||
|
Tab: cycle,
|
||||||
|
Down: cycle,
|
||||||
|
"Shift-Tab": cycle.bind(null, true),
|
||||||
|
Up: cycle.bind(null, true),
|
||||||
|
Enter: () => {
|
||||||
|
const wasHandled = insertSelectedPopupItem();
|
||||||
|
return wasHandled ? true : CodeMirror.Pass;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const autoCompleteCallback = autoComplete.bind(null, ctx);
|
||||||
|
const keypressCallback = onEditorKeypress.bind(null, ctx);
|
||||||
|
keyMap[autocompleteKey] = autoCompleteCallback;
|
||||||
|
cm.addKeyMap(keyMap);
|
||||||
|
|
||||||
|
cm.on("keydown", keypressCallback);
|
||||||
|
ed.on("change", autoCompleteCallback);
|
||||||
|
ed.on("destroy", destroy);
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
ed.off("destroy", destroy);
|
||||||
|
cm.off("keydown", keypressCallback);
|
||||||
|
ed.off("change", autoCompleteCallback);
|
||||||
|
cm.removeKeyMap(keyMap);
|
||||||
|
popup.destroy();
|
||||||
|
keyMap = popup = completer = null;
|
||||||
|
autocompleteMap.delete(ed);
|
||||||
|
}
|
||||||
|
|
||||||
|
autocompleteMap.set(ed, {
|
||||||
|
popup: popup,
|
||||||
|
completer: completer,
|
||||||
|
keyMap: keyMap,
|
||||||
|
destroy: destroy,
|
||||||
|
insertingSuggestion: false,
|
||||||
|
suggestionInsertedOnce: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides suggestions to autocomplete the current token/word being typed.
|
||||||
|
*/
|
||||||
|
function autoComplete({ ed, cm }) {
|
||||||
|
const autocompleteOpts = autocompleteMap.get(ed);
|
||||||
|
const { completer, popup } = autocompleteOpts;
|
||||||
|
if (
|
||||||
|
!completer ||
|
||||||
|
autocompleteOpts.insertingSuggestion ||
|
||||||
|
autocompleteOpts.doNotAutocomplete
|
||||||
|
) {
|
||||||
|
autocompleteOpts.insertingSuggestion = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cur = ed.getCursor();
|
||||||
|
completer
|
||||||
|
.complete(cm.getRange({ line: 0, ch: 0 }, cur), cur)
|
||||||
|
.then(suggestions => {
|
||||||
|
if (
|
||||||
|
!suggestions ||
|
||||||
|
!suggestions.length ||
|
||||||
|
suggestions[0].preLabel == null
|
||||||
|
) {
|
||||||
|
autocompleteOpts.suggestionInsertedOnce = false;
|
||||||
|
popup.once("popup-closed", () => {
|
||||||
|
// This event is used in tests.
|
||||||
|
ed.emit("after-suggest");
|
||||||
|
});
|
||||||
|
popup.hidePopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The cursor is at the end of the currently entered part of the token,
|
||||||
|
// like "backgr|" but we need to open the popup at the beginning of the
|
||||||
|
// character "b". Thus we need to calculate the width of the entered part
|
||||||
|
// of the token ("backgr" here).
|
||||||
|
|
||||||
|
const cursorElement = cm.display.cursorDiv.querySelector(
|
||||||
|
".CodeMirror-cursor"
|
||||||
|
);
|
||||||
|
const left = suggestions[0].preLabel.length * cm.defaultCharWidth();
|
||||||
|
popup.hidePopup();
|
||||||
|
popup.setItems(suggestions);
|
||||||
|
|
||||||
|
popup.once("popup-opened", () => {
|
||||||
|
// This event is used in tests.
|
||||||
|
ed.emit("after-suggest");
|
||||||
|
});
|
||||||
|
popup.openPopup(cursorElement, -1 * left, 0);
|
||||||
|
autocompleteOpts.suggestionInsertedOnce = false;
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a popup item into the current cursor location
|
||||||
|
* in the editor.
|
||||||
|
*/
|
||||||
|
function insertPopupItem(ed, popupItem) {
|
||||||
|
const { preLabel, text } = popupItem;
|
||||||
|
const cur = ed.getCursor();
|
||||||
|
const textBeforeCursor = ed.getText(cur.line).substring(0, cur.ch);
|
||||||
|
const backwardsTextBeforeCursor = textBeforeCursor
|
||||||
|
.split("")
|
||||||
|
.reverse()
|
||||||
|
.join("");
|
||||||
|
const backwardsPreLabel = preLabel
|
||||||
|
.split("")
|
||||||
|
.reverse()
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
// If there is additional text in the preLabel vs the line, then
|
||||||
|
// just insert the entire autocomplete text. An example:
|
||||||
|
// if you type 'a' and select '#about' from the autocomplete menu,
|
||||||
|
// then the final text needs to the end up as '#about'.
|
||||||
|
if (backwardsPreLabel.indexOf(backwardsTextBeforeCursor) === 0) {
|
||||||
|
ed.replaceText(text, { line: cur.line, ch: 0 }, cur);
|
||||||
|
} else {
|
||||||
|
ed.replaceText(text.slice(preLabel.length), cur, cur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cycles through provided suggestions by the popup in a top to bottom manner
|
||||||
|
* when `reverse` is not true. Opposite otherwise.
|
||||||
|
*/
|
||||||
|
function cycleSuggestions(ed, reverse) {
|
||||||
|
const autocompleteOpts = autocompleteMap.get(ed);
|
||||||
|
const { popup } = autocompleteOpts;
|
||||||
|
const cur = ed.getCursor();
|
||||||
|
autocompleteOpts.insertingSuggestion = true;
|
||||||
|
if (!autocompleteOpts.suggestionInsertedOnce) {
|
||||||
|
autocompleteOpts.suggestionInsertedOnce = true;
|
||||||
|
let firstItem;
|
||||||
|
if (reverse) {
|
||||||
|
firstItem = popup.getItemAtIndex(popup.itemCount - 1);
|
||||||
|
popup.selectPreviousItem();
|
||||||
|
} else {
|
||||||
|
firstItem = popup.getItemAtIndex(0);
|
||||||
|
if (firstItem.label == firstItem.preLabel && popup.itemCount > 1) {
|
||||||
|
firstItem = popup.getItemAtIndex(1);
|
||||||
|
popup.selectNextItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (popup.itemCount == 1) {
|
||||||
|
popup.hidePopup();
|
||||||
|
}
|
||||||
|
insertPopupItem(ed, firstItem);
|
||||||
|
} else {
|
||||||
|
const fromCur = {
|
||||||
|
line: cur.line,
|
||||||
|
ch: cur.ch - popup.selectedItem.text.length,
|
||||||
|
};
|
||||||
|
if (reverse) {
|
||||||
|
popup.selectPreviousItem();
|
||||||
|
} else {
|
||||||
|
popup.selectNextItem();
|
||||||
|
}
|
||||||
|
ed.replaceText(popup.selectedItem.text, fromCur, cur);
|
||||||
|
}
|
||||||
|
// This event is used in tests.
|
||||||
|
ed.emit("suggestion-entered");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onkeydown handler for the editor instance to prevent autocompleting on some
|
||||||
|
* keypresses.
|
||||||
|
*/
|
||||||
|
function onEditorKeypress({ ed, Editor }, cm, event) {
|
||||||
|
const autocompleteOpts = autocompleteMap.get(ed);
|
||||||
|
|
||||||
|
// Do not try to autocomplete with multiple selections.
|
||||||
|
if (ed.hasMultipleSelections()) {
|
||||||
|
autocompleteOpts.doNotAutocomplete = true;
|
||||||
|
autocompleteOpts.popup.hidePopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(event.ctrlKey || event.metaKey) &&
|
||||||
|
event.keyCode == KeyCodes.DOM_VK_SPACE
|
||||||
|
) {
|
||||||
|
// When Ctrl/Cmd + Space is pressed, two simultaneous keypresses are emitted
|
||||||
|
// first one for just the Ctrl/Cmd and second one for combo. The first one
|
||||||
|
// leave the autocompleteOpts.doNotAutocomplete as true, so we have to make
|
||||||
|
// it false
|
||||||
|
autocompleteOpts.doNotAutocomplete = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.ctrlKey || event.metaKey || event.altKey) {
|
||||||
|
autocompleteOpts.doNotAutocomplete = true;
|
||||||
|
autocompleteOpts.popup.hidePopup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event.keyCode) {
|
||||||
|
case KeyCodes.DOM_VK_RETURN:
|
||||||
|
autocompleteOpts.doNotAutocomplete = true;
|
||||||
|
break;
|
||||||
|
case KeyCodes.DOM_VK_ESCAPE:
|
||||||
|
if (autocompleteOpts.popup.isOpen) {
|
||||||
|
// Prevent the Console input to open, but still remove the autocomplete popup.
|
||||||
|
autocompleteOpts.doNotAutocomplete = true;
|
||||||
|
autocompleteOpts.popup.hidePopup();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KeyCodes.DOM_VK_LEFT:
|
||||||
|
case KeyCodes.DOM_VK_RIGHT:
|
||||||
|
case KeyCodes.DOM_VK_HOME:
|
||||||
|
case KeyCodes.DOM_VK_END:
|
||||||
|
autocompleteOpts.doNotAutocomplete = true;
|
||||||
|
autocompleteOpts.popup.hidePopup();
|
||||||
|
break;
|
||||||
|
case KeyCodes.DOM_VK_BACK_SPACE:
|
||||||
|
case KeyCodes.DOM_VK_DELETE:
|
||||||
|
if (ed.config.mode == Editor.modes.css) {
|
||||||
|
autocompleteOpts.completer.invalidateCache(ed.getCursor().line);
|
||||||
|
}
|
||||||
|
autocompleteOpts.doNotAutocomplete = true;
|
||||||
|
autocompleteOpts.popup.hidePopup();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
autocompleteOpts.doNotAutocomplete = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export functions
|
||||||
|
|
||||||
|
exports.initializeAutoCompletion = initializeAutoCompletion;
|