Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2017-05-04 11:12:17 +02:00
Родитель 8e3a1aaf44 237cea8312
Коммит 997eb39e9d
115 изменённых файлов: 2367 добавлений и 2636 удалений

Просмотреть файл

@ -1388,9 +1388,6 @@ pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox
// The remote URL of the FxA OAuth Server
pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
// Whether we display profile images in the UI or not.
pref("identity.fxaccounts.profile_image.enabled", true);
// Token server used by the FxA Sync identity.
pref("identity.sync.tokenserver.uri", "https://token.services.mozilla.com/1.0/sync/1.5");

Просмотреть файл

@ -285,7 +285,7 @@
accesskey="&sendPageToDevice.accesskey;"
hidden="true">
<menupopup id="context-sendpagetodevice-popup"
onpopupshowing="(() => { let browser = gBrowser || getPanelBrowser(); gFxAccounts.populateSendTabToDevicesMenu(event.target, browser.currentURI.spec, browser.contentTitle); })()"/>
onpopupshowing="(() => { let browser = gBrowser || getPanelBrowser(); gSync.populateSendTabToDevicesMenu(event.target, browser.currentURI.spec, browser.contentTitle); })()"/>
</menu>
<menuseparator id="context-sep-viewbgimage"/>
<menuitem id="context-viewbgimage"
@ -332,7 +332,7 @@
accesskey="&sendLinkToDevice.accesskey;"
hidden="true">
<menupopup id="context-sendlinktodevice-popup"
onpopupshowing="gFxAccounts.populateSendTabToDevicesMenu(event.target, gContextMenu.linkURL, gContextMenu.linkTextStr);"/>
onpopupshowing="gSync.populateSendTabToDevicesMenu(event.target, gContextMenu.linkURL, gContextMenu.linkTextStr);"/>
</menu>
<menuitem id="context-shareselect"
label="&shareSelect.label;"

Просмотреть файл

@ -1,430 +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 = {
_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",
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.
if (!this.weaveService.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");
},
isSendableURI(aURISpec) {
if (!aURISpec) {
return false;
}
// Disallow sending tabs with more than 65535 characters.
if (aURISpec.length > 65535) {
return false;
}
try {
// Filter out un-sendable URIs -- things like local files, object urls, etc.
const unsendableRegexp = new RegExp(
Services.prefs.getCharPref("services.sync.engine.tabs.filteredUrls"), "i");
return !unsendableRegexp.test(aURISpec);
} catch (e) {
// The preference has been removed, or is an invalid regexp, so we log an
// error and treat it as a valid URI -- and the more problematic case is
// the length, which we've already addressed.
Cu.reportError(`Failed to build url filter regexp for send tab: ${e}`);
return true;
}
},
get remoteClients() {
return Weave.Service.clientsEngine.remoteClients
.sort((a, b) => a.name.localeCompare(b.name));
},
init() {
// 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);
}
gNavToolbox.addEventListener("customizationstarting", this);
gNavToolbox.addEventListener("customizationending", this);
EnsureFxAccountsWebChannel();
this._initialized = true;
this.updateUI();
},
uninit() {
if (!this._initialized) {
return;
}
for (let topic of this.topics) {
Services.obs.removeObserver(this, topic);
}
this._initialized = false;
},
observe(subject, topic, data) {
switch (topic) {
case this.FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION:
this._cachedProfile = null;
// Fallthrough intended
default:
this.updateUI();
break;
}
},
handleEvent(event) {
this._inCustomizationMode = event.type == "customizationstarting";
this.updateUI();
},
// Note that updateUI() returns a Promise that's only used by tests.
updateUI() {
let profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled", false);
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) {
PanelUI.showBadgeOnlyNotification("fxa-needs-authentication");
} else {
PanelUI.removeNotification("fxa-needs-authentication");
}
}
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() {
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() {
openPreferences("paneSync", { urlParams: { entrypoint: "menupanel" } });
},
openAccountsPage(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(entryPoint) {
this.openAccountsPage("reauth", { entrypoint: entryPoint });
},
async openDevicesManagementPage(entryPoint) {
let url = await fxAccounts.promiseAccountsManageDevicesURI(entryPoint);
switchToTabHavingURI(url, true, {
replaceQueryString: true
});
},
sendTabToDevice(url, clientId, title) {
Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title);
},
populateSendTabToDevicesMenu(devicesPopup, url, title) {
// remove existing menu items
while (devicesPopup.hasChildNodes()) {
devicesPopup.firstChild.remove();
}
const fragment = document.createDocumentFragment();
const onTargetDeviceCommand = (event) => {
let clients = event.target.getAttribute("clientId") ?
[event.target.getAttribute("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(aPopupMenu, aTargetTab) {
if (!this.sendTabToDeviceEnabled ||
!this.weaveService.ready) {
return;
}
const targetURI = aTargetTab.linkedBrowser.currentURI.spec;
const showSendTab = this.remoteClients.length > 0 && this.isSendableURI(targetURI);
["context_sendTabToDevice", "context_sendTabToDevice_separator"]
.forEach(id => { document.getElementById(id).hidden = !showSendTab });
},
initPageContextMenu(contextMenu) {
if (!this.sendTabToDeviceEnabled ||
!this.weaveService.ready) {
return;
}
const remoteClientPresent = this.remoteClients.length > 0;
// showSendLink and showSendPage are mutually exclusive
let showSendLink = remoteClientPresent
&& (contextMenu.onSaveableLink || contextMenu.onPlainTextLink);
const showSendPage = !showSendLink && remoteClientPresent
&& !(contextMenu.isContentSelected ||
contextMenu.onImage || contextMenu.onCanvas ||
contextMenu.onVideo || contextMenu.onAudio ||
contextMenu.onLink || contextMenu.onTextInput)
&& this.isSendableURI(contextMenu.browser.currentURI.spec);
if (showSendLink) {
// This isn't part of the condition above since we don't want to try and
// send the page if a link is clicked on or selected but is not sendable.
showSendLink = this.isSendableURI(contextMenu.linkURL);
}
["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(this, "EnsureFxAccountsWebChannel",
"resource://gre/modules/FxAccountsWebChannel.jsm");
XPCOMUtils.defineLazyGetter(gFxAccounts, "weaveService", function() {
return Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
});

Просмотреть файл

@ -475,12 +475,7 @@
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());"
>
<menupopup id="menu_ToolsPopup">
<menuitem id="menu_openDownloads"
label="&downloads.label;"
accesskey="&downloads.accesskey;"
@ -497,17 +492,17 @@
label="&syncSignIn.label;"
accesskey="&syncSignIn.accesskey;"
observes="sync-setup-state"
oncommand="gSyncUI.openPrefs('menubar')"/>
oncommand="gSync.openPrefs('menubar')"/>
<menuitem id="sync-syncnowitem"
label="&syncSyncNowItem.label;"
accesskey="&syncSyncNowItem.accesskey;"
observes="sync-syncnow-state"
oncommand="gSyncUI.doSync(event);"/>
oncommand="gSync.doSync(event);"/>
<menuitem id="sync-reauthitem"
label="&syncReAuthItem.label;"
accesskey="&syncReAuthItem.accesskey;"
observes="sync-reauth-state"
oncommand="gSyncUI.openSignInAgainPage('menubar');"/>
oncommand="gSync.openSignInAgainPage('menubar');"/>
<menuseparator id="devToolsSeparator"/>
<menu id="webDeveloperMenu"
label="&webDeveloperMenu.label;"

Просмотреть файл

@ -0,0 +1,574 @@
/* 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://services-sync/UIState.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "EnsureFxAccountsWebChannel",
"resource://gre/modules/FxAccountsWebChannel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Weave",
"resource://services-sync/main.js");
if (AppConstants.MOZ_SERVICES_CLOUDSYNC) {
XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
"resource://gre/modules/CloudSync.jsm");
}
const MIN_STATUS_ANIMATION_DURATION = 1600;
var gSync = {
_initialized: 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 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 fxaStrings() {
delete this.fxaStrings;
return this.fxaStrings = Services.strings.createBundle(
"chrome://browser/locale/accounts.properties"
);
},
get syncStrings() {
delete this.syncStrings;
// XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
// but for now just make it work
return this.syncStrings = Services.strings.createBundle(
"chrome://weave/locale/sync.properties"
);
},
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() {
// Bail out if we're already initialized or for pop-up windows.
if (this._initialized || !window.toolbar.visible) {
return;
}
for (let topic of this._obs) {
Services.obs.addObserver(this, topic, true);
}
// initial label for the sync buttons.
let broadcaster = document.getElementById("sync-status");
broadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncnow.label"));
// 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);
}
}
this.maybeMoveSyncedTabsButton();
EnsureFxAccountsWebChannel();
this._initialized = true;
},
uninit() {
if (!this._initialized) {
return;
}
for (let topic of this._obs) {
Services.obs.removeObserver(this, topic);
}
this._initialized = false;
},
observe(subject, topic, data) {
if (!this._initialized) {
Cu.reportError("browser-sync observer called after unload: " + topic);
return;
}
switch (topic) {
case UIState.ON_UPDATE:
const state = UIState.get();
this.updateAllUI(state);
break;
case "quit-application":
// Stop the animation timer on shutdown, since we can't update the UI
// after this.
clearTimeout(this._syncAnimationTimer);
break;
case "weave:engine:sync:finish":
if (data != "clients") {
return;
}
this.onClientsSynced();
break;
}
},
updateAllUI(state) {
this.updatePanelBadge(state);
this.updatePanelPopup(state);
this.updateStateBroadcasters(state);
this.updateSyncButtonsTooltip(state);
this.updateSyncStatus(state);
},
updatePanelPopup(state) {
let defaultLabel = this.panelUIStatus.getAttribute("defaultlabel");
// The localization string is for the signed in text, but it's the default text as well
let defaultTooltiptext = this.panelUIStatus.getAttribute("signedinTooltiptext");
const status = state.status;
// Reset the status bar to its original state.
this.panelUILabel.setAttribute("label", defaultLabel);
this.panelUIStatus.setAttribute("tooltiptext", defaultTooltiptext);
this.panelUIFooter.removeAttribute("fxastatus");
this.panelUIAvatar.style.removeProperty("list-style-image");
if (status == UIState.STATUS_NOT_CONFIGURED) {
return;
}
// At this point we consider sync to be configured (but still can be in an error state).
if (status == UIState.STATUS_LOGIN_FAILED) {
let tooltipDescription = this.fxaStrings.formatStringFromName("reconnectDescription", [state.email], 1);
let errorLabel = this.panelUIStatus.getAttribute("errorlabel");
this.panelUIFooter.setAttribute("fxastatus", "login-failed");
this.panelUILabel.setAttribute("label", errorLabel);
this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
return;
} else if (status == UIState.STATUS_NOT_VERIFIED) {
let tooltipDescription = this.fxaStrings.formatStringFromName("verifyDescription", [state.email], 1);
let unverifiedLabel = this.panelUIStatus.getAttribute("unverifiedlabel");
this.panelUIFooter.setAttribute("fxastatus", "unverified");
this.panelUILabel.setAttribute("label", unverifiedLabel);
this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
return;
}
// At this point we consider sync to be logged-in.
this.panelUIFooter.setAttribute("fxastatus", "signedin");
this.panelUILabel.setAttribute("label", state.displayName || state.email);
if (state.avatarURL) {
let bgImage = "url(\"" + state.avatarURL + "\")";
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.panelUIAvatar.style.removeProperty("list-style-image");
}
};
img.src = state.avatarURL;
}
},
updatePanelBadge(state) {
if (state.status == UIState.STATUS_LOGIN_FAILED ||
state.status == UIState.STATUS_NOT_VERIFIED) {
PanelUI.showBadgeOnlyNotification("fxa-needs-authentication");
} else {
PanelUI.removeNotification("fxa-needs-authentication");
}
},
updateStateBroadcasters(state) {
const status = state.status;
// 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;
if (CloudSync && CloudSync.ready && CloudSync().adapters.count) {
document.getElementById("sync-syncnow-state").hidden = false;
} else if (status == UIState.STATUS_LOGIN_FAILED) {
// unhiding this element makes the menubar show the login failure state.
document.getElementById("sync-reauth-state").hidden = false;
} else if (status == UIState.STATUS_NOT_CONFIGURED ||
status == UIState.STATUS_NOT_VERIFIED) {
document.getElementById("sync-setup-state").hidden = false;
} else {
document.getElementById("sync-syncnow-state").hidden = false;
}
},
updateSyncStatus(state) {
const broadcaster = document.getElementById("sync-status");
const syncingUI = broadcaster.getAttribute("syncstatus") == "active";
if (state.syncing != syncingUI) { // Do we need to update the UI?
state.syncing ? this.onActivityStart() : this.onActivityStop();
}
},
onMenuPanelCommand() {
switch (this.panelUIFooter.getAttribute("fxastatus")) {
case "signedin":
this.openPrefs("menupanel");
break;
case "error":
if (this.panelUIFooter.getAttribute("fxastatus") == "unverified") {
this.openPrefs("menupanel");
} else {
this.openSignInAgainPage("menupanel");
}
break;
default:
this.openPrefs("menupanel");
break;
}
PanelUI.hide();
},
openAccountsPage(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(entryPoint) {
this.openAccountsPage("reauth", { entrypoint: entryPoint });
},
async openDevicesManagementPage(entryPoint) {
let url = await fxAccounts.promiseAccountsManageDevicesURI(entryPoint);
switchToTabHavingURI(url, true, {
replaceQueryString: true
});
},
sendTabToDevice(url, clientId, title) {
Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title);
},
populateSendTabToDevicesMenu(devicesPopup, url, title) {
// remove existing menu items
while (devicesPopup.hasChildNodes()) {
devicesPopup.firstChild.remove();
}
const fragment = document.createDocumentFragment();
const onTargetDeviceCommand = (event) => {
let clients = event.target.getAttribute("clientId") ?
[event.target.getAttribute("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.fxaStrings.GetStringFromName("sendTabToAllDevices.menuitem");
addTargetDevice("", allDevicesLabel);
}
devicesPopup.appendChild(fragment);
},
isSendableURI(aURISpec) {
if (!aURISpec) {
return false;
}
// Disallow sending tabs with more than 65535 characters.
if (aURISpec.length > 65535) {
return false;
}
try {
// Filter out un-sendable URIs -- things like local files, object urls, etc.
const unsendableRegexp = new RegExp(
Services.prefs.getCharPref("services.sync.engine.tabs.filteredUrls"), "i");
return !unsendableRegexp.test(aURISpec);
} catch (e) {
// The preference has been removed, or is an invalid regexp, so we log an
// error and treat it as a valid URI -- and the more problematic case is
// the length, which we've already addressed.
Cu.reportError(`Failed to build url filter regexp for send tab: ${e}`);
return true;
}
},
updateTabContextMenu(aPopupMenu, aTargetTab) {
if (!this.sendTabToDeviceEnabled || !this.weaveService.ready) {
return;
}
const targetURI = aTargetTab.linkedBrowser.currentURI.spec;
const showSendTab = this.remoteClients.length > 0 && this.isSendableURI(targetURI);
["context_sendTabToDevice", "context_sendTabToDevice_separator"]
.forEach(id => { document.getElementById(id).hidden = !showSendTab });
},
initPageContextMenu(contextMenu) {
if (!this.sendTabToDeviceEnabled || !this.weaveService.ready) {
return;
}
const remoteClientPresent = this.remoteClients.length > 0;
// showSendLink and showSendPage are mutually exclusive
let showSendLink = remoteClientPresent
&& (contextMenu.onSaveableLink || contextMenu.onPlainTextLink);
const showSendPage = !showSendLink && remoteClientPresent
&& !(contextMenu.isContentSelected ||
contextMenu.onImage || contextMenu.onCanvas ||
contextMenu.onVideo || contextMenu.onAudio ||
contextMenu.onLink || contextMenu.onTextInput)
&& this.isSendableURI(contextMenu.browser.currentURI.spec);
if (showSendLink) {
// This isn't part of the condition above since we don't want to try and
// send the page if a link is clicked on or selected but is not sendable.
showSendLink = this.isSendableURI(contextMenu.linkURL);
}
["context-sendpagetodevice", "context-sep-sendpagetodevice"]
.forEach(id => contextMenu.showItem(id, showSendPage));
["context-sendlinktodevice", "context-sep-sendlinktodevice"]
.forEach(id => contextMenu.showItem(id, showSendLink));
},
// Functions called by observers
onActivityStart() {
clearTimeout(this._syncAnimationTimer);
this._syncStartTime = Date.now();
let broadcaster = document.getElementById("sync-status");
broadcaster.setAttribute("syncstatus", "active");
broadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncing2.label"));
broadcaster.setAttribute("disabled", "true");
},
_onActivityStop() {
if (!gBrowser)
return;
let broadcaster = document.getElementById("sync-status");
broadcaster.removeAttribute("syncstatus");
broadcaster.removeAttribute("disabled");
broadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncnow.label"));
Services.obs.notifyObservers(null, "test:browser-sync:activity-stop");
},
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._onActivityStop(), animationTime);
} else {
this._onActivityStop();
}
},
// doSync forces a sync - it *does not* return a promise as it is called
// via the various UI components.
doSync() {
if (!UIState.isReady()) {
return;
}
const state = UIState.get();
if (state.status == UIState.STATUS_SIGNED_IN) {
setTimeout(() => Weave.Service.errorHandler.syncAndReportErrors(), 0);
}
Services.obs.notifyObservers(null, "cloudsync:user-sync");
},
openPrefs(entryPoint = "syncbutton") {
window.openPreferences("paneSync", { urlParams: { entrypoint: 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 we are 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 = Services.prefs.getBoolPref(prefName, false);
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(state) {
const status = state.status;
// 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 gSync for strings.
let tooltiptext;
if (status == UIState.STATUS_NOT_VERIFIED) {
// "needs verification"
tooltiptext = this.fxaStrings.formatStringFromName("verifyDescription", [state.email], 1);
} else if (status == UIState.STATUS_NOT_CONFIGURED) {
// "needs setup".
tooltiptext = this.syncStrings.GetStringFromName("signInToSync.description");
} else if (status == UIState.STATUS_LOGIN_FAILED) {
// "need to reconnect/re-enter your password"
tooltiptext = this.fxaStrings.formatStringFromName("reconnectDescription", [state.email], 1);
} else {
// Sync appears configured - format the "last synced at" time.
tooltiptext = this.formatLastSyncDate(state.lastSync);
}
let broadcaster = document.getElementById("sync-status");
if (broadcaster) {
if (tooltiptext) {
broadcaster.setAttribute("tooltiptext", tooltiptext);
} else {
broadcaster.removeAttribute("tooltiptext");
}
}
},
get withinLastWeekFormat() {
delete this.withinLastWeekFormat;
return this.withinLastWeekFormat = new Intl.DateTimeFormat(undefined,
{weekday: "long", hour: "numeric", minute: "numeric"});
},
get oneWeekOrOlderFormat() {
delete this.oneWeekOrOlderFormat;
return this.oneWeekOrOlderFormat = new Intl.DateTimeFormat(undefined,
{month: "long", day: "numeric"});
},
formatLastSyncDate(date) {
let sixDaysAgo = (() => {
let tempDate = new Date();
tempDate.setDate(tempDate.getDate() - 6);
tempDate.setHours(0, 0, 0, 0);
return tempDate;
})();
// It may be confusing for the user to see "Last Sync: Monday" when the last
// sync was indeed a Monday, but 3 weeks ago.
let dateFormat = date < sixDaysAgo ? this.oneWeekOrOlderFormat : this.withinLastWeekFormat;
let lastSyncDateString = dateFormat.format(date);
return this.syncStrings.formatStringFromName("lastSync2.label", [lastSyncDateString], 1);
},
onClientsSynced() {
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");
}
}
},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIObserver,
Ci.nsISupportsWeakReference
])
};
XPCOMUtils.defineLazyGetter(gSync, "weaveService", function() {
return Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
});

Просмотреть файл

@ -1,498 +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");
Cu.import("resource://gre/modules/Services.jsm");
if (AppConstants.MOZ_SERVICES_CLOUDSYNC) {
XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
"resource://gre/modules/CloudSync.jsm");
}
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,
_withinLastWeekFormat: null,
_oneWeekOrOlderFormat: null,
init() {
// 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", () => {
this._unloaded = true;
Services.obs.removeObserver(this, "weave:service:ready");
Services.obs.removeObserver(this, "quit-application");
if (this.weaveService.ready) {
this._obs.forEach(topic => {
Services.obs.removeObserver(this, topic);
});
}
}, { once: true });
},
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() {
return fxAccounts.getSignedInUser().then(user => {
// We want to treat "account needs verification" as "needs setup".
return !(user && user.verified);
});
},
// Returns a promise that resolves with true if the user currently signed in
// to Sync needs to be verified, false otherwise.
_needsVerification() {
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;
});
},
// 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() {
// 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;
if (CloudSync && CloudSync.ready && CloudSync().adapters.count) {
document.getElementById("sync-syncnow-state").hidden = false;
} else if (loginFailed) {
// 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() {
let brand = Services.strings.createBundle(
"chrome://branding/locale/brand.properties");
return brand.GetStringFromName("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");
}).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.openPrefs();
} else {
this.doSync();
}
}).catch(err => {
this.log.error("Failed to handle toolbar button command", err);
});
},
/**
* Open the Sync preferences.
*
* @param entryPoint
* Indicates the entrypoint from where this method was called.
*/
openPrefs(entryPoint = "syncbutton") {
openPreferences("paneSync", { urlParams: { entrypoint: entryPoint } });
},
openSignInAgainPage(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 = Services.prefs.getBoolPref(prefName, false);
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;
let user = yield fxAccounts.getSignedInUser();
if (user) {
email = user.email;
}
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");
}
}
}),
getWithinLastWeekFormat() {
return this._withinLastWeekFormat ||
(this._withinLastWeekFormat =
new Intl.DateTimeFormat(undefined, {weekday: "long", hour: "numeric", minute: "numeric"}));
},
getOneWeekOrOlderFormat() {
return this._oneWeekOrOlderFormat ||
(this._oneWeekOrOlderFormat =
new Intl.DateTimeFormat(undefined, {month: "long", day: "numeric"}));
},
formatLastSyncDate(date) {
let sixDaysAgo = (() => {
let tempDate = new Date();
tempDate.setDate(tempDate.getDate() - 6);
tempDate.setHours(0, 0, 0, 0);
return tempDate;
})();
// It may be confusing for the user to see "Last Sync: Monday" when the last
// sync was indeed a Monday, but 3 weeks ago.
let dateFormat = date < sixDaysAgo ? this.getOneWeekOrOlderFormat() : this.getWithinLastWeekFormat();
let lastSyncDateString = dateFormat.format(date);
return this._stringBundle.formatStringFromName("lastSync2.label", [lastSyncDateString], 1);
},
onClientsSynced() {
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 Services.strings.createBundle(
"chrome://weave/locale/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;
});

Просмотреть файл

@ -1503,8 +1503,7 @@ var gBrowserInit = {
PointerLock.init();
// initialize the sync UI
gSyncUI.init();
gFxAccounts.init();
gSync.init();
if (AppConstants.MOZ_DATA_REPORTING)
gDataNotificationInfoBar.init();
@ -1651,7 +1650,7 @@ var gBrowserInit = {
FullScreen.uninit();
gFxAccounts.uninit();
gSync.uninit();
gExtensionsNotifications.uninit();
@ -1814,7 +1813,7 @@ if (AppConstants.platform == "macosx") {
gPrivateBrowsingUI.init();
// initialize the sync UI
gSyncUI.init();
gSync.init();
if (AppConstants.E10S_TESTING_ONLY) {
gRemoteTabsUI.init();
@ -6799,7 +6798,7 @@ function checkEmptyPageOrigin(browser = gBrowser.selectedBrowser,
}
function BrowserOpenSyncTabs() {
gSyncUI.openSyncedTabsPanel();
gSync.openSyncedTabsPanel();
}
function ReportFalseDeceptiveSite() {
@ -8173,7 +8172,7 @@ var TabContextMenu = {
this.contextTab.addEventListener("TabAttrModified", this);
aPopupMenu.addEventListener("popuphiding", this);
gFxAccounts.updateTabContextMenu(aPopupMenu, this.contextTab);
gSync.updateTabContextMenu(aPopupMenu, this.contextTab);
},
handleEvent(aEvent) {
switch (aEvent.type) {

Просмотреть файл

@ -106,7 +106,7 @@
<menu id="context_sendTabToDevice" label="&sendTabToDevice.label;"
accesskey="&sendTabToDevice.accesskey;" hidden="true">
<menupopup id="context_sendTabToDevicePopupMenu"
onpopupshowing="gFxAccounts.populateSendTabToDevicesMenu(event.target, TabContextMenu.contextTab.linkedBrowser.currentURI.spec, TabContextMenu.contextTab.linkedBrowser.contentTitle);"/>
onpopupshowing="gSync.populateSendTabToDevicesMenu(event.target, TabContextMenu.contextTab.linkedBrowser.currentURI.spec, TabContextMenu.contextTab.linkedBrowser.contentTitle);"/>
</menu>
<menuseparator/>
<menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
@ -492,7 +492,7 @@
<menuitem label="&syncedTabs.context.managedevices.label;"
accesskey="&syncedTabs.context.managedevices.accesskey;"
id="syncedTabsManageDevices"
oncommand="gFxAccounts.openDevicesManagementPage('syncedtabs-sidebar');"/>
oncommand="gSync.openDevicesManagementPage('syncedtabs-sidebar');"/>
<menuitem label="&syncSyncNowItem.label;"
accesskey="&syncSyncNowItem.accesskey;"
id="syncedTabsRefresh"/>

Просмотреть файл

@ -30,7 +30,7 @@
<script type="application/javascript" src="chrome://browser/content/browser-safebrowsing.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-sidebar.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-social.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-syncui.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-sync.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-tabsintitlebar.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-thumbnails.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-trackingprotection.js"/>
@ -38,5 +38,3 @@
#ifdef MOZ_DATA_REPORTING
<script type="application/javascript" src="chrome://browser/content/browser-data-submission-info-bar.js"/>
#endif
<script type="application/javascript" src="chrome://browser/content/browser-fxaccounts.js"/>

Просмотреть файл

@ -121,9 +121,6 @@ with Files("browser-feeds.js"):
with Files("browser-fullZoom.js"):
BUG_COMPONENT = ("Firefox", "Tabbed Browsing")
with Files("browser-fxaccounts.js"):
BUG_COMPONENT = ("Core", "FxAccounts")
with Files("browser-gestureSupport.js"):
BUG_COMPONENT = ("Core", "Widget: Cocoa")
@ -145,7 +142,7 @@ with Files("browser-safebrowsing.js"):
with Files("*social*"):
BUG_COMPONENT = ("Firefox", "SocialAPI")
with Files("browser-syncui.js"):
with Files("browser-sync.js"):
BUG_COMPONENT = ("Firefox", "Sync")
with Files("browser-tabPreviews.xml"):

Просмотреть файл

@ -601,7 +601,7 @@ nsContextMenu.prototype = {
},
initSyncItems() {
gFxAccounts.initPageContextMenu(this);
gSync.initPageContextMenu(this);
},
openPasswordManager() {

Просмотреть файл

@ -8,7 +8,6 @@
[DEFAULT]
support-files =
POSTSearchEngine.xml
accounts_testRemoteCommands.html
alltabslistener.html
app_bug575561.html
app_subframe_bug575561.html
@ -18,7 +17,6 @@ support-files =
browser_bug678392-1.html
browser_bug678392-2.html
browser_bug970746.xhtml
browser_fxa_web_channel.html
browser_registerProtocolHandler_notification.html
browser_star_hsts.sjs
browser_tab_dragdrop2_frame1.xul
@ -94,11 +92,6 @@ support-files =
!/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_aboutAccounts.js]
skip-if = os == "linux" # Bug 958026
support-files =
content_aboutAccounts.js
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_aboutCertError.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_aboutNetError.js]
@ -376,11 +369,6 @@ skip-if = true # browser_drag.js is disabled, as it needs to be updated for the
tags = fullscreen
skip-if = os == "linux" # Linux: Intermittent failures - bug 941575.
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_fxaccounts.js]
support-files = fxa_profile_handler.sjs
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_fxa_web_channel.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_gestureSupport.js]
skip-if = e10s # Bug 863514 - no gesture support.
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
@ -497,9 +485,6 @@ support-files =
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_subframe_favicons_not_used.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_syncui.js]
skip-if = os == 'linux' # Bug 1304272
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_tab_close_dependent_window.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_tabDrop.js]

Просмотреть файл

@ -903,7 +903,7 @@ add_task(function* test_input_spell_false() {
const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
add_task(function* test_plaintext_sendpagetodevice() {
if (!gFxAccounts.sendTabToDeviceEnabled) {
if (!gSync.sendTabToDeviceEnabled) {
return;
}
yield ensureSyncReady();
@ -942,7 +942,7 @@ add_task(function* test_plaintext_sendpagetodevice() {
});
add_task(function* test_link_sendlinktodevice() {
if (!gFxAccounts.sendTabToDeviceEnabled) {
if (!gSync.sendTabToDeviceEnabled) {
return;
}
yield ensureSyncReady();

Просмотреть файл

@ -1,258 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var {Log} = Cu.import("resource://gre/modules/Log.jsm", {});
var {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
var {fxAccounts} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
var FxAccountsCommon = {};
Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon);
const TEST_ROOT = "http://example.com/browser/browser/base/content/test/general/";
// instrument gFxAccounts to send observer notifications when it's done
// what it does.
(function() {
let unstubs = {}; // The original functions we stub out.
// The stub functions.
let stubs = {
updateUI() {
return unstubs["updateUI"].call(gFxAccounts).then(() => {
Services.obs.notifyObservers(null, "test:browser_fxaccounts:updateUI");
});
},
// Opening preferences is trickier than it should be as leaks are reported
// due to the promises it fires off at load time and there's no clear way to
// know when they are done.
// So just ensure openPreferences is called rather than whether it opens.
openPreferences() {
Services.obs.notifyObservers(null, "test:browser_fxaccounts:openPreferences");
}
};
for (let name in stubs) {
unstubs[name] = gFxAccounts[name];
gFxAccounts[name] = stubs[name];
}
// and undo our damage at the end.
registerCleanupFunction(() => {
for (let name in unstubs) {
gFxAccounts[name] = unstubs[name];
}
stubs = unstubs = null;
});
})();
// Other setup/cleanup
var newTab;
Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri",
TEST_ROOT + "accounts_testRemoteCommands.html");
registerCleanupFunction(() => {
Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
Services.prefs.clearUserPref("identity.fxaccounts.remote.profile.uri");
gBrowser.removeTab(newTab);
});
add_task(function* initialize() {
// Set a new tab with something other than about:blank, so it doesn't get reused.
// We must wait for it to load or the promiseTabOpen() call in the next test
// gets confused.
newTab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla", {animate: false});
yield promiseTabLoaded(newTab);
});
// The elements we care about.
var panelUILabel = document.getElementById("PanelUI-fxa-label");
var panelUIStatus = document.getElementById("PanelUI-fxa-status");
var panelUIFooter = document.getElementById("PanelUI-footer-fxa");
// The tests
add_task(function* test_nouser() {
let user = yield fxAccounts.getSignedInUser();
Assert.strictEqual(user, null, "start with no user signed in");
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI");
Services.obs.notifyObservers(null, this.FxAccountsCommon.ONLOGOUT_NOTIFICATION);
yield promiseUpdateDone;
// Check the world - the FxA footer area is visible as it is offering a signin.
Assert.ok(isFooterVisible())
Assert.equal(panelUILabel.getAttribute("label"), panelUIStatus.getAttribute("defaultlabel"));
Assert.equal(panelUIStatus.getAttribute("tooltiptext"), panelUIStatus.getAttribute("signedinTooltiptext"));
Assert.ok(!panelUIFooter.hasAttribute("fxastatus"), "no fxsstatus when signed out");
Assert.ok(!panelUIFooter.hasAttribute("fxaprofileimage"), "no fxaprofileimage when signed out");
let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
panelUIStatus.click();
yield promisePreferencesOpened;
});
/*
XXX - Bug 1191162 - need a better hawk mock story or this will leak in debug builds.
add_task(function* test_unverifiedUser() {
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI");
yield setSignedInUser(false); // this will fire the observer that does the update.
yield promiseUpdateDone;
// Check the world.
Assert.ok(isFooterVisible())
Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
panelUIStatus.getAttribute("signedinTooltiptext"));
Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
panelUIStatus.click();
yield promisePreferencesOpened
yield signOut();
});
*/
add_task(function* test_verifiedUserEmptyProfile() {
// We see 2 updateUI() calls - one for the signedInUser and one after
// we first fetch the profile. We want them both to fire or we aren't testing
// the state we think we are testing.
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI", 2);
gFxAccounts._cachedProfile = null;
configureProfileURL({}); // successful but empty profile.
yield setSignedInUser(true); // this will fire the observer that does the update.
yield promiseUpdateDone;
// Check the world.
Assert.ok(isFooterVisible())
Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
panelUIStatus.getAttribute("signedinTooltiptext"));
Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
panelUIStatus.click();
yield promisePreferencesOpened;
yield signOut();
});
add_task(function* test_verifiedUserDisplayName() {
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI", 2);
gFxAccounts._cachedProfile = null;
configureProfileURL({ displayName: "Test User Display Name" });
yield setSignedInUser(true); // this will fire the observer that does the update.
yield promiseUpdateDone;
Assert.ok(isFooterVisible())
Assert.equal(panelUILabel.getAttribute("label"), "Test User Display Name");
Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
panelUIStatus.getAttribute("signedinTooltiptext"));
Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
yield signOut();
});
add_task(function* test_profileNotificationsClearsCache() {
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI", 1);
gFxAccounts._cachedProfile = { foo: "bar" };
Services.obs.notifyObservers(null, this.FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION);
Assert.ok(!gFxAccounts._cachedProfile);
yield promiseUpdateDone;
});
add_task(function* test_verifiedUserProfileFailure() {
// profile failure means only one observer fires.
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI", 1);
gFxAccounts._cachedProfile = null;
configureProfileURL(null, 500);
yield setSignedInUser(true); // this will fire the observer that does the update.
yield promiseUpdateDone;
Assert.ok(isFooterVisible());
Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
panelUIStatus.getAttribute("signedinTooltiptext"));
Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
yield signOut();
});
// Helpers.
function isFooterVisible() {
let style = window.getComputedStyle(panelUIFooter);
return style.getPropertyValue("display") == "flex";
}
function configureProfileURL(profile, responseStatus = 200) {
let responseBody = profile ? JSON.stringify(profile) : "";
let url = TEST_ROOT + "fxa_profile_handler.sjs?" +
"responseStatus=" + responseStatus +
"responseBody=" + responseBody +
// This is a bit cheeky - the FxA code will just append "/profile"
// to the preference value. We arrange for this to be seen by our
// .sjs as part of the query string.
"&path=";
Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", url);
}
function promiseObserver(topic, count = 1) {
return new Promise(resolve => {
let obs = (aSubject, aTopic, aData) => {
if (--count == 0) {
Services.obs.removeObserver(obs, aTopic);
resolve(aSubject);
}
}
Services.obs.addObserver(obs, topic);
});
}
var promiseTabOpen = Task.async(function*(urlBase) {
info("Waiting for tab to open...");
let event = yield promiseWaitForEvent(gBrowser.tabContainer, "TabOpen", true);
let tab = event.target;
yield promiseTabLoadEvent(tab);
ok(tab.linkedBrowser.currentURI.spec.startsWith(urlBase),
"Got " + tab.linkedBrowser.currentURI.spec + ", expecting " + urlBase);
let whenUnloaded = promiseTabUnloaded(tab);
gBrowser.removeTab(tab);
yield whenUnloaded;
});
function promiseTabUnloaded(tab) {
return new Promise(resolve => {
info("Wait for tab to unload");
function handle(event) {
tab.linkedBrowser.removeEventListener("unload", handle, true);
info("Got unload event");
resolve(event);
}
tab.linkedBrowser.addEventListener("unload", handle, true, true);
});
}
// FxAccounts helpers.
function setSignedInUser(verified) {
let data = {
email: "foo@example.com",
uid: "1234@lcip.org",
assertion: "foobar",
sessionToken: "dead",
kA: "beef",
kB: "cafe",
verified,
oauthTokens: {
// a token for the profile server.
profile: "key value",
}
}
return fxAccounts.setSignedInUser(data);
}
var signOut = Task.async(function* () {
// This test needs to make sure that any updates for the logout have
// completed before starting the next test, or we see the observer
// notifications get out of sync.
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI");
// we always want a "localOnly" signout here...
yield fxAccounts.signOut(true);
yield promiseUpdateDone;
});

Просмотреть файл

@ -1,205 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var {Log} = Cu.import("resource://gre/modules/Log.jsm", {});
var {Weave} = Cu.import("resource://services-sync/main.js", {});
var stringBundle = Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Ci.nsIStringBundleService)
.createBundle("chrome://weave/locale/sync.properties");
// ensure test output sees log messages.
Log.repository.getLogger("browserwindow.syncui").addAppender(new Log.DumpAppender());
// Send the specified sync-related notification and return a promise that
// resolves once gSyncUI._promiseUpateUI is complete and the UI is ready to check.
function notifyAndPromiseUIUpdated(topic) {
return new Promise(resolve => {
// Instrument gSyncUI so we know when the update is complete.
let oldPromiseUpdateUI = gSyncUI._promiseUpdateUI.bind(gSyncUI);
gSyncUI._promiseUpdateUI = function() {
return oldPromiseUpdateUI().then(() => {
// Restore our override.
gSyncUI._promiseUpdateUI = oldPromiseUpdateUI;
// Resolve the promise so the caller knows the update is done.
resolve();
});
};
// Now send the notification.
Services.obs.notifyObservers(null, topic);
});
}
// Sync manages 3 broadcasters so the menus correctly reflect the Sync state.
// Only one of these 3 should ever be visible - pass the ID of the broadcaster
// you expect to be visible and it will check it's the only one that is.
function checkBroadcasterVisible(broadcasterId) {
let all = ["sync-reauth-state", "sync-setup-state", "sync-syncnow-state"];
Assert.ok(all.indexOf(broadcasterId) >= 0, "valid id");
for (let check of all) {
let eltHidden = document.getElementById(check).hidden;
Assert.equal(eltHidden, check == broadcasterId ? false : true, check);
}
}
function promiseObserver(topic) {
return new Promise(resolve => {
let obs = (aSubject, aTopic, aData) => {
Services.obs.removeObserver(obs, aTopic);
resolve(aSubject);
}
Services.obs.addObserver(obs, topic);
});
}
function checkButtonTooltips(stringPrefix) {
for (let butId of ["PanelUI-remotetabs-syncnow", "PanelUI-fxa-icon"]) {
let text = document.getElementById(butId).getAttribute("tooltiptext");
let desc = `Text is "${text}", expecting it to start with "${stringPrefix}"`
Assert.ok(text.startsWith(stringPrefix), desc);
}
}
add_task(function* prepare() {
// add the Sync button to the toolbar so we can get it!
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_NAVBAR);
registerCleanupFunction(() => {
CustomizableUI.removeWidgetFromArea("sync-button");
});
let xps = Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
yield xps.whenLoaded();
// Put Sync and the UI into a known state.
Weave.Status.login = Weave.LOGIN_FAILED_NO_USERNAME;
yield notifyAndPromiseUIUpdated("weave:service:login:error");
checkBroadcasterVisible("sync-setup-state");
checkButtonTooltips("Sign In To Sync");
// mock out the "_needsSetup()" function so we don't short-circuit.
let oldNeedsSetup = window.gSyncUI._needsSetup;
window.gSyncUI._needsSetup = () => Promise.resolve(false);
registerCleanupFunction(() => {
window.gSyncUI._needsSetup = oldNeedsSetup;
// and an observer to set the state back to what it should be now we've
// restored the stub.
Services.obs.notifyObservers(null, "weave:service:login:finish");
});
// and a notification to have the state change away from "needs setup"
yield notifyAndPromiseUIUpdated("weave:service:login:finish");
checkBroadcasterVisible("sync-syncnow-state");
// open the sync-button panel so we can check elements in that.
document.getElementById("sync-button").click();
});
add_task(function* testSyncNeedsVerification() {
// mock out the "_needsVerification()" function
let oldNeedsVerification = window.gSyncUI._needsVerification;
window.gSyncUI._needsVerification = () => true;
try {
// a notification for the state change
yield notifyAndPromiseUIUpdated("weave:service:login:finish");
checkButtonTooltips("Verify");
} finally {
window.gSyncUI._needsVerification = oldNeedsVerification;
}
});
add_task(function* testSyncLoginError() {
checkBroadcasterVisible("sync-syncnow-state");
// Pretend we are in a "login failed" error state
Weave.Status.sync = Weave.LOGIN_FAILED;
Weave.Status.login = Weave.LOGIN_FAILED_LOGIN_REJECTED;
yield notifyAndPromiseUIUpdated("weave:ui:sync:error");
// But the menu *should* reflect the login error.
checkBroadcasterVisible("sync-reauth-state");
// The tooltips for the buttons should also reflect it.
checkButtonTooltips("Reconnect");
// Now pretend we just had a successful login - the error notification should go away.
Weave.Status.sync = Weave.STATUS_OK;
Weave.Status.login = Weave.LOGIN_SUCCEEDED;
yield notifyAndPromiseUIUpdated("weave:service:login:start");
yield notifyAndPromiseUIUpdated("weave:service:login:finish");
// The menus should be back to "all good"
checkBroadcasterVisible("sync-syncnow-state");
});
function checkButtonsStatus(shouldBeActive) {
for (let eid of [
"sync-status", // the broadcaster itself.
"sync-button", // the main sync button which observes the broadcaster
"PanelUI-fxa-icon", // the sync icon in the fxa footer that observes it.
]) {
let elt = document.getElementById(eid);
if (shouldBeActive) {
Assert.equal(elt.getAttribute("syncstatus"), "active", `${eid} should be active`);
} else {
Assert.ok(!elt.hasAttribute("syncstatus"), `${eid} should have no status attr`);
}
}
}
function* testButtonActions(startNotification, endNotification, expectActive = true) {
checkButtonsStatus(false);
// pretend a sync is starting.
yield notifyAndPromiseUIUpdated(startNotification);
checkButtonsStatus(expectActive);
// and has stopped
yield notifyAndPromiseUIUpdated(endNotification);
checkButtonsStatus(false);
}
function *doTestButtonActivities() {
// logins do not "activate" the spinner/button as they may block and make
// the UI look like Sync is never completing.
yield testButtonActions("weave:service:login:start", "weave:service:login:finish", false);
yield testButtonActions("weave:service:login:start", "weave:service:login:error", false);
// But notifications for Sync itself should activate it.
yield testButtonActions("weave:service:sync:start", "weave:service:sync:finish");
yield testButtonActions("weave:service:sync:start", "weave:service:sync:error");
// and ensure the counters correctly handle multiple in-flight syncs
yield notifyAndPromiseUIUpdated("weave:service:sync:start");
checkButtonsStatus(true);
// sync stops.
yield notifyAndPromiseUIUpdated("weave:service:sync:finish");
// Button should not be active.
checkButtonsStatus(false);
}
add_task(function* testButtonActivitiesInNavBar() {
// check the button's functionality while the button is in the NavBar - which
// it already is.
yield doTestButtonActivities();
});
add_task(function* testFormatLastSyncDateNow() {
let now = new Date();
let nowString = gSyncUI.formatLastSyncDate(now);
Assert.equal(nowString, "Last sync: " + now.toLocaleDateString(undefined, {weekday: "long", hour: "numeric", minute: "numeric"}));
});
add_task(function* testFormatLastSyncDateMonthAgo() {
let monthAgo = new Date();
monthAgo.setMonth(monthAgo.getMonth() - 1);
let monthAgoString = gSyncUI.formatLastSyncDate(monthAgo);
Assert.equal(monthAgoString, "Last sync: " + monthAgo.toLocaleDateString(undefined, {month: "long", day: "numeric"}));
});
add_task(function* testButtonActivitiesInPanel() {
// check the button's functionality while the button is in the panel - it's
// currently in the NavBar - move it to the panel and open it.
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
yield PanelUI.show();
try {
yield doTestButtonActivities();
} finally {
PanelUI.hide();
}
});

Просмотреть файл

@ -17,9 +17,9 @@ add_task(function* test() {
is(document.getElementById("context_reloadAllTabs").disabled, false, "Reload All Tabs is enabled");
if (gFxAccounts.sendTabToDeviceEnabled) {
const origIsSendableURI = gFxAccounts.isSendableURI;
gFxAccounts.isSendableURI = () => true;
if (gSync.sendTabToDeviceEnabled) {
const origIsSendableURI = gSync.isSendableURI;
gSync.isSendableURI = () => true;
// Check the send tab to device menu item
yield ensureSyncReady();
const oldGetter = setupRemoteClientsFixture(remoteClientsFixture);
@ -31,11 +31,11 @@ add_task(function* test() {
is(targets[0].getAttribute("label"), "Foo", "Foo target is present");
is(targets[1].getAttribute("label"), "Bar", "Bar target is present");
is(targets[3].getAttribute("label"), "All Devices", "All Devices target is present");
gFxAccounts.isSendableURI = () => false;
gSync.isSendableURI = () => false;
updateTabContextMenu(origTab);
is(document.getElementById("context_sendTabToDevice").hidden, true, "Send tab to device is hidden");
restoreRemoteClients(oldGetter);
gFxAccounts.isSendableURI = origIsSendableURI;
gSync.isSendableURI = origIsSendableURI;
}
// Hide the original tab.

Просмотреть файл

@ -1,34 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// This is basically an echo server!
// We just grab responseStatus and responseBody query params!
function reallyHandleRequest(request, response) {
var query = "?" + request.queryString;
var responseStatus = 200;
var match = /responseStatus=([^&]*)/.exec(query);
if (match) {
responseStatus = parseInt(match[1]);
}
var responseBody = "";
match = /responseBody=([^&]*)/.exec(query);
if (match) {
responseBody = decodeURIComponent(match[1]);
}
response.setStatusLine("1.0", responseStatus, "OK");
response.write(responseBody);
}
function handleRequest(request, response)
{
try {
reallyHandleRequest(request, response);
} catch (e) {
response.setStatusLine("1.0", 500, "NotOK");
response.write("Error handling request: " + e);
}
}

Просмотреть файл

@ -836,16 +836,16 @@ function getCertExceptionDialog(aLocation) {
function setupRemoteClientsFixture(fixture) {
let oldRemoteClientsGetter =
Object.getOwnPropertyDescriptor(gFxAccounts, "remoteClients").get;
Object.getOwnPropertyDescriptor(gSync, "remoteClients").get;
Object.defineProperty(gFxAccounts, "remoteClients", {
Object.defineProperty(gSync, "remoteClients", {
get() { return fixture; }
});
return oldRemoteClientsGetter;
}
function restoreRemoteClients(getter) {
Object.defineProperty(gFxAccounts, "remoteClients", {
Object.defineProperty(gSync, "remoteClients", {
get: getter
});
}

Просмотреть файл

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"plugin:mozilla/browser-test"
]
};

Просмотреть файл

@ -0,0 +1,9 @@
[browser_sync.js]
[browser_fxa_web_channel.js]
support-files=
browser_fxa_web_channel.html
[browser_aboutAccounts.js]
skip-if = os == "linux" # Bug 958026
support-files =
content_aboutAccounts.js
accounts_testRemoteCommands.html

Просмотреть файл

@ -17,7 +17,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
const CHROME_BASE = "chrome://mochitests/content/browser/browser/base/content/test/general/";
const CHROME_BASE = "chrome://mochitests/content/browser/browser/base/content/test/sync/";
// Preference helpers.
var changedPrefs = new Set();
@ -42,7 +42,7 @@ var gTests = [
},
*run() {
setPref("identity.fxaccounts.remote.signup.uri",
"https://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
"https://example.com/browser/browser/base/content/test/sync/accounts_testRemoteCommands.html");
let tab = yield promiseNewTabLoadEvent("about:accounts");
let mm = tab.linkedBrowser.messageManager;

Просмотреть файл

@ -17,7 +17,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
var {FxAccountsWebChannel} = Components.utils.import("resource://gre/modules/FxAccountsWebChannel.jsm", {});
const TEST_HTTP_PATH = "http://example.com";
const TEST_BASE_URL = TEST_HTTP_PATH + "/browser/browser/base/content/test/general/browser_fxa_web_channel.html";
const TEST_BASE_URL = TEST_HTTP_PATH + "/browser/browser/base/content/test/sync/browser_fxa_web_channel.html";
const TEST_CHANNEL_ID = "account_updates_test";
var gTests = [

Просмотреть файл

@ -0,0 +1,241 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function test_ui_state_notification_calls_updateAllUI() {
let called = false;
let updateAllUI = gSync.updateAllUI;
gSync.updateAllUI = () => { called = true; };
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
ok(called);
gSync.updateAllUI = updateAllUI;
});
add_task(async function test_ui_state_signedin() {
let state = {
status: UIState.STATUS_SIGNED_IN,
email: "foo@bar.com",
displayName: "Foo Bar",
avatarURL: "https://foo.bar",
lastSync: new Date(),
syncing: false
};
gSync.updateAllUI(state);
checkFxABadge(false);
let statusBarTooltip = gSync.panelUIStatus.getAttribute("signedinTooltiptext");
let lastSyncTooltip = gSync.formatLastSyncDate(new Date(state.lastSync));
checkPanelUIStatusBar({
label: "Foo Bar",
tooltip: statusBarTooltip,
fxastatus: "signedin",
avatarURL: "https://foo.bar",
syncing: false,
syncNowTooltip: lastSyncTooltip
});
checkRemoteTabsPanel("PanelUI-remotetabs-main", false);
checkMenuBarItem("sync-syncnowitem");
});
add_task(async function test_ui_state_syncing() {
let state = {
status: UIState.STATUS_SIGNED_IN,
email: "foo@bar.com",
displayName: "Foo Bar",
avatarURL: "https://foo.bar",
lastSync: new Date(),
syncing: true
};
gSync.updateAllUI(state);
checkSyncNowButton("PanelUI-fxa-icon", true);
checkSyncNowButton("PanelUI-remotetabs-syncnow", true);
// Be good citizens and remove the "syncing" state.
gSync.updateAllUI({
status: UIState.STATUS_SIGNED_IN,
email: "foo@bar.com",
lastSync: new Date(),
syncing: false
});
// Because we switch from syncing to non-syncing, and there's a timeout involved.
await promiseObserver("test:browser-sync:activity-stop");
});
add_task(async function test_ui_state_unconfigured() {
let state = {
status: UIState.STATUS_NOT_CONFIGURED
};
gSync.updateAllUI(state);
checkFxABadge(false);
let signedOffLabel = gSync.panelUIStatus.getAttribute("defaultlabel");
let statusBarTooltip = gSync.panelUIStatus.getAttribute("signedinTooltiptext");
checkPanelUIStatusBar({
label: signedOffLabel,
tooltip: statusBarTooltip
});
checkRemoteTabsPanel("PanelUI-remotetabs-setupsync");
checkMenuBarItem("sync-setup");
});
add_task(async function test_ui_state_unverified() {
let state = {
status: UIState.STATUS_NOT_VERIFIED,
email: "foo@bar.com",
lastSync: new Date(),
syncing: false
};
gSync.updateAllUI(state);
checkFxABadge(true);
let expectedLabel = gSync.panelUIStatus.getAttribute("unverifiedlabel");
let tooltipText = gSync.fxaStrings.formatStringFromName("verifyDescription", [state.email], 1);
checkPanelUIStatusBar({
label: expectedLabel,
tooltip: tooltipText,
fxastatus: "unverified",
avatarURL: null,
syncing: false,
syncNowTooltip: tooltipText
});
checkRemoteTabsPanel("PanelUI-remotetabs-setupsync", false);
checkMenuBarItem("sync-setup");
});
add_task(async function test_ui_state_loginFailed() {
let state = {
status: UIState.STATUS_LOGIN_FAILED,
email: "foo@bar.com"
};
gSync.updateAllUI(state);
checkFxABadge(true);
let expectedLabel = gSync.panelUIStatus.getAttribute("errorlabel");
let tooltipText = gSync.fxaStrings.formatStringFromName("reconnectDescription", [state.email], 1);
checkPanelUIStatusBar({
label: expectedLabel,
tooltip: tooltipText,
fxastatus: "login-failed",
avatarURL: null,
syncing: false,
syncNowTooltip: tooltipText
});
checkRemoteTabsPanel("PanelUI-remotetabs-reauthsync", false);
checkMenuBarItem("sync-reauthitem");
});
add_task(async function test_FormatLastSyncDateNow() {
let now = new Date();
let nowString = gSync.formatLastSyncDate(now);
is(nowString, "Last sync: " + now.toLocaleDateString(undefined, {weekday: "long", hour: "numeric", minute: "numeric"}),
"The date is correctly formatted");
});
add_task(async function test_FormatLastSyncDateMonthAgo() {
let monthAgo = new Date();
monthAgo.setMonth(monthAgo.getMonth() - 1);
let monthAgoString = gSync.formatLastSyncDate(monthAgo);
is(monthAgoString, "Last sync: " + monthAgo.toLocaleDateString(undefined, {month: "long", day: "numeric"}),
"The date is correctly formatted");
});
function checkFxABadge(shouldBeShown) {
let isShown = false;
for (let notification of PanelUI.notifications) {
if (notification.id == "fxa-needs-authentication") {
isShown = true;
break;
}
}
is(isShown, shouldBeShown, "the fxa badge has the right visibility");
}
function checkPanelUIStatusBar({label, tooltip, fxastatus, avatarURL, syncing, syncNowTooltip}) {
let labelNode = document.getElementById("PanelUI-fxa-label");
let tooltipNode = document.getElementById("PanelUI-fxa-status");
let statusNode = document.getElementById("PanelUI-footer-fxa");
let avatar = document.getElementById("PanelUI-fxa-avatar");
is(labelNode.getAttribute("label"), label, "panelUI-fxa label has the right value");
is(tooltipNode.getAttribute("tooltiptext"), tooltip, "panelUI-fxa tooltip has the right value");
if (fxastatus) {
is(statusNode.getAttribute("fxastatus"), fxastatus, "panelUI-fxa fxastatus has the right value");
} else {
ok(!statusNode.hasAttribute("fxastatus"), "panelUI-fxa fxastatus is unset")
}
if (avatarURL) {
is(avatar.style.listStyleImage, `url("${avatarURL}")`, "panelUI-fxa avatar URL is set");
} else {
ok(!statusNode.style.listStyleImage, "panelUI-fxa avatar URL is unset");
}
if (syncing != undefined && syncNowTooltip != undefined) {
checkSyncNowButton("PanelUI-fxa-icon", syncing, syncNowTooltip);
}
}
function checkRemoteTabsPanel(expectedShownItemId, syncing, syncNowTooltip) {
checkItemsVisiblities(["PanelUI-remotetabs-main",
"PanelUI-remotetabs-setupsync",
"PanelUI-remotetabs-reauthsync"],
expectedShownItemId);
if (syncing != undefined && syncNowTooltip != undefined) {
checkSyncNowButton("PanelUI-remotetabs-syncnow", syncing, syncNowTooltip);
}
}
function checkMenuBarItem(expectedShownItemId) {
checkItemsVisiblities(["sync-setup", "sync-syncnowitem", "sync-reauthitem"],
expectedShownItemId);
}
function checkSyncNowButton(buttonId, syncing, tooltip = null) {
const remoteTabsButton = document.getElementById(buttonId);
is(remoteTabsButton.getAttribute("syncstatus"), syncing ? "active" : "", "button active has the right value");
if (tooltip) {
is(remoteTabsButton.getAttribute("tooltiptext"), tooltip, "button tooltiptext is set to the right value");
}
if (buttonId == "PanelUI-fxa-icon") {
return;
}
is(remoteTabsButton.hasAttribute("disabled"), syncing, "disabled has the right value");
if (syncing) {
is(remoteTabsButton.getAttribute("label"), gSync.syncStrings.GetStringFromName("syncing2.label"), "label is set to the right value");
} else {
is(remoteTabsButton.getAttribute("label"), gSync.syncStrings.GetStringFromName("syncnow.label"), "label is set to the right value");
}
}
// Only one item visible at a time.
function checkItemsVisiblities(itemsIds, expectedShownItemId) {
for (let id of itemsIds) {
if (id == expectedShownItemId) {
ok(!document.getElementById(id).hidden, "menuitem " + id + " should be visible");
} else {
ok(document.getElementById(id).hidden, "menuitem " + id + " should be hidden");
}
}
}
function promiseObserver(topic) {
return new Promise(resolve => {
let obs = (aSubject, aTopic, aData) => {
Services.obs.removeObserver(obs, aTopic);
resolve(aSubject);
}
Services.obs.addObserver(obs, topic);
});
}

Просмотреть файл

@ -24,7 +24,7 @@
<script type="application/javascript" src="chrome://browser/content/browser.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-places.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-social.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-fxaccounts.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-sync.js"/>
<script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
<script type="application/javascript" src="chrome://browser/content/web-panels.js"/>

Просмотреть файл

@ -24,7 +24,7 @@
<script type="application/javascript" src="chrome://browser/content/browser.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-places.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-social.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-fxaccounts.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-sync.js"/>
<script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
<script type="application/javascript" src="chrome://browser/content/webext-panels.js"/>

Просмотреть файл

@ -73,7 +73,6 @@ browser.jar:
content/browser/browser-feeds.js (content/browser-feeds.js)
content/browser/browser-fullScreenAndPointerLock.js (content/browser-fullScreenAndPointerLock.js)
content/browser/browser-fullZoom.js (content/browser-fullZoom.js)
content/browser/browser-fxaccounts.js (content/browser-fxaccounts.js)
content/browser/browser-gestureSupport.js (content/browser-gestureSupport.js)
content/browser/browser-media.js (content/browser-media.js)
content/browser/browser-places.js (content/browser-places.js)
@ -82,7 +81,7 @@ browser.jar:
content/browser/browser-safebrowsing.js (content/browser-safebrowsing.js)
content/browser/browser-sidebar.js (content/browser-sidebar.js)
content/browser/browser-social.js (content/browser-social.js)
content/browser/browser-syncui.js (content/browser-syncui.js)
content/browser/browser-sync.js (content/browser-sync.js)
* content/browser/browser-tabPreviews.xml (content/browser-tabPreviews.xml)
#ifdef CAN_DRAW_IN_TITLEBAR
content/browser/browser-tabsintitlebar.js (content/browser-tabsintitlebar.js)

Просмотреть файл

@ -29,6 +29,7 @@ BROWSER_CHROME_MANIFESTS += [
'content/test/siteIdentity/browser.ini',
'content/test/social/browser.ini',
'content/test/static/browser.ini',
'content/test/sync/browser.ini',
'content/test/tabcrashed/browser.ini',
'content/test/tabPrompts/browser.ini',
'content/test/tabs/browser.ini',

Просмотреть файл

@ -472,7 +472,7 @@ const CustomizableWidgets = [
clientItem.setAttribute("itemtype", "client");
let window = doc.defaultView;
clientItem.setAttribute("tooltiptext",
window.gSyncUI.formatLastSyncDate(new Date(client.lastModified)));
window.gSync.formatLastSyncDate(new Date(client.lastModified)));
clientItem.textContent = client.name;
attachFragment.appendChild(clientItem);

Просмотреть файл

@ -31,19 +31,21 @@
hidden="true"/>
<hbox id="PanelUI-footer-fxa">
<hbox id="PanelUI-fxa-status"
label="&fxaSignedIn.tooltip;"
defaultlabel="&fxaSignIn.label;"
signedinTooltiptext="&fxaSignedIn.tooltip;"
tooltiptext="&fxaSignedIn.tooltip;"
errorlabel="&fxaSignInError.label;"
unverifiedlabel="&fxaUnverified.label;"
onclick="if (event.which == 1) gFxAccounts.onMenuPanelCommand();">
onclick="if (event.which == 1) gSync.onMenuPanelCommand();">
<image id="PanelUI-fxa-avatar"/>
<toolbarbutton id="PanelUI-fxa-label"
label="&fxaSignIn.label;"
fxabrandname="&syncBrand.fxAccount.label;"/>
</hbox>
<toolbarseparator/>
<toolbarbutton id="PanelUI-fxa-icon"
oncommand="gSyncUI.doSync();"
oncommand="gSync.doSync();"
closemenu="none">
<observes element="sync-status" attribute="syncstatus"/>
<observes element="sync-status" attribute="tooltiptext"/>
@ -125,11 +127,11 @@
<toolbarbutton id="PanelUI-remotetabs-view-managedevices"
class="subviewbutton"
label="&appMenuRemoteTabs.managedevices.label;"
oncommand="gFxAccounts.openDevicesManagementPage('syncedtabs-menupanel');"/>
oncommand="gSync.openDevicesManagementPage('syncedtabs-menupanel');"/>
<toolbarbutton id="PanelUI-remotetabs-syncnow"
observes="sync-status"
class="subviewbutton"
oncommand="gSyncUI.doSync();"
oncommand="gSync.doSync();"
closemenu="none"/>
<menuseparator id="PanelUI-remotetabs-separator"/>
</vbox>
@ -154,7 +156,7 @@
<hbox pack="center">
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
label="&appMenuRemoteTabs.openprefs.label;"
oncommand="gSyncUI.openPrefs('synced-tabs');"/>
oncommand="gSync.openPrefs('synced-tabs');"/>
</hbox>
</vbox>
</hbox>
@ -188,7 +190,7 @@
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
label="&appMenuRemoteTabs.signin.label;"
oncommand="gSyncUI.openPrefs('synced-tabs');"/>
oncommand="gSync.openPrefs('synced-tabs');"/>
</vbox>
<!-- When Sync needs re-authentication. This uses the exact same messaging
as "Sync is not configured" but remains a separate box so we get
@ -202,7 +204,7 @@
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
label="&appMenuRemoteTabs.signin.label;"
oncommand="gSyncUI.openPrefs('synced-tabs');"/>
oncommand="gSync.openPrefs('synced-tabs');"/>
</vbox>
</hbox>
</vbox>

Просмотреть файл

@ -122,7 +122,6 @@ skip-if = os == "linux"
[browser_985815_propagate_setToolbarVisibility.js]
[browser_987177_destroyWidget_xul.js]
[browser_987177_xul_wrapper_updating.js]
[browser_987185_syncButton.js]
[browser_987492_window_api.js]
[browser_987640_charEncoding.js]
[browser_988072_sidebar_events.js]
@ -157,3 +156,4 @@ skip-if = os == "mac"
[browser_check_tooltips_in_navbar.js]
[browser_editcontrols_update.js]
subsuite = clipboard
[browser_remote_tabs_button.js]

Просмотреть файл

@ -1,77 +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 syncService = {};
Components.utils.import("resource://services-sync/service.js", syncService);
var needsSetup;
var originalSync;
var service = syncService.Service;
var syncWasCalled = false;
add_task(function* testSyncButtonFunctionality() {
info("Check Sync button functionality");
storeInitialValues();
mockFunctions();
// add the Sync button to the panel
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
// check the button's functionality
yield PanelUI.show();
info("The panel menu was opened");
let syncButton = document.getElementById("sync-button");
ok(syncButton, "The Sync button was added to the Panel Menu");
// click the button - the panel should open.
syncButton.click();
let syncPanel = document.getElementById("PanelUI-remotetabs");
ok(syncPanel.getAttribute("current"), "Sync Panel is in view");
// Find and click the "setup" button.
let syncNowButton = document.getElementById("PanelUI-remotetabs-syncnow");
syncNowButton.click();
info("The sync button was clicked");
yield waitForCondition(() => syncWasCalled);
});
add_task(function* asyncCleanup() {
// reset the panel UI to the default state
yield resetCustomization();
ok(CustomizableUI.inDefaultState, "The panel UI is in default state again.");
if (isPanelUIOpen()) {
let panelHidePromise = promisePanelHidden(window);
PanelUI.hide();
yield panelHidePromise;
}
restoreValues();
});
function mockFunctions() {
// mock needsSetup
gSyncUI._needsSetup = () => Promise.resolve(false);
// mock service.errorHandler.syncAndReportErrors()
service.errorHandler.syncAndReportErrors = mocked_syncAndReportErrors;
}
function mocked_syncAndReportErrors() {
syncWasCalled = true;
}
function restoreValues() {
gSyncUI._needsSetup = needsSetup;
service.sync = originalSync;
}
function storeInitialValues() {
needsSetup = gSyncUI._needsSetup;
originalSync = service.sync;
}

Просмотреть файл

@ -0,0 +1,84 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
"use strict";
let syncService = {};
Components.utils.import("resource://services-sync/service.js", syncService);
const service = syncService.Service;
Components.utils.import("resource://services-sync/UIState.jsm");
let getState;
let originalSync;
let syncWasCalled = false;
// TODO: This test should probably be re-written, we don't really test much here.
add_task(async function testSyncRemoteTabsButtonFunctionality() {
info("Test the Sync Remote Tabs button in the PanelUI");
storeInitialValues();
mockFunctions();
// Force UI update.
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
// add the sync remote tabs button to the panel
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
// check the button's functionality
await PanelUI.show();
info("The panel menu was opened");
let syncRemoteTabsBtn = document.getElementById("sync-button");
ok(syncRemoteTabsBtn, "The sync remote tabs button was added to the Panel Menu");
// click the button - the panel should open.
syncRemoteTabsBtn.click();
let remoteTabsPanel = document.getElementById("PanelUI-remotetabs");
ok(remoteTabsPanel.getAttribute("current"), "Sync Panel is in view");
// Find and click the "setup" button.
let syncNowButton = document.getElementById("PanelUI-remotetabs-syncnow");
syncNowButton.click();
info("The sync now button was clicked");
await waitForCondition(() => syncWasCalled);
});
add_task(async function asyncCleanup() {
// reset the panel UI to the default state
await resetCustomization();
ok(CustomizableUI.inDefaultState, "The panel UI is in default state again.");
if (isPanelUIOpen()) {
let panelHidePromise = promisePanelHidden(window);
PanelUI.hide();
await panelHidePromise;
}
restoreValues();
});
function mockFunctions() {
// mock UIState.get()
UIState.get = () => ({
status: UIState.STATUS_SIGNED_IN,
email: "user@mozilla.com"
});
// mock service.errorHandler.syncAndReportErrors()
service.errorHandler.syncAndReportErrors = mocked_syncAndReportErrors;
}
function mocked_syncAndReportErrors() {
syncWasCalled = true;
}
function restoreValues() {
UIState.get = getState;
service.syncAndReportErrors = originalSync;
}
function storeInitialValues() {
getState = UIState.get;
originalSync = service.syncAndReportErrors;
}

Просмотреть файл

@ -229,11 +229,11 @@ add_task(function* () {
let syncNowButton = document.getElementById("PanelUI-remotetabs-syncnow");
let didSync = false;
let oldDoSync = gSyncUI.doSync;
gSyncUI.doSync = function() {
let oldDoSync = gSync.doSync;
gSync.doSync = function() {
didSync = true;
mockedInternal.hasSyncedThisSession = true;
gSyncUI.doSync = oldDoSync;
gSync.doSync = oldDoSync;
}
syncNowButton.click();
ok(didSync, "clicking the button called the correct function");

Просмотреть файл

@ -129,8 +129,6 @@ var gSyncPane = {
});
this.updateWeavePrefs();
this._initProfileImageUI();
},
_toggleComputerNameControls(editMode) {
@ -225,14 +223,6 @@ var gSyncPane = {
});
},
_initProfileImageUI() {
try {
if (Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled")) {
document.getElementById("fxaProfileImage").hidden = false;
}
} catch (e) { }
},
updateWeavePrefs() {
let service = Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
@ -243,8 +233,6 @@ var gSyncPane = {
fxaEmailAddress1Label.hidden = false;
displayNameLabel.hidden = true;
let profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled", false);
// determine the fxa status...
this._showLoadPage(service);
@ -300,7 +288,7 @@ var gSyncPane = {
return null;
}).then(data => {
let fxaLoginStatus = document.getElementById("fxaLoginStatus");
if (data && profileInfoEnabled) {
if (data) {
if (data.displayName) {
fxaLoginStatus.setAttribute("hasName", true);
displayNameLabel.hidden = false;

Просмотреть файл

@ -88,7 +88,7 @@
<vbox align="center" pack="center">
<image id="fxaProfileImage" class="actionable"
role="button"
onclick="gSyncPane.openChangeProfileImage(event);" hidden="true"
onclick="gSyncPane.openChangeProfileImage(event);"
onkeypress="gSyncPane.openChangeProfileImage(event);"
tooltiptext="&profilePicture.tooltip;"/>
</vbox>

Просмотреть файл

@ -129,8 +129,6 @@ var gSyncPane = {
});
this.updateWeavePrefs();
this._initProfileImageUI();
},
_toggleComputerNameControls(editMode) {
@ -225,14 +223,6 @@ var gSyncPane = {
});
},
_initProfileImageUI() {
try {
if (Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled")) {
document.getElementById("fxaProfileImage").hidden = false;
}
} catch (e) { }
},
updateWeavePrefs() {
let service = Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
@ -243,8 +233,6 @@ var gSyncPane = {
fxaEmailAddress1Label.hidden = false;
displayNameLabel.hidden = true;
let profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled", false);
// determine the fxa status...
this._showLoadPage(service);
@ -300,7 +288,7 @@ var gSyncPane = {
return null;
}).then(data => {
let fxaLoginStatus = document.getElementById("fxaLoginStatus");
if (data && profileInfoEnabled) {
if (data) {
if (data.displayName) {
fxaLoginStatus.setAttribute("hasName", true);
displayNameLabel.hidden = false;

Просмотреть файл

@ -87,7 +87,7 @@
<vbox align="center" pack="center">
<image id="fxaProfileImage" class="actionable"
role="button"
onclick="gSyncPane.openChangeProfileImage(event);" hidden="true"
onclick="gSyncPane.openChangeProfileImage(event);"
onkeypress="gSyncPane.openChangeProfileImage(event);"
tooltiptext="&profilePicture.tooltip;"/>
</vbox>

Просмотреть файл

@ -122,7 +122,7 @@ SyncedTabsDeckComponent.prototype = {
getPanelStatus() {
return this._accountStatus().then(exists => {
if (!exists || this._getChromeWindow(this._window).gSyncUI.loginFailed()) {
if (!exists || this._SyncedTabs.loginFailed) {
return this.PANELS.NOT_AUTHED_INFO;
}
if (!this._SyncedTabs.isConfiguredToSyncTabs) {
@ -166,7 +166,7 @@ SyncedTabsDeckComponent.prototype = {
},
openSyncPrefs() {
this._getChromeWindow(this._window).gSyncUI.openPrefs("tabs-sidebar");
this._getChromeWindow(this._window).gSync.openPrefs("tabs-sidebar");
}
};

Просмотреть файл

@ -213,7 +213,7 @@ TabListView.prototype = {
_updateClient(item, itemNode) {
itemNode.setAttribute("id", "item-" + item.id);
let lastSync = new Date(item.lastModified);
let lastSyncTitle = getChromeWindow(this._window).gSyncUI.formatLastSyncDate(lastSync);
let lastSyncTitle = getChromeWindow(this._window).gSync.formatLastSyncDate(lastSync);
itemNode.setAttribute("title", lastSyncTitle);
if (item.closed) {
itemNode.classList.add("closed");

Просмотреть файл

@ -140,16 +140,6 @@ add_task(function* testPanelStatus() {
let SyncedTabsMock = {
getTabClients() {}
};
let loginFailed = false;
let chromeWindowMock = {
gSyncUI: {
loginFailed() {
return loginFailed;
}
}
};
let getChromeWindowMock = sinon.stub();
getChromeWindowMock.returns(chromeWindowMock);
sinon.stub(listStore, "getData");
@ -158,8 +148,7 @@ add_task(function* testPanelStatus() {
fxAccounts,
deckStore,
listComponent,
SyncedTabs: SyncedTabsMock,
getChromeWindowMock
SyncedTabs: SyncedTabsMock
});
let isAuthed = false;
@ -169,10 +158,10 @@ add_task(function* testPanelStatus() {
isAuthed = true;
loginFailed = true;
SyncedTabsMock.loginFailed = true;
result = yield component.getPanelStatus();
Assert.equal(result, component.PANELS.NOT_AUTHED_INFO);
loginFailed = false;
SyncedTabsMock.loginFailed = false;
SyncedTabsMock.isConfiguredToSyncTabs = false;
result = yield component.getPanelStatus();
@ -211,12 +200,12 @@ add_task(function* testActions() {
openUILink() {},
};
let chromeWindowMock = {
gSyncUI: {
gSync: {
openPrefs() {}
}
};
sinon.spy(windowMock, "openUILink");
sinon.spy(chromeWindowMock.gSyncUI, "openPrefs");
sinon.spy(chromeWindowMock.gSync, "openPrefs");
let getChromeWindowMock = sinon.stub();
getChromeWindowMock.returns(chromeWindowMock);
@ -236,5 +225,5 @@ add_task(function* testActions() {
component.openSyncPrefs();
Assert.ok(getChromeWindowMock.calledWith(windowMock));
Assert.ok(chromeWindowMock.gSyncUI.openPrefs.called);
Assert.ok(chromeWindowMock.gSync.openPrefs.called);
});

Просмотреть файл

@ -18,7 +18,7 @@ function test() {
registerCleanupFunction(function*() {
yield signOut();
gFxAccounts.updateUI();
gSync.updateAllUI(UIState.get());
});
var tests = [
@ -35,7 +35,7 @@ var tests = [
yield setSignedInUser();
let userData = yield fxAccounts.getSignedInUser();
isnot(userData, null, "Logged in now");
gFxAccounts.updateUI(); // Causes a leak (see bug 1332985)
gSync.updateAllUI(UIState.get());
yield showMenuPromise("appMenu");
yield showHighlightPromise("accountStatus");
let highlight = document.getElementById("UITourHighlightContainer");

Просмотреть файл

@ -571,12 +571,12 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
}
#PanelUI-footer-fxa:not([fxastatus="signedin"]) > toolbarseparator,
#PanelUI-footer-fxa:not([fxastatus="signedin"]) > #PanelUI-fxa-icon,
#PanelUI-footer-fxa:not([fxaprofileimage]) > #PanelUI-fxa-status > #PanelUI-fxa-avatar {
#PanelUI-footer-fxa:not([fxastatus="signedin"]) > #PanelUI-fxa-icon {
display: none;
}
#PanelUI-footer-fxa[fxastatus="error"] > #PanelUI-fxa-status::after {
#PanelUI-footer-fxa[fxastatus="login-failed"] > #PanelUI-fxa-status::after,
#PanelUI-footer-fxa[fxastatus="unverified"] > #PanelUI-fxa-status::after {
content: url(chrome://browser/skin/warning.svg);
filter: drop-shadow(0 1px 0 hsla(206,50%,10%,.15));
width: 47px;
@ -725,8 +725,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
border-inline-start-style: none;
}
#PanelUI-footer-fxa[fxaprofileimage="set"] > #PanelUI-fxa-status > #PanelUI-fxa-label,
#PanelUI-footer-fxa[fxaprofileimage="enabled"]:not([fxastatus="error"]) > #PanelUI-fxa-status > #PanelUI-fxa-label {
#PanelUI-footer-fxa[fxastatus="signedin"] > #PanelUI-fxa-status > #PanelUI-fxa-label {
padding-inline-start: 0px;
}
@ -856,7 +855,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
display: none;
}
#PanelUI-fxa-icon[syncstatus="active"]:not([disabled]) {
#PanelUI-fxa-icon[syncstatus="active"] {
list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar.png);
}
@ -887,19 +886,10 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
}
#PanelUI-footer-fxa[fxastatus="signedin"] > #PanelUI-fxa-status > #PanelUI-fxa-label > .toolbarbutton-icon,
#PanelUI-footer-fxa[fxastatus="error"][fxaprofileimage="set"] > #PanelUI-fxa-status > #PanelUI-fxa-label > .toolbarbutton-icon {
#PanelUI-footer-fxa:not([fxastatus="signedin"]) > #PanelUI-fxa-status > #PanelUI-fxa-avatar {
display: none;
}
#PanelUI-footer-fxa[fxastatus="error"]:not([fxaprofileimage="set"]) > #PanelUI-fxa-status > #PanelUI-fxa-avatar {
display: none;
}
#PanelUI-fxa-status[disabled],
#PanelUI-fxa-icon[disabled] {
pointer-events: none;
}
#PanelUI-fxa-avatar {
width: 32px;
height: 32px;
@ -914,7 +904,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
margin-inline-end: 0;
}
#PanelUI-footer-fxa[fxaprofileimage="enabled"] > #PanelUI-fxa-status > #PanelUI-fxa-avatar {
#PanelUI-footer-fxa > #PanelUI-fxa-status > #PanelUI-fxa-avatar {
list-style-image: url(chrome://browser/skin/fxa/default-avatar.svg);
}
@ -935,16 +925,12 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
}
#PanelUI-help[disabled],
#PanelUI-quit[disabled],
#PanelUI-fxa-icon[disabled],
#PanelUI-fxa-avatar[disabled],
#PanelUI-fxa-label[disabled] > .toolbarbutton-icon,
#PanelUI-fxa-status::after {
#PanelUI-quit[disabled] {
opacity: 0.4;
}
#PanelUI-fxa-status:not([disabled]):hover,
#PanelUI-fxa-icon:not([disabled]):hover,
#PanelUI-fxa-status:hover,
#PanelUI-fxa-icon:hover,
#PanelUI-help:not([disabled]):hover,
#PanelUI-customize:hover,
#PanelUI-quit:not([disabled]):hover {
@ -952,8 +938,8 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
background-color: var(--arrowpanel-dimmed);
}
#PanelUI-fxa-status:not([disabled]):hover:active,
#PanelUI-fxa-icon:not([disabled]):hover:active,
#PanelUI-fxa-status:hover:active,
#PanelUI-fxa-icon:hover:active,
#PanelUI-help:not([disabled]):hover:active,
#PanelUI-customize:hover:active,
#PanelUI-quit:not([disabled]):hover:active {
@ -962,23 +948,26 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
}
#PanelUI-fxa-status:not([disabled]):hover,
#PanelUI-fxa-status:not([disabled]):hover:active,
#PanelUI-fxa-icon:not([disabled]):hover,
#PanelUI-fxa-icon:not([disabled]):hover:active {
#PanelUI-fxa-status:hover,
#PanelUI-fxa-status:hover:active,
#PanelUI-fxa-icon:hover,
#PanelUI-fxa-icon:hover:active {
outline: none;
}
#PanelUI-footer-fxa[fxastatus="error"] {
#PanelUI-footer-fxa[fxastatus="login-failed"],
#PanelUI-footer-fxa[fxastatus="unverified"] {
background-color: hsl(42,94%,88%);
border-top: 1px solid hsl(42,94%,70%);
}
#PanelUI-footer-fxa[fxastatus="error"] > #PanelUI-fxa-status:hover {
#PanelUI-footer-fxa[fxastatus="login-failed"] > #PanelUI-fxa-status:hover,
#PanelUI-footer-fxa[fxastatus="unverified"] > #PanelUI-fxa-status:hover {
background-color: hsl(42,94%,85%);
}
#PanelUI-footer-fxa[fxastatus="error"] > #PanelUI-fxa-status:hover:active {
#PanelUI-footer-fxa[fxastatus="login-failed"] > #PanelUI-fxa-status:hover:active,
#PanelUI-footer-fxa[fxastatus="unverified"] > #PanelUI-fxa-status:hover:active {
background-color: hsl(42,94%,82%);
box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
}
@ -1719,7 +1708,7 @@ menuitem[checked="true"].subviewbutton > .menu-iconic-left {
list-style-image: url(chrome://browser/skin/sync-horizontalbar@2x.png);
}
#PanelUI-fxa-icon[syncstatus="active"]:not([disabled]) {
#PanelUI-fxa-icon[syncstatus="active"] {
list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar@2x.png);
}

Просмотреть файл

@ -96,7 +96,9 @@ void
VRDisplayPresentation::DestroyLayers()
{
for (VRLayerChild* layer : mLayers) {
Unused << layer->SendDestroy();
if (layer->IsIPCOpen()) {
Unused << layer->SendDestroy();
}
}
mLayers.Clear();
}

Просмотреть файл

@ -20,6 +20,7 @@ VRLayerChild::VRLayerChild(uint32_t aVRDisplayID, VRManagerChild* aVRManagerChil
, mCanvasElement(nullptr)
, mShSurfClient(nullptr)
, mFront(nullptr)
, mIPCOpen(true)
{
}
@ -74,6 +75,12 @@ VRLayerChild::SubmitFrame()
SendSubmitFrame(mFront->GetIPDLActor());
}
bool
VRLayerChild::IsIPCOpen()
{
return mIPCOpen;
}
void
VRLayerChild::ClearSurfaces()
{
@ -81,5 +88,11 @@ VRLayerChild::ClearSurfaces()
mShSurfClient = nullptr;
}
void
VRLayerChild::ActorDestroy(ActorDestroyReason aWhy)
{
mIPCOpen = false;
}
} // namespace gfx
} // namespace mozilla

Просмотреть файл

@ -35,16 +35,19 @@ public:
VRLayerChild(uint32_t aVRDisplayID, VRManagerChild* aVRManagerChild);
void Initialize(dom::HTMLCanvasElement* aCanvasElement);
void SubmitFrame();
bool IsIPCOpen();
protected:
virtual ~VRLayerChild();
void ClearSurfaces();
virtual void ActorDestroy(ActorDestroyReason aWhy) override;
uint32_t mVRDisplayID;
RefPtr<dom::HTMLCanvasElement> mCanvasElement;
RefPtr<layers::SharedSurfaceTextureClient> mShSurfClient;
RefPtr<layers::TextureClient> mFront;
bool mIPCOpen;
};
} // namespace gfx

Просмотреть файл

@ -85,7 +85,7 @@ fails-if(stylo) HTTP(..) != kerning-sanity-check-nokern.html kerning-sanity-chec
HTTP(..) == font-features-across-space-1.html font-features-across-space-1-ref.html
fails-if(stylo) HTTP(..) == spacelookups.html spacelookups-ref.html
# tests whether word cache is in use by testing for ignored space kerns
fails-if(stylo) HTTP(..) == spacelookups-wordcache.html spacelookups-wordcache-ref.html
HTTP(..) == spacelookups-wordcache.html spacelookups-wordcache-ref.html
# requires Japanese font with feature support, WinXP lacks one
random-if(!winWidget&&!cocoaWidget) HTTP(..) == fwid-spaces.html fwid-spaces-ref.html
# Arial/Times New Roman on Win7+/OSX 10.6+ have kerning pairs that include spaces

Просмотреть файл

@ -43,7 +43,7 @@ skip css-values-3/vh_not_refreshing_on_chrome_iframe.html
skip css-values-3/vh-support-transform-origin.html
skip css-values-3/vh-support-transform-translate.html
fails-if(stylo) css-values-3/calc-in-calc.html
css-values-3/calc-in-calc.html
fails-if(stylo) css-values-3/vh-support-atviewport.html
#### CSS Writing Modes 3 #############################################

Просмотреть файл

@ -218,7 +218,7 @@ fails-if(!stylo) == css-values-3/attr-length-valid.html css-values-3/reference/2
fails-if(!stylo) == css-values-3/attr-px-invalid-cast.html css-values-3/reference/200-200-green.html
== css-values-3/attr-px-invalid-fallback.html css-values-3/reference/200-200-green.html
fails-if(!stylo) == css-values-3/attr-px-valid.html css-values-3/reference/200-200-green.html
fails-if(stylo) == css-values-3/calc-in-calc.html css-values-3/reference/all-green.html
== css-values-3/calc-in-calc.html css-values-3/reference/all-green.html
fails == css-values-3/calc-in-media-queries-001.html css-values-3/reference/all-green.html
fails == css-values-3/calc-in-media-queries-002.html css-values-3/reference/all-green.html
== css-values-3/calc-invalid-range-clamping.html css-values-3/reference/200-200-green.html

Просмотреть файл

@ -117,6 +117,8 @@ to mochitest command.
* test_initial_storage.html `grid` [*]
* test_property_syntax_errors.html `grid`: actually there are issues with this [*]
* test_value_storage.html `'grid` [*]
* Some issue with font-feature-settings:
* test_inherit_computation.html `value for 'font'` [8]
* Unimplemented prefixed properties:
* -moz-transform: need different parsing rules servo/servo#16003
* test_value_storage.html `-moz-transform`: need different parsing rules bug 1357906 [70]
@ -130,12 +132,9 @@ to mochitest command.
* test_specified_value_serialization.html `bug-721136` [1]
* Properties implemented but not in geckolib:
* font-feature-settings property servo/servo#15975
* test_compute_data_with_start_struct.html `font-feature-settings` [2]
* test_inherit_computation.html `font-feature-settings` [8]
* test_inherit_storage.html `font-feature-settings` [12]
* test_initial_computation.html `font-feature-settings` [4]
* test_initial_storage.html `font-feature-settings` [6]
* test_value_storage.html `font-feature-settings` [112]
* test_inherit_storage.html `font-feature-settings` [2]
* test_initial_storage.html `font-feature-settings` [1]
* test_value_storage.html `font-feature-settings` [118]
* image-orientation property bug 1341758
* test_value_storage.html `image-orientation` [40]
* Stylesheet cloning is somehow busted bug 1348481

Просмотреть файл

@ -321,6 +321,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
}
}
private String mName;
private volatile ICodecCallbacks mCallbacks;
private AsyncCodec mCodec;
private InputProcessor mInputProcessor;
@ -362,16 +363,9 @@ import java.util.concurrent.ConcurrentLinkedQueue;
if (DEBUG) { Log.d(LOGTAG, "configure " + this); }
MediaFormat fmt = format.asFormat();
String codecName = getCodecForFormat(fmt, flags == MediaCodec.CONFIGURE_FLAG_ENCODE ? true : false);
if (codecName == null) {
Log.e(LOGTAG, "FAIL: cannot find codec");
return false;
}
try {
AsyncCodec codec = AsyncCodecFactory.create(codecName);
MediaFormat fmt = format.asFormat();
AsyncCodec codec = createCodecForFormat(fmt, flags == MediaCodec.CONFIGURE_FLAG_ENCODE ? true : false);
MediaCrypto crypto = RemoteMediaDrmBridgeStub.getMediaCrypto(drmStubId);
if (DEBUG) {
boolean hasCrypto = crypto != null;
@ -397,12 +391,11 @@ import java.util.concurrent.ConcurrentLinkedQueue;
mCodec = codec;
mInputProcessor = new InputProcessor();
mOutputProcessor = new OutputProcessor(renderToSurface);
mSamplePool = new SamplePool(codecName, renderToSurface);
mSamplePool = new SamplePool(mName, renderToSurface);
if (DEBUG) { Log.d(LOGTAG, codec.toString() + " created. Render to surface?" + renderToSurface); }
return true;
} catch (Exception e) {
Log.e(LOGTAG, "FAIL: cannot create codec -- " + codecName);
e.printStackTrace();
Log.e(LOGTAG, "codec creation error", e);
return false;
}
}
@ -425,28 +418,34 @@ import java.util.concurrent.ConcurrentLinkedQueue;
mCodec = null;
}
private String getCodecForFormat(MediaFormat format, boolean isEncoder) {
private AsyncCodec createCodecForFormat(MediaFormat format, boolean isEncoder) throws IOException {
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime == null) {
return null;
if (mime == null || mime.isEmpty()) {
throw new IllegalArgumentException("invalid MIME type: " + mime);
}
int numCodecs = MediaCodecList.getCodecCount();
for (int i = 0; i < numCodecs; i++) {
MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
if (info.isEncoder() == !isEncoder) {
continue;
}
String[] types = info.getSupportedTypes();
for (String t : types) {
if (t.equalsIgnoreCase(mime)) {
return info.getName();
if (!t.equalsIgnoreCase(mime)) {
continue;
}
try {
AsyncCodec codec = AsyncCodecFactory.create(info.getName());
mName = info.getName();
return codec;
} catch (IllegalArgumentException e) {
Log.w(LOGTAG, "unable to create " + info.getName() + ". Try next codec.");
}
}
}
return null;
// TODO: API 21+ is simpler.
//static MediaCodecList sCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
//return sCodecList.findDecoderForFormat(format);
throw new RuntimeException("cannot create codec for " + mime);
}
@Override

Просмотреть файл

@ -14,7 +14,6 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
Cu.importGlobalProperties(["fetch"]);
@ -119,56 +118,53 @@ class BlocklistClient {
* in order to leverage the updateJSONBlocklist() below, which writes a new
* dump each time the collection changes.
*/
loadDumpFile() {
async loadDumpFile() {
// Replace OS specific path separator by / for URI.
const { components: folderFile } = OS.Path.split(this.filename);
const fileURI = `resource://app/defaults/${folderFile.join("/")}`;
return Task.spawn(function* loadFile() {
const response = yield fetch(fileURI);
if (!response.ok) {
throw new Error(`Could not read from '${fileURI}'`);
}
// Will be rejected if JSON is invalid.
return yield response.json();
});
const response = await fetch(fileURI);
if (!response.ok) {
throw new Error(`Could not read from '${fileURI}'`);
}
// Will be rejected if JSON is invalid.
return response.json();
}
validateCollectionSignature(remote, payload, collection, options = {}) {
async validateCollectionSignature(remote, payload, collection, options = {}) {
const {ignoreLocal} = options;
return Task.spawn((function* () {
// this is a content-signature field from an autograph response.
const {x5u, signature} = yield fetchCollectionMetadata(remote, collection);
const certChain = yield fetch(x5u).then((res) => res.text());
// this is a content-signature field from an autograph response.
const {x5u, signature} = await fetchCollectionMetadata(remote, collection);
const certChainResponse = await fetch(x5u)
const certChain = await certChainResponse.text();
const verifier = Cc["@mozilla.org/security/contentsignatureverifier;1"]
.createInstance(Ci.nsIContentSignatureVerifier);
const verifier = Cc["@mozilla.org/security/contentsignatureverifier;1"]
.createInstance(Ci.nsIContentSignatureVerifier);
let toSerialize;
if (ignoreLocal) {
toSerialize = {
last_modified: `${payload.last_modified}`,
data: payload.data
};
} else {
const {data: localRecords} = yield collection.list();
const records = mergeChanges(collection, localRecords, payload.changes);
toSerialize = {
last_modified: `${payload.lastModified}`,
data: records
};
}
let toSerialize;
if (ignoreLocal) {
toSerialize = {
last_modified: `${payload.last_modified}`,
data: payload.data
};
} else {
const {data: localRecords} = await collection.list();
const records = mergeChanges(collection, localRecords, payload.changes);
toSerialize = {
last_modified: `${payload.lastModified}`,
data: records
};
}
const serialized = CanonicalJSON.stringify(toSerialize);
const serialized = CanonicalJSON.stringify(toSerialize);
if (verifier.verifyContentSignature(serialized, "p384ecdsa=" + signature,
certChain,
this.signerName)) {
// In case the hash is valid, apply the changes locally.
return payload;
}
throw new Error(INVALID_SIGNATURE);
}).bind(this));
if (verifier.verifyContentSignature(serialized, "p384ecdsa=" + signature,
certChain,
this.signerName)) {
// In case the hash is valid, apply the changes locally.
return payload;
}
throw new Error(INVALID_SIGNATURE);
}
/**
@ -181,7 +177,7 @@ class BlocklistClient {
* @param {bool} options.loadDump load initial dump from disk on first sync (default: true)
* @return {Promise} which rejects on sync or process failure.
*/
maybeSync(lastModified, serverTime, options = {loadDump: true}) {
async maybeSync(lastModified, serverTime, options = {loadDump: true}) {
const {loadDump} = options;
const remote = Services.prefs.getCharPref(PREF_SETTINGS_SERVER);
const enforceCollectionSigning =
@ -198,78 +194,76 @@ class BlocklistClient {
}
}
return Task.spawn((function* syncCollection() {
let sqliteHandle;
try {
// Synchronize remote data into a local Sqlite DB.
sqliteHandle = yield FirefoxAdapter.openConnection({path: KINTO_STORAGE_PATH});
const options = {
hooks,
adapterOptions: {sqliteHandle},
};
const collection = this._kinto.collection(this.collectionName, options);
let sqliteHandle;
try {
// Synchronize remote data into a local Sqlite DB.
sqliteHandle = await FirefoxAdapter.openConnection({path: KINTO_STORAGE_PATH});
const options = {
hooks,
adapterOptions: {sqliteHandle},
};
const collection = this._kinto.collection(this.collectionName, options);
let collectionLastModified = yield collection.db.getLastModified();
let collectionLastModified = await collection.db.getLastModified();
// If there is no data currently in the collection, attempt to import
// initial data from the application defaults.
// This allows to avoid synchronizing the whole collection content on
// cold start.
if (!collectionLastModified && loadDump) {
try {
const initialData = yield this.loadDumpFile();
yield collection.db.loadDump(initialData.data);
collectionLastModified = yield collection.db.getLastModified();
} catch (e) {
// Report but go-on.
Cu.reportError(e);
}
}
// If the data is up to date, there's no need to sync. We still need
// to record the fact that a check happened.
if (lastModified <= collectionLastModified) {
this.updateLastCheck(serverTime);
return;
}
// Fetch changes from server.
// If there is no data currently in the collection, attempt to import
// initial data from the application defaults.
// This allows to avoid synchronizing the whole collection content on
// cold start.
if (!collectionLastModified && loadDump) {
try {
const {ok} = yield collection.sync({remote});
if (!ok) {
throw new Error("Sync failed");
}
const initialData = await this.loadDumpFile();
await collection.db.loadDump(initialData.data);
collectionLastModified = await collection.db.getLastModified();
} catch (e) {
if (e.message == INVALID_SIGNATURE) {
// if sync fails with a signature error, it's likely that our
// local data has been modified in some way.
// We will attempt to fix this by retrieving the whole
// remote collection.
const payload = yield fetchRemoteCollection(remote, collection);
yield this.validateCollectionSignature(remote, payload, collection, {ignoreLocal: true});
// if the signature is good (we haven't thrown), and the remote
// last_modified is newer than the local last_modified, replace the
// local data
const localLastModified = yield collection.db.getLastModified();
if (payload.last_modified >= localLastModified) {
yield collection.clear();
yield collection.loadDump(payload.data);
}
} else {
throw e;
}
// Report but go-on.
Cu.reportError(e);
}
// Read local collection of records.
const {data} = yield collection.list();
yield this.processCallback(data);
// Track last update.
this.updateLastCheck(serverTime);
} finally {
yield sqliteHandle.close();
}
}).bind(this));
// If the data is up to date, there's no need to sync. We still need
// to record the fact that a check happened.
if (lastModified <= collectionLastModified) {
this.updateLastCheck(serverTime);
return;
}
// Fetch changes from server.
try {
const {ok} = await collection.sync({remote});
if (!ok) {
throw new Error("Sync failed");
}
} catch (e) {
if (e.message == INVALID_SIGNATURE) {
// if sync fails with a signature error, it's likely that our
// local data has been modified in some way.
// We will attempt to fix this by retrieving the whole
// remote collection.
const payload = await fetchRemoteCollection(remote, collection);
await this.validateCollectionSignature(remote, payload, collection, {ignoreLocal: true});
// if the signature is good (we haven't thrown), and the remote
// last_modified is newer than the local last_modified, replace the
// local data
const localLastModified = await collection.db.getLastModified();
if (payload.last_modified >= localLastModified) {
await collection.clear();
await collection.loadDump(payload.data);
}
} else {
throw e;
}
}
// Read local collection of records.
const {data} = await collection.list();
await this.processCallback(data);
// Track last update.
this.updateLastCheck(serverTime);
} finally {
await sqliteHandle.close();
}
}
/**
@ -288,7 +282,7 @@ class BlocklistClient {
*
* @param {Object} records current records in the local db.
*/
function* updateCertBlocklist(records) {
async function updateCertBlocklist(records) {
const certList = Cc["@mozilla.org/security/certblocklist;1"]
.getService(Ci.nsICertBlocklist);
for (let item of records) {
@ -316,7 +310,7 @@ function* updateCertBlocklist(records) {
*
* @param {Object} records current records in the local db.
*/
function* updatePinningList(records) {
async function updatePinningList(records) {
if (!Services.prefs.getBoolPref(PREF_BLOCKLIST_PINNING_ENABLED)) {
return;
}
@ -361,16 +355,16 @@ function* updatePinningList(records) {
* @param {String} filename path relative to profile dir.
* @param {Object} records current records in the local db.
*/
function* updateJSONBlocklist(filename, records) {
async function updateJSONBlocklist(filename, records) {
// Write JSON dump for synchronous load at startup.
const path = OS.Path.join(OS.Constants.Path.profileDir, filename);
const blocklistFolder = OS.Path.dirname(path);
yield OS.File.makeDir(blocklistFolder, {from: OS.Constants.Path.profileDir});
await OS.File.makeDir(blocklistFolder, {from: OS.Constants.Path.profileDir});
const serialized = JSON.stringify({data: records}, null, 2);
try {
yield OS.File.writeAtomic(path, serialized, {tmpPath: path + ".tmp"});
await OS.File.writeAtomic(path, serialized, {tmpPath: path + ".tmp"});
// Notify change to `nsBlocklistService`
const eventData = {filename};
Services.cpmm.sendAsyncMessage("Blocklist:reload-from-disk", eventData);

Просмотреть файл

@ -7,7 +7,6 @@ this.EXPORTED_SYMBOLS = ["checkVersions", "addTestBlocklistClient"];
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.importGlobalProperties(["fetch"]);
const BlocklistClients = Cu.import("resource://services-common/blocklist-clients.js", {});
@ -33,98 +32,95 @@ this.addTestBlocklistClient = (name, client) => { gBlocklistClients[name] = clie
// This is called by the ping mechanism.
// returns a promise that rejects if something goes wrong
this.checkVersions = function() {
return Task.spawn(function* syncClients() {
// Check if the server backoff time is elapsed.
if (Services.prefs.prefHasUserValue(PREF_SETTINGS_SERVER_BACKOFF)) {
const backoffReleaseTime = Services.prefs.getCharPref(PREF_SETTINGS_SERVER_BACKOFF);
const remainingMilliseconds = parseInt(backoffReleaseTime, 10) - Date.now();
if (remainingMilliseconds > 0) {
throw new Error(`Server is asking clients to back off; retry in ${Math.ceil(remainingMilliseconds / 1000)}s.`);
} else {
Services.prefs.clearUserPref(PREF_SETTINGS_SERVER_BACKOFF);
}
}
// Fetch a versionInfo object that looks like:
// {"data":[
// {
// "host":"kinto-ota.dev.mozaws.net",
// "last_modified":1450717104423,
// "bucket":"blocklists",
// "collection":"certificates"
// }]}
// Right now, we only use the collection name and the last modified info
const kintoBase = Services.prefs.getCharPref(PREF_SETTINGS_SERVER);
const changesEndpoint = kintoBase + Services.prefs.getCharPref(PREF_BLOCKLIST_CHANGES_PATH);
// Use ETag to obtain a `304 Not modified` when no change occurred.
const headers = {};
if (Services.prefs.prefHasUserValue(PREF_BLOCKLIST_LAST_ETAG)) {
const lastEtag = Services.prefs.getCharPref(PREF_BLOCKLIST_LAST_ETAG);
if (lastEtag) {
headers["If-None-Match"] = lastEtag;
}
}
const response = yield fetch(changesEndpoint, {headers});
// Check if the server asked the clients to back off.
if (response.headers.has("Backoff")) {
const backoffSeconds = parseInt(response.headers.get("Backoff"), 10);
if (!isNaN(backoffSeconds)) {
const backoffReleaseTime = Date.now() + backoffSeconds * 1000;
Services.prefs.setCharPref(PREF_SETTINGS_SERVER_BACKOFF, backoffReleaseTime);
}
}
let versionInfo;
// No changes since last time. Go on with empty list of changes.
if (response.status == 304) {
versionInfo = {data: []};
this.checkVersions = async function() {
// Check if the server backoff time is elapsed.
if (Services.prefs.prefHasUserValue(PREF_SETTINGS_SERVER_BACKOFF)) {
const backoffReleaseTime = Services.prefs.getCharPref(PREF_SETTINGS_SERVER_BACKOFF);
const remainingMilliseconds = parseInt(backoffReleaseTime, 10) - Date.now();
if (remainingMilliseconds > 0) {
throw new Error(`Server is asking clients to back off; retry in ${Math.ceil(remainingMilliseconds / 1000)}s.`);
} else {
versionInfo = yield response.json();
Services.prefs.clearUserPref(PREF_SETTINGS_SERVER_BACKOFF);
}
}
// If the server is failing, the JSON response might not contain the
// expected data (e.g. error response - Bug 1259145)
if (!versionInfo.hasOwnProperty("data")) {
throw new Error("Polling for changes failed.");
// Fetch a versionInfo object that looks like:
// {"data":[
// {
// "host":"kinto-ota.dev.mozaws.net",
// "last_modified":1450717104423,
// "bucket":"blocklists",
// "collection":"certificates"
// }]}
// Right now, we only use the collection name and the last modified info
const kintoBase = Services.prefs.getCharPref(PREF_SETTINGS_SERVER);
const changesEndpoint = kintoBase + Services.prefs.getCharPref(PREF_BLOCKLIST_CHANGES_PATH);
// Use ETag to obtain a `304 Not modified` when no change occurred.
const headers = {};
if (Services.prefs.prefHasUserValue(PREF_BLOCKLIST_LAST_ETAG)) {
const lastEtag = Services.prefs.getCharPref(PREF_BLOCKLIST_LAST_ETAG);
if (lastEtag) {
headers["If-None-Match"] = lastEtag;
}
}
// Record new update time and the difference between local and server time
const serverTimeMillis = Date.parse(response.headers.get("Date"));
const response = await fetch(changesEndpoint, {headers});
// negative clockDifference means local time is behind server time
// by the absolute of that value in seconds (positive means it's ahead)
const clockDifference = Math.floor((Date.now() - serverTimeMillis) / 1000);
Services.prefs.setIntPref(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, clockDifference);
Services.prefs.setIntPref(PREF_BLOCKLIST_LAST_UPDATE, serverTimeMillis / 1000);
// Check if the server asked the clients to back off.
if (response.headers.has("Backoff")) {
const backoffSeconds = parseInt(response.headers.get("Backoff"), 10);
if (!isNaN(backoffSeconds)) {
const backoffReleaseTime = Date.now() + backoffSeconds * 1000;
Services.prefs.setCharPref(PREF_SETTINGS_SERVER_BACKOFF, backoffReleaseTime);
}
}
let firstError;
for (let collectionInfo of versionInfo.data) {
const {bucket, collection, last_modified: lastModified} = collectionInfo;
const client = gBlocklistClients[collection];
if (client && client.bucketName == bucket) {
try {
yield client.maybeSync(lastModified, serverTimeMillis);
} catch (e) {
if (!firstError) {
firstError = e;
}
let versionInfo;
// No changes since last time. Go on with empty list of changes.
if (response.status == 304) {
versionInfo = {data: []};
} else {
versionInfo = await response.json();
}
// If the server is failing, the JSON response might not contain the
// expected data (e.g. error response - Bug 1259145)
if (!versionInfo.hasOwnProperty("data")) {
throw new Error("Polling for changes failed.");
}
// Record new update time and the difference between local and server time
const serverTimeMillis = Date.parse(response.headers.get("Date"));
// negative clockDifference means local time is behind server time
// by the absolute of that value in seconds (positive means it's ahead)
const clockDifference = Math.floor((Date.now() - serverTimeMillis) / 1000);
Services.prefs.setIntPref(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, clockDifference);
Services.prefs.setIntPref(PREF_BLOCKLIST_LAST_UPDATE, serverTimeMillis / 1000);
let firstError;
for (let collectionInfo of versionInfo.data) {
const {bucket, collection, last_modified: lastModified} = collectionInfo;
const client = gBlocklistClients[collection];
if (client && client.bucketName == bucket) {
try {
await client.maybeSync(lastModified, serverTimeMillis);
} catch (e) {
if (!firstError) {
firstError = e;
}
}
}
if (firstError) {
// cause the promise to reject by throwing the first observed error
throw firstError;
}
}
if (firstError) {
// cause the promise to reject by throwing the first observed error
throw firstError;
}
// Save current Etag for next poll.
if (response.headers.has("ETag")) {
const currentEtag = response.headers.get("ETag");
Services.prefs.setCharPref(PREF_BLOCKLIST_LAST_ETAG, currentEtag);
}
});
// Save current Etag for next poll.
if (response.headers.has("ETag")) {
const currentEtag = response.headers.get("ETag");
Services.prefs.setCharPref(PREF_BLOCKLIST_LAST_ETAG, currentEtag);
}
};

Просмотреть файл

@ -31,7 +31,7 @@ var {interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://services-common/hawkrequest.js");
Cu.import("resource://services-common/observers.js");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
@ -207,7 +207,7 @@ this.HawkClient.prototype = {
retryOK = true) {
method = method.toLowerCase();
let deferred = Promise.defer();
let deferred = PromiseUtils.defer();
let uri = this.host + path;
let self = this;

Просмотреть файл

@ -13,7 +13,6 @@
*/
const { utils: Cu } = Components;
const { Sqlite } = Cu.import("resource://gre/modules/Sqlite.jsm", {});
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
const { Kinto } = Cu.import("resource://services-common/kinto-offline-client.js", {});
const SQLITE_PATH = "kinto.sqlite";
@ -212,24 +211,22 @@ class FirefoxAdapter extends Kinto.adapters.BaseAdapter {
*
* This will be called automatically by open().
*/
static _init(connection) {
return Task.spawn(function* () {
yield connection.executeTransaction(function* doSetup() {
const schema = yield connection.getSchemaVersion();
static async _init(connection) {
await connection.executeTransaction(function* doSetup() {
const schema = yield connection.getSchemaVersion();
if (schema == 0) {
if (schema == 0) {
for (let statementName of createStatements) {
yield connection.execute(statements[statementName]);
}
yield connection.setSchemaVersion(currentSchemaVersion);
} else if (schema != 1) {
throw new Error("Unknown database schema: " + schema);
for (let statementName of createStatements) {
yield connection.execute(statements[statementName]);
}
});
return connection;
yield connection.setSchemaVersion(currentSchemaVersion);
} else if (schema != 1) {
throw new Error("Unknown database schema: " + schema);
}
});
return connection;
}
_executeStatement(statement, params) {
@ -327,39 +324,37 @@ class FirefoxAdapter extends Kinto.adapters.BaseAdapter {
* @param {Array} records.
* @return {Array} imported records.
*/
loadDump(records) {
async loadDump(records) {
const connection = this._connection;
const collection_name = this.collection;
return Task.spawn(function* () {
yield connection.executeTransaction(function* doImport() {
for (let record of records) {
const params = {
collection_name,
record_id: record.id,
record: JSON.stringify(record),
};
yield connection.execute(statements.importData, params);
}
const lastModified = Math.max(...records.map(record => record.last_modified));
await connection.executeTransaction(function* doImport() {
for (let record of records) {
const params = {
collection_name,
record_id: record.id,
record: JSON.stringify(record),
};
const previousLastModified = yield connection.execute(
statements.getLastModified, params).then(result => {
return result.length > 0
? result[0].getResultByName("last_modified")
: -1;
});
if (lastModified > previousLastModified) {
const params = {
collection_name,
last_modified: lastModified,
};
yield connection.execute(statements.saveLastModified, params);
}
});
return records;
yield connection.execute(statements.importData, params);
}
const lastModified = Math.max(...records.map(record => record.last_modified));
const params = {
collection_name,
};
const previousLastModified = yield connection.execute(
statements.getLastModified, params).then(result => {
return result.length > 0
? result[0].getResultByName("last_modified")
: -1;
});
if (lastModified > previousLastModified) {
const params = {
collection_name,
last_modified: lastModified,
};
yield connection.execute(statements.saveLastModified, params);
}
});
return records;
}
saveLastModified(lastModified) {

Просмотреть файл

@ -18,7 +18,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
"resource://services-common/utils.js");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Task.jsm");
this.EXPORTED_SYMBOLS = [
"LogManager",
@ -71,7 +70,7 @@ FlushableStorageAppender.prototype = {
// Flush the current stream to a file. Somewhat counter-intuitively, you
// must pass a log which will be written to with details of the operation.
flushToFile: Task.async(function* (subdirArray, filename, log) {
async flushToFile(subdirArray, filename, log) {
let inStream = this.getInputStream();
this.reset();
if (!inStream) {
@ -81,12 +80,12 @@ FlushableStorageAppender.prototype = {
log.debug("Flushing file log");
log.trace("Beginning stream copy to " + filename + ": " + Date.now());
try {
yield this._copyStreamToFile(inStream, subdirArray, filename, log);
await this._copyStreamToFile(inStream, subdirArray, filename, log);
log.trace("onCopyComplete", Date.now());
} catch (ex) {
log.error("Failed to copy log stream to file", ex);
}
}),
},
/**
* Copy an input stream to the named file, doing everything off the main
@ -96,7 +95,7 @@ FlushableStorageAppender.prototype = {
* outputFileName is the filename to create.
* Returns a promise that is resolved on completion or rejected with an error.
*/
_copyStreamToFile: Task.async(function* (inputStream, subdirArray, outputFileName, log) {
async _copyStreamToFile(inputStream, subdirArray, outputFileName, log) {
// The log data could be large, so we don't want to pass it all in a single
// message, so use BUFFER_SIZE chunks.
const BUFFER_SIZE = 8192;
@ -106,9 +105,9 @@ FlushableStorageAppender.prototype = {
binaryStream.setInputStream(inputStream);
let outputDirectory = OS.Path.join(OS.Constants.Path.profileDir, ...subdirArray);
yield OS.File.makeDir(outputDirectory, { ignoreExisting: true, from: OS.Constants.Path.profileDir });
await OS.File.makeDir(outputDirectory, { ignoreExisting: true, from: OS.Constants.Path.profileDir });
let fullOutputFileName = OS.Path.join(outputDirectory, outputFileName);
let output = yield OS.File.open(fullOutputFileName, { write: true} );
let output = await OS.File.open(fullOutputFileName, { write: true} );
try {
while (true) {
let available = binaryStream.available();
@ -116,18 +115,18 @@ FlushableStorageAppender.prototype = {
break;
}
let chunk = binaryStream.readByteArray(Math.min(available, BUFFER_SIZE));
yield output.write(new Uint8Array(chunk));
await output.write(new Uint8Array(chunk));
}
} finally {
try {
binaryStream.close(); // inputStream is closed by the binaryStream
yield output.close();
await output.close();
} catch (ex) {
log.error("Failed to close the input stream", ex);
}
}
log.trace("finished copy to", fullOutputFileName);
}),
},
}
// The public LogManager object.
@ -242,7 +241,7 @@ LogManager.prototype = {
* file written or on error), SUCCESS_LOG_WRITTEN if a "success" log was
* written, or ERROR_LOG_WRITTEN if an "error" log was written.
*/
resetFileLog: Task.async(function* () {
async resetFileLog() {
try {
let flushToFile;
let reasonPrefix;
@ -266,7 +265,7 @@ LogManager.prototype = {
// We have reasonPrefix at the start of the filename so all "error"
// logs are grouped in about:sync-log.
let filename = reasonPrefix + "-" + this.logFilePrefix + "-" + Date.now() + ".txt";
yield this._fileAppender.flushToFile(this._logFileSubDirectoryEntries, filename, this._log);
await this._fileAppender.flushToFile(this._logFileSubDirectoryEntries, filename, this._log);
// It's not completely clear to markh why we only do log cleanups
// for errors, but for now the Sync semantics have been copied...
@ -275,7 +274,7 @@ LogManager.prototype = {
// there are occasional errors - let's address this later!)
if (reason == this.ERROR_LOG_WRITTEN && !this._cleaningUpFileLogs) {
this._log.trace("Scheduling cleanup.");
// Note we don't return/yield or otherwise wait on this promise - it
// Note we don't return/await or otherwise wait on this promise - it
// continues in the background
this.cleanupLogs().catch(err => {
this._log.error("Failed to cleanup logs", err);
@ -286,12 +285,12 @@ LogManager.prototype = {
this._log.error("Failed to resetFileLog", ex);
return null;
}
}),
},
/**
* Finds all logs older than maxErrorAge and deletes them using async I/O.
*/
cleanupLogs: Task.async(function* () {
async cleanupLogs() {
this._cleaningUpFileLogs = true;
let logDir = FileUtils.getDir("ProfD", this._logFileSubDirectoryEntries);
let iterator = new OS.File.DirectoryIterator(logDir.path);
@ -299,7 +298,7 @@ LogManager.prototype = {
let threshold = Date.now() - 1000 * maxAge;
this._log.debug("Log cleanup threshold time: " + threshold);
yield iterator.forEach(Task.async(function* (entry) {
await iterator.forEach(async (entry) => {
// Note that we don't check this.logFilePrefix is in the name - we cleanup
// all files in this directory regardless of that prefix so old logfiles
// for prefixes no longer in use are still cleaned up. See bug 1279145.
@ -309,23 +308,23 @@ LogManager.prototype = {
}
try {
// need to call .stat() as the enumerator doesn't give that to us on *nix.
let info = yield OS.File.stat(entry.path);
let info = await OS.File.stat(entry.path);
if (info.lastModificationDate.getTime() >= threshold) {
return;
}
this._log.trace(" > Cleanup removing " + entry.name +
" (" + info.lastModificationDate.getTime() + ")");
yield OS.File.remove(entry.path);
await OS.File.remove(entry.path);
this._log.trace("Deleted " + entry.name);
} catch (ex) {
this._log.debug("Encountered error trying to clean up old log file "
+ entry.name, ex);
}
}.bind(this)));
});
iterator.close();
this._cleaningUpFileLogs = false;
this._log.debug("Done deleting files.");
// This notification is used only for tests.
Services.obs.notifyObservers(null, "services-tests:common:log-manager:cleanup-logs");
}),
},
}

Просмотреть файл

@ -54,22 +54,17 @@ add_task(function* test_check_maybeSync() {
let updater = Cu.import("resource://services-common/blocklist-updater.js", {});
let syncPromise = new Promise(function(resolve, reject) {
// add a test kinto client that will respond to lastModified information
// for a collection called 'test-collection'
updater.addTestBlocklistClient("test-collection", {
bucketName: "blocklists",
maybeSync(lastModified, serverTime) {
do_check_eq(lastModified, 1000);
do_check_eq(serverTime, 2000);
resolve();
}
});
updater.checkVersions();
});
// ensure we get the maybeSync call
yield syncPromise;
// add a test kinto client that will respond to lastModified information
// for a collection called 'test-collection'
updater.addTestBlocklistClient("test-collection", {
bucketName: "blocklists",
maybeSync(lastModified, serverTime) {
do_check_eq(lastModified, 1000);
do_check_eq(serverTime, 2000);
}
});
yield updater.checkVersions();
// check the last_update is updated
do_check_eq(Services.prefs.getIntPref(PREF_LAST_UPDATE), 2);

Просмотреть файл

@ -3,7 +3,6 @@
"use strict";
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://services-common/hawkclient.js");
const SECOND_MS = 1000;
@ -490,9 +489,9 @@ function getTimestampDelta(authHeader, now = Date.now()) {
}
function deferredStop(server) {
let deferred = Promise.defer();
server.stop(deferred.resolve);
return deferred.promise;
return new Promise(resolve => {
server.stop(resolve);
});
}
function run_test() {

Просмотреть файл

@ -17,7 +17,6 @@ const {utils: Cu, interfaces: Ci} = Components;
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://services-common/utils.js");
@ -94,42 +93,41 @@ this.Credentials = Object.freeze({
},
setup(emailInput, passwordInput, options = {}) {
let deferred = Promise.defer();
log.debug("setup credentials for " + emailInput);
return new Promise(resolve => {
log.debug("setup credentials for " + emailInput);
let hkdfSalt = options.hkdfSalt || HKDF_SALT;
let hkdfLength = options.hkdfLength || HKDF_LENGTH;
let hmacLength = options.hmacLength || HMAC_LENGTH;
let hmacAlgorithm = options.hmacAlgorithm || HMAC_ALGORITHM;
let stretchedPWLength = options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES;
let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS;
let hkdfSalt = options.hkdfSalt || HKDF_SALT;
let hkdfLength = options.hkdfLength || HKDF_LENGTH;
let hmacLength = options.hmacLength || HMAC_LENGTH;
let hmacAlgorithm = options.hmacAlgorithm || HMAC_ALGORITHM;
let stretchedPWLength = options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES;
let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS;
let result = {};
let result = {};
let password = CommonUtils.encodeUTF8(passwordInput);
let salt = this.keyWordExtended("quickStretch", emailInput);
let password = CommonUtils.encodeUTF8(passwordInput);
let salt = this.keyWordExtended("quickStretch", emailInput);
let runnable = () => {
let start = Date.now();
let quickStretchedPW = CryptoUtils.pbkdf2Generate(
password, salt, pbkdf2Rounds, stretchedPWLength, hmacAlgorithm, hmacLength);
let runnable = () => {
let start = Date.now();
let quickStretchedPW = CryptoUtils.pbkdf2Generate(
password, salt, pbkdf2Rounds, stretchedPWLength, hmacAlgorithm, hmacLength);
result.quickStretchedPW = quickStretchedPW;
result.quickStretchedPW = quickStretchedPW;
result.authPW =
CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("authPW"), hkdfLength);
result.authPW =
CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("authPW"), hkdfLength);
result.unwrapBKey =
CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("unwrapBkey"), hkdfLength);
result.unwrapBKey =
CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("unwrapBkey"), hkdfLength);
log.debug("Credentials set up after " + (Date.now() - start) + " ms");
deferred.resolve(result);
}
log.debug("Credentials set up after " + (Date.now() - start) + " ms");
resolve(result);
}
Services.tm.dispatchToMainThread(runnable);
log.debug("Dispatched thread for credentials setup crypto work");
return deferred.promise;
Services.tm.dispatchToMainThread(runnable);
log.debug("Dispatched thread for credentials setup crypto work");
});
}
});

Просмотреть файл

@ -10,14 +10,13 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.importGlobalProperties(["URL"]);
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/rest.js");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/FxAccountsStorage.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
@ -894,7 +893,7 @@ FxAccountsInternal.prototype = {
return userData;
}
if (!currentState.whenKeysReadyDeferred) {
currentState.whenKeysReadyDeferred = Promise.defer();
currentState.whenKeysReadyDeferred = PromiseUtils.defer();
if (userData.keyFetchToken) {
this.fetchAndUnwrapKeys(userData.keyFetchToken).then(
(dataWithKeys) => {
@ -920,60 +919,58 @@ FxAccountsInternal.prototype = {
).then(result => currentState.resolve(result));
},
fetchAndUnwrapKeys(keyFetchToken) {
async fetchAndUnwrapKeys(keyFetchToken) {
if (logPII) {
log.debug("fetchAndUnwrapKeys: token: " + keyFetchToken);
}
let currentState = this.currentAccountState;
return Task.spawn(function* task() {
// Sign out if we don't have a key fetch token.
if (!keyFetchToken) {
log.warn("improper fetchAndUnwrapKeys() call: token missing");
yield this.signOut();
return null;
}
// Sign out if we don't have a key fetch token.
if (!keyFetchToken) {
log.warn("improper fetchAndUnwrapKeys() call: token missing");
await this.signOut();
return currentState.resolve(null);
}
let {kA, wrapKB} = yield this.fetchKeys(keyFetchToken);
let {kA, wrapKB} = await this.fetchKeys(keyFetchToken);
let data = yield currentState.getUserAccountData();
let data = await currentState.getUserAccountData();
// Sanity check that the user hasn't changed out from under us
if (data.keyFetchToken !== keyFetchToken) {
throw new Error("Signed in user changed while fetching keys!");
}
// Sanity check that the user hasn't changed out from under us
if (data.keyFetchToken !== keyFetchToken) {
throw new Error("Signed in user changed while fetching keys!");
}
// Next statements must be synchronous until we setUserAccountData
// so that we don't risk getting into a weird state.
let kB_hex = CryptoUtils.xor(CommonUtils.hexToBytes(data.unwrapBKey),
wrapKB);
// Next statements must be synchronous until we setUserAccountData
// so that we don't risk getting into a weird state.
let kB_hex = CryptoUtils.xor(CommonUtils.hexToBytes(data.unwrapBKey),
wrapKB);
if (logPII) {
log.debug("kB_hex: " + kB_hex);
}
let updateData = {
kA: CommonUtils.bytesAsHex(kA),
kB: CommonUtils.bytesAsHex(kB_hex),
keyFetchToken: null, // null values cause the item to be removed.
unwrapBKey: null,
}
if (logPII) {
log.debug("kB_hex: " + kB_hex);
}
let updateData = {
kA: CommonUtils.bytesAsHex(kA),
kB: CommonUtils.bytesAsHex(kB_hex),
keyFetchToken: null, // null values cause the item to be removed.
unwrapBKey: null,
}
log.debug("Keys Obtained: kA=" + !!updateData.kA + ", kB=" + !!updateData.kB);
if (logPII) {
log.debug("Keys Obtained: kA=" + updateData.kA + ", kB=" + updateData.kB);
}
log.debug("Keys Obtained: kA=" + !!updateData.kA + ", kB=" + !!updateData.kB);
if (logPII) {
log.debug("Keys Obtained: kA=" + updateData.kA + ", kB=" + updateData.kB);
}
yield currentState.updateUserAccountData(updateData);
// We are now ready for business. This should only be invoked once
// per setSignedInUser(), regardless of whether we've rebooted since
// setSignedInUser() was called.
this.notifyObservers(ONVERIFIED_NOTIFICATION);
return currentState.getUserAccountData();
}.bind(this)).then(result => currentState.resolve(result));
await currentState.updateUserAccountData(updateData);
// We are now ready for business. This should only be invoked once
// per setSignedInUser(), regardless of whether we've rebooted since
// setSignedInUser() was called.
this.notifyObservers(ONVERIFIED_NOTIFICATION);
data = await currentState.getUserAccountData();
return currentState.resolve(data);
},
getAssertionFromCert(data, keyPair, cert, audience) {
async getAssertionFromCert(data, keyPair, cert, audience) {
log.debug("getAssertionFromCert");
let d = Promise.defer();
let options = {
duration: ASSERTION_LIFETIME,
localtimeOffsetMsec: this.localtimeOffsetMsec,
@ -982,19 +979,21 @@ FxAccountsInternal.prototype = {
let currentState = this.currentAccountState;
// "audience" should look like "http://123done.org".
// The generated assertion will expire in two minutes.
jwcrypto.generateAssertion(cert, keyPair, audience, options, (err, signed) => {
if (err) {
log.error("getAssertionFromCert: " + err);
d.reject(err);
} else {
log.debug("getAssertionFromCert returning signed: " + !!signed);
if (logPII) {
log.debug("getAssertionFromCert returning signed: " + signed);
let assertion = await new Promise((resolve, reject) => {
jwcrypto.generateAssertion(cert, keyPair, audience, options, (err, signed) => {
if (err) {
log.error("getAssertionFromCert: " + err);
reject(err);
} else {
log.debug("getAssertionFromCert returning signed: " + !!signed);
if (logPII) {
log.debug("getAssertionFromCert returning signed: " + signed);
}
resolve(signed);
}
d.resolve(signed);
}
});
});
return d.promise.then(result => currentState.resolve(result));
return currentState.resolve(assertion);
},
getCertificateSigned(sessionToken, serializedPublicKey, lifetime) {
@ -1012,7 +1011,7 @@ FxAccountsInternal.prototype = {
/**
* returns a promise that fires with {keyPair, certificate}.
*/
getKeypairAndCertificate: Task.async(function* (currentState) {
async getKeypairAndCertificate(currentState) {
// If the debugging pref to ignore cached authentication credentials is set for Sync,
// then don't use any cached key pair/certificate, i.e., generate a new
// one and get it signed.
@ -1021,7 +1020,7 @@ FxAccountsInternal.prototype = {
// password.
let ignoreCachedAuthCredentials = Services.prefs.getBoolPref("services.sync.debug.ignoreCachedAuthCredentials", false);
let mustBeValidUntil = this.now() + ASSERTION_USE_PERIOD;
let accountData = yield currentState.getUserAccountData(["cert", "keyPair", "sessionToken"]);
let accountData = await currentState.getUserAccountData(["cert", "keyPair", "sessionToken"]);
let keyPairValid = !ignoreCachedAuthCredentials &&
accountData.keyPair &&
@ -1055,7 +1054,7 @@ FxAccountsInternal.prototype = {
keyPair = accountData.keyPair;
} else {
let keyWillBeValidUntil = this.now() + KEY_LIFETIME;
keyPair = yield new Promise((resolve, reject) => {
keyPair = await new Promise((resolve, reject) => {
jwcrypto.generateKeyPair("DS160", (err, kp) => {
if (err) {
reject(err);
@ -1072,7 +1071,7 @@ FxAccountsInternal.prototype = {
// and generate the cert.
let certWillBeValidUntil = this.now() + CERT_LIFETIME;
let certificate = yield this.getCertificateSigned(accountData.sessionToken,
let certificate = await this.getCertificateSigned(accountData.sessionToken,
keyPair.rawKeyPair.serializedPublicKey,
CERT_LIFETIME);
log.debug("getCertificate got a new one: " + !!certificate);
@ -1085,13 +1084,13 @@ FxAccountsInternal.prototype = {
validUntil: certWillBeValidUntil,
},
};
yield currentState.updateUserAccountData(toUpdate);
await currentState.updateUserAccountData(toUpdate);
}
return {
keyPair: keyPair.rawKeyPair,
certificate,
}
}),
},
getUserAccountData() {
return this.currentAccountState.getUserAccountData();
@ -1175,7 +1174,7 @@ FxAccountsInternal.prototype = {
// were already polling for receipt of an earlier email.
this.pollStartDate = Date.now();
if (!currentState.whenVerifiedDeferred) {
currentState.whenVerifiedDeferred = Promise.defer();
currentState.whenVerifiedDeferred = PromiseUtils.defer();
// This deferred might not end up with any handlers (eg, if sync
// is yet to start up.) This might cause "A promise chain failed to
// handle a rejection" messages, so add an error handler directly
@ -1348,7 +1347,7 @@ FxAccountsInternal.prototype = {
* AUTH_ERROR
* UNKNOWN_ERROR
*/
getOAuthToken: Task.async(function* (options = {}) {
async getOAuthToken(options = {}) {
log.debug("getOAuthToken enter");
let scope = options.scope;
if (typeof scope === "string") {
@ -1359,7 +1358,7 @@ FxAccountsInternal.prototype = {
throw this._error(ERROR_INVALID_PARAMETER, "Missing or invalid 'scope' option");
}
yield this._getVerifiedAccountOrReject();
await this._getVerifiedAccountOrReject();
// Early exit for a cached token.
let currentState = this.currentAccountState;
@ -1388,8 +1387,8 @@ FxAccountsInternal.prototype = {
try {
log.debug("getOAuthToken fetching new token from", oAuthURL);
let assertion = yield this.getAssertion(oAuthURL);
let result = yield client.getTokenFromAssertion(assertion, scopeString);
let assertion = await this.getAssertion(oAuthURL);
let result = await client.getTokenFromAssertion(assertion, scopeString);
let token = result.access_token;
// If we got one, cache it.
if (token) {
@ -1410,7 +1409,7 @@ FxAccountsInternal.prototype = {
} catch (err) {
throw this._errorToErrorClass(err);
}
}),
},
/**
* Remove an OAuth token from the token cache. Callers should call this
@ -1424,7 +1423,7 @@ FxAccountsInternal.prototype = {
* @return Promise.<undefined> This function will always resolve, even if
* an unknown token is passed.
*/
removeCachedOAuthToken: Task.async(function* (options) {
async removeCachedOAuthToken(options) {
if (!options.token || typeof options.token !== "string") {
throw this._error(ERROR_INVALID_PARAMETER, "Missing or invalid 'token' option");
}
@ -1436,7 +1435,7 @@ FxAccountsInternal.prototype = {
log.warn("FxA failed to revoke a cached token", err);
});
}
}),
},
async _getVerifiedAccountOrReject() {
let data = await this.currentAccountState.getUserAccountData();

Просмотреть файл

@ -7,7 +7,6 @@ this.EXPORTED_SYMBOLS = ["FxAccountsClient"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/hawkclient.js");
@ -564,45 +563,36 @@ this.FxAccountsClient.prototype = {
* "info": "https://docs.dev.lcip.og/errors/1234" // link to more info on the error
* }
*/
_request: function hawkRequest(path, method, credentials, jsonPayload) {
let deferred = Promise.defer();
async _request(path, method, credentials, jsonPayload) {
// We were asked to back off.
if (this.backoffError) {
log.debug("Received new request during backoff, re-rejecting.");
deferred.reject(this.backoffError);
return deferred.promise;
throw this.backoffError;
}
this.hawk.request(path, method, credentials, jsonPayload).then(
(response) => {
try {
let responseObj = JSON.parse(response.body);
deferred.resolve(responseObj);
} catch (err) {
log.error("json parse error on response: " + response.body);
deferred.reject({error: err});
}
},
(error) => {
log.error("error " + method + "ing " + path + ": " + JSON.stringify(error));
if (error.retryAfter) {
log.debug("Received backoff response; caching error as flag.");
this.backoffError = error;
// Schedule clearing of cached-error-as-flag.
CommonUtils.namedTimer(
this._clearBackoff,
error.retryAfter * 1000,
this,
"fxaBackoffTimer"
);
}
deferred.reject(error);
let response;
try {
response = await this.hawk.request(path, method, credentials, jsonPayload);
} catch (error) {
log.error("error " + method + "ing " + path + ": " + JSON.stringify(error));
if (error.retryAfter) {
log.debug("Received backoff response; caching error as flag.");
this.backoffError = error;
// Schedule clearing of cached-error-as-flag.
CommonUtils.namedTimer(
this._clearBackoff,
error.retryAfter * 1000,
this,
"fxaBackoffTimer"
);
}
);
return deferred.promise;
throw error;
}
try {
return JSON.parse(response.body);
} catch (error) {
log.error("json parse error on response: " + response.body);
throw {error};
}
},
};

Просмотреть файл

@ -10,7 +10,6 @@ Cu.import("resource://services-common/rest.js");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
"resource://gre/modules/FxAccounts.jsm");
@ -34,24 +33,24 @@ const CONFIG_PREFS = [
this.FxAccountsConfig = {
// Returns a promise that resolves with the URI of the remote UI flows.
promiseAccountsSignUpURI: Task.async(function*() {
yield this.ensureConfigured();
async promiseAccountsSignUpURI() {
await this.ensureConfigured();
let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.signup.uri");
if (fxAccounts.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
throw new Error("Firefox Accounts server must use HTTPS");
}
return url;
}),
},
// Returns a promise that resolves with the URI of the remote UI flows.
promiseAccountsSignInURI: Task.async(function*() {
yield this.ensureConfigured();
async promiseAccountsSignInURI() {
await this.ensureConfigured();
let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.signin.uri");
if (fxAccounts.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
throw new Error("Firefox Accounts server must use HTTPS");
}
return url;
}),
},
resetConfigURLs() {
let autoconfigURL = this.getAutoConfigURL();
@ -96,25 +95,25 @@ this.FxAccountsConfig = {
return rootURL;
},
ensureConfigured: Task.async(function*() {
let isSignedIn = !!(yield fxAccounts.getSignedInUser());
async ensureConfigured() {
let isSignedIn = !!(await fxAccounts.getSignedInUser());
if (!isSignedIn) {
yield this.fetchConfigURLs();
await this.fetchConfigURLs();
}
}),
},
// Read expected client configuration from the fxa auth server
// (from `identity.fxaccounts.autoconfig.uri`/.well-known/fxa-client-configuration)
// and replace all the relevant our prefs with the information found there.
// This is only done before sign-in and sign-up, and even then only if the
// `identity.fxaccounts.autoconfig.uri` preference is set.
fetchConfigURLs: Task.async(function*() {
async fetchConfigURLs() {
let rootURL = this.getAutoConfigURL();
if (!rootURL) {
return;
}
let configURL = rootURL + "/.well-known/fxa-client-configuration";
let jsonStr = yield new Promise((resolve, reject) => {
let jsonStr = await new Promise((resolve, reject) => {
let request = new RESTRequest(configURL);
request.setHeader("Accept", "application/json");
request.get(error => {
@ -170,6 +169,6 @@ this.FxAccountsConfig = {
log.error("Failed to initialize configuration preferences from autoconfig object", e);
throw e;
}
}),
},
};

Просмотреть файл

@ -18,7 +18,6 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/FxAccounts.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
XPCOMUtils.defineLazyServiceGetter(this, "permissionManager",

Просмотреть файл

@ -12,7 +12,6 @@ this.EXPORTED_SYMBOLS = ["FxAccountsOAuthGrantClient", "FxAccountsOAuthGrantClie
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://services-common/rest.js");

Просмотреть файл

@ -11,11 +11,9 @@ this.EXPORTED_SYMBOLS = ["FxAccountsProfileClient", "FxAccountsProfileClientErro
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/FxAccounts.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://services-common/rest.js");
Cu.importGlobalProperties(["URL"]);
@ -82,14 +80,14 @@ this.FxAccountsProfileClient.prototype = {
* Rejects: {FxAccountsProfileClientError} Profile client error.
* @private
*/
_createRequest: Task.async(function* (path, method = "GET", etag = null) {
async _createRequest(path, method = "GET", etag = null) {
let token = this.token;
if (!token) {
// tokens are cached, so getting them each request is cheap.
token = yield this.fxa.getOAuthToken(this.oauthOptions);
token = await this.fxa.getOAuthToken(this.oauthOptions);
}
try {
return (yield this._rawRequest(path, method, token, etag));
return (await this._rawRequest(path, method, token, etag));
} catch (ex) {
if (!(ex instanceof FxAccountsProfileClientError) || ex.code != 401) {
throw ex;
@ -100,22 +98,22 @@ this.FxAccountsProfileClient.prototype = {
}
// it's an auth error - assume our token expired and retry.
log.info("Fetching the profile returned a 401 - revoking our token and retrying");
yield this.fxa.removeCachedOAuthToken({token});
token = yield this.fxa.getOAuthToken(this.oauthOptions);
await this.fxa.removeCachedOAuthToken({token});
token = await this.fxa.getOAuthToken(this.oauthOptions);
// and try with the new token - if that also fails then we fail after
// revoking the token.
try {
return (yield this._rawRequest(path, method, token, etag));
return (await this._rawRequest(path, method, token, etag));
} catch (ex) {
if (!(ex instanceof FxAccountsProfileClientError) || ex.code != 401) {
throw ex;
}
log.info("Retry fetching the profile still returned a 401 - revoking our token and failing");
yield this.fxa.removeCachedOAuthToken({token});
await this.fxa.removeCachedOAuthToken({token});
throw ex;
}
}
}),
},
/**
* Remote "raw" request helper - doesn't handle auth errors and tokens.

Просмотреть файл

@ -10,7 +10,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/Task.jsm");
/**
* FxAccountsPushService manages Push notifications for Firefox Accounts in the browser
@ -194,12 +193,12 @@ FxAccountsPushService.prototype = {
* @returns {Promise}
* @private
*/
_onPasswordChanged: Task.async(function* () {
if (!(yield this.fxAccounts.sessionStatus())) {
yield this.fxAccounts.resetCredentials();
async _onPasswordChanged() {
if (!(await this.fxAccounts.sessionStatus())) {
await this.fxAccounts.resetCredentials();
Services.obs.notifyObservers(null, ON_ACCOUNT_STATE_CHANGE_NOTIFICATION);
}
}),
},
/**
* Fired when the Push server drops a subscription, or the subscription identifier changes.
*

Просмотреть файл

@ -12,7 +12,6 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://services-common/utils.js");
@ -65,7 +64,7 @@ this.FxAccountsStorageManager.prototype = {
this._promiseInitialized = this._initialize(accountData);
},
_initialize: Task.async(function* (accountData) {
async _initialize(accountData) {
log.trace("initializing new storage manager");
try {
if (accountData) {
@ -88,20 +87,20 @@ this.FxAccountsStorageManager.prototype = {
}
}
// write it out and we are done.
yield this._write();
await this._write();
return;
}
// So we were initialized without account data - that means we need to
// read the state from storage. We try and read plain storage first and
// only attempt to read secure storage if the plain storage had a user.
this._needToReadSecure = yield this._readPlainStorage();
this._needToReadSecure = await this._readPlainStorage();
if (this._needToReadSecure && this.secureStorage) {
yield this._doReadAndUpdateSecure();
await this._doReadAndUpdateSecure();
}
} finally {
log.trace("initializing of new storage manager done");
}
}),
},
finalize() {
// We can't throw this instance away while it is still writing or we may
@ -145,8 +144,8 @@ this.FxAccountsStorageManager.prototype = {
// data is returned except for "in memory" fields. Note that not specifying
// field names will soon be deprecated/removed - we want all callers to
// specify the fields they care about.
getAccountData: Task.async(function* (fieldNames = null) {
yield this._promiseInitialized;
async getAccountData(fieldNames = null) {
await this._promiseInitialized;
// We know we are initialized - this means our .cachedPlain is accurate
// and doesn't need to be read (it was read if necessary by initialize).
// So if there's no uid, there's no user signed in.
@ -160,7 +159,7 @@ this.FxAccountsStorageManager.prototype = {
result[name] = value;
}
// But the secure data may not have been read, so try that now.
yield this._maybeReadAndUpdateSecure();
await this._maybeReadAndUpdateSecure();
// .cachedSecure now has as much as it possibly can (which is possibly
// nothing if (a) secure storage remains locked and (b) we've never updated
// a field to be stored in secure storage.)
@ -188,7 +187,7 @@ this.FxAccountsStorageManager.prototype = {
} else if (FXA_PWDMGR_SECURE_FIELDS.has(fieldName)) {
// We may not have read secure storage yet.
if (!checkedSecure) {
yield this._maybeReadAndUpdateSecure();
await this._maybeReadAndUpdateSecure();
checkedSecure = true;
}
if (this.cachedSecure[fieldName] !== undefined) {
@ -199,12 +198,12 @@ this.FxAccountsStorageManager.prototype = {
}
}
return result;
}),
},
// Update just the specified fields. This DOES NOT allow you to change to
// a different user, nor to set the user as signed-out.
updateAccountData: Task.async(function* (newFields) {
yield this._promiseInitialized;
async updateAccountData(newFields) {
await this._promiseInitialized;
if (!("uid" in this.cachedPlain)) {
// If this storage instance shows no logged in user, then you can't
// update fields.
@ -244,12 +243,12 @@ this.FxAccountsStorageManager.prototype = {
}
// If we haven't yet read the secure data, do so now, else we may write
// out partial data.
yield this._maybeReadAndUpdateSecure();
await this._maybeReadAndUpdateSecure();
// Now save it - but don't wait on the _write promise - it's queued up as
// a storage operation, so .finalize() will wait for completion, but no need
// for us to.
this._write();
}),
},
_clearCachedData() {
this.cachedMemory = {};
@ -270,10 +269,10 @@ this.FxAccountsStorageManager.prototype = {
Note: _readPlainStorage is only called during initialize, so isn't
protected via _queueStorageOperation() nor _promiseInitialized.
*/
_readPlainStorage: Task.async(function* () {
async _readPlainStorage() {
let got;
try {
got = yield this.plainStorage.get();
got = await this.plainStorage.get();
} catch (err) {
// File hasn't been created yet. That will be done
// when write is called.
@ -299,12 +298,12 @@ this.FxAccountsStorageManager.prototype = {
this.cachedPlain[name] = value;
}
return true;
}),
},
/* If we haven't managed to read the secure storage, try now, so
we can merge our cached data with the data that's already been set.
*/
_maybeReadAndUpdateSecure: Task.async(function* () {
_maybeReadAndUpdateSecure() {
if (this.secureStorage == null || !this._needToReadSecure) {
return null;
}
@ -314,13 +313,13 @@ this.FxAccountsStorageManager.prototype = {
}
return null;
});
}),
},
/* Unconditionally read the secure storage and merge our cached data (ie, data
which has already been set while the secure storage was locked) with
the read data
*/
_doReadAndUpdateSecure: Task.async(function* () {
async _doReadAndUpdateSecure() {
let { uid, email } = this.cachedPlain;
try {
log.debug("reading secure storage with existing", Object.keys(this.cachedSecure));
@ -328,7 +327,7 @@ this.FxAccountsStorageManager.prototype = {
// updated cachedSecure before we've read it. That means that after we do
// manage to read we must write back the merged data.
let needWrite = Object.keys(this.cachedSecure).length != 0;
let readSecure = yield this.secureStorage.get(uid, email);
let readSecure = await this.secureStorage.get(uid, email);
// and update our cached data with it - anything already in .cachedSecure
// wins (including the fact it may be null or undefined, the latter
// which means it will be removed from storage.
@ -345,7 +344,7 @@ this.FxAccountsStorageManager.prototype = {
}
if (needWrite) {
log.debug("successfully read secure data; writing updated data back")
yield this._doWriteSecure();
await this._doWriteSecure();
}
}
this._needToReadSecure = false;
@ -357,7 +356,7 @@ this.FxAccountsStorageManager.prototype = {
throw ex;
}
}
}),
},
_write() {
// We don't want multiple writes happening concurrently, and we also need to
@ -365,7 +364,7 @@ this.FxAccountsStorageManager.prototype = {
return this._queueStorageOperation(() => this.__write());
},
__write: Task.async(function* () {
async __write() {
// Write everything back - later we could track what's actually dirty,
// but for now we write it all.
log.debug("writing plain storage", Object.keys(this.cachedPlain));
@ -373,7 +372,7 @@ this.FxAccountsStorageManager.prototype = {
version: DATA_FORMAT_VERSION,
accountData: this.cachedPlain,
}
yield this.plainStorage.set(toWritePlain);
await this.plainStorage.set(toWritePlain);
// If we have no secure storage manager we are done.
if (this.secureStorage == null) {
@ -382,14 +381,14 @@ this.FxAccountsStorageManager.prototype = {
// and only attempt to write to secure storage if we've managed to read it,
// otherwise we might clobber data that's already there.
if (!this._needToReadSecure) {
yield this._doWriteSecure();
await this._doWriteSecure();
}
}),
},
/* Do the actual write of secure data. Caller is expected to check if we actually
need to write and to ensure we are in a queued storage operation.
*/
_doWriteSecure: Task.async(function* () {
async _doWriteSecure() {
// We need to remove null items here.
for (let [name, value] of Object.entries(this.cachedSecure)) {
if (value == null) {
@ -402,7 +401,7 @@ this.FxAccountsStorageManager.prototype = {
accountData: this.cachedSecure,
}
try {
yield this.secureStorage.set(this.cachedPlain.uid, toWriteSecure);
await this.secureStorage.set(this.cachedPlain.uid, toWriteSecure);
} catch (ex) {
if (!(ex instanceof this.secureStorage.STORAGE_LOCKED)) {
throw ex;
@ -412,23 +411,23 @@ this.FxAccountsStorageManager.prototype = {
// read.
log.error("setAccountData: secure storage is locked trying to write");
}
}),
},
// Delete the data for an account - ie, called on "sign out".
deleteAccountData() {
return this._queueStorageOperation(() => this._deleteAccountData());
},
_deleteAccountData: Task.async(function* () {
async _deleteAccountData() {
log.debug("removing account data");
yield this._promiseInitialized;
yield this.plainStorage.set(null);
await this._promiseInitialized;
await this.plainStorage.set(null);
if (this.secureStorage) {
yield this.secureStorage.set(null);
await this.secureStorage.set(null);
}
this._clearCachedData();
log.debug("account data reset");
}),
},
}
/**
@ -495,9 +494,9 @@ LoginManagerStorage.prototype = {
// Clear any data from the login manager. Returns true if the login manager
// was unlocked (even if no existing logins existed) or false if it was
// locked (meaning we don't even know if it existed or not.)
_clearLoginMgrData: Task.async(function* () {
async _clearLoginMgrData() {
try { // Services.logins might be third-party and broken...
yield Services.logins.initializationPromise;
await Services.logins.initializationPromise;
if (!this._isLoggedIn) {
return false;
}
@ -510,12 +509,12 @@ LoginManagerStorage.prototype = {
log.error("Failed to clear login data: ${}", ex);
return false;
}
}),
},
set: Task.async(function* (uid, contents) {
async set(uid, contents) {
if (!contents) {
// Nuke it from the login manager.
let cleared = yield this._clearLoginMgrData();
let cleared = await this._clearLoginMgrData();
if (!cleared) {
// just log a message - we verify that the uid matches when
// we reload it, so having a stale entry doesn't really hurt.
@ -529,7 +528,7 @@ LoginManagerStorage.prototype = {
log.trace("starting write of user data to the login manager");
try { // Services.logins might be third-party and broken...
// and the stuff into the login manager.
yield Services.logins.initializationPromise;
await Services.logins.initializationPromise;
// If MP is locked we silently fail - the user may need to re-auth
// next startup.
if (!this._isLoggedIn) {
@ -563,14 +562,14 @@ LoginManagerStorage.prototype = {
// manager replacement that's simply broken.
log.error("Failed to save data to the login manager", ex);
}
}),
},
get: Task.async(function* (uid, email) {
async get(uid, email) {
log.trace("starting fetch of user data from the login manager");
try { // Services.logins might be third-party and broken...
// read the data from the login manager and merge it for return.
yield Services.logins.initializationPromise;
await Services.logins.initializationPromise;
if (!this._isLoggedIn) {
log.info("returning partial account data as the login manager is locked.");
@ -590,7 +589,7 @@ LoginManagerStorage.prototype = {
return JSON.parse(login.password);
}
log.info("username in the login manager doesn't match - ignoring it");
yield this._clearLoginMgrData();
await this._clearLoginMgrData();
} catch (ex) {
if (ex instanceof this.STORAGE_LOCKED) {
throw ex;
@ -600,7 +599,7 @@ LoginManagerStorage.prototype = {
log.error("Failed to get data from the login manager", ex);
}
return null;
}),
},
}
// A global variable to indicate if the login manager is available - it doesn't

Просмотреть файл

@ -85,7 +85,9 @@ this.FxAccountsWebChannel = function(options) {
this._webChannelId = options.channel_id;
// options.helpers is only specified by tests.
this._helpers = options.helpers || new FxAccountsWebChannelHelpers(options);
XPCOMUtils.defineLazyGetter(this, "_helpers", () => {
return options.helpers || new FxAccountsWebChannelHelpers(options);
});
this._setupChannel();
};

Просмотреть файл

@ -25,7 +25,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=963835
SimpleTest.waitForExplicitFinish();
Components.utils.import("resource://gre/modules/Promise.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/FxAccounts.jsm");
Components.utils.import("resource://gre/modules/FxAccountsClient.jsm");

Просмотреть файл

@ -8,7 +8,7 @@ Cu.import("resource://gre/modules/FxAccounts.jsm");
Cu.import("resource://gre/modules/FxAccountsClient.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
// We grab some additional stuff via backstage passes.
@ -96,31 +96,27 @@ function MockFxAccountsClient() {
// mock calls up to the auth server to determine whether the
// user account has been verified
this.recoveryEmailStatus = function(sessionToken) {
this.recoveryEmailStatus = async function(sessionToken) {
// simulate a call to /recovery_email/status
return Promise.resolve({
return {
email: this._email,
verified: this._verified
});
};
};
this.accountStatus = function(uid) {
let deferred = Promise.defer();
deferred.resolve(!!uid && (!this._deletedOnServer));
return deferred.promise;
this.accountStatus = async function(uid) {
return !!uid && (!this._deletedOnServer);
};
this.accountKeys = function(keyFetchToken) {
let deferred = Promise.defer();
do_timeout(50, () => {
let response = {
kA: expandBytes("11"),
wrapKB: expandBytes("22")
};
deferred.resolve(response);
return new Promise(resolve => {
do_timeout(50, () => {
resolve({
kA: expandBytes("11"),
wrapKB: expandBytes("22")
});
});
});
return deferred.promise;
};
this.resendVerificationEmail = function(sessionToken) {
@ -150,7 +146,7 @@ function MockFxAccounts() {
VERIFICATION_POLL_TIMEOUT_INITIAL: 100, // 100ms
_getCertificateSigned_calls: [],
_d_signCertificate: Promise.defer(),
_d_signCertificate: PromiseUtils.defer(),
_now_is: new Date(),
now() {
return this._now_is;
@ -792,7 +788,7 @@ add_task(function* test_getAssertion() {
do_check_eq(exp, now + ASSERTION_LIFETIME);
// Reset for next call.
fxa.internal._d_signCertificate = Promise.defer();
fxa.internal._d_signCertificate = PromiseUtils.defer();
// Getting a new assertion "soon" (i.e., w/o incrementing "now"), even for
// a new audience, should not provoke key generation or a signing request.

Просмотреть файл

@ -7,7 +7,6 @@ Cu.import("resource://services-common/utils.js");
Cu.import("resource://gre/modules/FxAccounts.jsm");
Cu.import("resource://gre/modules/FxAccountsClient.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Log.jsm");
initTestLogging("Trace");
@ -77,9 +76,7 @@ function MockFxAccountsClient(device) {
};
this.accountStatus = function(uid) {
let deferred = Promise.defer();
deferred.resolve(!!uid && (!this._deletedOnServer));
return deferred.promise;
return Promise.resolve(!!uid && (!this._deletedOnServer));
};
const { id: deviceId, name: deviceName, type: deviceType, sessionToken } = device;

Просмотреть файл

@ -4,7 +4,6 @@
"use strict";
Cu.import("resource://gre/modules/FxAccountsClient.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/hawkrequest.js");
Cu.import("resource://services-crypto/utils.js");
@ -35,9 +34,9 @@ var ACCOUNT_KEYS = {
};
function deferredStop(server) {
let deferred = Promise.defer();
server.stop(deferred.resolve);
return deferred.promise;
return new Promise(resolve => {
server.stop(resolve);
});
}
add_task(function* test_authenticated_get_request() {

Просмотреть файл

@ -2,7 +2,6 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://gre/modules/Credentials.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-crypto/utils.js");

Просмотреть файл

@ -64,9 +64,7 @@ function MockFxAccountsClient() {
this._verified = false;
this.accountStatus = function(uid) {
let deferred = Promise.defer();
deferred.resolve(!!uid && (!this._deletedOnServer));
return deferred.promise;
return Promise.resolve(!!uid && (!this._deletedOnServer));
};
this.signOut = function() { return Promise.resolve(); };

Просмотреть файл

@ -61,9 +61,7 @@ function MockFxAccountsClient() {
this._verified = false;
this.accountStatus = function(uid) {
let deferred = Promise.defer();
deferred.resolve(!!uid && (!this._deletedOnServer));
return deferred.promise;
return Promise.resolve(!!uid && (!this._deletedOnServer));
};
this.signOut = function() { return Promise.resolve(); };
@ -166,11 +164,12 @@ add_task(function* testRevoke() {
ok(token1, "got a token");
equal(token1, "token0");
// FxA fires an observer when the "background" revoke is complete.
let revokeComplete = promiseNotification("testhelper-fxa-revoke-complete");
// drop the new token from our cache.
yield fxa.removeCachedOAuthToken({token: token1});
yield revokeComplete;
// FxA fires an observer when the "background" revoke is complete.
yield promiseNotification("testhelper-fxa-revoke-complete");
// the revoke should have been successful.
equal(client.activeTokens.size, 0);
// fetching it again hits the server.
@ -198,10 +197,11 @@ add_task(function* testSignOutDestroysTokens() {
ok(token2, "got a token");
notEqual(token1, token2, "got a different token");
// FxA fires an observer when the "background" signout is complete.
let signoutComplete = promiseNotification("testhelper-fxa-signout-complete");
// now sign out - they should be removed.
yield fxa.signOut();
// FxA fires an observer when the "background" signout is complete.
yield promiseNotification("testhelper-fxa-signout-complete");
yield signoutComplete;
// No active tokens left.
equal(client.activeTokens.size, 0);
});

Просмотреть файл

@ -3,7 +3,6 @@
"use strict";
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/FxAccountsProfileClient.jsm");
Cu.import("resource://gre/modules/FxAccountsProfile.jsm");

Просмотреть файл

@ -5,8 +5,6 @@
// Tests for the FxA push service.
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/Log.jsm");

Просмотреть файл

@ -5,7 +5,6 @@
// Tests for the FxA storage manager.
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/FxAccountsStorage.jsm");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/Log.jsm");
@ -28,15 +27,15 @@ function MockedPlainStorage(accountData) {
this.numReads = 0;
}
MockedPlainStorage.prototype = {
get: Task.async(function* () {
async get() {
this.numReads++;
Assert.equal(this.numReads, 1, "should only ever be 1 read of acct data");
return this.data;
}),
},
set: Task.async(function* (data) {
async set(data) {
this.data = data;
}),
},
};
function MockedSecureStorage(accountData) {
@ -60,7 +59,7 @@ MockedSecureStorage.prototype = {
// "TypeError: this.STORAGE_LOCKED is not a constructor"
STORAGE_LOCKED: function() {},
/* eslint-enable object-shorthand */
get: Task.async(function* (uid, email) {
async get(uid, email) {
this.fetchCount++;
if (this.locked) {
throw new this.STORAGE_LOCKED();
@ -68,11 +67,11 @@ MockedSecureStorage.prototype = {
this.numReads++;
Assert.equal(this.numReads, 1, "should only ever be 1 read of unlocked data");
return this.data;
}),
},
set: Task.async(function* (uid, contents) {
async set(uid, contents) {
this.data = contents;
}),
},
}
function add_storage_task(testFunction) {
@ -336,12 +335,12 @@ add_task(function* checkLockedUpdates() {
// A helper for our queued tests. It creates a StorageManager and then queues
// an unresolved promise. The tests then do additional setup and checks, then
// resolves or rejects the blocked promise.
var setupStorageManagerForQueueTest = Task.async(function* () {
async function setupStorageManagerForQueueTest() {
let sm = new FxAccountsStorageManager();
sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone@somewhere.com"})
sm.secureStorage = new MockedSecureStorage({kA: "kA"});
sm.secureStorage.locked = true;
yield sm.initialize();
await sm.initialize();
let resolveBlocked, rejectBlocked;
let blockedPromise = new Promise((resolve, reject) => {
@ -351,7 +350,7 @@ var setupStorageManagerForQueueTest = Task.async(function* () {
sm._queueStorageOperation(() => blockedPromise);
return {sm, blockedPromise, resolveBlocked, rejectBlocked}
});
}
// First the general functionality.
add_task(function* checkQueueSemantics() {

Просмотреть файл

@ -220,6 +220,9 @@ this.configureIdentity = async function(identityOverrides, server) {
configureFxAccountIdentity(ns.Service.identity, config);
await ns.Service.identity.initializeWithCurrentIdentity();
// The token is fetched in the background, whenReadyToAuthenticate is resolved
// when we are ready.
await ns.Service.identity.whenReadyToAuthenticate.promise;
// and cheat to avoid requiring each test do an explicit login - give it
// a cluster URL.
if (config.fxaccount.token.endpoint) {

Просмотреть файл

@ -11,7 +11,6 @@ const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-sync/main.js");
Cu.import("resource://gre/modules/Preferences.jsm");
@ -56,14 +55,14 @@ let log = Log.repository.getLogger("Sync.RemoteTabs");
// A private singleton that does the work.
let SyncedTabsInternal = {
/* Make a "tab" record. Returns a promise */
_makeTab: Task.async(function* (client, tab, url, showRemoteIcons) {
async _makeTab(client, tab, url, showRemoteIcons) {
let icon;
if (showRemoteIcons) {
icon = tab.icon;
}
if (!icon) {
try {
icon = (yield PlacesUtils.promiseFaviconLinkUrl(url)).spec;
icon = (await PlacesUtils.promiseFaviconLinkUrl(url)).spec;
} catch (ex) { /* no favicon avaiable */ }
}
if (!icon) {
@ -77,10 +76,10 @@ let SyncedTabsInternal = {
client: client.id,
lastUsed: tab.lastUsed,
};
}),
},
/* Make a "client" record. Returns a promise for consistency with _makeTab */
_makeClient: Task.async(function* (client) {
async _makeClient(client) {
return {
id: client.id,
type: "client",
@ -89,14 +88,14 @@ let SyncedTabsInternal = {
lastModified: client.lastModified * 1000, // sec to ms
tabs: []
};
}),
},
_tabMatchesFilter(tab, filter) {
let reFilter = new RegExp(escapeRegExp(filter), "i");
return tab.url.match(reFilter) || tab.title.match(reFilter);
},
getTabClients: Task.async(function* (filter) {
async getTabClients(filter) {
log.info("Generating tab list with filter", filter);
let result = [];
@ -118,7 +117,7 @@ let SyncedTabsInternal = {
if (!Weave.Service.clientsEngine.remoteClientExists(client.id)) {
continue;
}
let clientRepr = yield this._makeClient(client);
let clientRepr = await this._makeClient(client);
log.debug("Processing client", clientRepr);
for (let tab of client.tabs) {
@ -137,7 +136,7 @@ let SyncedTabsInternal = {
if (!url || seenURLs.has(url)) {
continue;
}
let tabRepr = yield this._makeTab(client, tab, url, showRemoteIcons);
let tabRepr = await this._makeTab(client, tab, url, showRemoteIcons);
if (filter && !this._tabMatchesFilter(tabRepr, filter)) {
continue;
}
@ -151,7 +150,7 @@ let SyncedTabsInternal = {
}
log.info(`Final tab list has ${result.length} clients with ${ntabs} tabs.`);
return result;
}),
},
syncTabs(force) {
if (!force) {
@ -213,6 +212,14 @@ let SyncedTabsInternal = {
}
},
get loginFailed() {
if (!weaveXPCService.ready) {
log.debug("Sync isn't yet ready; assuming the login didn't fail");
return false;
}
return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
},
// Returns true if Sync is configured to Sync tabs, false otherwise
get isConfiguredToSyncTabs() {
if (!weaveXPCService.ready) {

Просмотреть файл

@ -0,0 +1,263 @@
/* 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";
/**
* @typedef {Object} UIState
* @property {string} status The Sync/FxA status, see STATUS_* constants.
* @property {string} [email] The FxA email configured to log-in with Sync.
* @property {string} [displayName] The user's FxA display name.
* @property {string} [avatarURL] The user's FxA avatar URL.
* @property {Date} [lastSync] The last sync time.
* @property {boolean} [syncing] Whether or not we are currently syncing.
*/
this.EXPORTED_SYMBOLS = ["UIState"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Weave",
"resource://services-sync/main.js");
const TOPICS = [
"weave:service:login:change",
"weave:service:login:error",
"weave:service:ready",
"weave:service:sync:start",
"weave:service:sync:finish",
"weave:service:sync:error",
"fxaccounts:onlogin", // Defined in FxAccountsCommon, pulling it is expensive.
"fxaccounts:onlogout",
"fxaccounts:profilechange",
];
const ON_UPDATE = "sync-ui-state:update"
const STATUS_NOT_CONFIGURED = "not_configured";
const STATUS_LOGIN_FAILED = "login_failed";
const STATUS_NOT_VERIFIED = "not_verified";
const STATUS_SIGNED_IN = "signed_in";
const DEFAULT_STATE = {
status: STATUS_NOT_CONFIGURED
};
const UIStateInternal = {
_initialized: false,
_state: null,
// We keep _syncing out of the state object because we can only track it
// using sync events and we can't determine it at any point in time.
_syncing: false,
get state() {
if (!this._state) {
return DEFAULT_STATE;
}
return Object.assign({}, this._state, { syncing: this._syncing });
},
isReady() {
if (!this._initialized) {
this.init();
return false;
}
return true;
},
init() {
this._initialized = true;
// Refresh the state in the background.
this.refreshState().catch(e => {
Cu.reportError(e);
});
},
// Used for testing.
reset() {
this._state = null;
this._syncing = false;
this._initialized = false;
},
observe(subject, topic, data) {
switch (topic) {
case "weave:service:sync:start":
this.toggleSyncActivity(true);
break;
case "weave:service:sync:finish":
case "weave:service:sync:error":
this.toggleSyncActivity(false);
break;
default:
this.refreshState().catch(e => {
Cu.reportError(e);
});
break;
}
},
// Builds a new state from scratch.
async refreshState() {
this._state = {};
await this._refreshFxAState();
this._setLastSyncTime(this._state); // We want this in case we change accounts.
this.notifyStateUpdated();
return this.state;
},
// Update the current state with the last sync time/currently syncing status.
toggleSyncActivity(syncing) {
this._syncing = syncing;
this._setLastSyncTime(this._state);
this.notifyStateUpdated();
},
notifyStateUpdated() {
Services.obs.notifyObservers(null, ON_UPDATE);
},
async _refreshFxAState() {
let userData = await this._getUserData();
this._populateWithUserData(this._state, userData);
if (this.state.status != STATUS_SIGNED_IN) {
return;
}
let profile = await this._getProfile();
if (!profile) {
return;
}
this._populateWithProfile(this._state, profile);
},
_populateWithUserData(state, userData) {
let status;
if (!userData) {
status = STATUS_NOT_CONFIGURED;
} else {
if (this._loginFailed()) {
status = STATUS_LOGIN_FAILED;
} else if (!userData.verified) {
status = STATUS_NOT_VERIFIED;
} else {
status = STATUS_SIGNED_IN;
}
state.email = userData.email;
}
state.status = status;
},
_populateWithProfile(state, profile) {
state.displayName = profile.displayName;
state.avatarURL = profile.avatar;
},
async _getUserData() {
try {
return await this.fxAccounts.getSignedInUser();
} catch (e) {
// This is most likely in tests, where 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.
Cu.reportError("Error updating FxA account info: " + e);
return null;
}
},
async _getProfile() {
try {
return await this.fxAccounts.getSignedInUserProfile();
} catch (e) {
// Not fetching the profile is sad but the FxA logs will already have noise.
return null;
}
},
_setLastSyncTime(state) {
if (state.status == UIState.STATUS_SIGNED_IN) {
try {
state.lastSync = new Date(Services.prefs.getCharPref("services.sync.lastSync", null));
} catch (_) {
state.lastSync = null;
}
}
},
_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(Ci.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;
},
set fxAccounts(mockFxAccounts) {
delete this.fxAccounts;
this.fxAccounts = mockFxAccounts;
}
};
XPCOMUtils.defineLazyModuleGetter(UIStateInternal, "fxAccounts",
"resource://gre/modules/FxAccounts.jsm");
for (let topic of TOPICS) {
Services.obs.addObserver(UIStateInternal, topic);
}
this.UIState = {
_internal: UIStateInternal,
ON_UPDATE,
STATUS_NOT_CONFIGURED,
STATUS_LOGIN_FAILED,
STATUS_NOT_VERIFIED,
STATUS_SIGNED_IN,
/**
* Returns true if the module has been initialized and the state set.
* If not, return false and trigger an init in the background.
*/
isReady() {
return this._internal.isReady();
},
/**
* @returns {UIState} The current Sync/FxA UI State.
*/
get() {
return this._internal.state;
},
/**
* Refresh the state. Used for testing, don't call this directly since
* UIState already listens to Sync/FxA notifications to determine if the state
* needs to be refreshed. ON_UPDATE will be fired once the state is refreshed.
*
* @returns {Promise<UIState>} Resolved once the state is refreshed.
*/
refresh() {
return this._internal.refreshState();
},
/**
* Reset the state of the whole module. Used for testing.
*/
reset() {
this._internal.reset();
}
};

Просмотреть файл

@ -11,7 +11,7 @@ var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/PromiseUtils.jsm");
Cu.import("resource://gre/modules/FxAccounts.jsm");
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-common/utils.js");
@ -170,7 +170,7 @@ this.BrowserIDManager.prototype = {
this._log.trace("initializeWithCurrentIdentity");
// Reset the world before we do anything async.
this.whenReadyToAuthenticate = Promise.defer();
this.whenReadyToAuthenticate = PromiseUtils.defer();
this.whenReadyToAuthenticate.promise.catch(err => {
this._log.error("Could not authenticate", err);
});
@ -559,7 +559,7 @@ this.BrowserIDManager.prototype = {
let getToken = assertion => {
log.debug("Getting a token");
let deferred = Promise.defer();
let deferred = PromiseUtils.defer();
let cb = function(err, token) {
if (err) {
return deferred.reject(err);

Просмотреть файл

@ -17,7 +17,6 @@ Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BookmarkValidator",
"resource://services-sync/bookmark_validator.js");
@ -438,14 +437,14 @@ BookmarksEngine.prototype = {
SyncEngine.prototype._syncStartup.call(this);
let cb = Async.makeSpinningCallback();
Task.spawn(function* () {
(async () => {
// For first-syncs, make a backup for the user to restore
if (this.lastSync == 0) {
this._log.debug("Bookmarks backup starting.");
yield PlacesBackups.create(null, true);
await PlacesBackups.create(null, true);
this._log.debug("Bookmarks backup done.");
}
}.bind(this)).then(
})().then(
cb, ex => {
// Failure to create a backup is somewhat bad, but probably not bad
// enough to prevent syncing of bookmarks - so just log the error and
@ -781,11 +780,11 @@ BookmarksStore.prototype = {
//
// See `PlacesSyncUtils.bookmarks.remove` for the implementation.
deletePending: Task.async(function* deletePending() {
let guidsToUpdate = yield PlacesSyncUtils.bookmarks.remove([...this._itemsToDelete]);
async deletePending() {
let guidsToUpdate = await PlacesSyncUtils.bookmarks.remove([...this._itemsToDelete]);
this.clearPendingDeletions();
return guidsToUpdate;
}),
},
clearPendingDeletions() {
this._itemsToDelete.clear();
@ -850,11 +849,11 @@ BookmarksStore.prototype = {
wipe: function BStore_wipe() {
this.clearPendingDeletions();
Async.promiseSpinningly(Task.spawn(function* () {
Async.promiseSpinningly((async () => {
// Save a backup before clearing out all bookmarks.
yield PlacesBackups.create(null, true);
yield PlacesSyncUtils.bookmarks.wipe();
}));
await PlacesBackups.create(null, true);
await PlacesSyncUtils.bookmarks.wipe();
})());
}
};
@ -927,8 +926,8 @@ BookmarksTracker.prototype = {
// Migrates tracker entries from the old JSON-based tracker to Places. This
// is called the first time we start tracking changes.
_migrateOldEntries: Task.async(function* () {
let existingIDs = yield Utils.jsonLoad("changes/" + this.file, this);
async _migrateOldEntries() {
let existingIDs = await Utils.jsonLoad("changes/" + this.file, this);
if (existingIDs === null) {
// If the tracker file doesn't exist, we don't need to migrate, even if
// the engine is enabled. It's possible we're upgrading before the first
@ -962,9 +961,9 @@ BookmarksTracker.prototype = {
modified: timestamp * 1000,
});
}
yield PlacesSyncUtils.bookmarks.migrateOldTrackerEntries(entries);
await PlacesSyncUtils.bookmarks.migrateOldTrackerEntries(entries);
return Utils.jsonRemove("changes/" + this.file, this);
}),
},
_needsMigration() {
return this.engine && this.engineIsEnabled() && this.engine.lastSync > 0;

Просмотреть файл

@ -36,6 +36,7 @@ EXTRA_JS_MODULES['services-sync'] += [
'modules/status.js',
'modules/SyncedTabs.jsm',
'modules/telemetry.js',
'modules/UIState.jsm',
'modules/util.js',
]

Просмотреть файл

@ -14,7 +14,6 @@ Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://testing-common/PlacesTestUtils.jsm");
Cu.import("resource:///modules/PlacesUIUtils.jsm");

Просмотреть файл

@ -0,0 +1,260 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// ================================================
// Load mocking/stubbing library, sinon
// docs: http://sinonjs.org/docs/
/* global sinon */
Cu.import("resource://gre/modules/Timer.jsm");
let window = {
document: {},
location: {},
setTimeout,
setInterval,
clearTimeout,
clearInterval,
};
let self = window;
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
loader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
// ================================================
Cu.import("resource://services-sync/UIState.jsm");
const UIStateInternal = UIState._internal;
add_task(async function test_isReady() {
UIState.reset();
let refreshState = sinon.spy(UIStateInternal, "refreshState");
// On the first call, returns false and triggers a refresh of the state
ok(!UIState.isReady());
ok(refreshState.calledOnce);
refreshState.reset();
// On subsequent calls, only return true
ok(UIState.isReady());
ok(!refreshState.called);
refreshState.restore();
});
add_task(async function test_refreshState_signedin() {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;
const now = new Date().toString();
Services.prefs.setCharPref("services.sync.lastSync", now);
UIStateInternal.syncing = false;
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve({ verified: true, email: "foo@bar.com" }),
getSignedInUserProfile: () => Promise.resolve({ displayName: "Foo Bar", avatar: "https://foo/bar" })
}
let state = await UIState.refresh();
equal(state.status, UIState.STATUS_SIGNED_IN);
equal(state.email, "foo@bar.com");
equal(state.displayName, "Foo Bar");
equal(state.avatarURL, "https://foo/bar");
equal(state.lastSync, now);
equal(state.syncing, false);
UIStateInternal.fxAccounts = fxAccountsOrig;
});
add_task(async function test_refreshState_signedin_profile_unavailable() {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;
const now = new Date().toString();
Services.prefs.setCharPref("services.sync.lastSync", now);
UIStateInternal.syncing = false;
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve({ verified: true, email: "foo@bar.com" }),
getSignedInUserProfile: () => Promise.reject(new Error("Profile unavailable"))
}
let state = await UIState.refresh();
equal(state.status, UIState.STATUS_SIGNED_IN);
equal(state.email, "foo@bar.com");
equal(state.displayName, undefined);
equal(state.avatarURL, undefined);
equal(state.lastSync, now);
equal(state.syncing, false);
UIStateInternal.fxAccounts = fxAccountsOrig;
});
add_task(async function test_refreshState_unconfigured() {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;
let getSignedInUserProfile = sinon.spy();
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve(null),
getSignedInUserProfile
}
let state = await UIState.refresh();
equal(state.status, UIState.STATUS_NOT_CONFIGURED);
equal(state.email, undefined);
equal(state.displayName, undefined);
equal(state.avatarURL, undefined);
equal(state.lastSync, undefined);
ok(!getSignedInUserProfile.called);
UIStateInternal.fxAccounts = fxAccountsOrig;
});
add_task(async function test_refreshState_unverified() {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;
let getSignedInUserProfile = sinon.spy();
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve({ verified: false, email: "foo@bar.com" }),
getSignedInUserProfile
}
let state = await UIState.refresh();
equal(state.status, UIState.STATUS_NOT_VERIFIED);
equal(state.email, "foo@bar.com");
equal(state.displayName, undefined);
equal(state.avatarURL, undefined);
equal(state.lastSync, undefined);
ok(!getSignedInUserProfile.called);
UIStateInternal.fxAccounts = fxAccountsOrig;
});
add_task(async function test_refreshState_loginFailed() {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;
let loginFailed = sinon.stub(UIStateInternal, "_loginFailed");
loginFailed.returns(true);
let getSignedInUserProfile = sinon.spy();
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve({ verified: true, email: "foo@bar.com" }),
getSignedInUserProfile
}
let state = await UIState.refresh();
equal(state.status, UIState.STATUS_LOGIN_FAILED);
equal(state.email, "foo@bar.com");
equal(state.displayName, undefined);
equal(state.avatarURL, undefined);
equal(state.lastSync, undefined);
ok(!getSignedInUserProfile.called);
loginFailed.restore();
UIStateInternal.fxAccounts = fxAccountsOrig;
});
add_task(async function test_observer_refreshState() {
let refreshState = sinon.spy(UIStateInternal, "refreshState");
let shouldRefresh = ["weave:service:login:change", "weave:service:login:error",
"weave:service:ready", "fxaccounts:onlogin",
"fxaccounts:onlogout", "fxaccounts:profilechange"];
for (let topic of shouldRefresh) {
let uiUpdateObserved = observeUIUpdate();
Services.obs.notifyObservers(null, topic);
await uiUpdateObserved;
ok(refreshState.calledOnce);
refreshState.reset();
}
refreshState.restore();
});
// Drive the UIState in a configured state.
async function configureUIState(syncing, lastSync = new Date()) {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;
UIStateInternal._syncing = syncing;
Services.prefs.setCharPref("services.sync.lastSync", lastSync.toString());
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve({ verified: true, email: "foo@bar.com" }),
getSignedInUserProfile: () => Promise.resolve({ displayName: "Foo Bar", avatar: "https://foo/bar" })
}
await UIState.refresh();
UIStateInternal.fxAccounts = fxAccountsOrig;
}
add_task(async function test_syncStarted() {
await configureUIState(false);
const oldState = Object.assign({}, UIState.get());
ok(!oldState.syncing);
let uiUpdateObserved = observeUIUpdate();
Services.obs.notifyObservers(null, "weave:service:sync:start");
await uiUpdateObserved;
const newState = Object.assign({}, UIState.get());
ok(newState.syncing);
});
add_task(async function test_syncFinished() {
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
await configureUIState(true, yesterday);
const oldState = Object.assign({}, UIState.get());
ok(oldState.syncing);
let uiUpdateObserved = observeUIUpdate();
Services.prefs.setCharPref("services.sync.lastSync", new Date().toString());
Services.obs.notifyObservers(null, "weave:service:sync:finish");
await uiUpdateObserved;
const newState = Object.assign({}, UIState.get());
ok(!newState.syncing);
ok(new Date(newState.lastSync) > new Date(oldState.lastSync));
});
add_task(async function test_syncError() {
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
await configureUIState(true, yesterday);
const oldState = Object.assign({}, UIState.get());
ok(oldState.syncing);
let uiUpdateObserved = observeUIUpdate();
Services.obs.notifyObservers(null, "weave:service:sync:error");
await uiUpdateObserved;
const newState = Object.assign({}, UIState.get());
ok(!newState.syncing);
deepEqual(newState.lastSync, oldState.lastSync);
});
function observeUIUpdate() {
return new Promise(resolve => {
let obs = (aSubject, aTopic, aData) => {
Services.obs.removeObserver(obs, aTopic);
const state = UIState.get();
resolve(state);
}
Services.obs.addObserver(obs, UIState.ON_UPDATE);
});
}

Просмотреть файл

@ -184,3 +184,5 @@ support-files = prefs_test_prefs_store.js
[test_telemetry.js]
requesttimeoutfactor = 4
[test_uistate.js]

5
servo/Cargo.lock сгенерированный
Просмотреть файл

@ -2799,6 +2799,7 @@ name = "style_tests"
version = "0.0.1"
dependencies = [
"app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
"html5ever 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3193,7 +3194,7 @@ dependencies = [
[[package]]
name = "webrender"
version = "0.36.0"
source = "git+https://github.com/servo/webrender#964df2fa00f330daf4f233669e37133f93113792"
source = "git+https://github.com/servo/webrender#e44a079266a181ddaae2fd27298cec1094b54b7e"
dependencies = [
"app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bincode 1.0.0-alpha6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3222,7 +3223,7 @@ dependencies = [
[[package]]
name = "webrender_traits"
version = "0.36.0"
source = "git+https://github.com/servo/webrender#964df2fa00f330daf4f233669e37133f93113792"
source = "git+https://github.com/servo/webrender#e44a079266a181ddaae2fd27298cec1094b54b7e"
dependencies = [
"app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bincode 1.0.0-alpha6 (registry+https://github.com/rust-lang/crates.io-index)",

Просмотреть файл

@ -35,7 +35,7 @@ impl ExtendableMessageEvent {
-> Root<ExtendableMessageEvent> {
let ev = box ExtendableMessageEvent {
event: ExtendableEvent::new_inherited(),
data: Heap::new(data.get()),
data: Heap::default(),
origin: origin,
lastEventId: lastEventId,
};
@ -44,6 +44,8 @@ impl ExtendableMessageEvent {
let event = ev.upcast::<Event>();
event.init_event(type_, bubbles, cancelable);
}
ev.data.set(data.get());
ev
}

Просмотреть файл

@ -20,7 +20,6 @@ use dom_struct::dom_struct;
use js::jsapi::{Heap, JSContext, JSObject};
use js::typedarray::{Float64Array, CreateWith};
use std::cell::Cell;
use std::ptr;
use webvr_traits::{WebVRGamepadData, WebVRGamepadHand, WebVRGamepadState};
#[dom_struct]
@ -47,7 +46,6 @@ impl Gamepad {
connected: bool,
timestamp: f64,
mapping_type: String,
axes: *mut JSObject,
buttons: &GamepadButtonList,
pose: Option<&VRPose>,
hand: WebVRGamepadHand,
@ -60,7 +58,7 @@ impl Gamepad {
connected: Cell::new(connected),
timestamp: Cell::new(timestamp),
mapping_type: mapping_type,
axes: Heap::new(axes),
axes: Heap::default(),
buttons: JS::from_ref(buttons),
pose: pose.map(JS::from_ref),
hand: hand,
@ -75,28 +73,24 @@ impl Gamepad {
state: &WebVRGamepadState) -> Root<Gamepad> {
let buttons = GamepadButtonList::new_from_vr(&global, &state.buttons);
let pose = VRPose::new(&global, &state.pose);
let cx = global.get_cx();
rooted!(in (cx) let mut axes = ptr::null_mut());
let gamepad = reflect_dom_object(box Gamepad::new_inherited(state.gamepad_id,
data.name.clone(),
index,
state.connected,
state.timestamp,
"".into(),
&buttons,
Some(&pose),
data.hand.clone(),
data.display_id),
global,
GamepadBinding::Wrap);
unsafe {
let _ = Float64Array::create(cx,
CreateWith::Slice(&state.axes),
axes.handle_mut());
let _ = Float64Array::create(global.get_cx(), CreateWith::Slice(&state.axes), gamepad.axes.handle_mut());
}
reflect_dom_object(box Gamepad::new_inherited(state.gamepad_id,
data.name.clone(),
index,
state.connected,
state.timestamp,
"".into(),
axes.get(),
&buttons,
Some(&pose),
data.hand.clone(),
data.display_id),
global,
GamepadBinding::Wrap)
gamepad
}
}

Просмотреть файл

@ -41,11 +41,14 @@ impl MessageEvent {
lastEventId: DOMString) -> Root<MessageEvent> {
let ev = box MessageEvent {
event: Event::new_inherited(),
data: Heap::new(data.get()),
data: Heap::default(),
origin: origin,
lastEventId: lastEventId,
};
reflect_dom_object(ev, global, MessageEventBinding::Wrap)
let ev = reflect_dom_object(ev, global, MessageEventBinding::Wrap);
ev.data.set(data.get());
ev
}
pub fn new(global: &GlobalScope, type_: Atom,

Просмотреть файл

@ -28,29 +28,29 @@ pub struct VREyeParameters {
unsafe_no_jsmanaged_fields!(WebVREyeParameters);
impl VREyeParameters {
#[allow(unsafe_code)]
#[allow(unrooted_must_root)]
fn new_inherited(parameters: WebVREyeParameters, global: &GlobalScope) -> VREyeParameters {
let fov = VRFieldOfView::new(&global, parameters.field_of_view.clone());
let result = VREyeParameters {
fn new_inherited(parameters: WebVREyeParameters, fov: &VRFieldOfView) -> VREyeParameters {
VREyeParameters {
reflector_: Reflector::new(),
parameters: DOMRefCell::new(parameters),
offset: Heap::default(),
fov: JS::from_ref(&*fov)
};
unsafe {
let _ = Float32Array::create(global.get_cx(),
CreateWith::Slice(&result.parameters.borrow().offset),
result.offset.handle_mut());
}
result
}
#[allow(unsafe_code)]
pub fn new(parameters: WebVREyeParameters, global: &GlobalScope) -> Root<VREyeParameters> {
reflect_dom_object(box VREyeParameters::new_inherited(parameters, global),
global,
VREyeParametersBinding::Wrap)
let fov = VRFieldOfView::new(&global, parameters.field_of_view.clone());
let eye_parameters = reflect_dom_object(box VREyeParameters::new_inherited(parameters, &fov),
global,
VREyeParametersBinding::Wrap);
unsafe {
let _ = Float32Array::create(global.get_cx(),
CreateWith::Slice(&eye_parameters.parameters.borrow().offset),
eye_parameters.offset.handle_mut());
}
eye_parameters
}
}

Просмотреть файл

@ -31,16 +31,8 @@ pub struct VRFrameData {
}
impl VRFrameData {
#[allow(unsafe_code)]
#[allow(unrooted_must_root)]
fn new(global: &GlobalScope) -> Root<VRFrameData> {
let matrix = [1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0f32];
let pose = VRPose::new(&global, &Default::default());
let framedata = VRFrameData {
fn new_inherited(pose: &VRPose) -> VRFrameData {
VRFrameData {
reflector_: Reflector::new(),
left_proj: Heap::default(),
left_view: Heap::default(),
@ -49,23 +41,25 @@ impl VRFrameData {
pose: JS::from_ref(&*pose),
timestamp: Cell::new(0.0),
first_timestamp: Cell::new(0.0)
};
let root = reflect_dom_object(box framedata,
global,
VRFrameDataBinding::Wrap);
unsafe {
let ref framedata = *root;
let _ = Float32Array::create(global.get_cx(), CreateWith::Slice(&matrix),
framedata.left_proj.handle_mut());
let _ = Float32Array::create(global.get_cx(), CreateWith::Slice(&matrix),
framedata.left_view.handle_mut());
let _ = Float32Array::create(global.get_cx(), CreateWith::Slice(&matrix),
framedata.right_proj.handle_mut());
let _ = Float32Array::create(global.get_cx(), CreateWith::Slice(&matrix),
framedata.right_view.handle_mut());
}
}
#[allow(unsafe_code)]
fn new(global: &GlobalScope) -> Root<VRFrameData> {
let matrix = [1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0f32];
let pose = VRPose::new(&global, &Default::default());
let root = reflect_dom_object(box VRFrameData::new_inherited(&pose),
global,
VRFrameDataBinding::Wrap);
let cx = global.get_cx();
create_typed_array(cx, &matrix, &root.left_proj);
create_typed_array(cx, &matrix, &root.left_view);
create_typed_array(cx, &matrix, &root.right_proj);
create_typed_array(cx, &matrix, &root.right_view);
root
}
@ -76,6 +70,13 @@ impl VRFrameData {
}
#[allow(unsafe_code)]
fn create_typed_array(cx: *mut JSContext, src: &[f32], dst: &Heap<*mut JSObject>) {
unsafe {
let _ = Float32Array::create(cx, CreateWith::Slice(src), dst.handle_mut());
}
}
impl VRFrameData {
#[allow(unsafe_code)]
pub fn update(&self, data: &WebVRFrameData) {

Просмотреть файл

@ -32,9 +32,7 @@ unsafe fn update_or_create_typed_array(cx: *mut JSContext,
match src {
Some(data) => {
if dst.get().is_null() {
rooted!(in (cx) let mut array = ptr::null_mut());
let _ = Float32Array::create(cx, CreateWith::Slice(data), array.handle_mut());
(*dst).set(array.get());
let _ = Float32Array::create(cx, CreateWith::Slice(data), dst.handle_mut());
} else {
typedarray!(in(cx) let array: Float32Array = dst.get());
if let Ok(mut array) = array {

Просмотреть файл

@ -26,28 +26,28 @@ pub struct VRStageParameters {
unsafe_no_jsmanaged_fields!(WebVRStageParameters);
impl VRStageParameters {
#[allow(unsafe_code)]
#[allow(unrooted_must_root)]
fn new_inherited(parameters: WebVRStageParameters, global: &GlobalScope) -> VRStageParameters {
let stage = VRStageParameters {
fn new_inherited(parameters: WebVRStageParameters) -> VRStageParameters {
VRStageParameters {
reflector_: Reflector::new(),
parameters: DOMRefCell::new(parameters),
transform: Heap::default()
};
// XXX unsound!
unsafe {
let _ = Float32Array::create(global.get_cx(),
CreateWith::Slice(&stage.parameters.borrow().sitting_to_standing_transform),
stage.transform.handle_mut());
}
stage
}
#[allow(unsafe_code)]
pub fn new(parameters: WebVRStageParameters, global: &GlobalScope) -> Root<VRStageParameters> {
reflect_dom_object(box VRStageParameters::new_inherited(parameters, global),
global,
VRStageParametersBinding::Wrap)
let cx = global.get_cx();
let stage_parameters = reflect_dom_object(box VRStageParameters::new_inherited(parameters),
global,
VRStageParametersBinding::Wrap);
unsafe {
let source = &stage_parameters.parameters.borrow().sitting_to_standing_transform;
let _ = Float32Array::create(cx,
CreateWith::Slice(source),
stage_parameters.transform.handle_mut());
}
stage_parameters
}
#[allow(unsafe_code)]

Просмотреть файл

@ -921,13 +921,13 @@ fn static_assert() {
need_clone=True) %>
% endfor
pub fn set_border_image_source(&mut self, v: longhands::border_image_source::computed_value::T) {
pub fn set_border_image_source(&mut self, image: longhands::border_image_source::computed_value::T) {
unsafe {
// Prevent leaking of the last elements we did set
Gecko_SetNullImageValue(&mut self.gecko.mBorderImageSource);
}
if let Some(image) = v.0 {
if let Some(image) = image.0 {
self.gecko.mBorderImageSource.set(image, &mut false)
}
}
@ -1296,12 +1296,47 @@ fn static_assert() {
skip_font_longhands = """font-family font-size font-size-adjust font-weight
font-synthesis -x-lang font-variant-alternates
font-variant-east-asian font-variant-ligatures
font-variant-numeric font-language-override"""
font-variant-numeric font-language-override
font-feature-settings"""
%>
<%self:impl_trait style_struct_name="Font"
skip_longhands="${skip_font_longhands}"
skip_additionals="*">
pub fn set_font_feature_settings(&mut self, v: longhands::font_feature_settings::computed_value::T) {
use properties::longhands::font_feature_settings::computed_value::T;
let current_settings = &mut self.gecko.mFont.fontFeatureSettings;
current_settings.clear_pod();
match v {
T::Normal => unsafe { current_settings.set_len_pod(0) },
T::Tag(feature_settings) => {
unsafe { current_settings.set_len_pod(feature_settings.len() as u32) };
for (current, feature) in current_settings.iter_mut().zip(feature_settings) {
current.mTag = feature.tag;
current.mValue = feature.value;
}
}
};
}
pub fn copy_font_feature_settings_from(&mut self, other: &Self ) {
let current_settings = &mut self.gecko.mFont.fontFeatureSettings;
let feature_settings = &other.gecko.mFont.fontFeatureSettings;
let settings_length = feature_settings.len() as u32;
current_settings.clear_pod();
unsafe { current_settings.set_len_pod(settings_length) };
for (current, feature) in current_settings.iter_mut().zip(feature_settings.iter()) {
current.mTag = feature.mTag;
current.mValue = feature.mValue;
}
}
pub fn set_font_family(&mut self, v: longhands::font_family::computed_value::T) {
use properties::longhands::font_family::computed_value::FontFamily;
use gecko_bindings::structs::FontFamilyType;
@ -2756,18 +2791,9 @@ fn static_assert() {
for (image, geckoimage) in images.0.into_iter().zip(self.gecko.${image_layers_field}
.mLayers.iter_mut()) {
% if shorthand == "background":
if let Some(image) = image.0 {
geckoimage.mImage.set(image, cacheable)
}
% else:
use properties::longhands::mask_image::single_value::computed_value::T;
match image {
T::Image(image) => geckoimage.mImage.set(image, cacheable),
_ => ()
}
% endif
if let Some(image) = image.0 {
geckoimage.mImage.set(image, cacheable)
}
}
}

Просмотреть файл

@ -309,7 +309,7 @@
computed,
inherited_style.get_font());
% else:
% if property.has_uncacheable_values:
% if property.has_uncacheable_values == "True":
context.mutate_style().mutate_${data.current_style_struct.name_lower}()
.set_${property.ident}(computed, cacheable ${maybe_wm});
% else:

Просмотреть файл

@ -12,82 +12,13 @@ ${helpers.predefined_type("background-color", "CSSColor",
spec="https://drafts.csswg.org/css-backgrounds/#background-color",
animation_value_type="IntermediateColor", complex_color=True)}
<%helpers:vector_longhand name="background-image" animation_value_type="none"
spec="https://drafts.csswg.org/css-backgrounds/#the-background-image"
has_uncacheable_values="${product == 'gecko'}">
use std::fmt;
use style_traits::ToCss;
use values::HasViewportPercentage;
use values::specified::Image;
pub mod computed_value {
use values::computed;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct T(pub Option<computed::Image>);
}
impl ToCss for computed_value::T {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match self.0 {
None => dest.write_str("none"),
Some(ref image) => image.to_css(dest),
}
}
}
no_viewport_percentage!(SpecifiedValue);
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct SpecifiedValue(pub Option<Image>);
impl ToCss for SpecifiedValue {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
SpecifiedValue(Some(ref image)) => image.to_css(dest),
SpecifiedValue(None) => dest.write_str("none"),
}
}
}
#[inline]
pub fn get_initial_value() -> computed_value::T {
computed_value::T(None)
}
#[inline]
pub fn get_initial_specified_value() -> SpecifiedValue {
SpecifiedValue(None)
}
pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
if input.try(|input| input.expect_ident_matching("none")).is_ok() {
Ok(SpecifiedValue(None))
} else {
Ok(SpecifiedValue(Some(try!(Image::parse(context, input)))))
}
}
impl ToComputedValue for SpecifiedValue {
type ComputedValue = computed_value::T;
#[inline]
fn to_computed_value(&self, context: &Context) -> computed_value::T {
match *self {
SpecifiedValue(None) => computed_value::T(None),
SpecifiedValue(Some(ref image)) =>
computed_value::T(Some(image.to_computed_value(context))),
}
}
#[inline]
fn from_computed_value(computed: &computed_value::T) -> Self {
match *computed {
computed_value::T(None) => SpecifiedValue(None),
computed_value::T(Some(ref image)) =>
SpecifiedValue(Some(ToComputedValue::from_computed_value(image))),
}
}
}
</%helpers:vector_longhand>
${helpers.predefined_type("background-image", "LayerImage",
initial_value="computed_value::T(None)",
initial_specified_value="SpecifiedValue(None)",
spec="https://drafts.csswg.org/css-backgrounds/#the-background-image",
vector="True",
animation_value_type="none",
has_uncacheable_values="True" if product == "gecko" else "False")}
<%helpers:predefined_type name="background-position-x" type="position::HorizontalPosition"
initial_value="computed::position::HorizontalPosition::zero()"

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше