2014-12-10 23:43:13 +00:00
|
|
|
/* 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/. */
|
|
|
|
|
2015-09-15 18:19:45 +00:00
|
|
|
var Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
|
2014-12-10 23:43:13 +00:00
|
|
|
|
2015-10-02 23:24:31 +00:00
|
|
|
Cu.import("resource://services-common/utils.js"); /*global: CommonUtils */
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
2014-12-10 23:43:13 +00:00
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
2015-07-27 23:11:33 +00:00
|
|
|
Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
|
2014-12-10 23:43:13 +00:00
|
|
|
|
2015-09-23 09:42:18 +00:00
|
|
|
XPCOMUtils.defineLazyGetter(window, "gChromeWin", () =>
|
2014-12-10 23:43:13 +00:00
|
|
|
window.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIWebNavigation)
|
|
|
|
.QueryInterface(Ci.nsIDocShellTreeItem)
|
|
|
|
.rootTreeItem
|
|
|
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIDOMWindow)
|
|
|
|
.QueryInterface(Ci.nsIDOMChromeWindow));
|
|
|
|
|
Bug 1321418 - Use GekcoBundle events in GeckoApp/BrowserApp; r=snorp r=sebastian r=gbrown
Bug 1321418 - 1. Use GekcoBundle events in GeckoApp; r=snorp r=sebastian
Switch GeckoApp to using GeckoBundle events everywhere. UI or Gecko
events are used if the event requires the UI or Gecko thread,
respectively, and background events are used for all other events.
There are changes to some other Java classes, such as SnackbarBuilder
and GeckoAccessibility, due to the switch to GeckoBundle.
For "Snackbar:Show", we need the global EventDispatcher because the
event can be sent to both GeckoApp and GeckoPreferences. Howveer, we
only want one listener registered at the same time, so we register and
unregister in GeckoApp's and GeckoPreferences's onPause and onResume
methods.
Bug 1321418 - 2. Use appropriate JS EventDispatcher to send GeckoApp events; r=snorp r=sebastian
Change JS code that sends events to GeckoApp to use either the global
EventDispatcher or the per-window EventDispatcher.
"Session:StatePurged" is not used so it's removed. "Gecko:Ready" in
geckoview.js is not necessary because it's only used for GeckoApp, so
it's removed from geckoview.js.
Bug 1321418 - 3. Use GeckoBundle events in BrowserApp; r=snorp r=sebastian
Switch BrowserApp to using GeckoBundle events, in a similar vein as
GeckoApp. UI or Gecko events are used if the event handlers required UI
or Gecko thread, respectively, and background events are used for all
other events.
Some other Java classes also have to be modified as a result of
switching to GeckoBundle.
Bug 1321418 - 4. Use global EventDispatcher to send BrowserApp events; r=snorp r=sebastian
Change JS code that sends events to BrowserApp to use the global
EventDispatcher instead of "Messaging".
Bug 1321418 - 5. Update usages of events in tests; r=gbrown
Update cases where we use or wait for events in tests.
2016-12-09 17:32:45 +00:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "EventDispatcher",
|
|
|
|
"resource://gre/modules/Messaging.jsm");
|
2015-12-21 16:52:12 +00:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Snackbars", "resource://gre/modules/Snackbars.jsm");
|
2015-03-09 21:40:32 +00:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Prompt",
|
|
|
|
"resource://gre/modules/Prompt.jsm");
|
|
|
|
|
2015-09-15 18:19:45 +00:00
|
|
|
var debug = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "AboutLogins");
|
2014-12-10 23:43:13 +00:00
|
|
|
|
2015-09-15 18:19:45 +00:00
|
|
|
var gStringBundle = Services.strings.createBundle("chrome://browser/locale/aboutLogins.properties");
|
2014-12-10 23:43:13 +00:00
|
|
|
|
2015-12-21 16:52:12 +00:00
|
|
|
function copyStringShowSnackbar(string, notifyString) {
|
2014-12-10 23:43:13 +00:00
|
|
|
try {
|
|
|
|
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
|
|
|
|
clipboard.copyString(string);
|
2016-02-17 14:14:20 +00:00
|
|
|
Snackbars.show(notifyString, Snackbars.LENGTH_LONG);
|
2014-12-10 23:43:13 +00:00
|
|
|
} catch (e) {
|
2015-06-11 01:52:27 +00:00
|
|
|
debug("Error copying from about:logins");
|
2016-02-17 14:14:20 +00:00
|
|
|
Snackbars.show(gStringBundle.GetStringFromName("loginsDetails.copyFailed"), Snackbars.LENGTH_LONG);
|
2014-12-10 23:43:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-09 21:11:52 +00:00
|
|
|
// Delay filtering while typing in MS
|
|
|
|
const FILTER_DELAY = 500;
|
|
|
|
|
2015-09-15 18:19:45 +00:00
|
|
|
var Logins = {
|
2015-01-07 01:28:30 +00:00
|
|
|
_logins: [],
|
2015-04-09 21:11:52 +00:00
|
|
|
_filterTimer: null,
|
2015-07-11 00:41:46 +00:00
|
|
|
_selectedLogin: null,
|
2015-01-07 01:28:30 +00:00
|
|
|
|
2015-10-02 23:24:31 +00:00
|
|
|
// Load the logins list, displaying interstitial UI (see
|
|
|
|
// #logins-list-loading-body) while loading. There are careful
|
|
|
|
// jank-avoiding measures taken in this function; be careful when
|
|
|
|
// modifying it!
|
|
|
|
//
|
|
|
|
// Returns a Promise that resolves to the list of logins, ordered by
|
|
|
|
// hostname.
|
|
|
|
_promiseLogins: function() {
|
2015-07-23 21:11:32 +00:00
|
|
|
let contentBody = document.getElementById("content-body");
|
|
|
|
let emptyBody = document.getElementById("empty-body");
|
|
|
|
let filterIcon = document.getElementById("filter-button");
|
|
|
|
|
2015-10-02 23:24:31 +00:00
|
|
|
let showSpinner = () => {
|
|
|
|
this._toggleListBody(true);
|
|
|
|
emptyBody.classList.add("hidden");
|
|
|
|
};
|
|
|
|
|
|
|
|
let getAllLogins = () => {
|
|
|
|
let logins = [];
|
|
|
|
try {
|
|
|
|
logins = Services.logins.getAllLogins();
|
|
|
|
} catch(e) {
|
|
|
|
// It's likely that the Master Password was not entered; give
|
|
|
|
// a hint to the next person.
|
|
|
|
throw new Error("Possible Master Password permissions error: " + e.toString());
|
|
|
|
}
|
2015-07-23 21:11:32 +00:00
|
|
|
|
2015-10-02 23:24:31 +00:00
|
|
|
logins.sort((a, b) => a.hostname.localeCompare(b.hostname));
|
2015-07-23 21:11:32 +00:00
|
|
|
|
2015-10-02 23:24:31 +00:00
|
|
|
return logins;
|
|
|
|
};
|
2015-07-23 21:11:32 +00:00
|
|
|
|
2015-10-02 23:24:31 +00:00
|
|
|
let hideSpinner = (logins) => {
|
|
|
|
this._toggleListBody(false);
|
2015-07-23 21:11:32 +00:00
|
|
|
|
2015-10-02 23:24:31 +00:00
|
|
|
if (!logins.length) {
|
|
|
|
contentBody.classList.add("hidden");
|
|
|
|
filterIcon.classList.add("hidden");
|
|
|
|
emptyBody.classList.remove("hidden");
|
|
|
|
} else {
|
|
|
|
contentBody.classList.remove("hidden");
|
|
|
|
emptyBody.classList.add("hidden");
|
|
|
|
}
|
|
|
|
|
|
|
|
return logins;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Return a promise that is resolved after a paint.
|
|
|
|
let waitForPaint = () => {
|
|
|
|
// We're changing 'display'. We need to wait for the new value to take
|
|
|
|
// effect; otherwise, we'll block and never paint a change. Since
|
|
|
|
// requestAnimationFrame callback is generally triggered *before* any
|
|
|
|
// style flush and layout, we wait for two animation frames. This
|
|
|
|
// approach was cribbed from
|
|
|
|
// https://dxr.mozilla.org/mozilla-central/rev/5abe3c4deab94270440422c850bbeaf512b1f38d/browser/base/content/browser-fullScreen.js?offset=0#469.
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// getAllLogins janks the main-thread. We need to paint before that jank;
|
|
|
|
// by throwing the janky load onto the next tick, we paint the spinner; the
|
|
|
|
// spinner is CSS animated off-main-thread.
|
|
|
|
return Promise.resolve()
|
|
|
|
.then(showSpinner)
|
|
|
|
.then(waitForPaint)
|
|
|
|
.then(getAllLogins)
|
|
|
|
.then(hideSpinner);
|
|
|
|
},
|
2015-07-23 21:11:32 +00:00
|
|
|
|
2015-10-02 23:24:31 +00:00
|
|
|
// Reload the logins list, displaying interstitial UI while loading.
|
|
|
|
// Update the stored and displayed list upon completion.
|
|
|
|
_reloadList: function() {
|
|
|
|
this._promiseLogins()
|
|
|
|
.then((logins) => {
|
|
|
|
this._logins = logins;
|
|
|
|
this._loadList(logins);
|
|
|
|
})
|
|
|
|
.catch((e) => {
|
|
|
|
// There's no way to recover from errors, sadly. Log and make
|
|
|
|
// it obvious that something is up.
|
|
|
|
this._logins = [];
|
|
|
|
debug("Failed to _reloadList!");
|
|
|
|
Cu.reportError(e);
|
|
|
|
});
|
2015-01-07 01:28:30 +00:00
|
|
|
},
|
|
|
|
|
2015-07-22 22:22:42 +00:00
|
|
|
_toggleListBody: function(isLoading) {
|
2015-07-23 21:11:32 +00:00
|
|
|
let contentBody = document.getElementById("content-body");
|
2015-07-22 22:22:42 +00:00
|
|
|
let loadingBody = document.getElementById("logins-list-loading-body");
|
|
|
|
|
|
|
|
if (isLoading) {
|
2015-07-23 21:11:32 +00:00
|
|
|
contentBody.classList.add("hidden");
|
2015-07-22 22:22:42 +00:00
|
|
|
loadingBody.classList.remove("hidden");
|
|
|
|
} else {
|
|
|
|
loadingBody.classList.add("hidden");
|
2015-07-23 21:11:32 +00:00
|
|
|
contentBody.classList.remove("hidden");
|
2015-07-22 22:22:42 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2014-12-10 23:43:13 +00:00
|
|
|
init: function () {
|
|
|
|
window.addEventListener("popstate", this , false);
|
|
|
|
|
|
|
|
Services.obs.addObserver(this, "passwordmgr-storage-changed", false);
|
2015-07-31 23:17:00 +00:00
|
|
|
document.getElementById("update-btn").addEventListener("click", this._onSaveEditLogin.bind(this), false);
|
2015-07-11 00:41:46 +00:00
|
|
|
document.getElementById("password-btn").addEventListener("click", this._onPasswordBtn.bind(this), false);
|
2014-12-10 23:43:13 +00:00
|
|
|
|
2015-01-07 01:28:30 +00:00
|
|
|
let filterInput = document.getElementById("filter-input");
|
|
|
|
let filterContainer = document.getElementById("filter-input-container");
|
|
|
|
|
2015-04-09 21:11:52 +00:00
|
|
|
filterInput.addEventListener("input", (event) => {
|
|
|
|
// Stop any in-progress filter timer
|
|
|
|
if (this._filterTimer) {
|
|
|
|
clearTimeout(this._filterTimer);
|
|
|
|
this._filterTimer = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start a new timer
|
|
|
|
this._filterTimer = setTimeout(() => {
|
|
|
|
this._filter(event);
|
|
|
|
}, FILTER_DELAY);
|
|
|
|
}, false);
|
|
|
|
|
2015-01-07 01:28:30 +00:00
|
|
|
filterInput.addEventListener("blur", (event) => {
|
|
|
|
filterContainer.setAttribute("hidden", true);
|
|
|
|
});
|
|
|
|
|
|
|
|
document.getElementById("filter-button").addEventListener("click", (event) => {
|
|
|
|
filterContainer.removeAttribute("hidden");
|
|
|
|
filterInput.focus();
|
|
|
|
}, false);
|
|
|
|
|
|
|
|
document.getElementById("filter-clear").addEventListener("click", (event) => {
|
2015-04-09 21:11:52 +00:00
|
|
|
// Stop any in-progress filter timer
|
|
|
|
if (this._filterTimer) {
|
|
|
|
clearTimeout(this._filterTimer);
|
|
|
|
this._filterTimer = null;
|
|
|
|
}
|
|
|
|
|
2015-01-07 01:28:30 +00:00
|
|
|
filterInput.blur();
|
|
|
|
filterInput.value = "";
|
|
|
|
this._loadList(this._logins);
|
|
|
|
}, false);
|
|
|
|
|
2014-12-10 23:43:13 +00:00
|
|
|
this._showList();
|
2015-07-11 00:41:46 +00:00
|
|
|
|
|
|
|
this._updatePasswordBtn(true);
|
2015-10-02 23:24:31 +00:00
|
|
|
|
|
|
|
this._reloadList();
|
2014-12-10 23:43:13 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
uninit: function () {
|
|
|
|
Services.obs.removeObserver(this, "passwordmgr-storage-changed");
|
|
|
|
window.removeEventListener("popstate", this, false);
|
|
|
|
},
|
|
|
|
|
2015-01-07 01:28:30 +00:00
|
|
|
_loadList: function (logins) {
|
2014-12-10 23:43:13 +00:00
|
|
|
let list = document.getElementById("logins-list");
|
2015-01-07 01:28:30 +00:00
|
|
|
let newList = list.cloneNode(false);
|
|
|
|
|
2014-12-10 23:43:13 +00:00
|
|
|
logins.forEach(login => {
|
|
|
|
let item = this._createItemForLogin(login);
|
2015-01-07 01:28:30 +00:00
|
|
|
newList.appendChild(item);
|
2014-12-10 23:43:13 +00:00
|
|
|
});
|
2015-01-07 01:28:30 +00:00
|
|
|
|
|
|
|
list.parentNode.replaceChild(newList, list);
|
2014-12-10 23:43:13 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_showList: function () {
|
2015-07-11 00:41:46 +00:00
|
|
|
let loginsListPage = document.getElementById("logins-list-page");
|
|
|
|
loginsListPage.classList.remove("hidden");
|
|
|
|
|
|
|
|
let editLoginPage = document.getElementById("edit-login-page");
|
|
|
|
editLoginPage.classList.add("hidden");
|
|
|
|
|
|
|
|
// If the Show/Hide password button has been flipped, reset it
|
|
|
|
if (this._isPasswordBtnInHideMode()) {
|
|
|
|
this._updatePasswordBtn(true);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_onPopState: function (event) {
|
|
|
|
// Called when back/forward is used to change the state of the page
|
|
|
|
if (event.state) {
|
|
|
|
this._showEditLoginDialog(event.state.id);
|
|
|
|
} else {
|
|
|
|
this._selectedLogin = null;
|
|
|
|
this._showList();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_showEditLoginDialog: function (login) {
|
|
|
|
let listPage = document.getElementById("logins-list-page");
|
|
|
|
listPage.classList.add("hidden");
|
|
|
|
|
|
|
|
let editLoginPage = document.getElementById("edit-login-page");
|
|
|
|
editLoginPage.classList.remove("hidden");
|
|
|
|
|
|
|
|
let usernameField = document.getElementById("username");
|
|
|
|
usernameField.value = login.username;
|
|
|
|
let passwordField = document.getElementById("password");
|
|
|
|
passwordField.value = login.password;
|
|
|
|
let domainField = document.getElementById("hostname");
|
|
|
|
domainField.value = login.hostname;
|
|
|
|
|
|
|
|
let img = document.getElementById("favicon");
|
|
|
|
this._loadFavicon(img, login.hostname);
|
|
|
|
|
|
|
|
let headerText = document.getElementById("edit-login-header-text");
|
|
|
|
if (login.hostname && (login.hostname != "")) {
|
|
|
|
headerText.textContent = login.hostname;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
headerText.textContent = gStringBundle.GetStringFromName("editLogin.fallbackTitle");
|
|
|
|
}
|
2015-07-31 23:17:00 +00:00
|
|
|
|
|
|
|
passwordField.addEventListener("input", (event) => {
|
|
|
|
let newPassword = passwordField.value;
|
|
|
|
let updateBtn = document.getElementById("update-btn");
|
|
|
|
|
|
|
|
if (newPassword === "") {
|
|
|
|
updateBtn.disabled = true;
|
|
|
|
updateBtn.classList.add("disabled-btn");
|
|
|
|
} else if ((newPassword !== "") && (updateBtn.disabled === true)) {
|
|
|
|
updateBtn.disabled = false;
|
|
|
|
updateBtn.classList.remove("disabled-btn");
|
|
|
|
}
|
|
|
|
}, false);
|
2015-07-11 00:41:46 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onSaveEditLogin: function() {
|
|
|
|
let newUsername = document.getElementById("username").value;
|
|
|
|
let newPassword = document.getElementById("password").value;
|
|
|
|
let newDomain = document.getElementById("hostname").value;
|
|
|
|
let origUsername = this._selectedLogin.username;
|
|
|
|
let origPassword = this._selectedLogin.password;
|
|
|
|
let origDomain = this._selectedLogin.hostname;
|
|
|
|
|
|
|
|
try {
|
|
|
|
if ((newUsername === origUsername) &&
|
|
|
|
(newPassword === origPassword) &&
|
|
|
|
(newDomain === origDomain) ) {
|
2016-02-17 14:14:20 +00:00
|
|
|
Snackbars.show(gStringBundle.GetStringFromName("editLogin.saved1"), Snackbars.LENGTH_LONG);
|
2015-07-11 00:41:46 +00:00
|
|
|
this._showList();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let logins = Services.logins.findLogins({}, origDomain, origDomain, null);
|
|
|
|
|
|
|
|
for (let i = 0; i < logins.length; i++) {
|
|
|
|
if (logins[i].username == origUsername) {
|
|
|
|
let clone = logins[i].clone();
|
|
|
|
clone.username = newUsername;
|
|
|
|
clone.password = newPassword;
|
|
|
|
clone.hostname = newDomain;
|
|
|
|
Services.logins.removeLogin(logins[i]);
|
|
|
|
Services.logins.addLogin(clone);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e) {
|
2016-02-17 14:14:20 +00:00
|
|
|
Snackbars.show(gStringBundle.GetStringFromName("editLogin.couldNotSave"), Snackbars.LENGTH_LONG);
|
2015-07-11 00:41:46 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-02-17 14:14:20 +00:00
|
|
|
Snackbars.show(gStringBundle.GetStringFromName("editLogin.saved1"), Snackbars.LENGTH_LONG);
|
2015-07-11 00:41:46 +00:00
|
|
|
this._showList();
|
|
|
|
},
|
|
|
|
|
|
|
|
_onPasswordBtn: function () {
|
|
|
|
this._updatePasswordBtn(this._isPasswordBtnInHideMode());
|
|
|
|
},
|
|
|
|
|
|
|
|
_updatePasswordBtn: function (aShouldShow) {
|
|
|
|
let passwordField = document.getElementById("password");
|
|
|
|
let button = document.getElementById("password-btn");
|
|
|
|
let show = gStringBundle.GetStringFromName("password-btn.show");
|
|
|
|
let hide = gStringBundle.GetStringFromName("password-btn.hide");
|
|
|
|
if (aShouldShow) {
|
|
|
|
passwordField.type = "password";
|
|
|
|
button.textContent = show;
|
|
|
|
button.classList.remove("password-btn-hide");
|
|
|
|
} else {
|
|
|
|
passwordField.type = "text";
|
|
|
|
button.textContent= hide;
|
|
|
|
button.classList.add("password-btn-hide");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_isPasswordBtnInHideMode: function () {
|
|
|
|
let button = document.getElementById("password-btn");
|
|
|
|
return button.classList.contains("password-btn-hide");
|
|
|
|
},
|
|
|
|
|
|
|
|
_showPassword: function(password) {
|
|
|
|
let passwordPrompt = new Prompt({
|
|
|
|
window: window,
|
|
|
|
message: password,
|
|
|
|
buttons: [
|
|
|
|
gStringBundle.GetStringFromName("loginsDialog.copy"),
|
|
|
|
gStringBundle.GetStringFromName("loginsDialog.cancel") ]
|
|
|
|
}).show((data) => {
|
|
|
|
switch (data.button) {
|
|
|
|
case 0:
|
|
|
|
// Corresponds to "Copy password" button.
|
2015-12-21 16:52:12 +00:00
|
|
|
copyStringShowSnackbar(password, gStringBundle.GetStringFromName("loginsDetails.passwordCopied"));
|
2015-07-11 00:41:46 +00:00
|
|
|
}
|
|
|
|
});
|
2014-12-10 23:43:13 +00:00
|
|
|
},
|
|
|
|
|
2015-04-09 19:48:45 +00:00
|
|
|
_onLoginClick: function (event) {
|
|
|
|
let loginItem = event.currentTarget;
|
|
|
|
let login = loginItem.login;
|
|
|
|
if (!login) {
|
|
|
|
debug("No login!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let prompt = new Prompt({
|
|
|
|
window: window,
|
|
|
|
});
|
|
|
|
let menuItems = [
|
2015-06-11 01:52:27 +00:00
|
|
|
{ label: gStringBundle.GetStringFromName("loginsMenu.showPassword") },
|
|
|
|
{ label: gStringBundle.GetStringFromName("loginsMenu.copyPassword") },
|
|
|
|
{ label: gStringBundle.GetStringFromName("loginsMenu.copyUsername") },
|
2015-07-11 00:41:46 +00:00
|
|
|
{ label: gStringBundle.GetStringFromName("loginsMenu.editLogin") },
|
2015-06-11 01:52:27 +00:00
|
|
|
{ label: gStringBundle.GetStringFromName("loginsMenu.delete") }
|
2015-04-09 19:48:45 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
prompt.setSingleChoiceItems(menuItems);
|
|
|
|
prompt.show((data) => {
|
|
|
|
// Switch on indices of buttons, as they were added when creating login item.
|
|
|
|
switch (data.button) {
|
|
|
|
case 0:
|
2015-07-11 00:41:46 +00:00
|
|
|
this._showPassword(login.password);
|
2015-04-09 19:48:45 +00:00
|
|
|
break;
|
|
|
|
case 1:
|
2015-12-21 16:52:12 +00:00
|
|
|
copyStringShowSnackbar(login.password, gStringBundle.GetStringFromName("loginsDetails.passwordCopied"));
|
2015-04-09 19:48:45 +00:00
|
|
|
break;
|
|
|
|
case 2:
|
2015-12-21 16:52:12 +00:00
|
|
|
copyStringShowSnackbar(login.username, gStringBundle.GetStringFromName("loginsDetails.usernameCopied"));
|
2015-06-03 00:43:20 +00:00
|
|
|
break;
|
|
|
|
case 3:
|
2015-07-11 00:41:46 +00:00
|
|
|
this._selectedLogin = login;
|
|
|
|
this._showEditLoginDialog(login);
|
|
|
|
history.pushState({ id: login.guid }, document.title);
|
|
|
|
break;
|
|
|
|
case 4:
|
2015-04-09 19:48:45 +00:00
|
|
|
let confirmPrompt = new Prompt({
|
|
|
|
window: window,
|
2015-06-11 01:52:27 +00:00
|
|
|
message: gStringBundle.GetStringFromName("loginsDialog.confirmDelete"),
|
2015-04-09 19:48:45 +00:00
|
|
|
buttons: [
|
2015-06-11 01:52:27 +00:00
|
|
|
gStringBundle.GetStringFromName("loginsDialog.confirm"),
|
|
|
|
gStringBundle.GetStringFromName("loginsDialog.cancel") ]
|
2015-04-09 19:48:45 +00:00
|
|
|
});
|
|
|
|
confirmPrompt.show((data) => {
|
|
|
|
switch (data.button) {
|
|
|
|
case 0:
|
|
|
|
// Corresponds to "confirm" button.
|
|
|
|
Services.logins.removeLogin(login);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2015-07-11 00:41:46 +00:00
|
|
|
_loadFavicon: function (aImg, aHostname) {
|
|
|
|
// Load favicon from cache.
|
Bug 1321418 - Use GekcoBundle events in GeckoApp/BrowserApp; r=snorp r=sebastian r=gbrown
Bug 1321418 - 1. Use GekcoBundle events in GeckoApp; r=snorp r=sebastian
Switch GeckoApp to using GeckoBundle events everywhere. UI or Gecko
events are used if the event requires the UI or Gecko thread,
respectively, and background events are used for all other events.
There are changes to some other Java classes, such as SnackbarBuilder
and GeckoAccessibility, due to the switch to GeckoBundle.
For "Snackbar:Show", we need the global EventDispatcher because the
event can be sent to both GeckoApp and GeckoPreferences. Howveer, we
only want one listener registered at the same time, so we register and
unregister in GeckoApp's and GeckoPreferences's onPause and onResume
methods.
Bug 1321418 - 2. Use appropriate JS EventDispatcher to send GeckoApp events; r=snorp r=sebastian
Change JS code that sends events to GeckoApp to use either the global
EventDispatcher or the per-window EventDispatcher.
"Session:StatePurged" is not used so it's removed. "Gecko:Ready" in
geckoview.js is not necessary because it's only used for GeckoApp, so
it's removed from geckoview.js.
Bug 1321418 - 3. Use GeckoBundle events in BrowserApp; r=snorp r=sebastian
Switch BrowserApp to using GeckoBundle events, in a similar vein as
GeckoApp. UI or Gecko events are used if the event handlers required UI
or Gecko thread, respectively, and background events are used for all
other events.
Some other Java classes also have to be modified as a result of
switching to GeckoBundle.
Bug 1321418 - 4. Use global EventDispatcher to send BrowserApp events; r=snorp r=sebastian
Change JS code that sends events to BrowserApp to use the global
EventDispatcher instead of "Messaging".
Bug 1321418 - 5. Update usages of events in tests; r=gbrown
Update cases where we use or wait for events in tests.
2016-12-09 17:32:45 +00:00
|
|
|
EventDispatcher.instance.sendRequestForResult({
|
2015-07-11 00:41:46 +00:00
|
|
|
type: "Favicon:CacheLoad",
|
|
|
|
url: aHostname,
|
|
|
|
}).then(function(faviconUrl) {
|
|
|
|
aImg.style.backgroundImage= "url('" + faviconUrl + "')";
|
|
|
|
aImg.style.visibility = "visible";
|
|
|
|
}, function(data) {
|
|
|
|
debug("Favicon cache failure : " + data);
|
|
|
|
aImg.style.visibility = "visible";
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2014-12-10 23:43:13 +00:00
|
|
|
_createItemForLogin: function (login) {
|
|
|
|
let loginItem = document.createElement("div");
|
|
|
|
|
|
|
|
loginItem.setAttribute("loginID", login.guid);
|
|
|
|
loginItem.className = "login-item list-item";
|
2015-03-09 21:40:32 +00:00
|
|
|
|
2015-04-09 19:48:45 +00:00
|
|
|
loginItem.addEventListener("click", this, true);
|
2014-12-10 23:43:13 +00:00
|
|
|
|
|
|
|
// Create item icon.
|
2015-02-18 19:43:52 +00:00
|
|
|
let img = document.createElement("div");
|
2014-12-10 23:43:13 +00:00
|
|
|
img.className = "icon";
|
2015-02-18 19:43:52 +00:00
|
|
|
|
2015-07-11 00:41:46 +00:00
|
|
|
this._loadFavicon(img, login.hostname);
|
2014-12-10 23:43:13 +00:00
|
|
|
loginItem.appendChild(img);
|
|
|
|
|
|
|
|
// Create item details.
|
|
|
|
let inner = document.createElement("div");
|
|
|
|
inner.className = "inner";
|
|
|
|
|
|
|
|
let details = document.createElement("div");
|
|
|
|
details.className = "details";
|
|
|
|
inner.appendChild(details);
|
|
|
|
|
|
|
|
let titlePart = document.createElement("div");
|
|
|
|
titlePart.className = "hostname";
|
|
|
|
titlePart.textContent = login.hostname;
|
|
|
|
details.appendChild(titlePart);
|
|
|
|
|
|
|
|
let versionPart = document.createElement("div");
|
|
|
|
versionPart.textContent = login.httpRealm;
|
|
|
|
versionPart.className = "realm";
|
|
|
|
details.appendChild(versionPart);
|
|
|
|
|
|
|
|
let descPart = document.createElement("div");
|
|
|
|
descPart.textContent = login.username;
|
|
|
|
descPart.className = "username";
|
|
|
|
inner.appendChild(descPart);
|
|
|
|
|
|
|
|
loginItem.appendChild(inner);
|
|
|
|
loginItem.login = login;
|
|
|
|
return loginItem;
|
|
|
|
},
|
|
|
|
|
|
|
|
handleEvent: function (event) {
|
|
|
|
switch (event.type) {
|
|
|
|
case "popstate": {
|
|
|
|
this._onPopState(event);
|
|
|
|
break;
|
|
|
|
}
|
2015-04-09 19:48:45 +00:00
|
|
|
case "click": {
|
|
|
|
this._onLoginClick(event);
|
|
|
|
break;
|
|
|
|
}
|
2014-12-10 23:43:13 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
observe: function (subject, topic, data) {
|
|
|
|
switch(topic) {
|
|
|
|
case "passwordmgr-storage-changed": {
|
2015-10-02 23:24:31 +00:00
|
|
|
this._reloadList();
|
2014-12-10 23:43:13 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-01-07 01:28:30 +00:00
|
|
|
_filter: function(event) {
|
2015-03-03 02:35:05 +00:00
|
|
|
let value = event.target.value.toLowerCase();
|
2015-01-07 01:28:30 +00:00
|
|
|
let logins = this._logins.filter((login) => {
|
|
|
|
if (login.hostname.toLowerCase().indexOf(value) != -1) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (login.username &&
|
|
|
|
login.username.toLowerCase().indexOf(value) != -1) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (login.httpRealm &&
|
|
|
|
login.httpRealm.toLowerCase().indexOf(value) != -1) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
this._loadList(logins);
|
2014-12-10 23:43:13 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-06-11 01:52:27 +00:00
|
|
|
window.addEventListener("load", Logins.init.bind(Logins), false);
|
|
|
|
window.addEventListener("unload", Logins.uninit.bind(Logins), false);
|