the huge 117+ fixes update
							
								
								
									
										80
									
								
								README.md
									
										
									
									
									
								
							
							
						
						| 
						 | 
					@ -1,36 +1,64 @@
 | 
				
			||||||
# MSFX
 | 
					# MSFX
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Fork of [the MSFX Firefox userChrome](https://github.com/WinClassic/MSFX).
 | 
					Fork of [the MSFX Firefox userChrome](https://github.com/WinClassic/MSFX).
 | 
				
			||||||
 | 
					
 | 
				
			||||||

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