the huge 117+ fixes update

This commit is contained in:
Cynthia Foxwell 2023-12-24 20:34:22 -07:00
parent baf06e964f
commit ccae90fb3e
110 changed files with 7809 additions and 2864 deletions

View file

@ -1,36 +1,64 @@
# MSFX
Fork of [the MSFX Firefox userChrome](https://github.com/WinClassic/MSFX).
![Screenshot of MSFX](.assets/screenshot.png)
## 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
* Bookmarks bar folder menus
* All tabs button
* Replaced with overflow arrows icon
* Scrollbars forced to light theme
* Selected address bar forced to light theme
* Resize grabber no longer appears when maximized
* Autoplay Pending indicator icon replaced
* Pinned tabs no longer have extra padding
* 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 when opening a new window
- Uses normal [firefox-scripts](https://github.com/xiaoxiaoflood/firefox-scripts) instead of a stripped down version for greater userscript compatibility
- Fixed up for 117+ and now bundled with again, as the upstream repo's maintainer is MIA.
- More accurate styles in some places
- Bookmarks bar folder menus
- All tabs button
- Replaced with overflow arrows icon
- Scrollbars forced to light theme
- Selected address bar forced to light theme
- Resize grabber no longer appears when maximized
- Autoplay Pending indicator icon replaced
- Accurate menus (117+)
- 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
* Set the following keys in `about:config`:
* `svg.context-properties.content.enabled` to `true`
* `ui.prefersReducedMotion` to `1`
* `browser.display.windows.non_native_menus` to `0`
* Follow [firefox-scripts](https://github.com/xiaoxiaoflood/firefox-scripts) installation.
* Install [Status Bar userscript](https://raw.githubusercontent.com/xiaoxiaoflood/firefox-scripts/master/chrome/status-bar.uc.js).
* Merge `chrome` folder from the repo into your profile's `chrome` folder.
- Set the following keys in `about:config`:
- `toolkit.legacyUserProfileCustomizations.stylesheets` to `true`
- `svg.context-properties.content.enabled` to `true`
- `ui.prefersReducedMotion` to `1`
- ~~`browser.display.windows.non_native_menus` to `0`~~ (removed in 117+)
- 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
* [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
* [userChromeJS Manager](https://raw.githubusercontent.com/xiaoxiaoflood/firefox-scripts/master/chrome/rebuild_userChrome.uc.js)
* [Save File to](https://raw.githubusercontent.com/xiaoxiaoflood/firefox-scripts/master/extensions/savefileto/savefileto.xpi)
* **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.
- [Extensions Options Menu](https://raw.githubusercontent.com/xiaoxiaoflood/firefox-scripts/master/chrome/extensionOptionsMenu.uc.js)
- More lightweight than the stock extensions menu, which is "hidden" by the theme anyways
- [userChromeJS Manager](https://raw.githubusercontent.com/xiaoxiaoflood/firefox-scripts/master/chrome/rebuild_userChrome.uc.js)

View file

@ -1,10 +1,9 @@
// 'Activity throbber' script for Firefox 60+ by Aris
(function () {
Components.utils.import("resource:///modules/CustomizableUI.jsm");
var { Services } = Components.utils.import(
"resource://gre/modules/Services.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);
@ -27,7 +26,9 @@ var ActivityThrobber = {
.querySelector("#activity_throbber")
.setAttribute("busy", "true");
} else
document.querySelector("#activity_throbber").removeAttribute("busy");
document
.querySelector("#activity_throbber")
.removeAttribute("busy");
}
// create a default toolbar button
@ -80,3 +81,4 @@ var ActivityThrobber = {
};
document.addEventListener("DOMContentLoaded", ActivityThrobber.init(), false);
})();

View file

@ -7,12 +7,11 @@
//
// workaround on Fx 71 to save/restore toolbar visibility
// creating an observer array always fails, so observers are created manually atm.
(function () {
Components.utils.import("resource:///modules/CustomizableUI.jsm");
var { Services } = Components.utils.import(
"resource://gre/modules/Services.jsm",
{}
);
var Services =
globalThis.Services ||
ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
var appversion = parseInt(Services.appinfo.version);
var AdditionalTopToolbars = {
@ -91,21 +90,17 @@ var AdditionalTopToolbars = {
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; \
} \
\
'
`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
@ -125,11 +120,14 @@ var AdditionalTopToolbars = {
);
});
});
observer1.observe(document.querySelector("#additional_top_toolbar1"), {
observer1.observe(
document.querySelector("#additional_top_toolbar1"),
{
attributes: true,
childList: true,
characterData: true,
});
}
);
}
if (number_of_additional_top_toolbars >= 2) {
var observer2 = new MutationObserver(function (mutations) {
@ -142,11 +140,14 @@ var AdditionalTopToolbars = {
);
});
});
observer2.observe(document.querySelector("#additional_top_toolbar2"), {
observer2.observe(
document.querySelector("#additional_top_toolbar2"),
{
attributes: true,
childList: true,
characterData: true,
});
}
);
}
if (number_of_additional_top_toolbars >= 3) {
var observer3 = new MutationObserver(function (mutations) {
@ -159,11 +160,14 @@ var AdditionalTopToolbars = {
);
});
});
observer3.observe(document.querySelector("#additional_top_toolbar3"), {
observer3.observe(
document.querySelector("#additional_top_toolbar3"),
{
attributes: true,
childList: true,
characterData: true,
});
}
);
}
if (number_of_additional_top_toolbars >= 4) {
var observer4 = new MutationObserver(function (mutations) {
@ -176,11 +180,14 @@ var AdditionalTopToolbars = {
);
});
});
observer4.observe(document.querySelector("#additional_top_toolbar4"), {
observer4.observe(
document.querySelector("#additional_top_toolbar4"),
{
attributes: true,
childList: true,
characterData: true,
});
}
);
}
if (number_of_additional_top_toolbars >= 5) {
var observer5 = new MutationObserver(function (mutations) {
@ -193,11 +200,14 @@ var AdditionalTopToolbars = {
);
});
});
observer5.observe(document.querySelector("#additional_top_toolbar5"), {
observer5.observe(
document.querySelector("#additional_top_toolbar5"),
{
attributes: true,
childList: true,
characterData: true,
});
}
);
}
} catch (e) {}
},
@ -216,3 +226,4 @@ setTimeout(function(){
AdditionalTopToolbars.init();
},500);
*/
})();

View file

@ -1,10 +1,11 @@
(function () {
function waitForElm(selector) {
return new Promise(resolve => {
return new Promise((resolve) => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(mutations => {
const observer = new MutationObserver((mutations) => {
if (document.querySelector(selector)) {
resolve(document.querySelector(selector));
observer.disconnect();
@ -13,30 +14,39 @@ function waitForElm(selector) {
observer.observe(document.body, {
childList: true,
subtree: true
subtree: true,
});
});
}
waitForElm('#find-button').then((elm) => {
waitForElm("#find-button").then((elm) => {
var find = document.querySelector("#find-button label.toolbarbutton-text");
if (find)
find.setAttribute("value", "Search");
});
setTimeout(function () {
waitForElm('#nav-bar').then((elm) => {
var library = document.querySelector("#bookmarks-menu-button label.toolbarbutton-text");
waitForElm("#nav-bar").then((elm) => {
var library = document.querySelector(
"#bookmarks-menu-button label.toolbarbutton-text"
);
if (library)
library.setAttribute("value", "Favorites");
});
}, 0);
waitForElm('#bookmarksMenu').then((elm) => {
var bookmarksmenu = document.querySelector("#bookmarksMenu label.menubar-text");
waitForElm("#bookmarksMenu").then((elm) => {
var bookmarksmenu = document.querySelector(
"#bookmarksMenu label.menubar-text"
);
if (bookmarksmenu)
bookmarksmenu.setAttribute("value", "Favorites");
bookmarksmenu.setAttribute("accesskey", "a");
//bookmarksmenu.setAttribute("accesskey", "a");
});
waitForElm('#bookmarksMenu').then((elm) => {
waitForElm("#bookmarksMenu").then((elm) => {
var bookmarksmenu = document.querySelector("#bookmarksMenu");
bookmarksmenu.setAttribute("accesskey", "a");
if (bookmarksmenu)
bookmarksmenu.removeAttribute("accesskey");
});
})();

View file

@ -1,8 +1,8 @@
(function () {
Components.utils.import("resource:///modules/CustomizableUI.jsm");
var { Services } = Components.utils.import(
"resource://gre/modules/Services.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);
@ -68,3 +68,4 @@ var IE6StatusBar = {
};
document.addEventListener("DOMContentLoaded", IE6StatusBar.init(), false);
})();

View file

@ -1,10 +1,9 @@
(function () {
try {
Components.utils.import("resource:///modules/CustomizableUI.jsm");
var { Services } = Components.utils.import(
"resource://gre/modules/Services.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);

View file

@ -2,7 +2,7 @@
// option: place urlbar 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",
{}
@ -32,10 +32,17 @@ var backbutton_on_toolbar_position = 0;
var forwardbutton_on_toolbar_position = 1;
var urlbar_on_toolbar_position = 0;
const separateAddressBar = true;
const moveNavigation = true;
const bottomTabs = true;
const menuToTop = true;
var MoveUrlbar = {
init: function () {
try {
document.getElementById("back-button").setAttribute("removable", "true");
document
.getElementById("back-button")
.setAttribute("removable", "true");
document
.getElementById("forward-button")
.setAttribute("removable", "true");
@ -43,20 +50,31 @@ var MoveUrlbar = {
.getElementById("urlbar-container")
.setAttribute("removable", "true");
document
.getElementById("additional_top_toolbar1")
.setAttribute("fullscreentoolbar", "true");
.getElementById("unified-extensions-button")
.setAttribute("removable", "true");
} catch (e) {}
/*CustomizableUI.addWidgetToArea("back-button", backbutton_on_toolbar);
// Forces back/forward to navigation bar
if (moveNavigation) {
CustomizableUI.addWidgetToArea("back-button", backbutton_on_toolbar);
CustomizableUI.moveWidgetWithinArea(
"back-button",
backbutton_on_toolbar_position
);
CustomizableUI.addWidgetToArea("forward-button", forwardbutton_on_toolbar);
CustomizableUI.addWidgetToArea(
"forward-button",
forwardbutton_on_toolbar
);
CustomizableUI.moveWidgetWithinArea(
"forward-button",
forwardbutton_on_toolbar_position
);
}
// Forces address bar to additional_top_toolbar1 (Address)
// THIS WILL SOFTBRICK YOUR FIREFOX IF ADDITIONAL TOP TOOLBARS AREN'T
// ENABLED BUT THIS IS
if (separateAddressBar) {
CustomizableUI.addWidgetToArea(
"urlbar-container",
"additional_top_toolbar1"
@ -64,20 +82,34 @@ var MoveUrlbar = {
CustomizableUI.moveWidgetWithinArea(
"urlbar-container",
urlbar_on_toolbar_position
);*/
);
}
// Moves tabs to the bottom of the toolbar stack
if (bottomTabs) {
document
.getElementById("navigator-toolbox")
.appendChild(document.getElementById("TabsToolbar"));
}
/*
try {
document.getElementById('back-button').setAttribute('removable','false');
document.getElementById('forward-button').setAttribute('removable','false');
document.getElementById('urlbar-container').setAttribute('removable','false');
} catch(e){}
*/
// 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);
})();

View file

Before

Width:  |  Height:  |  Size: 257 B

After

Width:  |  Height:  |  Size: 257 B

2503
chrome/msfx/msfx.css Normal file

File diff suppressed because one or more lines are too long

BIN
chrome/msfx/toolbar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -18,9 +18,7 @@ scrollbar
-moz-binding: url("chrome://global/content/bindings/scrollbar.xml#scrollbar");
cursor: default;
min-width: 16px !important;
background-repeat: repeat !important;
background-color: ThreeDHighlight !important;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdib3g9IjAgMCAyIDIiIHdpZHRoPSIyIiBoZWlnaHQ9IjIiPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxIiBoZWlnaHQ9IjEiIGZpbGw9Ii1tb3otZGlhbG9nIi8+PHJlY3QgZmlsbD0iVGhyZWVESGlnaGxpZ2h0IiB4PSIxIiB5PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIi8+PHJlY3QgeD0iMSIgeT0iMSIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0iLW1vei1kaWFsb2ciLz48cmVjdCBmaWxsPSJUaHJlZURIaWdobGlnaHQiIHg9IjAiIHk9IjEiIHdpZHRoPSIxIiBoZWlnaHQ9IjEiLz48L3N2Zz4=") !important;
opacity: 1 !important;
}
@ -66,6 +64,9 @@ slider[orient="vertical"]
{
color-scheme: light !important;
-moz-default-appearance: none;
background-color: ThreeDHighlight !important;
background-repeat: repeat !important;
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMiIgaGVpZ2h0PSIyIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAuNTI5MTcgLjUyOTE3IiB4bWw6c3BhY2U9InByZXNlcnZlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnPjxnPjxyZWN0IHg9Ii4yNjQ1OCIgeT0iMC4wMDAwIiB3aWR0aD0iLjI2NDU4IiBoZWlnaHQ9Ii4yNjQ1OCIgZmlsbD0iVGhyZWVEU2hhZG93Ii8+PHJlY3QgeD0iMC4wMDAwIiB5PSIuMjY0NTgiIHdpZHRoPSIuMjY0NTgiIGhlaWdodD0iLjI2NDU4IiBmaWxsPSJUaHJlZURTaGFkb3ciLz48cmVjdCB4PSIwLjAwMDAiIHk9IjAuMDAwMCIgd2lkdGg9Ii4yNjQ1OCIgaGVpZ2h0PSIuMjY0NTgiIGZpbGw9IlRocmVlREhpZ2hsaWdodCIvPjxyZWN0IHg9Ii4yNjQ1OCIgeT0iLjI2NDU4IiB3aWR0aD0iLjI2NDU4IiBoZWlnaHQ9Ii4yNjQ1OCIgZmlsbD0iVGhyZWVESGlnaGxpZ2h0Ii8+PC9nPjwvZz48L3N2Zz4=") !important;
}
scrollcorner
@ -82,7 +83,7 @@ scrollbarbutton[type="increment"]
{
-moz-default-appearance: none;
background-repeat: no-repeat !important;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSI3IiB5PSI0IiB4PSI1IiBmaWxsPSJtZW51dGV4dCIvPjxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjUiIHk9IjUiIHg9IjYiIGZpbGw9Im1lbnV0ZXh0Ii8+PHJlY3Qgd2lkdGg9IjEiIGhlaWdodD0iMyIgeT0iNiIgeD0iNyIgZmlsbD0ibWVudXRleHQiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB5PSI3IiB4PSI4IiBmaWxsPSJtZW51dGV4dCIvPg0KCQ0KCQ0KPC9zdmc+") !important;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4KICA8cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSI3IiB5PSI0IiB4PSI1IiBmaWxsPSJDYW52YXNUZXh0Ii8+PHJlY3Qgd2lkdGg9IjEiIGhlaWdodD0iNSIgeT0iNSIgeD0iNiIgZmlsbD0iQ2FudmFzVGV4dCIvPjxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjMiIHk9IjYiIHg9IjciIGZpbGw9IkNhbnZhc1RleHQiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB5PSI3IiB4PSI4IiBmaWxsPSJDYW52YXNUZXh0Ii8+CiAgCiAgCjwvc3ZnPg==") !important;
background-position: center center !important;
}
@ -90,7 +91,7 @@ scrollbar[orient="vertical"] > scrollbarbutton[type="increment"]
{
-moz-default-appearance: none;
background-repeat: no-repeat !important;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTUiIHdpZHRoPSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCBoZWlnaHQ9IjEiIHdpZHRoPSI3IiB4PSIzIiB5PSI1IiBmaWxsPSJtZW51dGV4dCIvPjxyZWN0IGhlaWdodD0iMSIgd2lkdGg9IjUiIHk9IjYiIHg9IjQiIGZpbGw9Im1lbnV0ZXh0Ii8+PHJlY3QgaGVpZ2h0PSIxIiB3aWR0aD0iMyIgeD0iNSIgeT0iNyIgZmlsbD0ibWVudXRleHQiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4PSI2IiB5PSI4IiBmaWxsPSJtZW51dGV4dCIvPg0KCQ0KCQ0KPC9zdmc+") !important;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTUiIHdpZHRoPSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4KICA8cmVjdCBoZWlnaHQ9IjEiIHdpZHRoPSI3IiB4PSIzIiB5PSI1IiBmaWxsPSJDYW52YXNUZXh0Ii8+PHJlY3QgaGVpZ2h0PSIxIiB3aWR0aD0iNSIgeT0iNiIgeD0iNCIgZmlsbD0iQ2FudmFzVGV4dCIvPjxyZWN0IGhlaWdodD0iMSIgd2lkdGg9IjMiIHg9IjUiIHk9IjciIGZpbGw9IkNhbnZhc1RleHQiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4PSI2IiB5PSI4IiBmaWxsPSJDYW52YXNUZXh0Ii8+CiAgCiAgCjwvc3ZnPg==") !important;
background-position: center center !important;
}
@ -98,7 +99,7 @@ scrollbarbutton[type="decrement"]
{
-moz-default-appearance: none;
background-repeat: no-repeat !important;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTUiIHdpZHRoPSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSI3IiB5PSI0IiB4PSI4IiBmaWxsPSJtZW51dGV4dCIvPjxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjUiIHk9IjUiIHg9IjciIGZpbGw9Im1lbnV0ZXh0Ii8+PHJlY3Qgd2lkdGg9IjEiIGhlaWdodD0iMyIgeT0iNiIgeD0iNiIgZmlsbD0ibWVudXRleHQiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB5PSI3IiB4PSI1IiBmaWxsPSJtZW51dGV4dCIvPg0KCQ0KCQ0KPC9zdmc+") !important;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTUiIHdpZHRoPSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4KICA8cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSI3IiB5PSI0IiB4PSI4IiBmaWxsPSJDYW52YXNUZXh0Ii8+PHJlY3Qgd2lkdGg9IjEiIGhlaWdodD0iNSIgeT0iNSIgeD0iNyIgZmlsbD0iQ2FudmFzVGV4dCIvPjxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjMiIHk9IjYiIHg9IjYiIGZpbGw9IkNhbnZhc1RleHQiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB5PSI3IiB4PSI1IiBmaWxsPSJDYW52YXNUZXh0Ii8+CiAgCiAgCjwvc3ZnPg==") !important;
background-position: center center !important;
}
@ -106,7 +107,7 @@ scrollbar[orient="vertical"] > scrollbarbutton[type="decrement"]
{
-moz-default-appearance: none;
background-repeat: no-repeat !important;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4NCgk8cmVjdCBoZWlnaHQ9IjEiIHdpZHRoPSI3IiB5PSI4IiB4PSIzIiBmaWxsPSJtZW51dGV4dCIvPjxyZWN0IGhlaWdodD0iMSIgd2lkdGg9IjUiIHg9IjQiIHk9IjciIGZpbGw9Im1lbnV0ZXh0Ii8+PHJlY3QgaGVpZ2h0PSIxIiB3aWR0aD0iMyIgeT0iNiIgeD0iNSIgZmlsbD0ibWVudXRleHQiLz48cmVjdCB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB5PSI1IiB4PSI2IiBmaWxsPSJtZW51dGV4dCIvPg0KCQ0KCQ0KPC9zdmc+") !important;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDE1IDE1Ij4KCTxyZWN0IGhlaWdodD0iMSIgd2lkdGg9IjciIHk9IjgiIHg9IjMiIGZpbGw9IkNhbnZhc1RleHQiLz48cmVjdCBoZWlnaHQ9IjEiIHdpZHRoPSI1IiB4PSI0IiB5PSI3IiBmaWxsPSJDYW52YXNUZXh0Ii8+PHJlY3QgaGVpZ2h0PSIxIiB3aWR0aD0iMyIgeT0iNiIgeD0iNSIgZmlsbD0iQ2FudmFzVGV4dCIvPjxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjEiIHk9IjUiIHg9IjYiIGZpbGw9IkNhbnZhc1RleHQiLz4KCQoJCjwvc3ZnPg==") !important;
background-position: center center !important;
}
@ -140,7 +141,9 @@ scrollbar[orient="vertical"] > scrollbarbutton[type="decrement"][disabled="true"
var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(
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);
})();

View file

@ -12,21 +12,31 @@
// [!] 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)
(function () {
Components.utils.import("resource:///modules/CustomizableUI.jsm");
var {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
var appversion = parseInt(Services.appinfo.version);
var AddSeparator = {
init: function () {
if (appversion >= 76 && location != 'chrome://browser/content/browser.xhtml')
if (
appversion >= 76 &&
location != "chrome://browser/content/browser.xhtml"
)
return;
try {
document
.getElementById("back-button")
.setAttribute("removable", "true");
document
.getElementById("forward-button")
.setAttribute("removable", "true");
} catch (e) {}
/* blank tab workaround */
try {
if(gBrowser.selectedBrowser.getAttribute('blank')) gBrowser.selectedBrowser.removeAttribute('blank');
if (gBrowser.selectedBrowser.getAttribute("blank"))
gBrowser.selectedBrowser.removeAttribute("blank");
} catch (e) {}
var tb_config_label = "Configuration Toolbar";
@ -35,23 +45,31 @@ var AddSeparator = {
var tb_spring_label = "Flexible Space";
try {
if(document.getElementById('configuration_toolbar') == null) {
if(appversion <= 62) var tb_config = document.createElement("toolbar");
if (document.getElementById("configuration_toolbar") == null) {
if (appversion <= 62)
var tb_config = document.createElement("toolbar");
else var tb_config = document.createXULElement("toolbar");
tb_config.setAttribute("id", "configuration_toolbar");
tb_config.setAttribute("customizable", "true");
tb_config.setAttribute("class","toolbar-primary chromeclass-toolbar browser-toolbar customization-target");
tb_config.setAttribute(
"class",
"toolbar-primary chromeclass-toolbar browser-toolbar customization-target"
);
tb_config.setAttribute("mode", "icons");
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");
tb_config.setAttribute(
"defaultset",
"toolbarspacer,toolbarseparator"
);
document.querySelector('#navigator-toolbox').appendChild(tb_config);
document.querySelector("#navigator-toolbox").appendChild(tb_config);
CustomizableUI.registerArea("configuration_toolbar", {legacy: true});
CustomizableUI.registerArea("configuration_toolbar", {
legacy: true,
});
if (appversion >= 65) CustomizableUI.registerToolbarNode(tb_config);
if (appversion <= 62) var tb_label = document.createElement("label");
@ -63,8 +81,8 @@ var AddSeparator = {
tb_config.appendChild(tb_label);
if(appversion <= 62) var tb_spacer = document.createElement("toolbarspacer");
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");
@ -74,8 +92,8 @@ var AddSeparator = {
tb_config.appendChild(tb_spacer);
if(appversion <= 62) var tb_sep = document.createElement("toolbarseparator");
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");
@ -85,8 +103,8 @@ var AddSeparator = {
tb_config.appendChild(tb_sep);
if(appversion <= 62) var tb_spring = document.createElement("toolbarspring");
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");
@ -98,9 +116,14 @@ var AddSeparator = {
tb_config.appendChild(tb_spring);
// CSS
var sss = Components.classes["@mozilla.org/content/style-sheet-service;1"].getService(Components.interfaces.nsIStyleSheetService);
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('\
var uri = Services.io.newURI(
"data:text/css;charset=utf-8," +
encodeURIComponent(
'\
\
#configuration_toolbar { \
-moz-appearance: none !important; \
@ -161,15 +184,17 @@ var AddSeparator = {
max-width: 100% !important; \
}\
\
'), null, null);
'
),
null,
null
);
sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
}
} catch (e) {}
}
}
},
};
/* initialization delay workaround */
document.addEventListener("DOMContentLoaded", AddSeparator.init(), false);
@ -179,3 +204,4 @@ setTimeout(function(){
AddSeparator.init();
},2000);
*/
})();

207
chrome/status-bar.uc.js Normal file
View 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();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

File diff suppressed because one or more lines are too long

View 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();
}
});
});
}

View 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;
}
}

View 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;
}
}

View 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>

View 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;
}

View 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;

View file

@ -0,0 +1,2 @@
content userchromejs ./
resource userchromejs ../

View 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;
};
}

View 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,
]);
}

View 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>

View 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

View 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;

Some files were not shown because too many files have changed in this diff Show more