/* 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 loaded into the browser window scope. /* eslint-env mozilla/browser-window */
createSyncedTabs() { if (SyncedTabs.isConfiguredToSyncTabs) { if (SyncedTabs.hasSyncedThisSession) { this.deck.selectedIndex =
SyncedTabsPanelList.sRemoteTabsDeckIndices.DECKINDEX_TABS;
} else { // Sync hasn't synced tabs yet, so show the "fetching" panel. this.deck.selectedIndex =
SyncedTabsPanelList.sRemoteTabsDeckIndices.DECKINDEX_FETCHING;
} // force a background sync.
SyncedTabs.syncTabs().catch(ex => {
console.error(ex);
}); this.deck.toggleAttribute("syncingtabs", true); // show the current list - it will be updated by our observer. this._showSyncedTabs(); if (this.separator) { this.separator.hidden = false;
}
} else { // not configured to sync tabs, so no point updating the list. this.deck.selectedIndex =
SyncedTabsPanelList.sRemoteTabsDeckIndices.DECKINDEX_TABSDISABLED; this.deck.toggleAttribute("syncingtabs", false); if (this.separator) { this.separator.hidden = true;
}
}
}
// Update the synced tab list after any existing in-flight updates are complete.
_showSyncedTabs(paginationInfo) { this._showSyncedTabsPromise = this._showSyncedTabsPromise.then(
() => { returnthis.__showSyncedTabs(paginationInfo);
},
e => {
console.error(e);
}
);
}
// Return a new promise to update the tab list.
__showSyncedTabs(paginationInfo) { if (!this.tabsList) { // Closed between the previous `this._showSyncedTabsPromise` // resolving and now. return undefined;
} return SyncedTabs.getTabClients()
.then(clients => {
let noTabs = !UIState.get().syncEnabled || !clients.length; this.deck.toggleAttribute("syncingtabs", !noTabs); if (this.separator) { this.separator.hidden = noTabs;
}
// The view may have been hidden while the promise was resolving. if (!this.tabsList) { return;
} if (clients.length === 0 && !SyncedTabs.hasSyncedThisSession) { // the "fetching tabs" deck is being shown - let's leave it there. // When that first sync completes we'll be notified and update. return;
}
if (clients.length === 0) { this.deck.selectedIndex =
SyncedTabsPanelList.sRemoteTabsDeckIndices.DECKINDEX_NOCLIENTS; return;
} this.deck.selectedIndex =
SyncedTabsPanelList.sRemoteTabsDeckIndices.DECKINDEX_TABS; this._clearSyncedTabList();
SyncedTabs.sortTabClientsByLastUsed(clients);
let fragment = document.createDocumentFragment();
let clientNumber = 0; for (let client of clients) { // add a menu separator for all clients other than the first. if (fragment.lastElementChild) {
let separator = document.createXULElement("toolbarseparator");
fragment.appendChild(separator);
} // We add the client's elements to a container, and indicate which // element labels it.
let labelId = `synced-tabs-client-${clientNumber++}`;
let container = document.createXULElement("vbox");
container.classList.add("PanelUI-remotetabs-clientcontainer");
container.setAttribute("role", "group");
container.setAttribute("aria-labelledby", labelId);
let clientPaginationInfo =
paginationInfo && paginationInfo.clientId == client.id
? paginationInfo
: { clientId: client.id }; this._appendSyncClient(
client,
container,
labelId,
clientPaginationInfo
);
fragment.appendChild(container);
} this.tabsList.appendChild(fragment);
})
.catch(err => {
console.error(err);
})
.then(() => { // an observer for tests.
Services.obs.notifyObservers( null, "synced-tabs-menu:test:tabs-updated"
);
});
}
_clearSyncedTabList() {
let list = this.tabsList; while (list.lastChild) {
list.lastChild.remove();
}
}
_appendSyncClient(client, container, labelId, paginationInfo) {
let { maxTabs = SyncedTabsPanelList.sRemoteTabsPerPage } = paginationInfo; // Create the element for the remote client.
let clientItem = document.createXULElement("label");
clientItem.setAttribute("id", labelId);
clientItem.setAttribute("itemtype", "client");
clientItem.setAttribute( "tooltiptext",
gSync.fluentStrings.formatValueSync("appmenu-fxa-last-sync", {
time: gSync.formatLastSyncDate(new Date(client.lastModified)),
})
);
clientItem.textContent = client.name;
container.appendChild(clientItem);
if (!client.tabs.length) {
let label = this._createNoSyncedTabsElement( "notabsforclientlabel",
container
);
label.setAttribute("class", "PanelUI-remotetabs-notabsforclient-label");
} else { // We have the client obj but we need the FxA device obj so we use the clients // engine to get us the FxA device
let device =
fxAccounts.device.recentDeviceList &&
fxAccounts.device.recentDeviceList.find(
d =>
d.id === Weave.Service.clientsEngine.getClientFxaDeviceId(client.id)
);
let remoteTabCloseAvailable =
device && fxAccounts.commands.closeTab.isDeviceCompatible(device);
let tabs = client.tabs.filter(t => !t.inactive);
let hasInactive = tabs.length != client.tabs.length;
if (hasInactive) {
container.append(this._createShowInactiveTabsElement(client, device));
} // If this page isn't displaying all (regular, active) tabs, show a "Show More" button.
let hasNextPage = tabs.length > maxTabs;
let nextPageIsLastPage =
hasNextPage &&
maxTabs + SyncedTabsPanelList.sRemoteTabsPerPage >= tabs.length; if (nextPageIsLastPage) { // When the user clicks "Show More", try to have at least sRemoteTabsNextPageMinTabs more tabs // to display in order to avoid user frustration
maxTabs = Math.min(
tabs.length - SyncedTabsPanelList.sRemoteTabsNextPageMinTabs,
maxTabs
);
} if (hasNextPage) {
tabs = tabs.slice(0, maxTabs);
} for (let [index, tab] of tabs.entries()) {
let tabEnt = this._createSyncedTabElement(
tab,
index,
device,
remoteTabCloseAvailable
);
container.appendChild(tabEnt);
} if (hasNextPage) {
let showAllEnt = this._createShowMoreSyncedTabsElement(paginationInfo);
container.appendChild(showAllEnt);
}
}
}
let item = document.createXULElement("toolbarbutton");
let tooltipText = (tabInfo.title ? tabInfo.title + "\n" : "") + tabInfo.url;
item.setAttribute("itemtype", "tab");
item.classList.add( "all-tabs-button", "subviewbutton", "subviewbutton-iconic"
);
item.setAttribute("targetURI", tabInfo.url);
item.setAttribute( "label",
tabInfo.title != "" ? tabInfo.title : tabInfo.url
); if (tabInfo.icon) {
item.setAttribute("image", tabInfo.icon);
}
item.setAttribute("tooltiptext", tooltipText); // We need to use "click" instead of "command" here so openUILink // respects different buttons (eg, to open in a new tab).
item.addEventListener("click", e => { // We want to differentiate between when the fxa panel is within the app menu/hamburger bar
let object = window.gSync._getEntryPointForElement(e.currentTarget);
SyncedTabs.recordSyncedTabsTelemetry(object, "click", {
tab_pos: index.toString(),
});
document.defaultView.openUILink(tabInfo.url, e, {
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
{}
),
}); if (BrowserUtils.whereToOpenLink(e) != "current") {
e.preventDefault();
e.stopPropagation();
} else {
CustomizableUI.hidePanelForNode(item);
}
});
tabContainer.appendChild(item); // We should only add an X button next to tabs if the device // is broadcasting that it can remotely close tabs if (canCloseTabs) {
let closeBtn = this._createCloseTabElement(tabInfo.url, device);
closeBtn.tab = item;
tabContainer.appendChild(closeBtn);
let undoBtn = this._createUndoCloseTabElement(tabInfo.url, device);
undoBtn.tab = item;
tabContainer.appendChild(undoBtn);
} return tabContainer;
}
let tabContainer = closeBtn.parentNode;
let tabList = tabContainer.parentNode;
let undoBtn = tabContainer.querySelector(".remote-tabs-undo-button");
let prevClose = tabList.querySelector( ".remote-tabs-undo-button:not([hidden])"
); if (prevClose) {
let prevCloseContainer = prevClose.parentNode;
prevCloseContainer.classList.add("tabitem-removed");
prevCloseContainer.addEventListener("transitionend", () => {
prevCloseContainer.remove();
});
}
closeBtn.hidden = true;
undoBtn.hidden = false; // This tab has been closed so we prevent the user from // interacting with it if (closeBtn.tab) {
closeBtn.tab.disabled = true;
} // The user could be hitting multiple tabs across multiple devices, with a few // seconds in-between -- we should not immediately fire off pushes, so we // add it to a queue and send in bulk at a later time
SyncedTabsManagement.enqueueTabToClose(device.id, url);
}); return closeBtn;
}
var gSync = {
_initialized: false,
_isCurrentlySyncing: false, // The last sync start time. Used to calculate the leftover animation time // once syncing completes (bug 1239042).
_syncStartTime: 0,
_syncAnimationTimer: 0,
_obs: ["weave:engine:sync:finish", "quit-application", UIState.ON_UPDATE],
get log() { if (!this._log) { const { Log } = ChromeUtils.importESModule( "resource://gre/modules/Log.sys.mjs"
);
let syncLog = Log.repository.getLogger("Sync.Browser");
syncLog.manageLevelFromPref("services.sync.log.logger.browser"); this._log = syncLog;
} returnthis._log;
},
get fluentStrings() { deletethis.fluentStrings; return (this.fluentStrings = new Localization(
[ "branding/brand.ftl", "browser/accounts.ftl", "browser/appmenu.ftl", "browser/sync.ftl", "browser/syncedTabs.ftl", "browser/newtab/asrouter.ftl",
], true
));
},
// Returns true if FxA is configured, but the send tab targets list isn't // ready yet.
get sendTabConfiguredAndLoading() { return (
UIState.get().status == UIState.STATUS_SIGNED_IN &&
!fxAccounts.device.recentDeviceList
);
},
get isSignedIn() { return UIState.get().status == UIState.STATUS_SIGNED_IN;
},
shouldHideSendContextMenuItems(enabled) { const state = UIState.get(); // Only show the "Send..." context menu items when sending would be possible if (
enabled &&
state.status == UIState.STATUS_SIGNED_IN &&
state.syncEnabled && this.getSendTabTargets().length
) { returnfalse;
} returntrue;
},
getSendTabTargets() { const targets = []; if (
UIState.get().status != UIState.STATUS_SIGNED_IN ||
!fxAccounts.device.recentDeviceList
) { return targets;
} for (let d of fxAccounts.device.recentDeviceList) { if (d.isCurrentDevice) { continue;
}
if (fxAccounts.commands.sendTab.isDeviceCompatible(d)) {
targets.push(d);
}
} return targets.sort((a, b) => b.lastAccessTime - a.lastAccessTime);
},
maybeUpdateUIState() { // Update the UI. if (UIState.isReady()) { const state = UIState.get(); // If we are not configured, the UI is already in the right state when // we open the window. We can avoid a repaint. if (state.status != UIState.STATUS_NOT_CONFIGURED) { this.updateAllUI(state);
}
}
},
init() { if (this._initialized) { return;
}
this._definePrefGetters();
if (!this.FXA_ENABLED) { this.onFxaDisabled(); return;
}
// Label for the sync buttons. const appMenuLabel = PanelMultiView.getViewNode(
document, "appMenu-fxa-label2"
); if (!appMenuLabel) { // We are in a window without our elements - just abort now, without // setting this._initialized, so we don't attempt to remove observers. return;
} // We start with every menuitem hidden (except for the "setup sync" state), // so that we don't need to init the sync UI on windows like pageInfo.xhtml // (see bug 1384856). // maybeUpdateUIState() also optimizes for this - if we should be in the // "setup sync" state, that function assumes we are already in it and // doesn't re-initialize the UI elements.
document.getElementById("sync-setup").hidden = false;
PanelMultiView.getViewNode(
document, "PanelUI-remotetabs-setupsync"
).hidden = false;
const appMenuHeaderTitle = PanelMultiView.getViewNode(
document, "appMenu-header-title"
); const appMenuHeaderDescription = PanelMultiView.getViewNode(
document, "appMenu-header-description"
); const appMenuHeaderText = PanelMultiView.getViewNode(
document, "appMenu-fxa-text"
);
appMenuHeaderTitle.hidden = true; // We must initialize the label attribute here instead of the markup // due to a timing error. The fluent label attribute was being applied // after we had updated appMenuLabel and thus displayed an incorrect // label for signed in users. const [headerDesc, headerText] = this.fluentStrings.formatValuesSync([ "appmenu-fxa-signed-in-label", "appmenu-fxa-sync-and-save-data2",
]);
appMenuHeaderDescription.value = headerDesc;
appMenuHeaderText.textContent = headerText;
for (let topic of this._obs) {
Services.obs.addObserver(this, topic, true);
}
// If the experiment is enabled, we'll need to update the panels // to show some different text to the user if (this.FXA_CTA_MENU_ENABLED) { this.updateFxAPanel(UIState.get()); this.updateCTAPanel();
}
const avatarIconVariant =
NimbusFeatures.fxaButtonVisibility.getVariable("avatarIconVariant"); if (avatarIconVariant) { this.applyAvatarIconVariant(avatarIconVariant);
}
this._initialized = true;
},
uninit() { if (!this._initialized) { return;
}
for (let topic of this._obs) {
Services.obs.removeObserver(this, topic);
}
if (NimbusFeatures.fxaAppMenuItem.getVariable("ctaCopyVariant")) {
NimbusFeatures.fxaAppMenuItem.recordExposureEvent();
}
},
onFxAPanelViewShowing(panelview) {
let messageId = panelview.getAttribute(
MenuMessage.SHOWING_FXA_MENU_MESSAGE_ATTR
); if (messageId) {
MenuMessage.recordMenuMessageTelemetry( "IMPRESSION",
MenuMessage.SOURCES.PXI_MENU,
messageId
);
let message = ASRouter.getMessageById(messageId);
ASRouter.addImpression(message);
}
let syncNowBtn = panelview.querySelector(".syncnow-label");
let l10nId = syncNowBtn.getAttribute( this._isCurrentlySyncing
? "syncing-data-l10n-id"
: "sync-now-data-l10n-id"
);
document.l10n.setAttributes(syncNowBtn, l10nId);
// This needs to exist because if the user is signed in // but the user disabled or disconnected sync we should not show the button const syncPrefsButtonEl = PanelMultiView.getViewNode(
document, "PanelUI-fxa-menu-sync-prefs-button"
); const syncEnabled = UIState.get().syncEnabled;
syncPrefsButtonEl.hidden = !syncEnabled; if (!syncEnabled) { this._disableSyncOffIndicator();
}
// We should ensure that we do not show the sign out button // if the user is not signed in const signOutButtonEl = PanelMultiView.getViewNode(
document, "PanelUI-fxa-menu-account-signout-button"
);
signOutButtonEl.hidden = !this.isSignedIn;
// Any variant on the CTA will have been applied inside of updateFxAPanel, // but now that the panel is showing, we record exposure. const ctaCopyVariant =
NimbusFeatures.fxaAvatarMenuItem.getVariable("ctaCopyVariant"); if (ctaCopyVariant) {
NimbusFeatures.fxaAvatarMenuItem.recordExposureEvent();
}
// We want to record exposure if the user has sync disabled and has // clicked to open the FxA panel if (this.isSignedIn && !UIState.get().syncEnabled) {
NimbusFeatures.syncSetupFlow.recordExposureEvent();
}
},
onCommand(button) { switch (button.id) { case"PanelUI-fxa-menu-sync-prefs-button": // fall through case"PanelUI-fxa-menu-setup-sync-button": this.openPrefsFromFxaMenu("sync_settings", button); break; case"PanelUI-fxa-menu-setup-sync-button-new": this.openChooseWhatToSync("sync_settings", button); break;
case"PanelUI-fxa-menu-sendtab-connect-device-button": // fall through case"PanelUI-fxa-menu-connect-device-button": this.clickOpenConnectAnotherDevice(button); break;
// Ensure we have *something* in `fxAccounts.device.recentDeviceList` as some // of our UI logic depends on it not being null. When FxA is notified of a // device change it will auto refresh `recentDeviceList`, and all UI which // shows the device list will start with `recentDeviceList`, but should also // force a refresh, both of which should mean in the worst-case, the UI is up // to date after a very short delay.
async ensureFxaDevices() { if (UIState.get().status != UIState.STATUS_SIGNED_IN) {
console.info("Skipping device list refresh; not signed in"); return;
} if (!fxAccounts.device.recentDeviceList) { if (await this.refreshFxaDevices()) { // Assuming we made the call successfully it should be impossible to end // up with a falsey recentDeviceList, so make noise if that's false. if (!fxAccounts.device.recentDeviceList) {
console.warn("Refreshing device list didn't find any devices.");
}
}
}
},
// Force a refresh of the fxa device list. Note that while it's theoretically // OK to call `fxAccounts.device.refreshDeviceList` multiple times concurrently // and regularly, this call tells it to avoid those protections, so will always // hit the FxA servers - therefore, you should be very careful how often you // call this. // Returns Promise<bool> to indicate whether a refresh was actually done.
async refreshFxaDevices() { if (UIState.get().status != UIState.STATUS_SIGNED_IN) {
console.info("Skipping device list refresh; not signed in"); returnfalse;
} try { // Do the actual refresh telling it to avoid the "flooding" protections.
await fxAccounts.device.refreshDeviceList({ ignoreCached: true }); returntrue;
} catch (e) { this.log.error("Refreshing device list failed.", e); returnfalse;
}
},
/** * Potential network call. Fetch the list of OAuth clients attached to the current Mozilla account. * @returns {Promise<boolean>} - Resolves to true if successful, false otherwise.
*/
async fetchListOfOAuthClients() { if (!this.isSignedIn) {
console.info("Skipping fetching other attached clients"); returnfalse;
} try { this._attachedClients = await fxAccounts.listAttachedOAuthClients(); returntrue;
} catch (e) { this.log.error("Could not fetch attached OAuth clients", e); returnfalse;
}
},
_populateSendTabToDevicesView(panelViewNode, reloadDevices = true) {
let bodyNode = panelViewNode.querySelector(".panel-subview-body");
let panelNode = panelViewNode.closest("panel");
let browser = gBrowser.selectedBrowser;
let uri = browser.currentURI;
let title = browser.contentTitle;
let multiselected = gBrowser.selectedTab.multiselected;
// This is on top because it also clears the device list between state // changes. this.populateSendTabToDevicesMenu(
bodyNode,
uri,
title,
multiselected,
(clientId, name, clientType, lastModified) => { if (!name) { return document.createXULElement("toolbarseparator");
}
let item = document.createXULElement("toolbarbutton");
item.setAttribute("wrap", true);
item.setAttribute("align", "start");
item.classList.add("sendToDevice-device", "subviewbutton"); if (clientId) {
item.classList.add("subviewbutton-iconic"); if (lastModified) {
let lastSyncDate = gSync.formatLastSyncDate(lastModified); if (lastSyncDate) {
item.setAttribute( "tooltiptext", this.fluentStrings.formatValueSync("appmenu-fxa-last-sync", {
time: lastSyncDate,
})
);
}
}
}
bodyNode.removeAttribute("state"); // If the app just started, we won't have fetched the device list yet. Sync // does this automatically ~10 sec after startup, but there's no trigger for // this if we're signed in to FxA, but not Sync. if (gSync.sendTabConfiguredAndLoading) {
bodyNode.setAttribute("state", "notready");
} if (reloadDevices) { // Force a refresh of the fxa device list in case the user connected a new // device, and is waiting for it to show up. this.refreshFxaDevices().then(_ => { if (!window.closed) { this._populateSendTabToDevicesView(panelViewNode, false);
}
});
}
},
async toggleAccountPanel(anchor = null, aEvent) { // Don't show the panel if the window is in customization mode. if (document.documentElement.hasAttribute("customizing")) { return;
}
// We read the state that's been set on the root node, since that makes // it easier to test the various front-end states without having to actually // have UIState know about it.
let fxaStatus = document.documentElement.getAttribute("fxastatus");
if (fxaStatus == "not_configured") { // sign in button in app (hamburger) menu // should take you straight to fxa sign in page if (anchor.id == "appMenu-fxa-label2") { this.openFxAEmailFirstPageFromFxaMenu(anchor);
PanelUI.hide(); return;
}
// If we're signed out but have the PXI pref enabled // we should show the PXI panel instead of taking the user // straight to FxA sign-in if (this.FXA_CTA_MENU_ENABLED) { this.updateFxAPanel(UIState.get()); this.updateCTAPanel(anchor);
PanelUI.showSubView("PanelUI-fxa", anchor, aEvent);
} elseif (anchor == fxaToolbarMenuBtn) { // The fxa toolbar button doesn't have much context before the user // clicks it so instead of going straight to the login page, // we take them to a page that has more information this.emitFxaToolbarTelemetry("toolbar_icon", anchor);
openTrustedLinkIn("about:preferences#sync", "tab");
PanelUI.hide();
} return;
} // If the user is signed in and we have the PXI pref enabled then add // the pxi panel to the existing toolbar if (this.FXA_CTA_MENU_ENABLED) { this.updateCTAPanel(anchor);
}
if (!gFxaToolbarAccessed) {
Services.prefs.setBoolPref("identity.fxaccounts.toolbar.accessed", true);
}
this.enableSendTabIfValidTab();
if (!this.getSendTabTargets().length) {
PanelMultiView.getViewNode(
document, "PanelUI-fxa-menu-sendtab-button"
).hidden = true;
}
_disableSyncOffIndicator() { const newSyncSetupEnabled =
NimbusFeatures.syncSetupFlow.getVariable("enabled"); const SYNC_PANEL_ACCESSED_PREF = "identity.fxaccounts.toolbar.syncSetup.panelAccessed"; // If the user was enrolled in the experiment and hasn't previously accessed // the panel, we disable the sync off indicator if (
newSyncSetupEnabled &&
!Services.prefs.getBoolPref(SYNC_PANEL_ACCESSED_PREF, false)
) { // Turn off the indicator so the user doesn't see it in subsequent openings
Services.prefs.setBoolPref(SYNC_PANEL_ACCESSED_PREF, true);
}
},
_shouldShowSyncOffIndicator() { // We only ever want to show the user the dot once, once they've clicked into the panel // we do not show them the dot anymore if (
Services.prefs.getBoolPref( "identity.fxaccounts.toolbar.syncSetup.panelAccessed", false
)
) { returnfalse;
} return NimbusFeatures.syncSetupFlow.getVariable("enabled");
},
// Reset UI elements to default state
cadButtonEl.setAttribute("disabled", true);
cadButtonEl.hidden = isNewSyncSetupFlowEnabled;
syncNowButtonEl.hidden = true;
signedInContainer.hidden = true;
fxaMenuAccountButtonEl.classList.remove("subviewbutton-nav");
fxaMenuAccountButtonEl.removeAttribute("closemenu");
syncSetupEl.removeAttribute("hidden");
menuHeaderDescriptionEl.hidden = false;
// The Firefox Account toolbar currently handles 3 different states for // users. The default `not_configured` state shows an empty avatar, `unverified` // state shows an avatar with an email icon, `login-failed` state shows an avatar // with a danger icon and the `verified` state will show the users // custom profile image or a filled avatar.
let stateValue = "not_configured";
let headerTitleL10nId;
let headerDescription;
// Update UI elements with determined values
mainWindowEl.setAttribute("fxastatus", stateValue);
menuHeaderTitleEl.value = this.fluentStrings.formatValueSync(headerTitleL10nId); // If we description is empty, we hide it
menuHeaderDescriptionEl.hidden = !headerDescription;
menuHeaderDescriptionEl.value = headerDescription; // We remove the data-l10n-id attribute here to prevent the node's value // attribute from being overwritten by Fluent when the panel is moved // around in the DOM.
menuHeaderTitleEl.removeAttribute("data-l10n-id");
menuHeaderDescriptionEl.removeAttribute("data-l10n-id");
},
enableSendTabIfValidTab() { // All tabs selected must be sendable for the Send Tab button to be enabled // on the FxA menu.
let canSendAllURIs = gBrowser.selectedTabs.every(
t => !!BrowserUtils.getShareableURL(t.linkedBrowser.currentURI)
);
// This is mis-named - it can be used to record any FxA UI telemetry, whether from // the toolbar or not. The required `sourceElement` param is enough to help us know // how to record the interaction.
emitFxaToolbarTelemetry(type, sourceElement) { if (UIState.isReady()) { const state = UIState.get(); const hasAvatar = state.avatarURL && !state.avatarIsDefault;
let extraOptions = {
fxa_status: state.status,
fxa_avatar: hasAvatar ? "true" : "false",
fxa_sync_on: state.syncEnabled,
};
let defaultLabel = this.fluentStrings.formatValueSync( "appmenu-fxa-signed-in-label"
); // Reset the status bar to its original state.
appMenuLabel.setAttribute("label", defaultLabel);
appMenuLabel.removeAttribute("aria-labelledby");
appMenuStatus.removeAttribute("fxastatus");
// While we prefer the display name in most case, in some strings // where the context is something like "Verify %s", the email // is used even when there's a display name. if (status == UIState.STATUS_LOGIN_FAILED) { const [tooltipDescription, errorLabel] = this.fluentStrings.formatValuesSync([
{ id: "account-reconnect", args: { email } },
{ id: "account-disconnected2" },
]);
appMenuStatus.setAttribute("fxastatus", "login-failed");
appMenuStatus.setAttribute("tooltiptext", tooltipDescription);
appMenuLabel.classList.add("subviewbutton-nav");
appMenuHeaderTitle.hidden = false;
appMenuHeaderTitle.value = errorLabel;
appMenuHeaderDescription.value = displayName || email;
async clickFxAMenuHeaderButton(sourceElement) { // Depending on the current logged in state of a user, // clicking the FxA header will either open // a sign-in page, account management page, or sync // preferences page. const { status } = UIState.get(); switch (status) { case UIState.STATUS_NOT_CONFIGURED: this.openFxAEmailFirstPageFromFxaMenu(sourceElement); break; case UIState.STATUS_LOGIN_FAILED: this.openPrefsFromFxaMenu("sync_settings", sourceElement); break; case UIState.STATUS_NOT_VERIFIED: this.openFxAEmailFirstPage("fxa_app_menu_reverify"); break; case UIState.STATUS_SIGNED_IN: this._openFxAManagePageFromElement(sourceElement);
}
},
// Gets the telemetry "entry point" we should use for a given UI element. // This entry-point is recorded in both client telemetry (typically called the "object") // and where applicable, also communicated to the server for server telemetry via a URL query param. // // It inspects the parent elements to determine if the element is within one of our "well known" // UI groups, in which case it will return a string for that group (eg, "fxa_app_menu", "fxa_toolbar_button"). // Otherwise (eg, the item might be directly on the context menu), it will return "fxa_discoverability_native".
_getEntryPointForElement(sourceElement) { // Note that when an element is in either the app menu or the toolbar button menu, // in both cases it *will* have a parent with ID "PanelUI-fxa-menu". But when // in the app menu, it will also have a grand-parent with ID "appMenu-popup". // So we must check for that outer grandparent first. const appMenuPanel = document.getElementById("appMenu-popup"); if (appMenuPanel.contains(sourceElement)) { return"fxa_app_menu";
} // If it *is* the toolbar button... if (sourceElement.id == "fxa-toolbar-menu-button") { return"fxa_avatar_menu";
} // ... or is in the panel shown by that button. const fxaMenu = document.getElementById("PanelUI-fxa-menu"); if (fxaMenu && fxaMenu.contains(sourceElement)) { return"fxa_avatar_menu";
} return"fxa_discoverability_native";
},
// Returns true if we managed to send the tab to any targets, false otherwise.
async sendTabToDevice(url, targets, title) { const fxaCommandsDevices = []; for (const target of targets) { if (fxAccounts.commands.sendTab.isDeviceCompatible(target)) {
fxaCommandsDevices.push(target);
} else { this.log.error(`Target ${target.id} unsuitable for send tab.`);
}
} // If a primary-password is enabled then it must be unlocked so FxA can get // the encryption keys from the login manager. (If we end up using the "sync" // fallback that would end up prompting by itself, but the FxA command route // will not) - so force that here.
let cryptoSDR = Cc["@mozilla.org/login-manager/crypto/SDR;1"].getService(
Ci.nsILoginManagerCrypto
); if (!cryptoSDR.isLoggedIn) { if (cryptoSDR.uiBusy) { this.log.info("Master password UI is busy - not sending the tabs"); returnfalse;
} try {
cryptoSDR.encrypt("bacon"); // forces the mp prompt.
} catch (e) { this.log.info( "Master password remains unlocked - not sending the tabs"
); returnfalse;
}
}
let numFailed = 0; if (fxaCommandsDevices.length) { this.log.info(
`Sending a tab to ${fxaCommandsDevices
.map(d => d.id)
.join(", ")} using FxA commands.`
); const report = await fxAccounts.commands.sendTab.send(
fxaCommandsDevices,
{ url, title }
); for (let { device, error } of report.failed) { this.log.error(
`Failed to send a tab with FxA commands for ${device.id}.`,
error
);
numFailed++;
}
} return numFailed < targets.length; // Good enough.
},
populateSendTabToDevicesMenu(
devicesPopup,
uri,
title,
multiselected,
createDeviceNodeFn,
isFxaMenu = false
) {
uri = BrowserUtils.getShareableURL(uri); if (!uri) { // log an error as everyone should have already checked this. this.log.error("Ignoring request to share a non-sharable URL"); return;
} if (!createDeviceNodeFn) {
createDeviceNodeFn = (targetId, name) => {
let eltName = name ? "menuitem" : "menuseparator"; return document.createXULElement(eltName);
};
}
// remove existing menu items for (let i = devicesPopup.children.length - 1; i >= 0; --i) {
let child = devicesPopup.children[i]; if (child.classList.contains("sync-menuitem")) {
child.remove();
}
}
if (gSync.sendTabConfiguredAndLoading) { // We can only be in this case in the page action menu. return;
}
const fragment = document.createDocumentFragment();
const state = UIState.get(); if (state.status == UIState.STATUS_SIGNED_IN) { const targets = this.getSendTabTargets(); if (targets.length) { this._appendSendTabDeviceList(
targets,
fragment,
createDeviceNodeFn,
uri.spec,
title,
multiselected,
isFxaMenu
);
} else { this._appendSendTabSingleDevice(fragment, createDeviceNodeFn);
}
} elseif (
state.status == UIState.STATUS_NOT_VERIFIED ||
state.status == UIState.STATUS_LOGIN_FAILED
) { this._appendSendTabVerify(fragment, createDeviceNodeFn);
} else { // The only status not handled yet is STATUS_NOT_CONFIGURED, and // when we're in that state, none of the menus that call // populateSendTabToDevicesMenu are available, so entering this // state is unexpected. thrownew Error( "Called populateSendTabToDevicesMenu when in STATUS_NOT_CONFIGURED " + "state."
);
}
const send = to => {
Promise.all(
tabsToSend.map(t => // sendTabToDevice does not reject. this.sendTabToDevice(t.url, to, t.title)
)
).then(results => { // Show the Sent! confirmation if any of the sends succeeded. if (results.includes(true)) { // FxA button could be hidden with CSS since the user is logged out, // although it seems likely this would only happen in testing...
let fxastatus = document.documentElement.getAttribute("fxastatus");
let anchorNode =
(fxastatus &&
fxastatus != "not_configured" &&
document.getElementById("fxa-toolbar-menu-button")?.parentNode
?.id != "widget-overflow-list" &&
document.getElementById("fxa-toolbar-menu-button")) ||
document.getElementById("PanelUI-menu-button");
ConfirmationHint.show(anchorNode, "confirmation-hint-send-to-device");
}
fxAccounts.flushLogFile();
});
}; const onSendAllCommand = () => {
send(targets);
}; const onTargetDeviceCommand = event => { const targetId = event.target.getAttribute("clientId"); const target = targets.find(t => t.id == targetId);
send([target]);
};
// "Send Tab to Device" menu item
updateTabContextMenu(aPopupMenu, aTargetTab) { // We may get here before initialisation. This situation // can lead to a empty label for 'Send To Device' Menu. this.init();
if (!this.FXA_ENABLED) { // These items are hidden in onFxaDisabled(). No need to do anything. return;
}
let hasASendableURI = false; for (let tab of aTargetTab.multiselected
? gBrowser.selectedTabs
: [aTargetTab]) { if (BrowserUtils.getShareableURL(tab.linkedBrowser.currentURI)) {
hasASendableURI = true; break;
}
} const enabled = !this.sendTabConfiguredAndLoading && hasASendableURI; const hideItems = this.shouldHideSendContextMenuItems(enabled);
let sendTabsToDevice = document.getElementById("context_sendTabToDevice");
sendTabsToDevice.disabled = !enabled;
// "Send Page to Device" and "Send Link to Device" menu items
updateContentContextMenu(contextMenu) { if (!this.FXA_ENABLED) {
--> --------------------
--> maximum size reached
--> --------------------
[ Verzeichnis aufwärts0.31unsichere Verbindung
Übersetzung europäischer Sprachen durch Browser
]