Merge mozilla-central to mozilla-inbound

This commit is contained in:
Marco Bonardo 2011-11-03 10:46:56 +01:00
Родитель 50ed6f94a5 e3a347fe0e
Коммит 66602c3f2f
95 изменённых файлов: 2280 добавлений и 2427 удалений

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

@ -369,6 +369,14 @@ body[dir=rtl] #restorePreviousSession::before {
bottom: 2%;
}
#syncLinksContainer {
padding-top: 1em;
}
.sync-link {
padding: 1em;
}
@media all and (max-height: 370px) {
#bottomSection {
visibility: hidden;

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

@ -102,6 +102,10 @@
<div id="aboutMozilla">
<a href="http://www.mozilla.com/about/">&abouthome.aboutMozilla;</a>
</div>
<div id="syncLinksContainer">
<a href="javascript:void(0);" class="sync-link" id="setupSyncLink">&abouthome.syncSetup.label;</a>
<a href="javascript:void(0);" class="sync-link" id="pairDeviceLink">&abouthome.pairDevice.label;</a>
</div>
</div>
</body>
</html>

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

@ -211,6 +211,24 @@ let gSyncUI = {
this.clearError(title);
},
// Set visibility of "Setup Sync" link
showSetupSyncAboutHome: function SUI_showSetupSyncAboutHome(toShow) {
let browsers = gBrowser.browsers;
for (let i = 0; i < browsers.length; i++) {
let b = browsers[i];
if ("about:home" == b.currentURI.spec) {
b.contentDocument.getElementById("setupSyncLink").hidden = !toShow;
}
}
},
onSetupComplete: function SUI_onSetupComplete() {
// Remove "setup sync" link in about:home if it is open.
this.showSetupSyncAboutHome(false);
onLoginFinish();
},
onLoginError: function SUI_onLoginError() {
// if login fails, any other notifications are essentially moot
Weave.Notifications.removeAll();
@ -255,6 +273,8 @@ let gSyncUI = {
onStartOver: function SUI_onStartOver() {
this.clearError();
// Make "setup sync" link visible in about:home if it is open.
this.showSetupSyncAboutHome(true);
},
onQuotaNotice: function onQuotaNotice(subject, data) {
@ -291,16 +311,40 @@ let gSyncUI = {
//XXXzpao should be part of syncCommon.js - which we might want to make a module...
// To be fixed in a followup (bug 583366)
openSetup: function SUI_openSetup() {
/**
* Invoke the Sync setup wizard.
*
* @param wizardType
* Indicates type of wizard to launch:
* null -- regular set up wizard
* "pair" -- pair a device first
* "reset" -- reset sync
*/
openSetup: function SUI_openSetup(wizardType) {
let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
if (win)
win.focus();
else {
window.openDialog("chrome://browser/content/syncSetup.xul",
"weaveSetup", "centerscreen,chrome,resizable=no");
"weaveSetup", "centerscreen,chrome,resizable=no",
wizardType);
}
},
openAddDevice: function () {
if (!Weave.Utils.ensureMPUnlocked())
return;
let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
if (win)
win.focus();
else
window.openDialog("chrome://browser/content/syncAddDevice.xul",
"syncAddDevice", "centerscreen,chrome,resizable=no");
},
openQuotaDialog: function SUI_openQuotaDialog() {
let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
if (win)
@ -462,7 +506,7 @@ let gSyncUI = {
this.onQuotaNotice();
break;
case "weave:service:setup-complete":
this.onLoginFinish();
this.onSetupComplete();
break;
case "weave:service:login:start":
this.onActivityStart();

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

@ -2675,6 +2675,10 @@ function BrowserOnAboutPageLoad(document) {
getService(Components.interfaces.nsISessionStore);
if (!ss.canRestoreLastSession)
document.getElementById("sessionRestoreContainer").hidden = true;
// Sync-related links
if (Services.prefs.prefHasUserValue("services.sync.username")) {
document.getElementById("setupSyncLink").hidden = true;
}
}
}
@ -2683,7 +2687,9 @@ function BrowserOnAboutPageLoad(document) {
*/
function BrowserOnClick(event) {
// Don't trust synthetic events
if (!event.isTrusted || event.target.localName != "button")
if (!event.isTrusted ||
(event.target.localName != "button" &&
event.target.className != "sync-link"))
return;
var ot = event.originalTarget;
@ -2813,6 +2819,16 @@ function BrowserOnClick(event) {
ss.restoreLastSession();
errorDoc.getElementById("sessionRestoreContainer").hidden = true;
}
else if (ot == errorDoc.getElementById("pairDeviceLink")) {
if (Services.prefs.prefHasUserValue("services.sync.username")) {
gSyncUI.openAddDevice();
} else {
gSyncUI.openSetup("pair");
}
}
else if (ot == errorDoc.getElementById("setupSyncLink")) {
gSyncUI.openSetup(null);
}
}
}

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

@ -44,12 +44,14 @@ let gProgressBar;
let gCounter = 0;
function onLoad(event) {
Services.obs.addObserver(increaseProgressBar, "weave:engine:sync:finish", false);
Services.obs.addObserver(increaseProgressBar, "weave:engine:sync:error", false);
Services.obs.addObserver(onEngineSync, "weave:engine:sync:finish", false);
Services.obs.addObserver(onEngineSync, "weave:engine:sync:error", false);
Services.obs.addObserver(onServiceSync, "weave:service:sync:finish", false);
Services.obs.addObserver(onServiceSync, "weave:service:sync:error", false);
gProgressBar = document.getElementById('uploadProgressBar');
if (Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
gProgressBar.max = Weave.Engines.getEnabled().length;
gProgressBar.style.display = "inline";
}
else {
@ -58,15 +60,46 @@ function onLoad(event) {
}
function onUnload(event) {
Services.obs.removeObserver(increaseProgressBar, "weave:engine:sync:finish");
Services.obs.removeObserver(increaseProgressBar, "weave:engine:sync:error");
cleanUpObservers();
}
function increaseProgressBar(){
function cleanUpObservers() {
try {
Services.obs.removeObserver(onEngineSync, "weave:engine:sync:finish", false);
Services.obs.removeObserver(onEngineSync, "weave:engine:sync:error", false);
Services.obs.removeObserver(onServiceSync, "weave:service:sync:finish", false);
Services.obs.removeObserver(onServiceSync, "weave:service:sync:error", false);
}
catch (e) {
// may be double called by unload & exit. Ignore.
}
}
function onEngineSync(subject, topic, data) {
// The Clients engine syncs first. At this point we don't necessarily know
// yet how many engines will be enabled, so we'll ignore the Clients engine
// and evaluate how many engines are enabled when the first "real" engine
// syncs.
if (data == "clients") {
return;
}
if (!gCounter &&
Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
gProgressBar.max = Weave.Engines.getEnabled().length;
}
gCounter += 1;
gProgressBar.setAttribute("value", gCounter);
}
function onServiceSync(subject, topic, data) {
// To address the case where 0 engines are synced, we will fill the
// progress bar so the user knows that the sync has finished.
gProgressBar.setAttribute("value", gProgressBar.max);
cleanUpObservers();
}
function closeTab() {
window.close();
}

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

@ -10,6 +10,9 @@ registerCleanupFunction(function() {
try {
Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
} catch (ex) {}
try {
Services.prefs.clearUserPref("services.sync.username");
} catch (ex) {}
});
let gTests = [
@ -114,6 +117,116 @@ let gTests = [
}
},
{
desc: "Check sync links visibility before and after Sync setup",
setup: function ()
{
try {
Services.prefs.clearUserPref("services.sync.username");
} catch (ex) {}
Services.obs.notifyObservers(null, "weave:service:ready", null);
},
run: function ()
{
let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
let pairLink = doc.getElementById("pairDeviceLink");
let setupLink = doc.getElementById("setupSyncLink");
ok(pairLink, "Found 'Pair Device' link");
ok(setupLink, "Found 'Set Up Sync' link");
ok(!pairLink.hidden, "'Pair' link is visible before setup");
ok(!setupLink.hidden, "'Set Up' link is visible before setup");
Services.obs.notifyObservers(null, "weave:service:setup-complete", null);
executeSoon(function () {
setupLink = doc.getElementById("setupSyncLink");
ok(setupLink.hidden, "'Set Up' link is hidden after setup");
ok(!pairLink.hidden, "'Pair' link is visible after setup");
executeSoon(runNextTest);
});
}
},
{
desc: "Check sync links visibility before and after Sync unlink",
setup: function ()
{
Services.prefs.setCharPref("services.sync.username", "someuser@domain.com");
Services.obs.notifyObservers(null, "weave:service:ready", null);
},
run: function ()
{
let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
let pairLink = doc.getElementById("pairDeviceLink");
let setupLink = doc.getElementById("setupSyncLink");
ok(!pairLink.hidden, "'Pair' link is visible before unlink");
ok(setupLink.hidden, "'Set Up' link is hidden before unlink");
Services.obs.notifyObservers(null, "weave:service:start-over", null);
executeSoon(function () {
setupLink = doc.getElementById("setupSyncLink");
ok(!setupLink.hidden, "'Set Up' link is visible after unlink");
ok(!pairLink.hidden, "'Pair' link is visible after unlink");
executeSoon(runNextTest);
});
}
},
{
desc: "Check Pair Device link opens correct dialog with Sync account ",
setup: function ()
{
Services.prefs.setCharPref("services.sync.username", "someuser@domain.com");
Services.obs.notifyObservers(null, "weave:service:ready", null);
},
run: function ()
{
expectDialogWindow("Sync:AddDevice");
let browser = gBrowser.selectedTab.linkedBrowser;
let button = browser.contentDocument.getElementById("pairDeviceLink");
EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow);
}
},
{
desc: "Check Pair Device link opens correct dialog without Sync account",
setup: function ()
{
try {
Services.prefs.clearUserPref("services.sync.username");
} catch (ex) {}
Services.obs.notifyObservers(null, "weave:service:ready", null);
},
run: function ()
{
expectDialogWindow("Weave:AccountSetup");
let browser = gBrowser.selectedTab.linkedBrowser;
let button = browser.contentDocument.getElementById("pairDeviceLink");
EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow);
}
},
{
desc: "Check Sync Setup link opens correct dialog (without Sync account)",
setup: function ()
{
try {
Services.prefs.clearUserPref("services.sync.username");
} catch (ex) {}
Services.obs.notifyObservers(null, "weave:service:ready", null);
},
run: function ()
{
expectDialogWindow("Weave:AccountSetup");
let browser = gBrowser.selectedTab.linkedBrowser;
let button = browser.contentDocument.getElementById("setupSyncLink");
EventUtils.sendMouseEvent({type: "click"}, button, browser.contentWindow);
}
},
];
function test()
@ -159,6 +272,22 @@ function runNextTest()
}
}
function expectDialogWindow(expectedDialog) {
Services.ww.registerNotification(function onWindow(subject, topic) {
let win = subject.QueryInterface(Components.interfaces.nsIDOMWindow);
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad, false);
let wintype = win.document.documentElement.getAttribute("windowtype");
if (topic == "domwindowopened" && wintype == expectedDialog) {
Services.ww.unregisterNotification(onWindow);
// Clean up dialog.
win.close();
executeSoon(runNextTest);
}
}, false);
});
}
function getStorage()
{
let aboutHomeURI = Services.io.newURI("moz-safe-about:home", null, null);

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

@ -18,3 +18,6 @@
text in <a/> will be linked to the featured add-ons on addons.mozilla.org
-->
<!ENTITY abouthome.defaultSnippet2.v1 "It's easy to customize your Firefox exactly the way you want it. <a>Choose from thousands of add-ons</a>.">
<!ENTITY abouthome.syncSetup.label "Set Up Sync">
<!ENTITY abouthome.pairDevice.label "Pair a Device">

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

@ -42,7 +42,6 @@ interface nsIChannel;
interface nsIStreamListener;
interface nsIURI;
interface nsIDocument;
interface nsIFrame;
/**
* This interface represents a content node that loads images. The interface
@ -66,7 +65,7 @@ interface nsIFrame;
* sufficient, when combined with the imageBlockingStatus information.)
*/
[scriptable, uuid(f7debb84-2854-4731-a57b-1bd752ad71f8)]
[scriptable, uuid(95c74255-df9a-4060-b5a0-0d111fcafe08)]
interface nsIImageLoadingContent : imgIDecoderObserver
{
/**
@ -129,18 +128,6 @@ interface nsIImageLoadingContent : imgIDecoderObserver
*/
imgIRequest getRequest(in long aRequestType);
/**
* Used to notify the image loading content node that a frame has been
* created.
*/
[notxpcom] void frameCreated(in nsIFrame aFrame);
/**
* Used to notify the image loading content node that a frame has been
* destroyed.
*/
[notxpcom] void frameDestroyed(in nsIFrame aFrame);
/**
* Used to find out what type of request one is dealing with (eg
* which request got passed through to the imgIDecoderObserver

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

@ -3225,7 +3225,6 @@ nsDocument::DeleteShell()
if (IsEventHandlingEnabled()) {
RevokeAnimationFrameNotifications();
}
mPresShell = nsnull;
}

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

@ -72,7 +72,6 @@
#include "nsIDOMNode.h"
#include "nsContentUtils.h"
#include "nsLayoutUtils.h"
#include "nsIContentPolicy.h"
#include "nsContentPolicyUtils.h"
#include "nsEventDispatcher.h"
@ -116,9 +115,7 @@ nsImageLoadingContent::nsImageLoadingContent()
mNewRequestsWillNeedAnimationReset(false),
mPendingRequestNeedsResetAnimation(false),
mCurrentRequestNeedsResetAnimation(false),
mStateChangerDepth(0),
mCurrentRequestRegistered(false),
mPendingRequestRegistered(false)
mStateChangerDepth(0)
{
if (!nsContentUtils::GetImgLoader()) {
mLoadingEnabled = false;
@ -199,16 +196,7 @@ nsImageLoadingContent::OnStartDecode(imgIRequest* aRequest)
SetBlockingOnload(true);
}
}
bool* requestFlag = GetRegisteredFlagForRequest(aRequest);
if (requestFlag) {
nsLayoutUtils::RegisterImageRequest(GetFramePresContext(), aRequest,
requestFlag);
} else {
NS_ERROR("Starting to decode an image other than our current/pending "
"request?");
}
LOOP_OVER_OBSERVERS(OnStartDecode(aRequest));
return NS_OK;
}
@ -341,13 +329,6 @@ nsImageLoadingContent::OnStopDecode(imgIRequest* aRequest,
nsIPresShell* shell = doc ? doc->GetShell() : nsnull;
if (shell) {
// Make sure that our image requests are deregistered from the refresh
// driver if they aren't animated. Note that this must be mCurrentRequest,
// or we would have aborted up above.
nsLayoutUtils::DeregisterImageRequestIfNotAnimated(GetFramePresContext(),
mCurrentRequest,
&mCurrentRequestRegistered);
// We need to figure out whether to kick off decoding
bool doRequestDecode = false;
@ -520,44 +501,6 @@ nsImageLoadingContent::GetRequest(PRInt32 aRequestType,
return NS_OK;
}
NS_IMETHODIMP_(void)
nsImageLoadingContent::FrameCreated(nsIFrame* aFrame)
{
NS_ASSERTION(aFrame, "aFrame is null");
// We need to make sure that our image request is registered.
nsPresContext* presContext = aFrame->PresContext();
if (mCurrentRequest) {
nsLayoutUtils::RegisterImageRequest(presContext, mCurrentRequest,
&mCurrentRequestRegistered);
nsLayoutUtils::DeregisterImageRequestIfNotAnimated(presContext,
mCurrentRequest,
&mCurrentRequestRegistered);
} else if (mPendingRequest) {
// We don't need to do the same check for animation, because this will be
// done when decoding is finished.
nsLayoutUtils::RegisterImageRequest(presContext, mPendingRequest,
&mPendingRequestRegistered);
}
}
NS_IMETHODIMP_(void)
nsImageLoadingContent::FrameDestroyed(nsIFrame* aFrame)
{
NS_ASSERTION(aFrame, "aFrame is null");
// We need to make sure that our image request is deregistered.
if (mCurrentRequest) {
nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(),
mCurrentRequest,
&mCurrentRequestRegistered);
} else if (mPendingRequest) {
nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(),
mPendingRequest,
&mPendingRequestRegistered);
}
}
NS_IMETHODIMP
nsImageLoadingContent::GetRequestType(imgIRequest* aRequest,
@ -924,23 +867,6 @@ nsImageLoadingContent::GetOurDocument()
return thisContent->OwnerDoc();
}
nsIFrame*
nsImageLoadingContent::GetOurPrimaryFrame()
{
nsCOMPtr<nsIContent> thisContent = do_QueryInterface(this);
return thisContent->GetPrimaryFrame();
}
nsPresContext* nsImageLoadingContent::GetFramePresContext()
{
nsIFrame* frame = GetOurPrimaryFrame();
if (!frame) {
return nsnull;
}
return frame->PresContext();
}
nsresult
nsImageLoadingContent::StringToURI(const nsAString& aSpec,
nsIDocument* aDocument,
@ -1060,16 +986,6 @@ nsImageLoadingContent::ClearCurrentRequest(nsresult aReason)
NS_ABORT_IF_FALSE(!mCurrentURI,
"Shouldn't have both mCurrentRequest and mCurrentURI!");
// Deregister this image from the refresh driver so it no longer receives
// notifications.
nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mCurrentRequest,
&mCurrentRequestRegistered);
// Deregister this image from the refresh driver so it no longer receives
// notifications.
nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mCurrentRequest,
&mCurrentRequestRegistered);
// Clean up the request.
UntrackImage(mCurrentRequest);
mCurrentRequest->CancelAndForgetObserver(aReason);
@ -1093,29 +1009,12 @@ nsImageLoadingContent::ClearPendingRequest(nsresult aReason)
nsCxPusher pusher;
pusher.PushNull();
// Deregister this image from the refresh driver so it no longer receives
// notifications.
nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mPendingRequest,
&mPendingRequestRegistered);
UntrackImage(mPendingRequest);
mPendingRequest->CancelAndForgetObserver(aReason);
mPendingRequest = nsnull;
mPendingRequestNeedsResetAnimation = false;
}
bool*
nsImageLoadingContent::GetRegisteredFlagForRequest(imgIRequest* aRequest)
{
if (aRequest == mCurrentRequest) {
return &mCurrentRequestRegistered;
} else if (aRequest == mPendingRequest) {
return &mPendingRequestRegistered;
} else {
return nsnull;
}
}
bool
nsImageLoadingContent::HaveSize(imgIRequest *aImage)
{

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

@ -146,24 +146,6 @@ protected:
*/
nsIDocument* GetOurDocument();
/**
* Helper function to get the frame associated with this content. Not named
* GetPrimaryFrame to prevent ambiguous method names in subclasses.
*
* @return The frame which we belong to, or nsnull if it doesn't exist.
*/
nsIFrame* GetOurPrimaryFrame();
/**
* Helper function to get the PresContext associated with this content's
* frame. Not named GetPresContext to prevent ambiguous method names in
* subclasses.
*
* @return The nsPresContext associated with our frame, or nsnull if either
* the frame doesn't exist, or the frame's prescontext doesn't exist.
*/
nsPresContext* GetFramePresContext();
/**
* CancelImageRequests is called by subclasses when they want to
* cancel all image requests (for example when the subclass is
@ -320,16 +302,6 @@ protected:
void ClearCurrentRequest(nsresult aReason);
void ClearPendingRequest(nsresult aReason);
/**
* Retrieve a pointer to the 'registered with the refresh driver' flag for
* which a particular image request corresponds.
*
* @returns A pointer to the boolean flag for a given image request, or
* |nsnull| if the request is not either |mPendingRequest| or
* |mCurrentRequest|.
*/
bool* GetRegisteredFlagForRequest(imgIRequest* aRequest);
/**
* Static helper method to tell us if we have the size of a request. The
* image may be null.
@ -410,11 +382,6 @@ private:
/* The number of nested AutoStateChangers currently tracking our state. */
PRUint8 mStateChangerDepth;
// Flags to indicate whether each of the current and pending requests are
// registered with the refresh driver.
bool mCurrentRequestRegistered;
bool mPendingRequestRegistered;
};
#endif // nsImageLoadingContent_h__

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

@ -60,6 +60,7 @@
#include "mozilla/ipc/XPCShellEnvironment.h"
#include "mozilla/jsipc/PContextWrapperChild.h"
#include "mozilla/net/NeckoChild.h"
#include "mozilla/Preferences.h"
#if defined(MOZ_SYDNEYAUDIO)
#include "nsAudioStream.h"
@ -68,7 +69,6 @@
#include "nsIObserverService.h"
#include "nsTObserverArray.h"
#include "nsIObserver.h"
#include "nsIPrefService.h"
#include "nsServiceManagerUtils.h"
#include "nsXULAppAPI.h"
#include "nsWeakReference.h"
@ -638,24 +638,14 @@ ContentChild::AddRemoteAlertObserver(const nsString& aData,
bool
ContentChild::RecvPreferenceUpdate(const PrefTuple& aPref)
{
nsCOMPtr<nsIPrefServiceInternal> prefs = do_GetService("@mozilla.org/preferences-service;1");
if (!prefs)
return false;
prefs->SetPreference(&aPref);
Preferences::SetPreference(&aPref);
return true;
}
bool
ContentChild::RecvClearUserPreference(const nsCString& aPrefName)
{
nsCOMPtr<nsIPrefServiceInternal> prefs = do_GetService("@mozilla.org/preferences-service;1");
if (!prefs)
return false;
prefs->ClearContentPref(aPrefName);
Preferences::ClearContentPref(aPrefName.get());
return true;
}

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

@ -51,7 +51,6 @@
#include "nsIDOMWindow.h"
#include "nsIPrefBranch.h"
#include "nsIPrefBranch2.h"
#include "nsIPrefService.h"
#include "nsIPrefLocalizedString.h"
#include "nsIObserverService.h"
#include "nsContentUtils.h"
@ -461,7 +460,7 @@ bool
ContentParent::RecvReadPrefsArray(InfallibleTArray<PrefTuple> *prefs)
{
EnsurePrefService();
mPrefService->MirrorPreferences(prefs);
Preferences::MirrorPreferences(prefs);
return true;
}
@ -707,33 +706,9 @@ ContentParent::Observe(nsISupports* aSubject,
// We know prefs are ASCII here.
NS_LossyConvertUTF16toASCII strData(aData);
nsCOMPtr<nsIPrefServiceInternal> prefService =
do_GetService("@mozilla.org/preferences-service;1");
bool prefNeedUpdate;
prefService->PrefHasUserValue(strData, &prefNeedUpdate);
// If the pref does not have a user value, check if it exist on the
// default branch or not
if (!prefNeedUpdate) {
nsCOMPtr<nsIPrefBranch> defaultBranch;
nsCOMPtr<nsIPrefService> prefsService = do_QueryInterface(prefService);
prefsService->GetDefaultBranch(nsnull, getter_AddRefs(defaultBranch));
PRInt32 prefType = nsIPrefBranch::PREF_INVALID;
defaultBranch->GetPrefType(strData.get(), &prefType);
prefNeedUpdate = (prefType != nsIPrefBranch::PREF_INVALID);
}
PrefTuple pref;
bool prefNeedUpdate = Preferences::MirrorPreference(strData.get(), &pref);
if (prefNeedUpdate) {
// Pref was created, or previously existed and its value
// changed.
PrefTuple pref;
#ifdef DEBUG
nsresult rv =
#endif
prefService->MirrorPreference(strData, &pref);
NS_ASSERTION(NS_SUCCEEDED(rv), "Pref has value but can't mirror?");
if (!SendPreferenceUpdate(pref)) {
return NS_ERROR_NOT_AVAILABLE;
}

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

@ -241,7 +241,7 @@ private:
nsCOMArray<nsIMemoryReporter> mMemoryReporters;
bool mIsAlive;
nsCOMPtr<nsIPrefServiceInternal> mPrefService;
nsCOMPtr<nsIPrefService> mPrefService;
bool mSendPermissionUpdates;

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

@ -56,10 +56,6 @@ interface imgIDecoderObserver;
#include "limits.h"
class nsIFrame;
namespace mozilla {
class TimeStamp;
}
%}
[ptr] native gfxImageSurface(gfxImageSurface);
@ -72,7 +68,6 @@ native gfxGraphicsFilter(gfxPattern::GraphicsFilter);
[ref] native nsIntRect(nsIntRect);
[ref] native nsIntSize(nsIntSize);
[ptr] native nsIFrame(nsIFrame);
[ref] native TimeStamp(mozilla::TimeStamp);
/**
* imgIContainer is the interface that represents an image. It allows
@ -266,13 +261,6 @@ interface imgIContainer : nsISupports
*/
void unlockImage();
/**
* Indicates that this imgIContainer has been triggered to update
* its internal animation state. Likely this should only be called
* from within nsImageFrame or objects of similar type.
*/
[notxpcom] void requestRefresh([const] in TimeStamp aTime);
/**
* Animation mode Constants
* 0 = normal

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

@ -173,10 +173,10 @@ namespace mozilla {
namespace imagelib {
#ifndef DEBUG
NS_IMPL_ISUPPORTS3(RasterImage, imgIContainer, nsIProperties,
NS_IMPL_ISUPPORTS4(RasterImage, imgIContainer, nsITimerCallback, nsIProperties,
nsISupportsWeakReference)
#else
NS_IMPL_ISUPPORTS4(RasterImage, imgIContainer, nsIProperties,
NS_IMPL_ISUPPORTS5(RasterImage, imgIContainer, nsITimerCallback, nsIProperties,
imgIContainerDebug, nsISupportsWeakReference)
#endif
@ -313,169 +313,6 @@ RasterImage::Init(imgIDecoderObserver *aObserver,
return NS_OK;
}
bool
RasterImage::AdvanceFrame(TimeStamp aTime, nsIntRect* aDirtyRect)
{
NS_ASSERTION(aTime <= TimeStamp::Now(),
"Given time appears to be in the future");
imgFrame* nextFrame = nsnull;
PRUint32 currentFrameIndex = mAnim->currentAnimationFrameIndex;
PRUint32 nextFrameIndex = mAnim->currentAnimationFrameIndex + 1;
PRUint32 timeout = 0;
// Figure out if we have the next full frame. This is more complicated than
// just checking for mFrames.Length() because decoders append their frames
// before they're filled in.
NS_ABORT_IF_FALSE(mDecoder || nextFrameIndex <= mFrames.Length(),
"How did we get 2 indices too far by incrementing?");
// If we don't have a decoder, we know we've got everything we're going to
// get. If we do, we only display fully-downloaded frames; everything else
// gets delayed.
bool haveFullNextFrame = !mDecoder ||
nextFrameIndex < mDecoder->GetCompleteFrameCount();
// If we're done decoding the next frame, go ahead and display it now and
// reinit with the next frame's delay time.
if (haveFullNextFrame) {
if (mFrames.Length() == nextFrameIndex) {
// End of Animation, unless we are looping forever
// If animation mode is "loop once", it's time to stop animating
if (mAnimationMode == kLoopOnceAnimMode || mLoopCount == 0) {
mAnimationFinished = PR_TRUE;
EvaluateAnimation();
}
// We may have used compositingFrame to build a frame, and then copied
// it back into mFrames[..]. If so, delete composite to save memory
if (mAnim->compositingFrame && mAnim->lastCompositedFrameIndex == -1) {
mAnim->compositingFrame = nsnull;
}
nextFrameIndex = 0;
if (mLoopCount > 0) {
mLoopCount--;
}
if (!mAnimating) {
// break out early if we are actually done animating
return false;
}
}
if (!(nextFrame = mFrames[nextFrameIndex])) {
// something wrong with the next frame, skip it
mAnim->currentAnimationFrameIndex = nextFrameIndex;
return false;
}
timeout = nextFrame->GetTimeout();
} else {
// Uh oh, the frame we want to show is currently being decoded (partial)
// Wait until the next refresh driver tick and try again
return false;
}
if (!(timeout > 0)) {
mAnimationFinished = PR_TRUE;
EvaluateAnimation();
}
imgFrame *frameToUse = nsnull;
if (nextFrameIndex == 0) {
frameToUse = nextFrame;
*aDirtyRect = mAnim->firstFrameRefreshArea;
} else {
imgFrame *curFrame = mFrames[currentFrameIndex];
if (!curFrame) {
return false;
}
// Change frame
if (NS_FAILED(DoComposite(aDirtyRect, curFrame,
nextFrame, nextFrameIndex))) {
// something went wrong, move on to next
NS_WARNING("RasterImage::AdvanceFrame(): Compositing of frame failed");
nextFrame->SetCompositingFailed(PR_TRUE);
mAnim->currentAnimationFrameIndex = nextFrameIndex;
mAnim->currentAnimationFrameTime = aTime;
return false;
}
nextFrame->SetCompositingFailed(PR_FALSE);
}
// Set currentAnimationFrameIndex at the last possible moment
mAnim->currentAnimationFrameIndex = nextFrameIndex;
mAnim->currentAnimationFrameTime = aTime;
return true;
}
//******************************************************************************
// [notxpcom] void requestRefresh ([const] in TimeStamp aTime);
NS_IMETHODIMP_(void)
RasterImage::RequestRefresh(const mozilla::TimeStamp& aTime)
{
if (!mAnimating || !ShouldAnimate()) {
return;
}
EnsureAnimExists();
// only advance the frame if the current time is greater than or
// equal to the current frame's end time.
TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime();
bool frameAdvanced = false;
// The dirtyRect variable will contain an accumulation of the sub-rectangles
// that are dirty for each frame we advance in AdvanceFrame().
nsIntRect dirtyRect;
while (currentFrameEndTime <= aTime) {
TimeStamp oldFrameEndTime = currentFrameEndTime;
nsIntRect frameDirtyRect;
bool didAdvance = AdvanceFrame(aTime, &frameDirtyRect);
frameAdvanced = frameAdvanced || didAdvance;
currentFrameEndTime = GetCurrentImgFrameEndTime();
// Accumulate the dirty area.
dirtyRect = dirtyRect.Union(frameDirtyRect);
// if we didn't advance a frame, and our frame end time didn't change,
// then we need to break out of this loop & wait for the frame(s)
// to finish downloading
if (!frameAdvanced && (currentFrameEndTime == oldFrameEndTime)) {
break;
}
}
if (frameAdvanced) {
nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(mObserver));
if (!observer) {
NS_ERROR("Refreshing image after its imgRequest is gone");
StopAnimation();
return;
}
// Notify listeners that our frame has actually changed, but do this only
// once for all frames that we've now passed (if AdvanceFrame() was called
// more than once).
#ifdef DEBUG
mFramesNotified++;
#endif
observer->FrameChanged(this, &dirtyRect);
}
}
//******************************************************************************
/* [noscript] imgIContainer extractFrame(PRUint32 aWhichFrame,
* [const] in nsIntRect aRegion,
@ -657,27 +494,6 @@ RasterImage::GetCurrentImgFrameIndex() const
return 0;
}
TimeStamp
RasterImage::GetCurrentImgFrameEndTime() const
{
imgFrame* currentFrame = mFrames[mAnim->currentAnimationFrameIndex];
TimeStamp currentFrameTime = mAnim->currentAnimationFrameTime;
PRInt64 timeout = currentFrame->GetTimeout();
if (timeout < 0) {
// We need to return a sentinel value in this case, because our logic
// doesn't work correctly if we have a negative timeout value. The reason
// this positive infinity was chosen was because it works with the loop in
// RequestRefresh() above.
return TimeStamp() + TimeDuration::FromMilliseconds(UINT64_MAX_VAL);
}
TimeDuration durationOfTimeout = TimeDuration::FromMilliseconds(timeout);
TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
return currentFrameEndTime;
}
imgFrame*
RasterImage::GetCurrentImgFrame()
{
@ -1322,18 +1138,25 @@ RasterImage::StartAnimation()
EnsureAnimExists();
imgFrame* currentFrame = GetCurrentImgFrame();
NS_ABORT_IF_FALSE(mAnim && !mAnim->timer, "Anim must exist and not have a timer yet");
// Default timeout to 100: the timer notify code will do the right
// thing, so just get that started.
PRInt32 timeout = 100;
imgFrame *currentFrame = GetCurrentImgFrame();
if (currentFrame) {
if (currentFrame->GetTimeout() < 0) { // -1 means display this frame forever
timeout = currentFrame->GetTimeout();
if (timeout < 0) { // -1 means display this frame forever
mAnimationFinished = true;
return NS_ERROR_ABORT;
}
// We need to set the time that this initial frame was first displayed, as
// this is used in AdvanceFrame().
mAnim->currentAnimationFrameTime = TimeStamp::Now();
}
mAnim->timer = do_CreateInstance("@mozilla.org/timer;1");
NS_ENSURE_TRUE(mAnim->timer, NS_ERROR_OUT_OF_MEMORY);
mAnim->timer->InitWithCallback(static_cast<nsITimerCallback*>(this),
timeout, nsITimer::TYPE_REPEATING_SLACK);
return NS_OK;
}
@ -1347,6 +1170,11 @@ RasterImage::StopAnimation()
if (mError)
return NS_ERROR_FAILURE;
if (mAnim->timer) {
mAnim->timer->Cancel();
mAnim->timer = nsnull;
}
return NS_OK;
}
@ -1602,6 +1430,127 @@ RasterImage::SetSourceSizeHint(PRUint32 sizeHint)
return NS_OK;
}
//******************************************************************************
/* void notify(in nsITimer timer); */
NS_IMETHODIMP
RasterImage::Notify(nsITimer *timer)
{
#ifdef DEBUG
mFramesNotified++;
#endif
// This should never happen since the timer is only set up in StartAnimation()
// after mAnim is checked to exist.
NS_ABORT_IF_FALSE(mAnim, "Need anim for Notify()");
NS_ABORT_IF_FALSE(timer, "Need timer for Notify()");
NS_ABORT_IF_FALSE(mAnim->timer == timer,
"RasterImage::Notify() called with incorrect timer");
if (!mAnimating || !ShouldAnimate())
return NS_OK;
nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(mObserver));
if (!observer) {
// the imgRequest that owns us is dead, we should die now too.
NS_ABORT_IF_FALSE(mAnimationConsumers == 0,
"If no observer, should have no consumers");
if (mAnimating)
StopAnimation();
return NS_OK;
}
if (mFrames.Length() == 0)
return NS_OK;
imgFrame *nextFrame = nsnull;
PRInt32 previousFrameIndex = mAnim->currentAnimationFrameIndex;
PRUint32 nextFrameIndex = mAnim->currentAnimationFrameIndex + 1;
PRInt32 timeout = 0;
// Figure out if we have the next full frame. This is more complicated than
// just checking for mFrames.Length() because decoders append their frames
// before they're filled in.
NS_ABORT_IF_FALSE(mDecoder || nextFrameIndex <= mFrames.Length(),
"How did we get 2 indicies too far by incrementing?");
// If we don't have a decoder, we know we've got everything we're going to get.
// If we do, we only display fully-downloaded frames; everything else gets delayed.
bool haveFullNextFrame = !mDecoder || nextFrameIndex < mDecoder->GetCompleteFrameCount();
// If we're done decoding the next frame, go ahead and display it now and
// reinit the timer with the next frame's delay time.
if (haveFullNextFrame) {
if (mFrames.Length() == nextFrameIndex) {
// End of Animation
// If animation mode is "loop once", it's time to stop animating
if (mAnimationMode == kLoopOnceAnimMode || mLoopCount == 0) {
mAnimationFinished = true;
EvaluateAnimation();
return NS_OK;
} else {
// We may have used compositingFrame to build a frame, and then copied
// it back into mFrames[..]. If so, delete composite to save memory
if (mAnim->compositingFrame && mAnim->lastCompositedFrameIndex == -1)
mAnim->compositingFrame = nsnull;
}
nextFrameIndex = 0;
if (mLoopCount > 0)
mLoopCount--;
}
if (!(nextFrame = mFrames[nextFrameIndex])) {
// something wrong with the next frame, skip it
mAnim->currentAnimationFrameIndex = nextFrameIndex;
mAnim->timer->SetDelay(100);
return NS_OK;
}
timeout = nextFrame->GetTimeout();
} else {
// Uh oh, the frame we want to show is currently being decoded (partial)
// Wait a bit and try again
mAnim->timer->SetDelay(100);
return NS_OK;
}
if (timeout > 0)
mAnim->timer->SetDelay(timeout);
else {
mAnimationFinished = true;
EvaluateAnimation();
}
nsIntRect dirtyRect;
if (nextFrameIndex == 0) {
dirtyRect = mAnim->firstFrameRefreshArea;
} else {
imgFrame *prevFrame = mFrames[previousFrameIndex];
if (!prevFrame)
return NS_OK;
// Change frame and announce it
if (NS_FAILED(DoComposite(&dirtyRect, prevFrame,
nextFrame, nextFrameIndex))) {
// something went wrong, move on to next
NS_WARNING("RasterImage::Notify(): Composing Frame Failed\n");
nextFrame->SetCompositingFailed(true);
mAnim->currentAnimationFrameIndex = nextFrameIndex;
return NS_OK;
} else {
nextFrame->SetCompositingFailed(false);
}
}
// Set currentAnimationFrameIndex at the last possible moment
mAnim->currentAnimationFrameIndex = nextFrameIndex;
// Refreshes the screen
observer->FrameChanged(this, &dirtyRect);
return NS_OK;
}
//******************************************************************************
// DoComposite gets called when the timer for animation get fired and we have to
// update the composited frame of the animation.

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

@ -71,7 +71,6 @@
#endif
class imgIDecoder;
class imgIContainerObserver;
class nsIInputStream;
#define NS_RASTERIMAGE_CID \
@ -82,12 +81,6 @@ class nsIInputStream;
{0xb1, 0x43, 0x33, 0x40, 0xc0, 0x01, 0x12, 0xf7} \
}
/**
* It would be nice if we had a macro for this in prtypes.h.
* TODO: Place this macro in prtypes.h as PR_UINT64_MAX.
*/
#define UINT64_MAX_VAL PRUint64(-1)
/**
* Handles static and animated image containers.
*
@ -95,27 +88,15 @@ class nsIInputStream;
* @par A Quick Walk Through
* The decoder initializes this class and calls AppendFrame() to add a frame.
* Once RasterImage detects more than one frame, it starts the animation
* with StartAnimation(). Note that the invalidation events for RasterImage are
* generated automatically using nsRefreshDriver.
* with StartAnimation().
*
* @par
* StartAnimation() initializes the animation helper object and sets the time
* the first frame was displayed to the current clock time.
* StartAnimation() creates a timer. The timer calls Notify when the
* specified frame delay time is up.
*
* @par
* When the refresh driver corresponding to the imgIContainer that this image is
* a part of notifies the RasterImage that it's time to invalidate,
* RequestRefresh() is called with a given TimeStamp to advance to. As long as
* the timeout of the given frame (the frame's "delay") plus the time that frame
* was first displayed is less than or equal to the TimeStamp given,
* RequestRefresh() calls AdvanceFrame().
*
* @par
* AdvanceFrame() is responsible for advancing a single frame of the animation.
* It can return true, meaning that the frame advanced, or false, meaning that
* the frame failed to advance (usually because the next frame hasn't been
* decoded yet). It is also responsible for performing the final animation stop
* procedure if the final frame of a non-looping animation is reached.
* Notify() moves on to the next frame, sets up the new timer delay, destroys
* the old frame, and forces a redraw via observer->FrameChanged().
*
* @par
* Each frame can have a different method of removing itself. These are
@ -170,6 +151,7 @@ class imgDecodeWorker;
class Decoder;
class RasterImage : public Image
, public nsITimerCallback
, public nsIProperties
, public nsSupportsWeakReference
#ifdef DEBUG
@ -178,6 +160,7 @@ class RasterImage : public Image
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSITIMERCALLBACK
NS_DECL_NSIPROPERTIES
#ifdef DEBUG
NS_DECL_IMGICONTAINERDEBUG
@ -200,7 +183,6 @@ public:
NS_SCRIPTABLE NS_IMETHOD LockImage(void);
NS_SCRIPTABLE NS_IMETHOD UnlockImage(void);
NS_SCRIPTABLE NS_IMETHOD ResetAnimation(void);
NS_IMETHOD_(void) RequestRefresh(const mozilla::TimeStamp& aTime);
// END NS_DECL_IMGICONTAINER
RasterImage(imgStatusTracker* aStatusTracker = nsnull);
@ -352,10 +334,6 @@ private:
//! Area of the first frame that needs to be redrawn on subsequent loops.
nsIntRect firstFrameRefreshArea;
PRUint32 currentAnimationFrameIndex; // 0 to numFrames-1
// the time that the animation advanced to the current frame
TimeStamp currentAnimationFrameTime;
//! Track the last composited frame for Optimizations (See DoComposite code)
PRInt32 lastCompositedFrameIndex;
/** For managing blending of frames
@ -374,33 +352,23 @@ private:
* when it's done with the current frame.
*/
nsAutoPtr<imgFrame> compositingPrevFrame;
//! Timer to animate multiframed images
nsCOMPtr<nsITimer> timer;
Anim() :
firstFrameRefreshArea(),
currentAnimationFrameIndex(0),
lastCompositedFrameIndex(-1) {}
~Anim() {}
lastCompositedFrameIndex(-1)
{
;
}
~Anim()
{
if (timer)
timer->Cancel();
}
};
/**
* Advances the animation. Typically, this will advance a single frame, but it
* may advance multiple frames. This may happen if we have infrequently
* "ticking" refresh drivers (e.g. in background tabs), or extremely short-
* lived animation frames.
*
* @param aTime the time that the animation should advance to. This will
* typically be <= TimeStamp::Now().
*
* @param [out] aDirtyRect a pointer to an nsIntRect which encapsulates the
* area to be repainted after the frame is advanced.
*
* @returns true, if the frame was successfully advanced, false if it was not
* able to be advanced (e.g. the frame to which we want to advance is
* still decoding). Note: If false is returned, then aDirtyRect will
* remain unmodified.
*/
bool AdvanceFrame(mozilla::TimeStamp aTime, nsIntRect* aDirtyRect);
/**
* Deletes and nulls out the frame in mFrames[framenum].
*
@ -417,7 +385,6 @@ private:
imgFrame* GetCurrentImgFrame();
imgFrame* GetCurrentDrawableImgFrame();
PRUint32 GetCurrentImgFrameIndex() const;
mozilla::TimeStamp GetCurrentImgFrameEndTime() const;
inline void EnsureAnimExists()
{
@ -438,7 +405,7 @@ private:
LockImage();
}
}
/** Function for doing the frame compositing of animations
*
* @param aDirtyRect Area that the display will need to update
@ -450,7 +417,7 @@ private:
imgFrame* aPrevFrame,
imgFrame* aNextFrame,
PRInt32 aNextFrameIndex);
/** Clears an area of <aFrame> with transparent black.
*
* @param aFrame Target Frame
@ -458,7 +425,7 @@ private:
* @note Does also clears the transparancy mask
*/
static void ClearFrame(imgFrame* aFrame);
//! @overload
static void ClearFrame(imgFrame* aFrame, nsIntRect &aRect);

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

@ -172,6 +172,7 @@ SVGDrawingCallback::operator()(gfxContext* aContext,
gfxContextMatrixAutoSaveRestore contextMatrixRestorer(aContext);
aContext->Multiply(gfxMatrix(aTransform).Invert());
nsPresContext* presContext = presShell->GetPresContext();
NS_ABORT_IF_FALSE(presContext, "pres shell w/out pres context");
@ -329,14 +330,6 @@ VectorImage::GetWidth(PRInt32* aWidth)
return NS_OK;
}
//******************************************************************************
/* [notxpcom] void requestRefresh ([const] in TimeStamp aTime); */
NS_IMETHODIMP_(void)
VectorImage::RequestRefresh(const mozilla::TimeStamp& aTime)
{
// TODO: Implement for b666446.
}
//******************************************************************************
/* readonly attribute PRInt32 height; */
NS_IMETHODIMP

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

@ -42,7 +42,6 @@
#include "Image.h"
#include "nsIStreamListener.h"
#include "nsWeakReference.h"
#include "mozilla/TimeStamp.h"
class imgIDecoderObserver;
@ -77,7 +76,6 @@ public:
NS_SCRIPTABLE NS_IMETHOD LockImage(void);
NS_SCRIPTABLE NS_IMETHOD UnlockImage(void);
NS_SCRIPTABLE NS_IMETHOD ResetAnimation(void);
NS_IMETHOD_(void) RequestRefresh(const mozilla::TimeStamp& aTime);
// END NS_DECL_IMGICONTAINER
VectorImage(imgStatusTracker* aStatusTracker = nsnull);

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

@ -93,27 +93,8 @@ _TEST_FILES = imgutils.js \
# test_bug478398.html disabled - See bug 579139
_CHROME_FILES = imgutils.js \
animationPolling.js \
lime-anim-100x100.svg \
animation.svg \
test_animSVGImage.html \
test_animation.html \
animated-gif-finalframe.gif \
animated-gif.gif \
animated-gif2.gif \
purple.gif \
test_svg_animatedGIF.html \
test_bullet_animation.html \
test_background_image_anim.html \
filter.svg \
filter-final.svg \
test_svg_filter_animation.html \
test_xultree_animation.xhtml \
test_changeOfSource.html \
test_changeOfSource2.html \
test_undisplayed_iframe.html \
iframe.html \
ref-iframe.html \
$(NULL)
libs:: $(_TEST_FILES)

Двоичные данные
image/test/mochitest/animated-gif-finalframe.gif

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 72 B

Двоичные данные
image/test/mochitest/animated-gif.gif

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 146 B

Двоичные данные
image/test/mochitest/animated-gif2.gif

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 165 B

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

@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<image id="anim" xlink:href="animated-gif.gif" width="40" height="40"/>
</svg>

До

Ширина:  |  Высота:  |  Размер: 170 B

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

@ -1,395 +0,0 @@
var currentTest;
var gIsImageLoaded = false;
var gIsRefImageLoaded = false;
function pollForSuccess ()
{
if (!currentTest.isTestFinished) {
if (!currentTest.reusingReferenceImage || (currentTest.reusingReferenceImage
&& gRefImageLoaded)) {
currentTest.checkImage();
}
setTimeout(pollForSuccess, currentTest.pollFreq);
}
};
function imageLoadCallback()
{
gIsImageLoaded = true;
}
function referencePoller()
{
currentTest.takeReferenceSnapshot();
}
function reuseImageCallback()
{
gIsRefImageLoaded = true;
}
function failTest ()
{
if (currentTest.isTestFinished || currentTest.closeFunc) {
return;
}
ok(false, "timing out after " + currentTest.timeout + "ms. "
+ "Animated image still doesn't look correct, " + "after call #"
+ currentTest.onStopFrameCounter + " to onStopFrame");
currentTest.wereFailures = true;
currentTest.enableDisplay(document.getElementById(currentTest.debugElementId));
currentTest.cleanUpAndFinish();
};
/**
* Create a new AnimationTest object.
*
* @param pollFreq The amount of time (in ms) to wait between consecutive
* snapshots if the reference image and the test image don't match.
* @param timeout The total amount of time (in ms) to wait before declaring the
* test as failed.
* @param referenceElementId The id attribute of the reference image element, or
* the source of the image to change to, once the reference snapshot has
* been successfully taken. This latter option could be used if you don't
* want the image to become invisible at any time during the test.
* @param imageElementId The id attribute of the test image element.
* @param debugElementId The id attribute of the div where links should be
* appended if the test fails.
* @param cleanId The id attribute of the div or element to use as the 'clean'
* test. This element is only enabled when we are testing to verify that
* the reference image has been loaded. It can be undefined.
* @param srcAttr The location of the source of the image, for preloading. This
* is usually not required, but it useful for preloading reference
* images.
* @param xulTest A boolean value indicating whether or not this is a XUL test
* (uses hidden=true/false rather than display: none to hide/show
* elements).
* @param closeFunc A function that should be called when this test is finished.
* If null, then cleanUpAndFinish() will be called. This can be used to
* chain tests together, so they are all finished exactly once.
* @returns {AnimationTest}
*/
function AnimationTest(pollFreq, timeout, referenceElementId, imageElementId,
debugElementId, cleanId, srcAttr, xulTest, closeFunc)
{
// We want to test the cold loading behavior, so clear cache in case an
// earlier test got our image in there already.
clearImageCache();
this.wereFailures = false;
this.pollFreq = pollFreq;
this.timeout = timeout;
this.imageElementId = imageElementId;
this.referenceElementId = referenceElementId;
if (!document.getElementById(referenceElementId)) {
// In this case, we're assuming the user passed in a string that
// indicates the source of the image they want to change to,
// after the reference image has been taken.
this.reusingImageAsReference = true;
}
this.srcAttr = srcAttr;
this.debugElementId = debugElementId;
this.referenceSnapshot = ""; // value will be set in takeReferenceSnapshot()
this.onStopFrameCounter = 0;
this.isTestFinished = false;
this.numRefsTaken = 0;
this.blankWaitTime = 0;
this.cleanId = cleanId ? cleanId : '';
this.xulTest = xulTest ? xulTest : '';
this.closeFunc = closeFunc ? closeFunc : '';
if (this.srcAttr) {
this.myImage = new Image();
this.myImage.onload = imageLoadCallback;
this.myImage.src = this.srcAttr;
} else {
gIsImageLoaded = true;
}
}
AnimationTest.prototype.outputDebugInfo = function(message, id, dataUri)
{
var debugElement = document.getElementById(this.debugElementId);
var newDataUriElement = document.createElement("a");
newDataUriElement.setAttribute("id", id);
newDataUriElement.setAttribute("href", dataUri);
newDataUriElement.appendChild(document.createTextNode(message));
debugElement.appendChild(newDataUriElement);
var brElement = document.createElement("br");
debugElement.appendChild(brElement);
};
AnimationTest.prototype.isFinished = function()
{
return this.isTestFinished;
};
AnimationTest.prototype.takeCleanSnapshot = function()
{
var cleanElement;
if (this.cleanId) {
cleanElement = document.getElementById(this.cleanId);
}
// Enable clean page comparison element
if (cleanElement) {
this.enableDisplay(cleanElement);
}
// Take a snapshot of the initial (clean) page
this.cleanSnapshot = snapshotWindow(window, false);
// Disable the clean page comparison element
if (cleanElement) {
this.disableDisplay(cleanElement);
}
var dataString1 = "Clean Snapshot";
this.outputDebugInfo(dataString1, 'cleanSnap',
this.cleanSnapshot.toDataURL());
};
AnimationTest.prototype.takeBlankSnapshot = function()
{
// Take a snapshot of the initial (essentially blank) page
this.blankSnapshot = snapshotWindow(window, false);
var dataString1 = "Initial Blank Snapshot";
this.outputDebugInfo(dataString1, 'blank1Snap',
this.blankSnapshot.toDataURL());
};
/**
* Begin the AnimationTest. This will utilize the information provided in the
* constructor to invoke a mochitest on animated images. It will automatically
* fail if allowed to run past the timeout.
*/
AnimationTest.prototype.beginTest = function ()
{
SimpleTest.waitForExplicitFinish();
currentTest = this;
this.takeReferenceSnapshot();
// In case something goes wrong, fail earlier than mochitest timeout,
// and with more information.
setTimeout(failTest, this.timeout);
if (!this.reusingImageAsReference) {
this.disableDisplay(document.getElementById(this.imageElementId));
}
this.setupPolledImage();
setTimeout(pollForSuccess, 10);
};
AnimationTest.prototype.setupPolledImage = function ()
{
// Make sure the image is visible
if (!this.reusingImageAsReference) {
this.enableDisplay(document.getElementById(this.imageElementId));
var currentSnapshot = snapshotWindow(window, false);
var result = compareSnapshots(currentSnapshot, this.referenceSnapshot, true);
var dataString = "Snapshot #" + this.onStopFrameCounter;
this.outputDebugInfo(dataString, 'snap' + this.onStopFrameCounter,
currentSnapshot.toDataURL());
if (result[0]) {
// SUCCESS!
ok(true, "Animated image looks correct, " + "at call #"
+ this.onStopFrameCounter + " to onStopFrame");
this.cleanUpAndFinish();
}
} else {
if (!gIsRefImageLoaded) {
this.myImage = new Image();
this.myImage.onload = reuseImageCallback;
document.getElementById(this.imageElementId).setAttribute('src',
this.referenceElementId);
}
}
}
AnimationTest.prototype.checkImage = function ()
{
if (this.isTestFinished) {
return;
}
this.onStopFrameCounter++;
// We need this for some tests, because we need to force the
// test image to be visible.
if (!this.reusingImageAsReference) {
this.enableDisplay(document.getElementById(this.imageElementId));
}
var currentSnapshot = snapshotWindow(window, false);
var result = compareSnapshots(currentSnapshot, this.referenceSnapshot, true);
var dataString = "Snapshot #" + this.onStopFrameCounter;
this.outputDebugInfo(dataString, 'snap' + this.onStopFrameCounter,
currentSnapshot.toDataURL());
if (result[0]) {
// SUCCESS!
ok(true, "Animated image looks correct, " + "at call #"
+ this.onStopFrameCounter + " to onStopFrame");
this.cleanUpAndFinish();
}
};
AnimationTest.prototype.takeReferenceSnapshot = function ()
{
this.numRefsTaken++;
// Test to make sure the reference image doesn't match a clean snapshot
if (!this.cleanSnapshot) {
this.takeCleanSnapshot();
}
// Used later to verify that the reference div disappeared
if (!this.blankSnapshot) {
this.takeBlankSnapshot();
}
if (this.reusingImageAsReference) {
// Show reference div, & take a snapshot
var referenceDiv = document.getElementById(this.imageElementId);
this.enableDisplay(referenceDiv);
this.referenceSnapshot = snapshotWindow(window, false);
var snapResult = compareSnapshots(this.cleanSnapshot, this.referenceSnapshot,
false);
if (!snapResult[0]) {
if (this.blankWaitTime > 2000) {
// if it took longer than two seconds to load the image, we probably
// have a problem.
this.wereFailures = true;
ok(snapResult[0],
"Reference snapshot shouldn't match clean (non-image) snapshot");
} else {
this.blankWaitTime += 20;
// let's wait a bit and see if it clears up
setTimeout(referencePoller, 20);
return;
}
}
ok(snapResult[0],
"Reference snapshot shouldn't match clean (non-image) snapshot");
var dataString = "Reference Snapshot #" + this.numRefsTaken;
this.outputDebugInfo(dataString, 'refSnapId',
this.referenceSnapshot.toDataURL());
} else {
// Make sure the animation section is hidden
this.disableDisplay(document.getElementById(this.imageElementId));
// Show reference div, & take a snapshot
var referenceDiv = document.getElementById(this.referenceElementId);
this.enableDisplay(referenceDiv);
this.referenceSnapshot = snapshotWindow(window, false);
var snapResult = compareSnapshots(this.cleanSnapshot, this.referenceSnapshot, false);
if (!snapResult[0]) {
if (this.blankWaitTime > 2000) {
// if it took longer than two seconds to load the image, we probably
// have a problem.
this.wereFailures = true;
ok(snapResult[0],
"Reference snapshot shouldn't match clean (non-image) snapshot");
} else {
this.blankWaitTime += 20;
// let's wait a bit and see if it clears up
setTimeout(referencePoller, 20);
return;
}
}
ok(snapResult[0],
"Reference snapshot shouldn't match clean (non-image) snapshot");
var dataString = "Reference Snapshot #" + this.numRefsTaken;
this.outputDebugInfo(dataString, 'refSnapId',
this.referenceSnapshot.toDataURL());
// Re-hide reference div, and take another snapshot to be sure it's gone
this.disableDisplay(referenceDiv);
this.testBlankCameBack();
}
};
AnimationTest.prototype.enableDisplay = function(element)
{
if (!element) {
return;
}
if (!this.xulTest) {
element.style.display = '';
} else {
element.setAttribute('hidden', 'false');
}
};
AnimationTest.prototype.disableDisplay = function(element)
{
if (!element) {
return;
}
if (!this.xulTest) {
element.style.display = 'none';
} else {
element.setAttribute('hidden', 'true');
}
};
AnimationTest.prototype.testBlankCameBack = function()
{
var blankSnapshot2 = snapshotWindow(window, false);
var result = compareSnapshots(this.blankSnapshot, blankSnapshot2, true);
ok(result[0], "Reference image should disappear when it becomes display:none");
if (!result[0]) {
this.wereFailures = true;
var dataString = "Second Blank Snapshot";
this.outputDebugInfo(dataString, 'blank2SnapId', result[2]);
}
};
AnimationTest.prototype.cleanUpAndFinish = function ()
{
// On the off chance that failTest and checkImage are triggered
// back-to-back, use a flag to prevent multiple calls to SimpleTest.finish.
if (this.isTestFinished) {
return;
}
this.isTestFinished = true;
// Call our closing function, if one exists
if (this.closeFunc) {
this.closeFunc();
return;
}
if (this.wereFailures) {
document.getElementById(this.debugElementId).style.display = 'block';
}
SimpleTest.finish();
document.getElementById(this.debugElementId).style.display = "";
};

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

@ -1,9 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<filter id="filter1" x="0%" y="0%" width="100%" height="100%">
<feImage xlink:href="animated-gif-finalframe.gif"/>
</filter>
<g>
<rect x="0" y="0" width="100%" height="100%" filter="url(#filter1)"/>
</g>
</svg>

До

Ширина:  |  Высота:  |  Размер: 299 B

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

@ -1,9 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<filter id="filter1" x="0%" y="0%" width="100%" height="100%">
<feImage xlink:href="animated-gif.gif"/>
</filter>
<g>
<rect x="0" y="0" width="100%" height="100%" filter="url(#filter1)"/>
</g>
</svg>

До

Ширина:  |  Высота:  |  Размер: 288 B

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

@ -1,5 +0,0 @@
<html>
<body bgcolor="gray">
<img src="animated-gif.gif">
</body>
</html>

Двоичные данные
image/test/mochitest/purple.gif

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 86 B

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

@ -1,6 +0,0 @@
<html>
<body bgcolor="gray">
<div id="referenceImage"
style="height: 40px; width: 40px; background: #2aff00"></div>
</body>
</html>

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

@ -1,46 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=666446
-->
<head>
<title>Test for Bug 666446 - General Animated GIF Test</title>
<script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
<script type="application/javascript" src="imgutils.js"></script>
<script type="application/javascript" src="animationPolling.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
Mozilla Bug 666446: lots of animated gifs swamp us with paint events
</a>
<p id="display"></p>
<div id="content">
<div id="referenceDiv" style="height: 40px; width: 40px;
display: none; background: #2aff00"></div>
<div id="animatedImage">
<img id="animatedGif" src="animated-gif.gif" style="display: none;">
<div id="text-descr"></div>
</div>
<div id="debug" style="display:none">
</div>
</div>
<pre id="test">
<script type="text/javascript;version=1.8">
const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
function main()
{
var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
'animatedGif', 'debug');
animTest.beginTest();
}
window.onload = main;
</script>
</pre>
</body>
</html>

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

@ -1,44 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=666446
-->
<head>
<title>Test for Bug 666446 - Animated Background Images</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
<script type="application/javascript" src="imgutils.js"></script>
<script type="application/javascript" src="animationPolling.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
Mozilla Bug 666446: lots of animated gifs swamp us with paint events
</a>
<p id="display"></p>
<div id="content">
<div id="referenceDiv" style="height: 140px; width: 140px;
display: none; background: #2aff00"></div>
<div id="bgImage" style="height: 140px; width: 140px; background-image: url(animated-gif.gif); display: none;">
</div>
</div>
<div id="debug" style="display:none"></div>
<pre id="test">
<script type="text/javascript;version=1.8">
/** Test for Bug 666446 nsImageLoader/RasterImage**/
const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
function main() {
var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
'bgImage', 'debug');
animTest.beginTest();
}
window.onload = main;
</script>
</pre>
</body>
</html>

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

@ -1,57 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=666446
-->
<head>
<title>Test for Bug 666446 - Animated Bullets</title>
<script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
<script type="application/javascript" src="imgutils.js"></script>
<script type="application/javascript" src="animationPolling.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
Mozilla Bug 666446: lots of animated gifs swamp us with paint events
</a>
<p id="display"></p>
<div id="content">
<div id="cleanDiv" style="display: none;">
<ul>
<li>Test 1</li>
</ul>
</div>
<div id="referenceDiv" style="display: none;">
<ul>
<li style="list-style-image: url(animated-gif-finalframe.gif);">Test 1</li>
</ul>
</div>
<div id="animatedImage" style="display: none;">
<ul>
<li style="list-style-image: url(animated-gif.gif);">Test 1</li>
</ul>
</div>
<div id="text-descr"></div>
<div id="debug" style="display:none">
</div>
</div>
<pre id="test">
<script type="text/javascript;version=1.8">
const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
function main()
{
var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
'animatedImage', 'debug', 'cleanDiv',
'animated-gif-finalframe.gif');
animTest.beginTest();
}
window.onload = main;
</script>
</pre>
</body>
</html>

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

@ -1,63 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=666446
-->
<head>
<title>Test for Bug 666446 - Change of Source (1st Version)</title>
<script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
<script type="application/javascript" src="imgutils.js"></script>
<script type="application/javascript" src="animationPolling.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
Mozilla Bug 666446: lots of animated gifs swamp us with paint events
</a>
<p id="display"></p>
<div id="content">
<div id="referenceDiv" style="height: 40px; width: 40px;
display: none; background: #2aff00;">
</div>
<div id="animatedImage">
<img id='animatedGif' src="animated-gif.gif" style="display: none;">
</div>
<div id="text-descr"></div>
<div id="debug" style="display:none">
</div>
</div>
<pre id="test">
<script type="text/javascript;version=1.8">
const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
var gAnimTest;
var gIntervalId;
function initSecondTest() {
document.getElementById('debug').style.display = 'none';
document.getElementById('referenceDiv').style.background = "#9600ff";
document.getElementById('animatedGif').setAttribute('src',
'animated-gif2.gif');
document.getElementById('animatedGif').style.display = 'none';
var secondTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
'animatedGif', 'debug', '', '', false);
secondTest.beginTest();
}
function main()
{
gAnimTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
'animatedGif', 'debug', '', '', false,
initSecondTest);
gAnimTest.beginTest();
}
window.onload = main;
</script>
</pre>
</body>
</html>

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

@ -1,48 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=666446
-->
<head>
<title>Test for Bug 691792 - Change of Source (2nd Version)</title>
<script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
<script type="application/javascript" src="imgutils.js"></script>
<script type="application/javascript" src="animationPolling.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=691792">
Mozilla Bug 691792: Change of src attribute for animated gifs no longer works as expected
</a>
<p id="display"></p>
<div id="content">
<div id="animatedImage">
<img id='animatedGif' src="purple.gif" style="display: none;">
</div>
<div id="text-descr"></div>
<div id="debug" style="display:none">
</div>
</div>
<pre id="test">
<script type="text/javascript;version=1.8">
const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
var gAnimTest;
var gIntervalId;
function main()
{
gAnimTest = new AnimationTest(20, FAILURE_TIMEOUT, 'animated-gif2.gif',
'animatedGif', 'debug', '', 'animated-gif2.gif',
false);
gAnimTest.beginTest();
}
window.onload = main;
</script>
</pre>
</body>
</html>

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

@ -1,52 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=666446
-->
<head>
<title>Test for Bug 666446 - Animated Raster Images inside of SVG Frames</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
<script type="application/javascript" src="imgutils.js"></script>
<script type="application/javascript" src="animationPolling.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
</head>
<!-- Make sure embed element is snapped to an exact pixel. -->
<div class="bug-header" style="height: 100px;">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
Mozilla Bug 666446: lots of animated gifs swamp us with paint events
</a>
</div>
<p id="display"></p>
<div id="content">
<div id="referenceDiv" style="height: 40px; width: 40px;
display: none; background: #2aff00"></div>
<!--
We use <embed> here instead of <img> because the <img> tag utilizes
the VectorImage class for SVG, whereas in this test, we are testing
RasterImage.
-->
<embed id="embeddedSVG" src="animation.svg" type="image/svg+xml" style="display: none;"/>
</div>
<div id="debug" style="display:none"></div>
<pre id="test">
<script type="text/javascript;version=1.8">
/** Test for Bug 666446 nsSVGImageFrame/RasterImage**/
const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
function main() {
var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
'embeddedSVG', 'debug', '', 'src');
animTest.beginTest();
}
window.onload = main;
</script>
</pre>
</body>
</html>

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

@ -1,42 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=666446
-->
<head>
<title>Test for Bug 666446 - Animated Images within SVG Filters</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
<script type="application/javascript" src="imgutils.js"></script>
<script type="application/javascript" src="animationPolling.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
Mozilla Bug 666446: lots of animated gifs swamp us with paint events
</a>
<p id="display"></p>
<div id="content">
<embed id="referenceImage" src="filter-final.svg" type="image/svg+xml" style="display: none;"/>
<embed id="embeddedSVGFilt" src="filter.svg" type="image/svg+xml" style="display: none;"/>
</div>
<div id="debug" style="display:none"></div>
<pre id="test">
<script type="text/javascript;version=1.8">
/** Test for Bug 666446 nsSVGFEImageElement/RasterImage**/
const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
function main() {
var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceImage',
'embeddedSVGFilt', 'debug', '', 'src');
animTest.beginTest();
}
window.onload = main;
</script>
</pre>
</body>
</html>

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

@ -1,48 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=666446
-->
<head>
<title>Test for Bug 666446 - Test for Animated Gif within IFRAME</title>
<script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
<script type="application/javascript" src="imgutils.js"></script>
<script type="application/javascript" src="animationPolling.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
Mozilla Bug 666446: lots of animated gifs swamp us with paint events</a>
<p id="display"></p>
<div id="content">
<div id="referenceDiv" style="display:none;">
<iframe id="referenceIFrame" src="ref-iframe.html" width="50%" height="100">
Browser does not support iframes.
</iframe>
</div>
<div id="animatedImage">
<iframe id="imageIFrame" src="iframe.html" width="50%" height="100" style="display: none;">
Browser does not support iframes.
</iframe>
</div>
<div id="debug" style="display: none"></div>
</div>
<pre id="test">
<script type="text/javascript;version=1.8">
const FAILURE_TIMEOUT = 120000; // Fail early after 120 seconds (2 minutes)
function main()
{
var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceDiv',
'imageIFrame', 'debug');
animTest.beginTest();
}
window.onload = main;
</script>
</pre>
</body>
</html>

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

@ -1,67 +0,0 @@
<!DOCTYPE HTML>
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xml:lang="en" lang="en">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=666446
-->
<head>
<title>Test for Bug 666446 - Animated Images within SVG Filters</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
<script type="application/javascript" src="imgutils.js"></script>
<script type="application/javascript" src="animationPolling.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=666446">
Mozilla Bug 666446: lots of animated gifs swamp us with paint events
</a>
<p id="display"></p>
<div id="content">
<xul:caption label="Bug 666446 - XULTree Test" />
<xul:separator />
<br />
<xul:window id="main" title="Bug 666446: XUL Tree Testing" width="100" height="100">
<xul:tree flex="1">
<xul:treecols>
<xul:treecol id="icon" label="Icon" flex="1" />
</xul:treecols>
<xul:treechildren>
<xul:treeitem id="referenceItem" hidden="true">
<xul:treerow>
<xul:treecell src="animated-gif-finalframe.gif" width="40" height="40" />
</xul:treerow>
</xul:treeitem>
<xul:treeitem id="imageItem" hidden="true">
<xul:treerow>
<xul:treecell src="animated-gif.gif" width="40" height="40" />
</xul:treerow>
</xul:treeitem>
</xul:treechildren>
</xul:tree>
</xul:window>
</div>
<div id="debug" style="display:none"></div>
<pre id="test">
<script type="text/javascript;version=1.8">
/** Test for Bug 666446 nsSVGFEImageElement/RasterImage**/
const FAILURE_TIMEOUT = 5000; // Fail early after 120 seconds (2 minutes)
function main() {
var animTest = new AnimationTest(20, FAILURE_TIMEOUT, 'referenceItem',
'imageItem', 'debug', '',
'animated-gif-finalframe.gif', true);
animTest.beginTest();
}
window.onload = main;
</script>
</pre>
</body>
</html>

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

@ -57,7 +57,6 @@
#include "nsStyleContext.h"
#include "nsGkAtoms.h"
#include "nsLayoutUtils.h"
// Paint forcing
#include "prenv.h"
@ -68,14 +67,17 @@ nsImageLoader::nsImageLoader(nsIFrame *aFrame, PRUint32 aActions,
nsImageLoader *aNextLoader)
: mFrame(aFrame),
mActions(aActions),
mNextLoader(aNextLoader),
mRequestRegistered(false)
mNextLoader(aNextLoader)
{
}
nsImageLoader::~nsImageLoader()
{
Destroy();
mFrame = nsnull;
if (mRequest) {
mRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
}
}
/* static */ already_AddRefed<nsImageLoader>
@ -103,15 +105,12 @@ nsImageLoader::Destroy()
todestroy->Destroy();
}
if (mRequest && mFrame) {
nsPresContext* presContext = mFrame->PresContext();
mFrame = nsnull;
nsLayoutUtils::DeregisterImageRequest(presContext, mRequest,
&mRequestRegistered);
if (mRequest) {
mRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
}
mFrame = nsnull;
mRequest = nsnull;
}
@ -128,31 +127,17 @@ nsImageLoader::Load(imgIRequest *aImage)
if (!aImage)
return NS_ERROR_FAILURE;
// Deregister mRequest from the refresh driver, since it is no longer
// going to be managed by this nsImageLoader.
nsPresContext* presContext = mFrame->PresContext();
nsLayoutUtils::DeregisterImageRequest(presContext, mRequest,
&mRequestRegistered);
// Make sure to clone into a temporary, then set mRequest, since
// cloning may notify and we don't want to trigger paints from this
// code.
nsCOMPtr<imgIRequest> newRequest;
nsresult rv = aImage->Clone(this, getter_AddRefs(newRequest));
mRequest.swap(newRequest);
// Re-register mRequest with the refresh driver, but immediately deregister
// if it isn't animated.
nsLayoutUtils::RegisterImageRequest(presContext, mRequest,
&mRequestRegistered);
nsLayoutUtils::DeregisterImageRequestIfNotAnimated(presContext, mRequest,
&mRequestRegistered);
return rv;
}
NS_IMETHODIMP nsImageLoader::OnStartContainer(imgIRequest *aRequest,
imgIContainer *aImage)
{
@ -284,35 +269,3 @@ nsImageLoader::DoRedraw(const nsRect* aDamageRect)
mFrame->Invalidate(bounds);
}
}
NS_IMETHODIMP
nsImageLoader::OnStartDecode(imgIRequest *aRequest)
{
// Register our image request with the refresh driver.
nsPresContext* presContext = mFrame->PresContext();
if (!presContext) {
return NS_OK;
}
if (mRequest == aRequest) {
nsLayoutUtils::RegisterImageRequest(presContext, mRequest,
&mRequestRegistered);
}
return NS_OK;
}
NS_IMETHODIMP
nsImageLoader::OnStopDecode(imgIRequest *aRequest, nsresult status,
const PRUnichar *statusArg)
{
if (mRequest == aRequest) {
// Deregister the imgIRequest with the refresh driver if the
// image is not animated.
nsLayoutUtils::DeregisterImageRequestIfNotAnimated(mFrame->PresContext(),
mRequest,
&mRequestRegistered);
}
return NS_OK;
}

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

@ -82,10 +82,6 @@ public:
// imgIDecoderObserver (override nsStubImageDecoderObserver)
NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
NS_IMETHOD OnStartDecode(imgIRequest *aRequest);
NS_IMETHOD OnStopDecode(imgIRequest *aRequest,
nsresult status,
const PRUnichar *statusArg);
NS_IMETHOD OnStopFrame(imgIRequest *aRequest, PRUint32 aFrame);
NS_IMETHOD OnStopRequest(imgIRequest *aRequest, bool aLastPart);
// Do not override OnDataAvailable since background images are not
@ -113,8 +109,4 @@ private:
nsCOMPtr<imgIRequest> mRequest;
PRUint32 mActions;
nsRefPtr<nsImageLoader> mNextLoader;
// This is a boolean flag indicating whether or not the current image request
// has been registered with the refresh driver.
bool mRequestRegistered;
};

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

@ -4327,102 +4327,6 @@ nsLayoutUtils::Shutdown()
}
}
/* static */
void
nsLayoutUtils::RegisterImageRequest(nsPresContext* aPresContext,
imgIRequest* aRequest,
bool* aRequestRegistered)
{
if (!aPresContext) {
return;
}
if (aRequestRegistered && *aRequestRegistered) {
// Our request is already registered with the refresh driver, so
// no need to register it again.
return;
}
if (aRequest) {
nsCOMPtr<imgIContainer> image;
aRequest->GetImage(getter_AddRefs(image));
if (image) {
if (!aPresContext->RefreshDriver()->AddImageRequest(aRequest)) {
NS_WARNING("Unable to add image request");
return;
}
if (aRequestRegistered) {
*aRequestRegistered = true;
}
}
}
}
/* static */
void
nsLayoutUtils::DeregisterImageRequest(nsPresContext* aPresContext,
imgIRequest* aRequest,
bool* aRequestRegistered)
{
if (!aPresContext) {
return;
}
// Deregister our imgIRequest with the refresh driver to
// complete tear-down, but only if it has been registered
if (aRequestRegistered && !*aRequestRegistered) {
return;
}
if (aRequest) {
nsCOMPtr<imgIContainer> image;
aRequest->GetImage(getter_AddRefs(image));
if (image) {
aPresContext->RefreshDriver()->RemoveImageRequest(aRequest);
if (aRequestRegistered) {
*aRequestRegistered = false;
}
}
}
}
/* static */
void
nsLayoutUtils::DeregisterImageRequestIfNotAnimated(nsPresContext* aPresContext,
imgIRequest* aRequest,
bool* aRequestRegistered)
{
if (!aPresContext) {
return;
}
if (aRequestRegistered && !*aRequestRegistered) {
// Image request isn't registered with the refresh driver - no need
// to try and deregister it.
return;
}
// Deregister the imgIRequest with the refresh driver if the
// image is not animated
nsCOMPtr<imgIContainer> imageContainer;
if (aRequest) {
aRequest->GetImage(getter_AddRefs(imageContainer));
bool animated;
if (!imageContainer) {
return;
}
nsresult rv = imageContainer->GetAnimated(&animated);
if (NS_SUCCEEDED(rv) && !animated) {
nsLayoutUtils::DeregisterImageRequest(aPresContext, aRequest,
aRequestRegistered);
}
}
}
nsSetAttrRunnable::nsSetAttrRunnable(nsIContent* aContent, nsIAtom* aAttrName,
const nsAString& aValue)
: mContent(aContent),

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

@ -1454,63 +1454,6 @@ public:
static void Shutdown();
/**
* Register an imgIRequest object with a refresh driver.
*
* @param aPresContext The nsPresContext whose refresh driver we want to
* register with.
* @param aRequest A pointer to the imgIRequest object which the client wants
* to register with the refresh driver.
* @param aRequestRegistered A pointer to a boolean value which indicates
* whether the given image request is registered. If
* *aRequestRegistered is true, then this request will not be
* registered again. If the request is registered by this function,
* then *aRequestRegistered will be set to true upon the completion of
* this function.
*
*/
static void RegisterImageRequest(nsPresContext* aPresContext,
imgIRequest* aRequest,
bool* aRequestRegistered);
/**
* Deregister an imgIRequest object from a refresh driver.
*
* @param aPresContext The nsPresContext whose refresh driver we want to
* deregister from.
* @param aRequest A pointer to the imgIRequest object with which the client
* previously registered and now wants to deregister from the refresh
* driver.
* @param aRequestRegistered A pointer to a boolean value which indicates
* whether the given image request is registered. If
* *aRequestRegistered is false, then this request will not be
* deregistered. If the request is deregistered by this function,
* then *aRequestRegistered will be set to false upon the completion of
* this function.
*/
static void DeregisterImageRequest(nsPresContext* aPresContext,
imgIRequest* aRequest,
bool* aRequestRegistered);
/**
* Deregister an imgIRequest object from a refresh driver, if the
* imgIRequest object represents a static (i.e. not animated) image.
*
* @param aPresContext The nsPresContext whose refresh driver we want to
* deregister from.
* @param aRequest A pointer to the imgIRequest object with which the client
* previously registered and now wants to deregister from the refresh
* driver.
* @param aRequestRegistered A pointer to a boolean value which indicates
* whether the given image request is registered. If
* *aRequestRegistered is false, then this request will not be
* deregistered. If the request is deregistered by this function,
* then *aRequestRegistered will be set to false upon the completion of
* this function.
*/
static void DeregisterImageRequestIfNotAnimated(nsPresContext* aPresContext,
imgIRequest* aRequest,
bool* aRequestRegistered);
#ifdef DEBUG
/**
* Assert that there are no duplicate continuations of the same frame

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

@ -114,7 +114,6 @@ nsRefreshDriver::nsRefreshDriver(nsPresContext *aPresContext)
mTimerIsPrecise(false),
mLastTimerInterval(0)
{
mRequests.Init();
}
nsRefreshDriver::~nsRefreshDriver()
@ -186,29 +185,6 @@ nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver *aObserver,
return array.RemoveElement(aObserver);
}
bool
nsRefreshDriver::AddImageRequest(imgIRequest* aRequest)
{
if (!mRequests.PutEntry(aRequest)) {
return false;
}
EnsureTimerStarted(false);
return true;
}
void
nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest)
{
mRequests.RemoveEntry(aRequest);
}
void nsRefreshDriver::ClearAllImageRequests()
{
mRequests.Clear();
}
void
nsRefreshDriver::EnsureTimerStarted(bool aAdjustingTimer)
{
@ -262,7 +238,6 @@ nsRefreshDriver::ObserverCount() const
for (PRUint32 i = 0; i < ArrayLength(mObservers); ++i) {
sum += mObservers[i].Length();
}
// Even while throttled, we need to process layout and style changes. Style
// changes can trigger transitions which fire events when they complete, and
// layout changes can affect media queries on child documents, triggering
@ -274,12 +249,6 @@ nsRefreshDriver::ObserverCount() const
return sum;
}
PRUint32
nsRefreshDriver::ImageRequestCount() const
{
return mRequests.Count();
}
void
nsRefreshDriver::UpdateMostRecentRefresh()
{
@ -334,7 +303,7 @@ nsRefreshDriver::Notify(nsITimer *aTimer)
UpdateMostRecentRefresh();
nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
if (!presShell || (ObserverCount() == 0 && ImageRequestCount() == 0)) {
if (!presShell || ObserverCount() == 0) {
// Things are being destroyed, or we no longer have any observers.
// We don't want to stop the timer when observers are initially
// removed, because sometimes observers can be added and removed
@ -363,7 +332,6 @@ nsRefreshDriver::Notify(nsITimer *aTimer)
return NS_OK;
}
}
if (i == 0) {
// Don't just loop while we have things in mBeforePaintTargets,
// the whole point is that event handlers should readd the
@ -436,17 +404,6 @@ nsRefreshDriver::Notify(nsITimer *aTimer)
}
}
/*
* Perform notification to imgIRequests subscribed to listen
* for refresh events.
*/
ImageRequestParameters parms = {mMostRecentRefresh};
if (mRequests.Count()) {
mRequests.EnumerateEntries(nsRefreshDriver::ImageRequestEnumerator, &parms);
EnsureTimerStarted(false);
}
if (mThrottled ||
(mTimerIsPrecise !=
(GetRefreshTimerType() == nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP))) {
@ -467,24 +424,6 @@ nsRefreshDriver::Notify(nsITimer *aTimer)
return NS_OK;
}
PLDHashOperator
nsRefreshDriver::ImageRequestEnumerator(nsISupportsHashKey* aEntry,
void* aUserArg)
{
ImageRequestParameters* parms =
static_cast<ImageRequestParameters*> (aUserArg);
mozilla::TimeStamp mostRecentRefresh = parms->ts;
imgIRequest* req = static_cast<imgIRequest*>(aEntry->GetKey());
NS_ABORT_IF_FALSE(req, "Unable to retrieve the image request");
nsCOMPtr<imgIContainer> image;
req->GetImage(getter_AddRefs(image));
if (image) {
image->RequestRefresh(mostRecentRefresh);
}
return PL_DHASH_NEXT;
}
void
nsRefreshDriver::Freeze()
{
@ -498,7 +437,7 @@ nsRefreshDriver::Thaw()
{
NS_ASSERTION(mFrozen, "Thaw called on an unfrozen refresh driver");
mFrozen = false;
if (ObserverCount() || ImageRequestCount()) {
if (ObserverCount()) {
// FIXME: This isn't quite right, since our EnsureTimerStarted call
// updates our mMostRecentRefresh, but the DoRefresh call won't run
// and notify our observers until we get back to the event loop.

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

@ -50,13 +50,10 @@
#include "nsTObserverArray.h"
#include "nsTArray.h"
#include "nsAutoPtr.h"
#include "nsTHashtable.h"
#include "nsHashKeys.h"
class nsPresContext;
class nsIPresShell;
class nsIDocument;
class imgIRequest;
/**
* An abstract base class to be implemented by callers wanting to be
@ -131,24 +128,6 @@ public:
bool RemoveRefreshObserver(nsARefreshObserver *aObserver,
mozFlushType aFlushType);
/**
* Add/Remove imgIRequest versions of observers.
*
* These are used for hooking into the refresh driver for
* controlling animated images.
*
* @note The refresh driver owns a reference to these listeners.
*
* @note Technically, imgIRequest objects are not nsARefreshObservers, but
* for controlling animated image repaint events, we subscribe the
* imgIRequests to the nsRefreshDriver for notification of paint events.
*
* @returns whether the operation succeeded, or void in the case of removal.
*/
bool AddImageRequest(imgIRequest* aRequest);
void RemoveImageRequest(imgIRequest* aRequest);
void ClearAllImageRequests();
/**
* Add / remove presshells that we should flush style and layout on
*/
@ -239,15 +218,10 @@ public:
private:
typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
typedef nsTHashtable<nsISupportsHashKey> RequestTable;
void EnsureTimerStarted(bool aAdjustingTimer);
void StopTimer();
PRUint32 ObserverCount() const;
PRUint32 ImageRequestCount() const;
static PLDHashOperator ImageRequestEnumerator(nsISupportsHashKey* aEntry,
void* aUserArg);
void UpdateMostRecentRefresh();
ObserverArray& ArrayFor(mozFlushType aFlushType);
// Trigger a refresh immediately, if haven't been disconnected or frozen.
@ -278,8 +252,6 @@ private:
// separate arrays for each flush type we support
ObserverArray mObservers[3];
RequestTable mRequests;
nsAutoTArray<nsIPresShell*, 16> mStyleFlushObservers;
nsAutoTArray<nsIPresShell*, 16> mLayoutFlushObservers;
// nsTArray on purpose, because we want to be able to swap.
@ -290,11 +262,6 @@ private:
// This is the last interval we used for our timer. May be 0 if we
// haven't computed a timer interval yet.
mutable PRInt32 mLastTimerInterval;
// Helper struct for processing image requests
struct ImageRequestParameters {
mozilla::TimeStamp ts;
};
};
#endif /* !defined(nsRefreshDriver_h_) */

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

@ -78,7 +78,6 @@ public:
NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
NS_IMETHOD OnDataAvailable(imgIRequest *aRequest, bool aCurrentFrame,
const nsIntRect *aRect);
NS_IMETHOD OnStartDecode(imgIRequest *aRequest);
NS_IMETHOD OnStopDecode(imgIRequest *aRequest, nsresult status,
const PRUnichar *statusArg);
// imgIContainerObserver (override nsStubImageDecoderObserver)
@ -102,10 +101,6 @@ nsBulletFrame::DestroyFrom(nsIFrame* aDestructRoot)
{
// Stop image loading first
if (mImageRequest) {
// Deregister our image request from the refresh driver
nsLayoutUtils::DeregisterImageRequest(PresContext(),
mImageRequest,
&mRequestRegistered);
mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
mImageRequest = nsnull;
}
@ -175,8 +170,6 @@ nsBulletFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
if (same) {
needNewRequest = false;
} else {
nsLayoutUtils::DeregisterImageRequest(PresContext(), mImageRequest,
&mRequestRegistered);
mImageRequest->Cancel(NS_ERROR_FAILURE);
mImageRequest = nsnull;
}
@ -185,15 +178,10 @@ nsBulletFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
if (needNewRequest) {
newRequest->Clone(mListener, getter_AddRefs(mImageRequest));
nsLayoutUtils::RegisterImageRequest(PresContext(), mImageRequest,
&mRequestRegistered);
}
} else {
// No image request on the new style context
if (mImageRequest) {
nsLayoutUtils::DeregisterImageRequest(PresContext(), mImageRequest,
&mRequestRegistered);
mImageRequest->Cancel(NS_ERROR_FAILURE);
mImageRequest = nsnull;
}
@ -1533,17 +1521,6 @@ NS_IMETHODIMP nsBulletFrame::OnDataAvailable(imgIRequest *aRequest,
return NS_OK;
}
NS_IMETHODIMP nsBulletFrame::OnStartDecode(imgIRequest* aRequest)
{
// Register the image request with the refresh driver.
if (aRequest == mImageRequest) {
nsLayoutUtils::RegisterImageRequest(PresContext(), mImageRequest,
&mRequestRegistered);
}
return NS_OK;
}
NS_IMETHODIMP nsBulletFrame::OnStopDecode(imgIRequest *aRequest,
nsresult aStatus,
const PRUnichar *aStatusArg)
@ -1560,14 +1537,6 @@ NS_IMETHODIMP nsBulletFrame::OnStopDecode(imgIRequest *aRequest,
}
#endif
// Deregister the imgIRequest with the refresh driver if the
// image is not animated.
if (aRequest == mImageRequest) {
nsLayoutUtils::DeregisterImageRequestIfNotAnimated(PresContext(),
mImageRequest,
&mRequestRegistered);
}
return NS_OK;
}
@ -1665,25 +1634,17 @@ NS_IMETHODIMP nsBulletListener::OnDataAvailable(imgIRequest *aRequest,
const nsIntRect *aRect)
{
if (!mFrame)
return NS_OK;
return NS_ERROR_FAILURE;
return mFrame->OnDataAvailable(aRequest, aCurrentFrame, aRect);
}
NS_IMETHODIMP nsBulletListener::OnStartDecode(imgIRequest *aRequest)
{
if (!mFrame)
return NS_OK;
return mFrame->OnStartDecode(aRequest);
}
NS_IMETHODIMP nsBulletListener::OnStopDecode(imgIRequest *aRequest,
nsresult status,
const PRUnichar *statusArg)
{
if (!mFrame)
return NS_OK;
return NS_ERROR_FAILURE;
return mFrame->OnStopDecode(aRequest, status, statusArg);
}
@ -1692,7 +1653,7 @@ NS_IMETHODIMP nsBulletListener::FrameChanged(imgIContainer *aContainer,
const nsIntRect *aDirtyRect)
{
if (!mFrame)
return NS_OK;
return NS_ERROR_FAILURE;
return mFrame->FrameChanged(aContainer, aDirtyRect);
}

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

@ -84,7 +84,6 @@ public:
NS_IMETHOD OnDataAvailable(imgIRequest *aRequest,
bool aCurrentFrame,
const nsIntRect *aRect);
NS_IMETHOD OnStartDecode(imgIRequest *aRequest);
NS_IMETHOD OnStopDecode(imgIRequest *aRequest,
nsresult aStatus,
const PRUnichar *aStatusArg);
@ -122,12 +121,6 @@ protected:
nsSize mComputedSize;
PRInt32 mOrdinal;
bool mTextIsRTL;
private:
// This is a boolean flag indicating whether or not the current image request
// has been registered with the refresh driver.
bool mRequestRegistered;
};
#endif /* nsBulletFrame_h___ */

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

@ -579,10 +579,6 @@ protected:
/**
* Implements Destroy(). Do not call this directly except from within a
* DestroyFrom() implementation.
*
* @note This will always be called, so it is not necessary to override
* Destroy() in subclasses of nsFrame, just DestroyFrom().
*
* @param aDestructRoot is the root of the subtree being destroyed
*/
virtual void DestroyFrom(nsIFrame* aDestructRoot) = 0;

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

@ -231,10 +231,6 @@ nsImageFrame::DestroyFrom(nsIFrame* aDestructRoot)
nsCxPusher pusher;
pusher.PushNull();
// Notify our image loading content that we are going away so it can
// deregister with our refresh driver.
imageLoader->FrameDestroyed(this);
imageLoader->RemoveObserver(mListener);
}
@ -280,10 +276,6 @@ nsImageFrame::Init(nsIContent* aContent,
if (!gIconLoad)
LoadIcons(aPresContext);
// We have a PresContext now, so we need to notify the image content node
// that it can register images.
imageLoader->FrameCreated(this);
// Give image loads associated with an image frame a small priority boost!
nsCOMPtr<imgIRequest> currentRequest;
imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,

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

@ -103,7 +103,6 @@ public:
NS_IMETHOD Init(nsIContent* aContent,
nsIFrame* aParent,
nsIFrame* aPrevInFlow);
virtual void DestroyFrom(nsIFrame* aDestructRoot);
/**
* Get the "type" of the frame
@ -180,10 +179,6 @@ nsSVGImageFrame::Init(nsIContent* aContent,
nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
NS_ENSURE_TRUE(imageLoader, NS_ERROR_UNEXPECTED);
// We should have a PresContext now, so let's notify our image loader that
// we need to register any image animations with the refresh driver.
imageLoader->FrameCreated(this);
// Push a null JSContext on the stack so that code that runs within
// the below code doesn't think it's being called by JS. See bug
// 604262.
@ -195,19 +190,6 @@ nsSVGImageFrame::Init(nsIContent* aContent,
return NS_OK;
}
/* virtual */ void
nsSVGImageFrame::DestroyFrom(nsIFrame* aDestructRoot)
{
nsCOMPtr<nsIImageLoadingContent> imageLoader =
do_QueryInterface(nsFrame::mContent);
if (imageLoader) {
imageLoader->FrameDestroyed(this);
}
nsFrame::DestroyFrom(aDestructRoot);
}
//----------------------------------------------------------------------
// nsIFrame methods:

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

@ -36,7 +36,6 @@
#include "nsFrame.h"
#include "nsSVGEffects.h"
#include "nsImageLoadingContent.h"
class nsSVGLeafFrame : public nsFrame
{
@ -48,12 +47,6 @@ protected:
public:
NS_DECL_FRAMEARENA_HELPERS
NS_IMETHOD Init(nsIContent* aContent,
nsIFrame* aParent,
nsIFrame* asPrevInFlow);
virtual void DestroyFrom(nsIFrame* aDestructRoot);
virtual bool IsFrameOfType(PRUint32 aFlags) const
{
return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG));
@ -77,33 +70,6 @@ NS_NewSVGLeafFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
NS_IMPL_FRAMEARENA_HELPERS(nsSVGLeafFrame)
NS_IMETHODIMP
nsSVGLeafFrame::Init(nsIContent* aContent, nsIFrame* aParent, nsIFrame* asPrevInFlow)
{
nsFrame::Init(aContent, aParent, asPrevInFlow);
nsCOMPtr<nsIImageLoadingContent> imageLoader =
do_QueryInterface(nsFrame::mContent);
if (imageLoader) {
imageLoader->FrameCreated(this);
}
return NS_OK;
}
/* virtual */ void
nsSVGLeafFrame::DestroyFrom(nsIFrame* aDestructRoot)
{
nsCOMPtr<nsIImageLoadingContent> imageLoader =
do_QueryInterface(nsFrame::mContent);
if (imageLoader) {
imageLoader->FrameDestroyed(this);
}
nsFrame::DestroyFrom(aDestructRoot);
}
/* virtual */ void
nsSVGLeafFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
{

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

@ -72,7 +72,6 @@
#include "nsIDOMDocument.h"
#include "nsTransform2D.h"
#include "nsITheme.h"
#include "nsIImageLoadingContent.h"
#include "nsIServiceManager.h"
#include "nsIURI.h"
@ -174,7 +173,6 @@ nsImageBoxFrame::AttributeChanged(PRInt32 aNameSpaceID,
nsImageBoxFrame::nsImageBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext):
nsLeafBoxFrame(aShell, aContext),
mIntrinsicSize(0,0),
mRequestRegistered(false),
mLoadFlags(nsIRequest::LOAD_NORMAL),
mUseSrcAttr(false),
mSuppressStyleCheck(false)
@ -197,16 +195,9 @@ nsImageBoxFrame::MarkIntrinsicWidthsDirty()
void
nsImageBoxFrame::DestroyFrom(nsIFrame* aDestructRoot)
{
if (mImageRequest) {
nsPresContext* presContext = PresContext();
NS_ASSERTION(presContext, "No PresContext");
nsLayoutUtils::DeregisterImageRequest(presContext,
mImageRequest,
&mRequestRegistered);
// Release image loader first so that it's refcnt can go to zero
// Release image loader first so that it's refcnt can go to zero
if (mImageRequest)
mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
}
if (mListener)
reinterpret_cast<nsImageBoxListener*>(mListener.get())->SetFrame(nsnull); // set the frame to null so we don't send messages to a dead object.
@ -241,12 +232,7 @@ nsImageBoxFrame::Init(nsIContent* aContent,
void
nsImageBoxFrame::UpdateImage()
{
nsPresContext* presContext = PresContext();
NS_ASSERTION(presContext, "No PresContext");
if (mImageRequest) {
nsLayoutUtils::DeregisterImageRequest(presContext, mImageRequest,
&mRequestRegistered);
mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
mImageRequest = nsnull;
}
@ -273,12 +259,6 @@ nsImageBoxFrame::UpdateImage()
nsContentUtils::LoadImage(uri, doc, mContent->NodePrincipal(),
doc->GetDocumentURI(), mListener, mLoadFlags,
getter_AddRefs(mImageRequest));
// Register our imgIRequest with the refresh driver
nsLayoutUtils::RegisterImageRequest(presContext,
mImageRequest,
&mRequestRegistered);
}
} else {
// Only get the list-style-image if we aren't being drawn
@ -603,31 +583,10 @@ NS_IMETHODIMP nsImageBoxFrame::OnStopContainer(imgIRequest *request,
return NS_OK;
}
NS_IMETHODIMP nsImageBoxFrame::OnStartDecode(imgIRequest* aRequest)
{
nsPresContext* presContext = PresContext();
NS_ASSERTION(presContext, "No PresContext");
nsLayoutUtils::RegisterImageRequest(presContext,
mImageRequest,
&mRequestRegistered);
return NS_OK;
}
NS_IMETHODIMP nsImageBoxFrame::OnStopDecode(imgIRequest *request,
nsresult aStatus,
const PRUnichar *statusArg)
{
// If the imgIRequest does not represent an animated image, then we should
// deregister it from our refresh driver.
nsPresContext* presContext = PresContext();
NS_ASSERTION(presContext, "No PresContext");
nsLayoutUtils::DeregisterImageRequestIfNotAnimated(presContext,
mImageRequest,
&mRequestRegistered);
if (NS_SUCCEEDED(aStatus))
// Fire an onload DOM event.
FireImageDOMEvent(mContent, NS_LOAD);
@ -665,7 +624,7 @@ NS_IMETHODIMP nsImageBoxListener::OnStartContainer(imgIRequest *request,
imgIContainer *image)
{
if (!mFrame)
return NS_OK;
return NS_ERROR_FAILURE;
return mFrame->OnStartContainer(request, image);
}
@ -674,26 +633,17 @@ NS_IMETHODIMP nsImageBoxListener::OnStopContainer(imgIRequest *request,
imgIContainer *image)
{
if (!mFrame)
return NS_OK;
return NS_ERROR_FAILURE;
return mFrame->OnStopContainer(request, image);
}
NS_IMETHODIMP nsImageBoxListener::OnStartDecode(imgIRequest *aRequest)
{
if (!mFrame) {
return NS_OK;
}
return mFrame->OnStartDecode(aRequest);
}
NS_IMETHODIMP nsImageBoxListener::OnStopDecode(imgIRequest *request,
nsresult status,
const PRUnichar *statusArg)
{
if (!mFrame)
return NS_OK;
return NS_ERROR_FAILURE;
return mFrame->OnStopDecode(request, status, statusArg);
}

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

@ -56,7 +56,6 @@ public:
// imgIDecoderObserver (override nsStubImageDecoderObserver)
NS_IMETHOD OnStartContainer(imgIRequest *request, imgIContainer *image);
NS_IMETHOD OnStopContainer(imgIRequest *request, imgIContainer *image);
NS_IMETHOD OnStartDecode(imgIRequest *aRequest);
NS_IMETHOD OnStopDecode(imgIRequest *request, nsresult status,
const PRUnichar *statusArg);
// imgIContainerObserver (override nsStubImageDecoderObserver)
@ -120,7 +119,6 @@ public:
NS_IMETHOD OnStartContainer(imgIRequest *request, imgIContainer *image);
NS_IMETHOD OnStopContainer(imgIRequest *request, imgIContainer *image);
NS_IMETHOD OnStartDecode(imgIRequest *aRequest);
NS_IMETHOD OnStopDecode(imgIRequest *request,
nsresult status,
const PRUnichar *statusArg);
@ -144,10 +142,6 @@ private:
nsSize mIntrinsicSize;
nsSize mImageSize;
// Boolean variable to determine if the current image request has been
// registered with the refresh driver.
bool mRequestRegistered;
nsCOMPtr<imgIRequest> mImageRequest;
nsCOMPtr<imgIDecoderObserver> mListener;

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

@ -1,64 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Dave Hyatt <hyatt@mozilla.org> (Original Author)
* Jan Varga <varga@ku.sk>
* Scott Johnson <sjohnson@mozilla.com>, Mozilla Corporation
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef nsITreeImageListener_h__
#define nsITreeImageListener_h__
// The interface for our image listener.
// {90586540-2D50-403e-8DCE-981CAA778444}
#define NS_ITREEIMAGELISTENER_IID \
{ 0x90586540, 0x2d50, 0x403e, { 0x8d, 0xce, 0x98, 0x1c, 0xaa, 0x77, 0x84, 0x44 } }
class nsITreeImageListener : public nsISupports
{
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITREEIMAGELISTENER_IID)
NS_IMETHOD AddCell(PRInt32 aIndex, nsITreeColumn* aCol) = 0;
/**
* Clear the internal frame pointer to prevent dereferencing an object
* that no longer exists.
*/
NS_IMETHOD ClearFrame() = 0;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsITreeImageListener, NS_ITREEIMAGELISTENER_IID)
#endif

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

@ -54,7 +54,6 @@
#include "nsTreeBodyFrame.h"
#include "nsTreeSelection.h"
#include "nsTreeImageListener.h"
#include "nsGkAtoms.h"
#include "nsCSSAnonBoxes.h"
@ -115,14 +114,6 @@ static PLDHashOperator
CancelImageRequest(const nsAString& aKey,
nsTreeImageCacheEntry aEntry, void* aData)
{
// If our imgIRequest object was registered with the refresh driver,
// then we need to deregister it.
nsTreeBodyFrame* frame = static_cast<nsTreeBodyFrame*>(aData);
nsLayoutUtils::DeregisterImageRequest(frame->PresContext(), aEntry.request,
nsnull);
aEntry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
return PL_DHASH_NEXT;
}
@ -173,8 +164,7 @@ nsTreeBodyFrame::nsTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aCont
// Destructor
nsTreeBodyFrame::~nsTreeBodyFrame()
{
mImageCache.EnumerateRead(CancelImageRequest, this);
DetachImageListeners();
mImageCache.EnumerateRead(CancelImageRequest, nsnull);
delete mSlots;
}
@ -207,11 +197,8 @@ nsTreeBodyFrame::Init(nsIContent* aContent,
mIndentation = GetIndentation();
mRowHeight = GetRowHeight();
NS_ENSURE_TRUE(mCreatedListeners.Init(), NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_TRUE(mImageCache.Init(16), NS_ERROR_OUT_OF_MEMORY);
EnsureBoxObject();
return rv;
}
@ -2167,14 +2154,10 @@ nsTreeBodyFrame::GetImage(PRInt32 aRowIndex, nsTreeColumn* aCol, bool aUseContex
if (!*aResult) {
// Create a new nsTreeImageListener object and pass it our row and column
// information.
nsTreeImageListener* listener = new nsTreeImageListener(this);
nsTreeImageListener* listener = new nsTreeImageListener(mTreeBoxObject);
if (!listener)
return NS_ERROR_OUT_OF_MEMORY;
if (!mCreatedListeners.PutEntry(listener)) {
return NS_ERROR_FAILURE;
}
listener->AddCell(aRowIndex, aCol);
nsCOMPtr<imgIDecoderObserver> imgDecoderObserver = listener;
@ -4253,7 +4236,7 @@ nsresult
nsTreeBodyFrame::ClearStyleAndImageCaches()
{
mStyleCache.Clear();
mImageCache.EnumerateRead(CancelImageRequest, this);
mImageCache.EnumerateRead(CancelImageRequest, nsnull);
mImageCache.Clear();
return NS_OK;
}
@ -4485,20 +4468,6 @@ nsTreeBodyFrame::PostScrollEvent()
}
}
void
nsTreeBodyFrame::DetachImageListeners()
{
mCreatedListeners.Clear();
}
void
nsTreeBodyFrame::RemoveTreeImageListener(nsTreeImageListener* aListener)
{
if (aListener) {
mCreatedListeners.RemoveEntry(aListener);
}
}
#ifdef ACCESSIBILITY
void
nsTreeBodyFrame::FireRowCountChangedEvent(PRInt32 aIndex, PRInt32 aCount)
@ -4672,19 +4641,3 @@ nsTreeBodyFrame::FullScrollbarsUpdate(bool aNeedsFullInvalidation)
nsContentUtils::AddScriptRunner(new nsOverflowChecker(this));
return weakFrame.IsAlive();
}
nsresult
nsTreeBodyFrame::OnStartDecode(imgIRequest* aRequest)
{
nsLayoutUtils::RegisterImageRequest(PresContext(), aRequest, nsnull);
return NS_OK;
}
nsresult
nsTreeBodyFrame::OnStopDecode(imgIRequest* aRequest, nsresult aStatus,
const PRUnichar* aStatusArg)
{
nsLayoutUtils::DeregisterImageRequestIfNotAnimated(PresContext(), aRequest,
nsnull);
return NS_OK;
}

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

@ -54,6 +54,7 @@
#include "nsTArray.h"
#include "nsTreeStyleCache.h"
#include "nsTreeColumns.h"
#include "nsTreeImageListener.h"
#include "nsAutoPtr.h"
#include "nsDataHashtable.h"
#include "imgIRequest.h"
@ -61,10 +62,8 @@
#include "nsScrollbarFrame.h"
#include "nsThreadUtils.h"
#include "mozilla/LookAndFeel.h"
#include "nsITreeImageListener.h"
class nsOverflowChecker;
class nsTreeImageListener;
// An entry in the tree's image cache
struct nsTreeImageCacheEntry
@ -92,13 +91,6 @@ public:
NS_DECL_QUERYFRAME
NS_DECL_FRAMEARENA_HELPERS
// Callback handler methods for refresh driver based animations.
// Calls to these functions are forwarded from nsTreeImageListener. These
// mirror how nsImageFrame works.
nsresult OnStartDecode(imgIRequest* aRequest);
nsresult OnStopDecode(imgIRequest* aRequest, nsresult aStatus,
const PRUnichar* aStatusArg);
// non-virtual signatures like nsITreeBodyFrame
nsresult GetColumns(nsITreeColumns **aColumns);
nsresult GetView(nsITreeView **aView);
@ -443,15 +435,6 @@ public:
return col;
}
/**
* Remove an nsITreeImageListener from being tracked by this frame. Only tree
* image listeners that are created by this frame are tracked.
*
* @param aListener A pointer to an nsTreeImageListener to no longer
* track.
*/
void RemoveTreeImageListener(nsTreeImageListener* aListener);
protected:
// Create a new timer. This method is used to delay various actions like
@ -482,12 +465,6 @@ protected:
void PostScrollEvent();
void FireScrollEvent();
/**
* Clear the pointer to this frame for all nsTreeImageListeners that were
* created by this frame.
*/
void DetachImageListeners();
#ifdef ACCESSIBILITY
/**
* Fires 'treeRowCountChanged' event asynchronously. The event supports
@ -625,11 +602,6 @@ protected: // Data Members
bool mHorizontalOverflow;
bool mReflowCallbackPosted;
// Hash table to keep track of which listeners we created and thus
// have pointers to us.
nsTHashtable<nsPtrHashKey<nsTreeImageListener> > mCreatedListeners;
}; // class nsTreeBodyFrame
#endif

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

@ -44,8 +44,8 @@
NS_IMPL_ISUPPORTS3(nsTreeImageListener, imgIDecoderObserver, imgIContainerObserver, nsITreeImageListener)
nsTreeImageListener::nsTreeImageListener(nsTreeBodyFrame* aTreeFrame)
: mTreeFrame(aTreeFrame),
nsTreeImageListener::nsTreeImageListener(nsITreeBoxObject* aTree)
: mTree(aTree),
mInvalidationSuppressed(true),
mInvalidationArea(nsnull)
{
@ -56,29 +56,6 @@ nsTreeImageListener::~nsTreeImageListener()
delete mInvalidationArea;
}
NS_IMETHODIMP
nsTreeImageListener::OnStartDecode(imgIRequest *aRequest)
{
if (!mTreeFrame) {
return NS_OK;
}
// grab the frame we want to use
return mTreeFrame->OnStartDecode(aRequest);
}
NS_IMETHODIMP
nsTreeImageListener::OnStopDecode(imgIRequest *aRequest,
nsresult aStatus,
const PRUnichar *aStatusArg)
{
if (!mTreeFrame) {
return NS_OK;
}
return mTreeFrame->OnStopDecode(aRequest, aStatus, aStatusArg);
}
NS_IMETHODIMP nsTreeImageListener::OnStartContainer(imgIRequest *aRequest,
imgIContainer *aImage)
{
@ -136,16 +113,10 @@ void
nsTreeImageListener::Invalidate()
{
if (!mInvalidationSuppressed) {
for (InvalidationArea* currArea = mInvalidationArea; currArea;
currArea = currArea->GetNext()) {
for (InvalidationArea* currArea = mInvalidationArea; currArea; currArea = currArea->GetNext()) {
// Loop from min to max, invalidating each cell that was listening for this image.
for (PRInt32 i = currArea->GetMin(); i <= currArea->GetMax(); ++i) {
if (mTreeFrame) {
nsITreeBoxObject* tree = mTreeFrame->GetTreeBoxObject();
if (tree) {
tree->InvalidateCell(i, currArea->GetCol());
}
}
mTree->InvalidateCell(i, currArea->GetCol());
}
}
}
@ -169,10 +140,3 @@ nsTreeImageListener::InvalidationArea::AddRow(PRInt32 aIndex)
else if (aIndex > mMax)
mMax = aIndex;
}
NS_IMETHODIMP
nsTreeImageListener::ClearFrame()
{
mTreeFrame = nsnull;
return NS_OK;
}

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

@ -44,21 +44,33 @@
#include "nsCOMPtr.h"
#include "nsITreeColumns.h"
#include "nsStubImageDecoderObserver.h"
#include "nsTreeBodyFrame.h"
#include "nsITreeImageListener.h"
class nsITreeBoxObject;
// The interface for our image listener.
// {90586540-2D50-403e-8DCE-981CAA778444}
#define NS_ITREEIMAGELISTENER_IID \
{ 0x90586540, 0x2d50, 0x403e, { 0x8d, 0xce, 0x98, 0x1c, 0xaa, 0x77, 0x84, 0x44 } }
class nsITreeImageListener : public nsISupports
{
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITREEIMAGELISTENER_IID)
NS_IMETHOD AddCell(PRInt32 aIndex, nsITreeColumn* aCol) = 0;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsITreeImageListener, NS_ITREEIMAGELISTENER_IID)
// This class handles image load observation.
class nsTreeImageListener : public nsStubImageDecoderObserver, public nsITreeImageListener
{
public:
nsTreeImageListener(nsTreeBodyFrame *aTreeFrame);
nsTreeImageListener(nsITreeBoxObject* aTree);
~nsTreeImageListener();
NS_DECL_ISUPPORTS
// imgIDecoderObserver (override nsStubImageDecoderObserver)
NS_IMETHOD OnStartDecode(imgIRequest *aRequest);
NS_IMETHOD OnStopDecode(imgIRequest *aRequest,
nsresult aStatus, const PRUnichar *aStatusArg);
NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
NS_IMETHOD OnDataAvailable(imgIRequest *aRequest, bool aCurrentFrame,
const nsIntRect *aRect);
@ -67,8 +79,7 @@ public:
const nsIntRect *aDirtyRect);
NS_IMETHOD AddCell(PRInt32 aIndex, nsITreeColumn* aCol);
NS_IMETHOD ClearFrame();
friend class nsTreeBodyFrame;
protected:
@ -76,7 +87,7 @@ protected:
void Invalidate();
private:
nsTreeBodyFrame* mTreeFrame;
nsITreeBoxObject* mTree;
// A guard that prevents us from recursive painting.
bool mInvalidationSuppressed;

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

@ -96,6 +96,7 @@
</div>
<div id="sync-setup">
<a id="syncSetupSync" href="#" onclick="openSetupSyncWizard();">&aboutHome.setupSync;</a>
<a id="syncPairDevice" href="#" onclick="openPairDeviceWizard();">&aboutHome.syncPairDevice;</a>
</div>
</div>
</div>
@ -433,8 +434,10 @@
function initSetupSync() {
let services = getChromeWin().Services;
if (services.prefs.prefHasUserValue("services.sync.username")) {
document.getElementById("syncSetupSync").style.display = "none";
}
syncConnected();
} else {
syncDisconnected();
}
services.obs.addObserver(syncConnected, "weave:service:setup-complete", false);
services.obs.addObserver(syncDisconnected, "weave:service:start-over", false);
}
@ -447,10 +450,12 @@
function syncConnected() {
document.getElementById("syncSetupSync").style.display = "none";
document.getElementById("syncPairDevice").style.display = "inline";
}
function syncDisconnected() {
document.getElementById("syncSetupSync").style.display = "inline";
document.getElementById("syncPairDevice").style.display = "none";
}
function openSetupSyncWizard() {
@ -458,6 +463,11 @@
chromeWin.WeaveGlue.open();
}
function openPairDeviceWizard() {
let chromeWin = getChromeWin();
chromeWin.SyncPairDevice.open();
}
function initLightbox() {
let prefs = getChromeWin().Services.prefs;
let channel = prefs.getCharPref("app.update.channel");

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

@ -113,6 +113,7 @@ XPCOMUtils.defineLazyGetter(this, "CommonUI", function() {
["MasterPasswordUI", "chrome://browser/content/MasterPasswordUI.js"],
#ifdef MOZ_SERVICES_SYNC
["WeaveGlue", "chrome://browser/content/sync.js"],
["SyncPairDevice", "chrome://browser/content/sync.js"],
#endif
["WebappsUI", "chrome://browser/content/WebappsUI.js"],
["SSLExceptions", "chrome://browser/content/exceptions.js"],

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

@ -24,6 +24,7 @@
- Brad Lassey <blassey@mozilla.com>
- Mark Finkle <mfinkle@mozila.com>
- Matt Brubeck <mbrubeck@mozila.com>
- Allison Naaktgeboren <ally@mozilla.com>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
@ -487,6 +488,7 @@
<button label="&sync.connect;" oncommand="WeaveGlue.tryConnect();" />
</setting>
<setting id="sync-connected" class="setting-group" title="&sync.connected;" type="control" collapsed="true">
<button id="sync-pairdevice" label="&sync.pair.title;" oncommand="SyncPairDevice.open();" />
<button id="sync-details" label="&sync.details;" type="checkbox" autocheck="false" checked="false" oncommand="WeaveGlue.showDetails();" />
</setting>
<setting id="sync-sync" class="setting-subgroup" type="control" collapsed="true">
@ -593,7 +595,15 @@
</hbox>
</vbox>
<vbox id="syncsetup-waiting" class="syncsetup-page" flex="1" hidden="true">
<description class="syncsetup-desc syncsetup-center" flex="1">&sync.setup.waiting;</description>
<progressmeter id="syncsetup-progressbar" mode="undetermined"/>
<vbox id="syncsetup-waiting-top" align="center" flex="1">
<description id="syncsetup-waiting-desc" class="syncsetup-desc syncsetup-center" flex="1">&sync.setup.waiting2;</description>
<description id="syncsetup-waitingdownload-desc" class="syncsetup-desc syncsetup-center" hidden="true" flex="1">&sync.setup.waitingdownload;</description>
</vbox>
<hbox class="prompt-buttons" pack="center" align="end">
<button id="syncsetup-waiting-cancel" class="prompt-button" oncommand="WeaveGlue.close();">&sync.setup.cancel;</button>
<button id="syncsetup-waiting-close" class="prompt-button" hidden="true" oncommand="WeaveGlue.close();">&sync.setup.close;</button>
</hbox>
</vbox>
<vbox id="syncsetup-fallback" class="syncsetup-page" flex="1" hidden="true">
<scrollbox class="prompt-message" orient="vertical" flex="1">
@ -618,6 +628,32 @@
</vbox>
</dialog>
</box>
<box id="syncpair-container" class="perm-modal-block" hidden="true">
<dialog id="syncpair-dialog" class="content-dialog" flex="1">
<hbox class="prompt-title">
<description>&sync.pair.title;</description>
</hbox>
<separator class="prompt-line"/>
<vbox id="syncpair-simple" class="syncsetup-page" flex="1">
<scrollbox id="sync-message" class="prompt-message" orient="vertical" flex="1">
<description class="syncsetup-desc syncsetup-center" flex="1">&sync.pair.description;</description>
<description class="syncsetup-center link" flex="1" onclick="SyncPairDevice.close(); WeaveGlue.openTutorial();">&sync.setup.tutorial;</description>
<separator/>
<vbox align="center" flex="1">
<textbox id="syncpair-code1" class="syncsetup-code" oninput="SyncPairDevice.onTextBoxInput(this);"/>
<textbox id="syncpair-code2" class="syncsetup-code" oninput="SyncPairDevice.onTextBoxInput(this);"/>
<textbox id="syncpair-code3" class="syncsetup-code" oninput="SyncPairDevice.onTextBoxInput(this);"/>
</vbox>
<separator flex="1"/>
</scrollbox>
<hbox class="prompt-buttons" pack="center">
<button class="prompt-button" oncommand="SyncPairDevice.close();">&sync.setup.cancel;</button>
<button id="syncpair-connectbutton" class="prompt-button" disabled="true" oncommand="SyncPairDevice.connect();">&sync.setup.connect;</button>
</hbox>
</vbox>
</dialog>
</box>
#endif
<arrowbox id="search-engines-popup" hidden="true" offset="18" flex="1" type="dialog">

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

@ -21,6 +21,7 @@
* Mark Finkle <mfinkle@mozila.com>
* Matt Brubeck <mbrubeck@mozila.com>
* Jono DiCarlo <jdicarlo@mozilla.com>
* Allison Naaktgeboren <ally@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -38,9 +39,14 @@
let WeaveGlue = {
setupData: null,
_boundOnEngineSync: null, // Needed to unhook the observers in close().
_boundOnServiceSync: null,
jpake: null,
_bundle: null,
_loginError: false,
_progressBar: null,
_progressValue: 0,
_progressMax: null,
init: function init() {
if (this._bundle)
@ -64,6 +70,9 @@ let WeaveGlue = {
} else if (Weave.Status.login != Weave.LOGIN_FAILED_NO_USERNAME) {
this.loadSetupData();
}
this._boundOnEngineSync = this.onEngineSync.bind(this);
this._boundOnServiceSync = this.onServiceSync.bind(this);
this._progressBar = document.getElementById("syncsetup-progressbar");
},
abortEasySetup: function abortEasySetup() {
@ -128,7 +137,16 @@ let WeaveGlue = {
onComplete: function onComplete(aCredentials) {
self.jpake = null;
self.close();
self._progressBar.mode = "determined";
document.getElementById("syncsetup-waiting-desc").hidden = true;
document.getElementById("syncsetup-waiting-cancel").hidden = true;
document.getElementById("syncsetup-waitingdownload-desc").hidden = false;
document.getElementById("syncsetup-waiting-close").hidden = false;
Services.obs.addObserver(self._boundOnEngineSync, "weave:engine:sync:finish", false);
Services.obs.addObserver(self._boundOnEngineSync, "weave:engine:sync:error", false);
Services.obs.addObserver(self._boundOnServiceSync, "weave:service:sync:finish", false);
Services.obs.addObserver(self._boundOnServiceSync, "weave:service:sync:error", false);
self.setupData = aCredentials;
self.connect();
},
@ -180,6 +198,7 @@ let WeaveGlue = {
this._resetScrollPosition();
document.getElementById("syncsetup-simple").hidden = true;
document.getElementById("syncsetup-waiting").hidden = true;
document.getElementById("syncsetup-fallback").hidden = false;
// Push the current setup data into the UI
@ -204,7 +223,37 @@ let WeaveGlue = {
this.canConnect();
},
onEngineSync: function onEngineSync(subject, topic, data) {
// The Clients engine syncs first. At this point we don't necessarily know
// yet how many engines will be enabled, so we'll ignore the Clients engine
// and evaluate how many engines are enabled when the first "real" engine
// syncs.
if (data == 'clients') {
return;
}
if (this._progressMax == null) {
this._progressMax = Weave.Engines.getEnabled().length;
this._progressBar.max = this._progressMax;
}
this._progressValue += 1;
this._progressBar.setAttribute("value", this._progressValue);
},
onServiceSync: function onServiceSync() {
this.close();
},
close: function close() {
try {
Services.obs.removeObserver(this._boundOnEngineSync, "weave:engine:sync:finish");
Services.obs.removeObserver(this._boundOnEngineSync, "weave:engine:sync:error");
Services.obs.removeObserver(this._boundOnServiceSync, "weave:service:sync:finish");
Services.obs.removeObserver(this._boundOnServiceSync, "weave:service:sync:error");
}
catch(e) {
// Observers weren't registered because we never got as far as onComplete.
}
if (this.jpake)
this.abortEasySetup();
@ -226,6 +275,15 @@ let WeaveGlue = {
this._elements.usecustomserver.checked = false;
this._elements.customserver.disabled = true;
this._elements.customserver.value = "";
document.getElementById("syncsetup-waiting-desc").hidden = false;
document.getElementById("syncsetup-waiting-cancel").hidden = false;
document.getElementById("syncsetup-waitingdownload-desc").hidden = true;
document.getElementById("syncsetup-waiting-close").hidden = true;
this._progressMax = null;
this._progressValue = 0;
this._progressBar.max = 0;
this._progressBar.value = 0;
this._progressBar.mode = "undetermined";
// Close the connect UI
document.getElementById("syncsetup-container").hidden = true;
@ -372,7 +430,7 @@ let WeaveGlue = {
elements[id] = document.getElementById("syncsetup-" + id);
});
let settingids = ["device", "connect", "connected", "disconnect", "sync", "details"];
let settingids = ["device", "connect", "connected", "disconnect", "sync", "details", "pairdevice"];
settingids.forEach(function(id) {
elements[id] = document.getElementById("sync-" + id);
});
@ -397,6 +455,7 @@ let WeaveGlue = {
let device = this._elements.device;
let disconnect = this._elements.disconnect;
let sync = this._elements.sync;
let pairdevice = this._elements.pairdevice;
// Show what went wrong with login if necessary
if (aTopic == "weave:ui:login:error") {
@ -541,3 +600,95 @@ let WeaveGlue = {
this.setupData.serverURL = serverURL;
}
};
const PIN_PART_LENGTH = 4;
let SyncPairDevice = {
jpake: null,
open: function open() {
this.code1.setAttribute("maxlength", PIN_PART_LENGTH);
this.code2.setAttribute("maxlength", PIN_PART_LENGTH);
this.code3.setAttribute("maxlength", PIN_PART_LENGTH);
this.nextFocusEl = {code1: this.code2,
code2: this.code3,
code3: this.connectbutton};
document.getElementById("syncpair-container").hidden = false;
BrowserUI.pushDialog(this);
this.code1.focus();
// Kick off a sync. That way the server will have the most recent data from
// this computer and it will show up immediately on the new device.
Weave.SyncScheduler.scheduleNextSync(0);
},
close: function close() {
this.code1.value = this.code2.value = this.code3.value = "";
this.code1.disabled = this.code2.disabled = this.code3.disabled = false;
this.connectbutton.disabled = true;
if (this.jpake) {
this.jpake.abort();
this.jpake = null;
}
document.getElementById("syncpair-container").hidden = true;
BrowserUI.popDialog();
},
onTextBoxInput: function onTextBoxInput(textbox) {
if (textbox && textbox.value.length == PIN_PART_LENGTH) {
let name = textbox.id.split("-")[1];
this.nextFocusEl[name].focus();
}
this.connectbutton.disabled =
!(this.code1.value.length == PIN_PART_LENGTH &&
this.code2.value.length == PIN_PART_LENGTH &&
this.code3.value.length == PIN_PART_LENGTH);
},
connect: function connect() {
let self = this;
let jpake = this.jpake = new Weave.JPAKEClient({
onPaired: function onPaired() {
let credentials = {account: Weave.Service.account,
password: Weave.Service.password,
synckey: Weave.Service.passphrase,
serverURL: Weave.Service.serverURL};
jpake.sendAndComplete(credentials);
},
onComplete: function onComplete() {
self.jpake = null;
self.close();
// Schedule a Sync for soonish to fetch the data uploaded by the
// device with which we just paired.
Weave.SyncScheduler.scheduleNextSync(Weave.SyncScheduler.activeInterval);
},
onAbort: function onAbort(error) {
self.jpake = null;
// Aborted by user, ignore.
if (error == Weave.JPAKE_ERROR_USERABORT) {
return;
}
self.code1.value = self.code2.value = self.code3.value = "";
self.code1.disabled = self.code2.disabled = self.code3.disabled = false;
self.code1.focus();
}
});
this.code1.disabled = this.code2.disabled = this.code3.disabled = true;
this.connectbutton.disabled = true;
let pin = this.code1.value + this.code2.value + this.code3.value;
let expectDelay = false;
jpake.pairWithPIN(pin, expectDelay);
}
};
["code1", "code2", "code3", "connectbutton"].forEach(function (id) {
XPCOMUtils.defineLazyGetter(SyncPairDevice, id, function() {
return document.getElementById("syncpair-" + id);
});
});

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

@ -19,4 +19,13 @@
(aboutHome.forAndroid): Second line of a multi-line button. Treat as a subtitle.
-->
<!ENTITY aboutHome.forAndroid "for Android">
<!ENTITY aboutHome.setupSync "Set Up Sync">
<!-- LOCALIZATION NOTE:
(aboutHome.setupSync): This string should match the desktop
equivalent, in particular concerning syncBrand.fullName.label.
-->
<!ENTITY aboutHome.setupSync "Set Up Sync">
<!-- LOCALIZATION NOTE:
(aboutHome.syncPairDevice): This string should match the desktop
equivalent, in particular concerning syncBrand.fullName.label.
-->
<!ENTITY aboutHome.syncPairDevice "Pair a Device">

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

@ -19,4 +19,9 @@
<!ENTITY sync.setup.connect "Connect">
<!ENTITY sync.setup.cancel "Cancel">
<!ENTITY sync.setup.tutorial "Show me how">
<!ENTITY sync.setup.waiting "Pairing in progress…">
<!ENTITY sync.setup.waiting2 "Waiting for other device…">
<!ENTITY sync.pair.title "Pair a Device">
<!ENTITY sync.pair.description "To activate your new device, select &#x0022;Set Up Sync&#x0022; on the device.">
<!ENTITY sync.setup.close "Close">
<!ENTITY sync.setup.waitingdownload "Your data is now being downloaded in the background. You can close this window at any time.">

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

@ -267,7 +267,8 @@ body[dir="rtl"] {
text-align: center;
}
#syncSetupSync {
#syncSetupSync,
#syncPairDevice {
text-decoration: underline;
color: blue;
}

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

@ -1534,7 +1534,11 @@ setting {
}
#syncsetup-waiting {
padding: 10em 0;
padding: 2em 0 0 0;
}
#syncsetup-waiting-top {
padding: 1em;
}
/* content scrollbars */

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

@ -65,7 +65,6 @@ typedef int (*PR_CALLBACK PrefChangedFunc)(const char *, void *);
namespace mozilla {
class Preferences : public nsIPrefService,
public nsIPrefServiceInternal,
public nsIObserver,
public nsIPrefBranchInternal,
public nsSupportsWeakReference
@ -73,7 +72,6 @@ class Preferences : public nsIPrefService,
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIPREFSERVICE
NS_DECL_NSIPREFSERVICEINTERNAL
NS_FORWARD_NSIPREFBRANCH(sRootBranch->)
NS_FORWARD_NSIPREFBRANCH2(sRootBranch->)
NS_DECL_NSIOBSERVER
@ -341,6 +339,14 @@ public:
static nsresult GetDefaultComplex(const char* aPref, const nsIID &aType,
void** aResult);
// Used to synchronise preferences between chrome and content processes.
static nsresult ReadExtensionPrefs(nsIFile *aFile);
static void MirrorPreferences(nsTArray<PrefTuple,
nsTArrayInfallibleAllocator> *aArray);
static bool MirrorPreference(const char *aPref, PrefTuple *aTuple);
static void ClearContentPref(const char *aPref);
static void SetPreference(const PrefTuple *aTuple);
protected:
nsresult NotifyServiceObservers(const char *aSubject);
nsresult UseDefaultPrefFile();

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

@ -163,29 +163,6 @@ interface nsIPrefService : nsISupports
};
[scriptable, uuid(08c8cd2f-8345-45ee-938d-37ee6d3661b2)]
interface nsIPrefServiceInternal : nsISupports
{
/**
* Called to read the preferences in the defaults/preferences/
* directory of a zip file
*
* @param aFile The zip file to be read.
*
* @return NS_OK The file was read and processed.
* @return Other The file failed to read or contained invalid data.
*
* @see readUserPrefs
*/
void readExtensionPrefs(in nsILocalFile aFile);
[noscript] void mirrorPreferences(in nsPreferencesArrayPtr aArray);
[noscript] void mirrorPreference(in ACString aPrefName, in nsPreferencePtr aPref);
[noscript] boolean prefHasUserValue(in ACString aPrefName);
[noscript] void setPreference(in nsPreferencePtrConst aPref);
[noscript] void clearContentPref(in ACString aPrefName);
};
%{C++
#define NS_PREFSERVICE_CID \

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

@ -303,7 +303,6 @@ NS_IMPL_THREADSAFE_RELEASE(Preferences)
NS_INTERFACE_MAP_BEGIN(Preferences)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefService)
NS_INTERFACE_MAP_ENTRY(nsIPrefService)
NS_INTERFACE_MAP_ENTRY(nsIPrefServiceInternal)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsIPrefBranch)
NS_INTERFACE_MAP_ENTRY(nsIPrefBranch2)
@ -463,9 +462,8 @@ Preferences::SavePrefFile(nsIFile *aFile)
return SavePrefFileInternal(aFile);
}
/* part of nsIPrefServiceInternal */
NS_IMETHODIMP
Preferences::ReadExtensionPrefs(nsILocalFile *aFile)
nsresult
Preferences::ReadExtensionPrefs(nsIFile *aFile)
{
nsresult rv;
nsCOMPtr<nsIZipReader> reader = do_CreateInstance(kZipReaderCID, &rv);
@ -513,47 +511,35 @@ Preferences::ReadExtensionPrefs(nsILocalFile *aFile)
return rv;
}
NS_IMETHODIMP
Preferences::PrefHasUserValue(const nsACString& aPrefName, bool* aHasValue)
{
*aHasValue = PREF_HasUserPref(aPrefName.BeginReading());
return NS_OK;
}
NS_IMETHODIMP
void
Preferences::SetPreference(const PrefTuple *aPref)
{
return pref_SetPrefTuple(*aPref, true);
pref_SetPrefTuple(*aPref, true);
}
NS_IMETHODIMP
Preferences::ClearContentPref(const nsACString& aPrefName)
void
Preferences::ClearContentPref(const char *aPref)
{
return PREF_ClearUserPref(aPrefName.BeginReading());
PREF_ClearUserPref(aPref);
}
NS_IMETHODIMP
Preferences::MirrorPreference(const nsACString& aPrefName, PrefTuple *aPref)
bool
Preferences::MirrorPreference(const char *aPref, PrefTuple *aTuple)
{
PrefHashEntry *pref = pref_HashTableLookup(PromiseFlatCString(aPrefName).get());
PrefHashEntry *entry = pref_HashTableLookup(aPref);
if (!entry)
return false;
if (!pref)
return NS_ERROR_NOT_AVAILABLE;
pref_GetTupleFromEntry(pref, aPref);
return NS_OK;
pref_GetTupleFromEntry(entry, aTuple);
return true;
}
NS_IMETHODIMP
void
Preferences::MirrorPreferences(nsTArray<PrefTuple,
nsTArrayInfallibleAllocator> *aArray)
{
aArray->SetCapacity(PL_DHASH_TABLE_SIZE(&gHashTable));
PL_DHashTableEnumerate(&gHashTable, pref_MirrorPrefs, aArray);
return NS_OK;
}
NS_IMETHODIMP

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

@ -724,7 +724,7 @@ SyncEngine.prototype = {
// Delete any existing data and reupload on bad version or missing meta.
// No crypto component here...? We could regenerate per-collection keys...
if (needsWipe) {
this.wipeServer(true);
this.wipeServer();
}
// Save objects that need to be uploaded in this._modified. We also save
@ -1248,7 +1248,10 @@ SyncEngine.prototype = {
},
wipeServer: function wipeServer() {
new Resource(this.engineURL).delete();
let response = new Resource(this.engineURL).delete();
if (response.status != 200 && response.status != 404) {
throw response;
}
this._resetClient();
},

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

@ -22,6 +22,7 @@
* Marina Samuel <msamuel@mozilla.com>
* Philipp von Weitershausen <philipp@weitershausen.de>
* Chenxia Liu <liuche@mozilla.com>
* Richard Newman <rnewman@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -55,6 +56,12 @@ Cu.import("resource://services-sync/main.js"); // So we can get to Service fo
let SyncScheduler = {
_log: Log4Moz.repository.getLogger("Sync.SyncScheduler"),
_fatalLoginStatus: [LOGIN_FAILED_NO_USERNAME,
LOGIN_FAILED_NO_PASSWORD,
LOGIN_FAILED_NO_PASSPHRASE,
LOGIN_FAILED_INVALID_PASSPHRASE,
LOGIN_FAILED_LOGIN_REJECTED],
/**
* The nsITimer object that schedules the next sync. See scheduleNextSync().
*/
@ -116,6 +123,7 @@ let SyncScheduler = {
},
observe: function observe(subject, topic, data) {
this._log.trace("Handling " + topic);
switch(topic) {
case "weave:engine:score:updated":
if (Status.login == LOGIN_SUCCEEDED) {
@ -163,7 +171,7 @@ let SyncScheduler = {
}
break;
case "weave:engine:sync:error":
// subject is the exception thrown by an engine's sync() method
// `subject` is the exception thrown by an engine's sync() method.
let exception = subject;
if (exception.status >= 500 && exception.status <= 504) {
this.requiresBackoff = true;
@ -172,12 +180,16 @@ let SyncScheduler = {
case "weave:service:login:error":
this.clearSyncTriggers();
// Try again later, just as if we threw an error... only without the
// error count.
if (Status.login == MASTER_PASSWORD_LOCKED) {
// Try again later, just as if we threw an error... only without the
// error count.
this._log.debug("Couldn't log in: master password is locked.");
this._log.trace("Scheduling a sync at MASTER_PASSWORD_LOCKED_RETRY_INTERVAL");
this.scheduleAtInterval(MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
} else if (this._fatalLoginStatus.indexOf(Status.login) == -1) {
// Not a fatal login error, just an intermittent network or server
// issue. Keep on syncin'.
this.checkSyncStatus();
}
break;
case "weave:service:logout:finish":
@ -449,6 +461,7 @@ let SyncScheduler = {
* Deal with sync errors appropriately
*/
handleSyncError: function handleSyncError() {
this._log.trace("In handleSyncError. Error count: " + this._syncErrors);
this._syncErrors++;
// Do nothing on the first couple of failures, if we're not in
@ -458,6 +471,8 @@ let SyncScheduler = {
this.scheduleNextSync();
return;
}
this._log.debug("Sync error count has exceeded " +
MAX_ERROR_COUNT_BEFORE_BACKOFF + "; enforcing backoff.");
Status.enforceBackoff = true;
}
@ -469,7 +484,8 @@ let SyncScheduler = {
* Remove any timers/observers that might trigger a sync
*/
clearSyncTriggers: function clearSyncTriggers() {
this._log.debug("Clearing sync triggers.");
this._log.debug("Clearing sync triggers and the global score.");
this.globalScore = this.nextSync = 0;
// Clear out any scheduled syncs
if (this.syncTimer)
@ -521,6 +537,7 @@ let ErrorHandler = {
},
observe: function observe(subject, topic, data) {
this._log.trace("Handling " + topic);
switch(topic) {
case "weave:engine:sync:applied":
if (subject.newFailed) {
@ -569,6 +586,19 @@ let ErrorHandler = {
this.dontIgnoreErrors = false;
break;
case "weave:service:sync:finish":
this._log.trace("Status.service is " + Status.service);
// Check both of these status codes: in the event of a failure in one
// engine, Status.service will be SYNC_FAILED_PARTIAL despite
// Status.sync being SYNC_SUCCEEDED.
// *facepalm*
if (Status.sync == SYNC_SUCCEEDED &&
Status.service == STATUS_OK) {
// Great. Let's clear our mid-sync 401 note.
this._log.trace("Clearing lastSyncReassigned.");
Svc.Prefs.reset("lastSyncReassigned");
}
if (Status.service == SYNC_FAILED_PARTIAL) {
this._log.debug("Some engines did not sync correctly.");
this.resetFileLog(Svc.Prefs.get("log.appender.file.logOnError"),
@ -590,7 +620,12 @@ let ErrorHandler = {
},
notifyOnNextTick: function notifyOnNextTick(topic) {
Utils.nextTick(function() Svc.Obs.notify(topic));
Utils.nextTick(function() {
this._log.trace("Notifying " + topic +
". Status.login is " + Status.login +
". Status.sync is " + Status.sync);
Svc.Obs.notify(topic);
}, this);
},
/**
@ -710,6 +745,7 @@ let ErrorHandler = {
shouldReportError: function shouldReportError() {
if (Status.login == MASTER_PASSWORD_LOCKED) {
this._log.trace("shouldReportError: false (master password locked).");
return false;
}
@ -721,8 +757,18 @@ let ErrorHandler = {
if (lastSync && ((Date.now() - Date.parse(lastSync)) >
Svc.Prefs.get("errorhandler.networkFailureReportTimeout") * 1000)) {
Status.sync = PROLONGED_SYNC_FAILURE;
this._log.trace("shouldReportError: true (prolonged sync failure).");
return true;
}
// We got a 401 mid-sync. Wait for the next sync before actually handling
// an error. This assumes that we'll get a 401 again on a login fetch in
// order to report the error.
if (!Weave.Service.clusterURL) {
this._log.trace("shouldReportError: false (no cluster URL; " +
"possible node reassignment).");
return false;
}
return ([Status.login, Status.sync].indexOf(SERVER_MAINTENANCE) == -1 &&
[Status.login, Status.sync].indexOf(LOGIN_FAILED_NETWORK_ERROR) == -1);
@ -742,7 +788,24 @@ let ErrorHandler = {
case 401:
Weave.Service.logout();
Status.login = LOGIN_FAILED_LOGIN_REJECTED;
this._log.info("Got 401 response; resetting clusterURL.");
Svc.Prefs.reset("clusterURL");
let delay = 0;
if (Svc.Prefs.get("lastSyncReassigned")) {
// We got a 401 in the middle of the previous sync, and we just got
// another. Login must have succeeded in order for us to get here, so
// the password should be correct.
// This is likely to be an intermittent server issue, so back off and
// give it time to recover.
this._log.warn("Last sync also failed for 401. Delaying next sync.");
delay = MINIMUM_BACKOFF_INTERVAL;
} else {
this._log.debug("New mid-sync 401 failure. Making a note.");
Svc.Prefs.set("lastSyncReassigned", true);
}
this._log.info("Attempting to schedule another sync.");
SyncScheduler.scheduleNextSync(delay);
break;
case 500:

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

@ -270,7 +270,7 @@ AsyncResource.prototype = {
_doRequest: function _doRequest(action, data, callback) {
this._log.trace("In _doRequest.");
this._callback = callback;
let channel = this._channel = this._createRequest();
let channel = this._createRequest();
if ("undefined" != typeof(data))
this._data = data;
@ -303,7 +303,7 @@ AsyncResource.prototype = {
channel.asyncOpen(listener, null);
},
_onComplete: function _onComplete(error, data) {
_onComplete: function _onComplete(error, data, channel) {
this._log.trace("In _onComplete. Error is " + error + ".");
if (error) {
@ -312,7 +312,6 @@ AsyncResource.prototype = {
}
this._data = data;
let channel = this._channel;
let action = channel.requestMethod;
this._log.trace("Channel: " + channel);
@ -539,69 +538,29 @@ ChannelListener.prototype = {
// Clear the abort timer now that the channel is done.
this.abortTimer.clear();
/**
* Shim to help investigate Bug 672878.
* We seem to be seeing a situation in which onStopRequest is being called
* with a success code, but no mResponseHead yet set (a failure condition).
* One possibility, according to bzbarsky, is that the channel status and
* the status argument don't match up. This block will log that situation.
*
* Fetching channel.status should not throw, but if it does requestStatus
* will be NS_ERROR_UNEXPECTED, which will cause this method to report
* failure.
*/
let requestStatus = Cr.NS_ERROR_UNEXPECTED;
let statusSuccess = Components.isSuccessCode(status);
try {
// From nsIRequest.
requestStatus = channel.status;
this._log.trace("Request status is " + requestStatus);
} catch (ex) {
this._log.warn("Got exception " + Utils.exceptionStr(ex) +
" fetching channel.status.");
}
if (statusSuccess && (status != requestStatus)) {
this._log.error("Request status " + requestStatus +
" does not match status arg " + status);
try {
channel.responseStatus;
} catch (ex) {
this._log.error("... and we got " + Utils.exceptionStr(ex) +
" retrieving responseStatus.");
}
}
let requestStatusSuccess = Components.isSuccessCode(requestStatus);
let uri = channel && channel.URI && channel.URI.spec || "<unknown>";
this._log.trace("Channel for " + channel.requestMethod + " " + uri + ": " +
"isSuccessCode(" + status + ")? " + statusSuccess + ", " +
"isSuccessCode(" + requestStatus + ")? " +
requestStatusSuccess);
"isSuccessCode(" + status + ")? " + statusSuccess);
if (this._data == '') {
this._data = null;
}
// Check both the nsIRequest status and the status we were provided.
// If either is unsuccessful, don't take the success branch!
//
// Pass back the failure code and stop execution. Use Components.Exception()
// instead of Error() so the exception is QI-able and can be passed across
// XPCOM borders while preserving the status code.
if (!statusSuccess || !requestStatusSuccess) {
// Pick the code that caused us to fail.
let code = statusSuccess ? requestStatus : status;
let message = Components.Exception("", code).name;
let error = Components.Exception(message, code);
this._onComplete(error);
if (!statusSuccess) {
let message = Components.Exception("", status).name;
let error = Components.Exception(message, status);
this._onComplete(error, undefined, channel);
return;
}
this._log.trace("Channel: flags = " + channel.loadFlags +
", URI = " + uri +
", HTTP success? " + channel.requestSucceeded);
this._onComplete(null, this._data);
this._onComplete(null, this._data, channel);
},
onDataAvailable: function Channel_onDataAvail(req, cb, stream, off, count) {

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

@ -370,6 +370,9 @@ RESTRequest.prototype = {
/*** nsIStreamListener ***/
onStartRequest: function onStartRequest(channel) {
// Update the channel in case we got redirected.
this.channel = channel;
if (this.status == this.ABORTED) {
this._log.trace("Not proceeding with onStartRequest, request was aborted.");
return;
@ -394,6 +397,9 @@ RESTRequest.prototype = {
},
onStopRequest: function onStopRequest(channel, context, statusCode) {
// Update the channel in case we got redirected.
this.channel = channel;
if (this.timeoutTimer) {
// Clear the abort timer now that the channel is done.
this.timeoutTimer.clear();
@ -406,42 +412,7 @@ RESTRequest.prototype = {
}
this.status = this.COMPLETED;
/**
* Shim to help investigate Bug 672878.
* We seem to be seeing a situation in which onStopRequest is being called
* with a success code, but no mResponseHead yet set (a failure condition).
* One possibility, according to bzbarsky, is that the channel status and
* the status argument don't match up. This block will log that situation.
*
* Fetching channel.status should not throw, but if it does requestStatus
* will be NS_ERROR_UNEXPECTED, which will cause this method to report
* failure.
*
* This code parallels nearly identical shimming in resource.js.
*/
let requestStatus = Cr.NS_ERROR_UNEXPECTED;
let statusSuccess = Components.isSuccessCode(statusCode);
try {
// From nsIRequest.
requestStatus = channel.status;
this._log.trace("Request status is " + requestStatus);
} catch (ex) {
this._log.warn("Got exception " + Utils.exceptionStr(ex) +
" fetching channel.status.");
}
if (statusSuccess && (statusCode != requestStatus)) {
this._log.error("Request status " + requestStatus +
" does not match status arg " + statusCode);
try {
channel.responseStatus;
} catch (ex) {
this._log.error("... and we got " + Utils.exceptionStr(ex) +
" retrieving responseStatus.");
}
}
let requestStatusSuccess = Components.isSuccessCode(requestStatus);
let uri = channel && channel.URI && channel.URI.spec || "<unknown>";
this._log.trace("Channel for " + channel.requestMethod + " " + uri +
" returned status code " + statusCode);
@ -449,7 +420,7 @@ RESTRequest.prototype = {
// Throw the failure code and stop execution. Use Components.Exception()
// instead of Error() so the exception is QI-able and can be passed across
// XPCOM borders while preserving the status code.
if (!statusSuccess || !requestStatusSuccess) {
if (!statusSuccess) {
let message = Components.Exception("", statusCode).name;
let error = Components.Exception(message, statusCode);
this.onComplete(error);

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

@ -523,8 +523,10 @@ WeaveSvc.prototype = {
return this.serverURL;
case 0:
case 200:
if (node == "null")
if (node == "null") {
node = null;
}
this._log.trace("_findCluster successfully returning " + node);
return node;
default:
ErrorHandler.checkServerError(node);
@ -702,8 +704,11 @@ WeaveSvc.prototype = {
return true;
}
} catch (e) {
} catch (ex) {
// This means no keys are present, or there's a network error.
this._log.debug("Failed to fetch and verify keys: "
+ Utils.exceptionStr(ex));
ErrorHandler.checkServerError(ex);
return false;
}
},
@ -731,9 +736,9 @@ WeaveSvc.prototype = {
}
try {
// Make sure we have a cluster to verify against
// this is a little weird, if we don't get a node we pretend
// to succeed, since that probably means we just don't have storage
// Make sure we have a cluster to verify against.
// This is a little weird, if we don't get a node we pretend
// to succeed, since that probably means we just don't have storage.
if (this.clusterURL == "" && !this._setCluster()) {
Status.sync = NO_SYNC_NODE_FOUND;
Svc.Obs.notify("weave:service:sync:delayed");
@ -826,6 +831,7 @@ WeaveSvc.prototype = {
let uploadRes = wbo.upload(this.cryptoKeysURL);
if (uploadRes.status != 200) {
this._log.warn("Got status " + uploadRes.status + " uploading new keys. What to do? Throw!");
ErrorHandler.checkServerError(uploadRes);
throw new Error("Unable to upload symmetric keys.");
}
this._log.info("Got status " + uploadRes.status + " uploading keys.");
@ -915,6 +921,7 @@ WeaveSvc.prototype = {
}))(),
startOver: function() {
this._log.trace("Invoking Service.startOver.");
Svc.Obs.notify("weave:engine:stop-tracking");
Status.resetSync();
@ -1367,7 +1374,14 @@ WeaveSvc.prototype = {
}
// Update engines because it might change what we sync.
this._updateEnabledEngines();
try {
this._updateEnabledEngines();
} catch (ex) {
this._log.debug("Updating enabled engines failed: " +
Utils.exceptionStr(ex));
ErrorHandler.checkServerError(ex);
throw ex;
}
try {
for each (let engine in Engines.getEnabled()) {
@ -1480,22 +1494,18 @@ WeaveSvc.prototype = {
_syncEngine: function WeaveSvc__syncEngine(engine) {
try {
engine.sync();
return true;
}
catch(e) {
// Maybe a 401, cluster update perhaps needed?
if (e.status == 401) {
// Log out and clear the cluster URL pref. That will make us perform
// cluster detection and password check on next sync, which handles
// both causes of 401s; in either case, we won't proceed with this
// sync so return false, but kick off a sync for next time.
this.logout();
Svc.Prefs.reset("clusterURL");
Utils.nextTick(this.sync, this);
// Maybe a 401, cluster update perhaps needed?
// We rely on ErrorHandler observing the sync failure notification to
// schedule another sync and clear node assignment values.
// Here we simply want to muffle the exception and return an
// appropriate value.
return false;
}
return true;
}
return true;
},
/**
@ -1555,22 +1565,34 @@ WeaveSvc.prototype = {
CollectionKeys.clear();
this.upgradeSyncKey(this.syncID);
// Wipe the server.
let wipeTimestamp = this.wipeServer();
// Upload a new meta/global record.
let meta = new WBORecord("meta", "global");
meta.payload.syncID = this.syncID;
meta.payload.storageVersion = STORAGE_VERSION;
meta.isNew = true;
this._log.debug("New metadata record: " + JSON.stringify(meta.payload));
let resp = new Resource(this.metaURL).put(meta);
if (!resp.success)
let res = new Resource(this.metaURL);
// It would be good to set the X-If-Unmodified-Since header to `timestamp`
// for this PUT to ensure at least some level of transactionality.
// Unfortunately, the servers don't support it after a wipe right now
// (bug 693893), so we're going to defer this until bug 692700.
let resp = res.put(meta);
if (!resp.success) {
// If we got into a race condition, we'll abort the sync this way, too.
// That's fine. We'll just wait till the next sync. The client that we're
// racing is probably busy uploading stuff right now anyway.
throw resp;
}
Records.set(this.metaURL, meta);
// Wipe everything we know about except meta because we just uploaded it
let collections = [Clients].concat(Engines.getAll()).map(function(engine) {
return engine.name;
});
this.wipeServer(collections);
// Generate, upload, and download new keys. Do this last so we don't wipe
// them...
@ -1581,36 +1603,51 @@ WeaveSvc.prototype = {
* Wipe user data from the server.
*
* @param collections [optional]
* Array of collections to wipe. If not given, all collections are wiped.
* Array of collections to wipe. If not given, all collections are
* wiped by issuing a DELETE request for `storageURL`.
*
* @param includeKeys [optional]
* If true, keys/pubkey and keys/privkey are deleted from the server.
* This is false by default, which will cause the usual upgrade paths
* to leave those keys on the server. This is to solve Bug 614737: old
* clients check for keys *before* checking storage versions.
*
* Note that this parameter only has an effect if `collections` is not
* passed. If you explicitly pass a list of collections, they will be
* processed regardless of the value of `includeKeys`.
* @return the server's timestamp of the (last) DELETE.
*/
wipeServer: function wipeServer(collections, includeKeyPairs)
wipeServer: function wipeServer(collections)
this._notify("wipe-server", "", function() {
let response;
if (!collections) {
collections = [];
let info = new Resource(this.infoURL).get();
for (let name in info.obj) {
if (includeKeyPairs || (name != "keys"))
collections.push(name);
// Strip the trailing slash.
let res = new Resource(this.storageURL.slice(0, -1));
res.setHeader("X-Confirm-Delete", "1");
try {
response = res.delete();
} catch (ex) {
this._log.debug("Failed to wipe server: " + Utils.exceptionStr(ex));
throw ex;
}
if (response.status != 200 && response.status != 404) {
this._log.debug("Aborting wipeServer. Server responded with " +
response.status + " response for " + this.storageURL);
throw response;
}
return response.headers["x-weave-timestamp"];
}
let timestamp;
for each (let name in collections) {
let url = this.storageURL + name;
let response = new Resource(url).delete();
try {
response = new Resource(url).delete();
} catch (ex) {
this._log.debug("Failed to wipe '" + name + "' collection: " +
Utils.exceptionStr(ex));
throw ex;
}
if (response.status != 200 && response.status != 404) {
throw "Aborting wipeServer. Server responded with "
+ response.status + " response for " + url;
this._log.debug("Aborting wipeServer. Server responded with " +
response.status + " response for " + url);
throw response;
}
if ("x-weave-timestamp" in response.headers) {
timestamp = response.headers["x-weave-timestamp"];
}
}
return timestamp;
})(),
/**
@ -1620,7 +1657,7 @@ WeaveSvc.prototype = {
* Array of engine names to wipe. If not given, all engines are used.
*/
wipeClient: function WeaveSvc_wipeClient(engines)
this._catch(this._notify("wipe-client", "", function() {
this._notify("wipe-client", "", function() {
// If we don't have any engines, reset the service and wipe all engines
if (!engines) {
// Clear out any service data
@ -1639,7 +1676,7 @@ WeaveSvc.prototype = {
// Save the password/passphrase just in-case they aren't restored by sync
this.persistLogin();
}))(),
})(),
/**
* Wipe all remote user data by wiping the server then telling each remote
@ -1648,8 +1685,8 @@ WeaveSvc.prototype = {
* @param engines [optional]
* Array of engine names to wipe. If not given, all engines are used.
*/
wipeRemote: function WeaveSvc_wipeRemote(engines)
this._catch(this._notify("wipe-remote", "", function() {
wipeRemote: function wipeRemote(engines) {
try {
// Make sure stuff gets uploaded.
this.resetClient(engines);
@ -1667,7 +1704,11 @@ WeaveSvc.prototype = {
// Make sure the changed clients get updated.
Clients.sync();
}))(),
} catch (ex) {
ErrorHandler.checkServerError(ex);
throw ex;
}
},
/**
* Reset local service information like logs, sync times, caches.
@ -1678,7 +1719,6 @@ WeaveSvc.prototype = {
// Pretend we've never synced to the server and drop cached data
this.syncID = "";
Svc.Prefs.reset("lastSync");
Records.clearCache();
}))(),

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

@ -1092,6 +1092,9 @@ let Utils = {
// If Master Password is enabled and locked, present a dialog to unlock it.
// Return whether the system is unlocked.
ensureMPUnlocked: function ensureMPUnlocked() {
if (!Utils.mpLocked()) {
return true;
}
let sdr = Cc["@mozilla.org/security/sdr;1"]
.getService(Ci.nsISecretDecoderRing);
try {

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

@ -17,6 +17,23 @@ let provider = {
};
Services.dirsvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(provider);
let timer;
function waitForZeroTimer(callback) {
// First wait >100ms (nsITimers can take up to that much time to fire, so
// we can account for the timer in delayedAutoconnect) and then two event
// loop ticks (to account for the Utils.nextTick() in autoConnect).
let ticks = 2;
function wait() {
if (ticks) {
ticks -= 1;
Utils.nextTick(wait);
return;
}
callback();
}
timer = Utils.namedTimer(wait, 150, {}, "timer");
}
btoa = Cu.import("resource://services-sync/log4moz.js").btoa;
function getTestLogger(component) {
return Log4Moz.repository.getLogger("Testing");

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

@ -1,3 +1,5 @@
const Cm = Components.manager;
// Shared logging for all HTTP server functions.
Cu.import("resource://services-sync/log4moz.js");
const SYNC_HTTP_LOGGER = "Sync.Test.Server";
@ -10,6 +12,17 @@ function new_timestamp() {
return Math.round(Date.now() / 10) / 100;
}
function return_timestamp(request, response, timestamp) {
if (!timestamp) {
timestamp = new_timestamp();
}
let body = "" + timestamp;
response.setHeader("X-Weave-Timestamp", body);
response.setStatusLine(request.httpVersion, 200, "OK");
response.bodyOutputStream.write(body, body.length);
return timestamp;
}
function httpd_setup (handlers) {
let server = new nsHttpServer();
let port = 8080;
@ -608,7 +621,7 @@ SyncServer.prototype = {
* subject to change: see Bug 650435.
*/
timestamp: function timestamp() {
return Math.round(Date.now() / 10) / 100;
return new_timestamp();
},
/**
@ -988,3 +1001,51 @@ function serverForUsers(users, contents, callback) {
server.start();
return server;
}
/**
* Proxy auth helpers.
*/
/**
* Fake a PAC to prompt a channel replacement.
*/
let PACSystemSettings = {
CID: Components.ID("{5645d2c1-d6d8-4091-b117-fe7ee4027db7}"),
contractID: "@mozilla.org/system-proxy-settings;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory,
Ci.nsISystemProxySettings]),
createInstance: function createInstance(outer, iid) {
if (outer) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return this.QueryInterface(iid);
},
lockFactory: function lockFactory(lock) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
// Replace this URI for each test to avoid caching. We want to ensure that
// each test gets a completely fresh setup.
PACURI: null,
getProxyForURI: function getProxyForURI(aURI) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
}
};
function installFakePAC() {
_("Installing fake PAC.");
Cm.nsIComponentRegistrar
.registerFactory(PACSystemSettings.CID,
"Fake system proxy-settings",
PACSystemSettings.contractID,
PACSystemSettings);
}
function uninstallFakePAC() {
_("Uninstalling fake PAC.");
let CID = PACSystemSettings.CID;
Cm.nsIComponentRegistrar.unregisterFactory(CID, PACSystemSettings);
}

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

@ -20,8 +20,7 @@ const NON_PROLONGED_ERROR_DURATION =
(Svc.Prefs.get('errorhandler.networkFailureReportTimeout') / 2) * 1000;
function setLastSync(lastSyncValue) {
Svc.Prefs.set("lastSync", (new Date(Date.now() -
lastSyncValue)).toString());
Svc.Prefs.set("lastSync", (new Date(Date.now() - lastSyncValue)).toString());
}
function CatapultEngine() {
@ -31,7 +30,9 @@ CatapultEngine.prototype = {
__proto__: SyncEngine.prototype,
exception: null, // tests fill this in
_sync: function _sync() {
throw this.exception;
if (this.exception) {
throw this.exception;
}
}
};
@ -69,7 +70,9 @@ function sync_httpd_setup() {
syncID: Service.syncID,
storageVersion: STORAGE_VERSION,
engines: {clients: {version: Clients.version,
syncID: Clients.syncID}}
syncID: Clients.syncID},
catapult: {version: Engines.get("catapult").version,
syncID: Engines.get("catapult").syncID}}
});
let clientsColl = new ServerCollection({}, true);
@ -79,23 +82,37 @@ function sync_httpd_setup() {
let handler_401 = httpd_handler(401, "Unauthorized");
return httpd_setup({
// Normal server behaviour.
"/1.1/johndoe/storage/meta/global": upd("meta", global.handler()),
"/1.1/johndoe/info/collections": collectionsHelper.handler,
"/1.1/johndoe/storage/crypto/keys":
upd("crypto", (new ServerWBO("keys")).handler()),
"/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler()),
// Credentials are wrong or node reallocated.
"/1.1/janedoe/storage/meta/global": handler_401,
"/1.1/janedoe/info/collections": handler_401,
"/maintenance/1.1/johnsmith/info/collections": service_unavailable,
// Maintenance or overloaded (503 + Retry-After) at info/collections.
"/maintenance/1.1/broken.info/info/collections": service_unavailable,
"/maintenance/1.1/janesmith/storage/meta/global": service_unavailable,
"/maintenance/1.1/janesmith/info/collections": collectionsHelper.handler,
// Maintenance or overloaded (503 + Retry-After) at meta/global.
"/maintenance/1.1/broken.meta/storage/meta/global": service_unavailable,
"/maintenance/1.1/broken.meta/info/collections": collectionsHelper.handler,
"/maintenance/1.1/foo/storage/meta/global": upd("meta", global.handler()),
"/maintenance/1.1/foo/info/collections": collectionsHelper.handler,
"/maintenance/1.1/foo/storage/crypto/keys": service_unavailable,
// Maintenance or overloaded (503 + Retry-After) at crypto/keys.
"/maintenance/1.1/broken.keys/storage/meta/global": upd("meta", global.handler()),
"/maintenance/1.1/broken.keys/info/collections": collectionsHelper.handler,
"/maintenance/1.1/broken.keys/storage/crypto/keys": service_unavailable,
// Maintenance or overloaded (503 + Retry-After) at wiping collection.
"/maintenance/1.1/broken.wipe/info/collections": collectionsHelper.handler,
"/maintenance/1.1/broken.wipe/storage/meta/global": upd("meta", global.handler()),
"/maintenance/1.1/broken.wipe/storage/crypto/keys":
upd("crypto", (new ServerWBO("keys")).handler()),
"/maintenance/1.1/broken.wipe/storage": service_unavailable,
"/maintenance/1.1/broken.wipe/storage/clients": upd("clients", clientsColl.handler()),
"/maintenance/1.1/broken.wipe/storage/catapult": service_unavailable
});
}
@ -104,7 +121,10 @@ function setUp() {
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/";
return generateAndUploadKeys();
}
function generateAndUploadKeys() {
generateNewKeys();
let serverKeys = CollectionKeys.asWBO("crypto", "keys");
serverKeys.encrypt(Service.syncKeyBundle);
@ -126,16 +146,34 @@ add_test(function test_401_logout() {
do_check_eq(Status.sync, SYNC_SUCCEEDED);
do_check_true(Service.isLoggedIn);
Svc.Obs.add("weave:service:sync:error", onSyncError);
function onSyncError() {
_("Got weave:service:sync:error in first sync.");
Svc.Obs.remove("weave:service:sync:error", onSyncError);
// Wait for the automatic next sync.
function onLoginError() {
_("Got weave:service:login:error in second sync.");
Svc.Obs.remove("weave:service:login:error", onLoginError);
do_check_eq(Status.login, LOGIN_FAILED_LOGIN_REJECTED);
do_check_false(Service.isLoggedIn);
// Clean up.
Utils.nextTick(function () {
Service.startOver();
server.stop(run_next_test);
});
}
Svc.Obs.add("weave:service:login:error", onLoginError);
}
// Make sync fail due to login rejected.
Service.username = "janedoe";
_("Starting first sync.");
Service.sync();
do_check_eq(Status.login, LOGIN_FAILED_LOGIN_REJECTED);
do_check_false(Service.isLoggedIn);
// Clean up.
Service.startOver();
server.stop(run_next_test);
_("First sync done.");
});
add_test(function test_credentials_changed_logout() {
@ -178,6 +216,10 @@ add_test(function test_shouldReportError() {
Status.login = MASTER_PASSWORD_LOCKED;
do_check_false(ErrorHandler.shouldReportError());
// Give ourselves a clusterURL so that the temporary 401 no-error situation
// doesn't come into play.
Service.clusterURL = "http://localhost:8080/";
// Test dontIgnoreErrors, non-network, non-prolonged, login error reported
Status.resetSync();
setLastSync(NON_PROLONGED_ERROR_DURATION);
@ -733,9 +775,9 @@ add_test(function test_info_collections_login_server_maintenance_error() {
let server = sync_httpd_setup();
setUp();
Service.username = "broken.info";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "johnsmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -772,9 +814,9 @@ add_test(function test_meta_global_login_server_maintenance_error() {
let server = sync_httpd_setup();
setUp();
Service.username = "broken.meta";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "janesmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -811,8 +853,8 @@ add_test(function test_crypto_keys_login_server_maintenance_error() {
let server = sync_httpd_setup();
setUp();
Service.username = "broken.keys";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "foo";
// Force re-download of keys
CollectionKeys.clear();
@ -878,9 +920,9 @@ add_test(function test_info_collections_login_prolonged_server_maintenance_error
let server = sync_httpd_setup();
setUp();
Service.username = "broken.info";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "johnsmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -910,9 +952,9 @@ add_test(function test_meta_global_login_prolonged_server_maintenance_error(){
let server = sync_httpd_setup();
setUp();
Service.username = "broken.meta";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "janesmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -937,13 +979,13 @@ add_test(function test_meta_global_login_prolonged_server_maintenance_error(){
Service.sync();
});
add_test(function test_crypto_keys_login_prolonged_server_maintenance_error(){
add_test(function test_download_crypto_keys_login_prolonged_server_maintenance_error(){
// Test crypto/keys prolonged server maintenance errors are reported.
let server = sync_httpd_setup();
setUp();
Service.username = "broken.keys";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "foo";
// Force re-download of keys
CollectionKeys.clear();
@ -971,6 +1013,116 @@ add_test(function test_crypto_keys_login_prolonged_server_maintenance_error(){
Service.sync();
});
add_test(function test_upload_crypto_keys_login_prolonged_server_maintenance_error(){
// Test crypto/keys prolonged server maintenance errors are reported.
let server = sync_httpd_setup();
// Start off with an empty account, do not upload a key.
Service.username = "broken.keys";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:login:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
setLastSync(PROLONGED_ERROR_DURATION);
Service.sync();
});
add_test(function test_wipeServer_login_prolonged_server_maintenance_error(){
// Test that we report prolonged server maintenance errors that occur whilst
// wiping the server.
let server = sync_httpd_setup();
// Start off with an empty account, do not upload a key.
Service.username = "broken.wipe";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:login:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
setLastSync(PROLONGED_ERROR_DURATION);
Service.sync();
});
add_test(function test_wipeRemote_prolonged_server_maintenance_error(){
// Test that we report prolonged server maintenance errors that occur whilst
// wiping all remote devices.
let server = sync_httpd_setup();
Service.username = "broken.wipe";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
generateAndUploadKeys();
let engine = Engines.get("catapult");
engine.exception = null;
engine.enabled = true;
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:sync:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_eq(Svc.Prefs.get("firstSync"), "wipeRemote");
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
Svc.Prefs.set("firstSync", "wipeRemote");
setLastSync(PROLONGED_ERROR_DURATION);
Service.sync();
});
add_test(function test_sync_syncAndReportErrors_server_maintenance_error() {
// Test server maintenance errors are reported
// when calling syncAndReportErrors.
@ -1004,9 +1156,9 @@ add_test(function test_info_collections_login_syncAndReportErrors_server_mainten
let server = sync_httpd_setup();
setUp();
Service.username = "broken.info";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "johnsmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -1037,9 +1189,9 @@ add_test(function test_meta_global_login_syncAndReportErrors_server_maintenance_
let server = sync_httpd_setup();
setUp();
Service.username = "broken.meta";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "janesmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -1064,14 +1216,14 @@ add_test(function test_meta_global_login_syncAndReportErrors_server_maintenance_
ErrorHandler.syncAndReportErrors();
});
add_test(function test_crypto_keys_login_syncAndReportErrors_server_maintenance_error() {
add_test(function test_download_crypto_keys_login_syncAndReportErrors_server_maintenance_error() {
// Test crypto/keys server maintenance errors are reported
// when calling syncAndReportErrors.
let server = sync_httpd_setup();
setUp();
Service.username = "broken.keys";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "foo";
// Force re-download of keys
CollectionKeys.clear();
@ -1099,6 +1251,117 @@ add_test(function test_crypto_keys_login_syncAndReportErrors_server_maintenance_
ErrorHandler.syncAndReportErrors();
});
add_test(function test_upload_crypto_keys_login_syncAndReportErrors_server_maintenance_error() {
// Test crypto/keys server maintenance errors are reported
// when calling syncAndReportErrors.
let server = sync_httpd_setup();
// Start off with an empty account, do not upload a key.
Service.username = "broken.keys";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:login:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
setLastSync(NON_PROLONGED_ERROR_DURATION);
ErrorHandler.syncAndReportErrors();
});
add_test(function test_wipeServer_login_syncAndReportErrors_server_maintenance_error() {
// Test crypto/keys server maintenance errors are reported
// when calling syncAndReportErrors.
let server = sync_httpd_setup();
// Start off with an empty account, do not upload a key.
Service.username = "broken.wipe";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:login:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
setLastSync(NON_PROLONGED_ERROR_DURATION);
ErrorHandler.syncAndReportErrors();
});
add_test(function test_wipeRemote_syncAndReportErrors_server_maintenance_error(){
// Test that we report prolonged server maintenance errors that occur whilst
// wiping all remote devices.
let server = sync_httpd_setup();
Service.username = "broken.wipe";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
generateAndUploadKeys();
let engine = Engines.get("catapult");
engine.exception = null;
engine.enabled = true;
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:sync:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:sync:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, SERVER_MAINTENANCE);
do_check_eq(Svc.Prefs.get("firstSync"), "wipeRemote");
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
Svc.Prefs.set("firstSync", "wipeRemote");
setLastSync(NON_PROLONGED_ERROR_DURATION);
ErrorHandler.syncAndReportErrors();
});
add_test(function test_sync_syncAndReportErrors_prolonged_server_maintenance_error() {
// Test prolonged server maintenance errors are
// reported when calling syncAndReportErrors.
@ -1132,9 +1395,9 @@ add_test(function test_info_collections_login_syncAndReportErrors_prolonged_serv
let server = sync_httpd_setup();
setUp();
Service.username = "broken.info";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "johnsmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -1165,9 +1428,9 @@ add_test(function test_meta_global_login_syncAndReportErrors_prolonged_server_ma
let server = sync_httpd_setup();
setUp();
Service.username = "broken.meta";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "janesmith";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
@ -1192,14 +1455,14 @@ add_test(function test_meta_global_login_syncAndReportErrors_prolonged_server_ma
ErrorHandler.syncAndReportErrors();
});
add_test(function test_crypto_keys_login_syncAndReportErrors_prolonged_server_maintenance_error() {
add_test(function test_download_crypto_keys_login_syncAndReportErrors_prolonged_server_maintenance_error() {
// Test crypto/keys server maintenance errors are reported
// when calling syncAndReportErrors.
let server = sync_httpd_setup();
setUp();
Service.username = "broken.keys";
Service.clusterURL = "http://localhost:8080/maintenance/";
Service.username = "foo";
// Force re-download of keys
CollectionKeys.clear();
@ -1227,6 +1490,76 @@ add_test(function test_crypto_keys_login_syncAndReportErrors_prolonged_server_ma
ErrorHandler.syncAndReportErrors();
});
add_test(function test_upload_crypto_keys_login_syncAndReportErrors_prolonged_server_maintenance_error() {
// Test crypto/keys server maintenance errors are reported
// when calling syncAndReportErrors.
let server = sync_httpd_setup();
// Start off with an empty account, do not upload a key.
Service.username = "broken.keys";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:login:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
setLastSync(PROLONGED_ERROR_DURATION);
ErrorHandler.syncAndReportErrors();
});
add_test(function test_wipeServer_login_syncAndReportErrors_prolonged_server_maintenance_error() {
// Test crypto/keys server maintenance errors are reported
// when calling syncAndReportErrors.
let server = sync_httpd_setup();
// Start off with an empty account, do not upload a key.
Service.username = "broken.wipe";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/maintenance/";
let backoffInterval;
Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
Svc.Obs.remove("weave:service:backoff:interval", observe);
backoffInterval = subject;
});
Svc.Obs.add("weave:ui:login:error", function onUIUpdate() {
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
do_check_true(Status.enforceBackoff);
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
clean();
server.stop(run_next_test);
});
do_check_false(Status.enforceBackoff);
do_check_eq(Status.service, STATUS_OK);
setLastSync(PROLONGED_ERROR_DURATION);
ErrorHandler.syncAndReportErrors();
});
add_test(function test_sync_engine_generic_fail() {
let server = sync_httpd_setup();

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

@ -0,0 +1,487 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
_("Test that node reassignment responses are respected on all kinds of " +
"requests.");
// Don't sync any engines by default.
Svc.DefaultPrefs.set("registerEngines", "")
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/policies.js");
Cu.import("resource://services-sync/rest.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/status.js");
Cu.import("resource://services-sync/log4moz.js");
function run_test() {
Log4Moz.repository.getLogger("Sync.AsyncResource").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.ErrorHandler").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.Resource").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.RESTRequest").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.SyncScheduler").level = Log4Moz.Level.Trace;
initTestLogging();
Engines.register(RotaryEngine);
// None of the failures in this file should result in a UI error.
function onUIError() {
do_throw("Errors should not be presented in the UI.");
}
Svc.Obs.add("weave:ui:login:error", onUIError);
Svc.Obs.add("weave:ui:sync:error", onUIError);
run_next_test();
}
/**
* Emulate the following Zeus config:
* $draining = data.get($prefix . $host . " draining");
* if ($draining == "drain.") {
* log.warn($log_host_db_status . " migrating=1 (node-reassignment)" .
* $log_suffix);
* http.sendResponse("401 Node reassignment", $content_type,
* '"server request: node reassignment"', "");
* }
*/
const reassignBody = "\"server request: node reassignment\"";
// API-compatible with SyncServer handler. Bind `handler` to something to use
// as a ServerCollection handler.
function handleReassign(handler, req, resp) {
resp.setStatusLine(req.httpVersion, 401, "Node reassignment");
resp.setHeader("Content-Type", "application/json");
resp.bodyOutputStream.write(reassignBody, reassignBody.length);
}
/**
* A node assignment handler.
*/
const newNodeBody = "http://localhost:8080/";
function installNodeHandler(server, next) {
function handleNodeRequest(req, resp) {
_("Client made a request for a node reassignment.");
resp.setStatusLine(req.httpVersion, 200, "OK");
resp.setHeader("Content-Type", "text/plain");
resp.bodyOutputStream.write(newNodeBody, newNodeBody.length);
Utils.nextTick(next);
}
let nodePath = "/user/1.0/johndoe/node/weave";
server.server.registerPathHandler(nodePath, handleNodeRequest);
_("Registered node handler at " + nodePath);
}
function prepareServer() {
Service.username = "johndoe";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.password = "ilovejane";
Service.serverURL = "http://localhost:8080/";
Service.clusterURL = "http://localhost:8080/";
do_check_eq(Service.userAPI, "http://localhost:8080/user/1.0/");
let server = new SyncServer();
server.registerUser("johndoe");
server.start();
return server;
}
function getReassigned() {
try {
return Services.prefs.getBoolPref("services.sync.lastSyncReassigned");
} catch (ex if (ex.result == Cr.NS_ERROR_UNEXPECTED)) {
return false;
} catch (ex) {
do_throw("Got exception retrieving lastSyncReassigned: " +
Utils.exceptionStr(ex));
}
}
/**
* Make a test request to `url`, then watch the result of two syncs
* to ensure that a node request was made.
* Runs `between` between the two. This can be used to undo deliberate failure
* setup, detach observers, etc.
*/
function syncAndExpectNodeReassignment(server, firstNotification, between,
secondNotification, url) {
function onwards() {
let nodeFetched = false;
function onFirstSync() {
_("First sync completed.");
Svc.Obs.remove(firstNotification, onFirstSync);
Svc.Obs.add(secondNotification, onSecondSync);
do_check_eq(Service.clusterURL, "");
// Track whether we fetched node/weave. We want to wait for the second
// sync to finish so that we're cleaned up for the next test, so don't
// run_next_test in the node handler.
nodeFetched = false;
// Verify that the client requests a node reassignment.
// Install a node handler to watch for these requests.
installNodeHandler(server, function () {
nodeFetched = true;
});
// Allow for tests to clean up error conditions.
between();
}
function onSecondSync() {
_("Second sync completed.");
Svc.Obs.remove(secondNotification, onSecondSync);
SyncScheduler.clearSyncTriggers();
// Make absolutely sure that any event listeners are done with their work
// before we proceed.
waitForZeroTimer(function () {
_("Second sync nextTick.");
do_check_true(nodeFetched);
Service.startOver();
server.stop(run_next_test);
});
}
Svc.Obs.add(firstNotification, onFirstSync);
Service.sync();
}
// Make sure that it works!
let request = new RESTRequest(url);
request.get(function () {
do_check_eq(request.response.status, 401);
Utils.nextTick(onwards);
});
}
add_test(function test_momentary_401_engine() {
_("Test a failure for engine URLs that's resolved by reassignment.");
let server = prepareServer();
let john = server.user("johndoe");
_("Enabling the Rotary engine.");
let engine = Engines.get("rotary");
engine.enabled = true;
// We need the server to be correctly set up prior to experimenting. Do this
// through a sync.
let global = {syncID: Service.syncID,
storageVersion: STORAGE_VERSION,
rotary: {version: engine.version,
syncID: engine.syncID}}
john.createCollection("meta").insert("global", global);
_("First sync to prepare server contents.");
Service.sync();
_("Setting up Rotary collection to 401.");
let rotary = john.createCollection("rotary");
let oldHandler = rotary.collectionHandler;
rotary.collectionHandler = handleReassign.bind(this, undefined);
// We want to verify that the clusterURL pref has been cleared after a 401
// inside a sync. Flag the Rotary engine to need syncing.
john.collection("rotary").timestamp += 1000;
function between() {
_("Undoing test changes.");
rotary.collectionHandler = oldHandler;
function onLoginStart() {
// lastSyncReassigned shouldn't be cleared until a sync has succeeded.
_("Ensuring that lastSyncReassigned is still set at next sync start.");
Svc.Obs.remove("weave:service:login:start", onLoginStart);
do_check_true(getReassigned());
}
_("Adding observer that lastSyncReassigned is still set on login.");
Svc.Obs.add("weave:service:login:start", onLoginStart);
}
syncAndExpectNodeReassignment(server,
"weave:service:sync:finish",
between,
"weave:service:sync:finish",
Service.storageURL + "rotary");
});
// This test ends up being a failing fetch *after we're already logged in*.
add_test(function test_momentary_401_info_collections() {
_("Test a failure for info/collections that's resolved by reassignment.");
let server = prepareServer();
_("First sync to prepare server contents.");
Service.sync();
// Return a 401 for info requests, particularly info/collections.
let oldHandler = server.toplevelHandlers.info;
server.toplevelHandlers.info = handleReassign;
function undo() {
_("Undoing test changes.");
server.toplevelHandlers.info = oldHandler;
}
syncAndExpectNodeReassignment(server,
"weave:service:sync:error",
undo,
"weave:service:sync:finish",
Service.infoURL);
});
add_test(function test_momentary_401_storage() {
_("Test a failure for any storage URL, not just engine parts. " +
"Resolved by reassignment.");
let server = prepareServer();
// Return a 401 for all storage requests.
let oldHandler = server.toplevelHandlers.storage;
server.toplevelHandlers.storage = handleReassign;
function undo() {
_("Undoing test changes.");
server.toplevelHandlers.storage = oldHandler;
}
syncAndExpectNodeReassignment(server,
"weave:service:login:error",
undo,
"weave:service:sync:finish",
Service.storageURL + "meta/global");
});
add_test(function test_loop_avoidance_storage() {
_("Test that a repeated failure doesn't result in a sync loop " +
"if node reassignment cannot resolve the failure.");
let server = prepareServer();
// Return a 401 for all storage requests.
let oldHandler = server.toplevelHandlers.storage;
server.toplevelHandlers.storage = handleReassign;
let firstNotification = "weave:service:login:error";
let secondNotification = "weave:service:login:error";
let thirdNotification = "weave:service:sync:finish";
let nodeFetched = false;
// Track the time. We want to make sure the duration between the first and
// second sync is small, and then that the duration between second and third
// is set to be large.
let now;
function onFirstSync() {
_("First sync completed.");
Svc.Obs.remove(firstNotification, onFirstSync);
Svc.Obs.add(secondNotification, onSecondSync);
do_check_eq(Service.clusterURL, "");
// We got a 401 mid-sync, and set the pref accordingly.
do_check_true(Services.prefs.getBoolPref("services.sync.lastSyncReassigned"));
// Track whether we fetched node/weave. We want to wait for the second
// sync to finish so that we're cleaned up for the next test, so don't
// run_next_test in the node handler.
nodeFetched = false;
// Verify that the client requests a node reassignment.
// Install a node handler to watch for these requests.
installNodeHandler(server, function () {
nodeFetched = true;
});
// Update the timestamp.
now = Date.now();
}
function onSecondSync() {
_("Second sync completed.");
Svc.Obs.remove(secondNotification, onSecondSync);
Svc.Obs.add(thirdNotification, onThirdSync);
// This sync occurred within the backoff interval.
let elapsedTime = Date.now() - now;
do_check_true(elapsedTime < MINIMUM_BACKOFF_INTERVAL);
// This pref will be true until a sync completes successfully.
do_check_true(getReassigned());
// The timer will be set for some distant time.
// We store nextSync in prefs, which offers us only limited resolution.
// Include that logic here.
let expectedNextSync = 1000 * Math.floor((now + MINIMUM_BACKOFF_INTERVAL) / 1000);
_("Next sync scheduled for " + SyncScheduler.nextSync);
_("Expected to be slightly greater than " + expectedNextSync);
do_check_true(SyncScheduler.nextSync >= expectedNextSync);
do_check_true(!!SyncScheduler.syncTimer);
// Undo our evil scheme.
server.toplevelHandlers.storage = oldHandler;
// Bring the timer forward to kick off a successful sync, so we can watch
// the pref get cleared.
SyncScheduler.scheduleNextSync(0);
}
function onThirdSync() {
Svc.Obs.remove(thirdNotification, onThirdSync);
// That'll do for now; no more syncs.
SyncScheduler.clearSyncTriggers();
// Make absolutely sure that any event listeners are done with their work
// before we proceed.
waitForZeroTimer(function () {
_("Third sync nextTick.");
do_check_false(getReassigned());
do_check_true(nodeFetched);
Service.startOver();
server.stop(run_next_test);
});
}
Svc.Obs.add(firstNotification, onFirstSync);
now = Date.now();
Service.sync();
});
add_test(function test_loop_avoidance_engine() {
_("Test that a repeated 401 in an engine doesn't result in a sync loop " +
"if node reassignment cannot resolve the failure.");
let server = prepareServer();
let john = server.user("johndoe");
_("Enabling the Rotary engine.");
let engine = Engines.get("rotary");
engine.enabled = true;
// We need the server to be correctly set up prior to experimenting. Do this
// through a sync.
let global = {syncID: Service.syncID,
storageVersion: STORAGE_VERSION,
rotary: {version: engine.version,
syncID: engine.syncID}}
john.createCollection("meta").insert("global", global);
_("First sync to prepare server contents.");
Service.sync();
_("Setting up Rotary collection to 401.");
let rotary = john.createCollection("rotary");
let oldHandler = rotary.collectionHandler;
rotary.collectionHandler = handleReassign.bind(this, undefined);
// Flag the Rotary engine to need syncing.
john.collection("rotary").timestamp += 1000;
function onLoginStart() {
// lastSyncReassigned shouldn't be cleared until a sync has succeeded.
_("Ensuring that lastSyncReassigned is still set at next sync start.");
do_check_true(getReassigned());
}
function beforeSuccessfulSync() {
_("Undoing test changes.");
rotary.collectionHandler = oldHandler;
}
function afterSuccessfulSync() {
Svc.Obs.remove("weave:service:login:start", onLoginStart);
Service.startOver();
server.stop(run_next_test);
}
let firstNotification = "weave:service:sync:finish";
let secondNotification = "weave:service:sync:finish";
let thirdNotification = "weave:service:sync:finish";
let nodeFetched = false;
// Track the time. We want to make sure the duration between the first and
// second sync is small, and then that the duration between second and third
// is set to be large.
let now;
function onFirstSync() {
_("First sync completed.");
Svc.Obs.remove(firstNotification, onFirstSync);
Svc.Obs.add(secondNotification, onSecondSync);
do_check_eq(Service.clusterURL, "");
_("Adding observer that lastSyncReassigned is still set on login.");
Svc.Obs.add("weave:service:login:start", onLoginStart);
// We got a 401 mid-sync, and set the pref accordingly.
do_check_true(Services.prefs.getBoolPref("services.sync.lastSyncReassigned"));
// Track whether we fetched node/weave. We want to wait for the second
// sync to finish so that we're cleaned up for the next test, so don't
// run_next_test in the node handler.
nodeFetched = false;
// Verify that the client requests a node reassignment.
// Install a node handler to watch for these requests.
installNodeHandler(server, function () {
nodeFetched = true;
});
// Update the timestamp.
now = Date.now();
}
function onSecondSync() {
_("Second sync completed.");
Svc.Obs.remove(secondNotification, onSecondSync);
Svc.Obs.add(thirdNotification, onThirdSync);
// This sync occurred within the backoff interval.
let elapsedTime = Date.now() - now;
do_check_true(elapsedTime < MINIMUM_BACKOFF_INTERVAL);
// This pref will be true until a sync completes successfully.
do_check_true(getReassigned());
// The timer will be set for some distant time.
// We store nextSync in prefs, which offers us only limited resolution.
// Include that logic here.
let expectedNextSync = 1000 * Math.floor((now + MINIMUM_BACKOFF_INTERVAL) / 1000);
_("Next sync scheduled for " + SyncScheduler.nextSync);
_("Expected to be slightly greater than " + expectedNextSync);
do_check_true(SyncScheduler.nextSync >= expectedNextSync);
do_check_true(!!SyncScheduler.syncTimer);
// Undo our evil scheme.
beforeSuccessfulSync();
// Bring the timer forward to kick off a successful sync, so we can watch
// the pref get cleared.
SyncScheduler.scheduleNextSync(0);
}
function onThirdSync() {
Svc.Obs.remove(thirdNotification, onThirdSync);
// That'll do for now; no more syncs.
SyncScheduler.clearSyncTriggers();
// Make absolutely sure that any event listeners are done with their work
// before we proceed.
waitForZeroTimer(function () {
_("Third sync nextTick.");
do_check_false(getReassigned());
do_check_true(nodeFetched);
afterSuccessfulSync();
});
}
Svc.Obs.add(firstNotification, onFirstSync);
now = Date.now();
Service.sync();
});

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

@ -6,9 +6,11 @@ Cu.import("resource://services-sync/util.js");
let logger;
let fetched = false;
function server_open(metadata, response) {
let body;
if (metadata.method == "GET") {
fetched = true;
body = "This path exists";
response.setStatusLine(metadata.httpVersion, 200, "OK");
} else {
@ -40,6 +42,15 @@ function server_404(metadata, response) {
response.bodyOutputStream.write(body, body.length);
}
let pacFetched = false;
function server_pac(metadata, response) {
pacFetched = true;
let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }';
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.setHeader("Content-Type", "application/x-ns-proxy-autoconfig", false);
response.bodyOutputStream.write(body, body.length);
}
let sample_data = {
some: "sample_data",
@ -150,12 +161,26 @@ function run_test() {
"/timestamp": server_timestamp,
"/headers": server_headers,
"/backoff": server_backoff,
"/pac1": server_pac,
"/quota-notice": server_quota_notice,
"/quota-error": server_quota_error
});
Svc.Prefs.set("network.numRetries", 1); // speed up test
// This apparently has to come first in order for our PAC URL to be hit.
// Don't put any other HTTP requests earlier in the file!
_("Testing handling of proxy auth redirection.");
PACSystemSettings.PACURI = "http://localhost:8080/pac1";
installFakePAC();
let proxiedRes = new Resource("http://localhost:8080/open");
let content = proxiedRes.get();
do_check_true(pacFetched);
do_check_true(fetched);
do_check_eq(content, "This path exists");
pacFetched = fetched = false;
uninstallFakePAC();
_("Resource object members");
let res = new Resource("http://localhost:8080/open");
do_check_true(res.uri instanceof Ci.nsIURI);
@ -168,7 +193,7 @@ function run_test() {
do_check_eq(res.data, null);
_("GET a non-password-protected resource");
let content = res.get();
content = res.get();
do_check_eq(content, "This path exists");
do_check_eq(content.status, 200);
do_check_true(content.success);
@ -474,6 +499,5 @@ function run_test() {
let uri2 = Utils.makeURL("http://foo/");
uri2.query = query;
do_check_eq(uri1.query, uri2.query);
server.stop(do_test_finished);
}

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

@ -6,9 +6,11 @@ Cu.import("resource://services-sync/util.js");
let logger;
let fetched = false;
function server_open(metadata, response) {
let body;
if (metadata.method == "GET") {
fetched = true;
body = "This path exists";
response.setStatusLine(metadata.httpVersion, 200, "OK");
} else {
@ -40,6 +42,15 @@ function server_404(metadata, response) {
response.bodyOutputStream.write(body, body.length);
}
let pacFetched = false;
function server_pac(metadata, response) {
_("Invoked PAC handler.");
pacFetched = true;
let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }';
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.setHeader("Content-Type", "application/x-ns-proxy-autoconfig", false);
response.bodyOutputStream.write(body, body.length);
}
let sample_data = {
some: "sample_data",
@ -154,6 +165,7 @@ function run_test() {
"/timestamp": server_timestamp,
"/headers": server_headers,
"/backoff": server_backoff,
"/pac2": server_pac,
"/quota-notice": server_quota_notice,
"/quota-error": server_quota_error
});
@ -162,9 +174,27 @@ function run_test() {
run_next_test();
}
// This apparently has to come first in order for our PAC URL to be hit.
// Don't put any other HTTP requests earlier in the file!
add_test(function test_proxy_auth_redirect() {
_("Ensure that a proxy auth redirect (which switches out our channel) " +
"doesn't break AsyncResource.");
PACSystemSettings.PACURI = "http://localhost:8080/pac2";
installFakePAC();
let res = new AsyncResource("http://localhost:8080/open");
res.get(function (error, result) {
do_check_true(!error);
do_check_true(pacFetched);
do_check_true(fetched);
do_check_eq("This path exists", result);
pacFetched = fetched = false;
uninstallFakePAC();
run_next_test();
});
});
add_test(function test_members() {
_("Resource object memebers");
_("Resource object members");
let res = new AsyncResource("http://localhost:8080/open");
do_check_true(res.uri instanceof Ci.nsIURI);
do_check_eq(res.uri.spec, "http://localhost:8080/open");

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

@ -41,6 +41,47 @@ add_test(function test_attributes() {
run_next_test();
});
/**
* Verify that a proxy auth redirect doesn't break us. This has to be the first
* request made in the file!
*/
add_test(function test_proxy_auth_redirect() {
let pacFetched = false;
function pacHandler(metadata, response) {
pacFetched = true;
let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }';
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.setHeader("Content-Type", "application/x-ns-proxy-autoconfig", false);
response.bodyOutputStream.write(body, body.length);
}
let fetched = false;
function original(metadata, response) {
fetched = true;
let body = "TADA!";
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.bodyOutputStream.write(body, body.length);
}
let server = httpd_setup({
"/original": original,
"/pac3": pacHandler
});
PACSystemSettings.PACURI = "http://localhost:8080/pac3";
installFakePAC();
let res = new RESTRequest("http://localhost:8080/original");
res.get(function (error) {
do_check_true(pacFetched);
do_check_true(fetched);
do_check_true(!error);
do_check_true(this.response.success);
do_check_eq("TADA!", this.response.body);
uninstallFakePAC();
server.stop(run_next_test);
});
});
/**
* Demonstrate API short-hand: create a request and dispatch it immediately.
*/

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

@ -2,8 +2,21 @@ Cu.import("resource://services-sync/main.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/log4moz.js");
function run_test() {
var requestBody;
initTestLogging("Trace");
Log4Moz.repository.getLogger("Sync.AsyncResource").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.Resource").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
run_next_test();
}
add_test(function test_change_password() {
let requestBody;
let server;
function send(statusCode, status, body) {
return function(request, response) {
requestBody = readBytesFromInputStream(request.bodyInputStream);
@ -12,10 +25,7 @@ function run_test() {
};
}
let server;
try {
Weave.Service.serverURL = "http://localhost:8080/";
Weave.Service.username = "johndoe";
Weave.Service.password = "ilovejane";
@ -26,11 +36,10 @@ function run_test() {
do_check_eq(Weave.Service.password, "ilovejane");
_("Let's fire up the server and actually change the password.");
server = httpd_setup({
server = httpd_setup({
"/user/1.0/johndoe/password": send(200, "OK", ""),
"/user/1.0/janedoe/password": send(401, "Unauthorized", "Forbidden!")
});
do_test_pending();
res = Weave.Service.changePassword("ILoveJane83");
do_check_true(res);
@ -60,8 +69,6 @@ function run_test() {
} finally {
Weave.Svc.Prefs.resetBranch("");
Services.logins.removeAllLogins();
if (server) {
server.stop(do_test_finished);
}
server.stop(run_next_test);
}
}
});

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

@ -51,7 +51,7 @@ function run_test() {
do_check_true(Weave.Service.isLoggedIn);
do_check_eq(Weave.Status.login, Weave.LOGIN_SUCCEEDED);
_("Simulate having changed the password somehwere else.");
_("Simulate having changed the password somewhere else.");
Weave.Service.password = "ilovejosephine";
_("Let's try to sync.");
@ -63,12 +63,18 @@ function run_test() {
_("We're no longer logged in.");
do_check_false(Weave.Service.isLoggedIn);
_("Sync status.");
do_check_eq(Weave.Status.login, Weave.LOGIN_FAILED_LOGIN_REJECTED);
_("Sync status won't have changed yet, because we haven't tried again.");
_("globalScore is reset upon starting a sync.");
do_check_eq(SyncScheduler.globalScore, 0);
_("Our next sync will fail appropriately.");
try {
Weave.Service.sync();
} catch (ex) {
}
do_check_eq(Weave.Status.login, Weave.LOGIN_FAILED_LOGIN_REJECTED);
} finally {
Weave.Svc.Prefs.resetBranch("");
server.stop(do_test_finished);

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

@ -29,7 +29,28 @@ function run_test() {
let cryptoColl = new ServerCollection({keys: keysWBO});
let metaColl = new ServerCollection({global: meta_global});
do_test_pending();
/**
* Handle the bulk DELETE request sent by wipeServer.
*/
function storageHandler(request, response) {
do_check_eq("DELETE", request.method);
do_check_true(request.hasHeader("X-Confirm-Delete"));
_("Wiping out all collections.");
cryptoColl.delete({});
clients.delete({});
metaColl.delete({});
let ts = new_timestamp();
collectionsHelper.update_collection("crypto", ts);
collectionsHelper.update_collection("clients", ts);
collectionsHelper.update_collection("meta", ts);
return_timestamp(request, response, ts);
}
let server = httpd_setup({
"/1.1/johndoe/storage": storageHandler,
"/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()),
"/1.1/johndoe/storage/crypto": upd("crypto", cryptoColl.handler()),
"/1.1/johndoe/storage/clients": upd("clients", clients.handler()),

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

@ -3,9 +3,11 @@ Cu.import("resource://services-sync/engines/clients.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/status.js");
Svc.DefaultPrefs.set("registerEngines", "");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-sync/policies.js");
initTestLogging();
@ -66,13 +68,30 @@ function sync_httpd_setup(handlers) {
function setUp() {
Service.username = "johndoe";
Service.password = "ilovejane";
Service.passphrase = "sekrit";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/";
// So that we can poke at meta/global.
new FakeCryptoService();
// Ensure that the server has valid keys so that logging in will work and not
// result in a server wipe, rendering many of these tests useless.
generateNewKeys();
let serverKeys = CollectionKeys.asWBO("crypto", "keys");
serverKeys.encrypt(Service.syncKeyBundle);
return serverKeys.upload(Service.cryptoKeysURL).success;
}
const PAYLOAD = 42;
function run_test() {
initTestLogging("Trace");
Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Sync.ErrorHandler").level = Log4Moz.Level.Trace;
run_next_test();
}
add_test(function test_newAccount() {
_("Test: New account does not disable locally enabled engines.");
let engine = Engines.get("steam");
@ -89,7 +108,6 @@ add_test(function test_newAccount() {
Service._ignorePrefObserver = false;
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Engine continues to be enabled.");
@ -118,7 +136,6 @@ add_test(function test_enabledLocally() {
engine.enabled = true;
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Meta record now contains the new engine.");
@ -143,6 +160,7 @@ add_test(function test_disabledLocally() {
version: engine.version}}
});
let steamCollection = new ServerWBO("steam", PAYLOAD);
let server = sync_httpd_setup({
"/1.1/johndoe/storage/meta/global": metaWBO.handler(),
"/1.1/johndoe/storage/steam": steamCollection.handler()
@ -157,7 +175,6 @@ add_test(function test_disabledLocally() {
engine.enabled = false;
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Meta record no longer contains engine.");
@ -174,6 +191,50 @@ add_test(function test_disabledLocally() {
}
});
add_test(function test_disabledLocally_wipe503() {
_("Test: Engine is enabled on remote clients and disabled locally");
Service.syncID = "abcdefghij";
let engine = Engines.get("steam");
let metaWBO = new ServerWBO("global", {
syncID: Service.syncID,
storageVersion: STORAGE_VERSION,
engines: {steam: {syncID: engine.syncID,
version: engine.version}}
});
let steamCollection = new ServerWBO("steam", PAYLOAD);
function service_unavailable(request, response) {
let body = "Service Unavailable";
response.setStatusLine(request.httpVersion, 503, "Service Unavailable");
response.setHeader("Retry-After", "23");
response.bodyOutputStream.write(body, body.length);
}
let server = sync_httpd_setup({
"/1.1/johndoe/storage/meta/global": metaWBO.handler(),
"/1.1/johndoe/storage/steam": service_unavailable
});
setUp();
_("Disable engine locally.");
Service._ignorePrefObserver = true;
engine.enabled = true;
Service._ignorePrefObserver = false;
engine.enabled = false;
Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
Svc.Obs.remove("weave:ui:sync:error", onSyncError);
do_check_eq(Status.sync, SERVER_MAINTENANCE);
Service.startOver();
server.stop(run_next_test);
});
_("Sync.");
ErrorHandler.syncAndReportErrors();
});
add_test(function test_enabledRemotely() {
_("Test: Engine is disabled locally and enabled on a remote client");
Service.syncID = "abcdefghij";
@ -205,7 +266,6 @@ add_test(function test_enabledRemotely() {
do_check_false(engine.enabled);
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Engine is enabled.");
@ -242,7 +302,6 @@ add_test(function test_disabledRemotelyTwoClients() {
Service._ignorePrefObserver = false;
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Disable engine by deleting from meta/global.");
@ -284,7 +343,6 @@ add_test(function test_disabledRemotely() {
Service._ignorePrefObserver = false;
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Engine is not disabled: only one client.");
@ -316,7 +374,6 @@ add_test(function test_dependentEnginesEnabledLocally() {
steamEngine.enabled = true;
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Meta record now contains the new engines.");
@ -348,6 +405,7 @@ add_test(function test_dependentEnginesDisabledLocally() {
let steamCollection = new ServerWBO("steam", PAYLOAD);
let stirlingCollection = new ServerWBO("stirling", PAYLOAD);
let server = sync_httpd_setup({
"/1.1/johndoe/storage/meta/global": metaWBO.handler(),
"/1.1/johndoe/storage/steam": steamCollection.handler(),
@ -365,7 +423,6 @@ add_test(function test_dependentEnginesDisabledLocally() {
do_check_false(stirlingEngine.enabled);
_("Sync.");
Weave.Service.login();
Weave.Service.sync();
_("Meta record no longer contains engines.");
@ -384,7 +441,3 @@ add_test(function test_dependentEnginesDisabledLocally() {
server.stop(run_next_test);
}
});
function run_test() {
run_next_test();
}

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

@ -13,22 +13,19 @@ FakeCollection.prototype = {
let self = this;
return function(request, response) {
let body = "";
self.timestamp = new_timestamp();
let timestamp = "" + self.timestamp;
if (request.method == "DELETE") {
body = JSON.stringify(Date.now() / 1000);
body = timestamp;
self.deleted = true;
}
response.setHeader("X-Weave-Timestamp", timestamp);
response.setStatusLine(request.httpVersion, 200, "OK");
response.bodyOutputStream.write(body, body.length);
};
}
};
function serviceUnavailable(request, response) {
let body = "Service Unavailable";
response.setStatusLine(request.httpVersion, 503, "Service Unavailable");
response.bodyOutputStream.write(body, body.length);
}
function setUpTestFixtures() {
let cryptoService = new FakeCryptoService();
@ -37,7 +34,13 @@ function setUpTestFixtures() {
Service.passphrase = "aabcdeabcdeabcdeabcdeabcde";
}
function test_withCollectionList_fail() {
function run_test() {
initTestLogging("Trace");
run_next_test();
}
add_test(function test_wipeServer_list_success() {
_("Service.wipeServer() deletes collections given as argument.");
let steam_coll = new FakeCollection();
@ -45,10 +48,42 @@ function test_withCollectionList_fail() {
let server = httpd_setup({
"/1.1/johndoe/storage/steam": steam_coll.handler(),
"/1.1/johndoe/storage/petrol": serviceUnavailable,
"/1.1/johndoe/storage/diesel": diesel_coll.handler(),
"/1.1/johndoe/storage/petrol": httpd_handler(404, "Not Found")
});
try {
setUpTestFixtures();
_("Confirm initial environment.");
do_check_false(steam_coll.deleted);
do_check_false(diesel_coll.deleted);
_("wipeServer() will happily ignore the non-existent collection and use the timestamp of the last DELETE that was successful.");
let timestamp = Service.wipeServer(["steam", "diesel", "petrol"]);
do_check_eq(timestamp, diesel_coll.timestamp);
_("wipeServer stopped deleting after encountering an error with the 'petrol' collection, thus only 'steam' has been deleted.");
do_check_true(steam_coll.deleted);
do_check_true(diesel_coll.deleted);
} finally {
server.stop(run_next_test);
Svc.Prefs.resetBranch("");
}
});
add_test(function test_wipeServer_list_503() {
_("Service.wipeServer() deletes collections given as argument.");
let steam_coll = new FakeCollection();
let diesel_coll = new FakeCollection();
let server = httpd_setup({
"/1.1/johndoe/storage/steam": steam_coll.handler(),
"/1.1/johndoe/storage/petrol": httpd_handler(503, "Service Unavailable"),
"/1.1/johndoe/storage/diesel": diesel_coll.handler()
});
do_test_pending();
try {
setUpTestFixtures();
@ -61,84 +96,126 @@ function test_withCollectionList_fail() {
let error;
try {
Service.wipeServer(["non-existent", "steam", "petrol", "diesel"]);
do_throw("Should have thrown!");
} catch(ex) {
error = ex;
}
_("wipeServer() threw this exception: " + error);
do_check_true(error != undefined);
do_check_eq(error.status, 503);
_("wipeServer stopped deleting after encountering an error with the 'petrol' collection, thus only 'steam' has been deleted.");
do_check_true(steam_coll.deleted);
do_check_false(diesel_coll.deleted);
} finally {
server.stop(do_test_finished);
server.stop(run_next_test);
Svc.Prefs.resetBranch("");
}
}
});
function test_wipeServer_leaves_collections() {
_("Service.wipeServer() deletes everything but keys.");
add_test(function test_wipeServer_all_success() {
_("Service.wipeServer() deletes all the things.");
let steam_coll = new FakeCollection();
let diesel_coll = new FakeCollection();
let keys_coll = new FakeCollection();
function info_collections(request, response) {
let collections = {};
let timestamp = Date.now() / 1000;
if (!steam_coll.deleted)
collections.steam = timestamp
if (!diesel_coll.deleted)
collections.diesel = timestamp;
if (!keys_coll.deleted)
collections.keys = timestamp;
let body = JSON.stringify(collections);
response.setStatusLine(request.httpVersion, 200, "OK");
response.bodyOutputStream.write(body, body.length);
/**
* Handle the bulk DELETE request sent by wipeServer.
*/
let deleted = false;
let serverTimestamp;
function storageHandler(request, response) {
do_check_eq("DELETE", request.method);
do_check_true(request.hasHeader("X-Confirm-Delete"));
deleted = true;
serverTimestamp = return_timestamp(request, response);
}
let server = httpd_setup({
"/1.1/johndoe/storage/steam": steam_coll.handler(),
"/1.1/johndoe/storage/diesel": diesel_coll.handler(),
"/1.1/johndoe/storage/keys": keys_coll.handler(),
"/1.1/johndoe/info/collections": info_collections
"/1.1/johndoe/storage": storageHandler
});
do_test_pending();
setUpTestFixtures();
try {
setUpTestFixtures();
_("Info URL: " + Service.infoURL);
_("Try deletion.");
let returnedTimestamp = Service.wipeServer();
do_check_true(deleted);
do_check_eq(returnedTimestamp, serverTimestamp);
_("Confirm initial environment.");
do_check_false(steam_coll.deleted);
do_check_false(diesel_coll.deleted);
do_check_false(keys_coll.deleted);
_("Collections: " + new Resource(Service.infoURL).get());
_("Try deletion.");
Service.wipeServer();
_("Collections: " + new Resource(Service.infoURL).get());
_("Make sure keys is still present.");
do_check_true(steam_coll.deleted);
do_check_true(diesel_coll.deleted);
do_check_false(keys_coll.deleted);
_("Delete everything.");
Service.wipeServer(null, true);
do_check_true(steam_coll.deleted);
do_check_true(diesel_coll.deleted);
do_check_true(keys_coll.deleted);
} finally {
server.stop(do_test_finished);
Svc.Prefs.resetBranch("");
server.stop(run_next_test);
Svc.Prefs.resetBranch("");
});
add_test(function test_wipeServer_all_404() {
_("Service.wipeServer() accepts a 404.");
/**
* Handle the bulk DELETE request sent by wipeServer. Returns a 404.
*/
let deleted = false;
let serverTimestamp;
function storageHandler(request, response) {
do_check_eq("DELETE", request.method);
do_check_true(request.hasHeader("X-Confirm-Delete"));
deleted = true;
serverTimestamp = new_timestamp();
response.setHeader("X-Weave-Timestamp", "" + serverTimestamp);
response.setStatusLine(request.httpVersion, 404, "Not Found");
}
}
function run_test() {
initTestLogging("Trace");
test_withCollectionList_fail();
test_wipeServer_leaves_collections();
}
let server = httpd_setup({
"/1.1/johndoe/storage": storageHandler
});
setUpTestFixtures();
_("Try deletion.");
let returnedTimestamp = Service.wipeServer();
do_check_true(deleted);
do_check_eq(returnedTimestamp, serverTimestamp);
server.stop(run_next_test);
Svc.Prefs.resetBranch("");
});
add_test(function test_wipeServer_all_503() {
_("Service.wipeServer() throws if it encounters a non-200/404 response.");
/**
* Handle the bulk DELETE request sent by wipeServer. Returns a 503.
*/
function storageHandler(request, response) {
do_check_eq("DELETE", request.method);
do_check_true(request.hasHeader("X-Confirm-Delete"));
response.setStatusLine(request.httpVersion, 503, "Service Unavailable");
}
let server = httpd_setup({
"/1.1/johndoe/storage": storageHandler
});
setUpTestFixtures();
_("Try deletion.");
let error;
try {
Service.wipeServer();
do_throw("Should have thrown!");
} catch (ex) {
error = ex;
}
do_check_eq(error.status, 503);
server.stop(run_next_test);
Svc.Prefs.resetBranch("");
});
add_test(function test_wipeServer_all_connectionRefused() {
_("Service.wipeServer() throws if it encounters a network problem.");
setUpTestFixtures();
_("Try deletion.");
try {
Service.wipeServer();
do_throw("Should have thrown!");
} catch (ex) {
do_check_eq(ex.result, Cr.NS_ERROR_CONNECTION_REFUSED);
}
run_next_test();
Svc.Prefs.resetBranch("");
});

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

@ -71,23 +71,6 @@ function cleanUpAndGo(server) {
});
}
let timer;
function waitForZeroTimer(callback) {
// First wait >100ms (nsITimers can take up to that much time to fire, so
// we can account for the timer in delayedAutoconnect) and then two event
// loop ticks (to account for the Utils.nextTick() in autoConnect).
let ticks = 2;
function wait() {
if (ticks) {
ticks -= 1;
Utils.nextTick(wait);
return;
}
callback();
}
timer = Utils.namedTimer(wait, 150, {}, "timer");
}
function run_test() {
initTestLogging("Trace");
@ -126,7 +109,7 @@ add_test(function test_prefAttributes() {
do_check_eq(SyncScheduler.syncThreshold, THRESHOLD);
_("'globalScore' corresponds to preference, defaults to zero.");
do_check_eq(Svc.Prefs.get('globalScore'), undefined);
do_check_eq(Svc.Prefs.get('globalScore'), 0);
do_check_eq(SyncScheduler.globalScore, 0);
SyncScheduler.globalScore = SCORE;
do_check_eq(SyncScheduler.globalScore, SCORE);
@ -863,3 +846,80 @@ add_test(function test_sync_503_Retry_After() {
cleanUpAndGo(server);
});
add_test(function test_loginError_recoverable_reschedules() {
_("Verify that a recoverable login error schedules a new sync.");
Service.username = "johndoe";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/";
Service.persistLogin();
Status.resetSync(); // reset Status.login
Svc.Obs.add("weave:service:login:error", function onLoginError() {
Svc.Obs.remove("weave:service:login:error", onLoginError);
Utils.nextTick(function aLittleBitAfterLoginError() {
do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
let expectedNextSync = Date.now() + SyncScheduler.syncInterval;
do_check_true(SyncScheduler.nextSync > Date.now());
do_check_true(SyncScheduler.nextSync <= expectedNextSync);
do_check_true(SyncScheduler.syncTimer.delay > 0);
do_check_true(SyncScheduler.syncTimer.delay <= SyncScheduler.syncInterval);
Svc.Obs.remove("weave:service:sync:start", onSyncStart);
cleanUpAndGo();
});
});
// Let's set it up so that a sync is overdue, both in terms of previously
// scheduled syncs and the global score. We still do not expect an immediate
// sync because we just tried (duh).
SyncScheduler.nextSync = Date.now() - 100000;
SyncScheduler.globalScore = SINGLE_USER_THRESHOLD + 1;
function onSyncStart() {
do_throw("Shouldn't have started a sync!");
}
Svc.Obs.add("weave:service:sync:start", onSyncStart);
// Sanity check.
do_check_eq(SyncScheduler.syncTimer, null);
do_check_eq(Status.checkSetup(), STATUS_OK);
do_check_eq(Status.login, LOGIN_SUCCEEDED);
SyncScheduler.scheduleNextSync(0);
});
add_test(function test_loginError_fatal_clearsTriggers() {
_("Verify that a fatal login error clears sync triggers.");
Service.username = "johndoe";
Service.password = "ilovejane";
Service.passphrase = "abcdeabcdeabcdeabcdeabcdea";
Service.clusterURL = "http://localhost:8080/";
Service.persistLogin();
Status.resetSync(); // reset Status.login
let server = httpd_setup({
"/1.1/johndoe/info/collections": httpd_handler(401, "Unauthorized")
});
Svc.Obs.add("weave:service:login:error", function onLoginError() {
Svc.Obs.remove("weave:service:login:error", onLoginError);
Utils.nextTick(function aLittleBitAfterLoginError() {
do_check_eq(Status.login, LOGIN_FAILED_LOGIN_REJECTED);
do_check_eq(SyncScheduler.nextSync, 0);
do_check_eq(SyncScheduler.syncTimer, null);
cleanUpAndGo(server);
});
});
// Sanity check.
do_check_eq(SyncScheduler.nextSync, 0);
do_check_eq(SyncScheduler.syncTimer, null);
do_check_eq(Status.checkSetup(), STATUS_OK);
do_check_eq(Status.login, LOGIN_SUCCEEDED);
SyncScheduler.scheduleNextSync(0);
});

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

@ -48,6 +48,7 @@ skip-if = os == "win" || os == "android"
[test_keys.js]
[test_load_modules.js]
[test_log4moz.js]
[test_node_reassignment.js]
[test_notifications.js]
[test_password_store.js]
[test_password_tracker.js]

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

@ -126,7 +126,7 @@ var Logger =
var now = new Date()
this.write(now.getFullYear() + "-" + (now.getMonth() < 9 ? '0' : '') +
(now.getMonth() + 1) + "-" +
(now.getDay() < 9 ? '0' : '') + (now.getDay() + 1) + " " +
(now.getDate() < 9 ? '0' : '') + (now.getDate() + 1) + " " +
(now.getHours() < 10 ? '0' : '') + now.getHours() + ":" +
(now.getMinutes() < 10 ? '0' : '') + now.getMinutes() + ":" +
(now.getSeconds() < 10 ? '0' : '') + now.getSeconds() + " " +

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

@ -140,7 +140,6 @@ var TPS =
Logger.logInfo("sync error; retrying...");
this._syncErrors++;
this._waitingForSync = false;
Weave.Service.logout();
Utils.nextTick(this.RunNextTestAction, this);
}
else if (this._waitingForSync) {
@ -156,7 +155,6 @@ var TPS =
// Wait a second before continuing, otherwise we can get
// 'sync not complete' errors.
Utils.namedTimer(function() {
Weave.Service.logout();
this.FinishAsyncOperation();
}, 1000, this, "postsync");
}

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

@ -41,7 +41,7 @@ from setuptools import setup, find_packages
version = '0.2.40'
deps = ['pulsebuildmonitor >= 0.2', 'MozillaPulse == .4',
'mozinfo == 0.3.1', 'mozprofile == 0.1a',
'mozinfo == 0.3.1', 'mozprofile == 0.1t',
'mozprocess == 0.1a', 'mozrunner == 3.0a', 'mozregression == 0.3',
'mozautolog >= 0.2.1']

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

@ -37,7 +37,7 @@
import datetime
def GenerateEmailBody(data, numpassed, numfailed, serverUrl):
def GenerateEmailBody(data, numpassed, numfailed, serverUrl, buildUrl):
now = datetime.datetime.now()
builddate = datetime.datetime.strptime(data['productversion']['buildid'],
@ -46,7 +46,7 @@ def GenerateEmailBody(data, numpassed, numfailed, serverUrl):
row = """
<tr>
<td><a href="http://hg.mozilla.org/services/services-central/file/tip/services/sync/tests/tps/{name}">{name}</a></td>
<td><a href="http://hg.mozilla.org/services/services-central/file/default/services/sync/tests/tps/{name}">{name}</a></td>
<td>{state}</td>
<td>{message}</td>
</tr>
@ -54,7 +54,7 @@ def GenerateEmailBody(data, numpassed, numfailed, serverUrl):
rowWithLog = """
<tr>
<td><a href="http://hg.mozilla.org/services/services-central/services/sync/tests/tps/file/tip/{name}">{name}</a></td>
<td><a href="http://hg.mozilla.org/services/services-central/file/default/services/sync/tests/tps/{name}">{name}</a></td>
<td>{state}</td>
<td>{message} [<a href="{logurl}">view log</a>]</td>
</tr>
@ -72,6 +72,9 @@ def GenerateEmailBody(data, numpassed, numfailed, serverUrl):
state=test['state'],
message=test['message'] if test['message'] else 'None')
firefox_version = data['productversion']['version']
if buildUrl is not None:
firefox_version = "<a href='%s'>%s</a>" % (buildUrl, firefox_version)
body = """
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
@ -169,7 +172,7 @@ def GenerateEmailBody(data, numpassed, numfailed, serverUrl):
</html>
""".format(date=now.ctime(),
firefox_version=data['productversion']['version'],
firefox_version=firefox_version,
firefox_date=builddate.ctime(),
sync_version=data['addonversion']['version'],
sync_type=data['synctype'],

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

@ -433,7 +433,14 @@ class TPSTestRunner(object):
from tps.emailtemplate import GenerateEmailBody
if body is None:
body = GenerateEmailBody(self.postdata, self.numpassed, self.numfailed, self.config['account']['serverURL'])
buildUrl = None
if self.firefoxRunner and self.firefoxRunner.url:
buildUrl = self.firefoxRunner.url
body = GenerateEmailBody(self.postdata,
self.numpassed,
self.numfailed,
self.config['account']['serverURL'],
self.buildUrl)
subj = "TPS Report: "
if self.numfailed == 0 and self.numpassed > 0:

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

@ -49,7 +49,6 @@
#include "nsILocalFile.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIPrefService.h"
#include "nsIProfileChangeStatus.h"
#include "nsISimpleEnumerator.h"
#include "nsIToolkitChromeRegistry.h"
@ -68,6 +67,7 @@
#include "nsReadableUtils.h"
#include "mozilla/Services.h"
#include "mozilla/Omnijar.h"
#include "mozilla/Preferences.h"
#include <stdlib.h>
@ -508,7 +508,7 @@ LoadExtensionDirectories(nsINIParser &parser,
{
nsresult rv;
PRInt32 i = 0;
nsCOMPtr<nsIPrefServiceInternal> prefs =
nsCOMPtr<nsIPrefService> prefs =
do_GetService("@mozilla.org/preferences-service;1");
do {
nsCAutoString buf("Extension");
@ -531,7 +531,7 @@ LoadExtensionDirectories(nsINIParser &parser,
XRE_AddJarManifestLocation(aType, dir);
if (!prefs)
continue;
prefs->ReadExtensionPrefs(dir);
mozilla::Preferences::ReadExtensionPrefs(dir);
}
else {
aDirectories.AppendObject(dir);