summaryrefslogtreecommitdiffstats
path: root/application/basilisk/base
diff options
context:
space:
mode:
Diffstat (limited to 'application/basilisk/base')
-rw-r--r--application/basilisk/base/content/browser-fxaccounts.js125
-rw-r--r--application/basilisk/base/content/browser-syncui.js82
-rw-r--r--application/basilisk/base/content/browser.js2
-rw-r--r--application/basilisk/base/content/sync/quota.js247
-rw-r--r--application/basilisk/base/content/sync/quota.xul65
-rw-r--r--application/basilisk/base/jar.mn2
6 files changed, 383 insertions, 140 deletions
diff --git a/application/basilisk/base/content/browser-fxaccounts.js b/application/basilisk/base/content/browser-fxaccounts.js
index 94a591f1e..e1d556bff 100644
--- a/application/basilisk/base/content/browser-fxaccounts.js
+++ b/application/basilisk/base/content/browser-fxaccounts.js
@@ -77,15 +77,6 @@ var gFxAccounts = {
return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
},
- get sendTabToDeviceEnabled() {
- return Services.prefs.getBoolPref("services.sync.sendTabToDevice.enabled");
- },
-
- get remoteClients() {
- return Weave.Service.clientsEngine.remoteClients
- .sort((a, b) => a.name.localeCompare(b.name));
- },
-
init: function () {
// Bail out if we're already initialized and for pop-up windows.
if (this._initialized || !window.toolbar.visible) {
@@ -153,11 +144,6 @@ var gFxAccounts = {
profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled");
} catch (e) { }
- // Bail out if FxA is disabled.
- if (!this.weave.fxAccountsEnabled) {
- return Promise.resolve();
- }
-
this.panelUIFooter.hidden = false;
// Make sure the button is disabled in customization mode.
@@ -176,6 +162,7 @@ var gFxAccounts = {
let defaultLabel = this.panelUIStatus.getAttribute("defaultlabel");
let errorLabel = this.panelUIStatus.getAttribute("errorlabel");
let unverifiedLabel = this.panelUIStatus.getAttribute("unverifiedlabel");
+ let settingslabel = this.panelUIStatus.getAttribute("settingslabel");
// The localization string is for the signed in text, but it's the default text as well
let defaultTooltiptext = this.panelUIStatus.getAttribute("signedinTooltiptext");
@@ -191,34 +178,19 @@ var gFxAccounts = {
this.panelUIFooter.removeAttribute("fxastatus");
this.panelUIFooter.removeAttribute("fxaprofileimage");
this.panelUIAvatar.style.removeProperty("list-style-image");
- let showErrorBadge = false;
- if (userData) {
- // At this point we consider the user as logged-in (but still can be in an error state)
- if (this.loginFailed) {
- let tooltipDescription = this.strings.formatStringFromName("reconnectDescription", [userData.email], 1);
- this.panelUIFooter.setAttribute("fxastatus", "error");
- this.panelUILabel.setAttribute("label", errorLabel);
- this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
- showErrorBadge = true;
- } else if (!userData.verified) {
- let tooltipDescription = this.strings.formatStringFromName("verifyDescription", [userData.email], 1);
- this.panelUIFooter.setAttribute("fxastatus", "error");
- this.panelUIFooter.setAttribute("unverified", "true");
- this.panelUILabel.setAttribute("label", unverifiedLabel);
- this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
- showErrorBadge = true;
- } else {
- this.panelUIFooter.setAttribute("fxastatus", "signedin");
- this.panelUILabel.setAttribute("label", userData.email);
- }
- if (profileInfoEnabled) {
- this.panelUIFooter.setAttribute("fxaprofileimage", "enabled");
- }
+
+ if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED) {
+ // Leave the default state
+ return;
}
- if (showErrorBadge) {
- gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_FXA, "fxa-needs-authentication");
+
+ if (this.loginFailed) {
+ this.panelUIFooter.setAttribute("fxastatus", "error");
+ this.panelUILabel.setAttribute("label", errorLabel);
} else {
- gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_FXA);
+ this.panelUIFooter.setAttribute("fxastatus", "signedin");
+ this.panelUILabel.setAttribute("label", settingslabel);
+ this.panelUIStatus.setAttribute("tooltiptext", "");
}
}
@@ -322,81 +294,12 @@ var gFxAccounts = {
this.openAccountsPage("reauth", { entrypoint: entryPoint });
},
- sendTabToDevice: function (url, clientId, title) {
- Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title);
- },
-
- populateSendTabToDevicesMenu: function (devicesPopup, url, title) {
- // remove existing menu items
- while (devicesPopup.hasChildNodes()) {
- devicesPopup.removeChild(devicesPopup.firstChild);
- }
-
- const fragment = document.createDocumentFragment();
-
- const onTargetDeviceCommand = (event) => {
- const clientId = event.target.getAttribute("clientId");
- const clients = clientId
- ? [clientId]
- : this.remoteClients.map(client => client.id);
-
- clients.forEach(clientId => this.sendTabToDevice(url, clientId, title));
- }
-
- function addTargetDevice(clientId, name) {
- const targetDevice = document.createElement("menuitem");
- targetDevice.addEventListener("command", onTargetDeviceCommand, true);
- targetDevice.setAttribute("class", "sendtab-target");
- targetDevice.setAttribute("clientId", clientId);
- targetDevice.setAttribute("label", name);
- fragment.appendChild(targetDevice);
- }
-
- const clients = this.remoteClients;
- for (let client of clients) {
- addTargetDevice(client.id, client.name);
- }
-
- // "All devices" menu item
- if (clients.length > 1) {
- const separator = document.createElement("menuseparator");
- fragment.appendChild(separator);
- const allDevicesLabel = this.strings.GetStringFromName("sendTabToAllDevices.menuitem");
- addTargetDevice("", allDevicesLabel);
- }
-
- devicesPopup.appendChild(fragment);
- },
-
updateTabContextMenu: function (aPopupMenu) {
- if (!this.sendTabToDeviceEnabled) {
- return;
- }
-
- const remoteClientPresent = this.remoteClients.length > 0;
- ["context_sendTabToDevice", "context_sendTabToDevice_separator"]
- .forEach(id => { document.getElementById(id).hidden = !remoteClientPresent });
+ // STUB
},
initPageContextMenu: function (contextMenu) {
- if (!this.sendTabToDeviceEnabled) {
- return;
- }
-
- const remoteClientPresent = this.remoteClients.length > 0;
- // showSendLink and showSendPage are mutually exclusive
- const showSendLink = remoteClientPresent
- && (contextMenu.onSaveableLink || contextMenu.onPlainTextLink);
- const showSendPage = !showSendLink && remoteClientPresent
- && !(contextMenu.isContentSelected ||
- contextMenu.onImage || contextMenu.onCanvas ||
- contextMenu.onVideo || contextMenu.onAudio ||
- contextMenu.onLink || contextMenu.onTextInput);
-
- ["context-sendpagetodevice", "context-sep-sendpagetodevice"]
- .forEach(id => contextMenu.showItem(id, showSendPage));
- ["context-sendlinktodevice", "context-sep-sendlinktodevice"]
- .forEach(id => contextMenu.showItem(id, showSendLink));
+ // STUB
}
};
diff --git a/application/basilisk/base/content/browser-syncui.js b/application/basilisk/base/content/browser-syncui.js
index 51bcb15d5..3a57140f2 100644
--- a/application/basilisk/base/content/browser-syncui.js
+++ b/application/basilisk/base/content/browser-syncui.js
@@ -19,6 +19,7 @@ var gSyncUI = {
_obs: ["weave:service:sync:start",
"weave:service:sync:finish",
"weave:service:sync:error",
+ "weave:service:quota:remaining",
"weave:service:setup-complete",
"weave:service:login:start",
"weave:service:login:finish",
@@ -246,6 +247,21 @@ var gSyncUI = {
this.updateUI();
},
+ onQuotaNotice: function onQuotaNotice(subject, data) {
+ let title = this._stringBundle.GetStringFromName("warning.sync.quota.label");
+ let description = this._stringBundle.GetStringFromName("warning.sync.quota.description");
+ let buttons = [];
+ buttons.push(new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.label"),
+ this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.accesskey"),
+ function() { gSyncUI.openQuotaDialog(); return true; }
+ ));
+
+ let notification = new Weave.Notification(
+ title, description, null, Weave.Notifications.PRIORITY_WARNING, buttons);
+ Weave.Notifications.replaceTitle(notification);
+ },
+
_getAppName: function () {
let brand = new StringBundle("chrome://branding/locale/brand.properties");
return brand.get("brandShortName");
@@ -293,17 +309,13 @@ var gSyncUI = {
*/
openSetup: function SUI_openSetup(wizardType, entryPoint = "syncbutton") {
- if (this.weaveService.fxAccountsEnabled) {
- this.openPrefs(entryPoint);
- } else {
- let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
- if (win)
- win.focus();
- else {
- window.openDialog("chrome://browser/content/sync/setup.xul",
- "weaveSetup", "centerscreen,chrome,resizable=no",
- wizardType);
- }
+ let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
+ if (win)
+ win.focus();
+ else {
+ window.openDialog("chrome://browser/content/sync/setup.xul",
+ "weaveSetup", "centerscreen,chrome,resizable=no",
+ wizardType);
}
},
@@ -328,23 +340,6 @@ var gSyncUI = {
gFxAccounts.openSignInAgainPage(entryPoint);
},
- openSyncedTabsPanel() {
- let placement = CustomizableUI.getPlacementOfWidget("sync-button");
- let area = placement ? placement.area : CustomizableUI.AREA_NAVBAR;
- let anchor = document.getElementById("sync-button") ||
- document.getElementById("PanelUI-menu-button");
- if (area == CustomizableUI.AREA_PANEL) {
- // The button is in the panel, so we need to show the panel UI, then our
- // subview.
- PanelUI.show().then(() => {
- PanelUI.showSubView("PanelUI-remotetabs", anchor, area);
- }).catch(Cu.reportError);
- } else {
- // It is placed somewhere else - just try and show it.
- PanelUI.showSubView("PanelUI-remotetabs", anchor, area);
- }
- },
-
/* After Sync is initialized we perform a once-only check for the sync
button being in "customize purgatory" and if so, move it to the panel.
This is done primarily for profiles created before SyncedTabs landed,
@@ -456,6 +451,32 @@ var gSyncUI = {
}
},
+ onSyncError: function SUI_onSyncError() {
+ this.log.debug("onSyncError: login=${login}, sync=${sync}", Weave.Status);
+ let title = this._stringBundle.GetStringFromName("error.sync.title");
+ let error = Weave.Utils.getErrorString(Weave.Status.sync);
+ let description =
+ this._stringBundle.formatStringFromName("error.sync.description", [error], 1);
+ let priority = Weave.Notifications.PRIORITY_WARNING;
+ let buttons = [];
+
+ if (Weave.Status.sync == Weave.OVER_QUOTA) {
+ description = this._stringBundle.GetStringFromName("error.sync.quota.description");
+ buttons.push(new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.label"),
+ this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.accesskey"),
+ function() { gSyncUI.openQuotaDialog(); return true; } )
+ );
+ // Only show the notification bar on Quota error. the panel will show the rest.
+ let notification =
+ new Weave.Notification(title, description, null, priority, buttons);
+ Weave.Notifications.replaceTitle(notification);
+ }
+
+ this.updateUI();
+ },
+
+
observe: function SUI_observe(subject, topic, data) {
this.log.debug("observed", topic);
if (this._unloaded) {
@@ -487,12 +508,17 @@ var gSyncUI = {
// Do nothing.
break;
case "weave:ui:sync:error":
+ this.onSyncError();
+ break;
case "weave:service:setup-complete":
case "weave:service:login:finish":
case "weave:service:login:start":
case "weave:service:start-over":
this.updateUI();
break;
+ case "weave:service:quota:remaining":
+ this.onQuotaNotice();
+ break;
case "weave:ui:login:error":
case "weave:service:login:error":
this.onLoginError();
diff --git a/application/basilisk/base/content/browser.js b/application/basilisk/base/content/browser.js
index d45956191..9fb997a42 100644
--- a/application/basilisk/base/content/browser.js
+++ b/application/basilisk/base/content/browser.js
@@ -6347,7 +6347,7 @@ function checkEmptyPageOrigin(browser = gBrowser.selectedBrowser,
}
function BrowserOpenSyncTabs() {
- gSyncUI.openSyncedTabsPanel();
+ switchToTabHavingURI("about:sync-tabs", true);
}
/**
diff --git a/application/basilisk/base/content/sync/quota.js b/application/basilisk/base/content/sync/quota.js
new file mode 100644
index 000000000..b416a44cc
--- /dev/null
+++ b/application/basilisk/base/content/sync/quota.js
@@ -0,0 +1,247 @@
+/* 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 Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource://gre/modules/DownloadUtils.jsm");
+
+var gSyncQuota = {
+
+ init: function init() {
+ this.bundle = document.getElementById("quotaStrings");
+ let caption = document.getElementById("treeCaption");
+ caption.firstChild.nodeValue = this.bundle.getString("quota.treeCaption.label");
+
+ gUsageTreeView.init();
+ this.tree = document.getElementById("usageTree");
+ this.tree.view = gUsageTreeView;
+
+ this.loadData();
+ },
+
+ loadData: function loadData() {
+ this._usage_req = Weave.Service.getStorageInfo(Weave.INFO_COLLECTION_USAGE,
+ function (error, usage) {
+ delete gSyncQuota._usage_req;
+ // displayUsageData handles null values, so no need to check 'error'.
+ gUsageTreeView.displayUsageData(usage);
+ });
+
+ let usageLabel = document.getElementById("usageLabel");
+ let bundle = this.bundle;
+
+ this._quota_req = Weave.Service.getStorageInfo(Weave.INFO_QUOTA,
+ function (error, quota) {
+ delete gSyncQuota._quota_req;
+
+ if (error) {
+ usageLabel.value = bundle.getString("quota.usageError.label");
+ return;
+ }
+ let used = gSyncQuota.convertKB(quota[0]);
+ if (!quota[1]) {
+ // No quota on the server.
+ usageLabel.value = bundle.getFormattedString(
+ "quota.usageNoQuota.label", used);
+ return;
+ }
+ let percent = Math.round(100 * quota[0] / quota[1]);
+ let total = gSyncQuota.convertKB(quota[1]);
+ usageLabel.value = bundle.getFormattedString(
+ "quota.usagePercentage.label", [percent].concat(used).concat(total));
+ });
+ },
+
+ onCancel: function onCancel() {
+ if (this._usage_req) {
+ this._usage_req.abort();
+ }
+ if (this._quota_req) {
+ this._quota_req.abort();
+ }
+ return true;
+ },
+
+ onAccept: function onAccept() {
+ let engines = gUsageTreeView.getEnginesToDisable();
+ for each (let engine in engines) {
+ Weave.Service.engineManager.get(engine).enabled = false;
+ }
+ if (engines.length) {
+ // The 'Weave' object will disappear once the window closes.
+ let Service = Weave.Service;
+ Weave.Utils.nextTick(function() { Service.sync(); });
+ }
+ return this.onCancel();
+ },
+
+ convertKB: function convertKB(value) {
+ return DownloadUtils.convertByteUnits(value * 1024);
+ }
+
+};
+
+var gUsageTreeView = {
+
+ _ignored: {keys: true,
+ meta: true,
+ clients: true},
+
+ /*
+ * Internal data structures underlaying the tree.
+ */
+ _collections: [],
+ _byname: {},
+
+ init: function init() {
+ let retrievingLabel = gSyncQuota.bundle.getString("quota.retrieving.label");
+ for each (let engine in Weave.Service.engineManager.getEnabled()) {
+ if (this._ignored[engine.name])
+ continue;
+
+ // Some engines use the same pref, which means they can only be turned on
+ // and off together. We need to combine them here as well.
+ let existing = this._byname[engine.prefName];
+ if (existing) {
+ existing.engines.push(engine.name);
+ continue;
+ }
+
+ let obj = {name: engine.prefName,
+ title: this._collectionTitle(engine),
+ engines: [engine.name],
+ enabled: true,
+ sizeLabel: retrievingLabel};
+ this._collections.push(obj);
+ this._byname[engine.prefName] = obj;
+ }
+ },
+
+ _collectionTitle: function _collectionTitle(engine) {
+ try {
+ return gSyncQuota.bundle.getString(
+ "collection." + engine.prefName + ".label");
+ } catch (ex) {
+ return engine.Name;
+ }
+ },
+
+ /*
+ * Process the quota information as returned by info/collection_usage.
+ */
+ displayUsageData: function displayUsageData(data) {
+ for each (let coll in this._collections) {
+ coll.size = 0;
+ // If we couldn't retrieve any data, just blank out the label.
+ if (!data) {
+ coll.sizeLabel = "";
+ continue;
+ }
+
+ for each (let engineName in coll.engines)
+ coll.size += data[engineName] || 0;
+ let sizeLabel = "";
+ sizeLabel = gSyncQuota.bundle.getFormattedString(
+ "quota.sizeValueUnit.label", gSyncQuota.convertKB(coll.size));
+ coll.sizeLabel = sizeLabel;
+ }
+ let sizeColumn = this.treeBox.columns.getNamedColumn("size");
+ this.treeBox.invalidateColumn(sizeColumn);
+ },
+
+ /*
+ * Handle click events on the tree.
+ */
+ onTreeClick: function onTreeClick(event) {
+ if (event.button == 2)
+ return;
+
+ let cell = this.treeBox.getCellAt(event.clientX, event.clientY);
+ if (cell.col && cell.col.id == "enabled")
+ this.toggle(cell.row);
+ },
+
+ /*
+ * Toggle enabled state of an engine.
+ */
+ toggle: function toggle(row) {
+ // Update the tree
+ let collection = this._collections[row];
+ collection.enabled = !collection.enabled;
+ this.treeBox.invalidateRow(row);
+ },
+
+ /*
+ * Return a list of engines (or rather their pref names) that should be
+ * disabled.
+ */
+ getEnginesToDisable: function getEnginesToDisable() {
+ // Tycho: return [coll.name for each (coll in this._collections) if (!coll.enabled)];
+ let engines = [];
+ for each (let coll in this._collections) {
+ if (!coll.enabled) {
+ engines.push(coll.name);
+ }
+ }
+ return engines;
+ },
+
+ // nsITreeView
+
+ get rowCount() {
+ return this._collections.length;
+ },
+
+ getRowProperties: function(index) { return ""; },
+ getCellProperties: function(row, col) { return ""; },
+ getColumnProperties: function(col) { return ""; },
+ isContainer: function(index) { return false; },
+ isContainerOpen: function(index) { return false; },
+ isContainerEmpty: function(index) { return false; },
+ isSeparator: function(index) { return false; },
+ isSorted: function() { return false; },
+ canDrop: function(index, orientation, dataTransfer) { return false; },
+ drop: function(row, orientation, dataTransfer) {},
+ getParentIndex: function(rowIndex) {},
+ hasNextSibling: function(rowIndex, afterIndex) { return false; },
+ getLevel: function(index) { return 0; },
+ getImageSrc: function(row, col) {},
+
+ getCellValue: function(row, col) {
+ return this._collections[row].enabled;
+ },
+
+ getCellText: function getCellText(row, col) {
+ let collection = this._collections[row];
+ switch (col.id) {
+ case "collection":
+ return collection.title;
+ case "size":
+ return collection.sizeLabel;
+ default:
+ return "";
+ }
+ },
+
+ setTree: function setTree(tree) {
+ this.treeBox = tree;
+ },
+
+ toggleOpenState: function(index) {},
+ cycleHeader: function(col) {},
+ selectionChanged: function() {},
+ cycleCell: function(row, col) {},
+ isEditable: function(row, col) { return false; },
+ isSelectable: function (row, col) { return false; },
+ setCellValue: function(row, col, value) {},
+ setCellText: function(row, col, value) {},
+ performAction: function(action) {},
+ performActionOnRow: function(action, row) {},
+ performActionOnCell: function(action, row, col) {}
+
+};
diff --git a/application/basilisk/base/content/sync/quota.xul b/application/basilisk/base/content/sync/quota.xul
new file mode 100644
index 000000000..99e6ed78b
--- /dev/null
+++ b/application/basilisk/base/content/sync/quota.xul
@@ -0,0 +1,65 @@
+<?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/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncQuota.css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncQuotaDTD SYSTEM "chrome://browser/locale/syncQuota.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncQuotaDTD;
+]>
+<dialog id="quotaDialog"
+ windowtype="Sync:ViewQuota"
+ persist="screenX screenY width height"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="gSyncQuota.init()"
+ buttons="accept,cancel"
+ title="&quota.dialogTitle.label;"
+ ondialogcancel="return gSyncQuota.onCancel();"
+ ondialogaccept="return gSyncQuota.onAccept();">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/quota.js"/>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="quotaStrings"
+ src="chrome://browser/locale/syncQuota.properties"/>
+ </stringbundleset>
+
+ <vbox flex="1">
+ <label id="usageLabel"
+ value="&quota.retrievingInfo.label;"/>
+ <separator/>
+ <tree id="usageTree"
+ seltype="single"
+ hidecolumnpicker="true"
+ onclick="gUsageTreeView.onTreeClick(event);"
+ flex="1">
+ <treecols>
+ <treecol id="enabled"
+ type="checkbox"
+ fixed="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="collection"
+ label="&quota.typeColumn.label;"
+ flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="size"
+ label="&quota.sizeColumn.label;"
+ flex="1"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+ <separator/>
+ <description id="treeCaption"> </description>
+ </vbox>
+
+</dialog>
diff --git a/application/basilisk/base/jar.mn b/application/basilisk/base/jar.mn
index 5ec92d79a..c288685b9 100644
--- a/application/basilisk/base/jar.mn
+++ b/application/basilisk/base/jar.mn
@@ -150,6 +150,8 @@ browser.jar:
content/browser/sync/customize.xul (content/sync/customize.xul)
content/browser/sync/customize.js (content/sync/customize.js)
content/browser/sync/customize.css (content/sync/customize.css)
+ content/browser/sync/quota.xul (content/sync/quota.xul)
+ content/browser/sync/quota.js (content/sync/quota.js)
content/browser/safeMode.css (content/safeMode.css)
content/browser/safeMode.js (content/safeMode.js)
content/browser/safeMode.xul (content/safeMode.xul)