Issue #303 Part 1: Move basilisk files from /browser to /application/basilisk
|
|
@ -1,428 +0,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/. */
|
||||
|
||||
// Note: this file is included in aboutDialog.xul if MOZ_UPDATER is defined.
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
|
||||
"resource://gre/modules/UpdateUtils.jsm");
|
||||
|
||||
const PREF_APP_UPDATE_CANCELATIONS_OSX = "app.update.cancelations.osx";
|
||||
const PREF_APP_UPDATE_ELEVATE_NEVER = "app.update.elevate.never";
|
||||
|
||||
var gAppUpdater;
|
||||
|
||||
function onUnload(aEvent) {
|
||||
if (gAppUpdater.isChecking)
|
||||
gAppUpdater.checker.stopChecking(Components.interfaces.nsIUpdateChecker.CURRENT_CHECK);
|
||||
// Safe to call even when there isn't a download in progress.
|
||||
gAppUpdater.removeDownloadListener();
|
||||
gAppUpdater = null;
|
||||
}
|
||||
|
||||
|
||||
function appUpdater()
|
||||
{
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "aus",
|
||||
"@mozilla.org/updates/update-service;1",
|
||||
"nsIApplicationUpdateService");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "checker",
|
||||
"@mozilla.org/updates/update-checker;1",
|
||||
"nsIUpdateChecker");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "um",
|
||||
"@mozilla.org/updates/update-manager;1",
|
||||
"nsIUpdateManager");
|
||||
|
||||
this.updateDeck = document.getElementById("updateDeck");
|
||||
|
||||
// Hide the update deck when the update window is already open and it's not
|
||||
// already applied, to avoid syncing issues between them. Applied updates
|
||||
// don't have any information to sync between the windows as they both just
|
||||
// show the "Restart to continue"-type button.
|
||||
if (Services.wm.getMostRecentWindow("Update:Wizard") &&
|
||||
!this.isApplied) {
|
||||
this.updateDeck.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.bundle = Services.strings.
|
||||
createBundle("chrome://browser/locale/browser.properties");
|
||||
|
||||
let manualURL = Services.urlFormatter.formatURLPref("app.update.url.manual");
|
||||
let manualLink = document.getElementById("manualLink");
|
||||
manualLink.value = manualURL;
|
||||
manualLink.href = manualURL;
|
||||
document.getElementById("failedLink").href = manualURL;
|
||||
|
||||
if (this.updateDisabledAndLocked) {
|
||||
this.selectPanel("adminDisabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isPending || this.isApplied) {
|
||||
this.selectPanel("apply");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.aus.isOtherInstanceHandlingUpdates) {
|
||||
this.selectPanel("otherInstanceHandlingUpdates");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isDownloading) {
|
||||
this.startDownload();
|
||||
// selectPanel("downloading") is called from setupDownloadingUI().
|
||||
return;
|
||||
}
|
||||
|
||||
// Honor the "Never check for updates" option by not only disabling background
|
||||
// update checks, but also in the About dialog, by presenting a
|
||||
// "Check for updates" button.
|
||||
// If updates are found, the user is then asked if he wants to "Update to <version>".
|
||||
if (!this.updateEnabled ||
|
||||
Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
|
||||
this.selectPanel("checkForUpdates");
|
||||
return;
|
||||
}
|
||||
|
||||
// That leaves the options
|
||||
// "Check for updates, but let me choose whether to install them", and
|
||||
// "Automatically install updates".
|
||||
// In both cases, we check for updates without asking.
|
||||
// In the "let me choose" case, we ask before downloading though, in onCheckComplete.
|
||||
this.checkForUpdates();
|
||||
}
|
||||
|
||||
appUpdater.prototype =
|
||||
{
|
||||
// true when there is an update check in progress.
|
||||
isChecking: false,
|
||||
|
||||
// true when there is an update already staged / ready to be applied.
|
||||
get isPending() {
|
||||
if (this.update) {
|
||||
return this.update.state == "pending" ||
|
||||
this.update.state == "pending-service" ||
|
||||
this.update.state == "pending-elevate";
|
||||
}
|
||||
return this.um.activeUpdate &&
|
||||
(this.um.activeUpdate.state == "pending" ||
|
||||
this.um.activeUpdate.state == "pending-service" ||
|
||||
this.um.activeUpdate.state == "pending-elevate");
|
||||
},
|
||||
|
||||
// true when there is an update already installed in the background.
|
||||
get isApplied() {
|
||||
if (this.update)
|
||||
return this.update.state == "applied" ||
|
||||
this.update.state == "applied-service";
|
||||
return this.um.activeUpdate &&
|
||||
(this.um.activeUpdate.state == "applied" ||
|
||||
this.um.activeUpdate.state == "applied-service");
|
||||
},
|
||||
|
||||
// true when there is an update download in progress.
|
||||
get isDownloading() {
|
||||
if (this.update)
|
||||
return this.update.state == "downloading";
|
||||
return this.um.activeUpdate &&
|
||||
this.um.activeUpdate.state == "downloading";
|
||||
},
|
||||
|
||||
// true when updating is disabled by an administrator.
|
||||
get updateDisabledAndLocked() {
|
||||
return !this.updateEnabled &&
|
||||
Services.prefs.prefIsLocked("app.update.enabled");
|
||||
},
|
||||
|
||||
// true when updating is enabled.
|
||||
get updateEnabled() {
|
||||
try {
|
||||
return Services.prefs.getBoolPref("app.update.enabled");
|
||||
}
|
||||
catch (e) { }
|
||||
return true; // Firefox default is true
|
||||
},
|
||||
|
||||
// true when updating in background is enabled.
|
||||
get backgroundUpdateEnabled() {
|
||||
return this.updateEnabled &&
|
||||
gAppUpdater.aus.canStageUpdates;
|
||||
},
|
||||
|
||||
// true when updating is automatic.
|
||||
get updateAuto() {
|
||||
try {
|
||||
return Services.prefs.getBoolPref("app.update.auto");
|
||||
}
|
||||
catch (e) { }
|
||||
return true; // Firefox default is true
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the panel of the updateDeck.
|
||||
*
|
||||
* @param aChildID
|
||||
* The id of the deck's child to select, e.g. "apply".
|
||||
*/
|
||||
selectPanel: function(aChildID) {
|
||||
let panel = document.getElementById(aChildID);
|
||||
|
||||
let button = panel.querySelector("button");
|
||||
if (button) {
|
||||
if (aChildID == "downloadAndInstall") {
|
||||
let updateVersion = gAppUpdater.update.displayVersion;
|
||||
button.label = this.bundle.formatStringFromName("update.downloadAndInstallButton.label", [updateVersion], 1);
|
||||
button.accessKey = this.bundle.GetStringFromName("update.downloadAndInstallButton.accesskey");
|
||||
}
|
||||
this.updateDeck.selectedPanel = panel;
|
||||
if (!document.commandDispatcher.focusedElement || // don't steal the focus
|
||||
document.commandDispatcher.focusedElement.localName == "button") // except from the other buttons
|
||||
button.focus();
|
||||
|
||||
} else {
|
||||
this.updateDeck.selectedPanel = panel;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check for updates
|
||||
*/
|
||||
checkForUpdates: function() {
|
||||
// Clear prefs that could prevent a user from discovering available updates.
|
||||
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) {
|
||||
Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
|
||||
}
|
||||
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
|
||||
Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
|
||||
}
|
||||
this.selectPanel("checkingForUpdates");
|
||||
this.isChecking = true;
|
||||
this.checker.checkForUpdates(this.updateCheckListener, true);
|
||||
// after checking, onCheckComplete() is called
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles oncommand for the "Restart to Update" button
|
||||
* which is presented after the download has been downloaded.
|
||||
*/
|
||||
buttonRestartAfterDownload: function() {
|
||||
if (!this.isPending && !this.isApplied) {
|
||||
return;
|
||||
}
|
||||
|
||||
gAppUpdater.selectPanel("restarting");
|
||||
|
||||
// Notify all windows that an application quit has been requested.
|
||||
let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
|
||||
createInstance(Components.interfaces.nsISupportsPRBool);
|
||||
Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
|
||||
|
||||
// Something aborted the quit process.
|
||||
if (cancelQuit.data) {
|
||||
gAppUpdater.selectPanel("apply");
|
||||
return;
|
||||
}
|
||||
|
||||
let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
|
||||
getService(Components.interfaces.nsIAppStartup);
|
||||
|
||||
// If already in safe mode restart in safe mode (bug 327119)
|
||||
if (Services.appinfo.inSafeMode) {
|
||||
appStartup.restartInSafeMode(Components.interfaces.nsIAppStartup.eAttemptQuit);
|
||||
return;
|
||||
}
|
||||
|
||||
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
|
||||
Components.interfaces.nsIAppStartup.eRestart);
|
||||
},
|
||||
|
||||
/**
|
||||
* Implements nsIUpdateCheckListener. The methods implemented by
|
||||
* nsIUpdateCheckListener are in a different scope from nsIIncrementalDownload
|
||||
* to make it clear which are used by each interface.
|
||||
*/
|
||||
updateCheckListener: {
|
||||
/**
|
||||
* See nsIUpdateService.idl
|
||||
*/
|
||||
onCheckComplete: function(aRequest, aUpdates, aUpdateCount) {
|
||||
gAppUpdater.isChecking = false;
|
||||
gAppUpdater.update = gAppUpdater.aus.
|
||||
selectUpdate(aUpdates, aUpdates.length);
|
||||
if (!gAppUpdater.update) {
|
||||
gAppUpdater.selectPanel("noUpdatesFound");
|
||||
return;
|
||||
}
|
||||
|
||||
if (gAppUpdater.update.unsupported) {
|
||||
if (gAppUpdater.update.detailsURL) {
|
||||
let unsupportedLink = document.getElementById("unsupportedLink");
|
||||
unsupportedLink.href = gAppUpdater.update.detailsURL;
|
||||
}
|
||||
gAppUpdater.selectPanel("unsupportedSystem");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gAppUpdater.aus.canApplyUpdates) {
|
||||
gAppUpdater.selectPanel("manualUpdate");
|
||||
return;
|
||||
}
|
||||
|
||||
if (gAppUpdater.updateAuto) // automatically download and install
|
||||
gAppUpdater.startDownload();
|
||||
else // ask
|
||||
gAppUpdater.selectPanel("downloadAndInstall");
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsIUpdateService.idl
|
||||
*/
|
||||
onError: function(aRequest, aUpdate) {
|
||||
// Errors in the update check are treated as no updates found. If the
|
||||
// update check fails repeatedly without a success the user will be
|
||||
// notified with the normal app update user interface so this is safe.
|
||||
gAppUpdater.isChecking = false;
|
||||
gAppUpdater.selectPanel("noUpdatesFound");
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsISupports.idl
|
||||
*/
|
||||
QueryInterface: function(aIID) {
|
||||
if (!aIID.equals(Components.interfaces.nsIUpdateCheckListener) &&
|
||||
!aIID.equals(Components.interfaces.nsISupports))
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
return this;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts the download of an update mar.
|
||||
*/
|
||||
startDownload: function() {
|
||||
if (!this.update)
|
||||
this.update = this.um.activeUpdate;
|
||||
this.update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
|
||||
this.update.setProperty("foregroundDownload", "true");
|
||||
|
||||
this.aus.pauseDownload();
|
||||
let state = this.aus.downloadUpdate(this.update, false);
|
||||
if (state == "failed") {
|
||||
this.selectPanel("downloadFailed");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setupDownloadingUI();
|
||||
},
|
||||
|
||||
/**
|
||||
* Switches to the UI responsible for tracking the download.
|
||||
*/
|
||||
setupDownloadingUI: function() {
|
||||
this.downloadStatus = document.getElementById("downloadStatus");
|
||||
this.downloadStatus.value =
|
||||
DownloadUtils.getTransferTotal(0, this.update.selectedPatch.size);
|
||||
this.selectPanel("downloading");
|
||||
this.aus.addDownloadListener(this);
|
||||
},
|
||||
|
||||
removeDownloadListener: function() {
|
||||
if (this.aus) {
|
||||
this.aus.removeDownloadListener(this);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsIRequestObserver.idl
|
||||
*/
|
||||
onStartRequest: function(aRequest, aContext) {
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsIRequestObserver.idl
|
||||
*/
|
||||
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
||||
switch (aStatusCode) {
|
||||
case Components.results.NS_ERROR_UNEXPECTED:
|
||||
if (this.update.selectedPatch.state == "download-failed" &&
|
||||
(this.update.isCompleteUpdate || this.update.patchCount != 2)) {
|
||||
// Verification error of complete patch, informational text is held in
|
||||
// the update object.
|
||||
this.removeDownloadListener();
|
||||
this.selectPanel("downloadFailed");
|
||||
break;
|
||||
}
|
||||
// Verification failed for a partial patch, complete patch is now
|
||||
// downloading so return early and do NOT remove the download listener!
|
||||
break;
|
||||
case Components.results.NS_BINDING_ABORTED:
|
||||
// Do not remove UI listener since the user may resume downloading again.
|
||||
break;
|
||||
case Components.results.NS_OK:
|
||||
this.removeDownloadListener();
|
||||
if (this.backgroundUpdateEnabled) {
|
||||
this.selectPanel("applying");
|
||||
let self = this;
|
||||
Services.obs.addObserver(function (aSubject, aTopic, aData) {
|
||||
// Update the UI when the background updater is finished
|
||||
let status = aData;
|
||||
if (status == "applied" || status == "applied-service" ||
|
||||
status == "pending" || status == "pending-service" ||
|
||||
status == "pending-elevate") {
|
||||
// If the update is successfully applied, or if the updater has
|
||||
// fallen back to non-staged updates, show the "Restart to Update"
|
||||
// button.
|
||||
self.selectPanel("apply");
|
||||
} else if (status == "failed") {
|
||||
// Background update has failed, let's show the UI responsible for
|
||||
// prompting the user to update manually.
|
||||
self.selectPanel("downloadFailed");
|
||||
} else if (status == "downloading") {
|
||||
// We've fallen back to downloading the full update because the
|
||||
// partial update failed to get staged in the background.
|
||||
// Therefore we need to keep our observer.
|
||||
self.setupDownloadingUI();
|
||||
return;
|
||||
}
|
||||
Services.obs.removeObserver(arguments.callee, "update-staged");
|
||||
}, "update-staged", false);
|
||||
} else {
|
||||
this.selectPanel("apply");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.removeDownloadListener();
|
||||
this.selectPanel("downloadFailed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsIProgressEventSink.idl
|
||||
*/
|
||||
onStatus: function(aRequest, aContext, aStatus, aStatusArg) {
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsIProgressEventSink.idl
|
||||
*/
|
||||
onProgress: function(aRequest, aContext, aProgress, aProgressMax) {
|
||||
this.downloadStatus.value =
|
||||
DownloadUtils.getTransferTotal(aProgress, aProgressMax);
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsISupports.idl
|
||||
*/
|
||||
QueryInterface: function(aIID) {
|
||||
if (!aIID.equals(Components.interfaces.nsIProgressEventSink) &&
|
||||
!aIID.equals(Components.interfaces.nsIRequestObserver) &&
|
||||
!aIID.equals(Components.interfaces.nsISupports))
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,97 +0,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/. */
|
||||
|
||||
#aboutDialog {
|
||||
width: 620px;
|
||||
/* Set an explicit line-height to avoid discrepancies in 'auto' spacing
|
||||
across screens with different device DPI, which may cause font metrics
|
||||
to round differently. */
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#rightBox {
|
||||
background-image: url("chrome://branding/content/about-wordmark.png");
|
||||
background-repeat: no-repeat;
|
||||
/* padding-top creates room for the wordmark */
|
||||
padding-top: 38px;
|
||||
margin-top:20px;
|
||||
}
|
||||
|
||||
#rightBox:-moz-locale-dir(rtl) {
|
||||
background-position: 100% 0;
|
||||
}
|
||||
|
||||
#bottomBox {
|
||||
padding: 15px 10px 0;
|
||||
}
|
||||
|
||||
#version {
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
margin-left: 0;
|
||||
-moz-user-select: text;
|
||||
-moz-user-focus: normal;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
#version:-moz-locale-dir(rtl) {
|
||||
direction: ltr;
|
||||
text-align: right;
|
||||
margin-left: 5px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#releasenotes {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#distribution,
|
||||
#distributionId {
|
||||
display: none;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.text-blurb {
|
||||
margin-bottom: 10px;
|
||||
margin-inline-start: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
#updateButton,
|
||||
#updateDeck > hbox > label {
|
||||
margin-inline-start: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
.update-throbber {
|
||||
width: 16px;
|
||||
min-height: 16px;
|
||||
margin-inline-end: 3px;
|
||||
list-style-image: url("chrome://global/skin/icons/loading.png");
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
.update-throbber {
|
||||
list-style-image: url("chrome://global/skin/icons/loading@2x.png");
|
||||
}
|
||||
}
|
||||
|
||||
description > .text-link,
|
||||
description > .text-link:focus {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.bottom-link,
|
||||
.bottom-link:focus {
|
||||
text-align: center;
|
||||
margin: 0 40px;
|
||||
}
|
||||
|
||||
#currentChannel {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
@ -1,91 +0,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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Services = object with smart getters for common XPCOM services
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/AppConstants.jsm");
|
||||
|
||||
function init(aEvent)
|
||||
{
|
||||
if (aEvent.target != document)
|
||||
return;
|
||||
|
||||
try {
|
||||
var distroId = Services.prefs.getCharPref("distribution.id");
|
||||
if (distroId) {
|
||||
var distroVersion = Services.prefs.getCharPref("distribution.version");
|
||||
|
||||
var distroIdField = document.getElementById("distributionId");
|
||||
distroIdField.value = distroId + " - " + distroVersion;
|
||||
distroIdField.style.display = "block";
|
||||
|
||||
try {
|
||||
// This is in its own try catch due to bug 895473 and bug 900925.
|
||||
var distroAbout = Services.prefs.getComplexValue("distribution.about",
|
||||
Components.interfaces.nsISupportsString);
|
||||
var distroField = document.getElementById("distribution");
|
||||
distroField.value = distroAbout;
|
||||
distroField.style.display = "block";
|
||||
}
|
||||
catch (ex) {
|
||||
// Pref is unset
|
||||
Components.utils.reportError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// Pref is unset
|
||||
}
|
||||
|
||||
// Include the build ID
|
||||
let versionField = document.getElementById("version");
|
||||
let version = Services.appinfo.version;
|
||||
let buildID = Services.appinfo.appBuildID;
|
||||
let year = buildID.slice(0, 4);
|
||||
let month = buildID.slice(4, 6);
|
||||
let day = buildID.slice(6, 8);
|
||||
let hour = buildID.slice(8, 10);
|
||||
let minute = buildID.slice(10, 12);
|
||||
if (Services.prefs.getBoolPref("general.useragent.appVersionIsBuildID")) {
|
||||
versionField.textContent = `${year}.${month}.${day}`;
|
||||
} else {
|
||||
versionField.textContent = `v` + version + ` (${year}-${month}-${day})`;
|
||||
}
|
||||
|
||||
// Display warning if this is an "a#" (nightly or aurora) build
|
||||
if (/a\d+$/.test(version)) {
|
||||
document.getElementById("experimental").hidden = false;
|
||||
document.getElementById("communityDesc").hidden = true;
|
||||
}
|
||||
|
||||
// Append "(32-bit)" or "(64-bit)" build architecture to the version number:
|
||||
let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
||||
let archResource = Services.appinfo.is64Bit
|
||||
? "aboutDialog.architecture.sixtyFourBit"
|
||||
: "aboutDialog.architecture.thirtyTwoBit";
|
||||
let arch = bundle.GetStringFromName(archResource);
|
||||
versionField.textContent += ` (${arch})`;
|
||||
|
||||
// Get Release Notes URL from Preferences
|
||||
let releaseNotesURL = Services.prefs.getCharPref("app.releaseNotesURL");
|
||||
document.getElementById("releasenotes").setAttribute("href", releaseNotesURL);
|
||||
|
||||
if (AppConstants.MOZ_UPDATER) {
|
||||
gAppUpdater = new appUpdater();
|
||||
|
||||
let channelLabel = document.getElementById("currentChannel");
|
||||
let currentChannelText = document.getElementById("currentChannelText");
|
||||
channelLabel.value = UpdateUtils.UpdateChannel;
|
||||
if (/^release($|\-)/.test(channelLabel.value))
|
||||
currentChannelText.hidden = true;
|
||||
}
|
||||
|
||||
if (AppConstants.platform == "macosx") {
|
||||
// it may not be sized at this point, and we need its width to calculate its position
|
||||
window.sizeToContent();
|
||||
window.moveTo((screen.availWidth / 2) - (window.outerWidth / 2), screen.availHeight / 5);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
<?xml version="1.0"?> <!-- -*- Mode: HTML -*- -->
|
||||
|
||||
# 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/content/aboutDialog.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://branding/content/aboutDialog.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % aboutDialogDTD SYSTEM "chrome://browser/locale/aboutDialog.dtd" >
|
||||
%aboutDialogDTD;
|
||||
]>
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
|
||||
#endif
|
||||
|
||||
<window xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
id="aboutDialog"
|
||||
windowtype="Browser:About"
|
||||
onload="init(event);"
|
||||
#ifdef MOZ_UPDATER
|
||||
onunload="onUnload(event);"
|
||||
#endif
|
||||
#ifdef XP_MACOSX
|
||||
inwindowmenu="false"
|
||||
#else
|
||||
title="&aboutDialog.title;"
|
||||
#endif
|
||||
role="dialog"
|
||||
aria-describedby="version distribution distributionId communityDesc contributeDesc trademark"
|
||||
>
|
||||
|
||||
<script type="application/javascript" src="chrome://browser/content/aboutDialog.js"/>
|
||||
#ifdef MOZ_UPDATER
|
||||
<script type="application/javascript" src="chrome://browser/content/aboutDialog-appUpdater.js"/>
|
||||
#endif
|
||||
<vbox id="aboutDialogContainer">
|
||||
<hbox id="clientBox">
|
||||
<vbox id="leftBox" flex="1"/>
|
||||
<vbox id="rightBox" flex="1">
|
||||
<hbox align="baseline">
|
||||
#expand <label id="version"></label>
|
||||
#ifndef NIGHTLY_BUILD
|
||||
<label id="releasenotes" class="text-link">&releaseNotes.link;</label>
|
||||
#endif
|
||||
</hbox>
|
||||
|
||||
<label id="distribution" class="text-blurb"/>
|
||||
<label id="distributionId" class="text-blurb"/>
|
||||
|
||||
<vbox id="detailsBox">
|
||||
<vbox id="updateBox">
|
||||
#ifdef MOZ_UPDATER
|
||||
<deck id="updateDeck" orient="vertical">
|
||||
<hbox id="checkForUpdates" align="center">
|
||||
<button id="checkForUpdatesButton" align="start"
|
||||
label="&update.checkForUpdatesButton.label;"
|
||||
accesskey="&update.checkForUpdatesButton.accesskey;"
|
||||
oncommand="gAppUpdater.checkForUpdates();"/>
|
||||
<spacer flex="1"/>
|
||||
</hbox>
|
||||
<hbox id="downloadAndInstall" align="center">
|
||||
<button id="downloadAndInstallButton" align="start"
|
||||
oncommand="gAppUpdater.startDownload();"/>
|
||||
<!-- label and accesskey will be filled by JS -->
|
||||
<spacer flex="1"/>
|
||||
</hbox>
|
||||
<hbox id="apply" align="center">
|
||||
<button id="updateButton" align="start"
|
||||
label="&update.updateButton.label2;"
|
||||
accesskey="&update.updateButton.accesskey;"
|
||||
oncommand="gAppUpdater.buttonRestartAfterDownload();"/>
|
||||
<spacer flex="1"/>
|
||||
</hbox>
|
||||
<hbox id="checkingForUpdates" align="center">
|
||||
<image class="update-throbber"/><label>&update.checkingForUpdates;</label>
|
||||
</hbox>
|
||||
<hbox id="downloading" align="center">
|
||||
<image class="update-throbber"/><label>&update.downloading.start;</label><label id="downloadStatus"/><label>&update.downloading.end;</label>
|
||||
</hbox>
|
||||
<hbox id="applying" align="center">
|
||||
<image class="update-throbber"/><label>&update.applying;</label>
|
||||
</hbox>
|
||||
<hbox id="downloadFailed" align="center">
|
||||
<label>&update.failed.start;</label><label id="failedLink" class="text-link">&update.failed.linkText;</label><label>&update.failed.end;</label>
|
||||
</hbox>
|
||||
<hbox id="adminDisabled" align="center">
|
||||
<label>&update.adminDisabled;</label>
|
||||
</hbox>
|
||||
<hbox id="noUpdatesFound" align="center">
|
||||
<label>&update.noUpdatesFound;</label>
|
||||
</hbox>
|
||||
<hbox id="otherInstanceHandlingUpdates" align="center">
|
||||
<label>&update.otherInstanceHandlingUpdates;</label>
|
||||
</hbox>
|
||||
<hbox id="manualUpdate" align="center">
|
||||
<label>&update.manual.start;</label><label id="manualLink" class="text-link"/><label>&update.manual.end;</label>
|
||||
</hbox>
|
||||
<hbox id="unsupportedSystem" align="center">
|
||||
<label>&update.unsupported.start;</label><label id="unsupportedLink" class="text-link">&update.unsupported.linkText;</label><label>&update.unsupported.end;</label>
|
||||
</hbox>
|
||||
<hbox id="restarting" align="center">
|
||||
<label>&update.restarting;</label>
|
||||
</hbox>
|
||||
</deck>
|
||||
#endif
|
||||
</vbox>
|
||||
|
||||
#ifdef MOZ_UPDATER
|
||||
<description class="text-blurb" id="currentChannelText">
|
||||
&channel.description.start;<label id="currentChannel"/>&channel.description.end;
|
||||
</description>
|
||||
#endif
|
||||
<vbox id="experimental" hidden="true">
|
||||
<description class="text-blurb" id="warningDesc">
|
||||
&warningDesc.version;
|
||||
</description>
|
||||
</vbox>
|
||||
#ifdef MC_PRIVATE_BUILD
|
||||
<description class="text-blurb" id="communityDesc">
|
||||
This is a private build of Basilisk. If you did not manually build this copy from source yourself, then please download an official version from the <label class="text-link" href="http://www.basilisk-browser.org/">Basilisk website</label>.
|
||||
</description>
|
||||
#else
|
||||
<description class="text-blurb" id="communityDesc">
|
||||
Basilisk is community software released by <label class="text-link" href="http://www.palemoon.org/">the Pale Moon team</label> and Mozilla developers. Learn <label class="text-link" useoriginprincipal="true" href="about:credits">who contributed</label> to this software.
|
||||
</description>
|
||||
<description class="text-blurb" id="contributeDesc">
|
||||
Want to help? Please consider <label class="text-link" href="https://www.palemoon.org/donations.shtml">donating</label> or get involved with our <label class="text-link" href="https://github.com/MoonchildProductions/UXP">development</label> of the Unified XUL Platform.
|
||||
</description>
|
||||
#endif
|
||||
</vbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<vbox id="bottomBox">
|
||||
<hbox pack="center">
|
||||
<label class="text-link bottom-link" useoriginprincipal="true" href="about:license">&bottomLinks.license;</label>
|
||||
<label class="text-link bottom-link" useoriginprincipal="true" href="about:rights">&bottomLinks.rights;</label>
|
||||
<label class="text-link bottom-link" href="https://www.palemoon.org/privacy.shtml">&bottomLinks.privacy;</label>
|
||||
</hbox>
|
||||
<description id="trademark">&trademarkInfo.part1;</description>
|
||||
</vbox>
|
||||
</vbox>
|
||||
|
||||
<keyset>
|
||||
<key keycode="VK_ESCAPE" oncommand="window.close();"/>
|
||||
</keyset>
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
#include browserMountPoints.inc
|
||||
#endif
|
||||
</window>
|
||||
|
|
@ -1,699 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % netErrorDTD
|
||||
SYSTEM "chrome://global/locale/netError.dtd">
|
||||
%netErrorDTD;
|
||||
<!ENTITY % globalDTD
|
||||
SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
]>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&loadError.label;</title>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutNetError.css" type="text/css" media="all" />
|
||||
<!-- If the location of the favicon is changed here, the FAVICON_ERRORPAGE_URL symbol in
|
||||
toolkit/components/places/src/nsFaviconService.h should be updated. -->
|
||||
<link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/warning-16.png"/>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
// The following parameters are parsed from the error URL:
|
||||
// e - the error code
|
||||
// s - custom CSS class to allow alternate styling/favicons
|
||||
// d - error description
|
||||
// captive - "true" to indicate we're behind a captive portal.
|
||||
// Any other value is ignored.
|
||||
|
||||
// Note that this file uses document.documentURI to get
|
||||
// the URL (with the format from above). This is because
|
||||
// document.location.href gets the current URI off the docshell,
|
||||
// which is the URL displayed in the location bar, i.e.
|
||||
// the URI that the user attempted to load.
|
||||
|
||||
let searchParams = new URLSearchParams(document.documentURI.split("?")[1]);
|
||||
|
||||
// Set to true on init if the error code is nssBadCert.
|
||||
let gIsCertError;
|
||||
|
||||
function getErrorCode()
|
||||
{
|
||||
return searchParams.get("e");
|
||||
}
|
||||
|
||||
function getCSSClass()
|
||||
{
|
||||
return searchParams.get("s");
|
||||
}
|
||||
|
||||
function getDescription()
|
||||
{
|
||||
return searchParams.get("d");
|
||||
}
|
||||
|
||||
function isCaptive() {
|
||||
return searchParams.get("captive") == "true";
|
||||
}
|
||||
|
||||
function retryThis(buttonEl)
|
||||
{
|
||||
// Note: The application may wish to handle switching off "offline mode"
|
||||
// before this event handler runs, but using a capturing event handler.
|
||||
|
||||
// Session history has the URL of the page that failed
|
||||
// to load, not the one of the error page. So, just call
|
||||
// reload(), which will also repost POST data correctly.
|
||||
try {
|
||||
location.reload();
|
||||
} catch (e) {
|
||||
// We probably tried to reload a URI that caused an exception to
|
||||
// occur; e.g. a nonexistent file.
|
||||
}
|
||||
|
||||
buttonEl.disabled = true;
|
||||
}
|
||||
|
||||
function doOverride(buttonEl) {
|
||||
var event = new CustomEvent("AboutNetErrorOverride", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
retryThis(buttonEl);
|
||||
}
|
||||
|
||||
function toggleDisplay(node) {
|
||||
const toggle = {
|
||||
"": "block",
|
||||
"none": "block",
|
||||
"block": "none"
|
||||
};
|
||||
return (node.style.display = toggle[node.style.display]);
|
||||
}
|
||||
|
||||
function showCertificateErrorReporting() {
|
||||
// Display error reporting UI
|
||||
document.getElementById("certificateErrorReporting").style.display = "block";
|
||||
}
|
||||
|
||||
function showPrefChangeContainer() {
|
||||
const panel = document.getElementById("prefChangeContainer");
|
||||
panel.style.display = "block";
|
||||
document.getElementById("netErrorButtonContainer").style.display = "none";
|
||||
document.getElementById("prefResetButton").addEventListener("click", function resetPreferences(e) {
|
||||
const event = new CustomEvent("AboutNetErrorResetPreferences", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
addAutofocus("prefResetButton", "beforeend");
|
||||
}
|
||||
|
||||
function setupAdvancedButton(allowOverride) {
|
||||
// Get the hostname and add it to the panel
|
||||
var panelId = gIsCertError ? "badCertAdvancedPanel" : "weakCryptoAdvancedPanel";
|
||||
var panel = document.getElementById(panelId);
|
||||
for (var span of panel.querySelectorAll("span.hostname")) {
|
||||
span.textContent = document.location.hostname;
|
||||
}
|
||||
if (!gIsCertError) {
|
||||
panel.replaceChild(document.getElementById("errorLongDesc"),
|
||||
document.getElementById("advancedLongDesc"));
|
||||
}
|
||||
|
||||
// Register click handler for the weakCryptoAdvancedPanel
|
||||
document.getElementById("advancedButton")
|
||||
.addEventListener("click", function togglePanelVisibility() {
|
||||
toggleDisplay(panel);
|
||||
if (gIsCertError) {
|
||||
// Toggling the advanced panel must ensure that the debugging
|
||||
// information panel is hidden as well, since it's opened by the
|
||||
// error code link in the advanced panel.
|
||||
var div = document.getElementById("certificateErrorDebugInformation");
|
||||
div.style.display = "none";
|
||||
}
|
||||
|
||||
if (panel.style.display == "block") {
|
||||
// send event to trigger telemetry ping
|
||||
var event = new CustomEvent("AboutNetErrorUIExpanded", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
});
|
||||
|
||||
if (allowOverride) {
|
||||
document.getElementById("overrideWeakCryptoPanel").style.display = "flex";
|
||||
var overrideLink = document.getElementById("overrideWeakCrypto");
|
||||
overrideLink.addEventListener("click", () => doOverride(overrideLink), false);
|
||||
}
|
||||
if (!gIsCertError) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getCSSClass() == "expertBadCert") {
|
||||
toggleDisplay(document.getElementById("badCertAdvancedPanel"));
|
||||
// Toggling the advanced panel must ensure that the debugging
|
||||
// information panel is hidden as well, since it's opened by the
|
||||
// error code link in the advanced panel.
|
||||
var div = document.getElementById("certificateErrorDebugInformation");
|
||||
div.style.display = "none";
|
||||
}
|
||||
|
||||
disallowCertOverridesIfNeeded();
|
||||
|
||||
document.getElementById("badCertTechnicalInfo").textContent = getDescription();
|
||||
}
|
||||
|
||||
function disallowCertOverridesIfNeeded() {
|
||||
var cssClass = getCSSClass();
|
||||
// Disallow overrides if this is a Strict-Transport-Security
|
||||
// host and the cert is bad (STS Spec section 7.3) or if the
|
||||
// certerror is in a frame (bug 633691).
|
||||
if (cssClass == "badStsCert" || window != top) {
|
||||
document.getElementById("exceptionDialogButton").setAttribute("hidden", "true");
|
||||
}
|
||||
if (cssClass == "badStsCert") {
|
||||
document.getElementById("badStsCertExplanation").removeAttribute("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function initPage()
|
||||
{
|
||||
var err = getErrorCode();
|
||||
gIsCertError = (err == "nssBadCert");
|
||||
// Only worry about captive portals if this is a cert error.
|
||||
let showCaptivePortalUI = isCaptive() && gIsCertError;
|
||||
if (showCaptivePortalUI) {
|
||||
err = "captivePortal";
|
||||
}
|
||||
|
||||
// if it's an unknown error or there's no title or description
|
||||
// defined, get the generic message
|
||||
var errTitle = document.getElementById("et_" + err);
|
||||
var errDesc = document.getElementById("ed_" + err);
|
||||
if (!errTitle || !errDesc)
|
||||
{
|
||||
errTitle = document.getElementById("et_generic");
|
||||
errDesc = document.getElementById("ed_generic");
|
||||
}
|
||||
|
||||
document.querySelector(".title-text").innerHTML = errTitle.innerHTML;
|
||||
|
||||
var sd = document.getElementById("errorShortDescText");
|
||||
if (sd) {
|
||||
if (gIsCertError) {
|
||||
sd.innerHTML = errDesc.innerHTML;
|
||||
}
|
||||
else {
|
||||
sd.textContent = getDescription();
|
||||
}
|
||||
}
|
||||
if (showCaptivePortalUI) {
|
||||
initPageCaptivePortal();
|
||||
return;
|
||||
}
|
||||
if (gIsCertError) {
|
||||
initPageCertError();
|
||||
return;
|
||||
}
|
||||
addAutofocus("errorTryAgain");
|
||||
|
||||
document.body.className = "neterror";
|
||||
|
||||
var ld = document.getElementById("errorLongDesc");
|
||||
if (ld)
|
||||
{
|
||||
ld.innerHTML = errDesc.innerHTML;
|
||||
}
|
||||
|
||||
if (err == "sslv3Used") {
|
||||
document.getElementById("learnMoreContainer").style.display = "block";
|
||||
var learnMoreLink = document.getElementById("learnMoreLink");
|
||||
learnMoreLink.href = "https://support.mozilla.org/kb/how-resolve-sslv3-error-messages-firefox";
|
||||
document.body.className = "certerror";
|
||||
}
|
||||
|
||||
if (err == "weakCryptoUsed") {
|
||||
document.body.className = "certerror";
|
||||
}
|
||||
|
||||
// remove undisplayed errors to avoid bug 39098
|
||||
var errContainer = document.getElementById("errorContainer");
|
||||
errContainer.parentNode.removeChild(errContainer);
|
||||
|
||||
var className = getCSSClass();
|
||||
if (className && className != "expertBadCert") {
|
||||
// Associate a CSS class with the root of the page, if one was passed in,
|
||||
// to allow custom styling.
|
||||
// Not "expertBadCert" though, don't want to deal with the favicon
|
||||
document.documentElement.className = className;
|
||||
|
||||
// Also, if they specified a CSS class, they must supply their own
|
||||
// favicon. In order to trigger the browser to repaint though, we
|
||||
// need to remove/add the link element.
|
||||
var favicon = document.getElementById("favicon");
|
||||
var faviconParent = favicon.parentNode;
|
||||
faviconParent.removeChild(favicon);
|
||||
favicon.setAttribute("href", "chrome://global/skin/icons/" + className + "_favicon.png");
|
||||
faviconParent.appendChild(favicon);
|
||||
}
|
||||
|
||||
if (err == "remoteXUL") {
|
||||
// Remove the "Try again" button for remote XUL errors given that
|
||||
// it is useless.
|
||||
document.getElementById("netErrorButtonContainer").style.display = "none";
|
||||
}
|
||||
|
||||
if (err == "cspBlocked") {
|
||||
// Remove the "Try again" button for CSP violations, since it's
|
||||
// almost certainly useless. (Bug 553180)
|
||||
document.getElementById("netErrorButtonContainer").style.display = "none";
|
||||
}
|
||||
|
||||
window.addEventListener("AboutNetErrorOptions", function(evt) {
|
||||
// Pinning errors are of type nssFailure2
|
||||
if (getErrorCode() == "nssFailure2" || getErrorCode() == "weakCryptoUsed") {
|
||||
document.getElementById("learnMoreContainer").style.display = "block";
|
||||
var learnMoreLink = document.getElementById("learnMoreLink");
|
||||
// nssFailure2 also gets us other non-overrideable errors. Choose
|
||||
// a "learn more" link based on description:
|
||||
if (getDescription().includes("mozilla_pkix_error_key_pinning_failure")) {
|
||||
learnMoreLink.href = "https://support.mozilla.org/kb/certificate-pinning-reports";
|
||||
}
|
||||
if (getErrorCode() == "weakCryptoUsed") {
|
||||
learnMoreLink.href = "https://support.mozilla.org/kb/how-resolve-weak-crypto-error-messages-firefox";
|
||||
}
|
||||
|
||||
var options = JSON.parse(evt.detail);
|
||||
if (options && options.enabled) {
|
||||
var checkbox = document.getElementById("automaticallyReportInFuture");
|
||||
showCertificateErrorReporting();
|
||||
if (options.automatic) {
|
||||
// set the checkbox
|
||||
checkbox.checked = true;
|
||||
}
|
||||
|
||||
checkbox.addEventListener("change", function(evt) {
|
||||
var event = new CustomEvent("AboutNetErrorSetAutomatic",
|
||||
{bubbles:true, detail:evt.target.checked});
|
||||
document.dispatchEvent(event);
|
||||
}, false);
|
||||
}
|
||||
const hasPrefStyleError = [
|
||||
"interrupted", // This happens with subresources that are above the max tls
|
||||
"SSL_ERROR_PROTOCOL_VERSION_ALERT",
|
||||
"SSL_ERROR_UNSUPPORTED_VERSION",
|
||||
"SSL_ERROR_NO_CYPHER_OVERLAP",
|
||||
"SSL_ERROR_NO_CIPHERS_SUPPORTED"
|
||||
].some((substring) => getDescription().includes(substring));
|
||||
// If it looks like an error that is user config based
|
||||
if (getErrorCode() == "nssFailure2" && hasPrefStyleError && options && options.changedCertPrefs) {
|
||||
showPrefChangeContainer();
|
||||
}
|
||||
}
|
||||
if (getErrorCode() == "weakCryptoUsed" || getErrorCode() == "sslv3Used") {
|
||||
setupAdvancedButton(getErrorCode() == "weakCryptoUsed");
|
||||
}
|
||||
}.bind(this), true, true);
|
||||
|
||||
var event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
|
||||
if (err == "inadequateSecurityError") {
|
||||
// Remove the "Try again" button for HTTP/2 inadequate security as it
|
||||
// is useless.
|
||||
document.getElementById("errorTryAgain").style.display = "none";
|
||||
|
||||
var container = document.getElementById("errorLongDesc");
|
||||
for (var span of container.querySelectorAll("span.hostname")) {
|
||||
span.textContent = document.location.hostname;
|
||||
}
|
||||
}
|
||||
|
||||
addDomainErrorLinks();
|
||||
}
|
||||
|
||||
function initPageCaptivePortal()
|
||||
{
|
||||
document.body.className = "captiveportal";
|
||||
document.title = document.getElementById("captivePortalPageTitle").textContent;
|
||||
|
||||
document.getElementById("openPortalLoginPageButton")
|
||||
.addEventListener("click", () => {
|
||||
let event = new CustomEvent("AboutNetErrorOpenCaptivePortal", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
|
||||
addAutofocus("openPortalLoginPageButton");
|
||||
setupAdvancedButton(true);
|
||||
|
||||
addDomainErrorLinks();
|
||||
|
||||
// When the portal is freed, an event is generated by the frame script
|
||||
// that we can pick up and attempt to reload the original page.
|
||||
window.addEventListener("AboutNetErrorCaptivePortalFreed", () => {
|
||||
document.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function initPageCertError() {
|
||||
document.body.className = "certerror";
|
||||
document.title = document.getElementById("certErrorPageTitle").textContent;
|
||||
for (let host of document.querySelectorAll(".hostname")) {
|
||||
host.textContent = document.location.hostname;
|
||||
}
|
||||
|
||||
addAutofocus("returnButton");
|
||||
setupAdvancedButton(true);
|
||||
|
||||
document.getElementById("learnMoreContainer").style.display = "block";
|
||||
|
||||
let checkbox = document.getElementById("automaticallyReportInFuture");
|
||||
checkbox.addEventListener("change", function({target: {checked}}) {
|
||||
document.dispatchEvent(new CustomEvent("AboutNetErrorSetAutomatic", {
|
||||
detail: checked,
|
||||
bubbles: true
|
||||
}));
|
||||
});
|
||||
|
||||
addEventListener("AboutNetErrorOptions", function(event) {
|
||||
var options = JSON.parse(event.detail);
|
||||
if (options && options.enabled) {
|
||||
// Display error reporting UI
|
||||
document.getElementById("certificateErrorReporting").style.display = "block";
|
||||
|
||||
// set the checkbox
|
||||
checkbox.checked = !!options.automatic;
|
||||
}
|
||||
}, true, true);
|
||||
|
||||
let event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
|
||||
document.getElementById("advancedButton").dispatchEvent(event);
|
||||
|
||||
addDomainErrorLinks();
|
||||
}
|
||||
|
||||
/* Only do autofocus if we're the toplevel frame; otherwise we
|
||||
don't want to call attention to ourselves! The key part is
|
||||
that autofocus happens on insertion into the tree, so we
|
||||
can remove the button, add @autofocus, and reinsert the
|
||||
button.
|
||||
*/
|
||||
function addAutofocus(buttonId, position = "afterbegin") {
|
||||
if (window.top == window) {
|
||||
var button = document.getElementById(buttonId);
|
||||
var parent = button.parentNode;
|
||||
button.remove();
|
||||
button.setAttribute("autofocus", "true");
|
||||
parent.insertAdjacentElement(position, button);
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to preserve the links contained in the error description, like
|
||||
the error code.
|
||||
|
||||
Also, in the case of SSL error pages about domain mismatch, see if
|
||||
we can hyperlink the user to the correct site. We don't want
|
||||
to do this generically since it allows MitM attacks to redirect
|
||||
users to a site under attacker control, but in certain cases
|
||||
it is safe (and helpful!) to do so. Bug 402210
|
||||
*/
|
||||
function addDomainErrorLinks() {
|
||||
// Rather than textContent, we need to treat description as HTML
|
||||
var sdid = gIsCertError ? "badCertTechnicalInfo" : "errorShortDescText";
|
||||
var sd = document.getElementById(sdid);
|
||||
if (sd) {
|
||||
var desc = getDescription();
|
||||
|
||||
// sanitize description text - see bug 441169
|
||||
|
||||
// First, find the index of the <a> tags we care about, being
|
||||
// careful not to use an over-greedy regex.
|
||||
var codeRe = /<a id="errorCode" title="([^"]+)">/;
|
||||
var codeResult = codeRe.exec(desc);
|
||||
var domainRe = /<a id="cert_domain_link" title="([^"]+)">/;
|
||||
var domainResult = domainRe.exec(desc);
|
||||
|
||||
// The order of these links in the description is fixed in
|
||||
// TransportSecurityInfo.cpp:formatOverridableCertErrorMessage.
|
||||
var firstResult = domainResult;
|
||||
if (!domainResult)
|
||||
firstResult = codeResult;
|
||||
if (!firstResult)
|
||||
return;
|
||||
// Remove sd's existing children
|
||||
sd.textContent = "";
|
||||
|
||||
// Everything up to the first link should be text content.
|
||||
sd.appendChild(document.createTextNode(desc.slice(0, firstResult.index)));
|
||||
|
||||
// Now create the actual links.
|
||||
if (domainResult) {
|
||||
createLink(sd, "cert_domain_link", domainResult[1])
|
||||
// Append text for anything between the two links.
|
||||
sd.appendChild(document.createTextNode(desc.slice(desc.indexOf("</a>") + "</a>".length, codeResult.index)));
|
||||
}
|
||||
createLink(sd, "errorCode", codeResult[1])
|
||||
|
||||
// Finally, append text for anything after the last closing </a>.
|
||||
sd.appendChild(document.createTextNode(desc.slice(desc.lastIndexOf("</a>") + "</a>".length)));
|
||||
}
|
||||
|
||||
if (gIsCertError) {
|
||||
// Initialize the error code link embedded in the error message to
|
||||
// display debug information about the cert error.
|
||||
var errorCode = document.getElementById("errorCode");
|
||||
if (errorCode) {
|
||||
errorCode.href = "javascript:void(0)";
|
||||
errorCode.addEventListener("click", () => {
|
||||
let debugInfo = document.getElementById("certificateErrorDebugInformation");
|
||||
debugInfo.style.display = "block";
|
||||
debugInfo.scrollIntoView({block: "start", behavior: "smooth"});
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the cert domain link.
|
||||
var link = document.getElementById("cert_domain_link");
|
||||
if (!link)
|
||||
return;
|
||||
|
||||
var okHost = link.getAttribute("title");
|
||||
var thisHost = document.location.hostname;
|
||||
var proto = document.location.protocol;
|
||||
|
||||
// If okHost is a wildcard domain ("*.example.com") let's
|
||||
// use "www" instead. "*.example.com" isn't going to
|
||||
// get anyone anywhere useful. bug 432491
|
||||
okHost = okHost.replace(/^\*\./, "www.");
|
||||
|
||||
/* case #1:
|
||||
* example.com uses an invalid security certificate.
|
||||
*
|
||||
* The certificate is only valid for www.example.com
|
||||
*
|
||||
* Make sure to include the "." ahead of thisHost so that
|
||||
* a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
|
||||
*
|
||||
* We'd normally just use a RegExp here except that we lack a
|
||||
* library function to escape them properly (bug 248062), and
|
||||
* domain names are famous for having '.' characters in them,
|
||||
* which would allow spurious and possibly hostile matches.
|
||||
*/
|
||||
if (okHost.endsWith("." + thisHost))
|
||||
link.href = proto + okHost;
|
||||
|
||||
/* case #2:
|
||||
* browser.garage.maemo.org uses an invalid security certificate.
|
||||
*
|
||||
* The certificate is only valid for garage.maemo.org
|
||||
*/
|
||||
if (thisHost.endsWith("." + okHost))
|
||||
link.href = proto + okHost;
|
||||
|
||||
// If we set a link, meaning there's something helpful for
|
||||
// the user here, expand the section by default
|
||||
if (link.href && getCSSClass() != "expertBadCert") {
|
||||
var panelId = gIsCertError ? "badCertAdvancedPanel" : "weakCryptoAdvancedPanel"
|
||||
toggleDisplay(document.getElementById(panelId));
|
||||
if (gIsCertError) {
|
||||
// Toggling the advanced panel must ensure that the debugging
|
||||
// information panel is hidden as well, since it's opened by the
|
||||
// error code link in the advanced panel.
|
||||
var div = document.getElementById("certificateErrorDebugInformation");
|
||||
div.style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createLink(el, id, text) {
|
||||
var anchorEl = document.createElement("a");
|
||||
anchorEl.setAttribute("id", id);
|
||||
anchorEl.setAttribute("title", text);
|
||||
anchorEl.appendChild(document.createTextNode(text));
|
||||
el.appendChild(anchorEl);
|
||||
}
|
||||
]]></script>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
<!-- Contains an alternate page title set on page init for cert errors. -->
|
||||
<div id="certErrorPageTitle" style="display: none;">&certerror.pagetitle1;</div>
|
||||
<div id="captivePortalPageTitle" style="display: none;">&captivePortal.title;</div>
|
||||
|
||||
<!-- ERROR ITEM CONTAINER (removed during loading to avoid bug 39098) -->
|
||||
<div id="errorContainer">
|
||||
<div id="errorTitlesContainer">
|
||||
<h1 id="et_generic">&generic.title;</h1>
|
||||
<h1 id="et_captivePortal">&captivePortal.title;</h1>
|
||||
<h1 id="et_dnsNotFound">&dnsNotFound.title;</h1>
|
||||
<h1 id="et_fileNotFound">&fileNotFound.title;</h1>
|
||||
<h1 id="et_fileAccessDenied">&fileAccessDenied.title;</h1>
|
||||
<h1 id="et_malformedURI">&malformedURI.title;</h1>
|
||||
<h1 id="et_unknownProtocolFound">&unknownProtocolFound.title;</h1>
|
||||
<h1 id="et_connectionFailure">&connectionFailure.title;</h1>
|
||||
<h1 id="et_netTimeout">&netTimeout.title;</h1>
|
||||
<h1 id="et_redirectLoop">&redirectLoop.title;</h1>
|
||||
<h1 id="et_unknownSocketType">&unknownSocketType.title;</h1>
|
||||
<h1 id="et_netReset">&netReset.title;</h1>
|
||||
<h1 id="et_notCached">¬Cached.title;</h1>
|
||||
<h1 id="et_netOffline">&netOffline.title;</h1>
|
||||
<h1 id="et_netInterrupt">&netInterrupt.title;</h1>
|
||||
<h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
|
||||
<h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
|
||||
<h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
|
||||
<h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
|
||||
<h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
|
||||
<h1 id="et_nssFailure2">&nssFailure2.title;</h1>
|
||||
<h1 id="et_nssBadCert">&certerror.longpagetitle1;</h1>
|
||||
<h1 id="et_cspBlocked">&cspBlocked.title;</h1>
|
||||
<h1 id="et_remoteXUL">&remoteXUL.title;</h1>
|
||||
<h1 id="et_corruptedContentErrorv2">&corruptedContentErrorv2.title;</h1>
|
||||
<h1 id="et_sslv3Used">&sslv3Used.title;</h1>
|
||||
<h1 id="et_weakCryptoUsed">&weakCryptoUsed.title;</h1>
|
||||
<h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1>
|
||||
</div>
|
||||
<div id="errorDescriptionsContainer">
|
||||
<div id="ed_generic">&generic.longDesc;</div>
|
||||
<div id="ed_captivePortal">&captivePortal.longDesc;</div>
|
||||
<div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
|
||||
<div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
|
||||
<div id="ed_fileAccessDenied">&fileAccessDenied.longDesc;</div>
|
||||
<div id="ed_malformedURI">&malformedURI.longDesc;</div>
|
||||
<div id="ed_unknownProtocolFound">&unknownProtocolFound.longDesc;</div>
|
||||
<div id="ed_connectionFailure">&connectionFailure.longDesc;</div>
|
||||
<div id="ed_netTimeout">&netTimeout.longDesc;</div>
|
||||
<div id="ed_redirectLoop">&redirectLoop.longDesc;</div>
|
||||
<div id="ed_unknownSocketType">&unknownSocketType.longDesc;</div>
|
||||
<div id="ed_netReset">&netReset.longDesc;</div>
|
||||
<div id="ed_notCached">¬Cached.longDesc;</div>
|
||||
<div id="ed_netOffline">&netOffline.longDesc2;</div>
|
||||
<div id="ed_netInterrupt">&netInterrupt.longDesc;</div>
|
||||
<div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
|
||||
<div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
|
||||
<div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
|
||||
<div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
|
||||
<div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
|
||||
<div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
|
||||
<div id="ed_nssBadCert">&certerror.introPara;</div>
|
||||
<div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
|
||||
<div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
|
||||
<div id="ed_corruptedContentErrorv2">&corruptedContentErrorv2.longDesc;</div>
|
||||
<div id="ed_sslv3Used">&sslv3Used.longDesc2;</div>
|
||||
<div id="ed_weakCryptoUsed">&weakCryptoUsed.longDesc2;</div>
|
||||
<div id="ed_inadequateSecurityError">&inadequateSecurityError.longDesc;</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PAGE CONTAINER (for styling purposes only) -->
|
||||
<div id="errorPageContainer" class="container">
|
||||
|
||||
<!-- Error Title -->
|
||||
<div class="title">
|
||||
<h1 class="title-text"/>
|
||||
</div>
|
||||
|
||||
<!-- LONG CONTENT (the section most likely to require scrolling) -->
|
||||
<div id="errorLongContent">
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorShortDesc">
|
||||
<p id="errorShortDescText" />
|
||||
</div>
|
||||
<p id="badStsCertExplanation" hidden="true">&certerror.whatShouldIDo.badStsCertExplanation;</p>
|
||||
|
||||
<div id="wrongSystemTimePanel" style="display: none;">
|
||||
&certerror.wrongSystemTime;
|
||||
</div>
|
||||
|
||||
<!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
|
||||
<div id="errorLongDesc" />
|
||||
|
||||
<div id="learnMoreContainer">
|
||||
<p><a href="https://support.mozilla.org/kb/what-does-your-connection-is-not-secure-mean" id="learnMoreLink" target="new">&errorReporting.learnMore;</a></p>
|
||||
</div>
|
||||
|
||||
<div id="prefChangeContainer" class="button-container">
|
||||
<p>&prefReset.longDesc;</p>
|
||||
<button id="prefResetButton" class="primary" autocomplete="off">&prefReset.label;</button>
|
||||
</div>
|
||||
|
||||
<div id="certErrorAndCaptivePortalButtonContainer" class="button-container">
|
||||
<button id="returnButton" class="primary" autocomplete="off">&returnToPreviousPage.label;</button>
|
||||
<button id="openPortalLoginPageButton" class="primary" autocomplete="off">&openPortalLoginPage.label;</button>
|
||||
<div class="button-spacer"></div>
|
||||
<button id="advancedButton" autocomplete="off">&advanced.label;</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="netErrorButtonContainer" class="button-container">
|
||||
<button id="errorTryAgain" class="primary" autocomplete="off" onclick="retryThis(this);">&retry.label;</button>
|
||||
</div>
|
||||
|
||||
<!-- UI for option to report certificate errors to Mozilla. Removed on
|
||||
init for other error types .-->
|
||||
<div id="certificateErrorReporting">
|
||||
<p class="toggle-container-with-text">
|
||||
<input type="checkbox" id="automaticallyReportInFuture" />
|
||||
<label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic2;</label>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="advancedPanelContainer">
|
||||
<div id="weakCryptoAdvancedPanel" class="advanced-panel">
|
||||
<div id="weakCryptoAdvancedDescription">
|
||||
<p>&weakCryptoAdvanced.longDesc;</p>
|
||||
</div>
|
||||
<div id="advancedLongDesc" />
|
||||
<div id="overrideWeakCryptoPanel">
|
||||
<a id="overrideWeakCrypto" href="#">&weakCryptoAdvanced.override;</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="badCertAdvancedPanel" class="advanced-panel">
|
||||
<p id="badCertTechnicalInfo"/>
|
||||
<button id="exceptionDialogButton">&securityOverride.exceptionButtonLabel;</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="certificateErrorDebugInformation">
|
||||
<button id="copyToClipboard">&certerror.copyToClipboard.label;</button>
|
||||
<div id="certificateErrorText"/>
|
||||
<button id="copyToClipboard">&certerror.copyToClipboard.label;</button>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
- Note: It is important to run the script this way, instead of using
|
||||
- an onload handler. This is because error pages are loaded as
|
||||
- LOAD_BACKGROUND, which means that onload handlers will not be executed.
|
||||
-->
|
||||
<script type="application/javascript">
|
||||
initPage();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
|
@ -1,108 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % netErrorDTD
|
||||
SYSTEM "chrome://global/locale/netError.dtd">
|
||||
%netErrorDTD;
|
||||
<!ENTITY % globalDTD
|
||||
SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % aboutrobotsDTD
|
||||
SYSTEM "chrome://browser/locale/aboutRobots.dtd">
|
||||
%aboutrobotsDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&robots.pagetitle;</title>
|
||||
<link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
|
||||
<link rel="icon" type="image/png" id="favicon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8%2F9hAAAACGFjVEwAAAASAAAAAJNtBPIAAAAaZmNUTAAAAAAAAAAQAAAAEAAAAAAAAAAALuAD6AABhIDeugAAALhJREFUOI2Nk8sNxCAMRDlGohauXFOMpfTiAlxICqAELltHLqlgctg1InzMRhpFAc%2BLGWTnmoeZYamt78zXdZmaQtQMADlnU0OIAlbmJUBEcO4bRKQY2rUXIPmAGnDuG%2FBx3%2FfvOPVaDUg%2BoAPUf1PArIMCSD5glMEsUGaG%2BkyAFWIBaCsKuA%2BHGCNijLgP133XgOEtaPFMy2vUolEGJoCIzBmoRUR9%2B7rxj16DZaW%2FmgtmxnJ8V3oAnApQwNS5zpcAAAAaZmNUTAAAAAEAAAAQAAAAEAAAAAAAAAAAAB4D6AIB52fclgAAACpmZEFUAAAAAjiNY2AYBVhBc3Pzf2LEcGreqcbwH1kDNjHauWAUjAJyAADymxf9WF%2Bu8QAAABpmY1RMAAAAAwAAABAAAAAQAAAAAAAAAAAAHgPoAgEK8Q9%2FAAAAFmZkQVQAAAAEOI1jYBgFo2AUjAIIAAAEEAAB0xIn4wAAABpmY1RMAAAABQAAABAAAAAQAAAAAAAAAAAAHgPoAgHnO30FAAAAQGZkQVQAAAAGOI1jYBieYKcaw39ixHCC%2F6cwFWMTw2rz%2F1MM%2F6Vu%2Ff%2F%2F%2FxTD%2F51qEIwuRjsXILuEGLFRMApgAADhNCsVfozYcAAAABpmY1RMAAAABwAAABAAAAAQAAAAAAAAAAAAHgPoAgEKra7sAAAAFmZkQVQAAAAIOI1jYBgFo2AUjAIIAAAEEAABM9s3hAAAABpmY1RMAAAACQAAABAAAAAQAAAAAAAAAAAAHgPoAgHn3p%2BwAAAAKmZkQVQAAAAKOI1jYBgFWEFzc%2FN%2FYsRwat6pxvAfWQM2Mdq5YBSMAnIAAPKbF%2F1BhPl6AAAAGmZjVEwAAAALAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAQpITFkAAAAWZmRBVAAAAAw4jWNgGAWjYBSMAggAAAQQAAHaszpmAAAAGmZjVEwAAAANAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAeeCPiMAAABAZmRBVAAAAA44jWNgGJ5gpxrDf2LEcIL%2FpzAVYxPDavP%2FUwz%2FpW79%2F%2F%2F%2FFMP%2FnWoQjC5GOxcgu4QYsVEwCmAAAOE0KxUmBL0KAAAAGmZjVEwAAAAPAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAQoU7coAAAAWZmRBVAAAABA4jWNgGAWjYBSMAggAAAQQAAEpOBELAAAAGmZjVEwAAAARAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAeYVWtoAAAAqZmRBVAAAABI4jWNgGAVYQXNz839ixHBq3qnG8B9ZAzYx2rlgFIwCcgAA8psX%2FWvpAecAAAAaZmNUTAAAABMAAAAQAAAAEAAAAAAAAAAAAB4D6AIBC4OJMwAAABZmZEFUAAAAFDiNY2AYBaNgFIwCCAAABBAAAcBQHOkAAAAaZmNUTAAAABUAAAAQAAAAEAAAAAAAAAAAAB4D6AIB5kn7SQAAAEBmZEFUAAAAFjiNY2AYnmCnGsN%2FYsRwgv%2BnMBVjE8Nq8%2F9TDP%2Blbv3%2F%2F%2F8Uw%2F%2BdahCMLkY7FyC7hBixUTAKYAAA4TQrFc%2BcEoQAAAAaZmNUTAAAABcAAAAQAAAAEAAAAAAAAAAAAB4D6AIBC98ooAAAABZmZEFUAAAAGDiNY2AYBaNgFIwCCAAABBAAASCZDI4AAAAaZmNUTAAAABkAAAAQAAAAEAAAAAAAAAAAAB4D6AIB5qwZ%2FAAAACpmZEFUAAAAGjiNY2AYBVhBc3Pzf2LEcGreqcbwH1kDNjHauWAUjAJyAADymxf9cjJWbAAAABpmY1RMAAAAGwAAABAAAAAQAAAAAAAAAAAAHgPoAgELOsoVAAAAFmZkQVQAAAAcOI1jYBgFo2AUjAIIAAAEEAAByfEBbAAAABpmY1RMAAAAHQAAABAAAAAQAAAAAAAAAAAAHgPoAgHm8LhvAAAAQGZkQVQAAAAeOI1jYBieYKcaw39ixHCC%2F6cwFWMTw2rz%2F1MM%2F6Vu%2Ff%2F%2F%2FxTD%2F51qEIwuRjsXILuEGLFRMApgAADhNCsVlxR3%2FgAAABpmY1RMAAAAHwAAABAAAAAQAAAAAAAAAAAAHgPoAgELZmuGAAAAFmZkQVQAAAAgOI1jYBgFo2AUjAIIAAAEEAABHP5cFQAAABpmY1RMAAAAIQAAABAAAAAQAAAAAAAAAAAAHgPoAgHlgtAOAAAAKmZkQVQAAAAiOI1jYBgFWEFzc%2FN%2FYsRwat6pxvAfWQM2Mdq5YBSMAnIAAPKbF%2F0%2FMvDdAAAAAElFTkSuQmCC"/>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
var buttonClicked = false;
|
||||
function robotButton()
|
||||
{
|
||||
var button = document.getElementById('errorTryAgain');
|
||||
if (buttonClicked) {
|
||||
button.style.visibility = "hidden";
|
||||
} else {
|
||||
var newLabel = button.getAttribute("label2");
|
||||
button.textContent = newLabel;
|
||||
buttonClicked = true;
|
||||
}
|
||||
}
|
||||
]]></script>
|
||||
|
||||
<style type="text/css"><![CDATA[
|
||||
#errorPageContainer {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
#errorPageContainer:before {
|
||||
content: url('chrome://browser/content/aboutRobots-icon.png');
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
body[dir=rtl] #icon,
|
||||
body[dir=rtl] #errorPageContainer:before {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
]]></style>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
|
||||
<!-- PAGE CONTAINER (for styling purposes only) -->
|
||||
<div id="errorPageContainer">
|
||||
|
||||
<!-- Error Title -->
|
||||
<div id="errorTitle">
|
||||
<h1 id="errorTitleText">&robots.errorTitleText;</h1>
|
||||
</div>
|
||||
|
||||
<!-- LONG CONTENT (the section most likely to require scrolling) -->
|
||||
<div id="errorLongContent">
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorShortDesc">
|
||||
<p id="errorShortDescText">&robots.errorShortDescText;</p>
|
||||
</div>
|
||||
|
||||
<!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
|
||||
<div id="errorLongDesc">
|
||||
<ul>
|
||||
<li>&robots.errorLongDesc1;</li>
|
||||
<li>&robots.errorLongDesc2;</li>
|
||||
<li>&robots.errorLongDesc3;</li>
|
||||
<li>&robots.errorLongDesc4;</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorTrailerDesc">
|
||||
<p id="errorTrailerDescText">&robots.errorTrailerDescText;</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Button -->
|
||||
<button id="errorTryAgain"
|
||||
label2="&robots.dontpress;"
|
||||
onclick="robotButton();">&retry.label;</button>
|
||||
|
||||
<img src="chrome://browser/content/aboutRobots-widget-left.png"
|
||||
style="position: absolute; bottom: -12px; left: -10px;"/>
|
||||
<img src="chrome://browser/content/aboutRobots-widget-left.png"
|
||||
style="position: absolute; bottom: -12px; right: -10px; transform: scaleX(-1);"/>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,11 +0,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/. */
|
||||
|
||||
html:not(.crashDumpSubmitted) #reportSent,
|
||||
html:not(.crashDumpAvailable) #reportBox,
|
||||
.container[multiple="true"] > .offers > #offerHelpMessageSingle,
|
||||
.container[multiple="false"] > .offers > #offerHelpMessageMultiple,
|
||||
.container[multiple="false"] > .button-container > #restoreAll {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -1,309 +0,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/. */
|
||||
|
||||
var AboutTabCrashed = {
|
||||
/**
|
||||
* This can be set to true once this page receives a message from the
|
||||
* parent saying whether or not a crash report is available.
|
||||
*/
|
||||
hasReport: false,
|
||||
|
||||
/**
|
||||
* The messages that we might receive from the parent.
|
||||
*/
|
||||
MESSAGES: [
|
||||
"SetCrashReportAvailable",
|
||||
"CrashReportSent",
|
||||
"UpdateCount",
|
||||
],
|
||||
|
||||
/**
|
||||
* Items for which we will listen for click events.
|
||||
*/
|
||||
CLICK_TARGETS: [
|
||||
"closeTab",
|
||||
"restoreTab",
|
||||
"restoreAll",
|
||||
"sendReport",
|
||||
],
|
||||
|
||||
/**
|
||||
* Returns information about this crashed tab.
|
||||
*
|
||||
* @return (Object) An object with the following properties:
|
||||
* title (String):
|
||||
* The title of the page that crashed.
|
||||
* URL (String):
|
||||
* The URL of the page that crashed.
|
||||
*/
|
||||
get pageData() {
|
||||
delete this.pageData;
|
||||
|
||||
let URL = document.documentURI;
|
||||
let queryString = URL.replace(/^about:tabcrashed?e=tabcrashed/, "");
|
||||
|
||||
let titleMatch = queryString.match(/d=([^&]*)/);
|
||||
let URLMatch = queryString.match(/u=([^&]*)/);
|
||||
|
||||
return this.pageData = {
|
||||
title: titleMatch && titleMatch[1] ? decodeURIComponent(titleMatch[1]) : "",
|
||||
URL: URLMatch && URLMatch[1] ? decodeURIComponent(URLMatch[1]) : "",
|
||||
};
|
||||
},
|
||||
|
||||
init() {
|
||||
this.MESSAGES.forEach((msg) => addMessageListener(msg, this.receiveMessage.bind(this)));
|
||||
addEventListener("DOMContentLoaded", this);
|
||||
|
||||
document.title = this.pageData.title;
|
||||
},
|
||||
|
||||
receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "UpdateCount": {
|
||||
this.setMultiple(message.data.count > 1);
|
||||
break;
|
||||
}
|
||||
case "SetCrashReportAvailable": {
|
||||
this.onSetCrashReportAvailable(message);
|
||||
break;
|
||||
}
|
||||
case "CrashReportSent": {
|
||||
this.onCrashReportSent();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "DOMContentLoaded": {
|
||||
this.onDOMContentLoaded();
|
||||
break;
|
||||
}
|
||||
case "click": {
|
||||
this.onClick(event);
|
||||
break;
|
||||
}
|
||||
case "input": {
|
||||
this.onInput(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onDOMContentLoaded() {
|
||||
this.CLICK_TARGETS.forEach((targetID) => {
|
||||
let el = document.getElementById(targetID);
|
||||
el.addEventListener("click", this);
|
||||
});
|
||||
|
||||
// For setting "emailMe" checkbox automatically on email value change.
|
||||
document.getElementById("email").addEventListener("input", this);
|
||||
|
||||
// Error pages are loaded as LOAD_BACKGROUND, so they don't get load events.
|
||||
let event = new CustomEvent("AboutTabCrashedLoad", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
|
||||
sendAsyncMessage("Load");
|
||||
},
|
||||
|
||||
onClick(event) {
|
||||
switch (event.target.id) {
|
||||
case "closeTab": {
|
||||
this.sendMessage("closeTab");
|
||||
break;
|
||||
}
|
||||
|
||||
case "restoreTab": {
|
||||
this.sendMessage("restoreTab");
|
||||
break;
|
||||
}
|
||||
|
||||
case "restoreAll": {
|
||||
this.sendMessage("restoreAll");
|
||||
break;
|
||||
}
|
||||
|
||||
case "sendReport": {
|
||||
this.showCrashReportUI(event.target.checked);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onInput(event) {
|
||||
switch (event.target.id) {
|
||||
case "email": {
|
||||
document.getElementById("emailMe").checked = !!event.target.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* After this page tells the parent that it has loaded, the parent
|
||||
* will respond with whether or not a crash report is available. This
|
||||
* method handles that message.
|
||||
*
|
||||
* @param message
|
||||
* The message from the parent, which should contain a data
|
||||
* Object property with the following properties:
|
||||
*
|
||||
* hasReport (bool):
|
||||
* Whether or not there is a crash report.
|
||||
*
|
||||
* sendReport (bool):
|
||||
* Whether or not the the user prefers to send the report
|
||||
* by default.
|
||||
*
|
||||
* includeURL (bool):
|
||||
* Whether or not the user prefers to send the URL of
|
||||
* the tab that crashed.
|
||||
*
|
||||
* emailMe (bool):
|
||||
* Whether or not to send the email address of the user
|
||||
* in the report.
|
||||
*
|
||||
* email (String):
|
||||
* The email address of the user (empty if emailMe is false).
|
||||
*
|
||||
* requestAutoSubmit (bool):
|
||||
* Whether or not we should ask the user to automatically
|
||||
* submit backlogged crash reports.
|
||||
*
|
||||
*/
|
||||
onSetCrashReportAvailable(message) {
|
||||
let data = message.data;
|
||||
|
||||
if (data.hasReport) {
|
||||
this.hasReport = true;
|
||||
document.documentElement.classList.add("crashDumpAvailable");
|
||||
|
||||
document.getElementById("sendReport").checked = data.sendReport;
|
||||
document.getElementById("includeURL").checked = data.includeURL;
|
||||
|
||||
if (data.requestEmail) {
|
||||
document.getElementById("requestEmail").hidden = false;
|
||||
document.getElementById("emailMe").checked = data.emailMe;
|
||||
if (data.emailMe) {
|
||||
document.getElementById("email").value = data.email;
|
||||
}
|
||||
}
|
||||
|
||||
this.showCrashReportUI(data.sendReport);
|
||||
} else {
|
||||
this.showCrashReportUI(false);
|
||||
}
|
||||
|
||||
if (data.requestAutoSubmit) {
|
||||
document.getElementById("requestAutoSubmit").hidden = false;
|
||||
}
|
||||
|
||||
let event = new CustomEvent("AboutTabCrashedReady", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for when the parent reports that the crash report associated
|
||||
* with this about:tabcrashed page has been sent.
|
||||
*/
|
||||
onCrashReportSent() {
|
||||
document.documentElement.classList.remove("crashDumpAvailable");
|
||||
document.documentElement.classList.add("crashDumpSubmitted");
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the display of the crash report form.
|
||||
*
|
||||
* @param shouldShow (bool)
|
||||
* True if the crash report form should be shown
|
||||
*/
|
||||
showCrashReportUI(shouldShow) {
|
||||
let options = document.getElementById("options");
|
||||
options.hidden = !shouldShow;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles whether or not the page is one of several visible pages
|
||||
* showing the crash reporter. This controls some of the language
|
||||
* on the page, along with what the "primary" button is.
|
||||
*
|
||||
* @param hasMultiple (bool)
|
||||
* True if there are multiple crash report pages being shown.
|
||||
*/
|
||||
setMultiple(hasMultiple) {
|
||||
let main = document.getElementById("main");
|
||||
main.setAttribute("multiple", hasMultiple);
|
||||
|
||||
let restoreTab = document.getElementById("restoreTab");
|
||||
|
||||
// The "Restore All" button has the "primary" class by default, so
|
||||
// we only need to modify the "Restore Tab" button.
|
||||
if (hasMultiple) {
|
||||
restoreTab.classList.remove("primary");
|
||||
} else {
|
||||
restoreTab.classList.add("primary");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends a message to the parent in response to the user choosing
|
||||
* one of the actions available on the page. This might also send up
|
||||
* crash report information if the user has chosen to submit a crash
|
||||
* report.
|
||||
*
|
||||
* @param messageName (String)
|
||||
* The message to send to the parent
|
||||
*/
|
||||
sendMessage(messageName) {
|
||||
let comments = "";
|
||||
let email = "";
|
||||
let URL = "";
|
||||
let sendReport = false;
|
||||
let emailMe = false;
|
||||
let includeURL = false;
|
||||
let autoSubmit = false;
|
||||
|
||||
if (this.hasReport) {
|
||||
sendReport = document.getElementById("sendReport").checked;
|
||||
if (sendReport) {
|
||||
comments = document.getElementById("comments").value.trim();
|
||||
|
||||
includeURL = document.getElementById("includeURL").checked;
|
||||
if (includeURL) {
|
||||
URL = this.pageData.URL.trim();
|
||||
}
|
||||
|
||||
if (!document.getElementById("requestEmail").hidden) {
|
||||
emailMe = document.getElementById("emailMe").checked;
|
||||
if (emailMe) {
|
||||
email = document.getElementById("email").value.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let requestAutoSubmit = document.getElementById("requestAutoSubmit");
|
||||
if (requestAutoSubmit.hidden) {
|
||||
// The checkbox is hidden if the user has already opted in to sending
|
||||
// backlogged crash reports.
|
||||
autoSubmit = true;
|
||||
} else {
|
||||
autoSubmit = document.getElementById("autoSubmit").checked;
|
||||
}
|
||||
|
||||
sendAsyncMessage(messageName, {
|
||||
sendReport,
|
||||
comments,
|
||||
email,
|
||||
emailMe,
|
||||
includeURL,
|
||||
URL,
|
||||
autoSubmit,
|
||||
hasReport: this.hasReport,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
AboutTabCrashed.init();
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % globalDTD
|
||||
SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % tabCrashedDTD
|
||||
SYSTEM "chrome://browser/locale/aboutTabCrashed.dtd">
|
||||
%tabCrashedDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" media="all"
|
||||
href="chrome://global/skin/in-content/info-pages.css"/>
|
||||
<link rel="stylesheet" type="text/css" media="all"
|
||||
href="chrome://browser/content/aboutTabCrashed.css"/>
|
||||
<link rel="stylesheet" type="text/css" media="all"
|
||||
href="chrome://browser/skin/aboutTabCrashed.css"/>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
<div id="main" class="container" multiple="false">
|
||||
|
||||
<div class="title">
|
||||
<h1 class="title-text">&tabCrashed.header2;</h1>
|
||||
</div>
|
||||
|
||||
<div class="offers">
|
||||
<h2>&tabCrashed.offerHelp;</h2>
|
||||
<p id="offerHelpMessageSingle">&tabCrashed.single.offerHelpMessage;</p>
|
||||
<p id="offerHelpMessageMultiple">&tabCrashed.multiple.offerHelpMessage;</p>
|
||||
</div>
|
||||
|
||||
<div id="reportBox">
|
||||
<h2>&tabCrashed.requestHelp;</h2>
|
||||
<p>&tabCrashed.requestHelpMessage;</p>
|
||||
|
||||
<h2>&tabCrashed.requestReport;</h2>
|
||||
|
||||
<div class="checkbox-with-label">
|
||||
<input type="checkbox" id="sendReport"/>
|
||||
<label for="sendReport">&tabCrashed.sendReport2;</label>
|
||||
</div>
|
||||
|
||||
<ul id="options">
|
||||
<li>
|
||||
<textarea id="comments" placeholder="&tabCrashed.commentPlaceholder2;" rows="4"></textarea>
|
||||
</li>
|
||||
|
||||
<li class="checkbox-with-label">
|
||||
<input type="checkbox" id="includeURL"/>
|
||||
<label for="includeURL">&tabCrashed.includeURL2;</label>
|
||||
</li>
|
||||
|
||||
<li id="requestEmail" hidden="true">
|
||||
<div class="checkbox-with-label">
|
||||
<input type="checkbox" id="emailMe"/>
|
||||
<label for="emailMe">&tabCrashed.emailMe;</label>
|
||||
</div>
|
||||
<input type="text" id="email" placeholder="&tabCrashed.emailPlaceholder;"/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div id="requestAutoSubmit" hidden="true">
|
||||
<h2>&tabCrashed.requestAutoSubmit2;</h2>
|
||||
<div class="checkbox-with-label">
|
||||
<input type="checkbox" id="autoSubmit"/>
|
||||
<label for="autoSubmit">&tabCrashed.autoSubmit;</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p id="reportSent">&tabCrashed.reportSent;</p>
|
||||
|
||||
<div class="button-container">
|
||||
<button id="closeTab">
|
||||
&tabCrashed.closeTab;</button>
|
||||
<button id="restoreTab" class="primary">
|
||||
&tabCrashed.restoreTab;</button>
|
||||
<button id="restoreAll" autofocus="true" class="primary">
|
||||
&tabCrashed.restoreAll;</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script type="text/javascript;version=1.8" src="chrome://browser/content/aboutTabCrashed.js"/>
|
||||
</html>
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#remote {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#networkError, #manage, #intro, #stage, #configError {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#oldsync {
|
||||
background: none;
|
||||
border: 0;
|
||||
color: #0095dd;
|
||||
}
|
||||
|
||||
#oldsync:focus {
|
||||
outline: 1px dotted #0095dd;
|
||||
}
|
||||
|
|
@ -1,543 +0,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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/FxAccounts.jsm");
|
||||
|
||||
var fxAccountsCommon = {};
|
||||
Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon);
|
||||
|
||||
// for master-password utilities
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
const PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash";
|
||||
const PREF_SYNC_SHOW_CUSTOMIZATION = "services.sync-setup.ui.showCustomizationDialog";
|
||||
|
||||
const ACTION_URL_PARAM = "action";
|
||||
|
||||
const OBSERVER_TOPICS = [
|
||||
fxAccountsCommon.ONVERIFIED_NOTIFICATION,
|
||||
fxAccountsCommon.ONLOGOUT_NOTIFICATION,
|
||||
];
|
||||
|
||||
function log(msg) {
|
||||
// dump("FXA: " + msg + "\n");
|
||||
}
|
||||
|
||||
function error(msg) {
|
||||
console.log("Firefox Account Error: " + msg + "\n");
|
||||
}
|
||||
|
||||
function getPreviousAccountNameHash() {
|
||||
try {
|
||||
return Services.prefs.getComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString).data;
|
||||
} catch (_) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function setPreviousAccountNameHash(acctName) {
|
||||
let string = Cc["@mozilla.org/supports-string;1"]
|
||||
.createInstance(Ci.nsISupportsString);
|
||||
string.data = sha256(acctName);
|
||||
Services.prefs.setComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString, string);
|
||||
}
|
||||
|
||||
function needRelinkWarning(acctName) {
|
||||
let prevAcctHash = getPreviousAccountNameHash();
|
||||
return prevAcctHash && prevAcctHash != sha256(acctName);
|
||||
}
|
||||
|
||||
// Given a string, returns the SHA265 hash in base64
|
||||
function sha256(str) {
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
// Data is an array of bytes.
|
||||
let data = converter.convertToByteArray(str, {});
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA256);
|
||||
hasher.update(data, data.length);
|
||||
|
||||
return hasher.finish(true);
|
||||
}
|
||||
|
||||
function promptForRelink(acctName) {
|
||||
let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
|
||||
let continueLabel = sb.GetStringFromName("continue.label");
|
||||
let title = sb.GetStringFromName("relinkVerify.title");
|
||||
let description = sb.formatStringFromName("relinkVerify.description",
|
||||
[acctName], 1);
|
||||
let body = sb.GetStringFromName("relinkVerify.heading") +
|
||||
"\n\n" + description;
|
||||
let ps = Services.prompt;
|
||||
let buttonFlags = (ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING) +
|
||||
(ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL) +
|
||||
ps.BUTTON_POS_1_DEFAULT;
|
||||
let pressed = Services.prompt.confirmEx(window, title, body, buttonFlags,
|
||||
continueLabel, null, null, null,
|
||||
{});
|
||||
return pressed == 0; // 0 is the "continue" button
|
||||
}
|
||||
|
||||
// If the last fxa account used for sync isn't this account, we display
|
||||
// a modal dialog checking they really really want to do this...
|
||||
// (This is sync-specific, so ideally would be in sync's identity module,
|
||||
// but it's a little more seamless to do here, and sync is currently the
|
||||
// only fxa consumer, so...
|
||||
function shouldAllowRelink(acctName) {
|
||||
return !needRelinkWarning(acctName) || promptForRelink(acctName);
|
||||
}
|
||||
|
||||
function updateDisplayedEmail(user) {
|
||||
let emailDiv = document.getElementById("email");
|
||||
if (emailDiv && user) {
|
||||
emailDiv.textContent = user.email;
|
||||
}
|
||||
}
|
||||
|
||||
var wrapper = {
|
||||
iframe: null,
|
||||
|
||||
init: function (url, urlParams) {
|
||||
// If a master-password is enabled, we want to encourage the user to
|
||||
// unlock it. Things still work if not, but the user will probably need
|
||||
// to re-auth next startup (in which case we will get here again and
|
||||
// re-prompt)
|
||||
Utils.ensureMPUnlocked();
|
||||
|
||||
let iframe = document.getElementById("remote");
|
||||
this.iframe = iframe;
|
||||
this.iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
|
||||
let docShell = this.iframe.frameLoader.docShell;
|
||||
docShell.QueryInterface(Ci.nsIWebProgress);
|
||||
docShell.addProgressListener(this.iframeListener, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
|
||||
iframe.addEventListener("load", this);
|
||||
|
||||
// Ideally we'd just merge urlParams with new URL(url).searchParams, but our
|
||||
// URLSearchParams implementation doesn't support iteration (bug 1085284).
|
||||
let urlParamStr = urlParams.toString();
|
||||
if (urlParamStr) {
|
||||
url += (url.includes("?") ? "&" : "?") + urlParamStr;
|
||||
}
|
||||
this.url = url;
|
||||
// Set the iframe's location with loadURI/LOAD_FLAGS_REPLACE_HISTORY to
|
||||
// avoid having a new history entry being added. REPLACE_HISTORY is used
|
||||
// to replace the current entry, which is `about:blank`.
|
||||
let webNav = iframe.frameLoader.docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
webNav.loadURI(url, Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY, null, null, null);
|
||||
},
|
||||
|
||||
retry: function () {
|
||||
let webNav = this.iframe.frameLoader.docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
webNav.loadURI(this.url, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY, null, null, null);
|
||||
},
|
||||
|
||||
iframeListener: {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference,
|
||||
Ci.nsISupports]),
|
||||
|
||||
onStateChange: function(aWebProgress, aRequest, aState, aStatus) {
|
||||
let failure = false;
|
||||
|
||||
// Captive portals sometimes redirect users
|
||||
if ((aState & Ci.nsIWebProgressListener.STATE_REDIRECTING)) {
|
||||
failure = true;
|
||||
} else if ((aState & Ci.nsIWebProgressListener.STATE_STOP)) {
|
||||
if (aRequest instanceof Ci.nsIHttpChannel) {
|
||||
try {
|
||||
failure = aRequest.responseStatus != 200;
|
||||
} catch (e) {
|
||||
failure = aStatus != Components.results.NS_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calling cancel() will raise some OnStateChange notifications by itself,
|
||||
// so avoid doing that more than once
|
||||
if (failure && aStatus != Components.results.NS_BINDING_ABORTED) {
|
||||
aRequest.cancel(Components.results.NS_BINDING_ABORTED);
|
||||
setErrorPage("networkError");
|
||||
}
|
||||
},
|
||||
|
||||
onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
|
||||
if (aRequest && aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
|
||||
aRequest.cancel(Components.results.NS_BINDING_ABORTED);
|
||||
setErrorPage("networkError");
|
||||
}
|
||||
},
|
||||
|
||||
onProgressChange: function() {},
|
||||
onStatusChange: function() {},
|
||||
onSecurityChange: function() {},
|
||||
},
|
||||
|
||||
handleEvent: function (evt) {
|
||||
switch (evt.type) {
|
||||
case "load":
|
||||
this.iframe.contentWindow.addEventListener("FirefoxAccountsCommand", this);
|
||||
this.iframe.removeEventListener("load", this);
|
||||
break;
|
||||
case "FirefoxAccountsCommand":
|
||||
this.handleRemoteCommand(evt);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* onLogin handler receives user credentials from the jelly after a
|
||||
* sucessful login and stores it in the fxaccounts service
|
||||
*
|
||||
* @param accountData the user's account data and credentials
|
||||
*/
|
||||
onLogin: function (accountData) {
|
||||
log("Received: 'login'. Data:" + JSON.stringify(accountData));
|
||||
|
||||
if (accountData.customizeSync) {
|
||||
Services.prefs.setBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION, true);
|
||||
}
|
||||
delete accountData.customizeSync;
|
||||
// sessionTokenContext is erroneously sent by the content server.
|
||||
// https://github.com/mozilla/fxa-content-server/issues/2766
|
||||
// To avoid having the FxA storage manager not knowing what to do with
|
||||
// it we delete it here.
|
||||
delete accountData.sessionTokenContext;
|
||||
|
||||
// We need to confirm a relink - see shouldAllowRelink for more
|
||||
let newAccountEmail = accountData.email;
|
||||
// The hosted code may have already checked for the relink situation
|
||||
// by sending the can_link_account command. If it did, then
|
||||
// it will indicate we don't need to ask twice.
|
||||
if (!accountData.verifiedCanLinkAccount && !shouldAllowRelink(newAccountEmail)) {
|
||||
// we need to tell the page we successfully received the message, but
|
||||
// then bail without telling fxAccounts
|
||||
this.injectData("message", { status: "login" });
|
||||
// after a successful login we return to preferences
|
||||
openPrefs();
|
||||
return;
|
||||
}
|
||||
delete accountData.verifiedCanLinkAccount;
|
||||
|
||||
// Remember who it was so we can log out next time.
|
||||
setPreviousAccountNameHash(newAccountEmail);
|
||||
|
||||
// A sync-specific hack - we want to ensure sync has been initialized
|
||||
// before we set the signed-in user.
|
||||
let xps = Cc["@mozilla.org/weave/service;1"]
|
||||
.getService(Ci.nsISupports)
|
||||
.wrappedJSObject;
|
||||
xps.whenLoaded().then(() => {
|
||||
updateDisplayedEmail(accountData);
|
||||
return fxAccounts.setSignedInUser(accountData);
|
||||
}).then(() => {
|
||||
// If the user data is verified, we want it to immediately look like
|
||||
// they are signed in without waiting for messages to bounce around.
|
||||
if (accountData.verified) {
|
||||
openPrefs();
|
||||
}
|
||||
this.injectData("message", { status: "login" });
|
||||
// until we sort out a better UX, just leave the jelly page in place.
|
||||
// If the account email is not yet verified, it will tell the user to
|
||||
// go check their email, but then it will *not* change state after
|
||||
// the verification completes (the browser will begin syncing, but
|
||||
// won't notify the user). If the email has already been verified,
|
||||
// the jelly will say "Welcome! You are successfully signed in as
|
||||
// EMAIL", but it won't then say "syncing started".
|
||||
}, (err) => this.injectData("message", { status: "error", error: err })
|
||||
);
|
||||
},
|
||||
|
||||
onCanLinkAccount: function(accountData) {
|
||||
// We need to confirm a relink - see shouldAllowRelink for more
|
||||
let ok = shouldAllowRelink(accountData.email);
|
||||
this.injectData("message", { status: "can_link_account", data: { ok: ok } });
|
||||
},
|
||||
|
||||
/**
|
||||
* onSignOut handler erases the current user's session from the fxaccounts service
|
||||
*/
|
||||
onSignOut: function () {
|
||||
log("Received: 'sign_out'.");
|
||||
|
||||
fxAccounts.signOut().then(
|
||||
() => this.injectData("message", { status: "sign_out" }),
|
||||
(err) => this.injectData("message", { status: "error", error: err })
|
||||
);
|
||||
},
|
||||
|
||||
handleRemoteCommand: function (evt) {
|
||||
log('command: ' + evt.detail.command);
|
||||
let data = evt.detail.data;
|
||||
|
||||
switch (evt.detail.command) {
|
||||
case "login":
|
||||
this.onLogin(data);
|
||||
break;
|
||||
case "can_link_account":
|
||||
this.onCanLinkAccount(data);
|
||||
break;
|
||||
case "sign_out":
|
||||
this.onSignOut(data);
|
||||
break;
|
||||
default:
|
||||
log("Unexpected remote command received: " + evt.detail.command + ". Ignoring command.");
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
injectData: function (type, content) {
|
||||
return fxAccounts.promiseAccountsSignUpURI().then(authUrl => {
|
||||
let data = {
|
||||
type: type,
|
||||
content: content
|
||||
};
|
||||
this.iframe.contentWindow.postMessage(data, authUrl);
|
||||
})
|
||||
.catch(e => {
|
||||
console.log("Failed to inject data", e);
|
||||
setErrorPage("configError");
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// Button onclick handlers
|
||||
function handleOldSync() {
|
||||
let chromeWin = window
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShellTreeItem)
|
||||
.rootTreeItem
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow)
|
||||
.QueryInterface(Ci.nsIDOMChromeWindow);
|
||||
let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "old-sync";
|
||||
chromeWin.switchToTabHavingURI(url, true);
|
||||
}
|
||||
|
||||
function getStarted() {
|
||||
show("remote");
|
||||
}
|
||||
|
||||
function retry() {
|
||||
show("remote");
|
||||
wrapper.retry();
|
||||
}
|
||||
|
||||
function openPrefs() {
|
||||
// Bug 1199303 calls for this tab to always be replaced with Preferences
|
||||
// rather than it opening in a different tab.
|
||||
window.location = "about:preferences#sync";
|
||||
}
|
||||
|
||||
function init() {
|
||||
fxAccounts.getSignedInUser().then(user => {
|
||||
// tests in particular might cause the window to start closing before
|
||||
// getSignedInUser has returned.
|
||||
if (window.closed) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
updateDisplayedEmail(user);
|
||||
|
||||
// Ideally we'd use new URL(document.URL).searchParams, but for about: URIs,
|
||||
// searchParams is empty.
|
||||
let urlParams = new URLSearchParams(document.URL.split("?")[1] || "");
|
||||
let action = urlParams.get(ACTION_URL_PARAM);
|
||||
urlParams.delete(ACTION_URL_PARAM);
|
||||
|
||||
switch (action) {
|
||||
case "signin":
|
||||
if (user) {
|
||||
// asking to sign-in when already signed in just shows manage.
|
||||
show("stage", "manage");
|
||||
} else {
|
||||
return fxAccounts.promiseAccountsSignInURI().then(url => {
|
||||
show("remote");
|
||||
wrapper.init(url, urlParams);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "signup":
|
||||
if (user) {
|
||||
// asking to sign-up when already signed in just shows manage.
|
||||
show("stage", "manage");
|
||||
} else {
|
||||
return fxAccounts.promiseAccountsSignUpURI().then(url => {
|
||||
show("remote");
|
||||
wrapper.init(url, urlParams);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "reauth":
|
||||
// ideally we would only show this when we know the user is in a
|
||||
// "must reauthenticate" state - but we don't.
|
||||
// As the email address will be included in the URL returned from
|
||||
// promiseAccountsForceSigninURI, just always show it.
|
||||
return fxAccounts.promiseAccountsForceSigninURI().then(url => {
|
||||
show("remote");
|
||||
wrapper.init(url, urlParams);
|
||||
});
|
||||
default:
|
||||
// No action specified.
|
||||
if (user) {
|
||||
show("stage", "manage");
|
||||
} else {
|
||||
// Attempt a migration if enabled or show the introductory page
|
||||
// otherwise.
|
||||
return migrateToDevEdition(urlParams).then(migrated => {
|
||||
if (!migrated) {
|
||||
show("stage", "intro");
|
||||
// load the remote frame in the background
|
||||
return fxAccounts.promiseAccountsSignUpURI().then(uri =>
|
||||
wrapper.init(uri, urlParams));
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
return Promise.resolve();
|
||||
}).catch(err => {
|
||||
console.log("Configuration or sign in error", err);
|
||||
setErrorPage("configError");
|
||||
});
|
||||
}
|
||||
|
||||
function setErrorPage(errorType) {
|
||||
show("stage", errorType);
|
||||
}
|
||||
|
||||
// Causes the "top-level" element with |id| to be shown - all other top-level
|
||||
// elements are hidden. Optionally, ensures that only 1 "second-level" element
|
||||
// inside the top-level one is shown.
|
||||
function show(id, childId) {
|
||||
// top-level items are either <div> or <iframe>
|
||||
let allTop = document.querySelectorAll("body > div, iframe");
|
||||
for (let elt of allTop) {
|
||||
if (elt.getAttribute("id") == id) {
|
||||
elt.style.display = 'block';
|
||||
} else {
|
||||
elt.style.display = 'none';
|
||||
}
|
||||
}
|
||||
if (childId) {
|
||||
// child items are all <div>
|
||||
let allSecond = document.querySelectorAll("#" + id + " > div");
|
||||
for (let elt of allSecond) {
|
||||
if (elt.getAttribute("id") == childId) {
|
||||
elt.style.display = 'block';
|
||||
} else {
|
||||
elt.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate sync data from the default profile to the dev-edition profile.
|
||||
// Returns a promise of a true value if migration succeeded, or false if it
|
||||
// failed.
|
||||
function migrateToDevEdition(urlParams) {
|
||||
let defaultProfilePath;
|
||||
try {
|
||||
defaultProfilePath = window.getDefaultProfilePath();
|
||||
} catch (e) {} // no default profile.
|
||||
let migrateSyncCreds = false;
|
||||
if (defaultProfilePath) {
|
||||
try {
|
||||
migrateSyncCreds = Services.prefs.getBoolPref("identity.fxaccounts.migrateToDevEdition");
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (!migrateSyncCreds) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
let fxAccountsStorage = OS.Path.join(defaultProfilePath, fxAccountsCommon.DEFAULT_STORAGE_FILENAME);
|
||||
return OS.File.read(fxAccountsStorage, { encoding: "utf-8" }).then(text => {
|
||||
let accountData = JSON.parse(text).accountData;
|
||||
updateDisplayedEmail(accountData);
|
||||
return fxAccounts.setSignedInUser(accountData);
|
||||
}).then(() => {
|
||||
return fxAccounts.promiseAccountsForceSigninURI().then(url => {
|
||||
show("remote");
|
||||
wrapper.init(url, urlParams);
|
||||
});
|
||||
}).then(null, error => {
|
||||
log("Failed to migrate FX Account: " + error);
|
||||
show("stage", "intro");
|
||||
// load the remote frame in the background
|
||||
fxAccounts.promiseAccountsSignUpURI().then(uri => {
|
||||
wrapper.init(uri, urlParams)
|
||||
}).catch(e => {
|
||||
console.log("Failed to load signup page", e);
|
||||
setErrorPage("configError");
|
||||
});
|
||||
}).then(() => {
|
||||
// Reset the pref after migration.
|
||||
Services.prefs.setBoolPref("identity.fxaccounts.migrateToDevEdition", false);
|
||||
return true;
|
||||
}).then(null, err => {
|
||||
Cu.reportError("Failed to reset the migrateToDevEdition pref: " + err);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function that returns the path of the default profile on disk. Will be
|
||||
// overridden in tests.
|
||||
function getDefaultProfilePath() {
|
||||
let defaultProfile = Cc["@mozilla.org/toolkit/profile-service;1"]
|
||||
.getService(Ci.nsIToolkitProfileService)
|
||||
.defaultProfile;
|
||||
return defaultProfile.rootDir.path;
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function onload() {
|
||||
document.removeEventListener("DOMContentLoaded", onload, true);
|
||||
init();
|
||||
var buttonGetStarted = document.getElementById('buttonGetStarted');
|
||||
buttonGetStarted.addEventListener('click', getStarted);
|
||||
|
||||
var buttonRetry = document.getElementById('buttonRetry');
|
||||
buttonRetry.addEventListener('click', retry);
|
||||
|
||||
var oldsync = document.getElementById('oldsync');
|
||||
oldsync.addEventListener('click', handleOldSync);
|
||||
|
||||
var buttonOpenPrefs = document.getElementById('buttonOpenPrefs')
|
||||
buttonOpenPrefs.addEventListener('click', openPrefs);
|
||||
}, true);
|
||||
|
||||
function initObservers() {
|
||||
function observe(subject, topic, data) {
|
||||
log("about:accounts observed " + topic);
|
||||
if (topic == fxAccountsCommon.ONLOGOUT_NOTIFICATION) {
|
||||
// All about:account windows get changed to action=signin on logout.
|
||||
window.location = "about:accounts?action=signin";
|
||||
return;
|
||||
}
|
||||
|
||||
// must be onverified - we want to open preferences.
|
||||
openPrefs();
|
||||
}
|
||||
|
||||
for (let topic of OBSERVER_TOPICS) {
|
||||
Services.obs.addObserver(observe, topic, false);
|
||||
}
|
||||
window.addEventListener("unload", function(event) {
|
||||
log("about:accounts unloading")
|
||||
for (let topic of OBSERVER_TOPICS) {
|
||||
Services.obs.removeObserver(observe, topic);
|
||||
}
|
||||
});
|
||||
}
|
||||
initObservers();
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- 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/. -->
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
|
||||
%brandDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % aboutAccountsDTD SYSTEM "chrome://browser/locale/aboutAccounts.dtd">
|
||||
%aboutAccountsDTD;
|
||||
<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
|
||||
%syncBrandDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" dir="&locale.dir;">
|
||||
<head>
|
||||
<title>&syncBrand.fullName.label;</title>
|
||||
<meta name="viewport" content="width=device-width"/>
|
||||
|
||||
|
||||
<link rel="icon" type="image/png" id="favicon"
|
||||
href="chrome://branding/content/icon32.png"/>
|
||||
<link rel="stylesheet"
|
||||
href="chrome://browser/content/aboutaccounts/normalize.css"
|
||||
type="text/css" />
|
||||
<link rel="stylesheet"
|
||||
href="chrome://browser/content/aboutaccounts/main.css"
|
||||
type="text/css" />
|
||||
<link rel="stylesheet"
|
||||
href="chrome://browser/content/aboutaccounts/aboutaccounts.css"
|
||||
type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="stage">
|
||||
|
||||
<div id="manage">
|
||||
<header>
|
||||
<h1>&aboutAccounts.connected;</h1>
|
||||
<div id="email"></div>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div class="graphic graphic-sync-intro"> </div>
|
||||
|
||||
<div class="button-row">
|
||||
<button id="buttonOpenPrefs" class="button" href="#" tabindex="0">&aboutAccountsConfig.syncPreferences.label;</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="intro">
|
||||
<header>
|
||||
<h1>&aboutAccounts.welcome;</h1>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div class="graphic graphic-sync-intro"> </div>
|
||||
|
||||
<div class="description">&aboutAccountsConfig.description;</div>
|
||||
|
||||
<div class="button-row">
|
||||
<button id="buttonGetStarted" class="button" tabindex="1">&aboutAccountsConfig.startButton.label;</button>
|
||||
</div>
|
||||
|
||||
<div class="links">
|
||||
<button id="oldsync" tabindex="2">&aboutAccountsConfig.useOldSync.label;</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="networkError">
|
||||
<header>
|
||||
<h1>&aboutAccounts.noConnection.title;</h1>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div class="graphic graphic-sync-intro"> </div>
|
||||
|
||||
<div class="description">&aboutAccounts.noConnection.description;</div>
|
||||
|
||||
<div class="button-row">
|
||||
<button id="buttonRetry" class="button" tabindex="3">&aboutAccounts.noConnection.retry;</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="configError">
|
||||
<header>
|
||||
<h1>&aboutAccounts.badConfig.title;</h1>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div class="graphic graphic-sync-intro"> </div>
|
||||
|
||||
<div class="description">&aboutAccounts.badConfig.description;</div>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<iframe mozframetype="content" id="remote" />
|
||||
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="chrome://browser/content/utilityOverlay.js"/>
|
||||
<script type="text/javascript;version=1.8"
|
||||
src="chrome://browser/content/aboutaccounts/aboutaccounts.js" />
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
|
@ -1,166 +0,0 @@
|
|||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #F2F2F2;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #424f59;
|
||||
font: message-box;
|
||||
font-size: 14px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0095dd;
|
||||
cursor: pointer; /* Use the correct cursor for anchors without an href */
|
||||
}
|
||||
|
||||
a:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a:focus {
|
||||
outline: 1px dotted #0095dd;
|
||||
}
|
||||
|
||||
|
||||
a.no-underline {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#stage {
|
||||
background:#fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.25);
|
||||
margin: 0 auto;
|
||||
min-height: 300px;
|
||||
padding: 60px 40px 40px 40px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
top: 80px;
|
||||
width: 420px;
|
||||
}
|
||||
|
||||
header h1
|
||||
{
|
||||
font-size: 24px;
|
||||
font-weight: 200;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
#intro header h1 {
|
||||
margin: 0 0 32px 0;
|
||||
}
|
||||
|
||||
#manage header h1 {
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
#manage header #email {
|
||||
margin-bottom: 23px;
|
||||
color: rgb(138, 155, 168);
|
||||
font-size: 19px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
margin-top: 45px;
|
||||
margin-bottom:20px;
|
||||
}
|
||||
|
||||
.button-row button,
|
||||
.button-row a.button {
|
||||
background: #0095dd;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
padding: 15px 0;
|
||||
transition-duration: 150ms;
|
||||
transition-property: background-color;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-row a.button {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button-row a.button:active,
|
||||
.button-row a.button:hover,
|
||||
.button-row a.button:focus,
|
||||
.button-row button:active,
|
||||
.button-row button:hover,
|
||||
.button-row button:focus {
|
||||
background: #08c;
|
||||
}
|
||||
|
||||
|
||||
.graphic-sync-intro {
|
||||
background-image: url(images/graphic_sync_intro.png);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 150px 195px;
|
||||
height: 195px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
text-indent: 100%;
|
||||
white-space: nowrap;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.description,
|
||||
.button-row {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.links {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
html {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#stage {
|
||||
box-shadow: none;
|
||||
margin: 30px auto 0 auto;
|
||||
min-height: none;
|
||||
min-width: 320px;
|
||||
padding: 0 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.button-row button,
|
||||
.button-row a.button {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Retina */
|
||||
@media
|
||||
only screen and (min-device-pixel-ratio: 2),
|
||||
only screen and ( min-resolution: 192dpi),
|
||||
only screen and ( min-resolution: 2dppx) {
|
||||
.graphic-sync-intro {
|
||||
background-image: url(images/graphic_sync_intro@2x.png);
|
||||
}
|
||||
}
|
||||
402
browser/base/content/aboutaccounts/normalize.css
vendored
|
|
@ -1,402 +0,0 @@
|
|||
/*! normalize.css v2.1.3 | MIT License | git.io/normalize */
|
||||
|
||||
/* ==========================================================================
|
||||
HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Correct `block` display not defined in IE 8/9.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct `inline-block` display not defined in IE 8/9.
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent modern browsers from displaying `audio` without controls.
|
||||
* Remove excess height in iOS 5 devices.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `[hidden]` styling not present in IE 8/9.
|
||||
* Hide the `template` element in IE, Safari, and Firefox < 22.
|
||||
*/
|
||||
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Base
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Set default font family to sans-serif.
|
||||
* 2. Prevent iOS text size adjust after orientation change, without disabling
|
||||
* user zoom.
|
||||
*/
|
||||
|
||||
html {
|
||||
font-family: sans-serif; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default margin.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Links
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background color from active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `outline` inconsistency between Chrome and other browsers.
|
||||
*/
|
||||
|
||||
a:focus {
|
||||
outline: thin dotted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improve readability when focused and also mouse hovered in all browsers.
|
||||
*/
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Typography
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address variable `h1` font-size and margin within `section` and `article`
|
||||
* contexts in Firefox 4+, Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9, Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in Safari 5 and Chrome.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address differences between Firefox and other browsers.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct font family set oddly in Safari 5 and Chrome.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improve readability of pre-formatted text in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set consistent quote types.
|
||||
*/
|
||||
|
||||
q {
|
||||
quotes: "\201C" "\201D" "\2018" "\2019";
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent and variable font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove border when inside `a` element in IE 8/9.
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct overflow displayed oddly in IE 9.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Figures
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address margin not present in IE 8/9 and Safari 5.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Define consistent border, margin, and padding.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `color` not being inherited in IE 8/9.
|
||||
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct font family not being inherited in all browsers.
|
||||
* 2. Correct font size not being inherited in all browsers.
|
||||
* 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 2 */
|
||||
margin: 0; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
|
||||
* the UA stylesheet.
|
||||
*/
|
||||
|
||||
button,
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
||||
* All other form control elements do not inherit `text-transform` values.
|
||||
* Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
|
||||
* Correct `select` style inheritance in Firefox 4+ and Opera.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
||||
* and `video` controls.
|
||||
* 2. Correct inability to style clickable `input` types in iOS.
|
||||
* 3. Improve usability and consistency of cursor style between image-type
|
||||
* `input` and others.
|
||||
*/
|
||||
|
||||
button,
|
||||
html input[type="button"], /* 1 */
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
cursor: pointer; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-set default cursor for disabled elements.
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
html input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Address box sizing set to `content-box` in IE 8/9/10.
|
||||
* 2. Remove excess padding in IE 8/9/10.
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
|
||||
* 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome.
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
box-sizing: content-box; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and search cancel button in Safari 5 and Chrome
|
||||
* on OS X.
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and border in Firefox 4+.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove default vertical scrollbar in IE 8/9.
|
||||
* 2. Improve readability and alignment in all browsers.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto; /* 1 */
|
||||
vertical-align: top; /* 2 */
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Tables
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove most spacing between table cells.
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#remote-report {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
|
@ -1,180 +0,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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const prefs = new Preferences("datareporting.healthreport.");
|
||||
|
||||
const PREF_UNIFIED = "toolkit.telemetry.unified";
|
||||
const PREF_REPORTING_URL = "datareporting.healthreport.about.reportUrl";
|
||||
|
||||
var healthReportWrapper = {
|
||||
init: function () {
|
||||
let iframe = document.getElementById("remote-report");
|
||||
iframe.addEventListener("load", healthReportWrapper.initRemotePage, false);
|
||||
iframe.src = this._getReportURI().spec;
|
||||
prefs.observe("uploadEnabled", this.updatePrefState, healthReportWrapper);
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
prefs.ignore("uploadEnabled", this.updatePrefState, healthReportWrapper);
|
||||
},
|
||||
|
||||
_getReportURI: function () {
|
||||
let url = Services.urlFormatter.formatURLPref(PREF_REPORTING_URL);
|
||||
return Services.io.newURI(url, null, null);
|
||||
},
|
||||
|
||||
setDataSubmission: function (enabled) {
|
||||
MozSelfSupport.healthReportDataSubmissionEnabled = enabled;
|
||||
this.updatePrefState();
|
||||
},
|
||||
|
||||
updatePrefState: function () {
|
||||
try {
|
||||
let prefs = {
|
||||
enabled: MozSelfSupport.healthReportDataSubmissionEnabled,
|
||||
};
|
||||
healthReportWrapper.injectData("prefs", prefs);
|
||||
}
|
||||
catch (ex) {
|
||||
healthReportWrapper.reportFailure(healthReportWrapper.ERROR_PREFS_FAILED);
|
||||
}
|
||||
},
|
||||
|
||||
sendTelemetryPingList: function () {
|
||||
console.log("AboutHealthReport: Collecting Telemetry ping list.");
|
||||
MozSelfSupport.getTelemetryPingList().then((list) => {
|
||||
console.log("AboutHealthReport: Sending Telemetry ping list.");
|
||||
this.injectData("telemetry-ping-list", list);
|
||||
}).catch((ex) => {
|
||||
console.log("AboutHealthReport: Collecting ping list failed: " + ex);
|
||||
});
|
||||
},
|
||||
|
||||
sendTelemetryPingData: function (pingId) {
|
||||
console.log("AboutHealthReport: Collecting Telemetry ping data.");
|
||||
MozSelfSupport.getTelemetryPing(pingId).then((ping) => {
|
||||
console.log("AboutHealthReport: Sending Telemetry ping data.");
|
||||
this.injectData("telemetry-ping-data", {
|
||||
id: pingId,
|
||||
pingData: ping,
|
||||
});
|
||||
}).catch((ex) => {
|
||||
console.log("AboutHealthReport: Loading ping data failed: " + ex);
|
||||
this.injectData("telemetry-ping-data", {
|
||||
id: pingId,
|
||||
error: "error-generic",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
sendCurrentEnvironment: function () {
|
||||
console.log("AboutHealthReport: Sending Telemetry environment data.");
|
||||
MozSelfSupport.getCurrentTelemetryEnvironment().then((environment) => {
|
||||
this.injectData("telemetry-current-environment-data", environment);
|
||||
}).catch((ex) => {
|
||||
console.log("AboutHealthReport: Collecting current environment data failed: " + ex);
|
||||
});
|
||||
},
|
||||
|
||||
sendCurrentPingData: function () {
|
||||
console.log("AboutHealthReport: Sending current Telemetry ping data.");
|
||||
MozSelfSupport.getCurrentTelemetrySubsessionPing().then((ping) => {
|
||||
this.injectData("telemetry-current-ping-data", ping);
|
||||
}).catch((ex) => {
|
||||
console.log("AboutHealthReport: Collecting current ping data failed: " + ex);
|
||||
});
|
||||
},
|
||||
|
||||
injectData: function (type, content) {
|
||||
let report = this._getReportURI();
|
||||
|
||||
// file URIs can't be used for targetOrigin, so we use "*" for this special case
|
||||
// in all other cases, pass in the URL to the report so we properly restrict the message dispatch
|
||||
let reportUrl = report.scheme == "file" ? "*" : report.spec;
|
||||
|
||||
let data = {
|
||||
type: type,
|
||||
content: content
|
||||
}
|
||||
|
||||
let iframe = document.getElementById("remote-report");
|
||||
iframe.contentWindow.postMessage(data, reportUrl);
|
||||
},
|
||||
|
||||
handleRemoteCommand: function (evt) {
|
||||
// Do an origin check to harden against the frame content being loaded from unexpected locations.
|
||||
let allowedPrincipal = Services.scriptSecurityManager.getCodebasePrincipal(this._getReportURI());
|
||||
let targetPrincipal = evt.target.nodePrincipal;
|
||||
if (!allowedPrincipal.equals(targetPrincipal)) {
|
||||
Cu.reportError(`Origin check failed for message "${evt.detail.command}": ` +
|
||||
`target origin is "${targetPrincipal.origin}", expected "${allowedPrincipal.origin}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (evt.detail.command) {
|
||||
case "DisableDataSubmission":
|
||||
this.setDataSubmission(false);
|
||||
break;
|
||||
case "EnableDataSubmission":
|
||||
this.setDataSubmission(true);
|
||||
break;
|
||||
case "RequestCurrentPrefs":
|
||||
this.updatePrefState();
|
||||
break;
|
||||
case "RequestTelemetryPingList":
|
||||
this.sendTelemetryPingList();
|
||||
break;
|
||||
case "RequestTelemetryPingData":
|
||||
this.sendTelemetryPingData(evt.detail.id);
|
||||
break;
|
||||
case "RequestCurrentEnvironment":
|
||||
this.sendCurrentEnvironment();
|
||||
break;
|
||||
case "RequestCurrentPingData":
|
||||
this.sendCurrentPingData();
|
||||
break;
|
||||
default:
|
||||
Cu.reportError("Unexpected remote command received: " + evt.detail.command + ". Ignoring command.");
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
initRemotePage: function () {
|
||||
let iframe = document.getElementById("remote-report").contentDocument;
|
||||
iframe.addEventListener("RemoteHealthReportCommand",
|
||||
function onCommand(e) { healthReportWrapper.handleRemoteCommand(e); },
|
||||
false);
|
||||
healthReportWrapper.updatePrefState();
|
||||
},
|
||||
|
||||
// error handling
|
||||
ERROR_INIT_FAILED: 1,
|
||||
ERROR_PAYLOAD_FAILED: 2,
|
||||
ERROR_PREFS_FAILED: 3,
|
||||
|
||||
reportFailure: function (error) {
|
||||
let details = {
|
||||
errorType: error,
|
||||
}
|
||||
healthReportWrapper.injectData("error", details);
|
||||
},
|
||||
|
||||
handleInitFailure: function () {
|
||||
healthReportWrapper.reportFailure(healthReportWrapper.ERROR_INIT_FAILED);
|
||||
},
|
||||
|
||||
handlePayloadFailure: function () {
|
||||
healthReportWrapper.reportFailure(healthReportWrapper.ERROR_PAYLOAD_FAILED);
|
||||
},
|
||||
}
|
||||
|
||||
window.addEventListener("load", function () { healthReportWrapper.init(); });
|
||||
window.addEventListener("unload", function () { healthReportWrapper.uninit(); });
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- 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/. -->
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
|
||||
%brandDTD;
|
||||
<!ENTITY % securityPrefsDTD SYSTEM "chrome://browser/locale/preferences/security.dtd">
|
||||
%securityPrefsDTD;
|
||||
<!ENTITY % aboutHealthReportDTD SYSTEM "chrome://browser/locale/aboutHealthReport.dtd">
|
||||
%aboutHealthReportDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&abouthealth.pagetitle;</title>
|
||||
<link rel="icon" type="image/png" id="favicon"
|
||||
href="chrome://branding/content/icon32.png"/>
|
||||
<link rel="stylesheet"
|
||||
href="chrome://browser/content/abouthealthreport/abouthealth.css"
|
||||
type="text/css" />
|
||||
<script type="text/javascript;version=1.8"
|
||||
src="chrome://browser/content/abouthealthreport/abouthealth.js" />
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="remote-report"/>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
@ -1,397 +0,0 @@
|
|||
%if 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/. */
|
||||
%endif
|
||||
|
||||
html {
|
||||
font: message-box;
|
||||
font-size: 100%;
|
||||
background-color: hsl(0,0%,95%);
|
||||
color: #000;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: -moz-box;
|
||||
-moz-box-orient: vertical;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: -moz-nativehyperlinktext;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
|
||||
#topSection {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#brandLogo {
|
||||
height: 192px;
|
||||
width: 192px;
|
||||
margin: 22px auto 31px;
|
||||
background-image: url("chrome://branding/content/about-logo.png");
|
||||
background-size: 192px auto;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#searchIconAndTextContainer {
|
||||
width: 470px;
|
||||
}
|
||||
|
||||
#searchIconAndTextContainer {
|
||||
display: -moz-box;
|
||||
height: 36px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#searchIcon {
|
||||
border: 1px transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: url("chrome://browser/skin/search-indicator-magnifying-glass.svg") center center no-repeat;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#searchText {
|
||||
margin-left: 0;
|
||||
-moz-box-flex: 1;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
padding-inline-start: 34px;
|
||||
padding-inline-end: 8px;
|
||||
background: hsla(0,0%,100%,.9) padding-box;
|
||||
border: 1px solid;
|
||||
border-radius: 2px 0 0 2px;
|
||||
border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
|
||||
box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
|
||||
0 0 2px hsla(210,65%,9%,.1) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.2);
|
||||
color: inherit;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
#searchText:dir(rtl) {
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
|
||||
#searchText[aria-expanded="true"] {
|
||||
border-radius: 2px 0 0 0;
|
||||
}
|
||||
|
||||
#searchText[aria-expanded="true"]:dir(rtl) {
|
||||
border-radius: 0 2px 0 0;
|
||||
}
|
||||
|
||||
#searchText[keepfocus],
|
||||
#searchText:focus,
|
||||
#searchText[autofocus] {
|
||||
border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6);
|
||||
}
|
||||
|
||||
#searchSubmit {
|
||||
margin-inline-start: -1px;
|
||||
color: transparent;
|
||||
background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go") center center no-repeat, linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
|
||||
padding: 0;
|
||||
border: 1px solid;
|
||||
border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
|
||||
border-radius: 0 2px 2px 0;
|
||||
border-inline-start: 1px solid transparent;
|
||||
box-shadow: 0 0 2px hsla(0,0%,100%,.5) inset,
|
||||
0 1px 0 hsla(0,0%,100%,.2);
|
||||
cursor: pointer;
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: 150ms;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
#searchSubmit:dir(rtl) {
|
||||
border-radius: 2px 0 0 2px;
|
||||
background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl"), linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1));
|
||||
}
|
||||
|
||||
#searchText:focus + #searchSubmit,
|
||||
#searchText[keepfocus] + #searchSubmit,
|
||||
#searchText + #searchSubmit:hover,
|
||||
#searchText[autofocus] + #searchSubmit {
|
||||
border-color: #59b5fc #45a3e7 #3294d5;
|
||||
}
|
||||
|
||||
#searchText:focus + #searchSubmit,
|
||||
#searchText[keepfocus] + #searchSubmit,
|
||||
#searchText[autofocus] + #searchSubmit {
|
||||
background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted"), linear-gradient(#4cb1ff, #1793e5);
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
|
||||
0 0 0 1px hsla(0,0%,100%,.1) inset,
|
||||
0 1px 0 hsla(210,54%,20%,.03);
|
||||
}
|
||||
|
||||
#searchText:focus + #searchSubmit:dir(rtl),
|
||||
#searchText[keepfocus] + #searchSubmit:dir(rtl),
|
||||
#searchText[autofocus] + #searchSubmit:dir(rtl) {
|
||||
background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl-inverted"), linear-gradient(#4cb1ff, #1793e5);
|
||||
}
|
||||
|
||||
#searchText + #searchSubmit:hover {
|
||||
background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted"), linear-gradient(#66bdff, #0d9eff);
|
||||
box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
|
||||
0 0 0 1px hsla(0,0%,100%,.1) inset,
|
||||
0 1px 0 hsla(210,54%,20%,.03),
|
||||
0 0 4px hsla(206,100%,20%,.2);
|
||||
}
|
||||
|
||||
#searchText + #searchSubmit:dir(rtl):hover {
|
||||
background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl-inverted"), linear-gradient(#66bdff, #0d9eff);
|
||||
}
|
||||
|
||||
#searchText + #searchSubmit:hover:active {
|
||||
box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset,
|
||||
0 0 1px hsla(211,79%,6%,.2) inset;
|
||||
transition-duration: 0ms;
|
||||
}
|
||||
|
||||
#launcher {
|
||||
display: -moz-box;
|
||||
-moz-box-align: center;
|
||||
-moz-box-pack: center;
|
||||
width: 100%;
|
||||
background-color: hsla(0,0%,0%,.03);
|
||||
border-top: 1px solid hsla(0,0%,0%,.03);
|
||||
box-shadow: 0 1px 2px hsla(0,0%,0%,.02) inset,
|
||||
0 -1px 0 hsla(0,0%,100%,.25);
|
||||
}
|
||||
|
||||
#launcher:not([session]),
|
||||
body[narrow] #launcher[session] {
|
||||
display: block; /* display separator and restore button on separate lines */
|
||||
text-align: center;
|
||||
white-space: nowrap; /* prevent navigational buttons from wrapping */
|
||||
}
|
||||
|
||||
.launchButton {
|
||||
display: -moz-box;
|
||||
-moz-box-orient: vertical;
|
||||
margin: 16px 1px;
|
||||
padding: 14px 6px;
|
||||
min-width: 88px;
|
||||
max-width: 176px;
|
||||
max-height: 85px;
|
||||
vertical-align: top;
|
||||
white-space: normal;
|
||||
background: transparent padding-box;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
color: #525c66;
|
||||
font-size: 75%;
|
||||
cursor: pointer;
|
||||
transition-property: background-color, border-color, box-shadow;
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
body[narrow] #launcher[session] > .launchButton {
|
||||
margin: 4px 1px;
|
||||
}
|
||||
|
||||
.launchButton:hover {
|
||||
background-color: hsla(211,79%,6%,.03);
|
||||
border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
|
||||
}
|
||||
|
||||
.launchButton:hover:active {
|
||||
background-image: linear-gradient(hsla(211,79%,6%,.02), hsla(211,79%,6%,.05));
|
||||
border-color: hsla(210,54%,20%,.2) hsla(210,54%,20%,.23) hsla(210,54%,20%,.25);
|
||||
box-shadow: 0 1px 1px hsla(211,79%,6%,.05) inset,
|
||||
0 0 1px hsla(211,79%,6%,.1) inset;
|
||||
transition-duration: 0ms;
|
||||
}
|
||||
|
||||
.launchButton[hidden],
|
||||
#launcher:not([session]) > #restorePreviousSessionSeparator,
|
||||
#launcher:not([session]) > #restorePreviousSession {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#restorePreviousSessionSeparator {
|
||||
width: 3px;
|
||||
height: 116px;
|
||||
margin: 0 10px;
|
||||
background-image: linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0)),
|
||||
linear-gradient(hsla(211,79%,6%,0), hsla(211,79%,6%,.2), hsla(211,79%,6%,0)),
|
||||
linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0));
|
||||
background-position: left top, center, right bottom;
|
||||
background-size: 1px auto;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
body[narrow] #restorePreviousSessionSeparator {
|
||||
margin: 0 auto;
|
||||
width: 512px;
|
||||
height: 3px;
|
||||
background-image: linear-gradient(to right, hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0)),
|
||||
linear-gradient(to right, hsla(211,79%,6%,0), hsla(211,79%,6%,.2), hsla(211,79%,6%,0)),
|
||||
linear-gradient(to right, hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0));
|
||||
background-size: auto 1px;
|
||||
}
|
||||
|
||||
#restorePreviousSession {
|
||||
max-width: none;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
body[narrow] #restorePreviousSession {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.launchButton::before {
|
||||
display: block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 0 auto 6px;
|
||||
line-height: 0; /* remove extra vertical space due to non-zero font-size */
|
||||
}
|
||||
|
||||
#downloads::before {
|
||||
content: url("chrome://browser/content/abouthome/downloads.png");
|
||||
}
|
||||
|
||||
#bookmarks::before {
|
||||
content: url("chrome://browser/content/abouthome/bookmarks.png");
|
||||
}
|
||||
|
||||
#history::before {
|
||||
content: url("chrome://browser/content/abouthome/history.png");
|
||||
}
|
||||
|
||||
#addons::before {
|
||||
content: url("chrome://browser/content/abouthome/addons.png");
|
||||
}
|
||||
|
||||
#sync::before {
|
||||
content: url("chrome://browser/content/abouthome/sync.png");
|
||||
}
|
||||
|
||||
#settings::before {
|
||||
content: url("chrome://browser/content/abouthome/settings.png");
|
||||
}
|
||||
|
||||
#restorePreviousSession::before {
|
||||
content: url("chrome://browser/content/abouthome/restore-large.png");
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
display: inline-block; /* display on same line as text label */
|
||||
vertical-align: middle;
|
||||
margin-bottom: 0;
|
||||
margin-inline-end: 8px;
|
||||
}
|
||||
|
||||
#restorePreviousSession:dir(rtl)::before {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
body[narrow] #restorePreviousSession::before {
|
||||
content: url("chrome://browser/content/abouthome/restore.png");
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
#aboutMozilla {
|
||||
display: block;
|
||||
position: relative; /* pin wordmark to edge of document, not of viewport */
|
||||
-moz-box-ordinal-group: 0;
|
||||
opacity: .5;
|
||||
transition: opacity 150ms;
|
||||
}
|
||||
|
||||
#aboutMozilla:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#aboutMozilla::before {
|
||||
content: url("chrome://browser/content/abouthome/mozilla.png");
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
width: 69px;
|
||||
height: 19px;
|
||||
}
|
||||
|
||||
/* [HiDPI]
|
||||
* At resolutions above 1dppx, prefer downscaling the 2x Retina graphics
|
||||
* rather than upscaling the original-size ones (bug 818940).
|
||||
*/
|
||||
@media not all and (max-resolution: 1dppx) {
|
||||
#brandLogo {
|
||||
background-image: url("chrome://branding/content/about-logo@2x.png");
|
||||
}
|
||||
|
||||
.launchButton::before,
|
||||
#aboutMozilla::before {
|
||||
transform: scale(.5);
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
.launchButton:dir(rtl)::before,
|
||||
#aboutMozilla:dir(rtl)::before {
|
||||
transform: scale(.5) translateX(32px);
|
||||
}
|
||||
|
||||
#downloads::before {
|
||||
content: url("chrome://browser/content/abouthome/downloads@2x.png");
|
||||
}
|
||||
|
||||
#bookmarks::before {
|
||||
content: url("chrome://browser/content/abouthome/bookmarks@2x.png");
|
||||
}
|
||||
|
||||
#history::before {
|
||||
content: url("chrome://browser/content/abouthome/history@2x.png");
|
||||
}
|
||||
|
||||
#addons::before {
|
||||
content: url("chrome://browser/content/abouthome/addons@2x.png");
|
||||
}
|
||||
|
||||
#sync::before {
|
||||
content: url("chrome://browser/content/abouthome/sync@2x.png");
|
||||
}
|
||||
|
||||
#settings::before {
|
||||
content: url("chrome://browser/content/abouthome/settings@2x.png");
|
||||
}
|
||||
|
||||
#restorePreviousSession::before {
|
||||
content: url("chrome://browser/content/abouthome/restore-large@2x.png");
|
||||
}
|
||||
|
||||
body[narrow] #restorePreviousSession::before {
|
||||
content: url("chrome://browser/content/abouthome/restore@2x.png");
|
||||
}
|
||||
|
||||
#restorePreviousSession:dir(rtl)::before {
|
||||
transform: scale(-0.5, 0.5) translateX(24px);
|
||||
transform-origin: top center;
|
||||
}
|
||||
|
||||
#aboutMozilla::before {
|
||||
content: url("chrome://browser/content/abouthome/mozilla@2x.png");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,118 +0,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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* import-globals-from ../contentSearchUI.js */
|
||||
|
||||
// IndexedDB storage constants.
|
||||
const DATABASE_NAME = "abouthome";
|
||||
const DATABASE_VERSION = 1;
|
||||
const DATABASE_STORAGE = "persistent";
|
||||
var searchText;
|
||||
|
||||
// This global tracks if the page has been set up before, to prevent double inits
|
||||
var gInitialized = false;
|
||||
var gObserver = new MutationObserver(function (mutations) {
|
||||
for (let mutation of mutations) {
|
||||
// The addition of the restore session button changes our width:
|
||||
if (mutation.attributeName == "session") {
|
||||
fitToWidth();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("pageshow", function () {
|
||||
// Delay search engine setup, cause browser.js::BrowserOnAboutPageLoad runs
|
||||
// later and may use asynchronous getters.
|
||||
window.gObserver.observe(document.documentElement, { attributes: true });
|
||||
window.gObserver.observe(document.getElementById("launcher"), { attributes: true });
|
||||
fitToWidth();
|
||||
setupSearch();
|
||||
window.addEventListener("resize", fitToWidth);
|
||||
|
||||
// Ask chrome to update snippets.
|
||||
var event = new CustomEvent("AboutHomeLoad", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
|
||||
window.addEventListener("pagehide", function() {
|
||||
window.gObserver.disconnect();
|
||||
window.removeEventListener("resize", fitToWidth);
|
||||
});
|
||||
|
||||
window.addEventListener("keypress", ev => {
|
||||
if (ev.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't focus the search-box on keypress if something other than the
|
||||
// body or document element has focus - don't want to steal input from other elements
|
||||
// Make an exception for <a> and <button> elements (and input[type=button|submit])
|
||||
// which don't usefully take keypresses anyway.
|
||||
// (except space, which is handled below)
|
||||
if (document.activeElement && document.activeElement != document.body &&
|
||||
document.activeElement != document.documentElement &&
|
||||
!["a", "button"].includes(document.activeElement.localName) &&
|
||||
!document.activeElement.matches("input:-moz-any([type=button],[type=submit])")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let modifiers = ev.ctrlKey + ev.altKey + ev.metaKey;
|
||||
// ignore Ctrl/Cmd/Alt, but not Shift
|
||||
// also ignore Tab, Insert, PageUp, etc., and Space
|
||||
if (modifiers != 0 || ev.charCode == 0 || ev.charCode == 32)
|
||||
return;
|
||||
|
||||
searchText.focus();
|
||||
// need to send the first keypress outside the search-box manually to it
|
||||
searchText.value += ev.key;
|
||||
});
|
||||
|
||||
function onSearchSubmit(aEvent)
|
||||
{
|
||||
gContentSearchController.search(aEvent);
|
||||
}
|
||||
|
||||
|
||||
var gContentSearchController;
|
||||
|
||||
function setupSearch()
|
||||
{
|
||||
// Set submit button label for when CSS background are disabled (e.g.
|
||||
// high contrast mode).
|
||||
document.getElementById("searchSubmit").value =
|
||||
document.body.getAttribute("dir") == "ltr" ? "\u25B6" : "\u25C0";
|
||||
|
||||
// The "autofocus" attribute doesn't focus the form element
|
||||
// immediately when the element is first drawn, so the
|
||||
// attribute is also used for styling when the page first loads.
|
||||
searchText = document.getElementById("searchText");
|
||||
searchText.addEventListener("blur", function searchText_onBlur() {
|
||||
searchText.removeEventListener("blur", searchText_onBlur);
|
||||
searchText.removeAttribute("autofocus");
|
||||
});
|
||||
|
||||
if (!gContentSearchController) {
|
||||
gContentSearchController =
|
||||
new ContentSearchUIController(searchText, searchText.parentNode,
|
||||
"abouthome", "homepage");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inform the test harness that we're done loading the page.
|
||||
*/
|
||||
function loadCompleted()
|
||||
{
|
||||
}
|
||||
|
||||
function fitToWidth() {
|
||||
if (document.documentElement.scrollWidth > window.innerWidth) {
|
||||
document.body.setAttribute("narrow", "true");
|
||||
} else if (document.body.hasAttribute("narrow")) {
|
||||
document.body.removeAttribute("narrow");
|
||||
fitToWidth();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
|
||||
%aboutHomeDTD;
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
|
||||
%browserDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&abouthome.pageTitle;</title>
|
||||
|
||||
<link rel="icon" type="image/png" id="favicon"
|
||||
href="chrome://branding/content/icon32.png"/>
|
||||
<link rel="stylesheet" type="text/css" media="all"
|
||||
href="chrome://browser/content/contentSearchUI.css"/>
|
||||
<link rel="stylesheet" type="text/css" media="all" defer="defer"
|
||||
href="chrome://browser/content/abouthome/aboutHome.css"/>
|
||||
|
||||
<script type="text/javascript;version=1.8"
|
||||
src="chrome://browser/content/abouthome/aboutHome.js"/>
|
||||
<script type="text/javascript;version=1.8"
|
||||
src="chrome://browser/content/contentSearchUI.js"/>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
<div class="spacer"/>
|
||||
<div id="topSection">
|
||||
<div id="brandLogo"></div>
|
||||
|
||||
<div id="searchIconAndTextContainer">
|
||||
<div id="searchIcon"/>
|
||||
<input type="text" name="q" value="" id="searchText" maxlength="256"
|
||||
aria-label="&contentSearchInput.label;" autofocus="autofocus"/>
|
||||
<input id="searchSubmit" type="button" onclick="onSearchSubmit(event)"
|
||||
title="&contentSearchSubmit.tooltip;"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spacer"/>
|
||||
|
||||
<div id="launcher">
|
||||
<button class="launchButton" id="downloads">&abouthome.downloadsButton.label;</button>
|
||||
<button class="launchButton" id="bookmarks">&abouthome.bookmarksButton.label;</button>
|
||||
<button class="launchButton" id="history">&abouthome.historyButton.label;</button>
|
||||
<button class="launchButton" id="addons">&abouthome.addonsButton.label;</button>
|
||||
<button class="launchButton" id="sync">&abouthome.syncButton.label;</button>
|
||||
#ifdef XP_WIN
|
||||
<button class="launchButton" id="settings">&abouthome.preferencesButtonWin.label;</button>
|
||||
#else
|
||||
<button class="launchButton" id="settings">&abouthome.preferencesButtonUnix.label;</button>
|
||||
#endif
|
||||
<div id="restorePreviousSessionSeparator"/>
|
||||
<button class="launchButton" id="restorePreviousSession">&historyRestoreLastSession.label;</button>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 898 B |
|
Before Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
|
@ -1,114 +0,0 @@
|
|||
<?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/.
|
||||
|
||||
<!DOCTYPE overlay [
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
|
||||
%brandDTD;
|
||||
<!ENTITY % baseMenuOverlayDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd">
|
||||
%baseMenuOverlayDTD;
|
||||
]>
|
||||
<overlay id="baseMenuOverlay"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
<!-- nsMenuBarX hides these and uses them to build the Application menu.
|
||||
When using Carbon widgets for Mac OS X widgets, some of these are not
|
||||
used as they only apply to Cocoa widget builds. All version of Firefox
|
||||
through Firefox 2 will use Carbon widgets. -->
|
||||
<menupopup id="menu_ToolsPopup">
|
||||
<menuitem id="menu_preferences" label="&preferencesCmdMac.label;" key="key_preferencesCmdMac" oncommand="openPreferences();"/>
|
||||
<menuitem id="menu_mac_services" label="&servicesMenuMac.label;"/>
|
||||
<menuitem id="menu_mac_hide_app" label="&hideThisAppCmdMac2.label;" key="key_hideThisAppCmdMac"/>
|
||||
<menuitem id="menu_mac_hide_others" label="&hideOtherAppsCmdMac.label;" key="key_hideOtherAppsCmdMac"/>
|
||||
<menuitem id="menu_mac_show_all" label="&showAllAppsCmdMac.label;"/>
|
||||
</menupopup>
|
||||
<!-- Mac window menu -->
|
||||
#include ../../../toolkit/content/macWindowMenu.inc
|
||||
#endif
|
||||
|
||||
#ifdef XP_WIN
|
||||
<menu id="helpMenu"
|
||||
label="&helpMenuWin.label;"
|
||||
accesskey="&helpMenuWin.accesskey;">
|
||||
#else
|
||||
<menu id="helpMenu"
|
||||
label="&helpMenu.label;"
|
||||
accesskey="&helpMenu.accesskey;">
|
||||
#endif
|
||||
<menupopup id="menu_HelpPopup" onpopupshowing="buildHelpMenu();">
|
||||
<menuitem id="menu_openHelp"
|
||||
oncommand="openHelpLink('firefox-help')"
|
||||
onclick="checkForMiddleClick(this, event);"
|
||||
label="&productHelp2.label;"
|
||||
accesskey="&productHelp2.accesskey;"
|
||||
#ifdef XP_MACOSX
|
||||
key="key_openHelpMac"/>
|
||||
#else
|
||||
/>
|
||||
#endif
|
||||
<menuitem id="menu_keyboardShortcuts"
|
||||
oncommand="openHelpLink('keyboard-shortcuts')"
|
||||
onclick="checkForMiddleClick(this, event);"
|
||||
label="&helpKeyboardShortcuts.label;"
|
||||
accesskey="&helpKeyboardShortcuts.accesskey;"/>
|
||||
#ifdef MOZ_TELEMETRY_REPORTING
|
||||
<menuitem id="healthReport"
|
||||
label="&healthReport2.label;"
|
||||
accesskey="&healthReport2.accesskey;"
|
||||
oncommand="openHealthReport()"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
#endif
|
||||
<menuitem id="troubleShooting"
|
||||
accesskey="&helpTroubleshootingInfo.accesskey;"
|
||||
label="&helpTroubleshootingInfo.label;"
|
||||
oncommand="openTroubleshootingPage()"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="feedbackPage"
|
||||
accesskey="&helpFeedbackPage.accesskey;"
|
||||
label="&helpFeedbackPage.label;"
|
||||
oncommand="openFeedbackPage()"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="helpSafeMode"
|
||||
accesskey="&helpSafeMode.accesskey;"
|
||||
label="&helpSafeMode.label;"
|
||||
stopaccesskey="&helpSafeMode.stop.accesskey;"
|
||||
stoplabel="&helpSafeMode.stop.label;"
|
||||
oncommand="safeModeRestart();"/>
|
||||
<menuseparator id="aboutSeparator"/>
|
||||
<menuitem id="aboutName"
|
||||
accesskey="&aboutProduct2.accesskey;"
|
||||
label="&aboutProduct2.label;"
|
||||
oncommand="openAboutDialog();"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<keyset id="baseMenuKeyset">
|
||||
#ifdef XP_MACOSX
|
||||
<key id="key_openHelpMac"
|
||||
oncommand="openHelpLink('firefox-osxkey');"
|
||||
key="&helpMac.commandkey;"
|
||||
modifiers="accel"/>
|
||||
<!-- These are used to build the Application menu under Cocoa widgets -->
|
||||
<key id="key_preferencesCmdMac"
|
||||
key="&preferencesCmdMac.commandkey;"
|
||||
modifiers="accel"/>
|
||||
<key id="key_hideThisAppCmdMac"
|
||||
key="&hideThisAppCmdMac2.commandkey;"
|
||||
modifiers="accel"/>
|
||||
<key id="key_hideOtherAppsCmdMac"
|
||||
key="&hideOtherAppsCmdMac.commandkey;"
|
||||
modifiers="accel,alt"/>
|
||||
#endif
|
||||
</keyset>
|
||||
|
||||
<stringbundleset id="stringbundleset">
|
||||
<stringbundle id="bundle_browser" src="chrome://browser/locale/browser.properties"/>
|
||||
<stringbundle id="bundle_browser_region" src="chrome://browser-region/locale/region.properties"/>
|
||||
</stringbundleset>
|
||||
</overlay>
|
||||
|
|
@ -1,196 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % blockedSiteDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
|
||||
%blockedSiteDTD;
|
||||
]>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" class="blacklist">
|
||||
<head>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/blockedSite.css" type="text/css" media="all" />
|
||||
<link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/blacklist_favicon.png"/>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
// Error url MUST be formatted like this:
|
||||
// about:blocked?e=error_code&u=url(&o=1)?
|
||||
// (o=1 when user overrides are allowed)
|
||||
|
||||
// Note that this file uses document.documentURI to get
|
||||
// the URL (with the format from above). This is because
|
||||
// document.location.href gets the current URI off the docshell,
|
||||
// which is the URL displayed in the location bar, i.e.
|
||||
// the URI that the user attempted to load.
|
||||
|
||||
function getErrorCode()
|
||||
{
|
||||
var url = document.documentURI;
|
||||
var error = url.search(/e\=/);
|
||||
var duffUrl = url.search(/\&u\=/);
|
||||
return decodeURIComponent(url.slice(error + 2, duffUrl));
|
||||
}
|
||||
|
||||
function getURL()
|
||||
{
|
||||
var url = document.documentURI;
|
||||
var match = url.match(/&u=([^&]+)&/);
|
||||
|
||||
// match == null if not found; if so, return an empty string
|
||||
// instead of what would turn out to be portions of the URI
|
||||
if (!match)
|
||||
return "";
|
||||
|
||||
url = decodeURIComponent(match[1]);
|
||||
|
||||
// If this is a view-source page, then get then real URI of the page
|
||||
if (url.startsWith("view-source:"))
|
||||
url = url.slice(12);
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this warning page should be overridable or whether
|
||||
* the "ignore warning" button should be hidden.
|
||||
*/
|
||||
function getOverride()
|
||||
{
|
||||
var url = document.documentURI;
|
||||
var match = url.match(/&o=1&/);
|
||||
return !!match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to get the hostname via document.location. Fail back
|
||||
* to getURL so that we always return something meaningful.
|
||||
*/
|
||||
function getHostString()
|
||||
{
|
||||
try {
|
||||
return document.location.hostname;
|
||||
} catch (e) {
|
||||
return getURL();
|
||||
}
|
||||
}
|
||||
|
||||
function initPage()
|
||||
{
|
||||
var error = "";
|
||||
switch (getErrorCode()) {
|
||||
case "malwareBlocked" :
|
||||
error = "malware";
|
||||
break;
|
||||
case "deceptiveBlocked" :
|
||||
error = "phishing";
|
||||
break;
|
||||
case "unwantedBlocked" :
|
||||
error = "unwanted";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
var el;
|
||||
|
||||
if (error !== "malware") {
|
||||
el = document.getElementById("errorTitleText_malware");
|
||||
el.parentNode.removeChild(el);
|
||||
el = document.getElementById("errorShortDescText_malware");
|
||||
el.parentNode.removeChild(el);
|
||||
el = document.getElementById("errorLongDescText_malware");
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
|
||||
if (error !== "phishing") {
|
||||
el = document.getElementById("errorTitleText_phishing");
|
||||
el.parentNode.removeChild(el);
|
||||
el = document.getElementById("errorShortDescText_phishing");
|
||||
el.parentNode.removeChild(el);
|
||||
el = document.getElementById("errorLongDescText_phishing");
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
|
||||
if (error !== "unwanted") {
|
||||
el = document.getElementById("errorTitleText_unwanted");
|
||||
el.parentNode.removeChild(el);
|
||||
el = document.getElementById("errorShortDescText_unwanted");
|
||||
el.parentNode.removeChild(el);
|
||||
el = document.getElementById("errorLongDescText_unwanted");
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
|
||||
// Set sitename
|
||||
document.getElementById(error + "_sitename").textContent = getHostString();
|
||||
document.title = document.getElementById("errorTitleText_" + error)
|
||||
.innerHTML;
|
||||
|
||||
if (!getOverride()) {
|
||||
var btn = document.getElementById("ignoreWarningButton");
|
||||
if (btn) {
|
||||
btn.parentNode.removeChild(btn);
|
||||
}
|
||||
}
|
||||
|
||||
// Inform the test harness that we're done loading the page
|
||||
var event = new CustomEvent("AboutBlockedLoaded");
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
]]></script>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
<div id="errorPageContainer" class="container">
|
||||
|
||||
<!-- Error Title -->
|
||||
<div id="errorTitle" class="title">
|
||||
<h1 class="title-text" id="errorTitleText_phishing">&safeb.blocked.phishingPage.title2;</h1>
|
||||
<h1 class="title-text" id="errorTitleText_malware">&safeb.blocked.malwarePage.title;</h1>
|
||||
<h1 class="title-text" id="errorTitleText_unwanted">&safeb.blocked.unwantedPage.title;</h1>
|
||||
</div>
|
||||
|
||||
<div id="errorLongContent">
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorShortDesc">
|
||||
<p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc2;</p>
|
||||
<p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
|
||||
<p id="errorShortDescText_unwanted">&safeb.blocked.unwantedPage.shortDesc;</p>
|
||||
</div>
|
||||
|
||||
<!-- Long Description -->
|
||||
<div id="errorLongDesc">
|
||||
<p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc2;</p>
|
||||
<p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
|
||||
<p id="errorLongDescText_unwanted">&safeb.blocked.unwantedPage.longDesc;</p>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div id="buttons" class="button-container">
|
||||
<!-- Commands handled in browser.js -->
|
||||
<button id="getMeOutButton" class="primary">&safeb.palm.accept.label;</button>
|
||||
<div class="button-spacer"></div>
|
||||
<button id="reportButton">&safeb.palm.reportPage.label;</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ignoreWarning">
|
||||
<button id="ignoreWarningButton">&safeb.palm.decline.label;</button>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
- Note: It is important to run the script this way, instead of using
|
||||
- an onload handler. This is because error pages are loaded as
|
||||
- LOAD_BACKGROUND, which means that onload handlers will not be executed.
|
||||
-->
|
||||
<script type="application/javascript">
|
||||
initPage();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,742 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
// Removes a doorhanger notification if all of the installs it was notifying
|
||||
// about have ended in some way.
|
||||
function removeNotificationOnEnd(notification, installs) {
|
||||
let count = installs.length;
|
||||
|
||||
function maybeRemove(install) {
|
||||
install.removeListener(this);
|
||||
|
||||
if (--count == 0) {
|
||||
// Check that the notification is still showing
|
||||
let current = PopupNotifications.getNotification(notification.id, notification.browser);
|
||||
if (current === notification)
|
||||
notification.remove();
|
||||
}
|
||||
}
|
||||
|
||||
for (let install of installs) {
|
||||
install.addListener({
|
||||
onDownloadCancelled: maybeRemove,
|
||||
onDownloadFailed: maybeRemove,
|
||||
onInstallFailed: maybeRemove,
|
||||
onInstallEnded: maybeRemove
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const gXPInstallObserver = {
|
||||
_findChildShell: function (aDocShell, aSoughtShell)
|
||||
{
|
||||
if (aDocShell == aSoughtShell)
|
||||
return aDocShell;
|
||||
|
||||
var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem);
|
||||
for (var i = 0; i < node.childCount; ++i) {
|
||||
var docShell = node.getChildAt(i);
|
||||
docShell = this._findChildShell(docShell, aSoughtShell);
|
||||
if (docShell == aSoughtShell)
|
||||
return docShell;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
_getBrowser: function (aDocShell)
|
||||
{
|
||||
for (let browser of gBrowser.browsers) {
|
||||
if (this._findChildShell(browser.docShell, aDocShell))
|
||||
return browser;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
pendingInstalls: new WeakMap(),
|
||||
|
||||
showInstallConfirmation: function(browser, installInfo, height = undefined) {
|
||||
// If the confirmation notification is already open cache the installInfo
|
||||
// and the new confirmation will be shown later
|
||||
if (PopupNotifications.getNotification("addon-install-confirmation", browser)) {
|
||||
let pending = this.pendingInstalls.get(browser);
|
||||
if (pending) {
|
||||
pending.push(installInfo);
|
||||
} else {
|
||||
this.pendingInstalls.set(browser, [installInfo]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let showNextConfirmation = () => {
|
||||
// Make sure the browser is still alive.
|
||||
if (gBrowser.browsers.indexOf(browser) == -1)
|
||||
return;
|
||||
|
||||
let pending = this.pendingInstalls.get(browser);
|
||||
if (pending && pending.length)
|
||||
this.showInstallConfirmation(browser, pending.shift());
|
||||
}
|
||||
|
||||
// If all installs have already been cancelled in some way then just show
|
||||
// the next confirmation
|
||||
if (installInfo.installs.every(i => i.state != AddonManager.STATE_DOWNLOADED)) {
|
||||
showNextConfirmation();
|
||||
return;
|
||||
}
|
||||
|
||||
const anchorID = "addons-notification-icon";
|
||||
|
||||
// Make notifications persist a minimum of 30 seconds
|
||||
var options = {
|
||||
displayURI: installInfo.originatingURI,
|
||||
timeout: Date.now() + 30000,
|
||||
};
|
||||
|
||||
let cancelInstallation = () => {
|
||||
if (installInfo) {
|
||||
for (let install of installInfo.installs) {
|
||||
// The notification may have been closed because the add-ons got
|
||||
// cancelled elsewhere, only try to cancel those that are still
|
||||
// pending install.
|
||||
if (install.state != AddonManager.STATE_CANCELLED)
|
||||
install.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
this.acceptInstallation = null;
|
||||
|
||||
showNextConfirmation();
|
||||
};
|
||||
|
||||
let unsigned = installInfo.installs.filter(i => i.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING);
|
||||
let someUnsigned = unsigned.length > 0 && unsigned.length < installInfo.installs.length;
|
||||
|
||||
options.eventCallback = (aEvent) => {
|
||||
switch (aEvent) {
|
||||
case "removed":
|
||||
cancelInstallation();
|
||||
break;
|
||||
case "shown":
|
||||
let addonList = document.getElementById("addon-install-confirmation-content");
|
||||
while (addonList.firstChild)
|
||||
addonList.firstChild.remove();
|
||||
|
||||
for (let install of installInfo.installs) {
|
||||
let container = document.createElement("hbox");
|
||||
|
||||
let name = document.createElement("label");
|
||||
name.setAttribute("value", install.addon.name);
|
||||
name.setAttribute("class", "addon-install-confirmation-name");
|
||||
container.appendChild(name);
|
||||
|
||||
if (someUnsigned && install.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
|
||||
let unsigned = document.createElement("label");
|
||||
unsigned.setAttribute("value", gNavigatorBundle.getString("addonInstall.unsigned"));
|
||||
unsigned.setAttribute("class", "addon-install-confirmation-unsigned");
|
||||
container.appendChild(unsigned);
|
||||
}
|
||||
|
||||
addonList.appendChild(container);
|
||||
}
|
||||
|
||||
this.acceptInstallation = () => {
|
||||
for (let install of installInfo.installs)
|
||||
install.install();
|
||||
installInfo = null;
|
||||
|
||||
Services.telemetry
|
||||
.getHistogramById("SECURITY_UI")
|
||||
.add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL_CLICK_THROUGH);
|
||||
};
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
|
||||
|
||||
let messageString;
|
||||
let notification = document.getElementById("addon-install-confirmation-notification");
|
||||
if (unsigned.length == installInfo.installs.length) {
|
||||
// None of the add-ons are verified
|
||||
messageString = gNavigatorBundle.getString("addonConfirmInstallUnsigned.message");
|
||||
notification.setAttribute("warning", "true");
|
||||
options.learnMoreURL += "unsigned-addons";
|
||||
}
|
||||
else if (unsigned.length == 0) {
|
||||
// All add-ons are verified or don't need to be verified
|
||||
messageString = gNavigatorBundle.getString("addonConfirmInstall.message");
|
||||
notification.removeAttribute("warning");
|
||||
options.learnMoreURL += "find-and-install-add-ons";
|
||||
}
|
||||
else {
|
||||
// Some of the add-ons are unverified, the list of names will indicate
|
||||
// which
|
||||
messageString = gNavigatorBundle.getString("addonConfirmInstallSomeUnsigned.message");
|
||||
notification.setAttribute("warning", "true");
|
||||
options.learnMoreURL += "unsigned-addons";
|
||||
}
|
||||
|
||||
let brandBundle = document.getElementById("bundle_brand");
|
||||
let brandShortName = brandBundle.getString("brandShortName");
|
||||
|
||||
messageString = PluralForm.get(installInfo.installs.length, messageString);
|
||||
messageString = messageString.replace("#1", brandShortName);
|
||||
messageString = messageString.replace("#2", installInfo.installs.length);
|
||||
|
||||
let cancelButton = document.getElementById("addon-install-confirmation-cancel");
|
||||
cancelButton.label = gNavigatorBundle.getString("addonInstall.cancelButton.label");
|
||||
cancelButton.accessKey = gNavigatorBundle.getString("addonInstall.cancelButton.accesskey");
|
||||
|
||||
let acceptButton = document.getElementById("addon-install-confirmation-accept");
|
||||
acceptButton.label = gNavigatorBundle.getString("addonInstall.acceptButton.label");
|
||||
acceptButton.accessKey = gNavigatorBundle.getString("addonInstall.acceptButton.accesskey");
|
||||
|
||||
if (height) {
|
||||
let notification = document.getElementById("addon-install-confirmation-notification");
|
||||
notification.style.minHeight = height + "px";
|
||||
}
|
||||
|
||||
let tab = gBrowser.getTabForBrowser(browser);
|
||||
if (tab) {
|
||||
gBrowser.selectedTab = tab;
|
||||
}
|
||||
|
||||
let popup = PopupNotifications.show(browser, "addon-install-confirmation",
|
||||
messageString, anchorID, null, null,
|
||||
options);
|
||||
|
||||
removeNotificationOnEnd(popup, installInfo.installs);
|
||||
|
||||
Services.telemetry
|
||||
.getHistogramById("SECURITY_UI")
|
||||
.add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
|
||||
},
|
||||
|
||||
observe: function (aSubject, aTopic, aData)
|
||||
{
|
||||
var brandBundle = document.getElementById("bundle_brand");
|
||||
var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
|
||||
var browser = installInfo.browser;
|
||||
|
||||
// Make sure the browser is still alive.
|
||||
if (!browser || gBrowser.browsers.indexOf(browser) == -1)
|
||||
return;
|
||||
|
||||
const anchorID = "addons-notification-icon";
|
||||
var messageString, action;
|
||||
var brandShortName = brandBundle.getString("brandShortName");
|
||||
|
||||
var notificationID = aTopic;
|
||||
// Make notifications persist a minimum of 30 seconds
|
||||
var options = {
|
||||
displayURI: installInfo.originatingURI,
|
||||
timeout: Date.now() + 30000,
|
||||
};
|
||||
|
||||
switch (aTopic) {
|
||||
case "addon-install-disabled": {
|
||||
notificationID = "xpinstall-disabled";
|
||||
|
||||
if (gPrefService.prefIsLocked("xpinstall.enabled")) {
|
||||
messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
|
||||
buttons = [];
|
||||
}
|
||||
else {
|
||||
messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
|
||||
|
||||
action = {
|
||||
label: gNavigatorBundle.getString("xpinstallDisabledButton"),
|
||||
accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
|
||||
callback: function editPrefs() {
|
||||
gPrefService.setBoolPref("xpinstall.enabled", true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
PopupNotifications.show(browser, notificationID, messageString, anchorID,
|
||||
action, null, options);
|
||||
break; }
|
||||
case "addon-install-origin-blocked": {
|
||||
messageString = gNavigatorBundle.getFormattedString("xpinstallPromptMessage",
|
||||
[brandShortName]);
|
||||
|
||||
let secHistogram = Components.classes["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry).getHistogramById("SECURITY_UI");
|
||||
secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
|
||||
let popup = PopupNotifications.show(browser, notificationID,
|
||||
messageString, anchorID,
|
||||
null, null, options);
|
||||
removeNotificationOnEnd(popup, installInfo.installs);
|
||||
break; }
|
||||
case "addon-install-blocked": {
|
||||
messageString = gNavigatorBundle.getFormattedString("xpinstallPromptMessage",
|
||||
[brandShortName]);
|
||||
|
||||
let secHistogram = Components.classes["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry).getHistogramById("SECURITY_UI");
|
||||
action = {
|
||||
label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
|
||||
accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
|
||||
callback: function() {
|
||||
secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED_CLICK_THROUGH);
|
||||
installInfo.install();
|
||||
}
|
||||
};
|
||||
|
||||
secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
|
||||
let popup = PopupNotifications.show(browser, notificationID,
|
||||
messageString, anchorID,
|
||||
action, null, options);
|
||||
removeNotificationOnEnd(popup, installInfo.installs);
|
||||
break; }
|
||||
case "addon-install-started": {
|
||||
let needsDownload = function needsDownload(aInstall) {
|
||||
return aInstall.state != AddonManager.STATE_DOWNLOADED;
|
||||
}
|
||||
// If all installs have already been downloaded then there is no need to
|
||||
// show the download progress
|
||||
if (!installInfo.installs.some(needsDownload))
|
||||
return;
|
||||
notificationID = "addon-progress";
|
||||
messageString = gNavigatorBundle.getString("addonDownloadingAndVerifying");
|
||||
messageString = PluralForm.get(installInfo.installs.length, messageString);
|
||||
messageString = messageString.replace("#1", installInfo.installs.length);
|
||||
options.installs = installInfo.installs;
|
||||
options.contentWindow = browser.contentWindow;
|
||||
options.sourceURI = browser.currentURI;
|
||||
options.eventCallback = (aEvent) => {
|
||||
switch (aEvent) {
|
||||
case "removed":
|
||||
options.contentWindow = null;
|
||||
options.sourceURI = null;
|
||||
break;
|
||||
}
|
||||
};
|
||||
let notification = PopupNotifications.show(browser, notificationID, messageString,
|
||||
anchorID, null, null, options);
|
||||
notification._startTime = Date.now();
|
||||
|
||||
let cancelButton = document.getElementById("addon-progress-cancel");
|
||||
cancelButton.label = gNavigatorBundle.getString("addonInstall.cancelButton.label");
|
||||
cancelButton.accessKey = gNavigatorBundle.getString("addonInstall.cancelButton.accesskey");
|
||||
|
||||
let acceptButton = document.getElementById("addon-progress-accept");
|
||||
if (Preferences.get("xpinstall.customConfirmationUI", false)) {
|
||||
acceptButton.label = gNavigatorBundle.getString("addonInstall.acceptButton.label");
|
||||
acceptButton.accessKey = gNavigatorBundle.getString("addonInstall.acceptButton.accesskey");
|
||||
} else {
|
||||
acceptButton.hidden = true;
|
||||
}
|
||||
break; }
|
||||
case "addon-install-failed": {
|
||||
// TODO This isn't terribly ideal for the multiple failure case
|
||||
for (let install of installInfo.installs) {
|
||||
let host;
|
||||
try {
|
||||
host = options.displayURI.host;
|
||||
} catch (e) {
|
||||
// displayURI might be missing or 'host' might throw for non-nsStandardURL nsIURIs.
|
||||
}
|
||||
|
||||
if (!host)
|
||||
host = (install.sourceURI instanceof Ci.nsIStandardURL) &&
|
||||
install.sourceURI.host;
|
||||
|
||||
let error = (host || install.error == 0) ? "addonInstallError" : "addonLocalInstallError";
|
||||
let args;
|
||||
if (install.error < 0) {
|
||||
error += install.error;
|
||||
args = [brandShortName, install.name];
|
||||
} else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
|
||||
error += "Blocklisted";
|
||||
args = [install.name];
|
||||
} else {
|
||||
error += "Incompatible";
|
||||
args = [brandShortName, Services.appinfo.version, install.name];
|
||||
}
|
||||
|
||||
// Add Learn More link when refusing to install an unsigned add-on
|
||||
if (install.error == AddonManager.ERROR_SIGNEDSTATE_REQUIRED) {
|
||||
options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons";
|
||||
}
|
||||
|
||||
messageString = gNavigatorBundle.getFormattedString(error, args);
|
||||
|
||||
PopupNotifications.show(browser, notificationID, messageString, anchorID,
|
||||
action, null, options);
|
||||
|
||||
// Can't have multiple notifications with the same ID, so stop here.
|
||||
break;
|
||||
}
|
||||
this._removeProgressNotification(browser);
|
||||
break; }
|
||||
case "addon-install-confirmation": {
|
||||
let showNotification = () => {
|
||||
let height = undefined;
|
||||
|
||||
if (PopupNotifications.isPanelOpen) {
|
||||
let rect = document.getElementById("addon-progress-notification").getBoundingClientRect();
|
||||
height = rect.height;
|
||||
}
|
||||
|
||||
this._removeProgressNotification(browser);
|
||||
this.showInstallConfirmation(browser, installInfo, height);
|
||||
};
|
||||
|
||||
let progressNotification = PopupNotifications.getNotification("addon-progress", browser);
|
||||
if (progressNotification) {
|
||||
let downloadDuration = Date.now() - progressNotification._startTime;
|
||||
let securityDelay = Services.prefs.getIntPref("security.dialog_enable_delay") - downloadDuration;
|
||||
if (securityDelay > 0) {
|
||||
setTimeout(() => {
|
||||
// The download may have been cancelled during the security delay
|
||||
if (PopupNotifications.getNotification("addon-progress", browser))
|
||||
showNotification();
|
||||
}, securityDelay);
|
||||
break;
|
||||
}
|
||||
}
|
||||
showNotification();
|
||||
break; }
|
||||
case "addon-install-complete": {
|
||||
let needsRestart = installInfo.installs.some(function(i) {
|
||||
return i.addon.pendingOperations != AddonManager.PENDING_NONE;
|
||||
});
|
||||
|
||||
if (needsRestart) {
|
||||
notificationID = "addon-install-restart";
|
||||
messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart");
|
||||
action = {
|
||||
label: gNavigatorBundle.getString("addonInstallRestartButton"),
|
||||
accessKey: gNavigatorBundle.getString("addonInstallRestartButton.accesskey"),
|
||||
callback: function() {
|
||||
BrowserUtils.restartApplication();
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
messageString = gNavigatorBundle.getString("addonsInstalled");
|
||||
action = null;
|
||||
}
|
||||
|
||||
messageString = PluralForm.get(installInfo.installs.length, messageString);
|
||||
messageString = messageString.replace("#1", installInfo.installs[0].name);
|
||||
messageString = messageString.replace("#2", installInfo.installs.length);
|
||||
messageString = messageString.replace("#3", brandShortName);
|
||||
|
||||
// Remove notificaion on dismissal, since it's possible to cancel the
|
||||
// install through the addons manager UI, making the "restart" prompt
|
||||
// irrelevant.
|
||||
options.removeOnDismissal = true;
|
||||
|
||||
PopupNotifications.show(browser, notificationID, messageString, anchorID,
|
||||
action, null, options);
|
||||
break; }
|
||||
}
|
||||
},
|
||||
_removeProgressNotification(aBrowser) {
|
||||
let notification = PopupNotifications.getNotification("addon-progress", aBrowser);
|
||||
if (notification)
|
||||
notification.remove();
|
||||
}
|
||||
};
|
||||
|
||||
var LightWeightThemeWebInstaller = {
|
||||
init: function () {
|
||||
let mm = window.messageManager;
|
||||
mm.addMessageListener("LightWeightThemeWebInstaller:Install", this);
|
||||
mm.addMessageListener("LightWeightThemeWebInstaller:Preview", this);
|
||||
mm.addMessageListener("LightWeightThemeWebInstaller:ResetPreview", this);
|
||||
},
|
||||
|
||||
receiveMessage: function (message) {
|
||||
// ignore requests from background tabs
|
||||
if (message.target != gBrowser.selectedBrowser) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = message.data;
|
||||
|
||||
switch (message.name) {
|
||||
case "LightWeightThemeWebInstaller:Install": {
|
||||
this._installRequest(data.themeData, data.principal, data.baseURI);
|
||||
break;
|
||||
}
|
||||
case "LightWeightThemeWebInstaller:Preview": {
|
||||
this._preview(data.themeData, data.principal, data.baseURI);
|
||||
break;
|
||||
}
|
||||
case "LightWeightThemeWebInstaller:ResetPreview": {
|
||||
this._resetPreview(data && data.principal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
switch (event.type) {
|
||||
case "TabSelect": {
|
||||
this._resetPreview();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get _manager () {
|
||||
let temp = {};
|
||||
Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
|
||||
delete this._manager;
|
||||
return this._manager = temp.LightweightThemeManager;
|
||||
},
|
||||
|
||||
_installRequest(dataString, principal, baseURI) {
|
||||
// Don't allow installing off null principals.
|
||||
if (!principal.URI) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = this._manager.parseTheme(dataString, baseURI);
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A notification bar with the option to undo is normally shown after a
|
||||
// theme is installed. But the discovery pane served from the url(s)
|
||||
// below has its own toggle switch for quick undos, so don't show the
|
||||
// notification in that case.
|
||||
let notify = this._shouldShowUndoPrompt(principal);
|
||||
if (this._isAllowed(principal)) {
|
||||
this._install(data, notify);
|
||||
return;
|
||||
}
|
||||
|
||||
let allowButtonText =
|
||||
gNavigatorBundle.getString("lwthemeInstallRequest.allowButton");
|
||||
let allowButtonAccesskey =
|
||||
gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey");
|
||||
let message =
|
||||
gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message",
|
||||
[principal.URI.host]);
|
||||
let buttons = [{
|
||||
label: allowButtonText,
|
||||
accessKey: allowButtonAccesskey,
|
||||
callback: function () {
|
||||
LightWeightThemeWebInstaller._install(data, notify);
|
||||
}
|
||||
}];
|
||||
|
||||
this._removePreviousNotifications();
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox();
|
||||
let notificationBar =
|
||||
notificationBox.appendNotification(message, "lwtheme-install-request", "",
|
||||
notificationBox.PRIORITY_INFO_MEDIUM,
|
||||
buttons);
|
||||
notificationBar.persistence = 1;
|
||||
},
|
||||
|
||||
_install: function (newLWTheme, notify) {
|
||||
let previousLWTheme = this._manager.currentTheme;
|
||||
|
||||
let listener = {
|
||||
onEnabling: function(aAddon, aRequiresRestart) {
|
||||
if (!aRequiresRestart) {
|
||||
return;
|
||||
}
|
||||
|
||||
let messageString = gNavigatorBundle.getFormattedString("lwthemeNeedsRestart.message",
|
||||
[aAddon.name], 1);
|
||||
|
||||
let action = {
|
||||
label: gNavigatorBundle.getString("lwthemeNeedsRestart.button"),
|
||||
accessKey: gNavigatorBundle.getString("lwthemeNeedsRestart.accesskey"),
|
||||
callback: function () {
|
||||
BrowserUtils.restartApplication();
|
||||
}
|
||||
};
|
||||
|
||||
let options = {
|
||||
timeout: Date.now() + 30000
|
||||
};
|
||||
|
||||
PopupNotifications.show(gBrowser.selectedBrowser, "addon-theme-change",
|
||||
messageString, "addons-notification-icon",
|
||||
action, null, options);
|
||||
},
|
||||
|
||||
onEnabled: function(aAddon) {
|
||||
if (notify) {
|
||||
LightWeightThemeWebInstaller._postInstallNotification(newLWTheme, previousLWTheme);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AddonManager.addAddonListener(listener);
|
||||
this._manager.currentTheme = newLWTheme;
|
||||
AddonManager.removeAddonListener(listener);
|
||||
},
|
||||
|
||||
_postInstallNotification: function (newTheme, previousTheme) {
|
||||
function text(id) {
|
||||
return gNavigatorBundle.getString("lwthemePostInstallNotification." + id);
|
||||
}
|
||||
|
||||
let buttons = [{
|
||||
label: text("undoButton"),
|
||||
accessKey: text("undoButton.accesskey"),
|
||||
callback: function () {
|
||||
LightWeightThemeWebInstaller._manager.forgetUsedTheme(newTheme.id);
|
||||
LightWeightThemeWebInstaller._manager.currentTheme = previousTheme;
|
||||
}
|
||||
}, {
|
||||
label: text("manageButton"),
|
||||
accessKey: text("manageButton.accesskey"),
|
||||
callback: function () {
|
||||
BrowserOpenAddonsMgr("addons://list/theme");
|
||||
}
|
||||
}];
|
||||
|
||||
this._removePreviousNotifications();
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox();
|
||||
let notificationBar =
|
||||
notificationBox.appendNotification(text("message"),
|
||||
"lwtheme-install-notification", "",
|
||||
notificationBox.PRIORITY_INFO_MEDIUM,
|
||||
buttons);
|
||||
notificationBar.persistence = 1;
|
||||
notificationBar.timeout = Date.now() + 20000; // 20 seconds
|
||||
},
|
||||
|
||||
_removePreviousNotifications: function () {
|
||||
let box = gBrowser.getNotificationBox();
|
||||
|
||||
["lwtheme-install-request",
|
||||
"lwtheme-install-notification"].forEach(function (value) {
|
||||
let notification = box.getNotificationWithValue(value);
|
||||
if (notification)
|
||||
box.removeNotification(notification);
|
||||
});
|
||||
},
|
||||
|
||||
_preview(dataString, principal, baseURI) {
|
||||
if (!this._isAllowed(principal))
|
||||
return;
|
||||
|
||||
let data = this._manager.parseTheme(dataString, baseURI);
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
this._resetPreview();
|
||||
gBrowser.tabContainer.addEventListener("TabSelect", this, false);
|
||||
this._manager.previewTheme(data);
|
||||
},
|
||||
|
||||
_resetPreview(principal) {
|
||||
if (!this._isAllowed(principal))
|
||||
return;
|
||||
gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
|
||||
this._manager.resetPreview();
|
||||
},
|
||||
|
||||
_isAllowed(principal) {
|
||||
if (!principal || !principal.URI || !principal.URI.schemeIs("https")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let pm = Services.perms;
|
||||
return pm.testPermission(principal.URI, "install") == pm.ALLOW_ACTION;
|
||||
},
|
||||
|
||||
_shouldShowUndoPrompt(principal) {
|
||||
if (!principal || !principal.URI) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let prePath = principal.URI.prePath;
|
||||
if (prePath == "https://addons.palemoon.org") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Listen for Lightweight Theme styling changes and update the browser's theme accordingly.
|
||||
*/
|
||||
var LightweightThemeListener = {
|
||||
_modifiedStyles: [],
|
||||
|
||||
init: function () {
|
||||
XPCOMUtils.defineLazyGetter(this, "styleSheet", function() {
|
||||
for (let i = document.styleSheets.length - 1; i >= 0; i--) {
|
||||
let sheet = document.styleSheets[i];
|
||||
if (sheet.href == "chrome://browser/skin/browser-lightweightTheme.css")
|
||||
return sheet;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
|
||||
Services.obs.addObserver(this, "lightweight-theme-optimized", false);
|
||||
if (document.documentElement.hasAttribute("lwtheme"))
|
||||
this.updateStyleSheet(document.documentElement.style.backgroundImage);
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
Services.obs.removeObserver(this, "lightweight-theme-styling-update");
|
||||
Services.obs.removeObserver(this, "lightweight-theme-optimized");
|
||||
},
|
||||
|
||||
/**
|
||||
* Append the headerImage to the background-image property of all rulesets in
|
||||
* browser-lightweightTheme.css.
|
||||
*
|
||||
* @param headerImage - a string containing a CSS image for the lightweight theme header.
|
||||
*/
|
||||
updateStyleSheet: function(headerImage) {
|
||||
if (!this.styleSheet)
|
||||
return;
|
||||
this.substituteRules(this.styleSheet.cssRules, headerImage);
|
||||
},
|
||||
|
||||
substituteRules: function(ruleList, headerImage, existingStyleRulesModified = 0) {
|
||||
let styleRulesModified = 0;
|
||||
for (let i = 0; i < ruleList.length; i++) {
|
||||
let rule = ruleList[i];
|
||||
if (rule instanceof Ci.nsIDOMCSSGroupingRule) {
|
||||
// Add the number of modified sub-rules to the modified count
|
||||
styleRulesModified += this.substituteRules(rule.cssRules, headerImage, existingStyleRulesModified + styleRulesModified);
|
||||
} else if (rule instanceof Ci.nsIDOMCSSStyleRule) {
|
||||
if (!rule.style.backgroundImage)
|
||||
continue;
|
||||
let modifiedIndex = existingStyleRulesModified + styleRulesModified;
|
||||
if (!this._modifiedStyles[modifiedIndex])
|
||||
this._modifiedStyles[modifiedIndex] = { backgroundImage: rule.style.backgroundImage };
|
||||
|
||||
rule.style.backgroundImage = this._modifiedStyles[modifiedIndex].backgroundImage + ", " + headerImage;
|
||||
styleRulesModified++;
|
||||
} else {
|
||||
Cu.reportError("Unsupported rule encountered");
|
||||
}
|
||||
}
|
||||
return styleRulesModified;
|
||||
},
|
||||
|
||||
// nsIObserver
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
if ((aTopic != "lightweight-theme-styling-update" && aTopic != "lightweight-theme-optimized") ||
|
||||
!this.styleSheet)
|
||||
return;
|
||||
|
||||
if (aTopic == "lightweight-theme-optimized" && aSubject != window)
|
||||
return;
|
||||
|
||||
let themeData = JSON.parse(aData);
|
||||
if (!themeData)
|
||||
return;
|
||||
this.updateStyleSheet("url(" + themeData.headerURL + ")");
|
||||
},
|
||||
};
|
||||
|
|
@ -1,257 +0,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/. */
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "cps",
|
||||
"@mozilla.org/network/captive-portal-service;1",
|
||||
"nsICaptivePortalService");
|
||||
|
||||
var CaptivePortalWatcher = {
|
||||
/**
|
||||
* This constant is chosen to be large enough for a portal recheck to complete,
|
||||
* and small enough that the delay in opening a tab isn't too noticeable.
|
||||
* Please see comments for _delayedCaptivePortalDetected for more details.
|
||||
*/
|
||||
PORTAL_RECHECK_DELAY_MS: Preferences.get("captivedetect.portalRecheckDelayMS", 500),
|
||||
|
||||
// This is the value used to identify the captive portal notification.
|
||||
PORTAL_NOTIFICATION_VALUE: "captive-portal-detected",
|
||||
|
||||
// This holds a weak reference to the captive portal tab so that we
|
||||
// don't leak it if the user closes it.
|
||||
_captivePortalTab: null,
|
||||
|
||||
/**
|
||||
* If a portal is detected when we don't have focus, we first wait for focus
|
||||
* and then add the tab if, after a recheck, the portal is still active. This
|
||||
* is set to true while we wait so that in the unlikely event that we receive
|
||||
* another notification while waiting, we don't do things twice.
|
||||
*/
|
||||
_delayedCaptivePortalDetectedInProgress: false,
|
||||
|
||||
// In the situation above, this is set to true while we wait for the recheck.
|
||||
// This flag exists so that tests can appropriately simulate a recheck.
|
||||
_waitingForRecheck: false,
|
||||
|
||||
get _captivePortalNotification() {
|
||||
let nb = document.getElementById("high-priority-global-notificationbox");
|
||||
return nb.getNotificationWithValue(this.PORTAL_NOTIFICATION_VALUE);
|
||||
},
|
||||
|
||||
get canonicalURL() {
|
||||
return Services.prefs.getCharPref("captivedetect.canonicalURL");
|
||||
},
|
||||
|
||||
get _browserBundle() {
|
||||
delete this._browserBundle;
|
||||
return this._browserBundle =
|
||||
Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
||||
},
|
||||
|
||||
init() {
|
||||
Services.obs.addObserver(this, "captive-portal-login", false);
|
||||
Services.obs.addObserver(this, "captive-portal-login-abort", false);
|
||||
Services.obs.addObserver(this, "captive-portal-login-success", false);
|
||||
|
||||
if (cps.state == cps.LOCKED_PORTAL) {
|
||||
// A captive portal has already been detected.
|
||||
this._captivePortalDetected();
|
||||
|
||||
// Automatically open a captive portal tab if there's no other browser window.
|
||||
let windows = Services.wm.getEnumerator("navigator:browser");
|
||||
if (windows.getNext() == window && !windows.hasMoreElements()) {
|
||||
this.ensureCaptivePortalTab();
|
||||
}
|
||||
}
|
||||
|
||||
cps.recheckCaptivePortal();
|
||||
},
|
||||
|
||||
uninit() {
|
||||
Services.obs.removeObserver(this, "captive-portal-login");
|
||||
Services.obs.removeObserver(this, "captive-portal-login-abort");
|
||||
Services.obs.removeObserver(this, "captive-portal-login-success");
|
||||
|
||||
|
||||
if (this._delayedCaptivePortalDetectedInProgress) {
|
||||
Services.obs.removeObserver(this, "xul-window-visible");
|
||||
}
|
||||
},
|
||||
|
||||
observe(aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "captive-portal-login":
|
||||
this._captivePortalDetected();
|
||||
break;
|
||||
case "captive-portal-login-abort":
|
||||
case "captive-portal-login-success":
|
||||
this._captivePortalGone();
|
||||
break;
|
||||
case "xul-window-visible":
|
||||
this._delayedCaptivePortalDetected();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_captivePortalDetected() {
|
||||
if (this._delayedCaptivePortalDetectedInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
let win = RecentWindow.getMostRecentBrowserWindow();
|
||||
// If no browser window has focus, open and show the tab when we regain focus.
|
||||
// This is so that if a different application was focused, when the user
|
||||
// (re-)focuses a browser window, we open the tab immediately in that window
|
||||
// so they can log in before continuing to browse.
|
||||
if (win != Services.ww.activeWindow) {
|
||||
this._delayedCaptivePortalDetectedInProgress = true;
|
||||
Services.obs.addObserver(this, "xul-window-visible", false);
|
||||
}
|
||||
|
||||
this._showNotification();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called after we regain focus if we detect a portal while a browser window
|
||||
* doesn't have focus. Triggers a portal recheck to reaffirm state, and adds
|
||||
* the tab if needed after a short delay to allow the recheck to complete.
|
||||
*/
|
||||
_delayedCaptivePortalDetected() {
|
||||
if (!this._delayedCaptivePortalDetectedInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
let win = RecentWindow.getMostRecentBrowserWindow();
|
||||
if (win != Services.ww.activeWindow) {
|
||||
// The window that got focused was not a browser window.
|
||||
return;
|
||||
}
|
||||
Services.obs.removeObserver(this, "xul-window-visible");
|
||||
this._delayedCaptivePortalDetectedInProgress = false;
|
||||
|
||||
if (win != window) {
|
||||
// Some other browser window got focus, we don't have to do anything.
|
||||
return;
|
||||
}
|
||||
// Trigger a portal recheck. The user may have logged into the portal via
|
||||
// another client, or changed networks.
|
||||
cps.recheckCaptivePortal();
|
||||
this._waitingForRecheck = true;
|
||||
let requestTime = Date.now();
|
||||
|
||||
let self = this;
|
||||
Services.obs.addObserver(function observer() {
|
||||
let time = Date.now() - requestTime;
|
||||
Services.obs.removeObserver(observer, "captive-portal-check-complete");
|
||||
self._waitingForRecheck = false;
|
||||
if (cps.state != cps.LOCKED_PORTAL) {
|
||||
// We're free of the portal!
|
||||
return;
|
||||
}
|
||||
|
||||
if (time <= self.PORTAL_RECHECK_DELAY_MS) {
|
||||
// The amount of time elapsed since we requested a recheck (i.e. since
|
||||
// the browser window was focused) was small enough that we can add and
|
||||
// focus a tab with the login page with no noticeable delay.
|
||||
self.ensureCaptivePortalTab();
|
||||
}
|
||||
}, "captive-portal-check-complete", false);
|
||||
},
|
||||
|
||||
_captivePortalGone() {
|
||||
if (this._delayedCaptivePortalDetectedInProgress) {
|
||||
Services.obs.removeObserver(this, "xul-window-visible");
|
||||
this._delayedCaptivePortalDetectedInProgress = false;
|
||||
}
|
||||
|
||||
this._removeNotification();
|
||||
},
|
||||
|
||||
handleEvent(aEvent) {
|
||||
if (aEvent.type != "TabSelect" || !this._captivePortalTab || !this._captivePortalNotification) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tab = this._captivePortalTab.get();
|
||||
let n = this._captivePortalNotification;
|
||||
if (!tab || !n) {
|
||||
return;
|
||||
}
|
||||
|
||||
let doc = tab.ownerDocument;
|
||||
let button = n.querySelector("button.notification-button");
|
||||
if (doc.defaultView.gBrowser.selectedTab == tab) {
|
||||
button.style.visibility = "hidden";
|
||||
} else {
|
||||
button.style.visibility = "visible";
|
||||
}
|
||||
},
|
||||
|
||||
_showNotification() {
|
||||
let buttons = [
|
||||
{
|
||||
label: this._browserBundle.GetStringFromName("captivePortal.showLoginPage"),
|
||||
callback: () => {
|
||||
this.ensureCaptivePortalTab();
|
||||
|
||||
// Returning true prevents the notification from closing.
|
||||
return true;
|
||||
},
|
||||
isDefault: true,
|
||||
},
|
||||
];
|
||||
|
||||
let message = this._browserBundle.GetStringFromName("captivePortal.infoMessage2");
|
||||
|
||||
let closeHandler = (aEventName) => {
|
||||
if (aEventName != "removed") {
|
||||
return;
|
||||
}
|
||||
gBrowser.tabContainer.removeEventListener("TabSelect", this);
|
||||
};
|
||||
|
||||
let nb = document.getElementById("high-priority-global-notificationbox");
|
||||
nb.appendNotification(message, this.PORTAL_NOTIFICATION_VALUE, "",
|
||||
nb.PRIORITY_INFO_MEDIUM, buttons, closeHandler);
|
||||
|
||||
gBrowser.tabContainer.addEventListener("TabSelect", this);
|
||||
},
|
||||
|
||||
_removeNotification() {
|
||||
let n = this._captivePortalNotification;
|
||||
if (!n || !n.parentNode) {
|
||||
return;
|
||||
}
|
||||
n.close();
|
||||
},
|
||||
|
||||
ensureCaptivePortalTab() {
|
||||
let tab;
|
||||
if (this._captivePortalTab) {
|
||||
tab = this._captivePortalTab.get();
|
||||
}
|
||||
|
||||
// If the tab is gone or going, we need to open a new one.
|
||||
if (!tab || tab.closing || !tab.parentNode) {
|
||||
tab = gBrowser.addTab(this.canonicalURL, { ownerTab: gBrowser.selectedTab });
|
||||
this._captivePortalTab = Cu.getWeakReference(tab);
|
||||
}
|
||||
|
||||
gBrowser.selectedTab = tab;
|
||||
|
||||
let canonicalURI = makeURI(this.canonicalURL);
|
||||
|
||||
// When we are no longer captive, close the tab if it's at the canonical URL.
|
||||
let tabCloser = () => {
|
||||
Services.obs.removeObserver(tabCloser, "captive-portal-login-abort");
|
||||
Services.obs.removeObserver(tabCloser, "captive-portal-login-success");
|
||||
if (!tab || tab.closing || !tab.parentNode || !tab.linkedBrowser ||
|
||||
!tab.linkedBrowser.currentURI.equalsExceptRef(canonicalURI)) {
|
||||
return;
|
||||
}
|
||||
gBrowser.removeTab(tab);
|
||||
}
|
||||
Services.obs.addObserver(tabCloser, "captive-portal-login-abort", false);
|
||||
Services.obs.addObserver(tabCloser, "captive-portal-login-success", false);
|
||||
},
|
||||
};
|
||||
|
|
@ -1,12 +0,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/.
|
||||
|
||||
<menu id="charsetMenu"
|
||||
label="&charsetMenu2.label;"
|
||||
accesskey="&charsetMenu2.accesskey;"
|
||||
oncommand="BrowserSetForcedCharacterSet(event.target.getAttribute('charset'));"
|
||||
onpopupshowing="CharsetMenu.build(event.target); UpdateCurrentCharset(this);">
|
||||
<menupopup>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
|
@ -1,454 +0,0 @@
|
|||
# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
# 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/.
|
||||
|
||||
# NB: IF YOU ADD ITEMS TO THIS FILE, PLEASE UPDATE THE WHITELIST IN
|
||||
# BrowserUITelemetry.jsm. SEE BUG 991757 FOR DETAILS.
|
||||
|
||||
<menugroup id="context-navigation">
|
||||
<menuitem id="context-back"
|
||||
class="menuitem-iconic"
|
||||
tooltiptext="&backButton.tooltip;"
|
||||
aria-label="&backCmd.label;"
|
||||
command="Browser:BackOrBackDuplicate"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="context-forward"
|
||||
class="menuitem-iconic"
|
||||
tooltiptext="&forwardButton.tooltip;"
|
||||
aria-label="&forwardCmd.label;"
|
||||
command="Browser:ForwardOrForwardDuplicate"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="context-reload"
|
||||
class="menuitem-iconic"
|
||||
tooltiptext="&reloadButton.tooltip;"
|
||||
aria-label="&reloadCmd.label;"
|
||||
oncommand="gContextMenu.reload(event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="context-stop"
|
||||
class="menuitem-iconic"
|
||||
tooltiptext="&stopButton.tooltip;"
|
||||
aria-label="&stopCmd.label;"
|
||||
command="Browser:Stop"/>
|
||||
<menuitem id="context-bookmarkpage"
|
||||
class="menuitem-iconic"
|
||||
observes="bookmarkThisPageBroadcaster"
|
||||
aria-label="&bookmarkPageCmd2.label;"
|
||||
oncommand="gContextMenu.bookmarkThisPage();"/>
|
||||
</menugroup>
|
||||
<menuseparator id="context-sep-navigation"/>
|
||||
<menuseparator id="page-menu-separator"/>
|
||||
<menuitem id="spell-no-suggestions"
|
||||
disabled="true"
|
||||
label="&spellNoSuggestions.label;"/>
|
||||
<menuitem id="spell-add-to-dictionary"
|
||||
label="&spellAddToDictionary.label;"
|
||||
accesskey="&spellAddToDictionary.accesskey;"
|
||||
oncommand="InlineSpellCheckerUI.addToDictionary();"/>
|
||||
<menuitem id="spell-undo-add-to-dictionary"
|
||||
label="&spellUndoAddToDictionary.label;"
|
||||
accesskey="&spellUndoAddToDictionary.accesskey;"
|
||||
oncommand="InlineSpellCheckerUI.undoAddToDictionary();" />
|
||||
<menuseparator id="spell-suggestions-separator"/>
|
||||
<menuitem id="context-openlinkincurrent"
|
||||
label="&openLinkCmdInCurrent.label;"
|
||||
accesskey="&openLinkCmdInCurrent.accesskey;"
|
||||
oncommand="gContextMenu.openLinkInCurrent();"/>
|
||||
# label and data-usercontextid are dynamically set.
|
||||
<menuitem id="context-openlinkincontainertab"
|
||||
accesskey="&openLinkCmdInTab.accesskey;"
|
||||
oncommand="gContextMenu.openLinkInTab(event);"/>
|
||||
<menuitem id="context-openlinkintab"
|
||||
label="&openLinkCmdInTab.label;"
|
||||
accesskey="&openLinkCmdInTab.accesskey;"
|
||||
data-usercontextid="0"
|
||||
oncommand="gContextMenu.openLinkInTab(event);"/>
|
||||
|
||||
<menu id="context-openlinkinusercontext-menu"
|
||||
label="&openLinkCmdInContainerTab.label;"
|
||||
accesskey="&openLinkCmdInContainerTab.accesskey;"
|
||||
hidden="true">
|
||||
<menupopup oncommand="gContextMenu.openLinkInTab(event);"
|
||||
onpopupshowing="return gContextMenu.createContainerMenu(event);" />
|
||||
</menu>
|
||||
|
||||
<menuitem id="context-openlink"
|
||||
label="&openLinkCmd.label;"
|
||||
accesskey="&openLinkCmd.accesskey;"
|
||||
oncommand="gContextMenu.openLink();"/>
|
||||
<menuitem id="context-openlinkprivate"
|
||||
label="&openLinkInPrivateWindowCmd.label;"
|
||||
accesskey="&openLinkInPrivateWindowCmd.accesskey;"
|
||||
oncommand="gContextMenu.openLinkInPrivateWindow();"/>
|
||||
<menuseparator id="context-sep-open"/>
|
||||
<menuitem id="context-bookmarklink"
|
||||
label="&bookmarkThisLinkCmd.label;"
|
||||
accesskey="&bookmarkThisLinkCmd.accesskey;"
|
||||
oncommand="gContextMenu.bookmarkLink();"/>
|
||||
<menuitem id="context-savelink"
|
||||
label="&saveLinkCmd.label;"
|
||||
accesskey="&saveLinkCmd.accesskey;"
|
||||
oncommand="gContextMenu.saveLink();"/>
|
||||
<menuitem id="context-copyemail"
|
||||
label="©EmailCmd.label;"
|
||||
accesskey="©EmailCmd.accesskey;"
|
||||
oncommand="gContextMenu.copyEmail();"/>
|
||||
<menuitem id="context-copylink"
|
||||
label="©LinkCmd.label;"
|
||||
accesskey="©LinkCmd.accesskey;"
|
||||
oncommand="gContextMenu.copyLink();"/>
|
||||
<menuseparator id="context-sep-copylink"/>
|
||||
<menuitem id="context-media-play"
|
||||
label="&mediaPlay.label;"
|
||||
accesskey="&mediaPlay.accesskey;"
|
||||
oncommand="gContextMenu.mediaCommand('play');"/>
|
||||
<menuitem id="context-media-pause"
|
||||
label="&mediaPause.label;"
|
||||
accesskey="&mediaPause.accesskey;"
|
||||
oncommand="gContextMenu.mediaCommand('pause');"/>
|
||||
<menuitem id="context-media-mute"
|
||||
label="&mediaMute.label;"
|
||||
accesskey="&mediaMute.accesskey;"
|
||||
oncommand="gContextMenu.mediaCommand('mute');"/>
|
||||
<menuitem id="context-media-unmute"
|
||||
label="&mediaUnmute.label;"
|
||||
accesskey="&mediaUnmute.accesskey;"
|
||||
oncommand="gContextMenu.mediaCommand('unmute');"/>
|
||||
<menu id="context-media-playbackrate" label="&mediaPlaybackRate2.label;" accesskey="&mediaPlaybackRate2.accesskey;">
|
||||
<menupopup>
|
||||
<menuitem id="context-media-playbackrate-050x"
|
||||
label="&mediaPlaybackRate050x2.label;"
|
||||
accesskey="&mediaPlaybackRate050x2.accesskey;"
|
||||
type="radio"
|
||||
name="playbackrate"
|
||||
oncommand="gContextMenu.mediaCommand('playbackRate', 0.5);"/>
|
||||
<menuitem id="context-media-playbackrate-100x"
|
||||
label="&mediaPlaybackRate100x2.label;"
|
||||
accesskey="&mediaPlaybackRate100x2.accesskey;"
|
||||
type="radio"
|
||||
name="playbackrate"
|
||||
checked="true"
|
||||
oncommand="gContextMenu.mediaCommand('playbackRate', 1.0);"/>
|
||||
<menuitem id="context-media-playbackrate-125x"
|
||||
label="&mediaPlaybackRate125x2.label;"
|
||||
accesskey="&mediaPlaybackRate125x2.accesskey;"
|
||||
type="radio"
|
||||
name="playbackrate"
|
||||
oncommand="gContextMenu.mediaCommand('playbackRate', 1.25);"/>
|
||||
<menuitem id="context-media-playbackrate-150x"
|
||||
label="&mediaPlaybackRate150x2.label;"
|
||||
accesskey="&mediaPlaybackRate150x2.accesskey;"
|
||||
type="radio"
|
||||
name="playbackrate"
|
||||
oncommand="gContextMenu.mediaCommand('playbackRate', 1.5);"/>
|
||||
<menuitem id="context-media-playbackrate-200x"
|
||||
label="&mediaPlaybackRate200x2.label;"
|
||||
accesskey="&mediaPlaybackRate200x2.accesskey;"
|
||||
type="radio"
|
||||
name="playbackrate"
|
||||
oncommand="gContextMenu.mediaCommand('playbackRate', 2.0);"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuitem id="context-media-loop"
|
||||
label="&mediaLoop.label;"
|
||||
accesskey="&mediaLoop.accesskey;"
|
||||
type="checkbox"
|
||||
oncommand="gContextMenu.mediaCommand('loop');"/>
|
||||
<menuitem id="context-media-showcontrols"
|
||||
label="&mediaShowControls.label;"
|
||||
accesskey="&mediaShowControls.accesskey;"
|
||||
oncommand="gContextMenu.mediaCommand('showcontrols');"/>
|
||||
<menuitem id="context-media-hidecontrols"
|
||||
label="&mediaHideControls.label;"
|
||||
accesskey="&mediaHideControls.accesskey;"
|
||||
oncommand="gContextMenu.mediaCommand('hidecontrols');"/>
|
||||
<menuitem id="context-video-fullscreen"
|
||||
accesskey="&videoFullScreen.accesskey;"
|
||||
label="&videoFullScreen.label;"
|
||||
oncommand="gContextMenu.mediaCommand('fullscreen');"/>
|
||||
<menuitem id="context-leave-dom-fullscreen"
|
||||
accesskey="&leaveDOMFullScreen.accesskey;"
|
||||
label="&leaveDOMFullScreen.label;"
|
||||
oncommand="gContextMenu.leaveDOMFullScreen();"/>
|
||||
<menuseparator id="context-media-sep-commands"/>
|
||||
<menuitem id="context-reloadimage"
|
||||
label="&reloadImageCmd.label;"
|
||||
accesskey="&reloadImageCmd.accesskey;"
|
||||
oncommand="gContextMenu.reloadImage();"/>
|
||||
<menuitem id="context-viewimage"
|
||||
label="&viewImageCmd.label;"
|
||||
accesskey="&viewImageCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewMedia(event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="context-viewvideo"
|
||||
label="&viewVideoCmd.label;"
|
||||
accesskey="&viewVideoCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewMedia(event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
#ifdef CONTEXT_COPY_IMAGE_CONTENTS
|
||||
<menuitem id="context-copyimage-contents"
|
||||
label="©ImageContentsCmd.label;"
|
||||
accesskey="©ImageContentsCmd.accesskey;"
|
||||
oncommand="goDoCommand('cmd_copyImage');"/>
|
||||
#endif
|
||||
<menuitem id="context-copyimage"
|
||||
label="©ImageCmd.label;"
|
||||
accesskey="©ImageCmd.accesskey;"
|
||||
oncommand="gContextMenu.copyMediaLocation();"/>
|
||||
<menuitem id="context-copyvideourl"
|
||||
label="©VideoURLCmd.label;"
|
||||
accesskey="©VideoURLCmd.accesskey;"
|
||||
oncommand="gContextMenu.copyMediaLocation();"/>
|
||||
<menuitem id="context-copyaudiourl"
|
||||
label="©AudioURLCmd.label;"
|
||||
accesskey="©AudioURLCmd.accesskey;"
|
||||
oncommand="gContextMenu.copyMediaLocation();"/>
|
||||
<menuseparator id="context-sep-copyimage"/>
|
||||
<menuitem id="context-saveimage"
|
||||
label="&saveImageCmd.label;"
|
||||
accesskey="&saveImageCmd.accesskey;"
|
||||
oncommand="gContextMenu.saveMedia();"/>
|
||||
<menuitem id="context-sendimage"
|
||||
label="&emailImageCmd.label;"
|
||||
accesskey="&emailImageCmd.accesskey;"
|
||||
oncommand="gContextMenu.sendMedia();"/>
|
||||
<menuitem id="context-setDesktopBackground"
|
||||
label="&setDesktopBackgroundCmd.label;"
|
||||
accesskey="&setDesktopBackgroundCmd.accesskey;"
|
||||
oncommand="gContextMenu.setDesktopBackground();"/>
|
||||
<menuitem id="context-viewimageinfo"
|
||||
label="&viewImageInfoCmd.label;"
|
||||
accesskey="&viewImageInfoCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewImageInfo();"/>
|
||||
<menuitem id="context-viewimagedesc"
|
||||
label="&viewImageDescCmd.label;"
|
||||
accesskey="&viewImageDescCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewImageDesc(event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="context-savevideo"
|
||||
label="&saveVideoCmd.label;"
|
||||
accesskey="&saveVideoCmd.accesskey;"
|
||||
oncommand="gContextMenu.saveMedia();"/>
|
||||
<menuitem id="context-saveaudio"
|
||||
label="&saveAudioCmd.label;"
|
||||
accesskey="&saveAudioCmd.accesskey;"
|
||||
oncommand="gContextMenu.saveMedia();"/>
|
||||
<menuitem id="context-video-saveimage"
|
||||
accesskey="&videoSaveImage.accesskey;"
|
||||
label="&videoSaveImage.label;"
|
||||
oncommand="gContextMenu.saveVideoFrameAsImage();"/>
|
||||
<menuitem id="context-sendvideo"
|
||||
label="&emailVideoCmd.label;"
|
||||
accesskey="&emailVideoCmd.accesskey;"
|
||||
oncommand="gContextMenu.sendMedia();"/>
|
||||
<menu id="context-castvideo"
|
||||
label="&castVideoCmd.label;"
|
||||
accesskey="&castVideoCmd.accesskey;">
|
||||
<menupopup id="context-castvideo-popup" onpopupshowing="gContextMenu.populateCastVideoMenu(this)"/>
|
||||
</menu>
|
||||
<menuitem id="context-sendaudio"
|
||||
label="&emailAudioCmd.label;"
|
||||
accesskey="&emailAudioCmd.accesskey;"
|
||||
oncommand="gContextMenu.sendMedia();"/>
|
||||
<menuitem id="context-ctp-play"
|
||||
label="&playPluginCmd.label;"
|
||||
accesskey="&playPluginCmd.accesskey;"
|
||||
oncommand="gContextMenu.playPlugin();"/>
|
||||
<menuitem id="context-ctp-hide"
|
||||
label="&hidePluginCmd.label;"
|
||||
accesskey="&hidePluginCmd.accesskey;"
|
||||
oncommand="gContextMenu.hidePlugin();"/>
|
||||
<menuseparator id="context-sep-ctp"/>
|
||||
<menuitem id="context-savepage"
|
||||
label="&savePageCmd.label;"
|
||||
accesskey="&savePageCmd.accesskey2;"
|
||||
oncommand="gContextMenu.savePageAs();"/>
|
||||
<menuseparator id="context-sep-sendpagetodevice" hidden="true"/>
|
||||
<menu id="context-sendpagetodevice"
|
||||
label="&sendPageToDevice.label;"
|
||||
accesskey="&sendPageToDevice.accesskey;"
|
||||
hidden="true">
|
||||
<menupopup id="context-sendpagetodevice-popup"
|
||||
onpopupshowing="(() => { let browser = gBrowser || getPanelBrowser(); gFxAccounts.populateSendTabToDevicesMenu(event.target, browser.currentURI.spec, browser.contentTitle); })()"/>
|
||||
</menu>
|
||||
<menuseparator id="context-sep-viewbgimage"/>
|
||||
<menuitem id="context-viewbgimage"
|
||||
label="&viewBGImageCmd.label;"
|
||||
accesskey="&viewBGImageCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewBGImage(event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
<menuitem id="context-undo"
|
||||
label="&undoCmd.label;"
|
||||
accesskey="&undoCmd.accesskey;"
|
||||
command="cmd_undo"/>
|
||||
<menuseparator id="context-sep-undo"/>
|
||||
<menuitem id="context-cut"
|
||||
label="&cutCmd.label;"
|
||||
accesskey="&cutCmd.accesskey;"
|
||||
command="cmd_cut"/>
|
||||
<menuitem id="context-copy"
|
||||
label="©Cmd.label;"
|
||||
accesskey="©Cmd.accesskey;"
|
||||
command="cmd_copy"/>
|
||||
<menuitem id="context-paste"
|
||||
label="&pasteCmd.label;"
|
||||
accesskey="&pasteCmd.accesskey;"
|
||||
command="cmd_paste"/>
|
||||
<menuitem id="context-delete"
|
||||
label="&deleteCmd.label;"
|
||||
accesskey="&deleteCmd.accesskey;"
|
||||
command="cmd_delete"/>
|
||||
<menuseparator id="context-sep-paste"/>
|
||||
<menuitem id="context-selectall"
|
||||
label="&selectAllCmd.label;"
|
||||
accesskey="&selectAllCmd.accesskey;"
|
||||
command="cmd_selectAll"/>
|
||||
<menuseparator id="context-sep-selectall"/>
|
||||
<menuitem id="context-keywordfield"
|
||||
label="&keywordfield.label;"
|
||||
accesskey="&keywordfield.accesskey;"
|
||||
oncommand="AddKeywordForSearchField();"/>
|
||||
<menuitem id="context-searchselect"
|
||||
oncommand="BrowserSearch.loadSearchFromContext(this.searchTerms);"/>
|
||||
<menuseparator id="context-sep-sendlinktodevice" hidden="true"/>
|
||||
<menu id="context-sendlinktodevice"
|
||||
label="&sendLinkToDevice.label;"
|
||||
accesskey="&sendLinkToDevice.accesskey;"
|
||||
hidden="true">
|
||||
<menupopup id="context-sendlinktodevice-popup"
|
||||
onpopupshowing="gFxAccounts.populateSendTabToDevicesMenu(event.target, gContextMenu.linkURL, gContextMenu.linkTextStr);"/>
|
||||
</menu>
|
||||
<menuseparator id="frame-sep"/>
|
||||
<menu id="frame" label="&thisFrameMenu.label;" accesskey="&thisFrameMenu.accesskey;">
|
||||
<menupopup>
|
||||
<menuitem id="context-showonlythisframe"
|
||||
label="&showOnlyThisFrameCmd.label;"
|
||||
accesskey="&showOnlyThisFrameCmd.accesskey;"
|
||||
oncommand="gContextMenu.showOnlyThisFrame();"/>
|
||||
<menuitem id="context-openframeintab"
|
||||
label="&openFrameCmdInTab.label;"
|
||||
accesskey="&openFrameCmdInTab.accesskey;"
|
||||
oncommand="gContextMenu.openFrameInTab();"/>
|
||||
<menuitem id="context-openframe"
|
||||
label="&openFrameCmd.label;"
|
||||
accesskey="&openFrameCmd.accesskey;"
|
||||
oncommand="gContextMenu.openFrame();"/>
|
||||
<menuseparator id="open-frame-sep"/>
|
||||
<menuitem id="context-reloadframe"
|
||||
label="&reloadFrameCmd.label;"
|
||||
accesskey="&reloadFrameCmd.accesskey;"
|
||||
oncommand="gContextMenu.reloadFrame();"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="context-bookmarkframe"
|
||||
label="&bookmarkThisFrameCmd.label;"
|
||||
accesskey="&bookmarkThisFrameCmd.accesskey;"
|
||||
oncommand="gContextMenu.addBookmarkForFrame();"/>
|
||||
<menuitem id="context-saveframe"
|
||||
label="&saveFrameCmd.label;"
|
||||
accesskey="&saveFrameCmd.accesskey;"
|
||||
oncommand="gContextMenu.saveFrame();"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="context-printframe"
|
||||
label="&printFrameCmd.label;"
|
||||
accesskey="&printFrameCmd.accesskey;"
|
||||
oncommand="gContextMenu.printFrame();"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="context-viewframesource"
|
||||
label="&viewFrameSourceCmd.label;"
|
||||
accesskey="&viewFrameSourceCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewFrameSource();"
|
||||
observes="isFrameImage"/>
|
||||
<menuitem id="context-viewframeinfo"
|
||||
label="&viewFrameInfoCmd.label;"
|
||||
accesskey="&viewFrameInfoCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewFrameInfo();"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuitem id="context-viewpartialsource-selection"
|
||||
label="&viewPartialSourceForSelectionCmd.label;"
|
||||
accesskey="&viewPartialSourceCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewPartialSource('selection');"
|
||||
observes="isImage"/>
|
||||
<menuitem id="context-viewpartialsource-mathml"
|
||||
label="&viewPartialSourceForMathMLCmd.label;"
|
||||
accesskey="&viewPartialSourceCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewPartialSource('mathml');"
|
||||
observes="isImage"/>
|
||||
<menuseparator id="context-sep-viewsource"/>
|
||||
<menuitem id="context-viewsource"
|
||||
label="&viewPageSourceCmd.label;"
|
||||
accesskey="&viewPageSourceCmd.accesskey;"
|
||||
oncommand="BrowserViewSource(gContextMenu.browser);"
|
||||
observes="canViewSource"/>
|
||||
<menuitem id="context-viewinfo"
|
||||
label="&viewPageInfoCmd.label;"
|
||||
accesskey="&viewPageInfoCmd.accesskey;"
|
||||
oncommand="gContextMenu.viewInfo();"/>
|
||||
<menuseparator id="spell-separator"/>
|
||||
<menuitem id="spell-check-enabled"
|
||||
label="&spellCheckToggle.label;"
|
||||
type="checkbox"
|
||||
accesskey="&spellCheckToggle.accesskey;"
|
||||
oncommand="InlineSpellCheckerUI.toggleEnabled(window);"/>
|
||||
<menuitem id="spell-add-dictionaries-main"
|
||||
label="&spellAddDictionaries.label;"
|
||||
accesskey="&spellAddDictionaries.accesskey;"
|
||||
oncommand="gContextMenu.addDictionaries();"/>
|
||||
<menu id="spell-dictionaries"
|
||||
label="&spellDictionaries.label;"
|
||||
accesskey="&spellDictionaries.accesskey;">
|
||||
<menupopup id="spell-dictionaries-menu">
|
||||
<menuseparator id="spell-language-separator"/>
|
||||
<menuitem id="spell-add-dictionaries"
|
||||
label="&spellAddDictionaries.label;"
|
||||
accesskey="&spellAddDictionaries.accesskey;"
|
||||
oncommand="gContextMenu.addDictionaries();"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuseparator hidden="true" id="context-sep-bidi"/>
|
||||
<menuitem hidden="true" id="context-bidi-text-direction-toggle"
|
||||
label="&bidiSwitchTextDirectionItem.label;"
|
||||
accesskey="&bidiSwitchTextDirectionItem.accesskey;"
|
||||
command="cmd_switchTextDirection"/>
|
||||
<menuitem hidden="true" id="context-bidi-page-direction-toggle"
|
||||
label="&bidiSwitchPageDirectionItem.label;"
|
||||
accesskey="&bidiSwitchPageDirectionItem.accesskey;"
|
||||
oncommand="gContextMenu.switchPageDirection();"/>
|
||||
<menuseparator id="fill-login-separator" hidden="true"/>
|
||||
<menu id="fill-login"
|
||||
label="&fillLoginMenu.label;"
|
||||
label-login="&fillLoginMenu.label;"
|
||||
label-password="&fillPasswordMenu.label;"
|
||||
label-username="&fillUsernameMenu.label;"
|
||||
accesskey="&fillLoginMenu.accesskey;"
|
||||
accesskey-login="&fillLoginMenu.accesskey;"
|
||||
accesskey-password="&fillPasswordMenu.accesskey;"
|
||||
accesskey-username="&fillUsernameMenu.accesskey;"
|
||||
hidden="true">
|
||||
<menupopup id="fill-login-popup">
|
||||
<menuitem id="fill-login-no-logins"
|
||||
label="&noLoginSuggestions.label;"
|
||||
disabled="true"
|
||||
hidden="true"/>
|
||||
<menuseparator id="saved-logins-separator"/>
|
||||
<menuitem id="fill-login-saved-passwords"
|
||||
label="&viewSavedLogins.label;"
|
||||
oncommand="gContextMenu.openPasswordManager();"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
#ifdef MOZ_DEVTOOLS
|
||||
<menuseparator id="inspect-separator" hidden="true"/>
|
||||
<menuitem id="context-inspect"
|
||||
hidden="true"
|
||||
label="&inspectContextMenu.label;"
|
||||
accesskey="&inspectContextMenu.accesskey;"
|
||||
oncommand="gContextMenu.inspectNode();"/>
|
||||
#endif
|
||||
<menuseparator id="context-media-eme-separator" hidden="true"/>
|
||||
<menuitem id="context-media-eme-learnmore"
|
||||
class="menuitem-iconic"
|
||||
hidden="true"
|
||||
label="&emeLearnMoreContextMenu.label;"
|
||||
accesskey="&emeLearnMoreContextMenu.accesskey;"
|
||||
oncommand="gContextMenu.drmLearnMore(event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
|
|
@ -1,587 +0,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/. */
|
||||
|
||||
/**
|
||||
* Tab previews utility, produces thumbnails
|
||||
*/
|
||||
var tabPreviews = {
|
||||
init: function tabPreviews_init() {
|
||||
if (this._selectedTab)
|
||||
return;
|
||||
this._selectedTab = gBrowser.selectedTab;
|
||||
|
||||
gBrowser.tabContainer.addEventListener("TabSelect", this, false);
|
||||
gBrowser.tabContainer.addEventListener("SSTabRestored", this, false);
|
||||
|
||||
let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
|
||||
.getService(Ci.nsIScreenManager);
|
||||
let left = {}, top = {}, width = {}, height = {};
|
||||
screenManager.primaryScreen.GetRectDisplayPix(left, top, width, height);
|
||||
this.aspectRatio = height.value / width.value;
|
||||
},
|
||||
|
||||
get: function tabPreviews_get(aTab) {
|
||||
let uri = aTab.linkedBrowser.currentURI.spec;
|
||||
|
||||
if (aTab.__thumbnail_lastURI &&
|
||||
aTab.__thumbnail_lastURI != uri) {
|
||||
aTab.__thumbnail = null;
|
||||
aTab.__thumbnail_lastURI = null;
|
||||
}
|
||||
|
||||
if (aTab.__thumbnail)
|
||||
return aTab.__thumbnail;
|
||||
|
||||
if (aTab.getAttribute("pending") == "true") {
|
||||
let img = new Image;
|
||||
img.src = PageThumbs.getThumbnailURL(uri);
|
||||
return img;
|
||||
}
|
||||
|
||||
return this.capture(aTab, !aTab.hasAttribute("busy"));
|
||||
},
|
||||
|
||||
capture: function tabPreviews_capture(aTab, aShouldCache) {
|
||||
let browser = aTab.linkedBrowser;
|
||||
let uri = browser.currentURI.spec;
|
||||
let canvas = PageThumbs.createCanvas(window);
|
||||
PageThumbs.shouldStoreThumbnail(browser, (aDoStore) => {
|
||||
if (aDoStore && aShouldCache) {
|
||||
PageThumbs.captureAndStore(browser, function () {
|
||||
let img = new Image;
|
||||
img.src = PageThumbs.getThumbnailURL(uri);
|
||||
aTab.__thumbnail = img;
|
||||
aTab.__thumbnail_lastURI = uri;
|
||||
canvas.getContext("2d").drawImage(img, 0, 0);
|
||||
});
|
||||
} else {
|
||||
PageThumbs.captureToCanvas(browser, canvas, () => {
|
||||
if (aShouldCache) {
|
||||
aTab.__thumbnail = canvas;
|
||||
aTab.__thumbnail_lastURI = uri;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return canvas;
|
||||
},
|
||||
|
||||
handleEvent: function tabPreviews_handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "TabSelect":
|
||||
if (this._selectedTab &&
|
||||
this._selectedTab.parentNode &&
|
||||
!this._pendingUpdate) {
|
||||
// Generate a thumbnail for the tab that was selected.
|
||||
// The timeout keeps the UI snappy and prevents us from generating thumbnails
|
||||
// for tabs that will be closed. During that timeout, don't generate other
|
||||
// thumbnails in case multiple TabSelect events occur fast in succession.
|
||||
this._pendingUpdate = true;
|
||||
setTimeout(function (self, aTab) {
|
||||
self._pendingUpdate = false;
|
||||
if (aTab.parentNode &&
|
||||
!aTab.hasAttribute("busy") &&
|
||||
!aTab.hasAttribute("pending"))
|
||||
self.capture(aTab, true);
|
||||
}, 2000, this, this._selectedTab);
|
||||
}
|
||||
this._selectedTab = event.target;
|
||||
break;
|
||||
case "SSTabRestored":
|
||||
this.capture(event.target, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var tabPreviewPanelHelper = {
|
||||
opening: function (host) {
|
||||
host.panel.hidden = false;
|
||||
|
||||
var handler = this._generateHandler(host);
|
||||
host.panel.addEventListener("popupshown", handler, false);
|
||||
host.panel.addEventListener("popuphiding", handler, false);
|
||||
|
||||
host._prevFocus = document.commandDispatcher.focusedElement;
|
||||
},
|
||||
_generateHandler: function (host) {
|
||||
var self = this;
|
||||
return function (event) {
|
||||
if (event.target == host.panel) {
|
||||
host.panel.removeEventListener(event.type, arguments.callee, false);
|
||||
self["_" + event.type](host);
|
||||
}
|
||||
};
|
||||
},
|
||||
_popupshown: function (host) {
|
||||
if ("setupGUI" in host)
|
||||
host.setupGUI();
|
||||
},
|
||||
_popuphiding: function (host) {
|
||||
if ("suspendGUI" in host)
|
||||
host.suspendGUI();
|
||||
|
||||
if (host._prevFocus) {
|
||||
Services.focus.setFocus(host._prevFocus, Ci.nsIFocusManager.FLAG_NOSCROLL);
|
||||
host._prevFocus = null;
|
||||
} else
|
||||
gBrowser.selectedBrowser.focus();
|
||||
|
||||
if (host.tabToSelect) {
|
||||
gBrowser.selectedTab = host.tabToSelect;
|
||||
host.tabToSelect = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ctrl-Tab panel
|
||||
*/
|
||||
var ctrlTab = {
|
||||
get panel () {
|
||||
delete this.panel;
|
||||
return this.panel = document.getElementById("ctrlTab-panel");
|
||||
},
|
||||
get showAllButton () {
|
||||
delete this.showAllButton;
|
||||
return this.showAllButton = document.getElementById("ctrlTab-showAll");
|
||||
},
|
||||
get previews () {
|
||||
delete this.previews;
|
||||
return this.previews = this.panel.getElementsByClassName("ctrlTab-preview");
|
||||
},
|
||||
get maxTabPreviews () {
|
||||
delete this.maxTabPreviews;
|
||||
return this.maxTabPreviews = this.previews.length - 1;
|
||||
},
|
||||
get canvasWidth () {
|
||||
delete this.canvasWidth;
|
||||
return this.canvasWidth = Math.ceil(screen.availWidth * .85 / this.maxTabPreviews);
|
||||
},
|
||||
get canvasHeight () {
|
||||
delete this.canvasHeight;
|
||||
return this.canvasHeight = Math.round(this.canvasWidth * tabPreviews.aspectRatio);
|
||||
},
|
||||
get keys () {
|
||||
var keys = {};
|
||||
["close", "find", "selectAll"].forEach(function (key) {
|
||||
keys[key] = document.getElementById("key_" + key)
|
||||
.getAttribute("key")
|
||||
.toLocaleLowerCase().charCodeAt(0);
|
||||
});
|
||||
delete this.keys;
|
||||
return this.keys = keys;
|
||||
},
|
||||
_selectedIndex: 0,
|
||||
get selected () {
|
||||
return this._selectedIndex < 0 ?
|
||||
document.activeElement :
|
||||
this.previews.item(this._selectedIndex);
|
||||
},
|
||||
get isOpen () {
|
||||
return this.panel.state == "open" || this.panel.state == "showing" || this._timer;
|
||||
},
|
||||
get tabCount () {
|
||||
return this.tabList.length;
|
||||
},
|
||||
get tabPreviewCount () {
|
||||
return Math.min(this.maxTabPreviews, this.tabCount);
|
||||
},
|
||||
|
||||
get tabList () {
|
||||
return this._recentlyUsedTabs;
|
||||
},
|
||||
|
||||
init: function ctrlTab_init() {
|
||||
if (!this._recentlyUsedTabs) {
|
||||
tabPreviews.init();
|
||||
|
||||
this._initRecentlyUsedTabs();
|
||||
this._init(true);
|
||||
}
|
||||
},
|
||||
|
||||
uninit: function ctrlTab_uninit() {
|
||||
this._recentlyUsedTabs = null;
|
||||
this._init(false);
|
||||
},
|
||||
|
||||
prefName: "browser.ctrlTab.previews",
|
||||
readPref: function ctrlTab_readPref() {
|
||||
var enable =
|
||||
gPrefService.getBoolPref(this.prefName) &&
|
||||
(!gPrefService.prefHasUserValue("browser.ctrlTab.disallowForScreenReaders") ||
|
||||
!gPrefService.getBoolPref("browser.ctrlTab.disallowForScreenReaders"));
|
||||
|
||||
if (enable)
|
||||
this.init();
|
||||
else
|
||||
this.uninit();
|
||||
},
|
||||
observe: function (aSubject, aTopic, aPrefName) {
|
||||
this.readPref();
|
||||
},
|
||||
|
||||
updatePreviews: function ctrlTab_updatePreviews() {
|
||||
for (let i = 0; i < this.previews.length; i++)
|
||||
this.updatePreview(this.previews[i], this.tabList[i]);
|
||||
|
||||
var showAllLabel = gNavigatorBundle.getString("ctrlTab.listAllTabs.label");
|
||||
this.showAllButton.label =
|
||||
PluralForm.get(this.tabCount, showAllLabel).replace("#1", this.tabCount);
|
||||
this.showAllButton.hidden = !allTabs.canOpen;
|
||||
},
|
||||
|
||||
updatePreview: function ctrlTab_updatePreview(aPreview, aTab) {
|
||||
if (aPreview == this.showAllButton)
|
||||
return;
|
||||
|
||||
aPreview._tab = aTab;
|
||||
|
||||
if (aPreview.firstChild)
|
||||
aPreview.removeChild(aPreview.firstChild);
|
||||
if (aTab) {
|
||||
let canvasWidth = this.canvasWidth;
|
||||
let canvasHeight = this.canvasHeight;
|
||||
aPreview.appendChild(tabPreviews.get(aTab));
|
||||
aPreview.setAttribute("label", aTab.label);
|
||||
aPreview.setAttribute("tooltiptext", aTab.label);
|
||||
aPreview.setAttribute("crop", aTab.crop);
|
||||
aPreview.setAttribute("canvaswidth", canvasWidth);
|
||||
aPreview.setAttribute("canvasstyle",
|
||||
"max-width:" + canvasWidth + "px;" +
|
||||
"min-width:" + canvasWidth + "px;" +
|
||||
"max-height:" + canvasHeight + "px;" +
|
||||
"min-height:" + canvasHeight + "px;");
|
||||
if (aTab.image)
|
||||
aPreview.setAttribute("image", aTab.image);
|
||||
else
|
||||
aPreview.removeAttribute("image");
|
||||
aPreview.hidden = false;
|
||||
} else {
|
||||
aPreview.hidden = true;
|
||||
aPreview.removeAttribute("label");
|
||||
aPreview.removeAttribute("tooltiptext");
|
||||
aPreview.removeAttribute("image");
|
||||
}
|
||||
},
|
||||
|
||||
advanceFocus: function ctrlTab_advanceFocus(aForward) {
|
||||
let selectedIndex = Array.indexOf(this.previews, this.selected);
|
||||
do {
|
||||
selectedIndex += aForward ? 1 : -1;
|
||||
if (selectedIndex < 0)
|
||||
selectedIndex = this.previews.length - 1;
|
||||
else if (selectedIndex >= this.previews.length)
|
||||
selectedIndex = 0;
|
||||
} while (this.previews[selectedIndex].hidden);
|
||||
|
||||
if (this._selectedIndex == -1) {
|
||||
// Focus is already in the panel.
|
||||
this.previews[selectedIndex].focus();
|
||||
} else {
|
||||
this._selectedIndex = selectedIndex;
|
||||
}
|
||||
|
||||
if (this._timer) {
|
||||
clearTimeout(this._timer);
|
||||
this._timer = null;
|
||||
this._openPanel();
|
||||
}
|
||||
},
|
||||
|
||||
_mouseOverFocus: function ctrlTab_mouseOverFocus(aPreview) {
|
||||
if (this._trackMouseOver)
|
||||
aPreview.focus();
|
||||
},
|
||||
|
||||
pick: function ctrlTab_pick(aPreview) {
|
||||
if (!this.tabCount)
|
||||
return;
|
||||
|
||||
var select = (aPreview || this.selected);
|
||||
|
||||
if (select == this.showAllButton)
|
||||
this.showAllTabs();
|
||||
else
|
||||
this.close(select._tab);
|
||||
},
|
||||
|
||||
showAllTabs: function ctrlTab_showAllTabs(aPreview) {
|
||||
this.close();
|
||||
document.getElementById("Browser:ShowAllTabs").doCommand();
|
||||
},
|
||||
|
||||
remove: function ctrlTab_remove(aPreview) {
|
||||
if (aPreview._tab)
|
||||
gBrowser.removeTab(aPreview._tab);
|
||||
},
|
||||
|
||||
attachTab: function ctrlTab_attachTab(aTab, aPos) {
|
||||
if (aTab.closing)
|
||||
return;
|
||||
|
||||
if (aPos == 0)
|
||||
this._recentlyUsedTabs.unshift(aTab);
|
||||
else if (aPos)
|
||||
this._recentlyUsedTabs.splice(aPos, 0, aTab);
|
||||
else
|
||||
this._recentlyUsedTabs.push(aTab);
|
||||
},
|
||||
|
||||
detachTab: function ctrlTab_detachTab(aTab) {
|
||||
var i = this._recentlyUsedTabs.indexOf(aTab);
|
||||
if (i >= 0)
|
||||
this._recentlyUsedTabs.splice(i, 1);
|
||||
},
|
||||
|
||||
open: function ctrlTab_open() {
|
||||
if (this.isOpen)
|
||||
return;
|
||||
|
||||
document.addEventListener("keyup", this, true);
|
||||
|
||||
this.updatePreviews();
|
||||
this._selectedIndex = 1;
|
||||
|
||||
// Add a slight delay before showing the UI, so that a quick
|
||||
// "ctrl-tab" keypress just flips back to the MRU tab.
|
||||
this._timer = setTimeout(function (self) {
|
||||
self._timer = null;
|
||||
self._openPanel();
|
||||
}, 200, this);
|
||||
},
|
||||
|
||||
_openPanel: function ctrlTab_openPanel() {
|
||||
tabPreviewPanelHelper.opening(this);
|
||||
|
||||
this.panel.width = Math.min(screen.availWidth * .99,
|
||||
this.canvasWidth * 1.25 * this.tabPreviewCount);
|
||||
var estimateHeight = this.canvasHeight * 1.25 + 75;
|
||||
this.panel.openPopupAtScreen(screen.availLeft + (screen.availWidth - this.panel.width) / 2,
|
||||
screen.availTop + (screen.availHeight - estimateHeight) / 2,
|
||||
false);
|
||||
},
|
||||
|
||||
close: function ctrlTab_close(aTabToSelect) {
|
||||
if (!this.isOpen)
|
||||
return;
|
||||
|
||||
if (this._timer) {
|
||||
clearTimeout(this._timer);
|
||||
this._timer = null;
|
||||
this.suspendGUI();
|
||||
if (aTabToSelect)
|
||||
gBrowser.selectedTab = aTabToSelect;
|
||||
return;
|
||||
}
|
||||
|
||||
this.tabToSelect = aTabToSelect;
|
||||
this.panel.hidePopup();
|
||||
},
|
||||
|
||||
setupGUI: function ctrlTab_setupGUI() {
|
||||
this.selected.focus();
|
||||
this._selectedIndex = -1;
|
||||
|
||||
// Track mouse movement after a brief delay so that the item that happens
|
||||
// to be under the mouse pointer initially won't be selected unintentionally.
|
||||
this._trackMouseOver = false;
|
||||
setTimeout(function (self) {
|
||||
if (self.isOpen)
|
||||
self._trackMouseOver = true;
|
||||
}, 0, this);
|
||||
},
|
||||
|
||||
suspendGUI: function ctrlTab_suspendGUI() {
|
||||
document.removeEventListener("keyup", this, true);
|
||||
|
||||
for (let preview of this.previews) {
|
||||
this.updatePreview(preview, null);
|
||||
}
|
||||
},
|
||||
|
||||
onKeyPress: function ctrlTab_onKeyPress(event) {
|
||||
var isOpen = this.isOpen;
|
||||
|
||||
if (isOpen) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
switch (event.keyCode) {
|
||||
case event.DOM_VK_TAB:
|
||||
if (event.ctrlKey && !event.altKey && !event.metaKey) {
|
||||
if (isOpen) {
|
||||
this.advanceFocus(!event.shiftKey);
|
||||
} else if (!event.shiftKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
let tabs = gBrowser.visibleTabs;
|
||||
if (tabs.length > 2) {
|
||||
this.open();
|
||||
} else if (tabs.length == 2) {
|
||||
let index = tabs[0].selected ? 1 : 0;
|
||||
gBrowser.selectedTab = tabs[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (isOpen && event.ctrlKey) {
|
||||
if (event.keyCode == event.DOM_VK_DELETE) {
|
||||
this.remove(this.selected);
|
||||
break;
|
||||
}
|
||||
switch (event.charCode) {
|
||||
case this.keys.close:
|
||||
this.remove(this.selected);
|
||||
break;
|
||||
case this.keys.find:
|
||||
case this.keys.selectAll:
|
||||
this.showAllTabs();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
removeClosingTabFromUI: function ctrlTab_removeClosingTabFromUI(aTab) {
|
||||
if (this.tabCount == 2) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
this.updatePreviews();
|
||||
|
||||
if (this.selected.hidden)
|
||||
this.advanceFocus(false);
|
||||
if (this.selected == this.showAllButton)
|
||||
this.advanceFocus(false);
|
||||
|
||||
// If the current tab is removed, another tab can steal our focus.
|
||||
if (aTab.selected && this.panel.state == "open") {
|
||||
setTimeout(function (selected) {
|
||||
selected.focus();
|
||||
}, 0, this.selected);
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function ctrlTab_handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "SSWindowRestored":
|
||||
this._initRecentlyUsedTabs();
|
||||
break;
|
||||
case "TabAttrModified":
|
||||
// tab attribute modified (e.g. label, crop, busy, image, selected)
|
||||
for (let i = this.previews.length - 1; i >= 0; i--) {
|
||||
if (this.previews[i]._tab && this.previews[i]._tab == event.target) {
|
||||
this.updatePreview(this.previews[i], event.target);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "TabSelect":
|
||||
this.detachTab(event.target);
|
||||
this.attachTab(event.target, 0);
|
||||
break;
|
||||
case "TabOpen":
|
||||
this.attachTab(event.target, 1);
|
||||
break;
|
||||
case "TabClose":
|
||||
this.detachTab(event.target);
|
||||
if (this.isOpen)
|
||||
this.removeClosingTabFromUI(event.target);
|
||||
break;
|
||||
case "keypress":
|
||||
this.onKeyPress(event);
|
||||
break;
|
||||
case "keyup":
|
||||
if (event.keyCode == event.DOM_VK_CONTROL)
|
||||
this.pick();
|
||||
break;
|
||||
case "popupshowing":
|
||||
if (event.target.id == "menu_viewPopup")
|
||||
document.getElementById("menu_showAllTabs").hidden = !allTabs.canOpen;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
filterForThumbnailExpiration: function (aCallback) {
|
||||
// Save a few more thumbnails than we actually display, so that when tabs
|
||||
// are closed, the previews we add instead still get thumbnails.
|
||||
const extraThumbnails = 3;
|
||||
const thumbnailCount = Math.min(this.tabPreviewCount + extraThumbnails,
|
||||
this.tabCount);
|
||||
|
||||
let urls = [];
|
||||
for (let i = 0; i < thumbnailCount; i++)
|
||||
urls.push(this.tabList[i].linkedBrowser.currentURI.spec);
|
||||
|
||||
aCallback(urls);
|
||||
},
|
||||
|
||||
_initRecentlyUsedTabs: function () {
|
||||
this._recentlyUsedTabs =
|
||||
Array.filter(gBrowser.tabs, tab => !tab.closing)
|
||||
.sort((tab1, tab2) => tab2.lastAccessed - tab1.lastAccessed);
|
||||
},
|
||||
|
||||
_init: function ctrlTab__init(enable) {
|
||||
var toggleEventListener = enable ? "addEventListener" : "removeEventListener";
|
||||
|
||||
window[toggleEventListener]("SSWindowRestored", this, false);
|
||||
|
||||
var tabContainer = gBrowser.tabContainer;
|
||||
tabContainer[toggleEventListener]("TabOpen", this, false);
|
||||
tabContainer[toggleEventListener]("TabAttrModified", this, false);
|
||||
tabContainer[toggleEventListener]("TabSelect", this, false);
|
||||
tabContainer[toggleEventListener]("TabClose", this, false);
|
||||
|
||||
document[toggleEventListener]("keypress", this, false);
|
||||
gBrowser.mTabBox.handleCtrlTab = !enable;
|
||||
|
||||
if (enable)
|
||||
PageThumbs.addExpirationFilter(this);
|
||||
else
|
||||
PageThumbs.removeExpirationFilter(this);
|
||||
|
||||
// If we're not running, hide the "Show All Tabs" menu item,
|
||||
// as Shift+Ctrl+Tab will be handled by the tab bar.
|
||||
document.getElementById("menu_showAllTabs").hidden = !enable;
|
||||
document.getElementById("menu_viewPopup")[toggleEventListener]("popupshowing", this);
|
||||
|
||||
// Also disable the <key> to ensure Shift+Ctrl+Tab never triggers
|
||||
// Show All Tabs.
|
||||
var key_showAllTabs = document.getElementById("key_showAllTabs");
|
||||
if (enable)
|
||||
key_showAllTabs.removeAttribute("disabled");
|
||||
else
|
||||
key_showAllTabs.setAttribute("disabled", "true");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* All Tabs menu
|
||||
*/
|
||||
var allTabs = {
|
||||
get toolbarButton() {
|
||||
return document.getElementById("alltabs-button");
|
||||
},
|
||||
|
||||
get canOpen() {
|
||||
return isElementVisible(this.toolbarButton);
|
||||
},
|
||||
|
||||
open: function allTabs_open() {
|
||||
if (this.canOpen) {
|
||||
// Without setTimeout, the menupopup won't stay open when invoking
|
||||
// "View > Show All Tabs" and the menu bar auto-hides.
|
||||
setTimeout(() => {
|
||||
this.toolbarButton.open = true;
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* Customization handler prepares this browser window for entering and exiting
|
||||
* customization mode by handling customizationstarting and customizationending
|
||||
* events.
|
||||
*/
|
||||
var CustomizationHandler = {
|
||||
handleEvent: function(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "customizationstarting":
|
||||
this._customizationStarting();
|
||||
break;
|
||||
case "customizationchange":
|
||||
this._customizationChange();
|
||||
break;
|
||||
case "customizationending":
|
||||
this._customizationEnding(aEvent.detail);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
isCustomizing: function() {
|
||||
return document.documentElement.hasAttribute("customizing");
|
||||
},
|
||||
|
||||
_customizationStarting: function() {
|
||||
// Disable the toolbar context menu items
|
||||
let menubar = document.getElementById("main-menubar");
|
||||
for (let childNode of menubar.childNodes)
|
||||
childNode.setAttribute("disabled", true);
|
||||
|
||||
let cmd = document.getElementById("cmd_CustomizeToolbars");
|
||||
cmd.setAttribute("disabled", "true");
|
||||
|
||||
UpdateUrlbarSearchSplitterState();
|
||||
|
||||
CombinedStopReload.uninit();
|
||||
PlacesToolbarHelper.customizeStart();
|
||||
DownloadsButton.customizeStart();
|
||||
|
||||
// The additional padding on the sides of the browser
|
||||
// can cause the customize tab to get clipped.
|
||||
let tabContainer = gBrowser.tabContainer;
|
||||
if (tabContainer.getAttribute("overflow") == "true") {
|
||||
let tabstrip = tabContainer.mTabstrip;
|
||||
tabstrip.ensureElementIsVisible(gBrowser.selectedTab, true);
|
||||
}
|
||||
},
|
||||
|
||||
_customizationChange: function() {
|
||||
PlacesToolbarHelper.customizeChange();
|
||||
},
|
||||
|
||||
_customizationEnding: function(aDetails) {
|
||||
// Update global UI elements that may have been added or removed
|
||||
if (aDetails.changed) {
|
||||
gURLBar = document.getElementById("urlbar");
|
||||
|
||||
gHomeButton.updateTooltip();
|
||||
XULBrowserWindow.init();
|
||||
|
||||
if (AppConstants.platform != "macosx")
|
||||
updateEditUIVisibility();
|
||||
|
||||
// Hacky: update the PopupNotifications' object's reference to the iconBox,
|
||||
// if it already exists, since it may have changed if the URL bar was
|
||||
// added/removed.
|
||||
if (!window.__lookupGetter__("PopupNotifications")) {
|
||||
PopupNotifications.iconBox =
|
||||
document.getElementById("notification-popup-box");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PlacesToolbarHelper.customizeDone();
|
||||
DownloadsButton.customizeDone();
|
||||
|
||||
// The url bar splitter state is dependent on whether stop/reload
|
||||
// and the location bar are combined, so we need this ordering
|
||||
CombinedStopReload.init();
|
||||
UpdateUrlbarSearchSplitterState();
|
||||
|
||||
// Update the urlbar
|
||||
URLBarSetURI();
|
||||
XULBrowserWindow.asyncUpdateUI();
|
||||
|
||||
// Re-enable parts of the UI we disabled during the dialog
|
||||
let menubar = document.getElementById("main-menubar");
|
||||
for (let childNode of menubar.childNodes)
|
||||
childNode.setAttribute("disabled", false);
|
||||
let cmd = document.getElementById("cmd_CustomizeToolbars");
|
||||
cmd.removeAttribute("disabled");
|
||||
|
||||
gBrowser.selectedBrowser.focus();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,127 +0,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/. */
|
||||
|
||||
const LOGGER_NAME = "Toolkit.Telemetry";
|
||||
const LOGGER_PREFIX = "DataNotificationInfoBar::";
|
||||
|
||||
/**
|
||||
* Represents an info bar that shows a data submission notification.
|
||||
*/
|
||||
var gDataNotificationInfoBar = {
|
||||
_OBSERVERS: [
|
||||
"datareporting:notify-data-policy:request",
|
||||
"datareporting:notify-data-policy:close",
|
||||
],
|
||||
|
||||
_DATA_REPORTING_NOTIFICATION: "data-reporting",
|
||||
|
||||
get _notificationBox() {
|
||||
delete this._notificationBox;
|
||||
return this._notificationBox = document.getElementById("global-notificationbox");
|
||||
},
|
||||
|
||||
get _log() {
|
||||
let Log = Cu.import("resource://gre/modules/Log.jsm", {}).Log;
|
||||
delete this._log;
|
||||
return this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
|
||||
},
|
||||
|
||||
init: function() {
|
||||
window.addEventListener("unload", () => {
|
||||
for (let o of this._OBSERVERS) {
|
||||
Services.obs.removeObserver(this, o);
|
||||
}
|
||||
}, false);
|
||||
|
||||
for (let o of this._OBSERVERS) {
|
||||
Services.obs.addObserver(this, o, true);
|
||||
}
|
||||
},
|
||||
|
||||
_getDataReportingNotification: function (name=this._DATA_REPORTING_NOTIFICATION) {
|
||||
return this._notificationBox.getNotificationWithValue(name);
|
||||
},
|
||||
|
||||
_displayDataPolicyInfoBar: function (request) {
|
||||
if (this._getDataReportingNotification()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let brandBundle = document.getElementById("bundle_brand");
|
||||
let appName = brandBundle.getString("brandShortName");
|
||||
let vendorName = brandBundle.getString("vendorShortName");
|
||||
|
||||
let message = gNavigatorBundle.getFormattedString(
|
||||
"dataReportingNotification.message",
|
||||
[appName, vendorName]);
|
||||
|
||||
this._actionTaken = false;
|
||||
|
||||
let buttons = [{
|
||||
label: gNavigatorBundle.getString("dataReportingNotification.button.label"),
|
||||
accessKey: gNavigatorBundle.getString("dataReportingNotification.button.accessKey"),
|
||||
popup: null,
|
||||
callback: () => {
|
||||
this._actionTaken = true;
|
||||
window.openAdvancedPreferences("dataChoicesTab");
|
||||
},
|
||||
}];
|
||||
|
||||
this._log.info("Creating data reporting policy notification.");
|
||||
this._notificationBox.appendNotification(
|
||||
message,
|
||||
this._DATA_REPORTING_NOTIFICATION,
|
||||
null,
|
||||
this._notificationBox.PRIORITY_INFO_HIGH,
|
||||
buttons,
|
||||
event => {
|
||||
if (event == "removed") {
|
||||
Services.obs.notifyObservers(null, "datareporting:notify-data-policy:close", null);
|
||||
}
|
||||
}
|
||||
);
|
||||
// It is important to defer calling onUserNotifyComplete() until we're
|
||||
// actually sure the notification was displayed. If we ever called
|
||||
// onUserNotifyComplete() without showing anything to the user, that
|
||||
// would be very good for user choice. It may also have legal impact.
|
||||
request.onUserNotifyComplete();
|
||||
},
|
||||
|
||||
_clearPolicyNotification: function () {
|
||||
let notification = this._getDataReportingNotification();
|
||||
if (notification) {
|
||||
this._log.debug("Closing notification.");
|
||||
notification.close();
|
||||
}
|
||||
},
|
||||
|
||||
observe: function(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "datareporting:notify-data-policy:request":
|
||||
let request = subject.wrappedJSObject.object;
|
||||
try {
|
||||
this._displayDataPolicyInfoBar(request);
|
||||
} catch (ex) {
|
||||
request.onUserNotifyFailed(ex);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case "datareporting:notify-data-policy:close":
|
||||
// If this observer fires, it means something else took care of
|
||||
// responding. Therefore, we don't need to do anything. So, we
|
||||
// act like we took action and clear state.
|
||||
this._actionTaken = true;
|
||||
this._clearPolicyNotification();
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference,
|
||||
]),
|
||||
};
|
||||
|
|
@ -1,142 +0,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/. */
|
||||
|
||||
/**
|
||||
* Listeners for the DevEdition theme. This adds an extra stylesheet
|
||||
* to browser.xul if a pref is set and no other themes are applied.
|
||||
*/
|
||||
var DevEdition = {
|
||||
_devtoolsThemePrefName: "devtools.theme",
|
||||
styleSheetLocation: "chrome://browser/skin/devedition.css",
|
||||
styleSheet: null,
|
||||
initialized: false,
|
||||
|
||||
get isStyleSheetEnabled() {
|
||||
return this.styleSheet && !this.styleSheet.sheet.disabled;
|
||||
},
|
||||
|
||||
get isThemeCurrentlyApplied() {
|
||||
let theme = LightweightThemeManager.currentTheme;
|
||||
return theme && theme.id == "firefox-devedition@mozilla.org";
|
||||
},
|
||||
|
||||
init: function () {
|
||||
this.initialized = true;
|
||||
Services.prefs.addObserver(this._devtoolsThemePrefName, this, false);
|
||||
Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
|
||||
Services.obs.addObserver(this, "lightweight-theme-window-updated", false);
|
||||
this._updateDevtoolsThemeAttribute();
|
||||
|
||||
if (this.isThemeCurrentlyApplied) {
|
||||
this._toggleStyleSheet(true);
|
||||
}
|
||||
},
|
||||
|
||||
createStyleSheet: function() {
|
||||
let styleSheetAttr = `href="${this.styleSheetLocation}" type="text/css"`;
|
||||
this.styleSheet = document.createProcessingInstruction(
|
||||
"xml-stylesheet", styleSheetAttr);
|
||||
this.styleSheet.addEventListener("load", this);
|
||||
document.insertBefore(this.styleSheet, document.documentElement);
|
||||
this.styleSheet.sheet.disabled = true;
|
||||
},
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
if (topic == "lightweight-theme-styling-update") {
|
||||
let newTheme = JSON.parse(data);
|
||||
if (newTheme && newTheme.id == "firefox-devedition@mozilla.org") {
|
||||
this._toggleStyleSheet(true);
|
||||
} else {
|
||||
this._toggleStyleSheet(false);
|
||||
}
|
||||
} else if (topic == "lightweight-theme-window-updated" && subject == window) {
|
||||
this._updateLWTBrightness();
|
||||
}
|
||||
|
||||
if (topic == "nsPref:changed" && data == this._devtoolsThemePrefName) {
|
||||
this._updateDevtoolsThemeAttribute();
|
||||
}
|
||||
},
|
||||
|
||||
_inferBrightness: function() {
|
||||
ToolbarIconColor.inferFromText();
|
||||
// Get an inverted full screen button if the dark theme is applied.
|
||||
if (this.isStyleSheetEnabled &&
|
||||
document.documentElement.getAttribute("devtoolstheme") == "dark") {
|
||||
document.documentElement.setAttribute("brighttitlebarforeground", "true");
|
||||
} else {
|
||||
document.documentElement.removeAttribute("brighttitlebarforeground");
|
||||
}
|
||||
},
|
||||
|
||||
_updateLWTBrightness() {
|
||||
if (this.isThemeCurrentlyApplied) {
|
||||
let devtoolsTheme = Services.prefs.getCharPref(this._devtoolsThemePrefName);
|
||||
let textColor = devtoolsTheme == "dark" ? "bright" : "dark";
|
||||
document.documentElement.setAttribute("lwthemetextcolor", textColor);
|
||||
}
|
||||
},
|
||||
|
||||
_updateDevtoolsThemeAttribute: function() {
|
||||
// Set an attribute on root element to make it possible
|
||||
// to change colors based on the selected devtools theme.
|
||||
let devtoolsTheme = Services.prefs.getCharPref(this._devtoolsThemePrefName);
|
||||
if (devtoolsTheme != "dark") {
|
||||
devtoolsTheme = "light";
|
||||
}
|
||||
document.documentElement.setAttribute("devtoolstheme", devtoolsTheme);
|
||||
this._updateLWTBrightness();
|
||||
this._inferBrightness();
|
||||
},
|
||||
|
||||
handleEvent: function(e) {
|
||||
if (e.type === "load") {
|
||||
this.styleSheet.removeEventListener("load", this);
|
||||
this.refreshBrowserDisplay();
|
||||
}
|
||||
},
|
||||
|
||||
refreshBrowserDisplay: function() {
|
||||
// Don't touch things on the browser if gBrowserInit.onLoad hasn't
|
||||
// yet fired.
|
||||
if (this.initialized) {
|
||||
gBrowser.tabContainer._positionPinnedTabs();
|
||||
this._inferBrightness();
|
||||
}
|
||||
},
|
||||
|
||||
_toggleStyleSheet: function(deveditionThemeEnabled) {
|
||||
let wasEnabled = this.isStyleSheetEnabled;
|
||||
if (deveditionThemeEnabled && !wasEnabled) {
|
||||
// The stylesheet may not have been created yet if it wasn't
|
||||
// needed on initial load. Make it now.
|
||||
if (!this.styleSheet) {
|
||||
this.createStyleSheet();
|
||||
}
|
||||
this.styleSheet.sheet.disabled = false;
|
||||
this.refreshBrowserDisplay();
|
||||
} else if (!deveditionThemeEnabled && wasEnabled) {
|
||||
this.styleSheet.sheet.disabled = true;
|
||||
this.refreshBrowserDisplay();
|
||||
}
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
Services.prefs.removeObserver(this._devtoolsThemePrefName, this);
|
||||
Services.obs.removeObserver(this, "lightweight-theme-styling-update", false);
|
||||
Services.obs.removeObserver(this, "lightweight-theme-window-updated", false);
|
||||
if (this.styleSheet) {
|
||||
this.styleSheet.removeEventListener("load", this);
|
||||
}
|
||||
this.styleSheet = null;
|
||||
}
|
||||
};
|
||||
|
||||
// If the DevEdition theme is going to be applied in gBrowserInit.onLoad,
|
||||
// then preload it now. This prevents a flash of unstyled content where the
|
||||
// normal theme is applied while the DevEdition stylesheet is loading.
|
||||
if (!AppConstants.RELEASE_OR_BETA &&
|
||||
this != Services.appShell.hiddenDOMWindow && DevEdition.isThemeCurrentlyApplied) {
|
||||
DevEdition.createStyleSheet();
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
<!DOCTYPE window [
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
|
||||
%browserDTD;
|
||||
<!ENTITY % baseMenuDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd" >
|
||||
%baseMenuDTD;
|
||||
<!ENTITY % charsetDTD SYSTEM "chrome://global/locale/charsetMenu.dtd" >
|
||||
%charsetDTD;
|
||||
<!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd" >
|
||||
%textcontextDTD;
|
||||
<!ENTITY % customizeToolbarDTD SYSTEM "chrome://global/locale/customizeToolbar.dtd">
|
||||
%customizeToolbarDTD;
|
||||
<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
|
||||
%placesDTD;
|
||||
#ifdef MOZ_SAFE_BROWSING
|
||||
<!ENTITY % safebrowsingDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
|
||||
%safebrowsingDTD;
|
||||
#endif
|
||||
<!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
|
||||
%aboutHomeDTD;
|
||||
<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
|
||||
%syncBrandDTD;
|
||||
]>
|
||||
|
||||
|
|
@ -1,646 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
|
||||
"resource://gre/modules/DeferredTask.jsm");
|
||||
|
||||
const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
|
||||
const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
|
||||
const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
|
||||
|
||||
const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI";
|
||||
|
||||
const PREF_SELECTED_APP = "browser.feeds.handlers.application";
|
||||
const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
|
||||
const PREF_SELECTED_ACTION = "browser.feeds.handler";
|
||||
const PREF_SELECTED_READER = "browser.feeds.handler.default";
|
||||
|
||||
const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
|
||||
const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
|
||||
const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
|
||||
const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
|
||||
|
||||
const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
|
||||
const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
|
||||
const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
|
||||
const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
|
||||
|
||||
const PREF_UPDATE_DELAY = 2000;
|
||||
|
||||
const SETTABLE_PREFS = new Set([
|
||||
PREF_VIDEO_SELECTED_ACTION,
|
||||
PREF_AUDIO_SELECTED_ACTION,
|
||||
PREF_SELECTED_ACTION,
|
||||
PREF_VIDEO_SELECTED_READER,
|
||||
PREF_AUDIO_SELECTED_READER,
|
||||
PREF_SELECTED_READER,
|
||||
PREF_VIDEO_SELECTED_WEB,
|
||||
PREF_AUDIO_SELECTED_WEB,
|
||||
PREF_SELECTED_WEB
|
||||
]);
|
||||
|
||||
const EXECUTABLE_PREFS = new Set([
|
||||
PREF_SELECTED_APP,
|
||||
PREF_VIDEO_SELECTED_APP,
|
||||
PREF_AUDIO_SELECTED_APP
|
||||
]);
|
||||
|
||||
const VALID_ACTIONS = new Set(["ask", "reader", "bookmarks"]);
|
||||
const VALID_READERS = new Set(["web", "client", "default", "bookmarks"]);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(this, "SHOULD_LOG",
|
||||
"feeds.log", false);
|
||||
|
||||
function LOG(str) {
|
||||
if (SHOULD_LOG)
|
||||
dump("*** Feeds: " + str + "\n");
|
||||
}
|
||||
|
||||
function getPrefActionForType(t) {
|
||||
switch (t) {
|
||||
case Ci.nsIFeed.TYPE_VIDEO:
|
||||
return PREF_VIDEO_SELECTED_ACTION;
|
||||
|
||||
case Ci.nsIFeed.TYPE_AUDIO:
|
||||
return PREF_AUDIO_SELECTED_ACTION;
|
||||
|
||||
default:
|
||||
return PREF_SELECTED_ACTION;
|
||||
}
|
||||
}
|
||||
|
||||
function getPrefReaderForType(t) {
|
||||
switch (t) {
|
||||
case Ci.nsIFeed.TYPE_VIDEO:
|
||||
return PREF_VIDEO_SELECTED_READER;
|
||||
|
||||
case Ci.nsIFeed.TYPE_AUDIO:
|
||||
return PREF_AUDIO_SELECTED_READER;
|
||||
|
||||
default:
|
||||
return PREF_SELECTED_READER;
|
||||
}
|
||||
}
|
||||
|
||||
function getPrefWebForType(t) {
|
||||
switch (t) {
|
||||
case Ci.nsIFeed.TYPE_VIDEO:
|
||||
return PREF_VIDEO_SELECTED_WEB;
|
||||
|
||||
case Ci.nsIFeed.TYPE_AUDIO:
|
||||
return PREF_AUDIO_SELECTED_WEB;
|
||||
|
||||
default:
|
||||
return PREF_SELECTED_WEB;
|
||||
}
|
||||
}
|
||||
|
||||
function getPrefAppForType(t) {
|
||||
switch (t) {
|
||||
case Ci.nsIFeed.TYPE_VIDEO:
|
||||
return PREF_VIDEO_SELECTED_APP;
|
||||
|
||||
case Ci.nsIFeed.TYPE_AUDIO:
|
||||
return PREF_AUDIO_SELECTED_APP;
|
||||
|
||||
default:
|
||||
return PREF_SELECTED_APP;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a feed type to a maybe-feed mimetype.
|
||||
*/
|
||||
function getMimeTypeForFeedType(aFeedType) {
|
||||
switch (aFeedType) {
|
||||
case Ci.nsIFeed.TYPE_VIDEO:
|
||||
return TYPE_MAYBE_VIDEO_FEED;
|
||||
|
||||
case Ci.nsIFeed.TYPE_AUDIO:
|
||||
return TYPE_MAYBE_AUDIO_FEED;
|
||||
|
||||
default:
|
||||
return TYPE_MAYBE_FEED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Feed Handler object manages discovery of RSS/ATOM feeds in web pages
|
||||
* and shows UI when they are discovered.
|
||||
*/
|
||||
var FeedHandler = {
|
||||
_prefChangeCallback: null,
|
||||
|
||||
/** Called when the user clicks on the Subscribe to This Page... menu item,
|
||||
* or when the user clicks the feed button when the page contains multiple
|
||||
* feeds.
|
||||
* Builds a menu of unique feeds associated with the page, and if there
|
||||
* is only one, shows the feed inline in the browser window.
|
||||
* @param container
|
||||
* The feed list container (menupopup or subview) to be populated.
|
||||
* @param isSubview
|
||||
* Whether we're creating a subview (true) or menu (false/undefined)
|
||||
* @return true if the menu/subview should be shown, false if there was only
|
||||
* one feed and the feed should be shown inline in the browser
|
||||
* window (do not show the menupopup/subview).
|
||||
*/
|
||||
buildFeedList(container, isSubview) {
|
||||
let feeds = gBrowser.selectedBrowser.feeds;
|
||||
if (!isSubview && feeds == null) {
|
||||
// XXX hack -- menu opening depends on setting of an "open"
|
||||
// attribute, and the menu refuses to open if that attribute is
|
||||
// set (because it thinks it's already open). onpopupshowing gets
|
||||
// called after the attribute is unset, and it doesn't get unset
|
||||
// if we return false. so we unset it here; otherwise, the menu
|
||||
// refuses to work past this point.
|
||||
container.parentNode.removeAttribute("open");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = container.childNodes.length - 1; i >= 0; --i) {
|
||||
let node = container.childNodes[i];
|
||||
if (isSubview && node.localName == "label")
|
||||
continue;
|
||||
container.removeChild(node);
|
||||
}
|
||||
|
||||
if (!feeds || feeds.length <= 1)
|
||||
return false;
|
||||
|
||||
// Build the menu showing the available feed choices for viewing.
|
||||
let itemNodeType = isSubview ? "toolbarbutton" : "menuitem";
|
||||
for (let feedInfo of feeds) {
|
||||
let item = document.createElement(itemNodeType);
|
||||
let baseTitle = feedInfo.title || feedInfo.href;
|
||||
item.setAttribute("label", baseTitle);
|
||||
item.setAttribute("feed", feedInfo.href);
|
||||
item.setAttribute("tooltiptext", feedInfo.href);
|
||||
item.setAttribute("crop", "center");
|
||||
let className = "feed-" + itemNodeType;
|
||||
if (isSubview) {
|
||||
className += " subviewbutton";
|
||||
}
|
||||
item.setAttribute("class", className);
|
||||
container.appendChild(item);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Subscribe to a given feed. Called when
|
||||
* 1. Page has a single feed and user clicks feed icon in location bar
|
||||
* 2. Page has a single feed and user selects Subscribe menu item
|
||||
* 3. Page has multiple feeds and user selects from feed icon popup (or subview)
|
||||
* 4. Page has multiple feeds and user selects from Subscribe submenu
|
||||
* @param href
|
||||
* The feed to subscribe to. May be null, in which case the
|
||||
* event target's feed attribute is examined.
|
||||
* @param event
|
||||
* The event this method is handling. Used to decide where
|
||||
* to open the preview UI. (Optional, unless href is null)
|
||||
*/
|
||||
subscribeToFeed(href, event) {
|
||||
// Just load the feed in the content area to either subscribe or show the
|
||||
// preview UI
|
||||
if (!href)
|
||||
href = event.target.getAttribute("feed");
|
||||
urlSecurityCheck(href, gBrowser.contentPrincipal,
|
||||
Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
|
||||
let feedURI = makeURI(href, document.characterSet);
|
||||
// Use the feed scheme so X-Moz-Is-Feed will be set
|
||||
// The value doesn't matter
|
||||
if (/^https?$/.test(feedURI.scheme))
|
||||
href = "feed:" + href;
|
||||
this.loadFeed(href, event);
|
||||
},
|
||||
|
||||
loadFeed(href, event) {
|
||||
let feeds = gBrowser.selectedBrowser.feeds;
|
||||
try {
|
||||
openUILink(href, event, { ignoreAlt: true });
|
||||
}
|
||||
finally {
|
||||
// We might default to a livebookmarks modal dialog,
|
||||
// so reset that if the user happens to click it again
|
||||
gBrowser.selectedBrowser.feeds = feeds;
|
||||
}
|
||||
},
|
||||
|
||||
get _feedMenuitem() {
|
||||
delete this._feedMenuitem;
|
||||
return this._feedMenuitem = document.getElementById("singleFeedMenuitemState");
|
||||
},
|
||||
|
||||
get _feedMenupopup() {
|
||||
delete this._feedMenupopup;
|
||||
return this._feedMenupopup = document.getElementById("multipleFeedsMenuState");
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the browser UI to show whether or not feeds are available when
|
||||
* a page is loaded or the user switches tabs to a page that has feeds.
|
||||
*/
|
||||
updateFeeds() {
|
||||
if (this._updateFeedTimeout)
|
||||
clearTimeout(this._updateFeedTimeout);
|
||||
|
||||
let feeds = gBrowser.selectedBrowser.feeds;
|
||||
let haveFeeds = feeds && feeds.length > 0;
|
||||
|
||||
let feedButton = document.getElementById("feed-button");
|
||||
if (feedButton) {
|
||||
if (haveFeeds) {
|
||||
feedButton.removeAttribute("disabled");
|
||||
} else {
|
||||
feedButton.setAttribute("disabled", "true");
|
||||
}
|
||||
}
|
||||
|
||||
if (!haveFeeds) {
|
||||
this._feedMenuitem.setAttribute("disabled", "true");
|
||||
this._feedMenuitem.removeAttribute("hidden");
|
||||
this._feedMenupopup.setAttribute("hidden", "true");
|
||||
return;
|
||||
}
|
||||
|
||||
if (feeds.length > 1) {
|
||||
this._feedMenuitem.setAttribute("hidden", "true");
|
||||
this._feedMenupopup.removeAttribute("hidden");
|
||||
} else {
|
||||
this._feedMenuitem.setAttribute("feed", feeds[0].href);
|
||||
this._feedMenuitem.removeAttribute("disabled");
|
||||
this._feedMenuitem.removeAttribute("hidden");
|
||||
this._feedMenupopup.setAttribute("hidden", "true");
|
||||
}
|
||||
},
|
||||
|
||||
addFeed(link, browserForLink) {
|
||||
if (!browserForLink.feeds)
|
||||
browserForLink.feeds = [];
|
||||
|
||||
browserForLink.feeds.push({ href: link.href, title: link.title });
|
||||
|
||||
// If this addition was for the current browser, update the UI. For
|
||||
// background browsers, we'll update on tab switch.
|
||||
if (browserForLink == gBrowser.selectedBrowser) {
|
||||
// Batch updates to avoid updating the UI for multiple onLinkAdded events
|
||||
// fired within 100ms of each other.
|
||||
if (this._updateFeedTimeout)
|
||||
clearTimeout(this._updateFeedTimeout);
|
||||
this._updateFeedTimeout = setTimeout(this.updateFeeds.bind(this), 100);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the human-readable display name of a file. This could be the
|
||||
* application name.
|
||||
* @param file
|
||||
* A nsIFile to look up the name of
|
||||
* @return The display name of the application represented by the file.
|
||||
*/
|
||||
_getFileDisplayName(file) {
|
||||
switch (AppConstants.platform) {
|
||||
case "win":
|
||||
if (file instanceof Ci.nsILocalFileWin) {
|
||||
try {
|
||||
return file.getVersionInfoField("FileDescription");
|
||||
} catch (e) {}
|
||||
}
|
||||
break;
|
||||
case "macosx":
|
||||
if (file instanceof Ci.nsILocalFileMac) {
|
||||
try {
|
||||
return file.bundleDisplayName;
|
||||
} catch (e) {}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return file.leafName;
|
||||
},
|
||||
|
||||
_chooseClientApp(aTitle, aTypeName, aBrowser) {
|
||||
const prefName = getPrefAppForType(aTypeName);
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
|
||||
fp.init(window, aTitle, Ci.nsIFilePicker.modeOpen);
|
||||
fp.appendFilters(Ci.nsIFilePicker.filterApps);
|
||||
|
||||
fp.open((aResult) => {
|
||||
if (aResult == Ci.nsIFilePicker.returnOK) {
|
||||
let selectedApp = fp.file;
|
||||
if (selectedApp) {
|
||||
// XXXben - we need to compare this with the running instance
|
||||
// executable just don't know how to do that via script
|
||||
// XXXmano TBD: can probably add this to nsIShellService
|
||||
let appName = "";
|
||||
switch (AppConstants.platform) {
|
||||
case "win":
|
||||
appName = AppConstants.MOZ_APP_NAME + ".exe";
|
||||
break;
|
||||
case "macosx":
|
||||
appName = AppConstants.MOZ_MACBUNDLE_NAME;
|
||||
break;
|
||||
default:
|
||||
appName = AppConstants.MOZ_APP_NAME + "-bin";
|
||||
break;
|
||||
}
|
||||
|
||||
if (fp.file.leafName != appName) {
|
||||
Services.prefs.setComplexValue(prefName, Ci.nsILocalFile, selectedApp);
|
||||
aBrowser.messageManager.sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem",
|
||||
{ name: this._getFileDisplayName(selectedApp),
|
||||
type: "SelectedAppMenuItem" });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
executeClientApp(aSpec, aTitle, aSubtitle, aFeedHandler) {
|
||||
// aFeedHandler is either "default", indicating the system default reader, or a pref-name containing
|
||||
// an nsILocalFile pointing to the feed handler's executable.
|
||||
|
||||
let clientApp = null;
|
||||
if (aFeedHandler == "default") {
|
||||
clientApp = Cc["@mozilla.org/browser/shell-service;1"]
|
||||
.getService(Ci.nsIShellService)
|
||||
.defaultFeedReader;
|
||||
} else {
|
||||
clientApp = Services.prefs.getComplexValue(aFeedHandler, Ci.nsILocalFile);
|
||||
}
|
||||
|
||||
// For the benefit of applications that might know how to deal with more
|
||||
// URLs than just feeds, send feed: URLs in the following format:
|
||||
//
|
||||
// http urls: replace scheme with feed, e.g.
|
||||
// http://foo.com/index.rdf -> feed://foo.com/index.rdf
|
||||
// other urls: prepend feed: scheme, e.g.
|
||||
// https://foo.com/index.rdf -> feed:https://foo.com/index.rdf
|
||||
let feedURI = NetUtil.newURI(aSpec);
|
||||
if (feedURI.schemeIs("http")) {
|
||||
feedURI.scheme = "feed";
|
||||
aSpec = feedURI.spec;
|
||||
} else {
|
||||
aSpec = "feed:" + aSpec;
|
||||
}
|
||||
|
||||
// Retrieving the shell service might fail on some systems, most
|
||||
// notably systems where GNOME is not installed.
|
||||
try {
|
||||
let ss = Cc["@mozilla.org/browser/shell-service;1"]
|
||||
.getService(Ci.nsIShellService);
|
||||
ss.openApplicationWithURI(clientApp, aSpec);
|
||||
} catch (e) {
|
||||
// If we couldn't use the shell service, fallback to using a
|
||||
// nsIProcess instance
|
||||
let p = Cc["@mozilla.org/process/util;1"]
|
||||
.createInstance(Ci.nsIProcess);
|
||||
p.init(clientApp);
|
||||
p.run(false, [aSpec], 1);
|
||||
}
|
||||
},
|
||||
|
||||
// nsISupports
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
init() {
|
||||
window.messageManager.addMessageListener("FeedWriter:ChooseClientApp", this);
|
||||
window.messageManager.addMessageListener("FeedWriter:GetSubscriptionUI", this);
|
||||
window.messageManager.addMessageListener("FeedWriter:SetFeedPrefsAndSubscribe", this);
|
||||
window.messageManager.addMessageListener("FeedWriter:ShownFirstRun", this);
|
||||
|
||||
Services.ppmm.addMessageListener("FeedConverter:ExecuteClientApp", this);
|
||||
|
||||
const prefs = Services.prefs;
|
||||
prefs.addObserver(PREF_SELECTED_ACTION, this, true);
|
||||
prefs.addObserver(PREF_SELECTED_READER, this, true);
|
||||
prefs.addObserver(PREF_SELECTED_WEB, this, true);
|
||||
prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, true);
|
||||
prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, true);
|
||||
prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, true);
|
||||
prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, true);
|
||||
prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, true);
|
||||
prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, true);
|
||||
},
|
||||
|
||||
uninit() {
|
||||
Services.ppmm.removeMessageListener("FeedConverter:ExecuteClientApp", this);
|
||||
|
||||
this._prefChangeCallback = null;
|
||||
},
|
||||
|
||||
// nsIObserver
|
||||
observe(subject, topic, data) {
|
||||
if (topic == "nsPref:changed") {
|
||||
LOG(`Pref changed ${data}`)
|
||||
if (this._prefChangeCallback) {
|
||||
this._prefChangeCallback.disarm();
|
||||
}
|
||||
// Multiple prefs are set at the same time, debounce to reduce noise
|
||||
// This can happen in one feed and we want to message all feed pages
|
||||
this._prefChangeCallback = new DeferredTask(() => {
|
||||
this._prefChanged(data);
|
||||
}, PREF_UPDATE_DELAY);
|
||||
this._prefChangeCallback.arm();
|
||||
}
|
||||
},
|
||||
|
||||
_prefChanged(prefName) {
|
||||
// Don't observe for PREF_*SELECTED_APP as user likely just picked one
|
||||
// That is also handled by SetApplicationLauncherMenuItem call
|
||||
// Rather than the others which happen on subscription
|
||||
switch (prefName) {
|
||||
case PREF_SELECTED_READER:
|
||||
case PREF_SELECTED_WEB:
|
||||
case PREF_VIDEO_SELECTED_READER:
|
||||
case PREF_VIDEO_SELECTED_WEB:
|
||||
case PREF_AUDIO_SELECTED_READER:
|
||||
case PREF_AUDIO_SELECTED_WEB:
|
||||
case PREF_SELECTED_ACTION:
|
||||
case PREF_VIDEO_SELECTED_ACTION:
|
||||
case PREF_AUDIO_SELECTED_ACTION:
|
||||
const response = {
|
||||
default: this._getReaderForType(Ci.nsIFeed.TYPE_FEED),
|
||||
[Ci.nsIFeed.TYPE_AUDIO]: this._getReaderForType(Ci.nsIFeed.TYPE_AUDIO),
|
||||
[Ci.nsIFeed.TYPE_VIDEO]: this._getReaderForType(Ci.nsIFeed.TYPE_VIDEO)
|
||||
};
|
||||
Services.mm.broadcastAsyncMessage("FeedWriter:PreferenceUpdated",
|
||||
response);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_initSubscriptionUIResponse(feedType) {
|
||||
const wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
|
||||
getService(Ci.nsIWebContentConverterService);
|
||||
const handlersRaw = wccr.getContentHandlers(getMimeTypeForFeedType(feedType));
|
||||
const handlers = [];
|
||||
for (let handler of handlersRaw) {
|
||||
LOG(`Handler found: ${handler}`);
|
||||
handlers.push({
|
||||
name: handler.name,
|
||||
uri: handler.uri
|
||||
});
|
||||
}
|
||||
let showFirstRunUI = true;
|
||||
// eslint-disable-next-line mozilla/use-default-preference-values
|
||||
try {
|
||||
showFirstRunUI = Services.prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI);
|
||||
} catch (ex) { }
|
||||
const response = { handlers, showFirstRunUI };
|
||||
let selectedClientApp;
|
||||
const feedTypePref = getPrefAppForType(feedType);
|
||||
try {
|
||||
selectedClientApp = Services.prefs.getComplexValue(feedTypePref, Ci.nsILocalFile);
|
||||
} catch (ex) {
|
||||
// Just do nothing, then we won't bother populating
|
||||
}
|
||||
|
||||
let defaultClientApp = null;
|
||||
try {
|
||||
// This can sometimes not exist
|
||||
defaultClientApp = Cc["@mozilla.org/browser/shell-service;1"]
|
||||
.getService(Ci.nsIShellService)
|
||||
.defaultFeedReader;
|
||||
} catch (ex) {
|
||||
// Just do nothing, then we don't bother populating
|
||||
}
|
||||
|
||||
if (selectedClientApp && selectedClientApp.exists()) {
|
||||
if (defaultClientApp && selectedClientApp.path != defaultClientApp.path) {
|
||||
// Only set the default menu item if it differs from the selected one
|
||||
response.defaultMenuItem = this._getFileDisplayName(defaultClientApp);
|
||||
}
|
||||
response.selectedMenuItem = this._getFileDisplayName(selectedClientApp);
|
||||
}
|
||||
response.reader = this._getReaderForType(feedType);
|
||||
return response;
|
||||
},
|
||||
|
||||
_setPref(aPrefName, aPrefValue, aIsComplex = false) {
|
||||
LOG(`FeedWriter._setPref ${aPrefName}`);
|
||||
// Ensure we have a pref that is settable
|
||||
if (aPrefName && SETTABLE_PREFS.has(aPrefName)) {
|
||||
if (aIsComplex) {
|
||||
const supportsString = Cc["@mozilla.org/supports-string;1"].
|
||||
createInstance(Ci.nsISupportsString);
|
||||
supportsString.data = aPrefValue;
|
||||
Services.prefs.setComplexValue(aPrefName, Ci.nsISupportsString, supportsString);
|
||||
} else {
|
||||
Services.prefs.setCharPref(aPrefName, aPrefValue);
|
||||
}
|
||||
} else {
|
||||
LOG(`FeedWriter._setPref ${aPrefName} not allowed`);
|
||||
}
|
||||
},
|
||||
|
||||
_getReaderForType(feedType) {
|
||||
let prefs = Services.prefs;
|
||||
let handler = "bookmarks";
|
||||
let url;
|
||||
// eslint-disable-next-line mozilla/use-default-preference-values
|
||||
try {
|
||||
handler = prefs.getCharPref(getPrefReaderForType(feedType));
|
||||
} catch (ex) { }
|
||||
|
||||
if (handler === "web") {
|
||||
try {
|
||||
url = prefs.getComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString).data;
|
||||
} catch (ex) {
|
||||
LOG("FeedWriter._setSelectedHandler: invalid or no handler in prefs");
|
||||
url = null;
|
||||
}
|
||||
}
|
||||
const alwaysUse = this._getAlwaysUseState(feedType);
|
||||
const action = prefs.getCharPref(getPrefActionForType(feedType));
|
||||
return { handler, url, alwaysUse, action };
|
||||
},
|
||||
|
||||
_getAlwaysUseState(feedType) {
|
||||
try {
|
||||
return Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask";
|
||||
} catch (ex) { }
|
||||
return false;
|
||||
},
|
||||
|
||||
receiveMessage(msg) {
|
||||
let handler;
|
||||
switch (msg.name) {
|
||||
case "FeedWriter:GetSubscriptionUI":
|
||||
const response = this._initSubscriptionUIResponse(msg.data.feedType);
|
||||
msg.target.messageManager
|
||||
.sendAsyncMessage("FeedWriter:GetSubscriptionUIResponse",
|
||||
response);
|
||||
break;
|
||||
case "FeedWriter:ChooseClientApp":
|
||||
this._chooseClientApp(msg.data.title, msg.data.feedType, msg.target);
|
||||
break;
|
||||
case "FeedWriter:ShownFirstRun":
|
||||
Services.prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false);
|
||||
break;
|
||||
case "FeedWriter:SetFeedPrefsAndSubscribe":
|
||||
const settings = msg.data;
|
||||
if (!settings.action || !VALID_ACTIONS.has(settings.action)) {
|
||||
LOG(`Invalid action ${settings.action}`);
|
||||
return;
|
||||
}
|
||||
if (!settings.reader || !VALID_READERS.has(settings.reader)) {
|
||||
LOG(`Invalid reader ${settings.reader}`);
|
||||
return;
|
||||
}
|
||||
const actionPref = getPrefActionForType(settings.feedType);
|
||||
this._setPref(actionPref, settings.action);
|
||||
const readerPref = getPrefReaderForType(settings.feedType);
|
||||
this._setPref(readerPref, settings.reader);
|
||||
handler = null;
|
||||
|
||||
switch (settings.reader) {
|
||||
case "web":
|
||||
// This is a web set URI by content using window.registerContentHandler()
|
||||
// Lets make sure we know about it before setting it
|
||||
const webPref = getPrefWebForType(settings.feedType);
|
||||
let wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
|
||||
getService(Ci.nsIWebContentConverterService);
|
||||
// If the user provided an invalid web URL this function won't give us a reference
|
||||
handler = wccr.getWebContentHandlerByURI(getMimeTypeForFeedType(settings.feedType), settings.uri);
|
||||
if (handler) {
|
||||
this._setPref(webPref, settings.uri, true);
|
||||
if (settings.useAsDefault) {
|
||||
wccr.setAutoHandler(getMimeTypeForFeedType(settings.feedType), handler);
|
||||
}
|
||||
msg.target.messageManager
|
||||
.sendAsyncMessage("FeedWriter:SetFeedPrefsAndSubscribeResponse",
|
||||
{ redirect: handler.getHandlerURI(settings.feedLocation) });
|
||||
} else {
|
||||
LOG(`No handler found for web ${settings.feedType} ${settings.uri}`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
const feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
|
||||
getService(Ci.nsIFeedResultService);
|
||||
|
||||
feedService.addToClientReader(settings.feedLocation,
|
||||
settings.feedTitle,
|
||||
settings.feedSubtitle,
|
||||
settings.feedType,
|
||||
settings.reader);
|
||||
}
|
||||
break;
|
||||
case "FeedConverter:ExecuteClientApp":
|
||||
// Always check feedHandler is from a set array of executable prefs
|
||||
if (EXECUTABLE_PREFS.has(msg.data.feedHandler)) {
|
||||
this.executeClientApp(msg.data.spec, msg.data.title,
|
||||
msg.data.subtitle, msg.data.feedHandler);
|
||||
} else {
|
||||
LOG(`FeedConverter:ExecuteClientApp - Will not exec ${msg.data.feedHandler}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -1,673 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
var PointerlockFsWarning = {
|
||||
|
||||
_element: null,
|
||||
_origin: null,
|
||||
|
||||
init: function() {
|
||||
this.Timeout.prototype = {
|
||||
start: function() {
|
||||
this.cancel();
|
||||
this._id = setTimeout(() => this._handle(), this._delay);
|
||||
},
|
||||
cancel: function() {
|
||||
if (this._id) {
|
||||
clearTimeout(this._id);
|
||||
this._id = 0;
|
||||
}
|
||||
},
|
||||
_handle: function() {
|
||||
this._id = 0;
|
||||
this._func();
|
||||
},
|
||||
get delay() {
|
||||
return this._delay;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Timeout object for managing timeout request. If it is started when
|
||||
* the previous call hasn't finished, it would automatically cancelled
|
||||
* the previous one.
|
||||
*/
|
||||
Timeout: function(func, delay) {
|
||||
this._id = 0;
|
||||
this._func = func;
|
||||
this._delay = delay;
|
||||
},
|
||||
|
||||
showPointerLock: function(aOrigin) {
|
||||
if (!document.fullscreen) {
|
||||
let timeout = gPrefService.getIntPref("pointer-lock-api.warning.timeout");
|
||||
this.show(aOrigin, "pointerlock-warning", timeout, 0);
|
||||
}
|
||||
},
|
||||
|
||||
showFullScreen: function(aOrigin) {
|
||||
let timeout = gPrefService.getIntPref("full-screen-api.warning.timeout");
|
||||
let delay = gPrefService.getIntPref("full-screen-api.warning.delay");
|
||||
this.show(aOrigin, "fullscreen-warning", timeout, delay);
|
||||
},
|
||||
|
||||
// Shows a warning that the site has entered fullscreen or
|
||||
// pointer lock for a short duration.
|
||||
show: function(aOrigin, elementId, timeout, delay) {
|
||||
|
||||
if (!this._element) {
|
||||
this._element = document.getElementById(elementId);
|
||||
// Setup event listeners
|
||||
this._element.addEventListener("transitionend", this);
|
||||
window.addEventListener("mousemove", this, true);
|
||||
// The timeout to hide the warning box after a while.
|
||||
this._timeoutHide = new this.Timeout(() => {
|
||||
this._state = "hidden";
|
||||
}, timeout);
|
||||
// The timeout to show the warning box when the pointer is at the top
|
||||
this._timeoutShow = new this.Timeout(() => {
|
||||
this._state = "ontop";
|
||||
this._timeoutHide.start();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
// Set the strings on the warning UI.
|
||||
if (aOrigin) {
|
||||
this._origin = aOrigin;
|
||||
}
|
||||
let uri = BrowserUtils.makeURI(this._origin);
|
||||
let host = null;
|
||||
try {
|
||||
host = uri.host;
|
||||
} catch (e) { }
|
||||
let textElem = this._element.querySelector(".pointerlockfswarning-domain-text");
|
||||
if (!host) {
|
||||
textElem.setAttribute("hidden", true);
|
||||
} else {
|
||||
textElem.removeAttribute("hidden");
|
||||
let hostElem = this._element.querySelector(".pointerlockfswarning-domain");
|
||||
// Document's principal's URI has a host. Display a warning including it.
|
||||
let utils = {};
|
||||
Cu.import("resource://gre/modules/DownloadUtils.jsm", utils);
|
||||
hostElem.textContent = utils.DownloadUtils.getURIHost(uri.spec)[0];
|
||||
}
|
||||
|
||||
this._element.dataset.identity =
|
||||
gIdentityHandler.pointerlockFsWarningClassName;
|
||||
|
||||
// User should be allowed to explicitly disable
|
||||
// the prompt if they really want.
|
||||
if (this._timeoutHide.delay <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Explicitly set the last state to hidden to avoid the warning
|
||||
// box being hidden immediately because of mousemove.
|
||||
this._state = "onscreen";
|
||||
this._lastState = "hidden";
|
||||
this._timeoutHide.start();
|
||||
},
|
||||
|
||||
close: function() {
|
||||
if (!this._element) {
|
||||
return;
|
||||
}
|
||||
// Cancel any pending timeout
|
||||
this._timeoutHide.cancel();
|
||||
this._timeoutShow.cancel();
|
||||
// Reset state of the warning box
|
||||
this._state = "hidden";
|
||||
this._element.setAttribute("hidden", true);
|
||||
// Remove all event listeners
|
||||
this._element.removeEventListener("transitionend", this);
|
||||
window.removeEventListener("mousemove", this, true);
|
||||
// Clear fields
|
||||
this._element = null;
|
||||
this._timeoutHide = null;
|
||||
this._timeoutShow = null;
|
||||
|
||||
// Ensure focus switches away from the (now hidden) warning box.
|
||||
// If the user clicked buttons in the warning box, it would have
|
||||
// been focused, and any key events would be directed at the (now
|
||||
// hidden) chrome document instead of the target document.
|
||||
gBrowser.selectedBrowser.focus();
|
||||
},
|
||||
|
||||
// State could be one of "onscreen", "ontop", "hiding", and
|
||||
// "hidden". Setting the state to "onscreen" and "ontop" takes
|
||||
// effect immediately, while setting it to "hidden" actually
|
||||
// turns the state to "hiding" before the transition finishes.
|
||||
_lastState: null,
|
||||
_STATES: ["hidden", "ontop", "onscreen"],
|
||||
get _state() {
|
||||
for (let state of this._STATES) {
|
||||
if (this._element.hasAttribute(state)) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
return "hiding";
|
||||
},
|
||||
set _state(newState) {
|
||||
let currentState = this._state;
|
||||
if (currentState == newState) {
|
||||
return;
|
||||
}
|
||||
if (currentState != "hiding") {
|
||||
this._lastState = currentState;
|
||||
this._element.removeAttribute(currentState);
|
||||
}
|
||||
if (newState != "hidden") {
|
||||
if (currentState != "hidden") {
|
||||
this._element.setAttribute(newState, true);
|
||||
} else {
|
||||
// When the previous state is hidden, the display was none,
|
||||
// thus no box was constructed. We need to wait for the new
|
||||
// display value taking effect first, otherwise, there won't
|
||||
// be any transition. Since requestAnimationFrame callback is
|
||||
// generally triggered before any style flush and layout, we
|
||||
// should wait for the second animation frame.
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (this._element) {
|
||||
this._element.setAttribute(newState, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function(event) {
|
||||
switch (event.type) {
|
||||
case "mousemove": {
|
||||
let state = this._state;
|
||||
if (state == "hidden") {
|
||||
// If the warning box is currently hidden, show it after
|
||||
// a short delay if the pointer is at the top.
|
||||
if (event.clientY != 0) {
|
||||
this._timeoutShow.cancel();
|
||||
} else if (this._timeoutShow.delay >= 0) {
|
||||
this._timeoutShow.start();
|
||||
}
|
||||
} else {
|
||||
let elemRect = this._element.getBoundingClientRect();
|
||||
if (state == "hiding" && this._lastState != "hidden") {
|
||||
// If we are on the hiding transition, and the pointer
|
||||
// moved near the box, restore to the previous state.
|
||||
if (event.clientY <= elemRect.bottom + 50) {
|
||||
this._state = this._lastState;
|
||||
this._timeoutHide.start();
|
||||
}
|
||||
} else if (state == "ontop" || this._lastState != "hidden") {
|
||||
// State being "ontop" or the previous state not being
|
||||
// "hidden" indicates this current warning box is shown
|
||||
// in response to user's action. Hide it immediately when
|
||||
// the pointer leaves that area.
|
||||
if (event.clientY > elemRect.bottom + 50) {
|
||||
this._state = "hidden";
|
||||
this._timeoutHide.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "transitionend": {
|
||||
if (this._state == "hiding") {
|
||||
this._element.setAttribute("hidden", true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var PointerLock = {
|
||||
|
||||
init: function() {
|
||||
window.messageManager.addMessageListener("PointerLock:Entered", this);
|
||||
window.messageManager.addMessageListener("PointerLock:Exited", this);
|
||||
},
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
switch (aMessage.name) {
|
||||
case "PointerLock:Entered": {
|
||||
PointerlockFsWarning.showPointerLock(aMessage.data.originNoSuffix);
|
||||
break;
|
||||
}
|
||||
case "PointerLock:Exited": {
|
||||
PointerlockFsWarning.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var FullScreen = {
|
||||
_MESSAGES: [
|
||||
"DOMFullscreen:Request",
|
||||
"DOMFullscreen:NewOrigin",
|
||||
"DOMFullscreen:Exit",
|
||||
"DOMFullscreen:Painted",
|
||||
],
|
||||
|
||||
init: function() {
|
||||
// called when we go into full screen, even if initiated by a web page script
|
||||
window.addEventListener("fullscreen", this, true);
|
||||
window.addEventListener("MozDOMFullscreen:Entered", this,
|
||||
/* useCapture */ true,
|
||||
/* wantsUntrusted */ false);
|
||||
window.addEventListener("MozDOMFullscreen:Exited", this,
|
||||
/* useCapture */ true,
|
||||
/* wantsUntrusted */ false);
|
||||
for (let type of this._MESSAGES) {
|
||||
window.messageManager.addMessageListener(type, this);
|
||||
}
|
||||
|
||||
if (window.fullScreen)
|
||||
this.toggle();
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
for (let type of this._MESSAGES) {
|
||||
window.messageManager.removeMessageListener(type, this);
|
||||
}
|
||||
this.cleanup();
|
||||
},
|
||||
|
||||
toggle: function () {
|
||||
var enterFS = window.fullScreen;
|
||||
|
||||
// Toggle the View:FullScreen command, which controls elements like the
|
||||
// fullscreen menuitem, and menubars.
|
||||
let fullscreenCommand = document.getElementById("View:FullScreen");
|
||||
if (enterFS) {
|
||||
fullscreenCommand.setAttribute("checked", enterFS);
|
||||
} else {
|
||||
fullscreenCommand.removeAttribute("checked");
|
||||
}
|
||||
|
||||
if (AppConstants.platform == "macosx") {
|
||||
// Make sure the menu items are adjusted.
|
||||
document.getElementById("enterFullScreenItem").hidden = enterFS;
|
||||
document.getElementById("exitFullScreenItem").hidden = !enterFS;
|
||||
}
|
||||
|
||||
if (!this._fullScrToggler) {
|
||||
this._fullScrToggler = document.getElementById("fullscr-toggler");
|
||||
this._fullScrToggler.addEventListener("mouseover", this._expandCallback, false);
|
||||
this._fullScrToggler.addEventListener("dragenter", this._expandCallback, false);
|
||||
this._fullScrToggler.addEventListener("touchmove", this._expandCallback, {passive: true});
|
||||
}
|
||||
|
||||
if (enterFS) {
|
||||
gNavToolbox.setAttribute("inFullscreen", true);
|
||||
document.documentElement.setAttribute("inFullscreen", true);
|
||||
if (!document.fullscreenElement && this.useLionFullScreen)
|
||||
document.documentElement.setAttribute("OSXLionFullscreen", true);
|
||||
} else {
|
||||
gNavToolbox.removeAttribute("inFullscreen");
|
||||
document.documentElement.removeAttribute("inFullscreen");
|
||||
document.documentElement.removeAttribute("OSXLionFullscreen");
|
||||
}
|
||||
|
||||
if (!document.fullscreenElement)
|
||||
this._updateToolbars(enterFS);
|
||||
|
||||
if (enterFS) {
|
||||
document.addEventListener("keypress", this._keyToggleCallback, false);
|
||||
document.addEventListener("popupshown", this._setPopupOpen, false);
|
||||
document.addEventListener("popuphidden", this._setPopupOpen, false);
|
||||
// In DOM fullscreen mode, we hide toolbars with CSS
|
||||
if (!document.fullscreenElement)
|
||||
this.hideNavToolbox(true);
|
||||
}
|
||||
else {
|
||||
this.showNavToolbox(false);
|
||||
// This is needed if they use the context menu to quit fullscreen
|
||||
this._isPopupOpen = false;
|
||||
this.cleanup();
|
||||
// In TabsInTitlebar._update(), we cancel the appearance update on
|
||||
// resize event for exiting fullscreen, since that happens before we
|
||||
// change the UI here in the "fullscreen" event. Hence we need to
|
||||
// call it here to ensure the appearance is properly updated. See
|
||||
// TabsInTitlebar._update() and bug 1173768.
|
||||
TabsInTitlebar.updateAppearance(true);
|
||||
}
|
||||
|
||||
if (enterFS && !document.fullscreenElement) {
|
||||
Services.telemetry.getHistogramById("FX_BROWSER_FULLSCREEN_USED")
|
||||
.add(1);
|
||||
}
|
||||
},
|
||||
|
||||
exitDomFullScreen : function() {
|
||||
document.exitFullscreen();
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
switch (event.type) {
|
||||
case "fullscreen":
|
||||
this.toggle();
|
||||
break;
|
||||
case "MozDOMFullscreen:Entered": {
|
||||
// The event target is the element which requested the DOM
|
||||
// fullscreen. If we were entering DOM fullscreen for a remote
|
||||
// browser, the target would be `gBrowser` and the original
|
||||
// target would be the browser which was the parameter of
|
||||
// `remoteFrameFullscreenChanged` call. If the fullscreen
|
||||
// request was initiated from an in-process browser, we need
|
||||
// to get its corresponding browser here.
|
||||
let browser;
|
||||
if (event.target == gBrowser) {
|
||||
browser = event.originalTarget;
|
||||
} else {
|
||||
let topWin = event.target.ownerGlobal.top;
|
||||
browser = gBrowser.getBrowserForContentWindow(topWin);
|
||||
}
|
||||
TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
|
||||
this.enterDomFullscreen(browser);
|
||||
break;
|
||||
}
|
||||
case "MozDOMFullscreen:Exited":
|
||||
TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
|
||||
this.cleanupDomFullscreen();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
let browser = aMessage.target;
|
||||
switch (aMessage.name) {
|
||||
case "DOMFullscreen:Request": {
|
||||
this._windowUtils.remoteFrameFullscreenChanged(browser);
|
||||
break;
|
||||
}
|
||||
case "DOMFullscreen:NewOrigin": {
|
||||
// Don't show the warning if we've already exited fullscreen.
|
||||
if (document.fullscreen) {
|
||||
PointerlockFsWarning.showFullScreen(aMessage.data.originNoSuffix);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "DOMFullscreen:Exit": {
|
||||
this._windowUtils.remoteFrameFullscreenReverted();
|
||||
break;
|
||||
}
|
||||
case "DOMFullscreen:Painted": {
|
||||
Services.obs.notifyObservers(window, "fullscreen-painted", "");
|
||||
TelemetryStopwatch.finish("FULLSCREEN_CHANGE_MS");
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
enterDomFullscreen : function(aBrowser) {
|
||||
|
||||
if (!document.fullscreenElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have a current pointerlock warning shown then hide it
|
||||
// before transition.
|
||||
PointerlockFsWarning.close();
|
||||
|
||||
// If it is a remote browser, send a message to ask the content
|
||||
// to enter fullscreen state. We don't need to do so if it is an
|
||||
// in-process browser, since all related document should have
|
||||
// entered fullscreen state at this point.
|
||||
// This should be done before the active tab check below to ensure
|
||||
// that the content document handles the pending request. Doing so
|
||||
// before the check is fine since we also check the activeness of
|
||||
// the requesting document in content-side handling code.
|
||||
if (this._isRemoteBrowser(aBrowser)) {
|
||||
aBrowser.messageManager.sendAsyncMessage("DOMFullscreen:Entered");
|
||||
}
|
||||
|
||||
// If we've received a fullscreen notification, we have to ensure that the
|
||||
// element that's requesting fullscreen belongs to the browser that's currently
|
||||
// active. If not, we exit fullscreen since the "full-screen document" isn't
|
||||
// actually visible now.
|
||||
if (!aBrowser || gBrowser.selectedBrowser != aBrowser ||
|
||||
// The top-level window has lost focus since the request to enter
|
||||
// full-screen was made. Cancel full-screen.
|
||||
Services.focus.activeWindow != window) {
|
||||
// This function is called synchronously in fullscreen change, so
|
||||
// we have to avoid calling exitFullscreen synchronously here.
|
||||
setTimeout(() => document.exitFullscreen(), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
document.documentElement.setAttribute("inDOMFullscreen", true);
|
||||
|
||||
if (gFindBarInitialized) {
|
||||
gFindBar.close(true);
|
||||
}
|
||||
|
||||
// Exit DOM full-screen mode upon open, close, or change tab.
|
||||
gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen);
|
||||
gBrowser.tabContainer.addEventListener("TabClose", this.exitDomFullScreen);
|
||||
gBrowser.tabContainer.addEventListener("TabSelect", this.exitDomFullScreen);
|
||||
|
||||
// Add listener to detect when the fullscreen window is re-focused.
|
||||
// If a fullscreen window loses focus, we show a warning when the
|
||||
// fullscreen window is refocused.
|
||||
window.addEventListener("activate", this);
|
||||
},
|
||||
|
||||
cleanup: function () {
|
||||
if (!window.fullScreen) {
|
||||
MousePosTracker.removeListener(this);
|
||||
document.removeEventListener("keypress", this._keyToggleCallback, false);
|
||||
document.removeEventListener("popupshown", this._setPopupOpen, false);
|
||||
document.removeEventListener("popuphidden", this._setPopupOpen, false);
|
||||
}
|
||||
},
|
||||
|
||||
cleanupDomFullscreen: function () {
|
||||
window.messageManager
|
||||
.broadcastAsyncMessage("DOMFullscreen:CleanUp");
|
||||
|
||||
PointerlockFsWarning.close();
|
||||
gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen);
|
||||
gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen);
|
||||
gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
|
||||
window.removeEventListener("activate", this);
|
||||
|
||||
document.documentElement.removeAttribute("inDOMFullscreen");
|
||||
},
|
||||
|
||||
_isRemoteBrowser: function (aBrowser) {
|
||||
return gMultiProcessBrowser && aBrowser.getAttribute("remote") == "true";
|
||||
},
|
||||
|
||||
get _windowUtils() {
|
||||
return window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
},
|
||||
|
||||
getMouseTargetRect: function()
|
||||
{
|
||||
return this._mouseTargetRect;
|
||||
},
|
||||
|
||||
// Event callbacks
|
||||
_expandCallback: function()
|
||||
{
|
||||
FullScreen.showNavToolbox();
|
||||
},
|
||||
onMouseEnter: function()
|
||||
{
|
||||
FullScreen.hideNavToolbox();
|
||||
},
|
||||
_keyToggleCallback: function(aEvent)
|
||||
{
|
||||
// if we can use the keyboard (eg Ctrl+L or Ctrl+E) to open the toolbars, we
|
||||
// should provide a way to collapse them too.
|
||||
if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
|
||||
FullScreen.hideNavToolbox();
|
||||
}
|
||||
// F6 is another shortcut to the address bar, but its not covered in OpenLocation()
|
||||
else if (aEvent.keyCode == aEvent.DOM_VK_F6)
|
||||
FullScreen.showNavToolbox();
|
||||
},
|
||||
|
||||
// Checks whether we are allowed to collapse the chrome
|
||||
_isPopupOpen: false,
|
||||
_isChromeCollapsed: false,
|
||||
_safeToCollapse: function () {
|
||||
if (!gPrefService.getBoolPref("browser.fullscreen.autohide"))
|
||||
return false;
|
||||
|
||||
// a popup menu is open in chrome: don't collapse chrome
|
||||
if (this._isPopupOpen)
|
||||
return false;
|
||||
|
||||
// On OS X Lion we don't want to hide toolbars.
|
||||
if (this.useLionFullScreen)
|
||||
return false;
|
||||
|
||||
// a textbox in chrome is focused (location bar anyone?): don't collapse chrome
|
||||
if (document.commandDispatcher.focusedElement &&
|
||||
document.commandDispatcher.focusedElement.ownerDocument == document &&
|
||||
document.commandDispatcher.focusedElement.localName == "input") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_setPopupOpen: function(aEvent)
|
||||
{
|
||||
// Popups should only veto chrome collapsing if they were opened when the chrome was not collapsed.
|
||||
// Otherwise, they would not affect chrome and the user would expect the chrome to go away.
|
||||
// e.g. we wouldn't want the autoscroll icon firing this event, so when the user
|
||||
// toggles chrome when moving mouse to the top, it doesn't go away again.
|
||||
if (aEvent.type == "popupshown" && !FullScreen._isChromeCollapsed &&
|
||||
aEvent.target.localName != "tooltip" && aEvent.target.localName != "window")
|
||||
FullScreen._isPopupOpen = true;
|
||||
else if (aEvent.type == "popuphidden" && aEvent.target.localName != "tooltip" &&
|
||||
aEvent.target.localName != "window") {
|
||||
FullScreen._isPopupOpen = false;
|
||||
// Try again to hide toolbar when we close the popup.
|
||||
FullScreen.hideNavToolbox(true);
|
||||
}
|
||||
},
|
||||
|
||||
// Autohide helpers for the context menu item
|
||||
getAutohide: function(aItem)
|
||||
{
|
||||
aItem.setAttribute("checked", gPrefService.getBoolPref("browser.fullscreen.autohide"));
|
||||
},
|
||||
setAutohide: function()
|
||||
{
|
||||
gPrefService.setBoolPref("browser.fullscreen.autohide", !gPrefService.getBoolPref("browser.fullscreen.autohide"));
|
||||
// Try again to hide toolbar when we change the pref.
|
||||
FullScreen.hideNavToolbox(true);
|
||||
},
|
||||
|
||||
showNavToolbox: function(trackMouse = true) {
|
||||
this._fullScrToggler.hidden = true;
|
||||
gNavToolbox.removeAttribute("fullscreenShouldAnimate");
|
||||
gNavToolbox.style.marginTop = "";
|
||||
|
||||
if (!this._isChromeCollapsed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Track whether mouse is near the toolbox
|
||||
if (trackMouse && !this.useLionFullScreen) {
|
||||
let rect = gBrowser.mPanelContainer.getBoundingClientRect();
|
||||
this._mouseTargetRect = {
|
||||
top: rect.top + 50,
|
||||
bottom: rect.bottom,
|
||||
left: rect.left,
|
||||
right: rect.right
|
||||
};
|
||||
MousePosTracker.addListener(this);
|
||||
}
|
||||
|
||||
this._isChromeCollapsed = false;
|
||||
},
|
||||
|
||||
hideNavToolbox: function (aAnimate = false) {
|
||||
if (this._isChromeCollapsed || !this._safeToCollapse())
|
||||
return;
|
||||
|
||||
this._fullScrToggler.hidden = false;
|
||||
|
||||
if (aAnimate && gPrefService.getBoolPref("browser.fullscreen.animate")) {
|
||||
gNavToolbox.setAttribute("fullscreenShouldAnimate", true);
|
||||
// Hide the fullscreen toggler until the transition ends.
|
||||
let listener = () => {
|
||||
gNavToolbox.removeEventListener("transitionend", listener, true);
|
||||
if (this._isChromeCollapsed)
|
||||
this._fullScrToggler.hidden = false;
|
||||
};
|
||||
gNavToolbox.addEventListener("transitionend", listener, true);
|
||||
this._fullScrToggler.hidden = true;
|
||||
}
|
||||
|
||||
gNavToolbox.style.marginTop =
|
||||
-gNavToolbox.getBoundingClientRect().height + "px";
|
||||
this._isChromeCollapsed = true;
|
||||
MousePosTracker.removeListener(this);
|
||||
},
|
||||
|
||||
_updateToolbars: function (aEnterFS) {
|
||||
for (let el of document.querySelectorAll("toolbar[fullscreentoolbar=true]")) {
|
||||
if (aEnterFS) {
|
||||
// Give the main nav bar and the tab bar the fullscreen context menu,
|
||||
// otherwise remove context menu to prevent breakage
|
||||
el.setAttribute("saved-context", el.getAttribute("context"));
|
||||
if (el.id == "nav-bar" || el.id == "TabsToolbar")
|
||||
el.setAttribute("context", "autohide-context");
|
||||
else
|
||||
el.removeAttribute("context");
|
||||
|
||||
// Set the inFullscreen attribute to allow specific styling
|
||||
// in fullscreen mode
|
||||
el.setAttribute("inFullscreen", true);
|
||||
} else {
|
||||
if (el.hasAttribute("saved-context")) {
|
||||
el.setAttribute("context", el.getAttribute("saved-context"));
|
||||
el.removeAttribute("saved-context");
|
||||
}
|
||||
el.removeAttribute("inFullscreen");
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarIconColor.inferFromText();
|
||||
|
||||
// For Lion fullscreen, all fullscreen controls are hidden, don't
|
||||
// bother to touch them. If we don't stop here, the following code
|
||||
// could cause the native fullscreen button be shown unexpectedly.
|
||||
// See bug 1165570.
|
||||
if (this.useLionFullScreen) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fullscreenctls = document.getElementById("window-controls");
|
||||
var navbar = document.getElementById("nav-bar");
|
||||
var ctlsOnTabbar = window.toolbar.visible;
|
||||
if (fullscreenctls.parentNode == navbar && ctlsOnTabbar) {
|
||||
fullscreenctls.removeAttribute("flex");
|
||||
document.getElementById("TabsToolbar").appendChild(fullscreenctls);
|
||||
}
|
||||
else if (fullscreenctls.parentNode.id == "TabsToolbar" && !ctlsOnTabbar) {
|
||||
fullscreenctls.setAttribute("flex", "1");
|
||||
navbar.appendChild(fullscreenctls);
|
||||
}
|
||||
fullscreenctls.hidden = !aEnterFS;
|
||||
}
|
||||
};
|
||||
XPCOMUtils.defineLazyGetter(FullScreen, "useLionFullScreen", function() {
|
||||
// We'll only use OS X Lion full screen if we're
|
||||
// * on OS X
|
||||
// * on Lion or higher (Darwin 11+)
|
||||
// * have fullscreenbutton="true"
|
||||
return AppConstants.isPlatformAndVersionAtLeast("macosx", 11) &&
|
||||
document.documentElement.getAttribute("fullscreenbutton") == "true";
|
||||
});
|
||||
|
|
@ -1,526 +0,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/. */
|
||||
|
||||
/**
|
||||
* Controls the "full zoom" setting and its site-specific preferences.
|
||||
*/
|
||||
var FullZoom = {
|
||||
// Identifies the setting in the content prefs database.
|
||||
name: "browser.content.full-zoom",
|
||||
|
||||
// browser.zoom.siteSpecific preference cache
|
||||
_siteSpecificPref: undefined,
|
||||
|
||||
// browser.zoom.updateBackgroundTabs preference cache
|
||||
updateBackgroundTabs: undefined,
|
||||
|
||||
// This maps the browser to monotonically increasing integer
|
||||
// tokens. _browserTokenMap[browser] is increased each time the zoom is
|
||||
// changed in the browser. See _getBrowserToken and _ignorePendingZoomAccesses.
|
||||
_browserTokenMap: new WeakMap(),
|
||||
|
||||
// Stores initial locations if we receive onLocationChange
|
||||
// events before we're initialized.
|
||||
_initialLocations: new WeakMap(),
|
||||
|
||||
get siteSpecific() {
|
||||
return this._siteSpecificPref;
|
||||
},
|
||||
|
||||
// nsISupports
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
|
||||
Ci.nsIObserver,
|
||||
Ci.nsIContentPrefObserver,
|
||||
Ci.nsISupportsWeakReference,
|
||||
Ci.nsISupports]),
|
||||
|
||||
// Initialization & Destruction
|
||||
|
||||
init: function FullZoom_init() {
|
||||
gBrowser.addEventListener("ZoomChangeUsingMouseWheel", this);
|
||||
|
||||
// Register ourselves with the service so we know when our pref changes.
|
||||
this._cps2 = Cc["@mozilla.org/content-pref/service;1"].
|
||||
getService(Ci.nsIContentPrefService2);
|
||||
this._cps2.addObserverForName(this.name, this);
|
||||
|
||||
this._siteSpecificPref =
|
||||
gPrefService.getBoolPref("browser.zoom.siteSpecific");
|
||||
this.updateBackgroundTabs =
|
||||
gPrefService.getBoolPref("browser.zoom.updateBackgroundTabs");
|
||||
// Listen for changes to the browser.zoom branch so we can enable/disable
|
||||
// updating background tabs and per-site saving and restoring of zoom levels.
|
||||
gPrefService.addObserver("browser.zoom.", this, true);
|
||||
|
||||
// If we received onLocationChange events for any of the current browsers
|
||||
// before we were initialized we want to replay those upon initialization.
|
||||
for (let browser of gBrowser.browsers) {
|
||||
if (this._initialLocations.has(browser)) {
|
||||
this.onLocationChange(...this._initialLocations.get(browser), browser);
|
||||
}
|
||||
}
|
||||
|
||||
// This should be nulled after initialization.
|
||||
this._initialLocations = null;
|
||||
},
|
||||
|
||||
destroy: function FullZoom_destroy() {
|
||||
gPrefService.removeObserver("browser.zoom.", this);
|
||||
this._cps2.removeObserverForName(this.name, this);
|
||||
gBrowser.removeEventListener("ZoomChangeUsingMouseWheel", this);
|
||||
},
|
||||
|
||||
|
||||
// Event Handlers
|
||||
|
||||
// nsIDOMEventListener
|
||||
|
||||
handleEvent: function FullZoom_handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "ZoomChangeUsingMouseWheel":
|
||||
let browser = this._getTargetedBrowser(event);
|
||||
this._ignorePendingZoomAccesses(browser);
|
||||
this._applyZoomToPref(browser);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// nsIObserver
|
||||
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "nsPref:changed":
|
||||
switch (aData) {
|
||||
case "browser.zoom.siteSpecific":
|
||||
this._siteSpecificPref =
|
||||
gPrefService.getBoolPref("browser.zoom.siteSpecific");
|
||||
break;
|
||||
case "browser.zoom.updateBackgroundTabs":
|
||||
this.updateBackgroundTabs =
|
||||
gPrefService.getBoolPref("browser.zoom.updateBackgroundTabs");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// nsIContentPrefObserver
|
||||
|
||||
onContentPrefSet: function FullZoom_onContentPrefSet(aGroup, aName, aValue, aIsPrivate) {
|
||||
this._onContentPrefChanged(aGroup, aValue, aIsPrivate);
|
||||
},
|
||||
|
||||
onContentPrefRemoved: function FullZoom_onContentPrefRemoved(aGroup, aName, aIsPrivate) {
|
||||
this._onContentPrefChanged(aGroup, undefined, aIsPrivate);
|
||||
},
|
||||
|
||||
/**
|
||||
* Appropriately updates the zoom level after a content preference has
|
||||
* changed.
|
||||
*
|
||||
* @param aGroup The group of the changed preference.
|
||||
* @param aValue The new value of the changed preference. Pass undefined to
|
||||
* indicate the preference's removal.
|
||||
*/
|
||||
_onContentPrefChanged: function FullZoom__onContentPrefChanged(aGroup, aValue, aIsPrivate) {
|
||||
if (this._isNextContentPrefChangeInternal) {
|
||||
// Ignore changes that FullZoom itself makes. This works because the
|
||||
// content pref service calls callbacks before notifying observers, and it
|
||||
// does both in the same turn of the event loop.
|
||||
delete this._isNextContentPrefChangeInternal;
|
||||
return;
|
||||
}
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
if (!browser.currentURI)
|
||||
return;
|
||||
|
||||
let ctxt = this._loadContextFromBrowser(browser);
|
||||
let domain = this._cps2.extractDomain(browser.currentURI.spec);
|
||||
if (aGroup) {
|
||||
if (aGroup == domain && ctxt.usePrivateBrowsing == aIsPrivate)
|
||||
this._applyPrefToZoom(aValue, browser);
|
||||
return;
|
||||
}
|
||||
|
||||
this._globalValue = aValue === undefined ? aValue :
|
||||
this._ensureValid(aValue);
|
||||
|
||||
// If the current page doesn't have a site-specific preference, then its
|
||||
// zoom should be set to the new global preference now that the global
|
||||
// preference has changed.
|
||||
let hasPref = false;
|
||||
let token = this._getBrowserToken(browser);
|
||||
this._cps2.getByDomainAndName(browser.currentURI.spec, this.name, ctxt, {
|
||||
handleResult: function () { hasPref = true; },
|
||||
handleCompletion: function () {
|
||||
if (!hasPref && token.isCurrent)
|
||||
this._applyPrefToZoom(undefined, browser);
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
// location change observer
|
||||
|
||||
/**
|
||||
* Called when the location of a tab changes.
|
||||
* When that happens, we need to update the current zoom level if appropriate.
|
||||
*
|
||||
* @param aURI
|
||||
* A URI object representing the new location.
|
||||
* @param aIsTabSwitch
|
||||
* Whether this location change has happened because of a tab switch.
|
||||
* @param aBrowser
|
||||
* (optional) browser object displaying the document
|
||||
*/
|
||||
onLocationChange: function FullZoom_onLocationChange(aURI, aIsTabSwitch, aBrowser) {
|
||||
let browser = aBrowser || gBrowser.selectedBrowser;
|
||||
|
||||
// If we haven't been initialized yet but receive an onLocationChange
|
||||
// notification then let's store and replay it upon initialization.
|
||||
if (this._initialLocations) {
|
||||
this._initialLocations.set(browser, [aURI, aIsTabSwitch]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore all pending async zoom accesses in the browser. Pending accesses
|
||||
// that started before the location change will be prevented from applying
|
||||
// to the new location.
|
||||
this._ignorePendingZoomAccesses(browser);
|
||||
|
||||
if (!aURI || (aIsTabSwitch && !this.siteSpecific)) {
|
||||
this._notifyOnLocationChange(browser);
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid the cps roundtrip and apply the default/global pref.
|
||||
if (aURI.spec == "about:blank") {
|
||||
this._applyPrefToZoom(undefined, browser,
|
||||
this._notifyOnLocationChange.bind(this, browser));
|
||||
return;
|
||||
}
|
||||
|
||||
// Media documents should always start at 1, and are not affected by prefs.
|
||||
if (!aIsTabSwitch && browser.isSyntheticDocument) {
|
||||
ZoomManager.setZoomForBrowser(browser, 1);
|
||||
// _ignorePendingZoomAccesses already called above, so no need here.
|
||||
this._notifyOnLocationChange(browser);
|
||||
return;
|
||||
}
|
||||
|
||||
// See if the zoom pref is cached.
|
||||
let ctxt = this._loadContextFromBrowser(browser);
|
||||
let pref = this._cps2.getCachedByDomainAndName(aURI.spec, this.name, ctxt);
|
||||
if (pref) {
|
||||
this._applyPrefToZoom(pref.value, browser,
|
||||
this._notifyOnLocationChange.bind(this, browser));
|
||||
return;
|
||||
}
|
||||
|
||||
// It's not cached, so we have to asynchronously fetch it.
|
||||
let value = undefined;
|
||||
let token = this._getBrowserToken(browser);
|
||||
this._cps2.getByDomainAndName(aURI.spec, this.name, ctxt, {
|
||||
handleResult: function (resultPref) { value = resultPref.value; },
|
||||
handleCompletion: function () {
|
||||
if (!token.isCurrent) {
|
||||
this._notifyOnLocationChange(browser);
|
||||
return;
|
||||
}
|
||||
this._applyPrefToZoom(value, browser,
|
||||
this._notifyOnLocationChange.bind(this, browser));
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
// update state of zoom type menu item
|
||||
|
||||
updateMenu: function FullZoom_updateMenu() {
|
||||
var menuItem = document.getElementById("toggle_zoom");
|
||||
|
||||
menuItem.setAttribute("checked", !ZoomManager.useFullZoom);
|
||||
},
|
||||
|
||||
// Setting & Pref Manipulation
|
||||
|
||||
/**
|
||||
* Reduces the zoom level of the page in the current browser.
|
||||
*/
|
||||
reduce: function FullZoom_reduce() {
|
||||
ZoomManager.reduce();
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
this._ignorePendingZoomAccesses(browser);
|
||||
this._applyZoomToPref(browser);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enlarges the zoom level of the page in the current browser.
|
||||
*/
|
||||
enlarge: function FullZoom_enlarge() {
|
||||
ZoomManager.enlarge();
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
this._ignorePendingZoomAccesses(browser);
|
||||
this._applyZoomToPref(browser);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the zoom level for the given browser to the given floating
|
||||
* point value, where 1 is the default zoom level.
|
||||
*/
|
||||
setZoom: function (value, browser = gBrowser.selectedBrowser) {
|
||||
ZoomManager.setZoomForBrowser(browser, value);
|
||||
this._ignorePendingZoomAccesses(browser);
|
||||
this._applyZoomToPref(browser);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the zoom level of the page in the given browser to the global zoom
|
||||
* level.
|
||||
*
|
||||
* @return A promise which resolves when the zoom reset has been applied.
|
||||
*/
|
||||
reset: function FullZoom_reset(browser = gBrowser.selectedBrowser) {
|
||||
let token = this._getBrowserToken(browser);
|
||||
let result = this._getGlobalValue(browser).then(value => {
|
||||
if (token.isCurrent) {
|
||||
ZoomManager.setZoomForBrowser(browser, value === undefined ? 1 : value);
|
||||
this._ignorePendingZoomAccesses(browser);
|
||||
Services.obs.notifyObservers(browser, "browser-fullZoom:zoomReset", "");
|
||||
}
|
||||
});
|
||||
this._removePref(browser);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the zoom level for a given browser.
|
||||
*
|
||||
* Per nsPresContext::setFullZoom, we can set the zoom to its current value
|
||||
* without significant impact on performance, as the setting is only applied
|
||||
* if it differs from the current setting. In fact getting the zoom and then
|
||||
* checking ourselves if it differs costs more.
|
||||
*
|
||||
* And perhaps we should always set the zoom even if it was more expensive,
|
||||
* since nsDocumentViewer::SetTextZoom claims that child documents can have
|
||||
* a different text zoom (although it would be unusual), and it implies that
|
||||
* those child text zooms should get updated when the parent zoom gets set,
|
||||
* and perhaps the same is true for full zoom
|
||||
* (although nsDocumentViewer::SetFullZoom doesn't mention it).
|
||||
*
|
||||
* So when we apply new zoom values to the browser, we simply set the zoom.
|
||||
* We don't check first to see if the new value is the same as the current
|
||||
* one.
|
||||
*
|
||||
* @param aValue The zoom level value.
|
||||
* @param aBrowser The zoom is set in this browser. Required.
|
||||
* @param aCallback If given, it's asynchronously called when complete.
|
||||
*/
|
||||
_applyPrefToZoom: function FullZoom__applyPrefToZoom(aValue, aBrowser, aCallback) {
|
||||
if (!this.siteSpecific || gInPrintPreviewMode) {
|
||||
this._executeSoon(aCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
// The browser is sometimes half-destroyed because this method is called
|
||||
// by content pref service callbacks, which themselves can be called at any
|
||||
// time, even after browsers are closed.
|
||||
if (!aBrowser.parentNode || aBrowser.isSyntheticDocument) {
|
||||
this._executeSoon(aCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
if (aValue !== undefined) {
|
||||
ZoomManager.setZoomForBrowser(aBrowser, this._ensureValid(aValue));
|
||||
this._ignorePendingZoomAccesses(aBrowser);
|
||||
this._executeSoon(aCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
let token = this._getBrowserToken(aBrowser);
|
||||
this._getGlobalValue(aBrowser).then(value => {
|
||||
if (token.isCurrent) {
|
||||
ZoomManager.setZoomForBrowser(aBrowser, value === undefined ? 1 : value);
|
||||
this._ignorePendingZoomAccesses(aBrowser);
|
||||
}
|
||||
this._executeSoon(aCallback);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves the zoom level of the page in the given browser to the content
|
||||
* prefs store.
|
||||
*
|
||||
* @param browser The zoom of this browser will be saved. Required.
|
||||
*/
|
||||
_applyZoomToPref: function FullZoom__applyZoomToPref(browser) {
|
||||
Services.obs.notifyObservers(browser, "browser-fullZoom:zoomChange", "");
|
||||
if (!this.siteSpecific ||
|
||||
gInPrintPreviewMode ||
|
||||
browser.isSyntheticDocument)
|
||||
return;
|
||||
|
||||
this._cps2.set(browser.currentURI.spec, this.name,
|
||||
ZoomManager.getZoomForBrowser(browser),
|
||||
this._loadContextFromBrowser(browser), {
|
||||
handleCompletion: function () {
|
||||
this._isNextContentPrefChangeInternal = true;
|
||||
}.bind(this),
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes from the content prefs store the zoom level of the given browser.
|
||||
*
|
||||
* @param browser The zoom of this browser will be removed. Required.
|
||||
*/
|
||||
_removePref: function FullZoom__removePref(browser) {
|
||||
Services.obs.notifyObservers(browser, "browser-fullZoom:zoomReset", "");
|
||||
if (browser.isSyntheticDocument)
|
||||
return;
|
||||
let ctxt = this._loadContextFromBrowser(browser);
|
||||
this._cps2.removeByDomainAndName(browser.currentURI.spec, this.name, ctxt, {
|
||||
handleCompletion: function () {
|
||||
this._isNextContentPrefChangeInternal = true;
|
||||
}.bind(this),
|
||||
});
|
||||
},
|
||||
|
||||
// Utilities
|
||||
|
||||
/**
|
||||
* Returns the zoom change token of the given browser. Asynchronous
|
||||
* operations that access the given browser's zoom should use this method to
|
||||
* capture the token before starting and use token.isCurrent to determine if
|
||||
* it's safe to access the zoom when done. If token.isCurrent is false, then
|
||||
* after the async operation started, either the browser's zoom was changed or
|
||||
* the browser was destroyed, and depending on what the operation is doing, it
|
||||
* may no longer be safe to set and get its zoom.
|
||||
*
|
||||
* @param browser The token of this browser will be returned.
|
||||
* @return An object with an "isCurrent" getter.
|
||||
*/
|
||||
_getBrowserToken: function FullZoom__getBrowserToken(browser) {
|
||||
let map = this._browserTokenMap;
|
||||
if (!map.has(browser))
|
||||
map.set(browser, 0);
|
||||
return {
|
||||
token: map.get(browser),
|
||||
get isCurrent() {
|
||||
// At this point, the browser may have been destructed and unbound but
|
||||
// its outer ID not removed from the map because outer-window-destroyed
|
||||
// hasn't been received yet. In that case, the browser is unusable, it
|
||||
// has no properties, so return false. Check for this case by getting a
|
||||
// property, say, docShell.
|
||||
return map.get(browser) === this.token && browser.parentNode;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the browser that the supplied zoom event is associated with.
|
||||
* @param event The ZoomChangeUsingMouseWheel event.
|
||||
* @return The associated browser element, if one exists, otherwise null.
|
||||
*/
|
||||
_getTargetedBrowser: function FullZoom__getTargetedBrowser(event) {
|
||||
let target = event.originalTarget;
|
||||
|
||||
// With remote content browsers, the event's target is the browser
|
||||
// we're looking for.
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
if (target instanceof window.XULElement &&
|
||||
target.localName == "browser" &&
|
||||
target.namespaceURI == XUL_NS)
|
||||
return target;
|
||||
|
||||
// With in-process content browsers, the event's target is the content
|
||||
// document.
|
||||
if (target.nodeType == Node.DOCUMENT_NODE)
|
||||
return gBrowser.getBrowserForDocument(target);
|
||||
|
||||
throw new Error("Unexpected ZoomChangeUsingMouseWheel event source");
|
||||
},
|
||||
|
||||
/**
|
||||
* Increments the zoom change token for the given browser so that pending
|
||||
* async operations know that it may be unsafe to access they zoom when they
|
||||
* finish.
|
||||
*
|
||||
* @param browser Pending accesses in this browser will be ignored.
|
||||
*/
|
||||
_ignorePendingZoomAccesses: function FullZoom__ignorePendingZoomAccesses(browser) {
|
||||
let map = this._browserTokenMap;
|
||||
map.set(browser, (map.get(browser) || 0) + 1);
|
||||
},
|
||||
|
||||
_ensureValid: function FullZoom__ensureValid(aValue) {
|
||||
// Note that undefined is a valid value for aValue that indicates a known-
|
||||
// not-to-exist value.
|
||||
if (isNaN(aValue))
|
||||
return 1;
|
||||
|
||||
if (aValue < ZoomManager.MIN)
|
||||
return ZoomManager.MIN;
|
||||
|
||||
if (aValue > ZoomManager.MAX)
|
||||
return ZoomManager.MAX;
|
||||
|
||||
return aValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the global browser.content.full-zoom content preference.
|
||||
*
|
||||
* @param browser The browser pertaining to the zoom.
|
||||
* @returns Promise<prefValue>
|
||||
* Resolves to the preference value when done.
|
||||
*/
|
||||
_getGlobalValue: function FullZoom__getGlobalValue(browser) {
|
||||
// * !("_globalValue" in this) => global value not yet cached.
|
||||
// * this._globalValue === undefined => global value known not to exist.
|
||||
// * Otherwise, this._globalValue is a number, the global value.
|
||||
return new Promise(resolve => {
|
||||
if ("_globalValue" in this) {
|
||||
resolve(this._globalValue);
|
||||
return;
|
||||
}
|
||||
let value = undefined;
|
||||
this._cps2.getGlobal(this.name, this._loadContextFromBrowser(browser), {
|
||||
handleResult: function (pref) { value = pref.value; },
|
||||
handleCompletion: (reason) => {
|
||||
this._globalValue = this._ensureValid(value);
|
||||
resolve(this._globalValue);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the load context from the given Browser.
|
||||
*
|
||||
* @param Browser The Browser whose load context will be returned.
|
||||
* @return The nsILoadContext of the given Browser.
|
||||
*/
|
||||
_loadContextFromBrowser: function FullZoom__loadContextFromBrowser(browser) {
|
||||
return browser.loadContext;
|
||||
},
|
||||
|
||||
/**
|
||||
* Asynchronously broadcasts "browser-fullZoom:location-change" so that
|
||||
* listeners can be notified when the zoom levels on those pages change.
|
||||
* The notification is always asynchronous so that observers are guaranteed a
|
||||
* consistent behavior.
|
||||
*/
|
||||
_notifyOnLocationChange: function FullZoom__notifyOnLocationChange(browser) {
|
||||
this._executeSoon(function () {
|
||||
Services.obs.notifyObservers(browser, "browser-fullZoom:location-change", "");
|
||||
});
|
||||
},
|
||||
|
||||
_executeSoon: function FullZoom__executeSoon(callback) {
|
||||
if (!callback)
|
||||
return;
|
||||
Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
},
|
||||
};
|
||||
|
|
@ -1,459 +0,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/. */
|
||||
|
||||
var gFxAccounts = {
|
||||
|
||||
SYNC_MIGRATION_NOTIFICATION_TITLE: "fxa-migration",
|
||||
|
||||
_initialized: false,
|
||||
_inCustomizationMode: false,
|
||||
_cachedProfile: null,
|
||||
|
||||
get weave() {
|
||||
delete this.weave;
|
||||
return this.weave = Cc["@mozilla.org/weave/service;1"]
|
||||
.getService(Ci.nsISupports)
|
||||
.wrappedJSObject;
|
||||
},
|
||||
|
||||
get topics() {
|
||||
// Do all this dance to lazy-load FxAccountsCommon.
|
||||
delete this.topics;
|
||||
return this.topics = [
|
||||
"weave:service:ready",
|
||||
"weave:service:login:change",
|
||||
"weave:service:setup-complete",
|
||||
"weave:service:sync:error",
|
||||
"weave:ui:login:error",
|
||||
"fxa-migration:state-changed",
|
||||
this.FxAccountsCommon.ONLOGIN_NOTIFICATION,
|
||||
this.FxAccountsCommon.ONLOGOUT_NOTIFICATION,
|
||||
this.FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
|
||||
];
|
||||
},
|
||||
|
||||
get panelUIFooter() {
|
||||
delete this.panelUIFooter;
|
||||
return this.panelUIFooter = document.getElementById("PanelUI-footer-fxa");
|
||||
},
|
||||
|
||||
get panelUIStatus() {
|
||||
delete this.panelUIStatus;
|
||||
return this.panelUIStatus = document.getElementById("PanelUI-fxa-status");
|
||||
},
|
||||
|
||||
get panelUIAvatar() {
|
||||
delete this.panelUIAvatar;
|
||||
return this.panelUIAvatar = document.getElementById("PanelUI-fxa-avatar");
|
||||
},
|
||||
|
||||
get panelUILabel() {
|
||||
delete this.panelUILabel;
|
||||
return this.panelUILabel = document.getElementById("PanelUI-fxa-label");
|
||||
},
|
||||
|
||||
get panelUIIcon() {
|
||||
delete this.panelUIIcon;
|
||||
return this.panelUIIcon = document.getElementById("PanelUI-fxa-icon");
|
||||
},
|
||||
|
||||
get strings() {
|
||||
delete this.strings;
|
||||
return this.strings = Services.strings.createBundle(
|
||||
"chrome://browser/locale/accounts.properties"
|
||||
);
|
||||
},
|
||||
|
||||
get loginFailed() {
|
||||
// Referencing Weave.Service will implicitly initialize sync, and we don't
|
||||
// want to force that - so first check if it is ready.
|
||||
let service = Cc["@mozilla.org/weave/service;1"]
|
||||
.getService(Components.interfaces.nsISupports)
|
||||
.wrappedJSObject;
|
||||
if (!service.ready) {
|
||||
return false;
|
||||
}
|
||||
// LOGIN_FAILED_LOGIN_REJECTED explicitly means "you must log back in".
|
||||
// All other login failures are assumed to be transient and should go
|
||||
// away by themselves, so aren't reflected here.
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let topic of this.topics) {
|
||||
Services.obs.addObserver(this, topic, false);
|
||||
}
|
||||
|
||||
gNavToolbox.addEventListener("customizationstarting", this);
|
||||
gNavToolbox.addEventListener("customizationending", this);
|
||||
|
||||
EnsureFxAccountsWebChannel();
|
||||
this._initialized = true;
|
||||
|
||||
this.updateUI();
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
if (!this._initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let topic of this.topics) {
|
||||
Services.obs.removeObserver(this, topic);
|
||||
}
|
||||
|
||||
this._initialized = false;
|
||||
},
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "fxa-migration:state-changed":
|
||||
this.onMigrationStateChanged(data, subject);
|
||||
break;
|
||||
case this.FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION:
|
||||
this._cachedProfile = null;
|
||||
// Fallthrough intended
|
||||
default:
|
||||
this.updateUI();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onMigrationStateChanged: function () {
|
||||
// Since we nuked most of the migration code, this notification will fire
|
||||
// once after legacy Sync has been disconnected (and should never fire
|
||||
// again)
|
||||
let nb = window.document.getElementById("global-notificationbox");
|
||||
|
||||
let msg = this.strings.GetStringFromName("autoDisconnectDescription")
|
||||
let signInLabel = this.strings.GetStringFromName("autoDisconnectSignIn.label");
|
||||
let signInAccessKey = this.strings.GetStringFromName("autoDisconnectSignIn.accessKey");
|
||||
let learnMoreLink = this.fxaMigrator.learnMoreLink;
|
||||
|
||||
let buttons = [
|
||||
{
|
||||
label: signInLabel,
|
||||
accessKey: signInAccessKey,
|
||||
callback: () => {
|
||||
this.openPreferences();
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
let fragment = document.createDocumentFragment();
|
||||
let msgNode = document.createTextNode(msg);
|
||||
fragment.appendChild(msgNode);
|
||||
if (learnMoreLink) {
|
||||
let link = document.createElement("label");
|
||||
link.className = "text-link";
|
||||
link.setAttribute("value", learnMoreLink.text);
|
||||
link.href = learnMoreLink.href;
|
||||
fragment.appendChild(link);
|
||||
}
|
||||
|
||||
nb.appendNotification(fragment,
|
||||
this.SYNC_MIGRATION_NOTIFICATION_TITLE,
|
||||
undefined,
|
||||
nb.PRIORITY_WARNING_LOW,
|
||||
buttons);
|
||||
|
||||
// ensure the hamburger menu reflects the newly disconnected state.
|
||||
this.updateAppMenuItem();
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
this._inCustomizationMode = event.type == "customizationstarting";
|
||||
this.updateAppMenuItem();
|
||||
},
|
||||
|
||||
updateUI: function () {
|
||||
// It's possible someone signed in to FxA after seeing our notification
|
||||
// about "Legacy Sync migration" (which now is actually "Legacy Sync
|
||||
// auto-disconnect") so kill that notification if it still exists.
|
||||
let nb = window.document.getElementById("global-notificationbox");
|
||||
let n = nb.getNotificationWithValue(this.SYNC_MIGRATION_NOTIFICATION_TITLE);
|
||||
if (n) {
|
||||
nb.removeNotification(n, true);
|
||||
}
|
||||
|
||||
this.updateAppMenuItem();
|
||||
},
|
||||
|
||||
// Note that updateAppMenuItem() returns a Promise that's only used by tests.
|
||||
updateAppMenuItem: function () {
|
||||
let profileInfoEnabled = false;
|
||||
try {
|
||||
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.
|
||||
if (this._inCustomizationMode) {
|
||||
this.panelUIStatus.setAttribute("disabled", "true");
|
||||
this.panelUILabel.setAttribute("disabled", "true");
|
||||
this.panelUIAvatar.setAttribute("disabled", "true");
|
||||
this.panelUIIcon.setAttribute("disabled", "true");
|
||||
} else {
|
||||
this.panelUIStatus.removeAttribute("disabled");
|
||||
this.panelUILabel.removeAttribute("disabled");
|
||||
this.panelUIAvatar.removeAttribute("disabled");
|
||||
this.panelUIIcon.removeAttribute("disabled");
|
||||
}
|
||||
|
||||
let defaultLabel = this.panelUIStatus.getAttribute("defaultlabel");
|
||||
let errorLabel = this.panelUIStatus.getAttribute("errorlabel");
|
||||
let unverifiedLabel = this.panelUIStatus.getAttribute("unverifiedlabel");
|
||||
// The localization string is for the signed in text, but it's the default text as well
|
||||
let defaultTooltiptext = this.panelUIStatus.getAttribute("signedinTooltiptext");
|
||||
|
||||
let updateWithUserData = (userData) => {
|
||||
// Window might have been closed while fetching data.
|
||||
if (window.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the button to its original state.
|
||||
this.panelUILabel.setAttribute("label", defaultLabel);
|
||||
this.panelUIStatus.setAttribute("tooltiptext", defaultTooltiptext);
|
||||
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 (showErrorBadge) {
|
||||
gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_FXA, "fxa-needs-authentication");
|
||||
} else {
|
||||
gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_FXA);
|
||||
}
|
||||
}
|
||||
|
||||
let updateWithProfile = (profile) => {
|
||||
if (profileInfoEnabled) {
|
||||
if (profile.displayName) {
|
||||
this.panelUILabel.setAttribute("label", profile.displayName);
|
||||
}
|
||||
if (profile.avatar) {
|
||||
this.panelUIFooter.setAttribute("fxaprofileimage", "set");
|
||||
let bgImage = "url(\"" + profile.avatar + "\")";
|
||||
this.panelUIAvatar.style.listStyleImage = bgImage;
|
||||
|
||||
let img = new Image();
|
||||
img.onerror = () => {
|
||||
// Clear the image if it has trouble loading. Since this callback is asynchronous
|
||||
// we check to make sure the image is still the same before we clear it.
|
||||
if (this.panelUIAvatar.style.listStyleImage === bgImage) {
|
||||
this.panelUIFooter.removeAttribute("fxaprofileimage");
|
||||
this.panelUIAvatar.style.removeProperty("list-style-image");
|
||||
}
|
||||
};
|
||||
img.src = profile.avatar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fxAccounts.getSignedInUser().then(userData => {
|
||||
// userData may be null here when the user is not signed-in, but that's expected
|
||||
updateWithUserData(userData);
|
||||
// unverified users cause us to spew log errors fetching an OAuth token
|
||||
// to fetch the profile, so don't even try in that case.
|
||||
if (!userData || !userData.verified || !profileInfoEnabled) {
|
||||
return null; // don't even try to grab the profile.
|
||||
}
|
||||
if (this._cachedProfile) {
|
||||
return this._cachedProfile;
|
||||
}
|
||||
return fxAccounts.getSignedInUserProfile().catch(err => {
|
||||
// Not fetching the profile is sad but the FxA logs will already have noise.
|
||||
return null;
|
||||
});
|
||||
}).then(profile => {
|
||||
if (!profile) {
|
||||
return;
|
||||
}
|
||||
updateWithProfile(profile);
|
||||
this._cachedProfile = profile; // Try to avoid fetching the profile on every UI update
|
||||
}).catch(error => {
|
||||
// This is most likely in tests, were we quickly log users in and out.
|
||||
// The most likely scenario is a user logged out, so reflect that.
|
||||
// Bug 995134 calls for better errors so we could retry if we were
|
||||
// sure this was the failure reason.
|
||||
this.FxAccountsCommon.log.error("Error updating FxA account info", error);
|
||||
updateWithUserData(null);
|
||||
});
|
||||
},
|
||||
|
||||
onMenuPanelCommand: function () {
|
||||
|
||||
switch (this.panelUIFooter.getAttribute("fxastatus")) {
|
||||
case "signedin":
|
||||
this.openPreferences();
|
||||
break;
|
||||
case "error":
|
||||
if (this.panelUIFooter.getAttribute("unverified")) {
|
||||
this.openPreferences();
|
||||
} else {
|
||||
this.openSignInAgainPage("menupanel");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.openPreferences();
|
||||
break;
|
||||
}
|
||||
|
||||
PanelUI.hide();
|
||||
},
|
||||
|
||||
openPreferences: function () {
|
||||
openPreferences("paneSync", { urlParams: { entrypoint: "menupanel" } });
|
||||
},
|
||||
|
||||
openAccountsPage: function (action, urlParams={}) {
|
||||
let params = new URLSearchParams();
|
||||
if (action) {
|
||||
params.set("action", action);
|
||||
}
|
||||
for (let name in urlParams) {
|
||||
if (urlParams[name] !== undefined) {
|
||||
params.set(name, urlParams[name]);
|
||||
}
|
||||
}
|
||||
let url = "about:accounts?" + params;
|
||||
switchToTabHavingURI(url, true, {
|
||||
replaceQueryString: true
|
||||
});
|
||||
},
|
||||
|
||||
openSignInAgainPage: function (entryPoint) {
|
||||
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 });
|
||||
},
|
||||
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(gFxAccounts, "FxAccountsCommon", function () {
|
||||
return Cu.import("resource://gre/modules/FxAccountsCommon.js", {});
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(gFxAccounts, "fxaMigrator",
|
||||
"resource://services-sync/FxaMigrator.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "EnsureFxAccountsWebChannel",
|
||||
"resource://gre/modules/FxAccountsWebChannel.jsm");
|
||||
|
|
@ -1,367 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
var gEMEHandler = {
|
||||
get uiEnabled() {
|
||||
#ifdef MOZ_EME
|
||||
let emeUIEnabled = Services.prefs.getBoolPref("browser.eme.ui.enabled");
|
||||
// Force-disable on WinXP:
|
||||
if (navigator.platform.toLowerCase().startsWith("win")) {
|
||||
emeUIEnabled = emeUIEnabled && parseFloat(Services.sysinfo.get("version")) >= 6;
|
||||
}
|
||||
return emeUIEnabled;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
},
|
||||
ensureEMEEnabled: function(browser, keySystem) {
|
||||
Services.prefs.setBoolPref("media.eme.enabled", true);
|
||||
if (keySystem) {
|
||||
if (keySystem.startsWith("com.adobe") &&
|
||||
Services.prefs.getPrefType("media.gmp-eme-adobe.enabled") &&
|
||||
!Services.prefs.getBoolPref("media.gmp-eme-adobe.enabled")) {
|
||||
Services.prefs.setBoolPref("media.gmp-eme-adobe.enabled", true);
|
||||
} else if (keySystem == "com.widevine.alpha" &&
|
||||
Services.prefs.getPrefType("media.gmp-widevinecdm.enabled") &&
|
||||
!Services.prefs.getBoolPref("media.gmp-widevinecdm.enabled")) {
|
||||
Services.prefs.setBoolPref("media.gmp-widevinecdm.enabled", true);
|
||||
}
|
||||
}
|
||||
browser.reload();
|
||||
},
|
||||
isKeySystemVisible: function(keySystem) {
|
||||
if (!keySystem) {
|
||||
return false;
|
||||
}
|
||||
if (keySystem.startsWith("com.adobe") &&
|
||||
Services.prefs.getPrefType("media.gmp-eme-adobe.visible")) {
|
||||
return Services.prefs.getBoolPref("media.gmp-eme-adobe.visible");
|
||||
}
|
||||
if (keySystem == "com.widevine.alpha" &&
|
||||
Services.prefs.getPrefType("media.gmp-widevinecdm.visible")) {
|
||||
return Services.prefs.getBoolPref("media.gmp-widevinecdm.visible");
|
||||
}
|
||||
return true;
|
||||
},
|
||||
getEMEDisabledFragment: function(msgId) {
|
||||
let mainMessage = gNavigatorBundle.getString("emeNotifications.drmContentDisabled.message");
|
||||
let [prefix, suffix] = mainMessage.split(/%(?:\$\d)?S/).map(s => document.createTextNode(s));
|
||||
let text = gNavigatorBundle.getString("emeNotifications.drmContentDisabled.learnMoreLabel");
|
||||
let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
|
||||
let link = document.createElement("label");
|
||||
link.className = "text-link";
|
||||
link.setAttribute("href", baseURL + "drm-content");
|
||||
link.textContent = text;
|
||||
|
||||
let fragment = document.createDocumentFragment();
|
||||
[prefix, link, suffix].forEach(n => fragment.appendChild(n));
|
||||
return fragment;
|
||||
},
|
||||
getMessageWithBrandName: function(notificationId) {
|
||||
let msgId = "emeNotifications." + notificationId + ".message";
|
||||
return gNavigatorBundle.getFormattedString(msgId, [this._brandShortName]);
|
||||
},
|
||||
receiveMessage: function({target: browser, data: data}) {
|
||||
let parsedData;
|
||||
try {
|
||||
parsedData = JSON.parse(data);
|
||||
} catch (ex) {
|
||||
Cu.reportError("Malformed EME video message with data: " + data);
|
||||
return;
|
||||
}
|
||||
let {status: status, keySystem: keySystem} = parsedData;
|
||||
// Don't need to show if disabled or keysystem not visible.
|
||||
if (!this.uiEnabled || !this.isKeySystemVisible(keySystem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let notificationId;
|
||||
let buttonCallback;
|
||||
// Notification message can be either a string or a DOM fragment.
|
||||
let notificationMessage;
|
||||
switch (status) {
|
||||
case "available":
|
||||
case "cdm-created":
|
||||
// Only show the chain icon for proprietary CDMs. Clearkey is not one.
|
||||
if (keySystem != "org.w3.clearkey") {
|
||||
this.showPopupNotificationForSuccess(browser, keySystem);
|
||||
}
|
||||
// ... and bail!
|
||||
return;
|
||||
|
||||
case "api-disabled":
|
||||
case "cdm-disabled":
|
||||
notificationId = "drmContentDisabled";
|
||||
buttonCallback = gEMEHandler.ensureEMEEnabled.bind(gEMEHandler, browser, keySystem)
|
||||
notificationMessage = this.getEMEDisabledFragment();
|
||||
break;
|
||||
|
||||
case "cdm-insufficient-version":
|
||||
notificationId = "drmContentCDMInsufficientVersion";
|
||||
notificationMessage = this.getMessageWithBrandName(notificationId);
|
||||
break;
|
||||
|
||||
case "cdm-not-installed":
|
||||
notificationId = "drmContentCDMInstalling";
|
||||
notificationMessage = this.getMessageWithBrandName(notificationId);
|
||||
break;
|
||||
|
||||
case "cdm-not-supported":
|
||||
// Not to pop up user-level notification because they cannot do anything
|
||||
// about it.
|
||||
return;
|
||||
default:
|
||||
Cu.reportError(new Error("Unknown message ('" + status + "') dealing with EME key request: " + data));
|
||||
return;
|
||||
}
|
||||
|
||||
// Now actually create the notification
|
||||
|
||||
let box = gBrowser.getNotificationBox(browser);
|
||||
if (box.getNotificationWithValue(notificationId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let buttons = [];
|
||||
if (buttonCallback) {
|
||||
let msgPrefix = "emeNotifications." + notificationId + ".";
|
||||
let btnLabelId = msgPrefix + "button.label";
|
||||
let btnAccessKeyId = msgPrefix + "button.accesskey";
|
||||
buttons.push({
|
||||
label: gNavigatorBundle.getString(btnLabelId),
|
||||
accessKey: gNavigatorBundle.getString(btnAccessKeyId),
|
||||
callback: buttonCallback
|
||||
});
|
||||
}
|
||||
|
||||
let iconURL = "chrome://browser/skin/drm-icon.svg#chains-black";
|
||||
|
||||
box.appendNotification(notificationMessage, notificationId, iconURL,
|
||||
box.PRIORITY_WARNING_MEDIUM, buttons);
|
||||
},
|
||||
showPopupNotificationForSuccess: function(browser, keySystem) {
|
||||
// We're playing EME content! Remove any "we can't play because..." messages.
|
||||
var box = gBrowser.getNotificationBox(browser);
|
||||
["drmContentDisabled",
|
||||
"drmContentCDMInstalling"
|
||||
].forEach(function (value) {
|
||||
var notification = box.getNotificationWithValue(value);
|
||||
if (notification)
|
||||
box.removeNotification(notification);
|
||||
});
|
||||
|
||||
// Don't bother creating it if it's already there:
|
||||
if (PopupNotifications.getNotification("drmContentPlaying", browser)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let msgPrefix = "emeNotifications.drmContentPlaying.";
|
||||
let msgId = msgPrefix + "message2";
|
||||
let btnLabelId = msgPrefix + "button.label";
|
||||
let btnAccessKeyId = msgPrefix + "button.accesskey";
|
||||
|
||||
let message = gNavigatorBundle.getFormattedString(msgId, [this._brandShortName]);
|
||||
let anchorId = "eme-notification-icon";
|
||||
let firstPlayPref = "browser.eme.ui.firstContentShown";
|
||||
if (!Services.prefs.getPrefType(firstPlayPref) ||
|
||||
!Services.prefs.getBoolPref(firstPlayPref)) {
|
||||
document.getElementById(anchorId).setAttribute("firstplay", "true");
|
||||
Services.prefs.setBoolPref(firstPlayPref, true);
|
||||
} else {
|
||||
document.getElementById(anchorId).removeAttribute("firstplay");
|
||||
}
|
||||
|
||||
let mainAction = {
|
||||
label: gNavigatorBundle.getString(btnLabelId),
|
||||
accessKey: gNavigatorBundle.getString(btnAccessKeyId),
|
||||
callback: function() { openPreferences("paneContent"); },
|
||||
dismiss: true
|
||||
};
|
||||
let options = {
|
||||
dismissed: true,
|
||||
eventCallback: aTopic => aTopic == "swapping",
|
||||
learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content",
|
||||
};
|
||||
PopupNotifications.show(browser, "drmContentPlaying", message, anchorId, mainAction, null, options);
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener])
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(gEMEHandler, "_brandShortName", function() {
|
||||
return document.getElementById("bundle_brand").getString("brandShortName");
|
||||
});
|
||||
|
||||
const TELEMETRY_DDSTAT_SHOWN = 0;
|
||||
const TELEMETRY_DDSTAT_SHOWN_FIRST = 1;
|
||||
const TELEMETRY_DDSTAT_CLICKED = 2;
|
||||
const TELEMETRY_DDSTAT_CLICKED_FIRST = 3;
|
||||
const TELEMETRY_DDSTAT_SOLVED = 4;
|
||||
|
||||
let gDecoderDoctorHandler = {
|
||||
getLabelForNotificationBox(type) {
|
||||
if (type == "adobe-cdm-not-found" &&
|
||||
AppConstants.platform == "win") {
|
||||
if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
|
||||
// We supply our own Learn More button so we don't need to populate the message here.
|
||||
return gNavigatorBundle.getFormattedString("emeNotifications.drmContentDisabled.message", [""]);
|
||||
}
|
||||
return gNavigatorBundle.getString("decoder.noCodecs.message");
|
||||
}
|
||||
if (type == "adobe-cdm-not-activated" &&
|
||||
AppConstants.platform == "win") {
|
||||
if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
|
||||
return gNavigatorBundle.getString("decoder.noCodecsXP.message");
|
||||
}
|
||||
if (!AppConstants.isPlatformAndVersionAtLeast("win", "6.1")) {
|
||||
return gNavigatorBundle.getString("decoder.noCodecsVista.message");
|
||||
}
|
||||
return gNavigatorBundle.getString("decoder.noCodecs.message");
|
||||
}
|
||||
if (type == "platform-decoder-not-found") {
|
||||
if (AppConstants.isPlatformAndVersionAtLeast("win", "6.1")) {
|
||||
return gNavigatorBundle.getString("decoder.noHWAcceleration.message");
|
||||
}
|
||||
if (AppConstants.isPlatformAndVersionAtLeast("win", "6")) {
|
||||
return gNavigatorBundle.getString("decoder.noHWAccelerationVista.message");
|
||||
}
|
||||
if (AppConstants.platform == "linux") {
|
||||
return gNavigatorBundle.getString("decoder.noCodecsLinux.message");
|
||||
}
|
||||
}
|
||||
if (type == "cannot-initialize-pulseaudio") {
|
||||
return gNavigatorBundle.getString("decoder.noPulseAudio.message");
|
||||
}
|
||||
if (type == "unsupported-libavcodec" &&
|
||||
AppConstants.platform == "linux") {
|
||||
return gNavigatorBundle.getString("decoder.unsupportedLibavcodec.message");
|
||||
}
|
||||
return "";
|
||||
},
|
||||
|
||||
getSumoForLearnHowButton(type) {
|
||||
if (AppConstants.platform == "win") {
|
||||
return "fix-video-audio-problems-firefox-windows";
|
||||
}
|
||||
if (type == "cannot-initialize-pulseaudio") {
|
||||
return "fix-common-audio-and-video-issues";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
|
||||
receiveMessage({target: browser, data: data}) {
|
||||
let box = gBrowser.getNotificationBox(browser);
|
||||
let notificationId = "decoder-doctor-notification";
|
||||
if (box.getNotificationWithValue(notificationId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parsedData;
|
||||
try {
|
||||
parsedData = JSON.parse(data);
|
||||
} catch (ex) {
|
||||
Cu.reportError("Malformed Decoder Doctor message with data: " + data);
|
||||
return;
|
||||
}
|
||||
// parsedData (the result of parsing the incoming 'data' json string)
|
||||
// contains analysis information from Decoder Doctor:
|
||||
// - 'type' is the type of issue, it determines which text to show in the
|
||||
// infobar.
|
||||
// - 'decoderDoctorReportId' is the Decoder Doctor issue identifier, to be
|
||||
// used here as key for the telemetry (counting infobar displays,
|
||||
// "Learn how" buttons clicks, and resolutions) and for the prefs used
|
||||
// to store at-issue formats.
|
||||
// - 'formats' contains a comma-separated list of formats (or key systems)
|
||||
// that suffer the issue. These are kept in a pref, which the backend
|
||||
// uses to later find when an issue is resolved.
|
||||
// - 'isSolved' is true when the notification actually indicates the
|
||||
// resolution of that issue, to be reported as telemetry.
|
||||
let {type, isSolved, decoderDoctorReportId, formats} = parsedData;
|
||||
type = type.toLowerCase();
|
||||
// Error out early on invalid ReportId
|
||||
if (!(/^\w+$/mi).test(decoderDoctorReportId)) {
|
||||
return
|
||||
}
|
||||
let title = gDecoderDoctorHandler.getLabelForNotificationBox(type);
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We keep the list of formats in prefs for the sake of the decoder itself,
|
||||
// which reads it to determine when issues get solved for these formats.
|
||||
// (Writing prefs from e10s content is now allowed.)
|
||||
let formatsPref = "media.decoder-doctor." + decoderDoctorReportId + ".formats";
|
||||
let buttonClickedPref = "media.decoder-doctor." + decoderDoctorReportId + ".button-clicked";
|
||||
let histogram =
|
||||
Services.telemetry.getKeyedHistogramById("DECODER_DOCTOR_INFOBAR_STATS");
|
||||
|
||||
let formatsInPref = Services.prefs.getPrefType(formatsPref) &&
|
||||
Services.prefs.getCharPref(formatsPref);
|
||||
|
||||
if (!isSolved) {
|
||||
if (!formats) {
|
||||
Cu.reportError("Malformed Decoder Doctor unsolved message with no formats");
|
||||
return;
|
||||
}
|
||||
if (!formatsInPref) {
|
||||
Services.prefs.setCharPref(formatsPref, formats);
|
||||
histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_SHOWN_FIRST);
|
||||
} else {
|
||||
// Split existing formats into an array of strings.
|
||||
let existing = formatsInPref.split(",").map(String.trim);
|
||||
// Keep given formats that were not already recorded.
|
||||
let newbies = formats.split(",").map(String.trim)
|
||||
.filter(x => !existing.includes(x));
|
||||
// And rewrite pref with the added new formats (if any).
|
||||
if (newbies.length) {
|
||||
Services.prefs.setCharPref(formatsPref,
|
||||
existing.concat(newbies).join(", "));
|
||||
}
|
||||
}
|
||||
histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_SHOWN);
|
||||
|
||||
let buttons = [];
|
||||
let sumo = gDecoderDoctorHandler.getSumoForLearnHowButton(type);
|
||||
if (sumo) {
|
||||
buttons.push({
|
||||
label: gNavigatorBundle.getString("decoder.noCodecs.button"),
|
||||
accessKey: gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
|
||||
callback() {
|
||||
let clickedInPref = Services.prefs.getPrefType(buttonClickedPref) &&
|
||||
Services.prefs.getBoolPref(buttonClickedPref);
|
||||
if (!clickedInPref) {
|
||||
Services.prefs.setBoolPref(buttonClickedPref, true);
|
||||
histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_CLICKED_FIRST);
|
||||
}
|
||||
histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_CLICKED);
|
||||
|
||||
let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
|
||||
openUILinkIn(baseURL + sumo, "tab");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
box.appendNotification(
|
||||
title,
|
||||
notificationId,
|
||||
"", // This uses the info icon as specified below.
|
||||
box.PRIORITY_INFO_LOW,
|
||||
buttons
|
||||
);
|
||||
} else if (formatsInPref) {
|
||||
// Issue is solved, and prefs haven't been cleared yet, meaning it's the
|
||||
// first time we get this resolution -> Clear prefs and report telemetry.
|
||||
Services.prefs.clearUserPref(formatsPref);
|
||||
Services.prefs.clearUserPref(buttonClickedPref);
|
||||
histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_SOLVED);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
window.getGroupMessageManager("browsers").addMessageListener("DecoderDoctor:Notification", gDecoderDoctorHandler);
|
||||
window.getGroupMessageManager("browsers").addMessageListener("EMEVideo:ContentMediaKeysRequest", gEMEHandler);
|
||||
window.addEventListener("unload", function() {
|
||||
window.getGroupMessageManager("browsers").removeMessageListener("EMEVideo:ContentMediaKeysRequest", gEMEHandler);
|
||||
window.getGroupMessageManager("browsers").removeMessageListener("DecoderDoctor:Notification", gDecoderDoctorHandler);
|
||||
}, false);
|
||||
|
|
@ -1,521 +0,0 @@
|
|||
# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
# 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/.
|
||||
|
||||
<menubar id="main-menubar"
|
||||
onpopupshowing="if (event.target.parentNode.parentNode == this &&
|
||||
!('@mozilla.org/widget/nativemenuservice;1' in Cc))
|
||||
this.setAttribute('openedwithkey',
|
||||
event.target.parentNode.openedWithKey);"
|
||||
style="border:0px;padding:0px;margin:0px;-moz-appearance:none">
|
||||
<menu id="file-menu" label="&fileMenu.label;"
|
||||
accesskey="&fileMenu.accesskey;">
|
||||
<menupopup id="menu_FilePopup"
|
||||
onpopupshowing="updateUserContextUIVisibility();">
|
||||
<menuitem id="menu_newNavigatorTab"
|
||||
label="&tabCmd.label;"
|
||||
command="cmd_newNavigatorTab"
|
||||
key="key_newNavigatorTab"
|
||||
accesskey="&tabCmd.accesskey;"/>
|
||||
<menu id="menu_newUserContext"
|
||||
label="&newUserContext.label;"
|
||||
accesskey="&newUserContext.accesskey;"
|
||||
hidden="true">
|
||||
<menupopup onpopupshowing="return createUserContextMenu(event);" />
|
||||
</menu>
|
||||
<menuitem id="menu_newNavigator"
|
||||
label="&newNavigatorCmd.label;"
|
||||
accesskey="&newNavigatorCmd.accesskey;"
|
||||
key="key_newNavigator"
|
||||
command="cmd_newNavigator"/>
|
||||
<menuitem id="menu_newPrivateWindow"
|
||||
label="&newPrivateWindow.label;"
|
||||
accesskey="&newPrivateWindow.accesskey;"
|
||||
command="Tools:PrivateBrowsing"
|
||||
key="key_privatebrowsing"/>
|
||||
#ifdef MAC_NON_BROWSER_WINDOW
|
||||
<menuitem id="menu_openLocation"
|
||||
label="&openLocationCmd.label;"
|
||||
command="Browser:OpenLocation"
|
||||
key="focusURLBar"/>
|
||||
#endif
|
||||
<menuitem id="menu_openFile"
|
||||
label="&openFileCmd.label;"
|
||||
command="Browser:OpenFile"
|
||||
key="openFileKb"
|
||||
accesskey="&openFileCmd.accesskey;"/>
|
||||
<menuitem id="menu_close"
|
||||
class="show-only-for-keyboard"
|
||||
label="&closeCmd.label;"
|
||||
key="key_close"
|
||||
accesskey="&closeCmd.accesskey;"
|
||||
command="cmd_close"/>
|
||||
<menuitem id="menu_closeWindow"
|
||||
class="show-only-for-keyboard"
|
||||
hidden="true"
|
||||
command="cmd_closeWindow"
|
||||
key="key_closeWindow"
|
||||
label="&closeWindow.label;"
|
||||
accesskey="&closeWindow.accesskey;"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_savePage"
|
||||
label="&savePageCmd.label;"
|
||||
accesskey="&savePageCmd.accesskey;"
|
||||
key="key_savePage"
|
||||
command="Browser:SavePage"/>
|
||||
<menuitem id="menu_sendLink"
|
||||
label="&emailPageCmd.label;"
|
||||
accesskey="&emailPageCmd.accesskey;"
|
||||
command="Browser:SendLink"/>
|
||||
<menuseparator/>
|
||||
#if !defined(MOZ_WIDGET_GTK)
|
||||
<menuitem id="menu_printSetup"
|
||||
label="&printSetupCmd.label;"
|
||||
accesskey="&printSetupCmd.accesskey;"
|
||||
command="cmd_pageSetup"/>
|
||||
#endif
|
||||
#ifndef XP_MACOSX
|
||||
<menuitem id="menu_printPreview"
|
||||
label="&printPreviewCmd.label;"
|
||||
accesskey="&printPreviewCmd.accesskey;"
|
||||
command="cmd_printPreview"/>
|
||||
#endif
|
||||
<menuitem id="menu_print"
|
||||
label="&printCmd.label;"
|
||||
accesskey="&printCmd.accesskey;"
|
||||
key="printKb"
|
||||
command="cmd_print"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="goOfflineMenuitem"
|
||||
label="&goOfflineCmd.label;"
|
||||
accesskey="&goOfflineCmd.accesskey;"
|
||||
type="checkbox"
|
||||
observes="workOfflineMenuitemState"
|
||||
oncommand="BrowserOffline.toggleOfflineStatus();"/>
|
||||
<menuitem id="menu_FileQuitItem"
|
||||
#ifdef XP_WIN
|
||||
label="&quitApplicationCmdWin2.label;"
|
||||
accesskey="&quitApplicationCmdWin2.accesskey;"
|
||||
#else
|
||||
#ifdef XP_MACOSX
|
||||
label="&quitApplicationCmdMac2.label;"
|
||||
#else
|
||||
label="&quitApplicationCmd.label;"
|
||||
accesskey="&quitApplicationCmd.accesskey;"
|
||||
#endif
|
||||
#ifdef XP_UNIX
|
||||
key="key_quitApplication"
|
||||
#endif
|
||||
#endif
|
||||
command="cmd_quitApplication"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="edit-menu" label="&editMenu.label;"
|
||||
accesskey="&editMenu.accesskey;">
|
||||
<menupopup id="menu_EditPopup"
|
||||
onpopupshowing="updateEditUIVisibility()"
|
||||
onpopuphidden="updateEditUIVisibility()">
|
||||
<menuitem id="menu_undo"
|
||||
label="&undoCmd.label;"
|
||||
key="key_undo"
|
||||
accesskey="&undoCmd.accesskey;"
|
||||
command="cmd_undo"/>
|
||||
<menuitem id="menu_redo"
|
||||
label="&redoCmd.label;"
|
||||
key="key_redo"
|
||||
accesskey="&redoCmd.accesskey;"
|
||||
command="cmd_redo"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_cut"
|
||||
label="&cutCmd.label;"
|
||||
key="key_cut"
|
||||
accesskey="&cutCmd.accesskey;"
|
||||
command="cmd_cut"/>
|
||||
<menuitem id="menu_copy"
|
||||
label="©Cmd.label;"
|
||||
key="key_copy"
|
||||
accesskey="©Cmd.accesskey;"
|
||||
command="cmd_copy"/>
|
||||
<menuitem id="menu_paste"
|
||||
label="&pasteCmd.label;"
|
||||
key="key_paste"
|
||||
accesskey="&pasteCmd.accesskey;"
|
||||
command="cmd_paste"/>
|
||||
<menuitem id="menu_delete"
|
||||
label="&deleteCmd.label;"
|
||||
key="key_delete"
|
||||
accesskey="&deleteCmd.accesskey;"
|
||||
command="cmd_delete"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_selectAll"
|
||||
label="&selectAllCmd.label;"
|
||||
key="key_selectAll"
|
||||
accesskey="&selectAllCmd.accesskey;"
|
||||
command="cmd_selectAll"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_find"
|
||||
label="&findOnCmd.label;"
|
||||
accesskey="&findOnCmd.accesskey;"
|
||||
key="key_find"
|
||||
command="cmd_find"/>
|
||||
<menuitem id="menu_findAgain"
|
||||
class="show-only-for-keyboard"
|
||||
label="&findAgainCmd.label;"
|
||||
accesskey="&findAgainCmd.accesskey;"
|
||||
key="key_findAgain"
|
||||
command="cmd_findAgain"/>
|
||||
<menuseparator hidden="true" id="textfieldDirection-separator"/>
|
||||
<menuitem id="textfieldDirection-swap"
|
||||
command="cmd_switchTextDirection"
|
||||
key="key_switchTextDirection"
|
||||
label="&bidiSwitchTextDirectionItem.label;"
|
||||
accesskey="&bidiSwitchTextDirectionItem.accesskey;"
|
||||
hidden="true"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="view-menu" label="&viewMenu.label;"
|
||||
accesskey="&viewMenu.accesskey;">
|
||||
<menupopup id="menu_viewPopup"
|
||||
onpopupshowing="updateCharacterEncodingMenuState();">
|
||||
<menu id="viewToolbarsMenu"
|
||||
label="&viewToolbarsMenu.label;"
|
||||
accesskey="&viewToolbarsMenu.accesskey;">
|
||||
<menupopup onpopupshowing="onViewToolbarsPopupShowing(event);">
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_customizeToolbars"
|
||||
label="&viewCustomizeToolbar.label;"
|
||||
accesskey="&viewCustomizeToolbar.accesskey;"
|
||||
command="cmd_CustomizeToolbars"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menu id="viewSidebarMenuMenu"
|
||||
label="&viewSidebarMenu.label;"
|
||||
accesskey="&viewSidebarMenu.accesskey;">
|
||||
<menupopup id="viewSidebarMenu">
|
||||
<menuitem id="menu_bookmarksSidebar"
|
||||
key="viewBookmarksSidebarKb"
|
||||
observes="viewBookmarksSidebar"/>
|
||||
<menuitem id="menu_historySidebar"
|
||||
key="key_gotoHistory"
|
||||
observes="viewHistorySidebar"
|
||||
label="&historyButton.label;"/>
|
||||
<menuitem id="menu_tabsSidebar"
|
||||
observes="viewTabsSidebar"
|
||||
label="&syncedTabs.sidebar.label;"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuseparator/>
|
||||
<menu id="viewFullZoomMenu" label="&fullZoom.label;"
|
||||
accesskey="&fullZoom.accesskey;"
|
||||
onpopupshowing="FullZoom.updateMenu();">
|
||||
<menupopup>
|
||||
<menuitem id="menu_zoomEnlarge"
|
||||
key="key_fullZoomEnlarge"
|
||||
label="&fullZoomEnlargeCmd.label;"
|
||||
accesskey="&fullZoomEnlargeCmd.accesskey;"
|
||||
command="cmd_fullZoomEnlarge"/>
|
||||
<menuitem id="menu_zoomReduce"
|
||||
key="key_fullZoomReduce"
|
||||
label="&fullZoomReduceCmd.label;"
|
||||
accesskey="&fullZoomReduceCmd.accesskey;"
|
||||
command="cmd_fullZoomReduce"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="menu_zoomReset"
|
||||
key="key_fullZoomReset"
|
||||
label="&fullZoomResetCmd.label;"
|
||||
accesskey="&fullZoomResetCmd.accesskey;"
|
||||
command="cmd_fullZoomReset"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="toggle_zoom"
|
||||
label="&fullZoomToggleCmd.label;"
|
||||
accesskey="&fullZoomToggleCmd.accesskey;"
|
||||
type="checkbox"
|
||||
command="cmd_fullZoomToggle"
|
||||
checked="false"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menu id="pageStyleMenu" label="&pageStyleMenu.label;"
|
||||
accesskey="&pageStyleMenu.accesskey;" observes="isImage">
|
||||
<menupopup onpopupshowing="gPageStyleMenu.fillPopup(this);">
|
||||
<menuitem id="menu_pageStyleNoStyle"
|
||||
label="&pageStyleNoStyle.label;"
|
||||
accesskey="&pageStyleNoStyle.accesskey;"
|
||||
oncommand="gPageStyleMenu.disableStyle();"
|
||||
type="radio"/>
|
||||
<menuitem id="menu_pageStylePersistentOnly"
|
||||
label="&pageStylePersistentOnly.label;"
|
||||
accesskey="&pageStylePersistentOnly.accesskey;"
|
||||
oncommand="gPageStyleMenu.switchStyleSheet('');"
|
||||
type="radio"
|
||||
checked="true"/>
|
||||
<menuseparator/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
#include browser-charsetmenu.inc
|
||||
<menuseparator/>
|
||||
#ifdef XP_MACOSX
|
||||
<menuitem id="enterFullScreenItem"
|
||||
accesskey="&enterFullScreenCmd.accesskey;"
|
||||
label="&enterFullScreenCmd.label;"
|
||||
key="key_fullScreen">
|
||||
<observes element="View:FullScreen" attribute="oncommand"/>
|
||||
<observes element="View:FullScreen" attribute="disabled"/>
|
||||
</menuitem>
|
||||
<menuitem id="exitFullScreenItem"
|
||||
accesskey="&exitFullScreenCmd.accesskey;"
|
||||
label="&exitFullScreenCmd.label;"
|
||||
key="key_fullScreen"
|
||||
hidden="true">
|
||||
<observes element="View:FullScreen" attribute="oncommand"/>
|
||||
<observes element="View:FullScreen" attribute="disabled"/>
|
||||
</menuitem>
|
||||
#else
|
||||
<menuitem id="fullScreenItem"
|
||||
accesskey="&fullScreenCmd.accesskey;"
|
||||
label="&fullScreenCmd.label;"
|
||||
key="key_fullScreen"
|
||||
type="checkbox"
|
||||
observes="View:FullScreen"/>
|
||||
#endif
|
||||
<menuitem id="menu_readerModeItem"
|
||||
observes="View:ReaderView"
|
||||
hidden="true"/>
|
||||
<menuitem id="menu_showAllTabs"
|
||||
hidden="true"
|
||||
accesskey="&showAllTabsCmd.accesskey;"
|
||||
label="&showAllTabsCmd.label;"
|
||||
command="Browser:ShowAllTabs"
|
||||
key="key_showAllTabs"/>
|
||||
<menuseparator hidden="true" id="documentDirection-separator"/>
|
||||
<menuitem id="documentDirection-swap"
|
||||
hidden="true"
|
||||
label="&bidiSwitchPageDirectionItem.label;"
|
||||
accesskey="&bidiSwitchPageDirectionItem.accesskey;"
|
||||
oncommand="gBrowser.selectedBrowser
|
||||
.messageManager
|
||||
.sendAsyncMessage('SwitchDocumentDirection');"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="history-menu"
|
||||
label="&historyMenu.label;"
|
||||
accesskey="&historyMenu.accesskey;">
|
||||
<menupopup id="goPopup"
|
||||
#ifndef XP_MACOSX
|
||||
placespopup="true"
|
||||
#endif
|
||||
oncommand="this.parentNode._placesView._onCommand(event);"
|
||||
onclick="checkForMiddleClick(this, event);"
|
||||
onpopupshowing="if (!this.parentNode._placesView)
|
||||
new HistoryMenu(event);"
|
||||
tooltip="bhTooltip"
|
||||
popupsinherittooltip="true">
|
||||
<menuitem id="menu_showAllHistory"
|
||||
label="&showAllHistoryCmd2.label;"
|
||||
#ifndef XP_MACOSX
|
||||
key="showAllHistoryKb"
|
||||
#endif
|
||||
command="Browser:ShowAllHistory"/>
|
||||
<menuitem id="sanitizeItem"
|
||||
label="&clearRecentHistory.label;"
|
||||
key="key_sanitize"
|
||||
command="Tools:Sanitize"/>
|
||||
<menuseparator id="sanitizeSeparator"/>
|
||||
<menuitem id="sync-tabs-menuitem"
|
||||
class="syncTabsMenuItem"
|
||||
label="&syncTabsMenu3.label;"
|
||||
oncommand="BrowserOpenSyncTabs();"
|
||||
hidden="true"/>
|
||||
<menuitem id="historyRestoreLastSession"
|
||||
label="&historyRestoreLastSession.label;"
|
||||
command="Browser:RestoreLastSession"/>
|
||||
<menu id="historyUndoMenu"
|
||||
class="recentlyClosedTabsMenu"
|
||||
label="&historyUndoMenu.label;"
|
||||
disabled="true">
|
||||
<menupopup id="historyUndoPopup"
|
||||
#ifndef XP_MACOSX
|
||||
placespopup="true"
|
||||
#endif
|
||||
onpopupshowing="document.getElementById('history-menu')._placesView.populateUndoSubmenu();"/>
|
||||
</menu>
|
||||
<menu id="historyUndoWindowMenu"
|
||||
class="recentlyClosedWindowsMenu"
|
||||
label="&historyUndoWindowMenu.label;"
|
||||
disabled="true">
|
||||
<menupopup id="historyUndoWindowPopup"
|
||||
#ifndef XP_MACOSX
|
||||
placespopup="true"
|
||||
#endif
|
||||
onpopupshowing="document.getElementById('history-menu')._placesView.populateUndoWindowSubmenu();"/>
|
||||
</menu>
|
||||
<menuseparator id="startHistorySeparator"
|
||||
class="hide-if-empty-places-result"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="bookmarksMenu"
|
||||
label="&bookmarksMenu.label;"
|
||||
accesskey="&bookmarksMenu.accesskey;"
|
||||
ondragenter="PlacesMenuDNDHandler.onDragEnter(event);"
|
||||
ondragover="PlacesMenuDNDHandler.onDragOver(event);"
|
||||
ondrop="PlacesMenuDNDHandler.onDrop(event);">
|
||||
<menupopup id="bookmarksMenuPopup"
|
||||
#ifndef XP_MACOSX
|
||||
placespopup="true"
|
||||
#endif
|
||||
context="placesContext"
|
||||
openInTabs="children"
|
||||
oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);"
|
||||
onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
|
||||
onpopupshowing="BookmarkingUI.onMainMenuPopupShowing(event);
|
||||
if (!this.parentNode._placesView)
|
||||
new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');"
|
||||
tooltip="bhTooltip" popupsinherittooltip="true">
|
||||
<menuitem id="bookmarksShowAll"
|
||||
label="&showAllBookmarks2.label;"
|
||||
command="Browser:ShowAllBookmarks"
|
||||
key="manBookmarkKb"/>
|
||||
<menuseparator id="organizeBookmarksSeparator"/>
|
||||
<menuitem id="menu_bookmarkThisPage"
|
||||
command="Browser:AddBookmarkAs"
|
||||
observes="bookmarkThisPageBroadcaster"
|
||||
key="addBookmarkAsKb"/>
|
||||
<menuitem id="subscribeToPageMenuitem"
|
||||
#ifndef XP_MACOSX
|
||||
class="menuitem-iconic"
|
||||
#endif
|
||||
label="&subscribeToPageMenuitem.label;"
|
||||
oncommand="return FeedHandler.subscribeToFeed(null, event);"
|
||||
onclick="checkForMiddleClick(this, event);"
|
||||
observes="singleFeedMenuitemState"/>
|
||||
<menu id="subscribeToPageMenupopup"
|
||||
#ifndef XP_MACOSX
|
||||
class="menu-iconic"
|
||||
#endif
|
||||
label="&subscribeToPageMenupopup.label;"
|
||||
observes="multipleFeedsMenuState">
|
||||
<menupopup id="subscribeToPageSubmenuMenupopup"
|
||||
onpopupshowing="return FeedHandler.buildFeedList(event.target);"
|
||||
oncommand="return FeedHandler.subscribeToFeed(null, event);"
|
||||
onclick="checkForMiddleClick(this, event);"/>
|
||||
</menu>
|
||||
<menuitem id="menu_bookmarkAllTabs"
|
||||
label="&addCurPagesCmd.label;"
|
||||
class="show-only-for-keyboard"
|
||||
command="Browser:BookmarkAllTabs"
|
||||
key="bookmarkAllTabsKb"/>
|
||||
<menuseparator/>
|
||||
<menuitem label="&recentBookmarks.label;"
|
||||
id="menu_recentBookmarks"
|
||||
disabled="true"/>
|
||||
<menuseparator id="bookmarksToolbarSeparator"/>
|
||||
<menu id="bookmarksToolbarFolderMenu"
|
||||
class="menu-iconic bookmark-item"
|
||||
label="&personalbarCmd.label;"
|
||||
container="true">
|
||||
<menupopup id="bookmarksToolbarFolderPopup"
|
||||
#ifndef XP_MACOSX
|
||||
placespopup="true"
|
||||
#endif
|
||||
context="placesContext"
|
||||
onpopupshowing="if (!this.parentNode._placesView)
|
||||
new PlacesMenu(event, 'place:folder=TOOLBAR');"/>
|
||||
</menu>
|
||||
<menu id="menu_unsortedBookmarks"
|
||||
class="menu-iconic bookmark-item"
|
||||
label="&otherBookmarksCmd.label;"
|
||||
container="true">
|
||||
<menupopup id="otherBookmarksFolderPopup"
|
||||
#ifndef XP_MACOSX
|
||||
placespopup="true"
|
||||
#endif
|
||||
context="placesContext"
|
||||
onpopupshowing="if (!this.parentNode._placesView)
|
||||
new PlacesMenu(event, 'place:folder=UNFILED_BOOKMARKS');"/>
|
||||
</menu>
|
||||
<menuseparator id="bookmarksMenuItemsSeparator"/>
|
||||
<!-- Bookmarks menu items -->
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
<menu id="tools-menu"
|
||||
label="&toolsMenu.label;"
|
||||
accesskey="&toolsMenu.accesskey;"
|
||||
onpopupshowing="mirrorShow(this)">
|
||||
<menupopup id="menu_ToolsPopup"
|
||||
# We have to use setTimeout() here to avoid a flickering menu bar when opening
|
||||
# the Tools menu, see bug 970769. This can be removed once we got rid of the
|
||||
# event loop spinning in Weave.Status._authManager.
|
||||
onpopupshowing="setTimeout(() => gSyncUI.updateUI());"
|
||||
>
|
||||
<menuitem id="menu_openDownloads"
|
||||
label="&downloads.label;"
|
||||
accesskey="&downloads.accesskey;"
|
||||
key="key_openDownloads"
|
||||
command="Tools:Downloads"/>
|
||||
<menuitem id="menu_openAddons"
|
||||
label="&addons.label;"
|
||||
accesskey="&addons.accesskey;"
|
||||
key="key_openAddons"
|
||||
command="Tools:Addons"/>
|
||||
|
||||
<!-- only one of sync-setup, sync-syncnowitem or sync-reauthitem will be showing at once -->
|
||||
<menuitem id="sync-setup"
|
||||
label="&syncSignIn.label;"
|
||||
accesskey="&syncSignIn.accesskey;"
|
||||
observes="sync-setup-state"
|
||||
oncommand="gSyncUI.openSetup(null, 'menubar')"/>
|
||||
<menuitem id="sync-syncnowitem"
|
||||
label="&syncSyncNowItem.label;"
|
||||
accesskey="&syncSyncNowItem.accesskey;"
|
||||
observes="sync-syncnow-state"
|
||||
oncommand="gSyncUI.doSync(event);"/>
|
||||
<menuitem id="sync-reauthitem"
|
||||
label="&syncReAuthItem.label;"
|
||||
accesskey="&syncReAuthItem.accesskey;"
|
||||
observes="sync-reauth-state"
|
||||
oncommand="gSyncUI.openSignInAgainPage('menubar');"/>
|
||||
<menuseparator id="devToolsSeparator"/>
|
||||
<menu id="webDeveloperMenu"
|
||||
label="&webDeveloperMenu.label;"
|
||||
accesskey="&webDeveloperMenu.accesskey;">
|
||||
<menupopup id="menuWebDeveloperPopup">
|
||||
<menuitem id="menu_pageSource"
|
||||
observes="devtoolsMenuBroadcaster_PageSource"
|
||||
accesskey="&pageSourceCmd.accesskey;"/>
|
||||
<menuitem id="javascriptConsole"
|
||||
observes="devtoolsMenuBroadcaster_ErrorConsole"
|
||||
accesskey="&errorConsoleCmd.accesskey;"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
<menuitem id="menu_pageInfo"
|
||||
accesskey="&pageInfoCmd.accesskey;"
|
||||
label="&pageInfoCmd.label;"
|
||||
#ifndef XP_WIN
|
||||
key="key_viewInfo"
|
||||
#endif
|
||||
command="View:PageInfo"/>
|
||||
<menu id="menu_mirrorTabCmd"
|
||||
hidden="true"
|
||||
accesskey="&mirrorTabCmd.accesskey;"
|
||||
label="&mirrorTabCmd.label;">
|
||||
<menupopup id="menu_mirrorTab-popup"
|
||||
onpopupshowing="populateMirrorTabMenu(this)"/>
|
||||
</menu>
|
||||
<menuseparator id="prefSep"/>
|
||||
<menuitem id="menu_preferences"
|
||||
label="&preferencesCmd2.label;"
|
||||
accesskey="&preferencesCmd2.accesskey;"
|
||||
oncommand="openPreferences();"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
<menu id="windowMenu" />
|
||||
#endif
|
||||
<menu id="helpMenu" />
|
||||
</menubar>
|
||||
|
|
@ -1,516 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
var gPluginHandler = {
|
||||
PREF_SESSION_PERSIST_MINUTES: "plugin.sessionPermissionNow.intervalInMinutes",
|
||||
PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays",
|
||||
MESSAGES: [
|
||||
"PluginContent:ShowClickToPlayNotification",
|
||||
"PluginContent:RemoveNotification",
|
||||
"PluginContent:UpdateHiddenPluginUI",
|
||||
"PluginContent:HideNotificationBar",
|
||||
"PluginContent:InstallSinglePlugin",
|
||||
"PluginContent:ShowPluginCrashedNotification",
|
||||
"PluginContent:SubmitReport",
|
||||
"PluginContent:LinkClickCallback",
|
||||
],
|
||||
|
||||
init: function () {
|
||||
const mm = window.messageManager;
|
||||
for (let msg of this.MESSAGES) {
|
||||
mm.addMessageListener(msg, this);
|
||||
}
|
||||
window.addEventListener("unload", this);
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
const mm = window.messageManager;
|
||||
for (let msg of this.MESSAGES) {
|
||||
mm.removeMessageListener(msg, this);
|
||||
}
|
||||
window.removeEventListener("unload", this);
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
if (event.type == "unload") {
|
||||
this.uninit();
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage: function (msg) {
|
||||
switch (msg.name) {
|
||||
case "PluginContent:ShowClickToPlayNotification":
|
||||
this.showClickToPlayNotification(msg.target, msg.data.plugins, msg.data.showNow,
|
||||
msg.principal, msg.data.location);
|
||||
break;
|
||||
case "PluginContent:RemoveNotification":
|
||||
this.removeNotification(msg.target, msg.data.name);
|
||||
break;
|
||||
case "PluginContent:UpdateHiddenPluginUI":
|
||||
this.updateHiddenPluginUI(msg.target, msg.data.haveInsecure, msg.data.actions,
|
||||
msg.principal, msg.data.location);
|
||||
break;
|
||||
case "PluginContent:HideNotificationBar":
|
||||
this.hideNotificationBar(msg.target, msg.data.name);
|
||||
break;
|
||||
case "PluginContent:InstallSinglePlugin":
|
||||
this.installSinglePlugin(msg.data.pluginInfo);
|
||||
break;
|
||||
case "PluginContent:ShowPluginCrashedNotification":
|
||||
this.showPluginCrashedNotification(msg.target, msg.data.messageString,
|
||||
msg.data.pluginID);
|
||||
break;
|
||||
case "PluginContent:SubmitReport":
|
||||
// Nothing to do here
|
||||
break;
|
||||
case "PluginContent:LinkClickCallback":
|
||||
switch (msg.data.name) {
|
||||
case "managePlugins":
|
||||
case "openHelpPage":
|
||||
case "openPluginUpdatePage":
|
||||
this[msg.data.name].call(this, msg.data.pluginTag);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Cu.reportError("gPluginHandler did not expect to handle message " + msg.name);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// Callback for user clicking on a disabled plugin
|
||||
managePlugins: function () {
|
||||
BrowserOpenAddonsMgr("addons://list/plugin");
|
||||
},
|
||||
|
||||
// Callback for user clicking on the link in a click-to-play plugin
|
||||
// (where the plugin has an update)
|
||||
openPluginUpdatePage: function(pluginTag) {
|
||||
let url = Services.blocklist.getPluginInfoURL(pluginTag);
|
||||
if (!url) {
|
||||
url = Services.blocklist.getPluginBlocklistURL(pluginTag);
|
||||
}
|
||||
openUILinkIn(url, "tab");
|
||||
},
|
||||
|
||||
submitReport: function submitReport(runID, keyVals, submitURLOptIn) {
|
||||
/*** STUB ***/
|
||||
return;
|
||||
},
|
||||
|
||||
// Callback for user clicking a "reload page" link
|
||||
reloadPage: function (browser) {
|
||||
browser.reload();
|
||||
},
|
||||
|
||||
// Callback for user clicking the help icon
|
||||
openHelpPage: function () {
|
||||
openHelpLink("plugin-crashed", false);
|
||||
},
|
||||
|
||||
_clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) {
|
||||
if (event == "showing") {
|
||||
Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_SHOWN")
|
||||
.add(!this.options.primaryPlugin);
|
||||
// Histograms always start at 0, even though our data starts at 1
|
||||
let histogramCount = this.options.pluginData.size - 1;
|
||||
if (histogramCount > 4) {
|
||||
histogramCount = 4;
|
||||
}
|
||||
Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_PLUGIN_COUNT")
|
||||
.add(histogramCount);
|
||||
}
|
||||
else if (event == "dismissed") {
|
||||
// Once the popup is dismissed, clicking the icon should show the full
|
||||
// list again
|
||||
this.options.primaryPlugin = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called from the plugin doorhanger to set the new permissions for a plugin
|
||||
* and activate plugins if necessary.
|
||||
* aNewState should be either "allownow" "allowalways" or "block"
|
||||
*/
|
||||
_updatePluginPermission: function (aNotification, aPluginInfo, aNewState) {
|
||||
let permission;
|
||||
let expireType;
|
||||
let expireTime;
|
||||
let histogram =
|
||||
Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_USER_ACTION");
|
||||
|
||||
// Update the permission manager.
|
||||
// Also update the current state of pluginInfo.fallbackType so that
|
||||
// subsequent opening of the notification shows the current state.
|
||||
switch (aNewState) {
|
||||
case "allownow":
|
||||
permission = Ci.nsIPermissionManager.ALLOW_ACTION;
|
||||
expireType = Ci.nsIPermissionManager.EXPIRE_SESSION;
|
||||
expireTime = Date.now() + Services.prefs.getIntPref(this.PREF_SESSION_PERSIST_MINUTES) * 60 * 1000;
|
||||
histogram.add(0);
|
||||
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
|
||||
break;
|
||||
|
||||
case "allowalways":
|
||||
permission = Ci.nsIPermissionManager.ALLOW_ACTION;
|
||||
expireType = Ci.nsIPermissionManager.EXPIRE_TIME;
|
||||
expireTime = Date.now() +
|
||||
Services.prefs.getIntPref(this.PREF_PERSISTENT_DAYS) * 24 * 60 * 60 * 1000;
|
||||
histogram.add(1);
|
||||
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
|
||||
break;
|
||||
|
||||
case "block":
|
||||
permission = Ci.nsIPermissionManager.PROMPT_ACTION;
|
||||
expireType = Ci.nsIPermissionManager.EXPIRE_NEVER;
|
||||
expireTime = 0;
|
||||
histogram.add(2);
|
||||
switch (aPluginInfo.blocklistState) {
|
||||
case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
|
||||
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE;
|
||||
break;
|
||||
case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
|
||||
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
|
||||
break;
|
||||
default:
|
||||
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY;
|
||||
}
|
||||
break;
|
||||
|
||||
// In case a plugin has already been allowed in another tab, the "continue allowing" button
|
||||
// shouldn't change any permissions but should run the plugin-enablement code below.
|
||||
case "continue":
|
||||
aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
|
||||
break;
|
||||
default:
|
||||
Cu.reportError(Error("Unexpected plugin state: " + aNewState));
|
||||
return;
|
||||
}
|
||||
|
||||
let browser = aNotification.browser;
|
||||
if (aNewState != "continue") {
|
||||
let principal = aNotification.options.principal;
|
||||
Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString,
|
||||
permission, expireType, expireTime);
|
||||
aPluginInfo.pluginPermissionType = expireType;
|
||||
}
|
||||
|
||||
browser.messageManager.sendAsyncMessage("BrowserPlugins:ActivatePlugins", {
|
||||
pluginInfo: aPluginInfo,
|
||||
newState: aNewState,
|
||||
});
|
||||
},
|
||||
|
||||
showClickToPlayNotification: function (browser, plugins, showNow,
|
||||
principal, location) {
|
||||
// It is possible that we've received a message from the frame script to show
|
||||
// a click to play notification for a principal that no longer matches the one
|
||||
// that the browser's content now has assigned (ie, the browser has browsed away
|
||||
// after the message was sent, but before the message was received). In that case,
|
||||
// we should just ignore the message.
|
||||
if (!principal.equals(browser.contentPrincipal)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Data URIs, when linked to from some page, inherit the principal of that
|
||||
// page. That means that we also need to compare the actual locations to
|
||||
// ensure we aren't getting a message from a Data URI that we're no longer
|
||||
// looking at.
|
||||
let receivedURI = BrowserUtils.makeURI(location);
|
||||
if (!browser.documentURI.equalsExceptRef(receivedURI)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let notification = PopupNotifications.getNotification("click-to-play-plugins", browser);
|
||||
|
||||
// If this is a new notification, create a pluginData map, otherwise append
|
||||
let pluginData;
|
||||
if (notification) {
|
||||
pluginData = notification.options.pluginData;
|
||||
} else {
|
||||
pluginData = new Map();
|
||||
}
|
||||
|
||||
for (var pluginInfo of plugins) {
|
||||
if (pluginData.has(pluginInfo.permissionString)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If a block contains an infoURL, we should always prefer that to the default
|
||||
// URL that we construct in-product, even for other blocklist types.
|
||||
let url = Services.blocklist.getPluginInfoURL(pluginInfo.pluginTag);
|
||||
|
||||
if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
|
||||
if (!url) {
|
||||
url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
|
||||
}
|
||||
}
|
||||
else {
|
||||
url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "clicktoplay";
|
||||
}
|
||||
pluginInfo.detailsLink = url;
|
||||
|
||||
pluginData.set(pluginInfo.permissionString, pluginInfo);
|
||||
}
|
||||
|
||||
let primaryPluginPermission = null;
|
||||
if (showNow) {
|
||||
primaryPluginPermission = plugins[0].permissionString;
|
||||
}
|
||||
|
||||
if (notification) {
|
||||
// Don't modify the notification UI while it's on the screen, that would be
|
||||
// jumpy and might allow clickjacking.
|
||||
if (showNow) {
|
||||
notification.options.primaryPlugin = primaryPluginPermission;
|
||||
notification.reshow();
|
||||
browser.messageManager.sendAsyncMessage("BrowserPlugins:NotificationShown");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let options = {
|
||||
dismissed: !showNow,
|
||||
eventCallback: this._clickToPlayNotificationEventCallback,
|
||||
primaryPlugin: primaryPluginPermission,
|
||||
pluginData: pluginData,
|
||||
principal: principal,
|
||||
};
|
||||
PopupNotifications.show(browser, "click-to-play-plugins",
|
||||
"", "plugins-notification-icon",
|
||||
null, null, options);
|
||||
browser.messageManager.sendAsyncMessage("BrowserPlugins:NotificationShown");
|
||||
},
|
||||
|
||||
removeNotification: function (browser, name) {
|
||||
let notification = PopupNotifications.getNotification(name, browser);
|
||||
if (notification)
|
||||
PopupNotifications.remove(notification);
|
||||
},
|
||||
|
||||
hideNotificationBar: function (browser, name) {
|
||||
let notificationBox = gBrowser.getNotificationBox(browser);
|
||||
let notification = notificationBox.getNotificationWithValue(name);
|
||||
if (notification)
|
||||
notificationBox.removeNotification(notification, true);
|
||||
},
|
||||
|
||||
updateHiddenPluginUI: function (browser, haveInsecure, actions,
|
||||
principal, location) {
|
||||
let origin = principal.originNoSuffix;
|
||||
|
||||
// It is possible that we've received a message from the frame script to show
|
||||
// the hidden plugin notification for a principal that no longer matches the one
|
||||
// that the browser's content now has assigned (ie, the browser has browsed away
|
||||
// after the message was sent, but before the message was received). In that case,
|
||||
// we should just ignore the message.
|
||||
if (!principal.equals(browser.contentPrincipal)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Data URIs, when linked to from some page, inherit the principal of that
|
||||
// page. That means that we also need to compare the actual locations to
|
||||
// ensure we aren't getting a message from a Data URI that we're no longer
|
||||
// looking at.
|
||||
let receivedURI = BrowserUtils.makeURI(location);
|
||||
if (!browser.documentURI.equalsExceptRef(receivedURI)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up the icon
|
||||
document.getElementById("plugins-notification-icon").classList.
|
||||
toggle("plugin-blocked", haveInsecure);
|
||||
|
||||
// Now configure the notification bar
|
||||
let notificationBox = gBrowser.getNotificationBox(browser);
|
||||
|
||||
function hideNotification() {
|
||||
let n = notificationBox.getNotificationWithValue("plugin-hidden");
|
||||
if (n) {
|
||||
notificationBox.removeNotification(n, true);
|
||||
}
|
||||
}
|
||||
|
||||
// There are three different cases when showing an infobar:
|
||||
// 1. A single type of plugin is hidden on the page. Show the UI for that
|
||||
// plugin.
|
||||
// 2a. Multiple types of plugins are hidden on the page. Show the multi-UI
|
||||
// with the vulnerable styling.
|
||||
// 2b. Multiple types of plugins are hidden on the page, but none are
|
||||
// vulnerable. Show the nonvulnerable multi-UI.
|
||||
function showNotification() {
|
||||
let n = notificationBox.getNotificationWithValue("plugin-hidden");
|
||||
if (n) {
|
||||
// If something is already shown, just keep it
|
||||
return;
|
||||
}
|
||||
|
||||
Services.telemetry.getHistogramById("PLUGINS_INFOBAR_SHOWN").
|
||||
add(true);
|
||||
|
||||
let message;
|
||||
// Icons set directly cannot be manipulated using moz-image-region, so
|
||||
// we use CSS classes instead.
|
||||
let brand = document.getElementById("bundle_brand").getString("brandShortName");
|
||||
|
||||
if (actions.length == 1) {
|
||||
let pluginInfo = actions[0];
|
||||
let pluginName = pluginInfo.pluginName;
|
||||
|
||||
switch (pluginInfo.fallbackType) {
|
||||
case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
|
||||
message = gNavigatorBundle.getFormattedString(
|
||||
"pluginActivateNew.message",
|
||||
[pluginName, origin]);
|
||||
break;
|
||||
case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
|
||||
message = gNavigatorBundle.getFormattedString(
|
||||
"pluginActivateOutdated.message",
|
||||
[pluginName, origin, brand]);
|
||||
break;
|
||||
case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
|
||||
message = gNavigatorBundle.getFormattedString(
|
||||
"pluginActivateVulnerable.message",
|
||||
[pluginName, origin, brand]);
|
||||
}
|
||||
} else {
|
||||
// Multi-plugin
|
||||
message = gNavigatorBundle.getFormattedString(
|
||||
"pluginActivateMultiple.message", [origin]);
|
||||
}
|
||||
|
||||
let buttons = [
|
||||
{
|
||||
label: gNavigatorBundle.getString("pluginContinueBlocking.label"),
|
||||
accessKey: gNavigatorBundle.getString("pluginContinueBlocking.accesskey"),
|
||||
callback: function() {
|
||||
Services.telemetry.getHistogramById("PLUGINS_INFOBAR_BLOCK").
|
||||
add(true);
|
||||
|
||||
Services.perms.addFromPrincipal(principal,
|
||||
"plugin-hidden-notification",
|
||||
Services.perms.DENY_ACTION);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: gNavigatorBundle.getString("pluginActivateTrigger.label"),
|
||||
accessKey: gNavigatorBundle.getString("pluginActivateTrigger.accesskey"),
|
||||
callback: function() {
|
||||
Services.telemetry.getHistogramById("PLUGINS_INFOBAR_ALLOW").
|
||||
add(true);
|
||||
|
||||
let curNotification =
|
||||
PopupNotifications.getNotification("click-to-play-plugins",
|
||||
browser);
|
||||
if (curNotification) {
|
||||
curNotification.reshow();
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
n = notificationBox.
|
||||
appendNotification(message, "plugin-hidden", null,
|
||||
notificationBox.PRIORITY_INFO_HIGH, buttons);
|
||||
if (haveInsecure) {
|
||||
n.classList.add('pluginVulnerable');
|
||||
}
|
||||
}
|
||||
|
||||
if (actions.length == 0) {
|
||||
hideNotification();
|
||||
} else {
|
||||
let notificationPermission = Services.perms.testPermissionFromPrincipal(
|
||||
principal, "plugin-hidden-notification");
|
||||
if (notificationPermission == Ci.nsIPermissionManager.DENY_ACTION) {
|
||||
hideNotification();
|
||||
} else {
|
||||
showNotification();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
contextMenuCommand: function (browser, plugin, command) {
|
||||
browser.messageManager.sendAsyncMessage("BrowserPlugins:ContextMenuCommand",
|
||||
{ command: command }, { plugin: plugin });
|
||||
},
|
||||
|
||||
// Crashed-plugin observer. Notified once per plugin crash, before events
|
||||
// are dispatched to individual plugin instances.
|
||||
NPAPIPluginCrashed : function(subject, topic, data) {
|
||||
let propertyBag = subject;
|
||||
if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
|
||||
!(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
|
||||
!propertyBag.hasKey("runID") ||
|
||||
!propertyBag.hasKey("pluginName")) {
|
||||
Cu.reportError("A NPAPI plugin crashed, but the properties of this plugin " +
|
||||
"cannot be read.");
|
||||
return;
|
||||
}
|
||||
|
||||
let runID = propertyBag.getPropertyAsUint32("runID");
|
||||
let uglyPluginName = propertyBag.getPropertyAsAString("pluginName");
|
||||
let pluginName = BrowserUtils.makeNicePluginName(uglyPluginName);
|
||||
let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
|
||||
|
||||
// If we don't have a minidumpID, we can't (or didn't) submit anything.
|
||||
// This can happen if the plugin is killed from the task manager.
|
||||
let state = "noSubmit";
|
||||
|
||||
let mm = window.getGroupMessageManager("browsers");
|
||||
mm.broadcastAsyncMessage("BrowserPlugins:NPAPIPluginProcessCrashed",
|
||||
{ pluginName, runID, state });
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows a plugin-crashed notification bar for a browser that has had an
|
||||
* invisiable NPAPI plugin crash, or a GMP plugin crash.
|
||||
*
|
||||
* @param browser
|
||||
* The browser to show the notification for.
|
||||
* @param messageString
|
||||
* The string to put in the notification bar
|
||||
* @param pluginID
|
||||
* The unique-per-process identifier for the NPAPI plugin or GMP.
|
||||
* For a GMP, this is the pluginID. For NPAPI plugins (where "pluginID"
|
||||
* means something different), this is the runID.
|
||||
*/
|
||||
showPluginCrashedNotification: function (browser, messageString, pluginID) {
|
||||
// If there's already an existing notification bar, don't do anything.
|
||||
let notificationBox = gBrowser.getNotificationBox(browser);
|
||||
let notification = notificationBox.getNotificationWithValue("plugin-crashed");
|
||||
if (notification) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure the notification bar
|
||||
let priority = notificationBox.PRIORITY_WARNING_MEDIUM;
|
||||
let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png";
|
||||
let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label");
|
||||
let reloadKey = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey");
|
||||
|
||||
let buttons = [{
|
||||
label: reloadLabel,
|
||||
accessKey: reloadKey,
|
||||
popup: null,
|
||||
callback: function() { browser.reload(); },
|
||||
}];
|
||||
|
||||
notification = notificationBox.appendNotification(messageString, "plugin-crashed",
|
||||
iconURL, priority, buttons);
|
||||
|
||||
// Add the "learn more" link.
|
||||
let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
let link = notification.ownerDocument.createElementNS(XULNS, "label");
|
||||
link.className = "text-link";
|
||||
link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore"));
|
||||
let crashurl = formatURL("app.support.baseURL", true);
|
||||
crashurl += "plugin-crashed-notificationbar";
|
||||
link.href = crashurl;
|
||||
let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
|
||||
description.appendChild(link);
|
||||
},
|
||||
};
|
||||
|
||||
gPluginHandler.init();
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/**
|
||||
* If the user has opted into blocking refresh and redirect attempts by
|
||||
* default, this handles showing the notification to the user which
|
||||
* gives them the option to let the refresh or redirect proceed.
|
||||
*/
|
||||
var RefreshBlocker = {
|
||||
init() {
|
||||
gBrowser.addEventListener("RefreshBlocked", this);
|
||||
},
|
||||
|
||||
uninit() {
|
||||
gBrowser.removeEventListener("RefreshBlocked", this);
|
||||
},
|
||||
|
||||
handleEvent: function(event) {
|
||||
if (event.type == "RefreshBlocked") {
|
||||
this.block(event.originalTarget, event.detail);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows the blocked refresh / redirect notification for some browser.
|
||||
*
|
||||
* @param browser (<xul:browser>)
|
||||
* The browser that had the refresh blocked. This will be the browser
|
||||
* for which we'll show the notification on.
|
||||
* @param data (object)
|
||||
* An object with the following properties:
|
||||
*
|
||||
* URI (string)
|
||||
* The URI that a page is attempting to refresh or redirect to.
|
||||
*
|
||||
* delay (int)
|
||||
* The delay (in milliseconds) before the page was going to reload
|
||||
* or redirect.
|
||||
*
|
||||
* sameURI (bool)
|
||||
* true if we're refreshing the page. false if we're redirecting.
|
||||
*
|
||||
* outerWindowID (int)
|
||||
* The outerWindowID of the frame that requested the refresh or
|
||||
* redirect.
|
||||
*/
|
||||
block(browser, data) {
|
||||
let brandBundle = document.getElementById("bundle_brand");
|
||||
let brandShortName = brandBundle.getString("brandShortName");
|
||||
let message =
|
||||
gNavigatorBundle.getFormattedString(data.sameURI ? "refreshBlocked.refreshLabel"
|
||||
: "refreshBlocked.redirectLabel",
|
||||
[brandShortName]);
|
||||
|
||||
let notificationBox = gBrowser.getNotificationBox(browser);
|
||||
let notification = notificationBox.getNotificationWithValue("refresh-blocked");
|
||||
|
||||
if (notification) {
|
||||
notification.label = message;
|
||||
} else {
|
||||
let refreshButtonText =
|
||||
gNavigatorBundle.getString("refreshBlocked.goButton");
|
||||
let refreshButtonAccesskey =
|
||||
gNavigatorBundle.getString("refreshBlocked.goButton.accesskey");
|
||||
|
||||
let buttons = [{
|
||||
label: refreshButtonText,
|
||||
accessKey: refreshButtonAccesskey,
|
||||
callback: function (notification, button) {
|
||||
if (browser.messageManager) {
|
||||
browser.messageManager.sendAsyncMessage("RefreshBlocker:Refresh", data);
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
notificationBox.appendNotification(message, "refresh-blocked",
|
||||
"chrome://browser/skin/Info.png",
|
||||
notificationBox.PRIORITY_INFO_MEDIUM,
|
||||
buttons);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -1,48 +0,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/. */
|
||||
|
||||
var gSafeBrowsing = {
|
||||
|
||||
setReportPhishingMenu: function() {
|
||||
// In order to detect whether or not we're at the phishing warning
|
||||
// page, we have to check the documentURI instead of the currentURI.
|
||||
// This is because when the DocShell loads an error page, the
|
||||
// currentURI stays at the original target, while the documentURI
|
||||
// will point to the internal error page we loaded instead.
|
||||
var docURI = gBrowser.selectedBrowser.documentURI;
|
||||
var isPhishingPage =
|
||||
docURI && docURI.spec.startsWith("about:blocked?e=deceptiveBlocked");
|
||||
|
||||
// Show/hide the appropriate menu item.
|
||||
document.getElementById("menu_HelpPopup_reportPhishingtoolmenu")
|
||||
.hidden = isPhishingPage;
|
||||
document.getElementById("menu_HelpPopup_reportPhishingErrortoolmenu")
|
||||
.hidden = !isPhishingPage;
|
||||
|
||||
var broadcasterId = isPhishingPage
|
||||
? "reportPhishingErrorBroadcaster"
|
||||
: "reportPhishingBroadcaster";
|
||||
|
||||
var broadcaster = document.getElementById(broadcasterId);
|
||||
if (!broadcaster)
|
||||
return;
|
||||
|
||||
// Now look at the currentURI to learn which page we were trying
|
||||
// to browse to.
|
||||
let uri = gBrowser.currentURI;
|
||||
if (uri && (uri.schemeIs("http") || uri.schemeIs("https")))
|
||||
broadcaster.removeAttribute("disabled");
|
||||
else
|
||||
broadcaster.setAttribute("disabled", true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to report a phishing page or a false positive
|
||||
* @param name String One of "Phish", "Error", "Malware" or "MalwareError"
|
||||
* @return String the report phishing URL.
|
||||
*/
|
||||
getReportURL: function(name) {
|
||||
return SafeBrowsing.getReportURL(name, gBrowser.currentURI);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,379 +0,0 @@
|
|||
# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
# 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/.
|
||||
|
||||
#ifdef XP_UNIX
|
||||
#ifndef XP_MACOSX
|
||||
#define XP_GNOME 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
<stringbundleset id="stringbundleset">
|
||||
<stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
|
||||
<stringbundle id="bundle_shell" src="chrome://browser/locale/shellservice.properties"/>
|
||||
<stringbundle id="bundle_preferences" src="chrome://browser/locale/preferences/preferences.properties"/>
|
||||
</stringbundleset>
|
||||
|
||||
<commandset id="mainCommandSet">
|
||||
<command id="cmd_newNavigator" oncommand="OpenBrowserWindow()" reserved="true"/>
|
||||
<command id="cmd_handleBackspace" oncommand="BrowserHandleBackspace();" />
|
||||
<command id="cmd_handleShiftBackspace" oncommand="BrowserHandleShiftBackspace();" />
|
||||
|
||||
<command id="cmd_newNavigatorTab" oncommand="BrowserOpenTab(event);" reserved="true"/>
|
||||
<command id="cmd_newNavigatorTabNoEvent" oncommand="BrowserOpenTab();" reserved="true"/>
|
||||
<command id="Browser:OpenFile" oncommand="BrowserOpenFileWindow();"/>
|
||||
<command id="Browser:SavePage" oncommand="saveBrowser(gBrowser.selectedBrowser);"/>
|
||||
|
||||
<command id="Browser:SendLink"
|
||||
oncommand="MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);"/>
|
||||
|
||||
<command id="cmd_pageSetup" oncommand="PrintUtils.showPageSetup();"/>
|
||||
<command id="cmd_print" oncommand="PrintUtils.printWindow(window.gBrowser.selectedBrowser.outerWindowID, window.gBrowser.selectedBrowser);"/>
|
||||
<command id="cmd_printPreview" oncommand="PrintUtils.printPreview(PrintPreviewListener);"/>
|
||||
<command id="cmd_close" oncommand="BrowserCloseTabOrWindow()" reserved="true"/>
|
||||
<command id="cmd_closeWindow" oncommand="BrowserTryToCloseWindow()" reserved="true"/>
|
||||
<command id="cmd_toggleMute" oncommand="gBrowser.selectedTab.toggleMuteAudio()"/>
|
||||
<command id="cmd_CustomizeToolbars" oncommand="BrowserCustomizeToolbar()"/>
|
||||
<command id="cmd_quitApplication" oncommand="goQuitApplication()" reserved="true"/>
|
||||
|
||||
|
||||
<commandset id="editMenuCommands"/>
|
||||
|
||||
<command id="View:PageSource" oncommand="BrowserViewSource(window.gBrowser.selectedBrowser);" observes="canViewSource"/>
|
||||
<command id="View:PageInfo" oncommand="BrowserPageInfo();"/>
|
||||
<command id="View:FullScreen" oncommand="BrowserFullScreen();"/>
|
||||
<command id="View:ReaderView" oncommand="ReaderParent.toggleReaderMode(event);"/>
|
||||
<command id="cmd_find"
|
||||
oncommand="gFindBar.onFindCommand();"
|
||||
observes="isImage"/>
|
||||
<command id="cmd_findAgain"
|
||||
oncommand="gFindBar.onFindAgainCommand(false);"
|
||||
observes="isImage"/>
|
||||
<command id="cmd_findPrevious"
|
||||
oncommand="gFindBar.onFindAgainCommand(true);"
|
||||
observes="isImage"/>
|
||||
#ifdef XP_MACOSX
|
||||
<command id="cmd_findSelection" oncommand="gFindBar.onFindSelectionCommand();"/>
|
||||
#endif
|
||||
<!-- work-around bug 392512 -->
|
||||
<command id="Browser:AddBookmarkAs"
|
||||
oncommand="PlacesCommandHook.bookmarkCurrentPage(true, PlacesUtils.bookmarksMenuFolderId);"/>
|
||||
<!-- The command disabled state must be manually updated through
|
||||
PlacesCommandHook.updateBookmarkAllTabsCommand() -->
|
||||
<command id="Browser:BookmarkAllTabs"
|
||||
oncommand="PlacesCommandHook.bookmarkCurrentPages();"/>
|
||||
<command id="Browser:Home" oncommand="BrowserHome();"/>
|
||||
<command id="Browser:Back" oncommand="BrowserBack();" disabled="true"/>
|
||||
<command id="Browser:BackOrBackDuplicate" oncommand="BrowserBack(event);" disabled="true">
|
||||
<observes element="Browser:Back" attribute="disabled"/>
|
||||
</command>
|
||||
<command id="Browser:Forward" oncommand="BrowserForward();" disabled="true"/>
|
||||
<command id="Browser:ForwardOrForwardDuplicate" oncommand="BrowserForward(event);" disabled="true">
|
||||
<observes element="Browser:Forward" attribute="disabled"/>
|
||||
</command>
|
||||
<command id="Browser:Stop" oncommand="BrowserStop();" disabled="true"/>
|
||||
<command id="Browser:Reload" oncommand="if (event.shiftKey) BrowserReloadSkipCache(); else BrowserReload()" disabled="true"/>
|
||||
<command id="Browser:ReloadOrDuplicate" oncommand="BrowserReloadOrDuplicate(event)" disabled="true">
|
||||
<observes element="Browser:Reload" attribute="disabled"/>
|
||||
</command>
|
||||
<command id="Browser:ReloadSkipCache" oncommand="BrowserReloadSkipCache()" disabled="true">
|
||||
<observes element="Browser:Reload" attribute="disabled"/>
|
||||
</command>
|
||||
<command id="Browser:NextTab" oncommand="gBrowser.tabContainer.advanceSelectedTab(1, true);" reserved="true"/>
|
||||
<command id="Browser:PrevTab" oncommand="gBrowser.tabContainer.advanceSelectedTab(-1, true);" reserved="true"/>
|
||||
<command id="Browser:ShowAllTabs" oncommand="allTabs.open();"/>
|
||||
<command id="cmd_fullZoomReduce" oncommand="FullZoom.reduce()"/>
|
||||
<command id="cmd_fullZoomEnlarge" oncommand="FullZoom.enlarge()"/>
|
||||
<command id="cmd_fullZoomReset" oncommand="FullZoom.reset()"/>
|
||||
<command id="cmd_fullZoomToggle" oncommand="ZoomManager.toggleZoom();"/>
|
||||
<command id="cmd_gestureRotateLeft" oncommand="gGestureSupport.rotate(event.sourceEvent)"/>
|
||||
<command id="cmd_gestureRotateRight" oncommand="gGestureSupport.rotate(event.sourceEvent)"/>
|
||||
<command id="cmd_gestureRotateEnd" oncommand="gGestureSupport.rotateEnd()"/>
|
||||
<command id="Browser:OpenLocation" oncommand="openLocation();"/>
|
||||
<command id="Browser:RestoreLastSession" oncommand="restoreLastSession();" disabled="true"/>
|
||||
<command id="Browser:NewUserContextTab" oncommand="openNewUserContextTab(event.sourceEvent);" reserved="true"/>
|
||||
<command id="Browser:OpenAboutContainers" oncommand="openPreferences('paneContainers');"/>
|
||||
|
||||
<command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
|
||||
<command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
|
||||
<command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
|
||||
<command id="Tools:ErrorConsole" oncommand="toJavaScriptConsole()"/>
|
||||
<command id="Tools:Sanitize"
|
||||
oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
|
||||
<command id="Tools:PrivateBrowsing"
|
||||
oncommand="OpenBrowserWindow({private: true});" reserved="true"/>
|
||||
<command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
|
||||
<command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
|
||||
</commandset>
|
||||
|
||||
<commandset id="placesCommands">
|
||||
<command id="Browser:ShowAllBookmarks"
|
||||
oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');"/>
|
||||
<command id="Browser:ShowAllHistory"
|
||||
oncommand="PlacesCommandHook.showPlacesOrganizer('History');"/>
|
||||
</commandset>
|
||||
|
||||
<broadcasterset id="mainBroadcasterSet">
|
||||
<broadcaster id="viewBookmarksSidebar" autoCheck="false" label="&bookmarksButton.label;"
|
||||
type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/bookmarks/bookmarksPanel.xul"
|
||||
oncommand="SidebarUI.toggle('viewBookmarksSidebar');"/>
|
||||
|
||||
<!-- for both places and non-places, the sidebar lives at
|
||||
chrome://browser/content/history/history-panel.xul so there are no
|
||||
problems when switching between versions -->
|
||||
<broadcaster id="viewHistorySidebar" autoCheck="false" sidebartitle="&historyButton.label;"
|
||||
type="checkbox" group="sidebar"
|
||||
sidebarurl="chrome://browser/content/history/history-panel.xul"
|
||||
oncommand="SidebarUI.toggle('viewHistorySidebar');"/>
|
||||
|
||||
<broadcaster id="viewWebPanelsSidebar" autoCheck="false"
|
||||
type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/web-panels.xul"
|
||||
oncommand="SidebarUI.toggle('viewWebPanelsSidebar');"/>
|
||||
|
||||
<broadcaster id="bookmarkThisPageBroadcaster"
|
||||
label="&bookmarkThisPageCmd.label;"
|
||||
bookmarklabel="&bookmarkThisPageCmd.label;"
|
||||
editlabel="&editThisBookmarkCmd.label;"/>
|
||||
|
||||
<!-- popup blocking menu items -->
|
||||
<broadcaster id="blockedPopupAllowSite"
|
||||
accesskey="&allowPopups.accesskey;"
|
||||
oncommand="gPopupBlockerObserver.toggleAllowPopupsForSite(event);"/>
|
||||
<broadcaster id="blockedPopupEditSettings"
|
||||
#ifdef XP_WIN
|
||||
label="&editPopupSettings.label;"
|
||||
#else
|
||||
label="&editPopupSettingsUnix.label;"
|
||||
#endif
|
||||
accesskey="&editPopupSettings.accesskey;"
|
||||
oncommand="gPopupBlockerObserver.editPopupSettings();"/>
|
||||
<broadcaster id="blockedPopupDontShowMessage"
|
||||
accesskey="&dontShowMessage.accesskey;"
|
||||
type="checkbox"
|
||||
oncommand="gPopupBlockerObserver.dontShowMessage();"/>
|
||||
<broadcaster id="blockedPopupsSeparator"/>
|
||||
<broadcaster id="isImage"/>
|
||||
<broadcaster id="canViewSource"/>
|
||||
<broadcaster id="isFrameImage"/>
|
||||
<broadcaster id="singleFeedMenuitemState" disabled="true"/>
|
||||
<broadcaster id="multipleFeedsMenuState" hidden="true"/>
|
||||
|
||||
<!-- Sync broadcasters -->
|
||||
<!-- A broadcaster of a number of attributes suitable for "sync now" UI -
|
||||
A 'syncstatus' attribute is set while actively syncing, and the label
|
||||
attribute which changes from "sync now" to "syncing" etc. -->
|
||||
<broadcaster id="sync-status"/>
|
||||
<!-- broadcasters of the "hidden" attribute to reflect setup state for
|
||||
menus -->
|
||||
<broadcaster id="sync-setup-state"/>
|
||||
<broadcaster id="sync-syncnow-state" hidden="true"/>
|
||||
<broadcaster id="sync-reauth-state" hidden="true"/>
|
||||
<broadcaster id="viewTabsSidebar" autoCheck="false" sidebartitle="&syncedTabs.sidebar.label;"
|
||||
type="checkbox" group="sidebar"
|
||||
sidebarurl="chrome://browser/content/syncedtabs/sidebar.xhtml"
|
||||
oncommand="SidebarUI.toggle('viewTabsSidebar');"/>
|
||||
<broadcaster id="workOfflineMenuitemState"/>
|
||||
|
||||
<broadcaster id="devtoolsMenuBroadcaster_ErrorConsole"
|
||||
label="&errorConsoleCmd.label;"
|
||||
command="Tools:ErrorConsole"/>
|
||||
|
||||
<broadcaster id="devtoolsMenuBroadcaster_PageSource"
|
||||
label="&pageSourceCmd.label;"
|
||||
key="key_viewSource"
|
||||
command="View:PageSource">
|
||||
<observes element="canViewSource" attribute="disabled"/>
|
||||
|
||||
</broadcaster>
|
||||
</broadcasterset>
|
||||
|
||||
<keyset id="mainKeyset">
|
||||
<key id="key_newNavigator"
|
||||
key="&newNavigatorCmd.key;"
|
||||
command="cmd_newNavigator"
|
||||
modifiers="accel"/>
|
||||
<key id="key_newNavigatorTab" key="&tabCmd.commandkey;" modifiers="accel" command="cmd_newNavigatorTabNoEvent"/>
|
||||
<key id="focusURLBar" key="&openCmd.commandkey;" command="Browser:OpenLocation"
|
||||
modifiers="accel"/>
|
||||
#ifndef XP_MACOSX
|
||||
<key id="focusURLBar2" key="&urlbar.accesskey;" command="Browser:OpenLocation"
|
||||
modifiers="alt"/>
|
||||
#endif
|
||||
|
||||
#
|
||||
# Search Command Key Logic works like this:
|
||||
#
|
||||
# Unix: Ctrl+K (cross platform binding)
|
||||
# Ctrl+J (in case of emacs Ctrl-K conflict)
|
||||
# Mac: Cmd+K (cross platform binding)
|
||||
# Cmd+Opt+F (platform convention)
|
||||
# Win: Ctrl+K (cross platform binding)
|
||||
# Ctrl+E (IE compat)
|
||||
#
|
||||
# We support Ctrl+K on all platforms now and advertise it in the menu since it is
|
||||
# our standard - it is a "safe" choice since it is near no harmful keys like "W" as
|
||||
# "E" is. People mourning the loss of Ctrl+K for emacs compat can switch their GTK
|
||||
# system setting to use emacs emulation, and we should respect it. Focus-Search-Box
|
||||
# is a fundamental keybinding and we are maintaining a XP binding so that it is easy
|
||||
# for people to switch to Linux.
|
||||
#
|
||||
<key id="key_search" key="&searchFocus.commandkey;" command="Tools:Search" modifiers="accel"/>
|
||||
#ifdef XP_MACOSX
|
||||
<key id="key_search2" key="&findOnCmd.commandkey;" command="Tools:Search" modifiers="accel,alt"/>
|
||||
#endif
|
||||
#ifdef XP_WIN
|
||||
<key id="key_search2" key="&searchFocus.commandkey2;" command="Tools:Search" modifiers="accel"/>
|
||||
#endif
|
||||
#ifdef XP_GNOME
|
||||
<key id="key_search2" key="&searchFocusUnix.commandkey;" command="Tools:Search" modifiers="accel"/>
|
||||
<key id="key_openDownloads" key="&downloadsUnix.commandkey;" command="Tools:Downloads" modifiers="accel,shift"/>
|
||||
#else
|
||||
<key id="key_openDownloads" key="&downloads.commandkey;" command="Tools:Downloads" modifiers="accel"/>
|
||||
#endif
|
||||
<key id="key_openAddons" key="&addons.commandkey;" command="Tools:Addons" modifiers="accel,shift"/>
|
||||
<key id="openFileKb" key="&openFileCmd.commandkey;" command="Browser:OpenFile" modifiers="accel"/>
|
||||
<key id="key_savePage" key="&savePageCmd.commandkey;" command="Browser:SavePage" modifiers="accel"/>
|
||||
<key id="printKb" key="&printCmd.commandkey;" command="cmd_print" modifiers="accel"/>
|
||||
<key id="key_close" key="&closeCmd.key;" command="cmd_close" modifiers="accel"/>
|
||||
<key id="key_closeWindow" key="&closeCmd.key;" command="cmd_closeWindow" modifiers="accel,shift"/>
|
||||
<key id="key_toggleMute" key="&toggleMuteCmd.key;" command="cmd_toggleMute" modifiers="control"/>
|
||||
<key id="key_undo"
|
||||
key="&undoCmd.key;"
|
||||
modifiers="accel"/>
|
||||
#ifdef XP_UNIX
|
||||
<key id="key_redo" key="&undoCmd.key;" modifiers="accel,shift"/>
|
||||
#else
|
||||
<key id="key_redo" key="&redoCmd.key;" modifiers="accel"/>
|
||||
#endif
|
||||
<key id="key_cut"
|
||||
key="&cutCmd.key;"
|
||||
modifiers="accel"/>
|
||||
<key id="key_copy"
|
||||
key="©Cmd.key;"
|
||||
modifiers="accel"/>
|
||||
<key id="key_paste"
|
||||
key="&pasteCmd.key;"
|
||||
modifiers="accel"/>
|
||||
<key id="key_delete" keycode="VK_DELETE" command="cmd_delete"/>
|
||||
<key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/>
|
||||
|
||||
<key keycode="VK_BACK" command="cmd_handleBackspace"/>
|
||||
<key keycode="VK_BACK" command="cmd_handleShiftBackspace" modifiers="shift"/>
|
||||
#ifndef XP_MACOSX
|
||||
<key id="goBackKb" keycode="VK_LEFT" command="Browser:Back" modifiers="alt"/>
|
||||
<key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="alt"/>
|
||||
#else
|
||||
<key id="goBackKb" keycode="VK_LEFT" command="Browser:Back" modifiers="accel" />
|
||||
<key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="accel" />
|
||||
#endif
|
||||
#ifdef XP_UNIX
|
||||
<key id="goBackKb2" key="&goBackCmd.commandKey;" command="Browser:Back" modifiers="accel"/>
|
||||
<key id="goForwardKb2" key="&goForwardCmd.commandKey;" command="Browser:Forward" modifiers="accel"/>
|
||||
#endif
|
||||
<key id="goHome" keycode="VK_HOME" command="Browser:Home" modifiers="alt"/>
|
||||
<key keycode="VK_F5" command="Browser:Reload"/>
|
||||
#ifndef XP_MACOSX
|
||||
<key id="showAllHistoryKb" key="&showAllHistoryCmd.commandkey;" command="Browser:ShowAllHistory" modifiers="accel,shift"/>
|
||||
<key keycode="VK_F5" command="Browser:ReloadSkipCache" modifiers="accel"/>
|
||||
<key id="key_fullScreen" keycode="VK_F11" command="View:FullScreen"/>
|
||||
#else
|
||||
<key id="key_fullScreen" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,control"/>
|
||||
<key id="key_fullScreen_old" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,shift"/>
|
||||
<key keycode="VK_F11" command="View:FullScreen"/>
|
||||
#endif
|
||||
<key id="key_toggleReaderMode" key="&toggleReaderMode.key;" command="View:ReaderView" modifiers="accel,alt" disabled="true"/>
|
||||
<key key="&reloadCmd.commandkey;" command="Browser:Reload" modifiers="accel" id="key_reload"/>
|
||||
<key key="&reloadCmd.commandkey;" command="Browser:ReloadSkipCache" modifiers="accel,shift"/>
|
||||
<key id="key_viewSource" key="&pageSourceCmd.commandkey;" command="View:PageSource" modifiers="accel"/>
|
||||
#ifndef XP_WIN
|
||||
<key id="key_viewInfo" key="&pageInfoCmd.commandkey;" command="View:PageInfo" modifiers="accel"/>
|
||||
#endif
|
||||
<key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
|
||||
<key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
|
||||
<key id="key_findPrevious" key="&findAgainCmd.commandkey;" command="cmd_findPrevious" modifiers="accel,shift"/>
|
||||
#ifdef XP_MACOSX
|
||||
<key id="key_findSelection" key="&findSelectionCmd.commandkey;" command="cmd_findSelection" modifiers="accel"/>
|
||||
#endif
|
||||
<key keycode="&findAgainCmd.commandkey2;" command="cmd_findAgain"/>
|
||||
<key keycode="&findAgainCmd.commandkey2;" command="cmd_findPrevious" modifiers="shift"/>
|
||||
|
||||
<key id="addBookmarkAsKb" key="&bookmarkThisPageCmd.commandkey;" command="Browser:AddBookmarkAs" modifiers="accel"/>
|
||||
# Accel+Shift+A-F are reserved on GTK
|
||||
#ifndef MOZ_WIDGET_GTK
|
||||
<key id="bookmarkAllTabsKb" key="&bookmarkThisPageCmd.commandkey;" oncommand="PlacesCommandHook.bookmarkCurrentPages();" modifiers="accel,shift"/>
|
||||
<key id="manBookmarkKb" key="&bookmarksCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/>
|
||||
#else
|
||||
<key id="manBookmarkKb" key="&bookmarksGtkCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/>
|
||||
#endif
|
||||
<key id="viewBookmarksSidebarKb" key="&bookmarksCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
|
||||
#ifdef XP_WIN
|
||||
# Cmd+I is conventially mapped to Info on MacOS X, thus it should not be
|
||||
# overridden for other purposes there.
|
||||
<key id="viewBookmarksSidebarWinKb" key="&bookmarksWinCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
|
||||
#endif
|
||||
|
||||
<key id="key_stop" keycode="VK_ESCAPE" command="Browser:Stop"/>
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
<key id="key_stop_mac" modifiers="accel" key="&stopCmd.macCommandKey;" command="Browser:Stop"/>
|
||||
#endif
|
||||
|
||||
<key id="key_gotoHistory"
|
||||
key="&historySidebarCmd.commandKey;"
|
||||
#ifdef XP_MACOSX
|
||||
modifiers="accel,shift"
|
||||
#else
|
||||
modifiers="accel"
|
||||
#endif
|
||||
command="viewHistorySidebar"/>
|
||||
|
||||
<key id="key_fullZoomReduce" key="&fullZoomReduceCmd.commandkey;" command="cmd_fullZoomReduce" modifiers="accel"/>
|
||||
<key key="&fullZoomReduceCmd.commandkey2;" command="cmd_fullZoomReduce" modifiers="accel"/>
|
||||
<key id="key_fullZoomEnlarge" key="&fullZoomEnlargeCmd.commandkey;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
|
||||
<key key="&fullZoomEnlargeCmd.commandkey2;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
|
||||
<key key="&fullZoomEnlargeCmd.commandkey3;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
|
||||
<key id="key_fullZoomReset" key="&fullZoomResetCmd.commandkey;" command="cmd_fullZoomReset" modifiers="accel"/>
|
||||
<key key="&fullZoomResetCmd.commandkey2;" command="cmd_fullZoomReset" modifiers="accel"/>
|
||||
|
||||
<key id="key_showAllTabs" command="Browser:ShowAllTabs" keycode="VK_TAB" modifiers="control,shift"/>
|
||||
|
||||
<key id="key_switchTextDirection" key="&bidiSwitchTextDirectionItem.commandkey;" command="cmd_switchTextDirection" modifiers="accel,shift" />
|
||||
|
||||
<key id="key_privatebrowsing" command="Tools:PrivateBrowsing" key="&privateBrowsingCmd.commandkey;" modifiers="accel,shift"/>
|
||||
<key id="key_sanitize" command="Tools:Sanitize" keycode="VK_DELETE" modifiers="accel,shift"/>
|
||||
#ifdef XP_MACOSX
|
||||
<key id="key_sanitize_mac" command="Tools:Sanitize" keycode="VK_BACK" modifiers="accel,shift"/>
|
||||
<key id="key_quitApplication" key="&quitApplicationCmdUnix.key;" modifiers="accel" reserved="true"/>
|
||||
#elifdef XP_UNIX
|
||||
<key id="key_quitApplication" key="&quitApplicationCmdUnix.key;" command="cmd_quitApplication" modifiers="accel"/>
|
||||
#endif
|
||||
|
||||
#ifdef FULL_BROWSER_WINDOW
|
||||
<key id="key_undoCloseTab" command="History:UndoCloseTab" key="&tabCmd.commandkey;" modifiers="accel,shift"/>
|
||||
#endif
|
||||
<key id="key_undoCloseWindow" command="History:UndoCloseWindow" key="&newNavigatorCmd.key;" modifiers="accel,shift"/>
|
||||
|
||||
#ifdef XP_GNOME
|
||||
#define NUM_SELECT_TAB_MODIFIER alt
|
||||
#else
|
||||
#define NUM_SELECT_TAB_MODIFIER accel
|
||||
#endif
|
||||
|
||||
#expand <key id="key_selectTab1" oncommand="gBrowser.selectTabAtIndex(0, event);" key="1" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectTab2" oncommand="gBrowser.selectTabAtIndex(1, event);" key="2" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectTab3" oncommand="gBrowser.selectTabAtIndex(2, event);" key="3" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectTab4" oncommand="gBrowser.selectTabAtIndex(3, event);" key="4" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectTab5" oncommand="gBrowser.selectTabAtIndex(4, event);" key="5" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectTab6" oncommand="gBrowser.selectTabAtIndex(5, event);" key="6" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectTab7" oncommand="gBrowser.selectTabAtIndex(6, event);" key="7" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectTab8" oncommand="gBrowser.selectTabAtIndex(7, event);" key="8" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
#expand <key id="key_selectLastTab" oncommand="gBrowser.selectTabAtIndex(-1, event);" key="9" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
|
||||
|
||||
</keyset>
|
||||
|
||||
# Used by baseMenuOverlay
|
||||
#ifdef XP_MACOSX
|
||||
<commandset id="baseMenuCommandSet" />
|
||||
#endif
|
||||
<keyset id="baseMenuKeyset" />
|
||||
|
|
@ -1,337 +0,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/. */
|
||||
|
||||
/**
|
||||
* SidebarUI controls showing and hiding the browser sidebar.
|
||||
*
|
||||
* @note
|
||||
* Some of these methods take a commandID argument - we expect to find a
|
||||
* xul:broadcaster element with the specified ID.
|
||||
* The following attributes on that element may be used and/or modified:
|
||||
* - id (required) the string to match commandID. The convention
|
||||
* is to use this naming scheme: 'view<sidebar-name>Sidebar'.
|
||||
* - sidebarurl (required) specifies the URL to load in this sidebar.
|
||||
* - sidebartitle or label (in that order) specify the title to
|
||||
* display on the sidebar.
|
||||
* - checked indicates whether the sidebar is currently displayed.
|
||||
* Note that toggleSidebar updates this attribute when
|
||||
* it changes the sidebar's visibility.
|
||||
* - group this attribute must be set to "sidebar".
|
||||
*/
|
||||
var SidebarUI = {
|
||||
browser: null,
|
||||
|
||||
_box: null,
|
||||
_title: null,
|
||||
_splitter: null,
|
||||
|
||||
init() {
|
||||
this._box = document.getElementById("sidebar-box");
|
||||
this.browser = document.getElementById("sidebar");
|
||||
this._title = document.getElementById("sidebar-title");
|
||||
this._splitter = document.getElementById("sidebar-splitter");
|
||||
|
||||
if (!this.adoptFromWindow(window.opener)) {
|
||||
let commandID = this._box.getAttribute("sidebarcommand");
|
||||
if (commandID) {
|
||||
let command = document.getElementById(commandID);
|
||||
if (command) {
|
||||
this._delayedLoad = true;
|
||||
this._box.hidden = false;
|
||||
this._splitter.hidden = false;
|
||||
command.setAttribute("checked", "true");
|
||||
} else {
|
||||
// Remove the |sidebarcommand| attribute, because the element it
|
||||
// refers to no longer exists, so we should assume this sidebar
|
||||
// panel has been uninstalled. (249883)
|
||||
this._box.removeAttribute("sidebarcommand");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
uninit() {
|
||||
let enumerator = Services.wm.getEnumerator(null);
|
||||
enumerator.getNext();
|
||||
if (!enumerator.hasMoreElements()) {
|
||||
document.persist("sidebar-box", "sidebarcommand");
|
||||
document.persist("sidebar-box", "width");
|
||||
document.persist("sidebar-box", "src");
|
||||
document.persist("sidebar-title", "value");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Try and adopt the status of the sidebar from another window.
|
||||
* @param {Window} sourceWindow - Window to use as a source for sidebar status.
|
||||
* @return true if we adopted the state, or false if the caller should
|
||||
* initialize the state itself.
|
||||
*/
|
||||
adoptFromWindow(sourceWindow) {
|
||||
// No source window, or it being closed, or not chrome, or in a different
|
||||
// private-browsing context means we can't adopt.
|
||||
if (!sourceWindow || sourceWindow.closed ||
|
||||
!sourceWindow.document.documentURIObject.schemeIs("chrome") ||
|
||||
PrivateBrowsingUtils.isWindowPrivate(window) != PrivateBrowsingUtils.isWindowPrivate(sourceWindow)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the opener had a sidebar, open the same sidebar in our window.
|
||||
// The opener can be the hidden window too, if we're coming from the state
|
||||
// where no windows are open, and the hidden window has no sidebar box.
|
||||
let sourceUI = sourceWindow.SidebarUI;
|
||||
if (!sourceUI || !sourceUI._box) {
|
||||
// no source UI or no _box means we also can't adopt the state.
|
||||
return false;
|
||||
}
|
||||
if (sourceUI._box.hidden) {
|
||||
// just hidden means we have adopted the hidden state.
|
||||
return true;
|
||||
}
|
||||
|
||||
let commandID = sourceUI._box.getAttribute("sidebarcommand");
|
||||
let commandElem = document.getElementById(commandID);
|
||||
|
||||
// dynamically generated sidebars will fail this check, but we still
|
||||
// consider it adopted.
|
||||
if (!commandElem) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this._title.setAttribute("value",
|
||||
sourceUI._title.getAttribute("value"));
|
||||
this._box.setAttribute("width", sourceUI._box.boxObject.width);
|
||||
|
||||
this._box.setAttribute("sidebarcommand", commandID);
|
||||
// Note: we're setting 'src' on this._box, which is a <vbox>, not on
|
||||
// the <browser id="sidebar">. This lets us delay the actual load until
|
||||
// delayedStartup().
|
||||
this._box.setAttribute("src", sourceUI.browser.getAttribute("src"));
|
||||
this._delayedLoad = true;
|
||||
|
||||
this._box.hidden = false;
|
||||
this._splitter.hidden = false;
|
||||
commandElem.setAttribute("checked", "true");
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* If loading a sidebar was delayed on startup, start the load now.
|
||||
*/
|
||||
startDelayedLoad() {
|
||||
if (!this._delayedLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.browser.setAttribute("src", this._box.getAttribute("src"));
|
||||
},
|
||||
|
||||
/**
|
||||
* Fire a "SidebarFocused" event on the sidebar's |window| to give the sidebar
|
||||
* a chance to adjust focus as needed. An additional event is needed, because
|
||||
* we don't want to focus the sidebar when it's opened on startup or in a new
|
||||
* window, only when the user opens the sidebar.
|
||||
*/
|
||||
_fireFocusedEvent() {
|
||||
let event = new CustomEvent("SidebarFocused", {bubbles: true});
|
||||
this.browser.contentWindow.dispatchEvent(event);
|
||||
|
||||
// Run the original function for backwards compatibility.
|
||||
fireSidebarFocusedEvent();
|
||||
},
|
||||
|
||||
/**
|
||||
* True if the sidebar is currently open.
|
||||
*/
|
||||
get isOpen() {
|
||||
return !this._box.hidden;
|
||||
},
|
||||
|
||||
/**
|
||||
* The ID of the current sidebar (ie, the ID of the broadcaster being used).
|
||||
* This can be set even if the sidebar is hidden.
|
||||
*/
|
||||
get currentID() {
|
||||
return this._box.getAttribute("sidebarcommand");
|
||||
},
|
||||
|
||||
get title() {
|
||||
return this._title.value;
|
||||
},
|
||||
|
||||
set title(value) {
|
||||
this._title.value = value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle the visibility of the sidebar. If the sidebar is hidden or is open
|
||||
* with a different commandID, then the sidebar will be opened using the
|
||||
* specified commandID. Otherwise the sidebar will be hidden.
|
||||
*
|
||||
* @param {string} commandID ID of the xul:broadcaster element to use.
|
||||
* @return {Promise}
|
||||
*/
|
||||
toggle(commandID = this.currentID) {
|
||||
if (this.isOpen && commandID == this.currentID) {
|
||||
this.hide();
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.show(commandID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the sidebar, using the parameters from the specified broadcaster.
|
||||
* @see SidebarUI note.
|
||||
*
|
||||
* @param {string} commandID ID of the xul:broadcaster element to use.
|
||||
*/
|
||||
show(commandID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let sidebarBroadcaster = document.getElementById(commandID);
|
||||
if (!sidebarBroadcaster || sidebarBroadcaster.localName != "broadcaster") {
|
||||
reject(new Error("Invalid sidebar broadcaster specified: " + commandID));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isOpen && commandID != this.currentID) {
|
||||
BrowserUITelemetry.countSidebarEvent(this.currentID, "hide");
|
||||
}
|
||||
|
||||
let broadcasters = document.getElementsByAttribute("group", "sidebar");
|
||||
for (let broadcaster of broadcasters) {
|
||||
// skip elements that observe sidebar broadcasters and random
|
||||
// other elements
|
||||
if (broadcaster.localName != "broadcaster") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (broadcaster != sidebarBroadcaster) {
|
||||
broadcaster.removeAttribute("checked");
|
||||
} else {
|
||||
sidebarBroadcaster.setAttribute("checked", "true");
|
||||
}
|
||||
}
|
||||
|
||||
this._box.hidden = false;
|
||||
this._splitter.hidden = false;
|
||||
|
||||
this._box.setAttribute("sidebarcommand", sidebarBroadcaster.id);
|
||||
|
||||
let title = sidebarBroadcaster.getAttribute("sidebartitle");
|
||||
if (!title) {
|
||||
title = sidebarBroadcaster.getAttribute("label");
|
||||
}
|
||||
this._title.value = title;
|
||||
|
||||
let url = sidebarBroadcaster.getAttribute("sidebarurl");
|
||||
this.browser.setAttribute("src", url); // kick off async load
|
||||
|
||||
// We set this attribute here in addition to setting it on the <browser>
|
||||
// element itself, because the code in SidebarUI.uninit() persists this
|
||||
// attribute, not the "src" of the <browser id="sidebar">. The reason it
|
||||
// does that is that we want to delay sidebar load a bit when a browser
|
||||
// window opens. See delayedStartup() and SidebarUI.startDelayedLoad().
|
||||
this._box.setAttribute("src", url);
|
||||
|
||||
if (this.browser.contentDocument.location.href != url) {
|
||||
let onLoad = event => {
|
||||
this.browser.removeEventListener("load", onLoad, true);
|
||||
|
||||
// We're handling the 'load' event before it bubbles up to the usual
|
||||
// (non-capturing) event handlers. Let it bubble up before firing the
|
||||
// SidebarFocused event.
|
||||
setTimeout(() => this._fireFocusedEvent(), 0);
|
||||
|
||||
// Run the original function for backwards compatibility.
|
||||
sidebarOnLoad(event);
|
||||
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.browser.addEventListener("load", onLoad, true);
|
||||
} else {
|
||||
// Older code handled this case, so we do it too.
|
||||
this._fireFocusedEvent();
|
||||
resolve();
|
||||
}
|
||||
|
||||
let selBrowser = gBrowser.selectedBrowser;
|
||||
selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
|
||||
{commandID: commandID, isOpen: true}
|
||||
);
|
||||
BrowserUITelemetry.countSidebarEvent(commandID, "show");
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the sidebar.
|
||||
*/
|
||||
hide() {
|
||||
if (!this.isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
let commandID = this._box.getAttribute("sidebarcommand");
|
||||
let sidebarBroadcaster = document.getElementById(commandID);
|
||||
|
||||
if (sidebarBroadcaster.getAttribute("checked") != "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace the document currently displayed in the sidebar with about:blank
|
||||
// so that we can free memory by unloading the page. We need to explicitly
|
||||
// create a new content viewer because the old one doesn't get destroyed
|
||||
// until about:blank has loaded (which does not happen as long as the
|
||||
// element is hidden).
|
||||
this.browser.setAttribute("src", "about:blank");
|
||||
this.browser.docShell.createAboutBlankContentViewer(null);
|
||||
|
||||
sidebarBroadcaster.removeAttribute("checked");
|
||||
this._box.setAttribute("sidebarcommand", "");
|
||||
this._title.value = "";
|
||||
this._box.hidden = true;
|
||||
this._splitter.hidden = true;
|
||||
|
||||
let selBrowser = gBrowser.selectedBrowser;
|
||||
selBrowser.focus();
|
||||
selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
|
||||
{commandID: commandID, isOpen: false}
|
||||
);
|
||||
BrowserUITelemetry.countSidebarEvent(commandID, "hide");
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* This exists for backards compatibility - it will be called once a sidebar is
|
||||
* ready, following any request to show it.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
function fireSidebarFocusedEvent() {}
|
||||
|
||||
/**
|
||||
* This exists for backards compatibility - it gets called when a sidebar has
|
||||
* been loaded.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
function sidebarOnLoad(event) {}
|
||||
|
||||
/**
|
||||
* This exists for backards compatibility, and is equivilent to
|
||||
* SidebarUI.toggle() without the forceOpen param. With forceOpen set to true,
|
||||
* it is equalivent to SidebarUI.show().
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
function toggleSidebar(commandID, forceOpen = false) {
|
||||
Deprecated.warning("toggleSidebar() is deprecated, please use SidebarUI.toggle() or SidebarUI.show() instead",
|
||||
"https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Sidebar");
|
||||
|
||||
if (forceOpen) {
|
||||
SidebarUI.show(commandID);
|
||||
} else {
|
||||
SidebarUI.toggle(commandID);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,548 +0,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/. */
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
#ifdef MOZ_SERVICES_CLOUDSYNC
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
|
||||
"resource://gre/modules/CloudSync.jsm");
|
||||
#endif
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
|
||||
"resource://gre/modules/FxAccounts.jsm");
|
||||
|
||||
const MIN_STATUS_ANIMATION_DURATION = 1600;
|
||||
|
||||
// gSyncUI handles updating the tools menu and displaying notifications.
|
||||
var gSyncUI = {
|
||||
_obs: ["weave:service:sync:start",
|
||||
"weave:service:sync:finish",
|
||||
"weave:service:sync:error",
|
||||
"weave:service:setup-complete",
|
||||
"weave:service:login:start",
|
||||
"weave:service:login:finish",
|
||||
"weave:service:login:error",
|
||||
"weave:service:logout:finish",
|
||||
"weave:service:start-over",
|
||||
"weave:service:start-over:finish",
|
||||
"weave:ui:login:error",
|
||||
"weave:ui:sync:error",
|
||||
"weave:ui:sync:finish",
|
||||
"weave:ui:clear-error",
|
||||
"weave:engine:sync:finish"
|
||||
],
|
||||
|
||||
_unloaded: false,
|
||||
// The last sync start time. Used to calculate the leftover animation time
|
||||
// once syncing completes (bug 1239042).
|
||||
_syncStartTime: 0,
|
||||
_syncAnimationTimer: 0,
|
||||
|
||||
init: function () {
|
||||
Cu.import("resource://services-common/stringbundle.js");
|
||||
|
||||
// Proceed to set up the UI if Sync has already started up.
|
||||
// Otherwise we'll do it when Sync is firing up.
|
||||
if (this.weaveService.ready) {
|
||||
this.initUI();
|
||||
return;
|
||||
}
|
||||
|
||||
// Sync isn't ready yet, but we can still update the UI with an initial
|
||||
// state - we haven't called initUI() yet, but that's OK - that's more
|
||||
// about observers for state changes, and will be called once Sync is
|
||||
// ready to start sending notifications.
|
||||
this.updateUI();
|
||||
|
||||
Services.obs.addObserver(this, "weave:service:ready", true);
|
||||
Services.obs.addObserver(this, "quit-application", true);
|
||||
|
||||
// Remove the observer if the window is closed before the observer
|
||||
// was triggered.
|
||||
window.addEventListener("unload", function onUnload() {
|
||||
gSyncUI._unloaded = true;
|
||||
window.removeEventListener("unload", onUnload, false);
|
||||
Services.obs.removeObserver(gSyncUI, "weave:service:ready");
|
||||
Services.obs.removeObserver(gSyncUI, "quit-application");
|
||||
|
||||
if (Weave.Status.ready) {
|
||||
gSyncUI._obs.forEach(function(topic) {
|
||||
Services.obs.removeObserver(gSyncUI, topic);
|
||||
});
|
||||
}
|
||||
}, false);
|
||||
},
|
||||
|
||||
initUI: function SUI_initUI() {
|
||||
// If this is a browser window?
|
||||
if (gBrowser) {
|
||||
this._obs.push("weave:notification:added");
|
||||
}
|
||||
|
||||
this._obs.forEach(function(topic) {
|
||||
Services.obs.addObserver(this, topic, true);
|
||||
}, this);
|
||||
|
||||
// initial label for the sync buttons.
|
||||
let broadcaster = document.getElementById("sync-status");
|
||||
broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncnow.label"));
|
||||
|
||||
this.maybeMoveSyncedTabsButton();
|
||||
|
||||
this.updateUI();
|
||||
},
|
||||
|
||||
|
||||
// Returns a promise that resolves with true if Sync needs to be configured,
|
||||
// false otherwise.
|
||||
_needsSetup() {
|
||||
// If Sync is configured for FxAccounts then we do that promise-dance.
|
||||
if (this.weaveService.fxAccountsEnabled) {
|
||||
return fxAccounts.getSignedInUser().then(user => {
|
||||
// We want to treat "account needs verification" as "needs setup".
|
||||
return !(user && user.verified);
|
||||
});
|
||||
}
|
||||
// We are using legacy sync - check that.
|
||||
let firstSync = "";
|
||||
try {
|
||||
firstSync = Services.prefs.getCharPref("services.sync.firstSync");
|
||||
} catch (e) { }
|
||||
|
||||
return Promise.resolve(Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED ||
|
||||
firstSync == "notReady");
|
||||
},
|
||||
|
||||
// Returns a promise that resolves with true if the user currently signed in
|
||||
// to Sync needs to be verified, false otherwise.
|
||||
_needsVerification() {
|
||||
// For callers who care about the distinction between "needs setup" and
|
||||
// "needs verification"
|
||||
if (this.weaveService.fxAccountsEnabled) {
|
||||
return fxAccounts.getSignedInUser().then(user => {
|
||||
// If there is no user, they can't be in a "needs verification" state.
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
return !user.verified;
|
||||
});
|
||||
}
|
||||
|
||||
// Otherwise we are configured for legacy Sync, which has no verification
|
||||
// concept.
|
||||
return Promise.resolve(false);
|
||||
},
|
||||
|
||||
// Note that we don't show login errors in a notification bar here, but do
|
||||
// still need to track a login-failed state so the "Tools" menu updates
|
||||
// with the correct state.
|
||||
_loginFailed: function () {
|
||||
// If Sync isn't already ready, we don't want to force it to initialize
|
||||
// by referencing Weave.Status - and it isn't going to be accurate before
|
||||
// Sync is ready anyway.
|
||||
if (!this.weaveService.ready) {
|
||||
this.log.debug("_loginFailed has sync not ready, so returning false");
|
||||
return false;
|
||||
}
|
||||
this.log.debug("_loginFailed has sync state=${sync}",
|
||||
{ sync: Weave.Status.login});
|
||||
return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
|
||||
},
|
||||
|
||||
// Kick off an update of the UI - does *not* return a promise.
|
||||
updateUI() {
|
||||
this._promiseUpdateUI().catch(err => {
|
||||
this.log.error("updateUI failed", err);
|
||||
})
|
||||
},
|
||||
|
||||
// Updates the UI - returns a promise.
|
||||
_promiseUpdateUI() {
|
||||
return this._needsSetup().then(needsSetup => {
|
||||
if (!gBrowser)
|
||||
return Promise.resolve();
|
||||
|
||||
let loginFailed = this._loginFailed();
|
||||
|
||||
// Start off with a clean slate
|
||||
document.getElementById("sync-reauth-state").hidden = true;
|
||||
document.getElementById("sync-setup-state").hidden = true;
|
||||
document.getElementById("sync-syncnow-state").hidden = true;
|
||||
|
||||
#ifdef MOZ_SERVICES_CLOUDSYNC
|
||||
if (CloudSync && CloudSync.ready && CloudSync().adapters.count) {
|
||||
document.getElementById("sync-syncnow-state").hidden = false;
|
||||
} else if (loginFailed) {
|
||||
#else
|
||||
if (loginFailed) {
|
||||
#endif
|
||||
// unhiding this element makes the menubar show the login failure state.
|
||||
document.getElementById("sync-reauth-state").hidden = false;
|
||||
} else if (needsSetup) {
|
||||
document.getElementById("sync-setup-state").hidden = false;
|
||||
} else {
|
||||
document.getElementById("sync-syncnow-state").hidden = false;
|
||||
}
|
||||
|
||||
return this._updateSyncButtonsTooltip();
|
||||
});
|
||||
},
|
||||
|
||||
// Functions called by observers
|
||||
onActivityStart() {
|
||||
if (!gBrowser)
|
||||
return;
|
||||
|
||||
this.log.debug("onActivityStart");
|
||||
|
||||
clearTimeout(this._syncAnimationTimer);
|
||||
this._syncStartTime = Date.now();
|
||||
|
||||
let broadcaster = document.getElementById("sync-status");
|
||||
broadcaster.setAttribute("syncstatus", "active");
|
||||
broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncing2.label"));
|
||||
broadcaster.setAttribute("disabled", "true");
|
||||
|
||||
this.updateUI();
|
||||
},
|
||||
|
||||
_updateSyncStatus() {
|
||||
if (!gBrowser)
|
||||
return;
|
||||
let broadcaster = document.getElementById("sync-status");
|
||||
broadcaster.removeAttribute("syncstatus");
|
||||
broadcaster.removeAttribute("disabled");
|
||||
broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncnow.label"));
|
||||
this.updateUI();
|
||||
},
|
||||
|
||||
onActivityStop() {
|
||||
if (!gBrowser)
|
||||
return;
|
||||
this.log.debug("onActivityStop");
|
||||
|
||||
let now = Date.now();
|
||||
let syncDuration = now - this._syncStartTime;
|
||||
|
||||
if (syncDuration < MIN_STATUS_ANIMATION_DURATION) {
|
||||
let animationTime = MIN_STATUS_ANIMATION_DURATION - syncDuration;
|
||||
clearTimeout(this._syncAnimationTimer);
|
||||
this._syncAnimationTimer = setTimeout(() => this._updateSyncStatus(), animationTime);
|
||||
} else {
|
||||
this._updateSyncStatus();
|
||||
}
|
||||
},
|
||||
|
||||
onLoginError: function SUI_onLoginError() {
|
||||
this.log.debug("onLoginError: login=${login}, sync=${sync}", Weave.Status);
|
||||
|
||||
// We don't show any login errors here; browser-fxaccounts shows them in
|
||||
// the hamburger menu.
|
||||
this.updateUI();
|
||||
},
|
||||
|
||||
onLogout: function SUI_onLogout() {
|
||||
this.updateUI();
|
||||
},
|
||||
|
||||
_getAppName: function () {
|
||||
let brand = new StringBundle("chrome://branding/locale/brand.properties");
|
||||
return brand.get("brandShortName");
|
||||
},
|
||||
|
||||
// Commands
|
||||
// doSync forces a sync - it *does not* return a promise as it is called
|
||||
// via the various UI components.
|
||||
doSync() {
|
||||
this._needsSetup().then(needsSetup => {
|
||||
if (!needsSetup) {
|
||||
setTimeout(() => Weave.Service.errorHandler.syncAndReportErrors(), 0);
|
||||
}
|
||||
Services.obs.notifyObservers(null, "cloudsync:user-sync", null);
|
||||
}).catch(err => {
|
||||
this.log.error("Failed to force a sync", err);
|
||||
});
|
||||
},
|
||||
|
||||
// Handle clicking the toolbar button - which either opens the Sync setup
|
||||
// pages or forces a sync now. Does *not* return a promise as it is called
|
||||
// via the UI.
|
||||
handleToolbarButton() {
|
||||
this._needsSetup().then(needsSetup => {
|
||||
if (needsSetup || this._loginFailed()) {
|
||||
this.openSetup();
|
||||
} else {
|
||||
this.doSync();
|
||||
}
|
||||
}).catch(err => {
|
||||
this.log.error("Failed to handle toolbar button command", err);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoke the Sync setup wizard.
|
||||
*
|
||||
* @param wizardType
|
||||
* Indicates type of wizard to launch:
|
||||
* null -- regular set up wizard
|
||||
* "pair" -- pair a device first
|
||||
* "reset" -- reset sync
|
||||
* @param entryPoint
|
||||
* Indicates the entrypoint from where this method was called.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Open the legacy-sync device pairing UI. Note used for FxA Sync.
|
||||
openAddDevice: function () {
|
||||
if (!Weave.Utils.ensureMPUnlocked())
|
||||
return;
|
||||
|
||||
let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
|
||||
if (win)
|
||||
win.focus();
|
||||
else
|
||||
window.openDialog("chrome://browser/content/sync/addDevice.xul",
|
||||
"syncAddDevice", "centerscreen,chrome,resizable=no");
|
||||
},
|
||||
|
||||
openPrefs: function (entryPoint) {
|
||||
openPreferences("paneSync", { urlParams: { entrypoint: entryPoint } });
|
||||
},
|
||||
|
||||
openSignInAgainPage: function (entryPoint = "syncbutton") {
|
||||
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,
|
||||
where the button defaulted to being in that purgatory.
|
||||
We use a preference to ensure we only do it once, so people can still
|
||||
customize it away and have it stick.
|
||||
*/
|
||||
maybeMoveSyncedTabsButton() {
|
||||
const prefName = "browser.migrated-sync-button";
|
||||
let migrated = false;
|
||||
try {
|
||||
migrated = Services.prefs.getBoolPref(prefName);
|
||||
} catch (_) {}
|
||||
if (migrated) {
|
||||
return;
|
||||
}
|
||||
if (!CustomizableUI.getPlacementOfWidget("sync-button")) {
|
||||
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
|
||||
}
|
||||
Services.prefs.setBoolPref(prefName, true);
|
||||
},
|
||||
|
||||
/* Update the tooltip for the sync-status broadcaster (which will update the
|
||||
Sync Toolbar button and the Sync spinner in the FxA hamburger area.)
|
||||
If Sync is configured, the tooltip is when the last sync occurred,
|
||||
otherwise the tooltip reflects the fact that Sync needs to be
|
||||
(re-)configured.
|
||||
*/
|
||||
_updateSyncButtonsTooltip: Task.async(function* () {
|
||||
if (!gBrowser)
|
||||
return;
|
||||
|
||||
let email;
|
||||
try {
|
||||
email = Services.prefs.getCharPref("services.sync.username");
|
||||
} catch (ex) {}
|
||||
|
||||
let needsSetup = yield this._needsSetup();
|
||||
let needsVerification = yield this._needsVerification();
|
||||
let loginFailed = this._loginFailed();
|
||||
// This is a little messy as the Sync buttons are 1/2 Sync related and
|
||||
// 1/2 FxA related - so for some strings we use Sync strings, but for
|
||||
// others we reach into gFxAccounts for strings.
|
||||
let tooltiptext;
|
||||
if (needsVerification) {
|
||||
// "needs verification"
|
||||
tooltiptext = gFxAccounts.strings.formatStringFromName("verifyDescription", [email], 1);
|
||||
} else if (needsSetup) {
|
||||
// "needs setup".
|
||||
tooltiptext = this._stringBundle.GetStringFromName("signInToSync.description");
|
||||
} else if (loginFailed) {
|
||||
// "need to reconnect/re-enter your password"
|
||||
tooltiptext = gFxAccounts.strings.formatStringFromName("reconnectDescription", [email], 1);
|
||||
} else {
|
||||
// Sync appears configured - format the "last synced at" time.
|
||||
try {
|
||||
let lastSync = new Date(Services.prefs.getCharPref("services.sync.lastSync"));
|
||||
tooltiptext = this.formatLastSyncDate(lastSync);
|
||||
}
|
||||
catch (e) {
|
||||
// pref doesn't exist (which will be the case until we've seen the
|
||||
// first successful sync) or is invalid (which should be impossible!)
|
||||
// Just leave tooltiptext as the empty string in these cases, which
|
||||
// will cause the tooltip to be removed below.
|
||||
}
|
||||
}
|
||||
|
||||
// We've done all our promise-y work and ready to update the UI - make
|
||||
// sure it hasn't been torn down since we started.
|
||||
if (!gBrowser)
|
||||
return;
|
||||
|
||||
let broadcaster = document.getElementById("sync-status");
|
||||
if (broadcaster) {
|
||||
if (tooltiptext) {
|
||||
broadcaster.setAttribute("tooltiptext", tooltiptext);
|
||||
} else {
|
||||
broadcaster.removeAttribute("tooltiptext");
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
formatLastSyncDate: function(date) {
|
||||
let dateFormat;
|
||||
let sixDaysAgo = (() => {
|
||||
let date = new Date();
|
||||
date.setDate(date.getDate() - 6);
|
||||
date.setHours(0, 0, 0, 0);
|
||||
return date;
|
||||
})();
|
||||
// It may be confusing for the user to see "Last Sync: Monday" when the last sync was a indeed a Monday but 3 weeks ago
|
||||
if (date < sixDaysAgo) {
|
||||
dateFormat = {month: 'long', day: 'numeric'};
|
||||
} else {
|
||||
dateFormat = {weekday: 'long', hour: 'numeric', minute: 'numeric'};
|
||||
}
|
||||
let lastSyncDateString = date.toLocaleDateString(undefined, dateFormat);
|
||||
return this._stringBundle.formatStringFromName("lastSync2.label", [lastSyncDateString], 1);
|
||||
},
|
||||
|
||||
onClientsSynced: function() {
|
||||
let broadcaster = document.getElementById("sync-syncnow-state");
|
||||
if (broadcaster) {
|
||||
if (Weave.Service.clientsEngine.stats.numClients > 1) {
|
||||
broadcaster.setAttribute("devices-status", "multi");
|
||||
} else {
|
||||
broadcaster.setAttribute("devices-status", "single");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
observe: function SUI_observe(subject, topic, data) {
|
||||
this.log.debug("observed", topic);
|
||||
if (this._unloaded) {
|
||||
Cu.reportError("SyncUI observer called after unload: " + topic);
|
||||
return;
|
||||
}
|
||||
|
||||
// Unwrap, just like Svc.Obs, but without pulling in that dependency.
|
||||
if (subject && typeof subject == "object" &&
|
||||
("wrappedJSObject" in subject) &&
|
||||
("observersModuleSubjectWrapper" in subject.wrappedJSObject)) {
|
||||
subject = subject.wrappedJSObject.object;
|
||||
}
|
||||
|
||||
// First handle "activity" only.
|
||||
switch (topic) {
|
||||
case "weave:service:sync:start":
|
||||
this.onActivityStart();
|
||||
break;
|
||||
case "weave:service:sync:finish":
|
||||
case "weave:service:sync:error":
|
||||
this.onActivityStop();
|
||||
break;
|
||||
}
|
||||
// Now non-activity state (eg, enabled, errors, etc)
|
||||
// Note that sync uses the ":ui:" notifications for errors because sync.
|
||||
switch (topic) {
|
||||
case "weave:ui:sync:finish":
|
||||
// Do nothing.
|
||||
break;
|
||||
case "weave:ui:sync:error":
|
||||
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:ui:login:error":
|
||||
case "weave:service:login:error":
|
||||
this.onLoginError();
|
||||
break;
|
||||
case "weave:service:logout:finish":
|
||||
this.onLogout();
|
||||
break;
|
||||
case "weave:service:start-over:finish":
|
||||
this.updateUI();
|
||||
break;
|
||||
case "weave:service:ready":
|
||||
this.initUI();
|
||||
break;
|
||||
case "weave:notification:added":
|
||||
this.initNotifications();
|
||||
break;
|
||||
case "weave:engine:sync:finish":
|
||||
if (data != "clients") {
|
||||
return;
|
||||
}
|
||||
this.onClientsSynced();
|
||||
break;
|
||||
case "quit-application":
|
||||
// Stop the animation timer on shutdown, since we can't update the UI
|
||||
// after this.
|
||||
clearTimeout(this._syncAnimationTimer);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference
|
||||
])
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(gSyncUI, "_stringBundle", function() {
|
||||
// XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
|
||||
// but for now just make it work
|
||||
return Cc["@mozilla.org/intl/stringbundle;1"].
|
||||
getService(Ci.nsIStringBundleService).
|
||||
createBundle("chrome://weave/locale/services/sync.properties");
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(gSyncUI, "log", function() {
|
||||
return Log.repository.getLogger("browserwindow.syncui");
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(gSyncUI, "weaveService", function() {
|
||||
return Components.classes["@mozilla.org/weave/service;1"]
|
||||
.getService(Components.interfaces.nsISupports)
|
||||
.wrappedJSObject;
|
||||
});
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
# -*- Mode: HTML -*-
|
||||
# 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/.
|
||||
|
||||
<bindings id="tabPreviews"
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl">
|
||||
<binding id="ctrlTab-preview" extends="chrome://global/content/bindings/button.xml#button-base">
|
||||
<content pack="center">
|
||||
<xul:stack>
|
||||
<xul:vbox class="ctrlTab-preview-inner" align="center" pack="center"
|
||||
xbl:inherits="width=canvaswidth">
|
||||
<xul:hbox class="tabPreview-canvas" xbl:inherits="style=canvasstyle">
|
||||
<children/>
|
||||
</xul:hbox>
|
||||
<xul:label xbl:inherits="value=label,crop" class="plain"/>
|
||||
</xul:vbox>
|
||||
<xul:hbox class="ctrlTab-favicon-container" xbl:inherits="hidden=noicon">
|
||||
<xul:image class="ctrlTab-favicon" xbl:inherits="src=image"/>
|
||||
</xul:hbox>
|
||||
</xul:stack>
|
||||
</content>
|
||||
<handlers>
|
||||
<handler event="mouseover" action="ctrlTab._mouseOverFocus(this);"/>
|
||||
<handler event="command" action="ctrlTab.pick(this);"/>
|
||||
<handler event="click" button="1" action="ctrlTab.remove(this);"/>
|
||||
#ifdef XP_MACOSX
|
||||
# Control+click is a right click on OS X
|
||||
<handler event="click" button="2" action="ctrlTab.pick(this);"/>
|
||||
#endif
|
||||
</handlers>
|
||||
</binding>
|
||||
</bindings>
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
// This file is used as a stub object for platforms which
|
||||
// don't have CAN_DRAW_IN_TITLEBAR defined.
|
||||
|
||||
var TabsInTitlebar = {
|
||||
init: function () {},
|
||||
uninit: function () {},
|
||||
allowedBy: function (condition, allow) {},
|
||||
updateAppearance: function updateAppearance(aForce) {},
|
||||
get enabled() {
|
||||
return document.documentElement.getAttribute("tabsintitlebar") == "true";
|
||||
},
|
||||
};
|
||||
|
|
@ -1,307 +0,0 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
// Note: the file browser-tabsintitlebar-stub.js is used instead of
|
||||
// this one on platforms which don't have CAN_DRAW_IN_TITLEBAR defined.
|
||||
|
||||
var TabsInTitlebar = {
|
||||
init: function () {
|
||||
if (this._initialized) {
|
||||
return;
|
||||
}
|
||||
this._readPref();
|
||||
Services.prefs.addObserver(this._prefName, this, false);
|
||||
|
||||
// We need to update the appearance of the titlebar when the menu changes
|
||||
// from the active to the inactive state. We can't, however, rely on
|
||||
// DOMMenuBarInactive, because the menu fires this event and then removes
|
||||
// the inactive attribute after an event-loop spin.
|
||||
//
|
||||
// Because updating the appearance involves sampling the heights and margins
|
||||
// of various elements, it's important that the layout be more or less
|
||||
// settled before updating the titlebar. So instead of listening to
|
||||
// DOMMenuBarActive and DOMMenuBarInactive, we use a MutationObserver to
|
||||
// watch the "invalid" attribute directly.
|
||||
let menu = document.getElementById("toolbar-menubar");
|
||||
this._menuObserver = new MutationObserver(this._onMenuMutate);
|
||||
this._menuObserver.observe(menu, {attributes: true});
|
||||
|
||||
this.onAreaReset = function(aArea) {
|
||||
if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR)
|
||||
this._update(true);
|
||||
};
|
||||
this.onWidgetAdded = this.onWidgetRemoved = function(aWidgetId, aArea) {
|
||||
if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR)
|
||||
this._update(true);
|
||||
};
|
||||
CustomizableUI.addListener(this);
|
||||
|
||||
addEventListener("resolutionchange", this, false);
|
||||
|
||||
this._initialized = true;
|
||||
if (this._updateOnInit) {
|
||||
// We don't need to call this with 'true', even if original calls
|
||||
// (before init()) did, because this will be the first call and so
|
||||
// we will update anyway.
|
||||
this._update();
|
||||
}
|
||||
},
|
||||
|
||||
allowedBy: function (condition, allow) {
|
||||
if (allow) {
|
||||
if (condition in this._disallowed) {
|
||||
delete this._disallowed[condition];
|
||||
this._update(true);
|
||||
}
|
||||
} else if (!(condition in this._disallowed)) {
|
||||
this._disallowed[condition] = null;
|
||||
this._update(true);
|
||||
}
|
||||
},
|
||||
|
||||
updateAppearance: function updateAppearance(aForce) {
|
||||
this._update(aForce);
|
||||
},
|
||||
|
||||
get enabled() {
|
||||
return document.documentElement.getAttribute("tabsintitlebar") == "true";
|
||||
},
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
if (topic == "nsPref:changed")
|
||||
this._readPref();
|
||||
},
|
||||
|
||||
handleEvent: function (aEvent) {
|
||||
if (aEvent.type == "resolutionchange" && aEvent.target == window) {
|
||||
this._update(true);
|
||||
}
|
||||
},
|
||||
|
||||
_onMenuMutate: function (aMutations) {
|
||||
for (let mutation of aMutations) {
|
||||
if (mutation.attributeName == "inactive" ||
|
||||
mutation.attributeName == "autohide") {
|
||||
TabsInTitlebar._update(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_initialized: false,
|
||||
_updateOnInit: false,
|
||||
_disallowed: {},
|
||||
_prefName: "browser.tabs.drawInTitlebar",
|
||||
_lastSizeMode: null,
|
||||
|
||||
_readPref: function () {
|
||||
this.allowedBy("pref",
|
||||
Services.prefs.getBoolPref(this._prefName));
|
||||
},
|
||||
|
||||
_update: function (aForce=false) {
|
||||
let $ = id => document.getElementById(id);
|
||||
let rect = ele => ele.getBoundingClientRect();
|
||||
let verticalMargins = cstyle => parseFloat(cstyle.marginBottom) + parseFloat(cstyle.marginTop);
|
||||
|
||||
if (window.fullScreen)
|
||||
return;
|
||||
|
||||
// In some edgecases it is possible for this to fire before we've initialized.
|
||||
// Don't run now, but don't forget to run it when we do initialize.
|
||||
if (!this._initialized) {
|
||||
this._updateOnInit = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aForce) {
|
||||
// _update is called on resize events, because the window is not ready
|
||||
// after sizemode events. However, we only care about the event when the
|
||||
// sizemode is different from the last time we updated the appearance of
|
||||
// the tabs in the titlebar.
|
||||
let sizemode = document.documentElement.getAttribute("sizemode");
|
||||
if (this._lastSizeMode == sizemode) {
|
||||
return;
|
||||
}
|
||||
let oldSizeMode = this._lastSizeMode;
|
||||
this._lastSizeMode = sizemode;
|
||||
// Don't update right now if we are leaving fullscreen, since the UI is
|
||||
// still changing in the consequent "fullscreen" event. Code there will
|
||||
// call this function again when everything is ready.
|
||||
// See browser-fullScreen.js: FullScreen.toggle and bug 1173768.
|
||||
if (oldSizeMode == "fullscreen") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let allowed = (Object.keys(this._disallowed)).length == 0;
|
||||
|
||||
let titlebar = $("titlebar");
|
||||
let titlebarContent = $("titlebar-content");
|
||||
let menubar = $("toolbar-menubar");
|
||||
|
||||
if (allowed) {
|
||||
// We set the tabsintitlebar attribute first so that our CSS for
|
||||
// tabsintitlebar manifests before we do our measurements.
|
||||
document.documentElement.setAttribute("tabsintitlebar", "true");
|
||||
updateTitlebarDisplay();
|
||||
|
||||
// Try to avoid reflows in this code by calculating dimensions first and
|
||||
// then later set the properties affecting layout together in a batch.
|
||||
|
||||
// Get the full height of the tabs toolbar:
|
||||
let tabsToolbar = $("TabsToolbar");
|
||||
let tabsStyles = window.getComputedStyle(tabsToolbar);
|
||||
let fullTabsHeight = rect(tabsToolbar).height + verticalMargins(tabsStyles);
|
||||
// Buttons first:
|
||||
let captionButtonsBoxWidth = rect($("titlebar-buttonbox-container")).width;
|
||||
|
||||
let secondaryButtonsWidth, menuHeight, fullMenuHeight, menuStyles;
|
||||
if (AppConstants.platform == "macosx") {
|
||||
secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
|
||||
// No need to look up the menubar stuff on OS X:
|
||||
menuHeight = 0;
|
||||
fullMenuHeight = 0;
|
||||
} else {
|
||||
// Otherwise, get the height and margins separately for the menubar
|
||||
menuHeight = rect(menubar).height;
|
||||
menuStyles = window.getComputedStyle(menubar);
|
||||
fullMenuHeight = verticalMargins(menuStyles) + menuHeight;
|
||||
}
|
||||
|
||||
// And get the height of what's in the titlebar:
|
||||
let titlebarContentHeight = rect(titlebarContent).height;
|
||||
|
||||
// Begin setting CSS properties which will cause a reflow
|
||||
|
||||
// If the menubar is around (menuHeight is non-zero), try to adjust
|
||||
// its full height (i.e. including margins) to match the titlebar,
|
||||
// by changing the menubar's bottom padding
|
||||
if (menuHeight) {
|
||||
// Calculate the difference between the titlebar's height and that of the menubar
|
||||
let menuTitlebarDelta = titlebarContentHeight - fullMenuHeight;
|
||||
let paddingBottom;
|
||||
// The titlebar is bigger:
|
||||
if (menuTitlebarDelta > 0) {
|
||||
fullMenuHeight += menuTitlebarDelta;
|
||||
// If there is already padding on the menubar, we need to add that
|
||||
// to the difference so the total padding is correct:
|
||||
if ((paddingBottom = menuStyles.paddingBottom)) {
|
||||
menuTitlebarDelta += parseFloat(paddingBottom);
|
||||
}
|
||||
menubar.style.paddingBottom = menuTitlebarDelta + "px";
|
||||
// The menubar is bigger, but has bottom padding we can remove:
|
||||
} else if (menuTitlebarDelta < 0 && (paddingBottom = menuStyles.paddingBottom)) {
|
||||
let existingPadding = parseFloat(paddingBottom);
|
||||
// menuTitlebarDelta is negative; work out what's left, but don't set negative padding:
|
||||
let desiredPadding = Math.max(0, existingPadding + menuTitlebarDelta);
|
||||
menubar.style.paddingBottom = desiredPadding + "px";
|
||||
// We've changed the menu height now:
|
||||
fullMenuHeight += desiredPadding - existingPadding;
|
||||
}
|
||||
}
|
||||
|
||||
// Next, we calculate how much we need to stretch the titlebar down to
|
||||
// go all the way to the bottom of the tab strip, if necessary.
|
||||
let tabAndMenuHeight = fullTabsHeight + fullMenuHeight;
|
||||
|
||||
if (tabAndMenuHeight > titlebarContentHeight) {
|
||||
// We need to increase the titlebar content's outer height (ie including margins)
|
||||
// to match the tab and menu height:
|
||||
let extraMargin = tabAndMenuHeight - titlebarContentHeight;
|
||||
if (AppConstants.platform != "macosx") {
|
||||
titlebarContent.style.marginBottom = extraMargin + "px";
|
||||
}
|
||||
|
||||
titlebarContentHeight += extraMargin;
|
||||
} else {
|
||||
titlebarContent.style.removeProperty("margin-bottom");
|
||||
}
|
||||
|
||||
// Then add a negative margin to the titlebar, so that the following elements
|
||||
// will overlap it by the lesser of the titlebar height or the tabstrip+menu.
|
||||
let minTitlebarOrTabsHeight = Math.min(titlebarContentHeight, tabAndMenuHeight);
|
||||
titlebar.style.marginBottom = "-" + minTitlebarOrTabsHeight + "px";
|
||||
|
||||
// Finally, size the placeholders:
|
||||
if (AppConstants.platform == "macosx") {
|
||||
this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
|
||||
}
|
||||
this._sizePlaceholder("caption-buttons", captionButtonsBoxWidth);
|
||||
|
||||
} else {
|
||||
document.documentElement.removeAttribute("tabsintitlebar");
|
||||
updateTitlebarDisplay();
|
||||
|
||||
if (AppConstants.platform == "macosx") {
|
||||
let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
|
||||
this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
|
||||
}
|
||||
|
||||
// Reset the margins and padding that might have been modified:
|
||||
titlebarContent.style.marginTop = "";
|
||||
titlebarContent.style.marginBottom = "";
|
||||
titlebar.style.marginBottom = "";
|
||||
menubar.style.paddingBottom = "";
|
||||
}
|
||||
|
||||
ToolbarIconColor.inferFromText();
|
||||
if (CustomizationHandler.isCustomizing()) {
|
||||
gCustomizeMode.updateLWTStyling();
|
||||
}
|
||||
},
|
||||
|
||||
_sizePlaceholder: function (type, width) {
|
||||
Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='"+ type +"']"),
|
||||
function (node) { node.width = width; });
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
this._initialized = false;
|
||||
removeEventListener("resolutionchange", this);
|
||||
Services.prefs.removeObserver(this._prefName, this);
|
||||
this._menuObserver.disconnect();
|
||||
CustomizableUI.removeListener(this);
|
||||
}
|
||||
};
|
||||
|
||||
function updateTitlebarDisplay() {
|
||||
if (AppConstants.platform == "macosx") {
|
||||
// OS X and the other platforms differ enough to necessitate this kind of
|
||||
// special-casing. Like the other platforms where we CAN_DRAW_IN_TITLEBAR,
|
||||
// we draw in the OS X titlebar when putting the tabs up there. However, OS X
|
||||
// also draws in the titlebar when a lightweight theme is applied, regardless
|
||||
// of whether or not the tabs are drawn in the titlebar.
|
||||
if (TabsInTitlebar.enabled) {
|
||||
document.documentElement.setAttribute("chromemargin-nonlwtheme", "0,-1,-1,-1");
|
||||
document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1");
|
||||
document.documentElement.removeAttribute("drawtitle");
|
||||
} else {
|
||||
// We set chromemargin-nonlwtheme to "" instead of removing it as a way of
|
||||
// making sure that LightweightThemeConsumer doesn't take it upon itself to
|
||||
// detect this value again if and when we do a lwtheme state change.
|
||||
document.documentElement.setAttribute("chromemargin-nonlwtheme", "");
|
||||
let isCustomizing = document.documentElement.hasAttribute("customizing");
|
||||
let hasLWTheme = document.documentElement.hasAttribute("lwtheme");
|
||||
let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
|
||||
if ((!hasLWTheme || isCustomizing) && !isPrivate) {
|
||||
document.documentElement.removeAttribute("chromemargin");
|
||||
}
|
||||
document.documentElement.setAttribute("drawtitle", "true");
|
||||
}
|
||||
} else if (TabsInTitlebar.enabled) {
|
||||
// not OS X
|
||||
document.documentElement.setAttribute("chromemargin", "0,2,2,2");
|
||||
} else {
|
||||
document.documentElement.removeAttribute("chromemargin");
|
||||
}
|
||||
}
|
||||
|
||||
function onTitlebarMaxClick() {
|
||||
if (window.windowState == window.STATE_MAXIMIZED)
|
||||
window.restore();
|
||||
else
|
||||
window.maximize();
|
||||
}
|
||||
|
|
@ -1,142 +0,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/. */
|
||||
|
||||
/**
|
||||
* Keeps thumbnails of open web pages up-to-date.
|
||||
*/
|
||||
var gBrowserThumbnails = {
|
||||
/**
|
||||
* Pref that controls whether we can store SSL content on disk
|
||||
*/
|
||||
PREF_DISK_CACHE_SSL: "browser.cache.disk_cache_ssl",
|
||||
|
||||
_captureDelayMS: 1000,
|
||||
|
||||
/**
|
||||
* Used to keep track of disk_cache_ssl preference
|
||||
*/
|
||||
_sslDiskCacheEnabled: null,
|
||||
|
||||
/**
|
||||
* Map of capture() timeouts assigned to their browsers.
|
||||
*/
|
||||
_timeouts: null,
|
||||
|
||||
/**
|
||||
* List of tab events we want to listen for.
|
||||
*/
|
||||
_tabEvents: ["TabClose", "TabSelect"],
|
||||
|
||||
init: function Thumbnails_init() {
|
||||
PageThumbs.addExpirationFilter(this);
|
||||
gBrowser.addTabsProgressListener(this);
|
||||
Services.prefs.addObserver(this.PREF_DISK_CACHE_SSL, this, false);
|
||||
|
||||
this._sslDiskCacheEnabled =
|
||||
Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
|
||||
|
||||
this._tabEvents.forEach(function (aEvent) {
|
||||
gBrowser.tabContainer.addEventListener(aEvent, this, false);
|
||||
}, this);
|
||||
|
||||
this._timeouts = new WeakMap();
|
||||
},
|
||||
|
||||
uninit: function Thumbnails_uninit() {
|
||||
PageThumbs.removeExpirationFilter(this);
|
||||
gBrowser.removeTabsProgressListener(this);
|
||||
Services.prefs.removeObserver(this.PREF_DISK_CACHE_SSL, this);
|
||||
|
||||
this._tabEvents.forEach(function (aEvent) {
|
||||
gBrowser.tabContainer.removeEventListener(aEvent, this, false);
|
||||
}, this);
|
||||
},
|
||||
|
||||
handleEvent: function Thumbnails_handleEvent(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "scroll":
|
||||
let browser = aEvent.currentTarget;
|
||||
if (this._timeouts.has(browser))
|
||||
this._delayedCapture(browser);
|
||||
break;
|
||||
case "TabSelect":
|
||||
this._delayedCapture(aEvent.target.linkedBrowser);
|
||||
break;
|
||||
case "TabClose": {
|
||||
this._clearTimeout(aEvent.target.linkedBrowser);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
observe: function Thumbnails_observe() {
|
||||
this._sslDiskCacheEnabled =
|
||||
Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
|
||||
},
|
||||
|
||||
filterForThumbnailExpiration:
|
||||
function Thumbnails_filterForThumbnailExpiration(aCallback) {
|
||||
aCallback(this._topSiteURLs);
|
||||
},
|
||||
|
||||
/**
|
||||
* State change progress listener for all tabs.
|
||||
*/
|
||||
onStateChange: function Thumbnails_onStateChange(aBrowser, aWebProgress,
|
||||
aRequest, aStateFlags, aStatus) {
|
||||
if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)
|
||||
this._delayedCapture(aBrowser);
|
||||
},
|
||||
|
||||
_capture: function Thumbnails_capture(aBrowser) {
|
||||
// Only capture about:newtab top sites.
|
||||
if (this._topSiteURLs.indexOf(aBrowser.currentURI.spec) == -1)
|
||||
return;
|
||||
this._shouldCapture(aBrowser, function (aResult) {
|
||||
if (aResult) {
|
||||
PageThumbs.captureAndStoreIfStale(aBrowser);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_delayedCapture: function Thumbnails_delayedCapture(aBrowser) {
|
||||
if (this._timeouts.has(aBrowser))
|
||||
clearTimeout(this._timeouts.get(aBrowser));
|
||||
else
|
||||
aBrowser.addEventListener("scroll", this, true);
|
||||
|
||||
let timeout = setTimeout(function () {
|
||||
this._clearTimeout(aBrowser);
|
||||
this._capture(aBrowser);
|
||||
}.bind(this), this._captureDelayMS);
|
||||
|
||||
this._timeouts.set(aBrowser, timeout);
|
||||
},
|
||||
|
||||
_shouldCapture: function Thumbnails_shouldCapture(aBrowser, aCallback) {
|
||||
// Capture only if it's the currently selected tab.
|
||||
if (aBrowser != gBrowser.selectedBrowser) {
|
||||
aCallback(false);
|
||||
return;
|
||||
}
|
||||
PageThumbs.shouldStoreThumbnail(aBrowser, aCallback);
|
||||
},
|
||||
|
||||
get _topSiteURLs() {
|
||||
return NewTabUtils.links.getLinks().reduce((urls, link) => {
|
||||
if (link)
|
||||
urls.push(link.url);
|
||||
return urls;
|
||||
}, []);
|
||||
},
|
||||
|
||||
_clearTimeout: function Thumbnails_clearTimeout(aBrowser) {
|
||||
if (this._timeouts.has(aBrowser)) {
|
||||
aBrowser.removeEventListener("scroll", this, false);
|
||||
clearTimeout(this._timeouts.get(aBrowser));
|
||||
this._timeouts.delete(aBrowser);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -1,177 +0,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/. */
|
||||
|
||||
var TrackingProtection = {
|
||||
// If the user ignores the doorhanger, we stop showing it after some time.
|
||||
PREF_ENABLED_GLOBALLY: "privacy.trackingprotection.enabled",
|
||||
PREF_ENABLED_IN_PRIVATE_WINDOWS: "privacy.trackingprotection.pbmode.enabled",
|
||||
enabledGlobally: false,
|
||||
enabledInPrivateWindows: false,
|
||||
container: null,
|
||||
content: null,
|
||||
icon: null,
|
||||
activeTooltipText: null,
|
||||
disabledTooltipText: null,
|
||||
|
||||
init() {
|
||||
let $ = selector => document.querySelector(selector);
|
||||
this.container = $("#tracking-protection-container");
|
||||
this.content = $("#tracking-protection-content");
|
||||
this.icon = $("#tracking-protection-icon");
|
||||
|
||||
this.updateEnabled();
|
||||
Services.prefs.addObserver(this.PREF_ENABLED_GLOBALLY, this, false);
|
||||
Services.prefs.addObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this, false);
|
||||
|
||||
this.activeTooltipText =
|
||||
gNavigatorBundle.getString("trackingProtection.icon.activeTooltip");
|
||||
this.disabledTooltipText =
|
||||
gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip");
|
||||
|
||||
this.enabledHistogramAdd(this.enabledGlobally);
|
||||
this.disabledPBMHistogramAdd(!this.enabledInPrivateWindows);
|
||||
},
|
||||
|
||||
uninit() {
|
||||
Services.prefs.removeObserver(this.PREF_ENABLED_GLOBALLY, this);
|
||||
Services.prefs.removeObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this);
|
||||
},
|
||||
|
||||
observe() {
|
||||
this.updateEnabled();
|
||||
},
|
||||
|
||||
get enabled() {
|
||||
return this.enabledGlobally ||
|
||||
(this.enabledInPrivateWindows &&
|
||||
PrivateBrowsingUtils.isWindowPrivate(window));
|
||||
},
|
||||
|
||||
updateEnabled() {
|
||||
this.enabledGlobally =
|
||||
Services.prefs.getBoolPref(this.PREF_ENABLED_GLOBALLY);
|
||||
this.enabledInPrivateWindows =
|
||||
Services.prefs.getBoolPref(this.PREF_ENABLED_IN_PRIVATE_WINDOWS);
|
||||
this.container.hidden = !this.enabled;
|
||||
},
|
||||
|
||||
enabledHistogramAdd(value) {
|
||||
if (PrivateBrowsingUtils.isWindowPrivate(window)) {
|
||||
return;
|
||||
}
|
||||
Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").add(value);
|
||||
},
|
||||
|
||||
disabledPBMHistogramAdd(value) {
|
||||
if (PrivateBrowsingUtils.isWindowPrivate(window)) {
|
||||
return;
|
||||
}
|
||||
Services.telemetry.getHistogramById("TRACKING_PROTECTION_PBM_DISABLED").add(value);
|
||||
},
|
||||
|
||||
eventsHistogramAdd(value) {
|
||||
if (PrivateBrowsingUtils.isWindowPrivate(window)) {
|
||||
return;
|
||||
}
|
||||
Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS").add(value);
|
||||
},
|
||||
|
||||
shieldHistogramAdd(value) {
|
||||
if (PrivateBrowsingUtils.isWindowPrivate(window)) {
|
||||
return;
|
||||
}
|
||||
Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD").add(value);
|
||||
},
|
||||
|
||||
onSecurityChange(state, isSimulated) {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only animate the shield if the event was not fired directly from
|
||||
// the tabbrowser (due to a browser change).
|
||||
if (isSimulated) {
|
||||
this.icon.removeAttribute("animate");
|
||||
} else {
|
||||
this.icon.setAttribute("animate", "true");
|
||||
}
|
||||
|
||||
let isBlocking = state & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT;
|
||||
let isAllowing = state & Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT;
|
||||
|
||||
if (isBlocking) {
|
||||
this.icon.setAttribute("tooltiptext", this.activeTooltipText);
|
||||
this.icon.setAttribute("state", "blocked-tracking-content");
|
||||
this.content.setAttribute("state", "blocked-tracking-content");
|
||||
|
||||
this.shieldHistogramAdd(2);
|
||||
} else if (isAllowing) {
|
||||
this.icon.setAttribute("tooltiptext", this.disabledTooltipText);
|
||||
this.icon.setAttribute("state", "loaded-tracking-content");
|
||||
this.content.setAttribute("state", "loaded-tracking-content");
|
||||
|
||||
this.shieldHistogramAdd(1);
|
||||
} else {
|
||||
this.icon.removeAttribute("tooltiptext");
|
||||
this.icon.removeAttribute("state");
|
||||
this.content.removeAttribute("state");
|
||||
|
||||
// We didn't show the shield
|
||||
this.shieldHistogramAdd(0);
|
||||
}
|
||||
|
||||
// Telemetry for state change.
|
||||
this.eventsHistogramAdd(0);
|
||||
},
|
||||
|
||||
disableForCurrentPage() {
|
||||
// Convert document URI into the format used by
|
||||
// nsChannelClassifier::ShouldEnableTrackingProtection.
|
||||
// Any scheme turned into https is correct.
|
||||
let normalizedUrl = Services.io.newURI(
|
||||
"https://" + gBrowser.selectedBrowser.currentURI.hostPort,
|
||||
null, null);
|
||||
|
||||
// Add the current host in the 'trackingprotection' consumer of
|
||||
// the permission manager using a normalized URI. This effectively
|
||||
// places this host on the tracking protection allowlist.
|
||||
if (PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser)) {
|
||||
PrivateBrowsingUtils.addToTrackingAllowlist(normalizedUrl);
|
||||
} else {
|
||||
Services.perms.add(normalizedUrl,
|
||||
"trackingprotection", Services.perms.ALLOW_ACTION);
|
||||
}
|
||||
|
||||
// Telemetry for disable protection.
|
||||
this.eventsHistogramAdd(1);
|
||||
|
||||
// Hide the control center.
|
||||
document.getElementById("identity-popup").hidePopup();
|
||||
|
||||
BrowserReload();
|
||||
},
|
||||
|
||||
enableForCurrentPage() {
|
||||
// Remove the current host from the 'trackingprotection' consumer
|
||||
// of the permission manager. This effectively removes this host
|
||||
// from the tracking protection allowlist.
|
||||
let normalizedUrl = Services.io.newURI(
|
||||
"https://" + gBrowser.selectedBrowser.currentURI.hostPort,
|
||||
null, null);
|
||||
|
||||
if (PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser)) {
|
||||
PrivateBrowsingUtils.removeFromTrackingAllowlist(normalizedUrl);
|
||||
} else {
|
||||
Services.perms.remove(normalizedUrl, "trackingprotection");
|
||||
}
|
||||
|
||||
// Telemetry for enable protection.
|
||||
this.eventsHistogramAdd(2);
|
||||
|
||||
// Hide the control center.
|
||||
document.getElementById("identity-popup").hidePopup();
|
||||
|
||||
BrowserReload();
|
||||
},
|
||||
};
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<stringbundleset id="stringbundleset"/>
|
||||
|
||||
<commandset id="mainCommandSet"/>
|
||||
<commandset id="baseMenuCommandSet"/>
|
||||
<commandset id="placesCommands"/>
|
||||
|
||||
<broadcasterset id="mainBroadcasterSet"/>
|
||||
|
||||
<keyset id="mainKeyset"/>
|
||||
<keyset id="baseMenuKeyset"/>
|
||||
|
||||
<menubar id="main-menubar"/>
|
||||
|
|
@ -1,161 +0,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/. */
|
||||
|
||||
.contentSearchSuggestionTable {
|
||||
background-color: hsla(0,0%,100%,.99);
|
||||
border: 1px solid hsla(0, 0%, 0%, .2);
|
||||
border-top: none;
|
||||
box-shadow: 0 5px 10px hsla(0, 0%, 0%, .1);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: 1001;
|
||||
-moz-user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionTable:-moz-dir(rtl) {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionsList {
|
||||
border-bottom: 1px solid hsl(0, 0%, 92%);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionTable,
|
||||
.contentSearchSuggestionsList {
|
||||
border-spacing: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.contentSearchHeaderRow,
|
||||
.contentSearchSuggestionRow {
|
||||
margin: 0;
|
||||
max-width: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.contentSearchHeaderRow > td > img,
|
||||
.contentSearchSuggestionRow > td > .historyIcon {
|
||||
margin-inline-end: 8px;
|
||||
margin-bottom: -3px;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionTable .historyIcon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
background-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon");
|
||||
}
|
||||
|
||||
.contentSearchSuggestionRow.selected > td > .historyIcon {
|
||||
background-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon-active");
|
||||
}
|
||||
|
||||
.contentSearchHeader > img {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionRow.remote > td > .historyIcon {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.contentSearchSuggestionRow.selected {
|
||||
background-color: Highlight;
|
||||
color: HighlightText;
|
||||
}
|
||||
|
||||
.contentSearchHeader,
|
||||
.contentSearchSuggestionEntry {
|
||||
margin: 0;
|
||||
max-width: inherit;
|
||||
overflow: hidden;
|
||||
padding: 4px 10px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
.contentSearchHeader {
|
||||
background-color: hsl(0, 0%, 97%);
|
||||
color: #666;
|
||||
border-bottom: 1px solid hsl(0, 0%, 92%);
|
||||
}
|
||||
|
||||
.contentSearchSuggestionsContainer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.contentSearchSearchWithHeaderSearchText {
|
||||
white-space: pre;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.contentSearchOneOffItem {
|
||||
-moz-appearance: none;
|
||||
height: 32px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAWCAYAAAABxvaqAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gofECQNNVW2/AAAABBJREFUGFdjOHPmzH8GehEA/KpKg9YTf4AAAAAASUVORK5CYII=');
|
||||
background-repeat: no-repeat;
|
||||
background-position: right center;
|
||||
}
|
||||
|
||||
.contentSearchOneOffItem:-moz-dir(rtl) {
|
||||
background-position: left center;
|
||||
}
|
||||
|
||||
.contentSearchOneOffItem > img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.contentSearchOneOffItem:not(.last-row) {
|
||||
border-bottom: 1px solid hsl(0, 0%, 92%);
|
||||
}
|
||||
|
||||
.contentSearchOneOffItem.end-of-row {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.contentSearchOneOffItem.selected {
|
||||
background-color: Highlight;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.contentSearchOneOffsTable {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.contentSearchSettingsButton {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 32px;
|
||||
border: none;
|
||||
border-top: 1px solid hsla(0, 0%, 0%, .08);
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.contentSearchSettingsButton.selected {
|
||||
background-color: hsl(0, 0%, 90%);
|
||||
}
|
||||
|
||||
.contentSearchSettingsButton:active {
|
||||
background-color: hsl(0, 0%, 85%);
|
||||
}
|
||||
|
|
@ -1,915 +0,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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.ContentSearchUIController = (function () {
|
||||
|
||||
const MAX_DISPLAYED_SUGGESTIONS = 6;
|
||||
const SUGGESTION_ID_PREFIX = "searchSuggestion";
|
||||
const ONE_OFF_ID_PREFIX = "oneOff";
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
/**
|
||||
* Creates a new object that manages search suggestions and their UI for a text
|
||||
* box.
|
||||
*
|
||||
* The UI consists of an html:table that's inserted into the DOM after the given
|
||||
* text box and styled so that it appears as a dropdown below the text box.
|
||||
*
|
||||
* @param inputElement
|
||||
* Search suggestions will be based on the text in this text box.
|
||||
* Assumed to be an html:input. xul:textbox is untested but might work.
|
||||
* @param tableParent
|
||||
* The suggestion table is appended as a child to this element. Since
|
||||
* the table is absolutely positioned and its top and left values are set
|
||||
* to be relative to the top and left of the page, either the parent and
|
||||
* all its ancestors should not be positioned elements (i.e., their
|
||||
* positions should be "static"), or the parent's position should be the
|
||||
* top left of the page.
|
||||
* @param healthReportKey
|
||||
* This will be sent with the search data for FHR to record the search.
|
||||
* @param searchPurpose
|
||||
* Sent with search data, see nsISearchEngine.getSubmission.
|
||||
* @param idPrefix
|
||||
* The IDs of elements created by the object will be prefixed with this
|
||||
* string.
|
||||
*/
|
||||
function ContentSearchUIController(inputElement, tableParent, healthReportKey,
|
||||
searchPurpose, idPrefix="") {
|
||||
this.input = inputElement;
|
||||
this._idPrefix = idPrefix;
|
||||
this._healthReportKey = healthReportKey;
|
||||
this._searchPurpose = searchPurpose;
|
||||
|
||||
let tableID = idPrefix + "searchSuggestionTable";
|
||||
this.input.autocomplete = "off";
|
||||
this.input.setAttribute("aria-autocomplete", "true");
|
||||
this.input.setAttribute("aria-controls", tableID);
|
||||
tableParent.appendChild(this._makeTable(tableID));
|
||||
|
||||
this.input.addEventListener("keypress", this);
|
||||
this.input.addEventListener("input", this);
|
||||
this.input.addEventListener("focus", this);
|
||||
this.input.addEventListener("blur", this);
|
||||
window.addEventListener("ContentSearchService", this);
|
||||
|
||||
this._stickyInputValue = "";
|
||||
this._hideSuggestions();
|
||||
|
||||
this._getSearchEngines();
|
||||
this._getStrings();
|
||||
}
|
||||
|
||||
ContentSearchUIController.prototype = {
|
||||
|
||||
// The timeout (ms) of the remote suggestions. Corresponds to
|
||||
// SearchSuggestionController.remoteTimeout. Uses
|
||||
// SearchSuggestionController's default timeout if falsey.
|
||||
remoteTimeout: undefined,
|
||||
_oneOffButtons: [],
|
||||
// Setting up the one off buttons causes an uninterruptible reflow. If we
|
||||
// receive the list of engines while the newtab page is loading, this reflow
|
||||
// may regress performance - so we set this flag and only set up the buttons
|
||||
// if it's set when the suggestions table is actually opened.
|
||||
_pendingOneOffRefresh: undefined,
|
||||
|
||||
get defaultEngine() {
|
||||
return this._defaultEngine;
|
||||
},
|
||||
|
||||
set defaultEngine(engine) {
|
||||
if (this._defaultEngine && this._defaultEngine.icon) {
|
||||
URL.revokeObjectURL(this._defaultEngine.icon);
|
||||
}
|
||||
let icon;
|
||||
if (engine.iconBuffer) {
|
||||
icon = this._getFaviconURIFromBuffer(engine.iconBuffer);
|
||||
}
|
||||
else {
|
||||
icon = this._getImageURIForCurrentResolution(
|
||||
"chrome://mozapps/skin/places/defaultFavicon.png");
|
||||
}
|
||||
this._defaultEngine = {
|
||||
name: engine.name,
|
||||
icon: icon,
|
||||
};
|
||||
this._updateDefaultEngineHeader();
|
||||
|
||||
if (engine && document.activeElement == this.input) {
|
||||
this._speculativeConnect();
|
||||
}
|
||||
},
|
||||
|
||||
get engines() {
|
||||
return this._engines;
|
||||
},
|
||||
|
||||
set engines(val) {
|
||||
this._engines = val;
|
||||
this._pendingOneOffRefresh = true;
|
||||
},
|
||||
|
||||
// The selectedIndex is the index of the element with the "selected" class in
|
||||
// the list obtained by concatenating the suggestion rows, one-off buttons, and
|
||||
// search settings button.
|
||||
get selectedIndex() {
|
||||
let allElts = [...this._suggestionsList.children,
|
||||
...this._oneOffButtons,
|
||||
document.getElementById("contentSearchSettingsButton")];
|
||||
for (let i = 0; i < allElts.length; ++i) {
|
||||
let elt = allElts[i];
|
||||
if (elt.classList.contains("selected")) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
set selectedIndex(idx) {
|
||||
// Update the table's rows, and the input when there is a selection.
|
||||
this._table.removeAttribute("aria-activedescendant");
|
||||
this.input.removeAttribute("aria-activedescendant");
|
||||
|
||||
let allElts = [...this._suggestionsList.children,
|
||||
...this._oneOffButtons,
|
||||
document.getElementById("contentSearchSettingsButton")];
|
||||
// If we are selecting a suggestion and a one-off is selected, don't deselect it.
|
||||
let excludeIndex = idx < this.numSuggestions && this.selectedButtonIndex > -1 ?
|
||||
this.numSuggestions + this.selectedButtonIndex : -1;
|
||||
for (let i = 0; i < allElts.length; ++i) {
|
||||
let elt = allElts[i];
|
||||
let ariaSelectedElt = i < this.numSuggestions ? elt.firstChild : elt;
|
||||
if (i == idx) {
|
||||
elt.classList.add("selected");
|
||||
ariaSelectedElt.setAttribute("aria-selected", "true");
|
||||
this.input.setAttribute("aria-activedescendant", ariaSelectedElt.id);
|
||||
}
|
||||
else if (i != excludeIndex) {
|
||||
elt.classList.remove("selected");
|
||||
ariaSelectedElt.setAttribute("aria-selected", "false");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get selectedButtonIndex() {
|
||||
let elts = [...this._oneOffButtons,
|
||||
document.getElementById("contentSearchSettingsButton")];
|
||||
for (let i = 0; i < elts.length; ++i) {
|
||||
if (elts[i].classList.contains("selected")) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
set selectedButtonIndex(idx) {
|
||||
let elts = [...this._oneOffButtons,
|
||||
document.getElementById("contentSearchSettingsButton")];
|
||||
for (let i = 0; i < elts.length; ++i) {
|
||||
let elt = elts[i];
|
||||
if (i == idx) {
|
||||
elt.classList.add("selected");
|
||||
elt.setAttribute("aria-selected", "true");
|
||||
}
|
||||
else {
|
||||
elt.classList.remove("selected");
|
||||
elt.setAttribute("aria-selected", "false");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get selectedEngineName() {
|
||||
let selectedElt = this._oneOffsTable.querySelector(".selected");
|
||||
if (selectedElt) {
|
||||
return selectedElt.engineName;
|
||||
}
|
||||
return this.defaultEngine.name;
|
||||
},
|
||||
|
||||
get numSuggestions() {
|
||||
return this._suggestionsList.children.length;
|
||||
},
|
||||
|
||||
selectAndUpdateInput: function (idx) {
|
||||
this.selectedIndex = idx;
|
||||
let newValue = this.suggestionAtIndex(idx) || this._stickyInputValue;
|
||||
// Setting the input value when the value has not changed commits the current
|
||||
// IME composition, which we don't want to do.
|
||||
if (this.input.value != newValue) {
|
||||
this.input.value = newValue;
|
||||
}
|
||||
this._updateSearchWithHeader();
|
||||
},
|
||||
|
||||
suggestionAtIndex: function (idx) {
|
||||
let row = this._suggestionsList.children[idx];
|
||||
return row ? row.textContent : null;
|
||||
},
|
||||
|
||||
deleteSuggestionAtIndex: function (idx) {
|
||||
// Only form history suggestions can be deleted.
|
||||
if (this.isFormHistorySuggestionAtIndex(idx)) {
|
||||
let suggestionStr = this.suggestionAtIndex(idx);
|
||||
this._sendMsg("RemoveFormHistoryEntry", suggestionStr);
|
||||
this._suggestionsList.children[idx].remove();
|
||||
this.selectAndUpdateInput(-1);
|
||||
}
|
||||
},
|
||||
|
||||
isFormHistorySuggestionAtIndex: function (idx) {
|
||||
let row = this._suggestionsList.children[idx];
|
||||
return row && row.classList.contains("formHistory");
|
||||
},
|
||||
|
||||
addInputValueToFormHistory: function () {
|
||||
this._sendMsg("AddFormHistoryEntry", this.input.value);
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
this["_on" + event.type[0].toUpperCase() + event.type.substr(1)](event);
|
||||
},
|
||||
|
||||
_onCommand: function(aEvent) {
|
||||
if (this.selectedButtonIndex == this._oneOffButtons.length) {
|
||||
// Settings button was selected.
|
||||
this._sendMsg("ManageEngines");
|
||||
return;
|
||||
}
|
||||
|
||||
this.search(aEvent);
|
||||
|
||||
if (aEvent) {
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
search: function (aEvent) {
|
||||
if (!this.defaultEngine) {
|
||||
return; // Not initialized yet.
|
||||
}
|
||||
|
||||
let searchText = this.input;
|
||||
let searchTerms;
|
||||
if (this._table.hidden ||
|
||||
aEvent.originalTarget.id == "contentSearchDefaultEngineHeader" ||
|
||||
aEvent instanceof KeyboardEvent) {
|
||||
searchTerms = searchText.value;
|
||||
}
|
||||
else {
|
||||
searchTerms = this.suggestionAtIndex(this.selectedIndex) || searchText.value;
|
||||
}
|
||||
// Send an event that will perform a search and Firefox Health Report will
|
||||
// record that a search from the healthReportKey passed to the constructor.
|
||||
let eventData = {
|
||||
engineName: this.selectedEngineName,
|
||||
searchString: searchTerms,
|
||||
healthReportKey: this._healthReportKey,
|
||||
searchPurpose: this._searchPurpose,
|
||||
originalEvent: {
|
||||
shiftKey: aEvent.shiftKey,
|
||||
ctrlKey: aEvent.ctrlKey,
|
||||
metaKey: aEvent.metaKey,
|
||||
altKey: aEvent.altKey,
|
||||
},
|
||||
};
|
||||
if ("button" in aEvent) {
|
||||
eventData.originalEvent.button = aEvent.button;
|
||||
}
|
||||
|
||||
if (this.suggestionAtIndex(this.selectedIndex)) {
|
||||
eventData.selection = {
|
||||
index: this.selectedIndex,
|
||||
kind: undefined,
|
||||
};
|
||||
if (aEvent instanceof MouseEvent) {
|
||||
eventData.selection.kind = "mouse";
|
||||
} else if (aEvent instanceof KeyboardEvent) {
|
||||
eventData.selection.kind = "key";
|
||||
}
|
||||
}
|
||||
|
||||
this._sendMsg("Search", eventData);
|
||||
this.addInputValueToFormHistory();
|
||||
},
|
||||
|
||||
_onInput: function () {
|
||||
if (!this.input.value) {
|
||||
this._stickyInputValue = "";
|
||||
this._hideSuggestions();
|
||||
}
|
||||
else if (this.input.value != this._stickyInputValue) {
|
||||
// Only fetch new suggestions if the input value has changed.
|
||||
this._getSuggestions();
|
||||
this.selectAndUpdateInput(-1);
|
||||
}
|
||||
this._updateSearchWithHeader();
|
||||
},
|
||||
|
||||
_onKeypress: function (event) {
|
||||
let selectedIndexDelta = 0;
|
||||
let selectedSuggestionDelta = 0;
|
||||
let selectedOneOffDelta = 0;
|
||||
|
||||
switch (event.keyCode) {
|
||||
case event.DOM_VK_UP:
|
||||
if (this._table.hidden) {
|
||||
return;
|
||||
}
|
||||
if (event.getModifierState("Accel")) {
|
||||
if (event.shiftKey) {
|
||||
selectedSuggestionDelta = -1;
|
||||
break;
|
||||
}
|
||||
this._cycleCurrentEngine(true);
|
||||
break;
|
||||
}
|
||||
if (event.altKey) {
|
||||
selectedOneOffDelta = -1;
|
||||
break;
|
||||
}
|
||||
selectedIndexDelta = -1;
|
||||
break;
|
||||
case event.DOM_VK_DOWN:
|
||||
if (this._table.hidden) {
|
||||
this._getSuggestions();
|
||||
return;
|
||||
}
|
||||
if (event.getModifierState("Accel")) {
|
||||
if (event.shiftKey) {
|
||||
selectedSuggestionDelta = 1;
|
||||
break;
|
||||
}
|
||||
this._cycleCurrentEngine(false);
|
||||
break;
|
||||
}
|
||||
if (event.altKey) {
|
||||
selectedOneOffDelta = 1;
|
||||
break;
|
||||
}
|
||||
selectedIndexDelta = 1;
|
||||
break;
|
||||
case event.DOM_VK_TAB:
|
||||
if (this._table.hidden) {
|
||||
return;
|
||||
}
|
||||
// Shift+tab when either the first or no one-off is selected, as well as
|
||||
// tab when the settings button is selected, should change focus as normal.
|
||||
if ((this.selectedButtonIndex <= 0 && event.shiftKey) ||
|
||||
this.selectedButtonIndex == this._oneOffButtons.length && !event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
selectedOneOffDelta = event.shiftKey ? -1 : 1;
|
||||
break;
|
||||
case event.DOM_VK_RIGHT:
|
||||
// Allow normal caret movement until the caret is at the end of the input.
|
||||
if (this.input.selectionStart != this.input.selectionEnd ||
|
||||
this.input.selectionEnd != this.input.value.length) {
|
||||
return;
|
||||
}
|
||||
if (this.numSuggestions && this.selectedIndex >= 0 &&
|
||||
this.selectedIndex < this.numSuggestions) {
|
||||
this.input.value = this.suggestionAtIndex(this.selectedIndex);
|
||||
this.input.setAttribute("selection-index", this.selectedIndex);
|
||||
this.input.setAttribute("selection-kind", "key");
|
||||
} else {
|
||||
// If we didn't select anything, make sure to remove the attributes
|
||||
// in case they were populated last time.
|
||||
this.input.removeAttribute("selection-index");
|
||||
this.input.removeAttribute("selection-kind");
|
||||
}
|
||||
this._stickyInputValue = this.input.value;
|
||||
this._hideSuggestions();
|
||||
return;
|
||||
case event.DOM_VK_RETURN:
|
||||
this._onCommand(event);
|
||||
return;
|
||||
case event.DOM_VK_DELETE:
|
||||
if (this.selectedIndex >= 0) {
|
||||
this.deleteSuggestionAtIndex(this.selectedIndex);
|
||||
}
|
||||
return;
|
||||
case event.DOM_VK_ESCAPE:
|
||||
if (!this._table.hidden) {
|
||||
this._hideSuggestions();
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
let currentIndex = this.selectedIndex;
|
||||
if (selectedIndexDelta) {
|
||||
let newSelectedIndex = currentIndex + selectedIndexDelta;
|
||||
if (newSelectedIndex < -1) {
|
||||
newSelectedIndex = this.numSuggestions + this._oneOffButtons.length;
|
||||
}
|
||||
// If are moving up from the first one off, we have to deselect the one off
|
||||
// manually because the selectedIndex setter tries to exclude the selected
|
||||
// one-off (which is desirable for accel+shift+up/down).
|
||||
if (currentIndex == this.numSuggestions && selectedIndexDelta == -1) {
|
||||
this.selectedButtonIndex = -1;
|
||||
}
|
||||
this.selectAndUpdateInput(newSelectedIndex);
|
||||
}
|
||||
|
||||
else if (selectedSuggestionDelta) {
|
||||
let newSelectedIndex;
|
||||
if (currentIndex >= this.numSuggestions || currentIndex == -1) {
|
||||
// No suggestion already selected, select the first/last one appropriately.
|
||||
newSelectedIndex = selectedSuggestionDelta == 1 ?
|
||||
0 : this.numSuggestions - 1;
|
||||
}
|
||||
else {
|
||||
newSelectedIndex = currentIndex + selectedSuggestionDelta;
|
||||
}
|
||||
if (newSelectedIndex >= this.numSuggestions) {
|
||||
newSelectedIndex = -1;
|
||||
}
|
||||
this.selectAndUpdateInput(newSelectedIndex);
|
||||
}
|
||||
|
||||
else if (selectedOneOffDelta) {
|
||||
let newSelectedIndex;
|
||||
let currentButton = this.selectedButtonIndex;
|
||||
if (currentButton == -1 || currentButton == this._oneOffButtons.length) {
|
||||
// No one-off already selected, select the first/last one appropriately.
|
||||
newSelectedIndex = selectedOneOffDelta == 1 ?
|
||||
0 : this._oneOffButtons.length - 1;
|
||||
}
|
||||
else {
|
||||
newSelectedIndex = currentButton + selectedOneOffDelta;
|
||||
}
|
||||
// Allow selection of the settings button via the tab key.
|
||||
if (newSelectedIndex == this._oneOffButtons.length &&
|
||||
event.keyCode != event.DOM_VK_TAB) {
|
||||
newSelectedIndex = -1;
|
||||
}
|
||||
this.selectedButtonIndex = newSelectedIndex;
|
||||
}
|
||||
|
||||
// Prevent the input's caret from moving.
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
_currentEngineIndex: -1,
|
||||
_cycleCurrentEngine: function (aReverse) {
|
||||
if ((this._currentEngineIndex == this._engines.length - 1 && !aReverse) ||
|
||||
(this._currentEngineIndex == 0 && aReverse)) {
|
||||
return;
|
||||
}
|
||||
this._currentEngineIndex += aReverse ? -1 : 1;
|
||||
let engineName = this._engines[this._currentEngineIndex].name;
|
||||
this._sendMsg("SetCurrentEngine", engineName);
|
||||
},
|
||||
|
||||
_onFocus: function () {
|
||||
if (this._mousedown) {
|
||||
return;
|
||||
}
|
||||
// When the input box loses focus to something in our table, we refocus it
|
||||
// immediately. This causes the focus highlight to flicker, so we set a
|
||||
// custom attribute which consumers should use for focus highlighting. This
|
||||
// attribute is removed only when we do not immediately refocus the input
|
||||
// box, thus eliminating flicker.
|
||||
this.input.setAttribute("keepfocus", "true");
|
||||
this._speculativeConnect();
|
||||
},
|
||||
|
||||
_onBlur: function () {
|
||||
if (this._mousedown) {
|
||||
// At this point, this.input has lost focus, but a new element has not yet
|
||||
// received it. If we re-focus this.input directly, the new element will
|
||||
// steal focus immediately, so we queue it instead.
|
||||
setTimeout(() => this.input.focus(), 0);
|
||||
return;
|
||||
}
|
||||
this.input.removeAttribute("keepfocus");
|
||||
this._hideSuggestions();
|
||||
},
|
||||
|
||||
_onMousemove: function (event) {
|
||||
let idx = this._indexOfTableItem(event.target);
|
||||
if (idx >= this.numSuggestions) {
|
||||
this.selectedButtonIndex = idx - this.numSuggestions;
|
||||
return;
|
||||
}
|
||||
this.selectedIndex = idx;
|
||||
},
|
||||
|
||||
_onMouseup: function (event) {
|
||||
if (event.button == 2) {
|
||||
return;
|
||||
}
|
||||
this._onCommand(event);
|
||||
},
|
||||
|
||||
_onMouseout: function (event) {
|
||||
// We only deselect one-off buttons and the settings button when they are
|
||||
// moused out.
|
||||
let idx = this._indexOfTableItem(event.originalTarget);
|
||||
if (idx >= this.numSuggestions) {
|
||||
this.selectedButtonIndex = -1;
|
||||
}
|
||||
},
|
||||
|
||||
_onClick: function (event) {
|
||||
this._onMouseup(event);
|
||||
},
|
||||
|
||||
_onContentSearchService: function (event) {
|
||||
let methodName = "_onMsg" + event.detail.type;
|
||||
if (methodName in this) {
|
||||
this[methodName](event.detail.data);
|
||||
}
|
||||
},
|
||||
|
||||
_onMsgFocusInput: function (event) {
|
||||
this.input.focus();
|
||||
},
|
||||
|
||||
_onMsgSuggestions: function (suggestions) {
|
||||
// Ignore the suggestions if their search string or engine doesn't match
|
||||
// ours. Due to the async nature of message passing, this can easily happen
|
||||
// when the user types quickly.
|
||||
if (this._stickyInputValue != suggestions.searchString ||
|
||||
this.defaultEngine.name != suggestions.engineName) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._clearSuggestionRows();
|
||||
|
||||
// Position and size the table.
|
||||
let { left } = this.input.getBoundingClientRect();
|
||||
this._table.style.top = this.input.offsetHeight + "px";
|
||||
this._table.style.minWidth = this.input.offsetWidth + "px";
|
||||
this._table.style.maxWidth = (window.innerWidth - left - 40) + "px";
|
||||
|
||||
// Add the suggestions to the table.
|
||||
let searchWords =
|
||||
new Set(suggestions.searchString.trim().toLowerCase().split(/\s+/));
|
||||
for (let i = 0; i < MAX_DISPLAYED_SUGGESTIONS; i++) {
|
||||
let type, idx;
|
||||
if (i < suggestions.formHistory.length) {
|
||||
[type, idx] = ["formHistory", i];
|
||||
}
|
||||
else {
|
||||
let j = i - suggestions.formHistory.length;
|
||||
if (j < suggestions.remote.length) {
|
||||
[type, idx] = ["remote", j];
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this._suggestionsList.appendChild(
|
||||
this._makeTableRow(type, suggestions[type][idx], i, searchWords));
|
||||
}
|
||||
|
||||
if (this._table.hidden) {
|
||||
this.selectedIndex = -1;
|
||||
if (this._pendingOneOffRefresh) {
|
||||
this._setUpOneOffButtons();
|
||||
delete this._pendingOneOffRefresh;
|
||||
}
|
||||
this._currentEngineIndex =
|
||||
this._engines.findIndex(aEngine => aEngine.name == this.defaultEngine.name);
|
||||
this._table.hidden = false;
|
||||
this.input.setAttribute("aria-expanded", "true");
|
||||
this._originalDefaultEngine = {
|
||||
name: this.defaultEngine.name,
|
||||
icon: this.defaultEngine.icon,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
_onMsgSuggestionsCancelled: function () {
|
||||
if (!this._table.hidden) {
|
||||
this._hideSuggestions();
|
||||
}
|
||||
},
|
||||
|
||||
_onMsgState: function (state) {
|
||||
this.engines = state.engines;
|
||||
// No point updating the default engine (and the header) if there's no change.
|
||||
if (this.defaultEngine &&
|
||||
this.defaultEngine.name == state.currentEngine.name &&
|
||||
this.defaultEngine.icon == state.currentEngine.icon) {
|
||||
return;
|
||||
}
|
||||
this.defaultEngine = state.currentEngine;
|
||||
},
|
||||
|
||||
_onMsgCurrentState: function (state) {
|
||||
this._onMsgState(state);
|
||||
},
|
||||
|
||||
_onMsgCurrentEngine: function (engine) {
|
||||
this.defaultEngine = engine;
|
||||
this._pendingOneOffRefresh = true;
|
||||
},
|
||||
|
||||
_onMsgStrings: function (strings) {
|
||||
this._strings = strings;
|
||||
this._updateDefaultEngineHeader();
|
||||
this._updateSearchWithHeader();
|
||||
document.getElementById("contentSearchSettingsButton").textContent =
|
||||
this._strings.searchSettings;
|
||||
this.input.setAttribute("placeholder", this._strings.searchPlaceholder);
|
||||
},
|
||||
|
||||
_updateDefaultEngineHeader: function () {
|
||||
let header = document.getElementById("contentSearchDefaultEngineHeader");
|
||||
header.firstChild.setAttribute("src", this.defaultEngine.icon);
|
||||
if (!this._strings) {
|
||||
return;
|
||||
}
|
||||
while (header.firstChild.nextSibling) {
|
||||
header.firstChild.nextSibling.remove();
|
||||
}
|
||||
header.appendChild(document.createTextNode(
|
||||
this._strings.searchHeader.replace("%S", this.defaultEngine.name)));
|
||||
},
|
||||
|
||||
_updateSearchWithHeader: function () {
|
||||
if (!this._strings) {
|
||||
return;
|
||||
}
|
||||
let searchWithHeader = document.getElementById("contentSearchSearchWithHeader");
|
||||
if (this.input.value) {
|
||||
searchWithHeader.innerHTML = this._strings.searchForSomethingWith;
|
||||
searchWithHeader.querySelector('.contentSearchSearchWithHeaderSearchText').textContent = this.input.value;
|
||||
} else {
|
||||
searchWithHeader.textContent = this._strings.searchWithHeader;
|
||||
}
|
||||
},
|
||||
|
||||
_speculativeConnect: function () {
|
||||
if (this.defaultEngine) {
|
||||
this._sendMsg("SpeculativeConnect", this.defaultEngine.name);
|
||||
}
|
||||
},
|
||||
|
||||
_makeTableRow: function (type, suggestionStr, currentRow, searchWords) {
|
||||
let row = document.createElementNS(HTML_NS, "tr");
|
||||
row.dir = "auto";
|
||||
row.classList.add("contentSearchSuggestionRow");
|
||||
row.classList.add(type);
|
||||
row.setAttribute("role", "presentation");
|
||||
row.addEventListener("mousemove", this);
|
||||
row.addEventListener("mouseup", this);
|
||||
|
||||
let entry = document.createElementNS(HTML_NS, "td");
|
||||
let img = document.createElementNS(HTML_NS, "div");
|
||||
img.setAttribute("class", "historyIcon");
|
||||
entry.appendChild(img);
|
||||
entry.classList.add("contentSearchSuggestionEntry");
|
||||
entry.setAttribute("role", "option");
|
||||
entry.id = this._idPrefix + SUGGESTION_ID_PREFIX + currentRow;
|
||||
entry.setAttribute("aria-selected", "false");
|
||||
|
||||
let suggestionWords = suggestionStr.trim().toLowerCase().split(/\s+/);
|
||||
for (let i = 0; i < suggestionWords.length; i++) {
|
||||
let word = suggestionWords[i];
|
||||
let wordSpan = document.createElementNS(HTML_NS, "span");
|
||||
if (searchWords.has(word)) {
|
||||
wordSpan.classList.add("typed");
|
||||
}
|
||||
wordSpan.textContent = word;
|
||||
entry.appendChild(wordSpan);
|
||||
if (i < suggestionWords.length - 1) {
|
||||
entry.appendChild(document.createTextNode(" "));
|
||||
}
|
||||
}
|
||||
|
||||
row.appendChild(entry);
|
||||
return row;
|
||||
},
|
||||
|
||||
// Converts favicon array buffer into a data URI.
|
||||
_getFaviconURIFromBuffer: function (buffer) {
|
||||
let blob = new Blob([buffer]);
|
||||
return URL.createObjectURL(blob);
|
||||
},
|
||||
|
||||
// Adds "@2x" to the name of the given PNG url for "retina" screens.
|
||||
_getImageURIForCurrentResolution: function (uri) {
|
||||
if (window.devicePixelRatio > 1) {
|
||||
return uri.replace(/\.png$/, "@2x.png");
|
||||
}
|
||||
return uri;
|
||||
},
|
||||
|
||||
_getSearchEngines: function () {
|
||||
this._sendMsg("GetState");
|
||||
},
|
||||
|
||||
_getStrings: function () {
|
||||
this._sendMsg("GetStrings");
|
||||
},
|
||||
|
||||
_getSuggestions: function () {
|
||||
this._stickyInputValue = this.input.value;
|
||||
if (this.defaultEngine) {
|
||||
this._sendMsg("GetSuggestions", {
|
||||
engineName: this.defaultEngine.name,
|
||||
searchString: this.input.value,
|
||||
remoteTimeout: this.remoteTimeout,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_clearSuggestionRows: function() {
|
||||
while (this._suggestionsList.firstElementChild) {
|
||||
this._suggestionsList.firstElementChild.remove();
|
||||
}
|
||||
},
|
||||
|
||||
_hideSuggestions: function () {
|
||||
this.input.setAttribute("aria-expanded", "false");
|
||||
this.selectedIndex = -1;
|
||||
this.selectedButtonIndex = -1;
|
||||
this._currentEngineIndex = -1;
|
||||
this._table.hidden = true;
|
||||
},
|
||||
|
||||
_indexOfTableItem: function (elt) {
|
||||
if (elt.classList.contains("contentSearchOneOffItem")) {
|
||||
return this.numSuggestions + this._oneOffButtons.indexOf(elt);
|
||||
}
|
||||
if (elt.classList.contains("contentSearchSettingsButton")) {
|
||||
return this.numSuggestions + this._oneOffButtons.length;
|
||||
}
|
||||
while (elt && elt.localName != "tr") {
|
||||
elt = elt.parentNode;
|
||||
}
|
||||
if (!elt) {
|
||||
throw new Error("Element is not a row");
|
||||
}
|
||||
return elt.rowIndex;
|
||||
},
|
||||
|
||||
_makeTable: function (id) {
|
||||
this._table = document.createElementNS(HTML_NS, "table");
|
||||
this._table.id = id;
|
||||
this._table.hidden = true;
|
||||
this._table.classList.add("contentSearchSuggestionTable");
|
||||
this._table.setAttribute("role", "presentation");
|
||||
|
||||
// When the search input box loses focus, we want to immediately give focus
|
||||
// back to it if the blur was because the user clicked somewhere in the table.
|
||||
// onBlur uses the _mousedown flag to detect this.
|
||||
this._table.addEventListener("mousedown", () => { this._mousedown = true; });
|
||||
document.addEventListener("mouseup", () => { delete this._mousedown; });
|
||||
|
||||
// Deselect the selected element on mouseout if it wasn't a suggestion.
|
||||
this._table.addEventListener("mouseout", this);
|
||||
|
||||
// If a search is loaded in the same tab, ensure the suggestions dropdown
|
||||
// is hidden immediately when the page starts loading and not when it first
|
||||
// appears, in order to provide timely feedback to the user.
|
||||
window.addEventListener("beforeunload", () => { this._hideSuggestions(); });
|
||||
|
||||
let headerRow = document.createElementNS(HTML_NS, "tr");
|
||||
let header = document.createElementNS(HTML_NS, "td");
|
||||
headerRow.setAttribute("class", "contentSearchHeaderRow");
|
||||
header.setAttribute("class", "contentSearchHeader");
|
||||
let iconImg = document.createElementNS(HTML_NS, "img");
|
||||
header.appendChild(iconImg);
|
||||
header.id = "contentSearchDefaultEngineHeader";
|
||||
headerRow.appendChild(header);
|
||||
headerRow.addEventListener("click", this);
|
||||
this._table.appendChild(headerRow);
|
||||
|
||||
let row = document.createElementNS(HTML_NS, "tr");
|
||||
row.setAttribute("class", "contentSearchSuggestionsContainer");
|
||||
let cell = document.createElementNS(HTML_NS, "td");
|
||||
cell.setAttribute("class", "contentSearchSuggestionsContainer");
|
||||
this._suggestionsList = document.createElementNS(HTML_NS, "table");
|
||||
this._suggestionsList.setAttribute("class", "contentSearchSuggestionsList");
|
||||
cell.appendChild(this._suggestionsList);
|
||||
row.appendChild(cell);
|
||||
this._table.appendChild(row);
|
||||
this._suggestionsList.setAttribute("role", "listbox");
|
||||
|
||||
this._oneOffsTable = document.createElementNS(HTML_NS, "table");
|
||||
this._oneOffsTable.setAttribute("class", "contentSearchOneOffsTable");
|
||||
this._oneOffsTable.classList.add("contentSearchSuggestionsContainer");
|
||||
this._oneOffsTable.setAttribute("role", "group");
|
||||
this._table.appendChild(this._oneOffsTable);
|
||||
|
||||
headerRow = document.createElementNS(HTML_NS, "tr");
|
||||
header = document.createElementNS(HTML_NS, "td");
|
||||
headerRow.setAttribute("class", "contentSearchHeaderRow");
|
||||
header.setAttribute("class", "contentSearchHeader");
|
||||
headerRow.appendChild(header);
|
||||
header.id = "contentSearchSearchWithHeader";
|
||||
this._oneOffsTable.appendChild(headerRow);
|
||||
|
||||
let button = document.createElementNS(HTML_NS, "button");
|
||||
button.setAttribute("class", "contentSearchSettingsButton");
|
||||
button.classList.add("contentSearchHeaderRow");
|
||||
button.classList.add("contentSearchHeader");
|
||||
button.id = "contentSearchSettingsButton";
|
||||
button.addEventListener("click", this);
|
||||
button.addEventListener("mousemove", this);
|
||||
this._table.appendChild(button);
|
||||
|
||||
return this._table;
|
||||
},
|
||||
|
||||
_setUpOneOffButtons: function () {
|
||||
// Sometimes we receive a CurrentEngine message from the ContentSearch service
|
||||
// before we've received a State message - i.e. before we have our engines.
|
||||
if (!this._engines) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (this._oneOffsTable.firstChild.nextSibling) {
|
||||
this._oneOffsTable.firstChild.nextSibling.remove();
|
||||
}
|
||||
|
||||
this._oneOffButtons = [];
|
||||
|
||||
let engines = this._engines.filter(aEngine => aEngine.name != this.defaultEngine.name)
|
||||
.filter(aEngine => !aEngine.hidden);
|
||||
if (!engines.length) {
|
||||
this._oneOffsTable.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const kDefaultButtonWidth = 49; // 48px + 1px border.
|
||||
let rowWidth = this.input.offsetWidth - 2; // 2px border.
|
||||
let enginesPerRow = Math.floor(rowWidth / kDefaultButtonWidth);
|
||||
let buttonWidth = Math.floor(rowWidth / enginesPerRow);
|
||||
|
||||
let row = document.createElementNS(HTML_NS, "tr");
|
||||
let cell = document.createElementNS(HTML_NS, "td");
|
||||
row.setAttribute("class", "contentSearchSuggestionsContainer");
|
||||
cell.setAttribute("class", "contentSearchSuggestionsContainer");
|
||||
|
||||
for (let i = 0; i < engines.length; ++i) {
|
||||
let engine = engines[i];
|
||||
if (i > 0 && i % enginesPerRow == 0) {
|
||||
row.appendChild(cell);
|
||||
this._oneOffsTable.appendChild(row);
|
||||
row = document.createElementNS(HTML_NS, "tr");
|
||||
cell = document.createElementNS(HTML_NS, "td");
|
||||
row.setAttribute("class", "contentSearchSuggestionsContainer");
|
||||
cell.setAttribute("class", "contentSearchSuggestionsContainer");
|
||||
}
|
||||
let button = document.createElementNS(HTML_NS, "button");
|
||||
button.setAttribute("class", "contentSearchOneOffItem");
|
||||
let img = document.createElementNS(HTML_NS, "img");
|
||||
let uri;
|
||||
if (engine.iconBuffer) {
|
||||
uri = this._getFaviconURIFromBuffer(engine.iconBuffer);
|
||||
}
|
||||
else {
|
||||
uri = this._getImageURIForCurrentResolution(
|
||||
"chrome://browser/skin/search-engine-placeholder.png");
|
||||
}
|
||||
img.setAttribute("src", uri);
|
||||
img.addEventListener("load", function imgLoad() {
|
||||
img.removeEventListener("load", imgLoad);
|
||||
URL.revokeObjectURL(uri);
|
||||
});
|
||||
button.appendChild(img);
|
||||
button.style.width = buttonWidth + "px";
|
||||
button.setAttribute("title", engine.name);
|
||||
|
||||
button.engineName = engine.name;
|
||||
button.addEventListener("click", this);
|
||||
button.addEventListener("mousemove", this);
|
||||
|
||||
if (engines.length - i <= enginesPerRow - (i % enginesPerRow)) {
|
||||
button.classList.add("last-row");
|
||||
}
|
||||
|
||||
if ((i + 1) % enginesPerRow == 0) {
|
||||
button.classList.add("end-of-row");
|
||||
}
|
||||
|
||||
button.id = ONE_OFF_ID_PREFIX + i;
|
||||
cell.appendChild(button);
|
||||
this._oneOffButtons.push(button);
|
||||
}
|
||||
row.appendChild(cell);
|
||||
this._oneOffsTable.appendChild(row);
|
||||
this._oneOffsTable.hidden = false;
|
||||
},
|
||||
|
||||
_sendMsg: function (type, data=null) {
|
||||
dispatchEvent(new CustomEvent("ContentSearchClient", {
|
||||
detail: {
|
||||
type: type,
|
||||
data: data,
|
||||
},
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
return ContentSearchUIController;
|
||||
})();
|
||||
|
Before Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 260 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 509 B |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 287 KiB |
|
Before Width: | Height: | Size: 896 B |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 375 KiB |
|
Before Width: | Height: | Size: 751 KiB |
|
Before Width: | Height: | Size: 731 B |
|
Before Width: | Height: | Size: 93 KiB |