merge fx-team to mozilla-central

This commit is contained in:
Carsten "Tomcat" Book 2014-04-17 12:49:25 +02:00
commit b5c721a037
25 changed files with 596 additions and 249 deletions

View File

@ -1425,6 +1425,8 @@ pref("browser.cache.auto_delete_cache_version", 1);
// -1 means no experiment is run and we use the preferred value for frecency (6h)
pref("browser.cache.frecency_experiment", 0);
pref("browser.translation.detectLanguage", false);
// Telemetry experiments settings.
pref("experiments.enabled", false);
pref("experiments.manifest.fetchIntervalSeconds", 86400);

View File

@ -147,6 +147,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "gCustomizationTabPreloader",
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Translation",
"resource:///modules/translation/Translation.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SitePermissions",
"resource:///modules/SitePermissions.jsm");
@ -1033,6 +1036,7 @@ var gBrowserInit = {
gFormSubmitObserver.init();
gRemoteTabsUI.init();
gPageStyleMenu.init();
LanguageDetectionListener.init();
// Initialize the full zoom setting.
// We do this before the session restore service gets initialized so we can
@ -4464,9 +4468,6 @@ var TabsInTitlebar = {
let titlebar = $("titlebar");
let titlebarContent = $("titlebar-content");
let menubar = $("toolbar-menubar");
#ifdef XP_MACOSX
let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
#endif
if (allowed) {
// We set the tabsintitlebar attribute first so that our CSS for
@ -4484,6 +4485,7 @@ var TabsInTitlebar = {
let captionButtonsBoxWidth = rect($("titlebar-buttonbox-container")).width;
#ifdef XP_MACOSX
let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
// No need to look up the menubar stuff on OS X:
let menuHeight = 0;
let fullMenuHeight = 0;
@ -4558,6 +4560,9 @@ var TabsInTitlebar = {
// Finally, size the placeholders:
#ifdef XP_MACOSX
this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
#endif
this._sizePlaceholder("caption-buttons", captionButtonsBoxWidth);
if (!this._draghandles) {
@ -4579,16 +4584,16 @@ var TabsInTitlebar = {
document.documentElement.removeAttribute("tabsintitlebar");
updateTitlebarDisplay();
#ifdef XP_MACOSX
let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
#endif
// Reset the margins and padding that might have been modified:
titlebarContent.style.marginTop = "";
titlebarContent.style.marginBottom = "";
titlebar.style.marginBottom = "";
menubar.style.paddingBottom = "";
}
#ifdef XP_MACOSX
this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
#endif
},
_sizePlaceholder: function (type, width) {
@ -5342,6 +5347,15 @@ function setStyleDisabled(disabled) {
}
var LanguageDetectionListener = {
init: function() {
window.messageManager.addMessageListener("LanguageDetection:Result", msg => {
Translation.languageDetected(msg.target, msg.data);
});
}
};
var BrowserOffline = {
_inited: false,

View File

@ -12,6 +12,8 @@ Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
"resource:///modules/ContentLinkHandler.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
"resource:///modules/translation/LanguageDetector.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
"resource://gre/modules/LoginManagerContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
@ -387,3 +389,49 @@ let PageStyleHandler = {
},
};
PageStyleHandler.init();
let TranslationHandler = {
init: function() {
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
},
/* nsIWebProgressListener implementation */
onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
if (!aWebProgress.isTopLevel ||
!(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP))
return;
let url = aRequest.name;
if (!url.startsWith("http://") && !url.startsWith("https://"))
return;
// Grab a 60k sample of text from the page.
let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
.createInstance(Ci.nsIDocumentEncoder);
encoder.init(content.document, "text/plain", encoder.SkipInvisibleContent);
let string = encoder.encodeToStringWithMaxLength(60 * 1024);
// Language detection isn't reliable on very short strings.
if (string.length < 100)
return;
LanguageDetector.detectLanguage(string).then(result => {
if (result.confident)
sendAsyncMessage("LanguageDetection:Result", result.language);
});
},
// Unused methods.
onProgressChange: function() {},
onLocationChange: function() {},
onStatusChange: function() {},
onSecurityChange: function() {},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference])
};
if (Services.prefs.getBoolPref("browser.translation.detectLanguage"))
TranslationHandler.init();

View File

@ -11,42 +11,11 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
"resource:///modules/translation/LanguageDetector.jsm");
/* Create an object keeping the information related to translation for
* a specific browser. This object is passed to the translation
* infobar so that it can initialize itself. The properties exposed to
* the infobar are:
* - supportedSourceLanguages, array of supported source language codes
* - supportedTargetLanguages, array of supported target language codes
* - detectedLanguage, code of the language detected on the web page.
* - defaultTargetLanguage, code of the language to use by default for
* translation.
* - state, the state in which the infobar should be displayed
* - STATE_{OFFER,TRANSLATING,TRANSLATED,ERROR} constants.
* - translatedFrom, if already translated, source language code.
* - translatedTo, if already translated, target language code.
* - translate, method starting the translation of the current page.
* - showOriginalContent, method showing the original page content.
* - showTranslatedContent, method showing the translation for an
* already translated page whose original content is shown.
* - originalShown, boolean indicating if the original or translated
* version of the page is shown.
*/
this.Translation = function(aBrowser) {
this.browser = aBrowser;
};
this.Translation.prototype = {
this.Translation = {
supportedSourceLanguages: ["en", "zh", "ja", "es", "de", "fr", "ru", "ar", "ko", "pt"],
supportedTargetLanguages: ["en", "pl", "tr", "vi"],
STATE_OFFER: 0,
STATE_TRANSLATING: 1,
STATE_TRANSLATED: 2,
STATE_ERROR: 3,
_defaultTargetLanguage: "",
get defaultTargetLanguage() {
if (!this._defaultTargetLanguage) {
@ -58,6 +27,43 @@ this.Translation.prototype = {
return this._defaultTargetLanguage;
},
languageDetected: function(aBrowser, aDetectedLanguage) {
if (this.supportedSourceLanguages.indexOf(aDetectedLanguage) != -1 &&
aDetectedLanguage != this.defaultTargetLanguage) {
if (!aBrowser.translationUI)
aBrowser.translationUI = new TranslationUI(aBrowser);
aBrowser.translationUI.showTranslationUI(aDetectedLanguage);
}
}
};
/* TranslationUI objects keep the information related to translation for
* a specific browser. This object is passed to the translation
* infobar so that it can initialize itself. The properties exposed to
* the infobar are:
* - detectedLanguage, code of the language detected on the web page.
* - state, the state in which the infobar should be displayed
* - STATE_{OFFER,TRANSLATING,TRANSLATED,ERROR} constants.
* - translatedFrom, if already translated, source language code.
* - translatedTo, if already translated, target language code.
* - translate, method starting the translation of the current page.
* - showOriginalContent, method showing the original page content.
* - showTranslatedContent, method showing the translation for an
* already translated page whose original content is shown.
* - originalShown, boolean indicating if the original or translated
* version of the page is shown.
*/
function TranslationUI(aBrowser) {
this.browser = aBrowser;
}
TranslationUI.prototype = {
STATE_OFFER: 0,
STATE_TRANSLATING: 1,
STATE_TRANSLATED: 2,
STATE_ERROR: 3,
get doc() this.browser.contentDocument,
translate: function(aFrom, aTo) {

View File

@ -29,8 +29,6 @@ function waitForCondition(condition, nextTest, errorMsg) {
}
var TranslationStub = {
__proto__: Translation.prototype,
translate: function(aFrom, aTo) {
this.state = this.STATE_TRANSLATING;
this.translatedFrom = aFrom;
@ -54,6 +52,14 @@ var TranslationStub = {
}
};
function showTranslationUI(aDetectedLanguage) {
let browser = gBrowser.selectedBrowser;
Translation.languageDetected(browser, aDetectedLanguage);
let ui = browser.translationUI;
for (let name of ["translate", "_reset", "failTranslation", "finishTranslation"])
ui[name] = TranslationStub[name];
return ui.notificationBox.getNotificationWithValue("translation");
}
function test() {
waitForExplicitFinish();
@ -83,35 +89,35 @@ function checkURLBarIcon(aExpectTranslated = false) {
function run_tests(aFinishCallback) {
info("Show an info bar saying the current page is in French");
let notif = TranslationStub.showTranslationUI("fr");
is(notif.state, TranslationStub.STATE_OFFER, "the infobar is offering translation");
let notif = showTranslationUI("fr");
is(notif.state, notif.translation.STATE_OFFER, "the infobar is offering translation");
is(notif._getAnonElt("detectedLanguage").value, "fr", "The detected language is displayed");
checkURLBarIcon();
info("Click the 'Translate' button");
notif._getAnonElt("translate").click();
is(notif.state, TranslationStub.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "fr", "from language correct");
is(TranslationStub.translatedTo, TranslationStub.defaultTargetLanguage, "from language correct");
is(notif.state, notif.translation.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(notif.translation.translatedFrom, "fr", "from language correct");
is(notif.translation.translatedTo, Translation.defaultTargetLanguage, "from language correct");
checkURLBarIcon();
info("Make the translation fail and check we are in the error state.");
TranslationStub.failTranslation();
is(notif.state, TranslationStub.STATE_ERROR, "infobar in the error state");
notif.translation.failTranslation();
is(notif.state, notif.translation.STATE_ERROR, "infobar in the error state");
checkURLBarIcon();
info("Click the try again button");
notif._getAnonElt("tryAgain").click();
is(notif.state, TranslationStub.STATE_TRANSLATING, "infobar in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "fr", "from language correct");
is(TranslationStub.translatedTo, TranslationStub.defaultTargetLanguage, "from language correct");
is(notif.state, notif.translation.STATE_TRANSLATING, "infobar in the translating state");
ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(notif.translation.translatedFrom, "fr", "from language correct");
is(notif.translation.translatedTo, Translation.defaultTargetLanguage, "from language correct");
checkURLBarIcon();
info("Make the translation succeed and check we are in the 'translated' state.");
TranslationStub.finishTranslation();
is(notif.state, TranslationStub.STATE_TRANSLATED, "infobar in the translated state");
notif.translation.finishTranslation();
is(notif.state, notif.translation.STATE_TRANSLATED, "infobar in the translated state");
checkURLBarIcon(true);
info("Test 'Show original' / 'Show Translation' buttons.");
@ -137,45 +143,45 @@ function run_tests(aFinishCallback) {
let from = notif._getAnonElt("fromLanguage");
from.value = "es";
from.doCommand();
is(notif.state, TranslationStub.STATE_TRANSLATING, "infobar in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "es", "from language correct");
is(TranslationStub.translatedTo, TranslationStub.defaultTargetLanguage, "to language correct");
is(notif.state, notif.translation.STATE_TRANSLATING, "infobar in the translating state");
ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(notif.translation.translatedFrom, "es", "from language correct");
is(notif.translation.translatedTo, Translation.defaultTargetLanguage, "to language correct");
// We want to show the 'translated' icon while re-translating,
// because we are still displaying the previous translation.
checkURLBarIcon(true);
TranslationStub.finishTranslation();
notif.translation.finishTranslation();
checkURLBarIcon(true);
info("Check that changing the target language causes a re-translation");
let to = notif._getAnonElt("toLanguage");
to.value = "pl";
to.doCommand();
is(notif.state, TranslationStub.STATE_TRANSLATING, "infobar in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "es", "from language correct");
is(TranslationStub.translatedTo, "pl", "to language correct");
is(notif.state, notif.translation.STATE_TRANSLATING, "infobar in the translating state");
ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(notif.translation.translatedFrom, "es", "from language correct");
is(notif.translation.translatedTo, "pl", "to language correct");
checkURLBarIcon(true);
TranslationStub.finishTranslation();
notif.translation.finishTranslation();
checkURLBarIcon(true);
// Cleanup.
notif.close();
info("Reopen the info bar to check that it's possible to override the detected language.");
notif = TranslationStub.showTranslationUI("fr");
is(notif.state, TranslationStub.STATE_OFFER, "the infobar is offering translation");
notif = showTranslationUI("fr");
is(notif.state, notif.translation.STATE_OFFER, "the infobar is offering translation");
is(notif._getAnonElt("detectedLanguage").value, "fr", "The detected language is displayed");
// Change the language and click 'Translate'
notif._getAnonElt("detectedLanguage").value = "ja";
notif._getAnonElt("translate").click();
is(notif.state, TranslationStub.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!TranslationStub.translatedFrom, "Translation.translate has been called");
is(TranslationStub.translatedFrom, "ja", "from language correct");
is(notif.state, notif.translation.STATE_TRANSLATING, "the infobar is in the translating state");
ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
is(notif.translation.translatedFrom, "ja", "from language correct");
notif.close();
info("Reopen to check the 'Not Now' button closes the notification.");
notif = TranslationStub.showTranslationUI("fr");
notif = showTranslationUI("fr");
let notificationBox = gBrowser.getNotificationBox();
ok(!!notificationBox.getNotificationWithValue("translation"), "there's a 'translate' notification");
notif._getAnonElt("notNow").click();

View File

@ -102,7 +102,7 @@
// Fill the lists of supported source languages.
let detectedLanguage = this._getAnonElt("detectedLanguage");
let fromLanguage = this._getAnonElt("fromLanguage");
for (let code of this.translation.supportedSourceLanguages) {
for (let code of Translation.supportedSourceLanguages) {
let name = bundle.GetStringFromName(code);
detectedLanguage.appendItem(name, code);
fromLanguage.appendItem(name, code);
@ -115,7 +115,7 @@
// Fill the list of supporter target languages.
let toLanguage = this._getAnonElt("toLanguage");
for (let code of this.translation.supportedTargetLanguages)
for (let code of Translation.supportedTargetLanguages)
toLanguage.appendItem(bundle.GetStringFromName(code), code);
if (aTranslation.translatedTo)
@ -143,7 +143,7 @@
this._getAnonElt("fromLanguage").value =
this._getAnonElt("detectedLanguage").value;
this._getAnonElt("toLanguage").value =
this.translation.defaultTargetLanguage;
Translation.defaultTargetLanguage;
}
this._handleButtonHiding(false);

View File

@ -252,6 +252,10 @@ Experiments.Policy = function () {
this._log = Log.repository.getLoggerWithMessagePrefix(
"Browser.Experiments.Policy",
"Policy #" + gPolicyCounter++ + "::");
// Set to true to ignore hash verification on downloaded XPIs. This should
// not be used outside of testing.
this.ignoreHashes = false;
};
Experiments.Policy.prototype = {
@ -372,19 +376,24 @@ Experiments.Experiments.prototype = {
AsyncShutdown.profileBeforeChange.addBlocker("Experiments.jsm shutdown",
this.uninit.bind(this));
this._startWatchingAddons();
this._registerWithAddonManager();
this._loadTask = Task.spawn(this._loadFromCache.bind(this));
let deferred = Promise.defer();
this._loadTask = this._loadFromCache();
this._loadTask.then(
() => {
this._log.trace("_loadTask finished ok");
this._loadTask = null;
this._run();
this._run().then(deferred.resolve, deferred.reject);
},
(e) => {
this._log.error("_loadFromCache caught error: " + e);
deferred.reject(e);
}
);
return deferred.promise;
},
/**
@ -402,7 +411,7 @@ Experiments.Experiments.prototype = {
yield this._loadTask;
if (!this._shutdown) {
this._stopWatchingAddons();
this._unregisterWithAddonManager();
gPrefs.ignore(PREF_LOGGING, configureLogging);
gPrefs.ignore(PREF_MANIFEST_URI, this.updateManifest, this);
@ -423,12 +432,14 @@ Experiments.Experiments.prototype = {
this._log.info("Completed uninitialization.");
}),
_startWatchingAddons: function () {
_registerWithAddonManager: function () {
this._log.trace("Registering instance with Addon Manager.");
AddonManager.addAddonListener(this);
AddonManager.addInstallListener(this);
},
_stopWatchingAddons: function () {
_unregisterWithAddonManager: function () {
AddonManager.removeInstallListener(this);
AddonManager.removeAddonListener(this);
},
@ -812,7 +823,7 @@ Experiments.Experiments.prototype = {
/*
* Task function, load the cached experiments manifest file from disk.
*/
_loadFromCache: function*() {
_loadFromCache: Task.async(function* () {
this._log.trace("_loadFromCache");
let path = this._cacheFilePath;
try {
@ -822,7 +833,7 @@ Experiments.Experiments.prototype = {
// No cached manifest yet.
this._experiments = new Map();
}
},
}),
_populateFromCache: function (data) {
this._log.trace("populateFromCache() - data: " + JSON.stringify(data));
@ -1498,8 +1509,9 @@ Experiments.ExperimentEntry.prototype = {
_installAddon: function* () {
let deferred = Promise.defer();
let install = yield addonInstallForURL(this._manifestData.xpiURL,
this._manifestData.xpiHash);
let hash = this._policy.ignoreHashes ? null : this._manifestData.xpiHash;
let install = yield addonInstallForURL(this._manifestData.xpiURL, hash);
gActiveInstallURLs.add(install.sourceURI.spec);
let failureHandler = (install, handler) => {

View File

@ -140,4 +140,23 @@ add_task(function* test_startStop() {
Assert.equal(experiment.enabled, false, "Experiment should be disabled.");
addons = yield getExperimentAddons();
Assert.equal(addons.length, 0, "Experiment add-on is uninstalled.");
// Ensure hash validation works.
// We set an incorrect hash and expect the install to fail.
experiment._manifestData.xpiHash = "sha1:41014dcc66b4dcedcd973491a1530a32f0517d8a";
let errored = false;
try {
yield experiment.start();
} catch (ex) {
errored = true;
}
Assert.ok(experiment._failedStart, "Experiment failed to start.");
Assert.ok(errored, "start() threw an exception.");
// Make sure "ignore hashes" mode works.
gPolicy.ignoreHashes = true;
let changes = yield experiment.start();
Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL);
yield experiment.stop();
gPolicy.ignoreHashes = false;
});

View File

@ -1366,9 +1366,9 @@ add_task(function* testUnknownExperimentsUninstalled() {
Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are present.");
// Simulate us not listening.
experiments._stopWatchingAddons();
experiments._unregisterWithAddonManager();
yield AddonTestUtils.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
experiments._startWatchingAddons();
experiments._registerWithAddonManager();
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "Experiment 1 installed via AddonManager");
@ -1452,9 +1452,9 @@ add_task(function* testEnabledAfterRestart() {
Assert.ok(addons[0].isActive, "That experiment is active.");
dump("Restarting Addon Manager\n");
experiments._stopWatchingAddons();
experiments._unregisterWithAddonManager();
restartManager();
experiments._startWatchingAddons();
experiments._registerWithAddonManager();
addons = yield getExperimentAddons();
Assert.equal(addons.length, 1, "The experiment is still there after restart.");

View File

@ -55,7 +55,7 @@
}
@keyframes moveY {
/* These values are adjusted for the padding and height of the panel. */
from { margin-top: -6px; } to { margin-top: 58px; }
from { margin-top: -.5em; } to { margin-top: calc(64px - .5em); }
}
#PanelUI-button {

View File

@ -35,7 +35,7 @@ interface nsIDocumentEncoderNodeFixup : nsISupports
nsIDOMNode fixupNode(in nsIDOMNode aNode, out boolean aSerializeCloneKids);
};
[scriptable, uuid(7222bdf1-c2b9-41f1-a40a-a3d65283a95b)]
[scriptable, uuid(1158bd7e-a08b-4ff6-9417-6f99144cfccc)]
interface nsIDocumentEncoder : nsISupports
{
// Output methods flag bits. There are a frightening number of these,
@ -329,6 +329,21 @@ interface nsIDocumentEncoder : nsISupports
AString encodeToStringWithContext( out AString aContextString,
out AString aInfoString);
/**
* Encode the document into a string of limited size.
* @param aMaxLength After aMaxLength characters, the encoder will stop
* encoding new data.
* Only values > 0 will be considered.
* The returned string may be slightly larger than
* aMaxLength because some serializers (eg. HTML)
* may need to close some tags after they stop
* encoding new data, or finish a line (72 columns
* by default for the plain text serializer).
*
* @return The document encoded into a string.
*/
AString encodeToStringWithMaxLength(in unsigned long aMaxLength);
/**
* Set the fixup object associated with node persistence.
* @param aFixup The fixup object.

View File

@ -81,7 +81,8 @@ protected:
nsINode* aOriginalNode = nullptr);
nsresult SerializeToStringRecursive(nsINode* aNode,
nsAString& aStr,
bool aDontSerializeRoot);
bool aDontSerializeRoot,
uint32_t aMaxLength = 0);
nsresult SerializeNodeEnd(nsINode* aNode, nsAString& aStr);
// This serializes the content of aNode.
nsresult SerializeToStringIterative(nsINode* aNode,
@ -450,8 +451,13 @@ nsDocumentEncoder::SerializeNodeEnd(nsINode* aNode,
nsresult
nsDocumentEncoder::SerializeToStringRecursive(nsINode* aNode,
nsAString& aStr,
bool aDontSerializeRoot)
bool aDontSerializeRoot,
uint32_t aMaxLength)
{
if (aMaxLength > 0 && aStr.Length() >= aMaxLength) {
return NS_OK;
}
if (!IsVisibleNode(aNode))
return NS_OK;
@ -487,7 +493,12 @@ nsDocumentEncoder::SerializeToStringRecursive(nsINode* aNode,
}
if (!aDontSerializeRoot) {
rv = SerializeNodeStart(maybeFixedNode, 0, -1, aStr, aNode);
int32_t endOffset = -1;
if (aMaxLength > 0) {
MOZ_ASSERT(aMaxLength >= aStr.Length());
endOffset = aMaxLength - aStr.Length();
}
rv = SerializeNodeStart(maybeFixedNode, 0, endOffset, aStr, aNode);
NS_ENSURE_SUCCESS(rv, rv);
}
@ -496,7 +507,7 @@ nsDocumentEncoder::SerializeToStringRecursive(nsINode* aNode,
for (nsINode* child = nsNodeUtils::GetFirstChildOfTemplateOrNode(node);
child;
child = child->GetNextSibling()) {
rv = SerializeToStringRecursive(child, aStr, false);
rv = SerializeToStringRecursive(child, aStr, false, aMaxLength);
NS_ENSURE_SUCCESS(rv, rv);
}
@ -1016,6 +1027,13 @@ nsDocumentEncoder::SerializeRangeToString(nsRange *aRange,
NS_IMETHODIMP
nsDocumentEncoder::EncodeToString(nsAString& aOutputString)
{
return EncodeToStringWithMaxLength(0, aOutputString);
}
NS_IMETHODIMP
nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength,
nsAString& aOutputString)
{
if (!mDocument)
return NS_ERROR_NOT_INITIALIZED;
@ -1146,7 +1164,7 @@ nsDocumentEncoder::EncodeToString(nsAString& aOutputString)
rv = mSerializer->AppendDocumentStart(mDocument, output);
if (NS_SUCCEEDED(rv)) {
rv = SerializeToStringRecursive(mDocument, output, false);
rv = SerializeToStringRecursive(mDocument, output, false, aMaxLength);
}
}

View File

@ -278,7 +278,8 @@ nsPlainTextSerializer::AppendText(nsIContent* aText,
return NS_ERROR_FAILURE;
}
int32_t endoffset = (aEndOffset == -1) ? frag->GetLength() : aEndOffset;
int32_t fragLength = frag->GetLength();
int32_t endoffset = (aEndOffset == -1) ? fragLength : std::min(aEndOffset, fragLength);
NS_ASSERTION(aStartOffset <= endoffset, "A start offset is beyond the end of the text fragment!");
int32_t length = endoffset - aStartOffset;

View File

@ -138,7 +138,8 @@ nsXMLContentSerializer::AppendTextData(nsIContent* aNode,
return NS_ERROR_FAILURE;
}
int32_t endoffset = (aEndOffset == -1) ? frag->GetLength() : aEndOffset;
int32_t fragLength = frag->GetLength();
int32_t endoffset = (aEndOffset == -1) ? fragLength : std::min(aEndOffset, fragLength);
int32_t length = endoffset - aStartOffset;
NS_ASSERTION(aStartOffset >= 0, "Negative start offset for text fragment!");
@ -300,12 +301,16 @@ nsXMLContentSerializer::AppendComment(nsIContent* aComment,
rv = comment->GetData(data);
if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
if (aStartOffset || (aEndOffset != -1)) {
int32_t length = (aEndOffset == -1) ? data.Length() : aEndOffset;
int32_t dataLength = data.Length();
if (aStartOffset || (aEndOffset != -1 && aEndOffset < dataLength)) {
int32_t length =
(aEndOffset == -1) ? dataLength : std::min(aEndOffset, dataLength);
length -= aStartOffset;
nsAutoString frag;
data.Mid(frag, aStartOffset, length);
if (length > 0) {
data.Mid(frag, aStartOffset, length);
}
data.Assign(frag);
}

View File

@ -559,6 +559,7 @@ skip-if = buildapp == 'b2g' || toolkit == 'android' #bug 904183 # b2g(bug 904183
[test_domparser_null_char.html]
[test_domparsing.html]
[test_elementTraversal.html]
[test_encodeToStringWithMaxLength.html]
[test_fileapi.html]
skip-if = e10s
[test_fileapi_slice.html]

View File

@ -0,0 +1,64 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=995321
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 995321 - encodeToStringWithMaxLength</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
function getEncoder() {
const de = SpecialPowers.Ci.nsIDocumentEncoder;
const Cc = SpecialPowers.Cc;
// Create a plaintext encoder without flags.
var encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
.createInstance(de);
encoder.init(document, "text/plain", 0);
return encoder;
}
function testPlaintextSerializerWithMaxLength() {
var string = getEncoder().encodeToString();
var shorterString = getEncoder().encodeToStringWithMaxLength(1);
ok(shorterString.length < 1 + 72,
"test length is in the expected range after limiting the length to 1");
ok(string.startsWith(shorterString.trimRight()),
"the shorter string has the expected content");
shorterString = getEncoder().encodeToStringWithMaxLength(300);
ok(shorterString.length < 300 + 72,
"test length is in the expected range after limiting the length to 300");
ok(string.startsWith(shorterString.trimRight()),
"the shorter string has the expected content");
is(getEncoder().encodeToStringWithMaxLength(0), string,
"limiting the length to 0 should be ignored");
is(getEncoder().encodeToStringWithMaxLength(10000), string,
"limiting the length to a huge value should return the whole page");
SimpleTest.finish();
}
addLoadEvent(testPlaintextSerializerWithMaxLength);
SimpleTest.waitForExplicitFinish();
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=995321">Mozilla Bug 995321</a>
<p id="display"></p>
<div id="content" style="display: none">
The <em>Mozilla</em> project is a global community of <strong>people</strong> who believe that openness, innovation, and opportunity are key to the continued health of the Internet. We have worked together since 1998 to ensure that the Internet is developed in a way that benefits everyone. We are best known for creating the Mozilla Firefox web browser.
The Mozilla project uses a community-based approach to create world-class open source software and to develop new types of collaborative activities. We create communities of people involved in making the Internet experience better for all of us.
As a result of these efforts, we have distilled a set of principles that we believe are critical for the Internet to continue to benefit the public good as well as commercial aspects of life. We set out these principles below.
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -10,7 +10,7 @@ public class testPictureLinkContextMenu extends ContentContextMenuTest {
private static final String tabs [] = { "Image", "Link" };
private static final String photoMenuItems [] = { "Copy Image Location", "Share Image", "Set Image As", "Save Image" };
private static final String linkMenuItems [] = { "Open Link in New Tab", "Open Link in Private Tab", "Copy Link", "Share Link", "Bookmark Link"};
private static final String linkTitle = "^Link$";
private static final String imageTitle = "^Image$";
public void testPictureLinkContextMenu() {
blockForGeckoReady();
@ -20,21 +20,19 @@ public class testPictureLinkContextMenu extends ContentContextMenuTest {
loadAndPaint(PICTURE_PAGE_URL);
verifyPageTitle(PICTURE_PAGE_TITLE);
switchTabs(imageTitle);
verifyContextMenuItems(photoMenuItems);
verifyTabs(tabs);
switchTabs(imageTitle);
verifyCopyOption(photoMenuItems[0], "Firefox.jpg"); // Test the "Copy Image Location" option
switchTabs(imageTitle);
verifyShareOption(photoMenuItems[1], PICTURE_PAGE_TITLE); // Test the "Share Image" option
switchTabs(linkTitle);
verifyContextMenuItems(linkMenuItems);
openTabFromContextMenu(linkMenuItems[0],2); // Test the "Open in New Tab" option - expecting 2 tabs: the original and the new one
switchTabs(linkTitle);
openTabFromContextMenu(linkMenuItems[1],2); // Test the "Open in Private Tab" option - expecting only 2 tabs in normal mode
switchTabs(linkTitle);
verifyCopyOption(linkMenuItems[2], BLANK_PAGE_URL); // Test the "Copy Link" option
switchTabs(linkTitle);
verifyShareOption(linkMenuItems[3], PICTURE_PAGE_TITLE); // Test the "Share Link" option
switchTabs(linkTitle);
verifyBookmarkLinkOption(linkMenuItems[4],BLANK_PAGE_URL); // Test the "Bookmark Link" option
}

View File

@ -2102,6 +2102,11 @@ var NativeWindow = {
else this._targetRef = null;
},
get defaultContext() {
delete this.defaultContext;
return this.defaultContext = Strings.browser.GetStringFromName("browser.menu.context.default");
},
/* Gets menuitems for an arbitrary node
* Parameters:
* element - The element to look at. If this element has a contextmenu attribute, the
@ -2183,7 +2188,7 @@ var NativeWindow = {
} catch(ex) { }
// Fallback to the default
return Strings.browser.GetStringFromName("browser.menu.context.default");
return this.defaultContext;
},
// Adds context menu items added through the add-on api
@ -2338,7 +2343,8 @@ var NativeWindow = {
*/
_reformatList: function(target) {
let contexts = Object.keys(this.menus);
if (contexts.length == 1) {
if (contexts.length === 1) {
// If there's only one context, we'll only show a single flat single select list
return this._reformatMenuItems(target, this.menus[contexts[0]]);
}
@ -2357,12 +2363,24 @@ var NativeWindow = {
*/
_reformatListAsTabs: function(target, menus) {
let itemArray = [];
for (let context in menus) {
// Sort the keys so that "link" is always first
let contexts = Object.keys(this.menus);
contexts.sort((context1, context2) => {
if (context1 === this.defaultContext) {
return -1;
} else if (context2 === this.defaultContext) {
return 1;
}
return 0;
});
contexts.forEach(context => {
itemArray.push({
label: context,
items: this._reformatMenuItems(target, menus[context])
});
}
});
return itemArray;
},
@ -8386,8 +8404,10 @@ HTMLContextMenuItem.prototype = Object.create(ContextMenuItem.prototype, {
}
var items = NativeWindow.contextmenus._getHTMLContextMenuItemsForMenu(elt, target);
// This menu will always only have one context, but we still make sure its the "right" one.
var context = NativeWindow.contextmenus._getContextType(target);
if (items.length > 0) {
NativeWindow.contextmenus._addMenuItems(items, "link");
NativeWindow.contextmenus._addMenuItems(items, context);
}
} catch(ex) {

View File

@ -242,10 +242,16 @@ int APIENTRY WinMain(
/* BEGIN Mozilla customizations */
bool showProgress = true;
bool extractOnly = false;
if (switches.Left(3).CompareNoCase(UString(L"-ms")) == 0 ||
switches.Left(4).CompareNoCase(UString(L"/INI")) == 0 ||
switches.Left(2).CompareNoCase(UString(L"/S")) == 0)
switches.Left(4).CompareNoCase(UString(L"/ini")) == 0 ||
switches.Left(2).CompareNoCase(UString(L"/s")) == 0) {
showProgress = false;
} else if (switches.Left(12).CompareNoCase(UString(L"/extractdir=")) == 0) {
assumeYes = true;
showProgress = false;
extractOnly = true;
}
/* END Mozilla customizations */
AString config;
@ -290,16 +296,20 @@ int APIENTRY WinMain(
}
NFile::NDirectory::CTempDirectory tempDir;
if (!tempDir.Create(kTempDirPrefix))
/* Mozilla customizations - Added !extractOnly */
if (!extractOnly && !tempDir.Create(kTempDirPrefix))
{
if (!assumeYes)
MyMessageBox(L"Can not create temp folder archive");
return 1;
}
/* BEGIN Mozilla customizations */
UString tempDirPath = (extractOnly ? switches.Mid(12) : GetUnicodeString(tempDir.GetPath()));
/* END Mozilla customizations */
COpenCallbackGUI openCallback;
UString tempDirPath = GetUnicodeString(tempDir.GetPath());
bool isCorrupt = false;
UString errorMessage;
HRESULT result = ExtractArchive(fullPath, tempDirPath, &openCallback, showProgress,
@ -320,6 +330,13 @@ int APIENTRY WinMain(
return 1;
}
/* BEGIN Mozilla customizations */
// The code immediately above handles the error case for extraction.
if (extractOnly) {
return 0;
}
/* END Mozilla customizations */
CCurrentDirRestorer currentDirRestorer;
if (!SetCurrentDirectory(tempDir.GetPath()))

View File

@ -164,9 +164,9 @@ bool CProgressDialog::OnButtonClicked(int buttonID, HWND buttonHWND)
ProgressSynch.SetPaused(true);
int res = ::MessageBoxW(HWND(*this),
L"Are you sure you want to cancel?",
_title, MB_YESNOCANCEL);
_title, MB_YESNO);
ProgressSynch.SetPaused(paused);
if (res == IDCANCEL || res == IDNO)
if (res == IDNO)
return true;
break;
}

View File

@ -502,6 +502,13 @@ ErrorHandler.prototype = {
*/
dontIgnoreErrors: false,
/**
* Flag that indicates if we have already reported a prolonged failure.
* Once set, we don't report it again, meaning this error is only reported
* one per run.
*/
didReportProlongedError: false,
init: function init() {
Svc.Obs.add("weave:engine:sync:applied", this);
Svc.Obs.add("weave:engine:sync:error", this);
@ -772,7 +779,13 @@ ErrorHandler.prototype = {
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).");
if (this.didReportProlongedError) {
this._log.trace("shouldReportError: false (prolonged sync failure, but" +
" we've already reported it).");
return false;
}
this._log.trace("shouldReportError: true (first prolonged sync failure).");
this.didReportProlongedError = true;
return true;
}

View File

@ -150,6 +150,7 @@ function clean() {
Service.startOver();
Status.resetSync();
Status.resetBackoff();
errorHandler.didReportProlongedError = false;
}
add_identity_test(this, function test_401_logout() {
@ -297,18 +298,32 @@ add_identity_test(this, function test_shouldReportError() {
do_check_true(errorHandler.shouldReportError());
// Test non-network, prolonged, login error reported
do_check_false(errorHandler.didReportProlongedError);
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
Status.login = LOGIN_FAILED_NO_PASSWORD;
do_check_true(errorHandler.shouldReportError());
do_check_true(errorHandler.didReportProlongedError);
// Second time with prolonged error and without resetting
// didReportProlongedError, sync error should not be reported.
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
Status.login = LOGIN_FAILED_NO_PASSWORD;
do_check_false(errorHandler.shouldReportError());
do_check_true(errorHandler.didReportProlongedError);
// Test non-network, prolonged, sync error reported
Status.resetSync();
setLastSync(PROLONGED_ERROR_DURATION);
errorHandler.dontIgnoreErrors = false;
errorHandler.didReportProlongedError = false;
Status.sync = CREDENTIALS_CHANGED;
do_check_true(errorHandler.shouldReportError());
do_check_true(errorHandler.didReportProlongedError);
errorHandler.didReportProlongedError = false;
// Test network, prolonged, login error reported
Status.resetSync();
@ -316,6 +331,8 @@ add_identity_test(this, function test_shouldReportError() {
errorHandler.dontIgnoreErrors = false;
Status.login = LOGIN_FAILED_NETWORK_ERROR;
do_check_true(errorHandler.shouldReportError());
do_check_true(errorHandler.didReportProlongedError);
errorHandler.didReportProlongedError = false;
// Test network, prolonged, sync error reported
Status.resetSync();
@ -323,6 +340,8 @@ add_identity_test(this, function test_shouldReportError() {
errorHandler.dontIgnoreErrors = false;
Status.sync = LOGIN_FAILED_NETWORK_ERROR;
do_check_true(errorHandler.shouldReportError());
do_check_true(errorHandler.didReportProlongedError);
errorHandler.didReportProlongedError = false;
// Test non-network, non-prolonged, login error reported
Status.resetSync();
@ -330,6 +349,7 @@ add_identity_test(this, function test_shouldReportError() {
errorHandler.dontIgnoreErrors = false;
Status.login = LOGIN_FAILED_NO_PASSWORD;
do_check_true(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test non-network, non-prolonged, sync error reported
Status.resetSync();
@ -337,6 +357,7 @@ add_identity_test(this, function test_shouldReportError() {
errorHandler.dontIgnoreErrors = false;
Status.sync = CREDENTIALS_CHANGED;
do_check_true(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test network, non-prolonged, login error reported
Status.resetSync();
@ -344,6 +365,7 @@ add_identity_test(this, function test_shouldReportError() {
errorHandler.dontIgnoreErrors = false;
Status.login = LOGIN_FAILED_NETWORK_ERROR;
do_check_false(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test network, non-prolonged, sync error reported
Status.resetSync();
@ -351,6 +373,7 @@ add_identity_test(this, function test_shouldReportError() {
errorHandler.dontIgnoreErrors = false;
Status.sync = LOGIN_FAILED_NETWORK_ERROR;
do_check_false(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test server maintenance, sync errors are not reported
Status.resetSync();
@ -358,6 +381,7 @@ add_identity_test(this, function test_shouldReportError() {
errorHandler.dontIgnoreErrors = false;
Status.sync = SERVER_MAINTENANCE;
do_check_false(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test server maintenance, login errors are not reported
Status.resetSync();
@ -365,6 +389,7 @@ add_identity_test(this, function test_shouldReportError() {
errorHandler.dontIgnoreErrors = false;
Status.login = SERVER_MAINTENANCE;
do_check_false(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test prolonged, server maintenance, sync errors are reported
Status.resetSync();
@ -372,6 +397,8 @@ add_identity_test(this, function test_shouldReportError() {
errorHandler.dontIgnoreErrors = false;
Status.sync = SERVER_MAINTENANCE;
do_check_true(errorHandler.shouldReportError());
do_check_true(errorHandler.didReportProlongedError);
errorHandler.didReportProlongedError = false;
// Test prolonged, server maintenance, login errors are reported
Status.resetSync();
@ -379,6 +406,8 @@ add_identity_test(this, function test_shouldReportError() {
errorHandler.dontIgnoreErrors = false;
Status.login = SERVER_MAINTENANCE;
do_check_true(errorHandler.shouldReportError());
do_check_true(errorHandler.didReportProlongedError);
errorHandler.didReportProlongedError = false;
// Test dontIgnoreErrors, server maintenance, sync errors are reported
Status.resetSync();
@ -386,6 +415,8 @@ add_identity_test(this, function test_shouldReportError() {
errorHandler.dontIgnoreErrors = true;
Status.sync = SERVER_MAINTENANCE;
do_check_true(errorHandler.shouldReportError());
// dontIgnoreErrors means we don't set didReportProlongedError
do_check_false(errorHandler.didReportProlongedError);
// Test dontIgnoreErrors, server maintenance, login errors are reported
Status.resetSync();
@ -393,6 +424,7 @@ add_identity_test(this, function test_shouldReportError() {
errorHandler.dontIgnoreErrors = true;
Status.login = SERVER_MAINTENANCE;
do_check_true(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test dontIgnoreErrors, prolonged, server maintenance,
// sync errors are reported
@ -401,6 +433,7 @@ add_identity_test(this, function test_shouldReportError() {
errorHandler.dontIgnoreErrors = true;
Status.sync = SERVER_MAINTENANCE;
do_check_true(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
// Test dontIgnoreErrors, prolonged, server maintenance,
// login errors are reported
@ -409,7 +442,7 @@ add_identity_test(this, function test_shouldReportError() {
errorHandler.dontIgnoreErrors = true;
Status.login = SERVER_MAINTENANCE;
do_check_true(errorHandler.shouldReportError());
do_check_false(errorHandler.didReportProlongedError);
});
add_identity_test(this, function test_shouldReportError_master_password() {
@ -625,6 +658,7 @@ add_task(function test_login_prolonged_non_network_error() {
Svc.Obs.add("weave:ui:login:error", function onSyncError() {
Svc.Obs.remove("weave:ui:login:error", onSyncError);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_true(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -651,6 +685,7 @@ add_task(function test_sync_prolonged_non_network_error() {
Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
Svc.Obs.remove("weave:ui:sync:error", onSyncError);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_true(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -671,6 +706,7 @@ add_identity_test(this, function test_login_prolonged_network_error() {
Svc.Obs.add("weave:ui:login:error", function onSyncError() {
Svc.Obs.remove("weave:ui:login:error", onSyncError);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_true(errorHandler.didReportProlongedError);
clean();
deferred.resolve();
@ -688,6 +724,7 @@ add_test(function test_sync_prolonged_network_error() {
Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
Svc.Obs.remove("weave:ui:sync:error", onSyncError);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_true(errorHandler.didReportProlongedError);
Services.io.offline = false;
clean();
@ -708,6 +745,7 @@ add_task(function test_login_non_network_error() {
Svc.Obs.add("weave:ui:login:error", function onSyncError() {
Svc.Obs.remove("weave:ui:login:error", onSyncError);
do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD);
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -734,6 +772,7 @@ add_task(function test_sync_non_network_error() {
Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
Svc.Obs.remove("weave:ui:sync:error", onSyncError);
do_check_eq(Status.sync, CREDENTIALS_CHANGED);
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -755,6 +794,7 @@ add_identity_test(this, function test_login_network_error() {
Svc.Obs.remove("weave:ui:clear-error", onClearError);
do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
do_check_false(errorHandler.didReportProlongedError);
Services.io.offline = false;
clean();
@ -773,6 +813,7 @@ add_test(function test_sync_network_error() {
Svc.Obs.add("weave:ui:sync:finish", function onUIUpdate() {
Svc.Obs.remove("weave:ui:sync:finish", onUIUpdate);
do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
do_check_false(errorHandler.didReportProlongedError);
Services.io.offline = false;
clean();
@ -807,6 +848,7 @@ add_identity_test(this, function test_sync_server_maintenance_error() {
do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
do_check_eq(Status.sync, SERVER_MAINTENANCE);
do_check_false(errorHandler.didReportProlongedError);
Svc.Obs.remove("weave:ui:sync:error", onSyncError);
clean();
@ -850,6 +892,7 @@ add_identity_test(this, function test_info_collections_login_server_maintenance_
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
do_check_false(errorHandler.didReportProlongedError);
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
clean();
@ -892,6 +935,7 @@ add_identity_test(this, function test_meta_global_login_server_maintenance_error
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
do_check_false(errorHandler.didReportProlongedError);
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
clean();
@ -937,6 +981,7 @@ add_identity_test(this, function test_crypto_keys_login_server_maintenance_error
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
do_check_false(errorHandler.didReportProlongedError);
Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
clean();
@ -964,6 +1009,7 @@ add_task(function test_sync_prolonged_server_maintenance_error() {
Svc.Obs.remove("weave:ui:sync:error", onUIUpdate);
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_true(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -998,6 +1044,7 @@ add_identity_test(this, function test_info_collections_login_prolonged_server_ma
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_true(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1033,6 +1080,7 @@ add_identity_test(this, function test_meta_global_login_prolonged_server_mainten
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_true(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1070,6 +1118,7 @@ add_identity_test(this, function test_download_crypto_keys_login_prolonged_serve
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_true(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1105,6 +1154,7 @@ add_identity_test(this, function test_upload_crypto_keys_login_prolonged_server_
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_true(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1141,6 +1191,7 @@ add_identity_test(this, function test_wipeServer_login_prolonged_server_maintena
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_true(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1183,6 +1234,7 @@ add_identity_test(this, function test_wipeRemote_prolonged_server_maintenance_er
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
do_check_eq(Svc.Prefs.get("firstSync"), "wipeRemote");
do_check_true(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1214,6 +1266,7 @@ add_task(function test_sync_syncAndReportErrors_server_maintenance_error() {
Svc.Obs.remove("weave:ui:sync:error", onUIUpdate);
do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
do_check_eq(Status.sync, SERVER_MAINTENANCE);
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1249,6 +1302,7 @@ add_identity_test(this, function test_info_collections_login_syncAndReportErrors
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1285,6 +1339,7 @@ add_identity_test(this, function test_meta_global_login_syncAndReportErrors_serv
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1323,6 +1378,7 @@ add_identity_test(this, function test_download_crypto_keys_login_syncAndReportEr
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1359,6 +1415,7 @@ add_identity_test(this, function test_upload_crypto_keys_login_syncAndReportErro
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1395,6 +1452,7 @@ add_identity_test(this, function test_wipeServer_login_syncAndReportErrors_serve
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1436,6 +1494,7 @@ add_identity_test(this, function test_wipeRemote_syncAndReportErrors_server_main
do_check_eq(Status.service, SYNC_FAILED);
do_check_eq(Status.sync, SERVER_MAINTENANCE);
do_check_eq(Svc.Prefs.get("firstSync"), "wipeRemote");
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1467,6 +1526,9 @@ add_task(function test_sync_syncAndReportErrors_prolonged_server_maintenance_err
Svc.Obs.remove("weave:ui:sync:error", onUIUpdate);
do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
do_check_eq(Status.sync, SERVER_MAINTENANCE);
// syncAndReportErrors means dontIgnoreErrors, which means
// didReportProlongedError not touched.
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1502,6 +1564,9 @@ add_identity_test(this, function test_info_collections_login_syncAndReportErrors
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
// syncAndReportErrors means dontIgnoreErrors, which means
// didReportProlongedError not touched.
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1538,6 +1603,9 @@ add_identity_test(this, function test_meta_global_login_syncAndReportErrors_prol
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
// syncAndReportErrors means dontIgnoreErrors, which means
// didReportProlongedError not touched.
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1576,6 +1644,9 @@ add_identity_test(this, function test_download_crypto_keys_login_syncAndReportEr
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
// syncAndReportErrors means dontIgnoreErrors, which means
// didReportProlongedError not touched.
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1612,6 +1683,9 @@ add_identity_test(this, function test_upload_crypto_keys_login_syncAndReportErro
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
// syncAndReportErrors means dontIgnoreErrors, which means
// didReportProlongedError not touched.
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);
@ -1648,6 +1722,9 @@ add_identity_test(this, function test_wipeServer_login_syncAndReportErrors_prolo
do_check_eq(backoffInterval, 42);
do_check_eq(Status.service, LOGIN_FAILED);
do_check_eq(Status.login, SERVER_MAINTENANCE);
// syncAndReportErrors means dontIgnoreErrors, which means
// didReportProlongedError not touched.
do_check_false(errorHandler.didReportProlongedError);
clean();
server.stop(deferred.resolve);

View File

@ -7,169 +7,150 @@ let gCategoryUtilities;
let gInstalledAddons = [];
let gContext = this;
function test() {
waitForExplicitFinish();
add_task(function* initializeState() {
gManagerWindow = yield open_manager();
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
open_manager(null, (win) => {
gManagerWindow = win;
gCategoryUtilities = new CategoryUtilities(win);
// The Experiments Manager will interfere with us by preventing installs
// of experiments it doesn't know about. We remove it from the equation
// because here we are only concerned with core Addon Manager operation,
// not the superset Experiments Manager has imposed.
if ("@mozilla.org/browser/experiments-service;1" in Components.classes) {
Components.utils.import("resource:///modules/experiments/Experiments.jsm", gContext);
// The Experiments Manager will interfere with us by preventing installs
// of experiments it doesn't know about. We remove it from the equation
// because here we are only concerned with core Addon Manager operation,
// not the superset Experiments Manager has imposed.
if ("@mozilla.org/browser/experiments-service;1" in Components.classes) {
Components.utils.import("resource:///modules/experiments/Experiments.jsm", gContext);
// There is a race condition between XPCOM service initialization and
// this test running. We have to initialize the instance first, then
// uninitialize it to prevent this.
let instance = gContext.Experiments.instance();
instance.uninit().then(run_next_test);
} else {
run_next_test();
}
});
}
function end_test() {
for (let addon of gInstalledAddons) {
addon.uninstall();
// There is a race condition between XPCOM service initialization and
// this test running. We have to initialize the instance first, then
// uninitialize it to prevent this.
let instance = gContext.Experiments.instance();
yield instance.uninit();
}
close_manager(gManagerWindow, () => {
if ("@mozilla.org/browser/experiments-service;1" in Components.classes) {
gContext.Experiments.instance().init();
finish();
} else {
finish();
}
});
}
});
// On an empty profile with no experiments, the experiment category
// should be hidden.
add_test(function testInitialState() {
add_task(function* testInitialState() {
Assert.ok(gCategoryUtilities.get("experiment", false), "Experiment tab is defined.");
Assert.ok(!gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab hidden by default.");
run_next_test();
});
add_test(function testExperimentInfoNotVisible() {
gCategoryUtilities.openType("extension", () => {
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
is_element_hidden(el, "Experiment info not visible on other types.");
run_next_test();
});
add_task(function* testExperimentInfoNotVisible() {
yield gCategoryUtilities.openType("extension");
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
is_element_hidden(el, "Experiment info not visible on other types.");
});
// If we have an active experiment, we should see the experiments tab
// and that tab should have some messages.
add_test(function testActiveExperiment() {
install_addon("addons/browser_experiment1.xpi", (addon) => {
gInstalledAddons.push(addon);
add_task(function* testActiveExperiment() {
let addon = yield install_addon("addons/browser_experiment1.xpi");
gInstalledAddons.push(addon);
Assert.ok(addon.userDisabled, "Add-on is disabled upon initial install.");
Assert.equal(addon.isActive, false, "Add-on is not active.");
Assert.ok(addon.userDisabled, "Add-on is disabled upon initial install.");
Assert.equal(addon.isActive, false, "Add-on is not active.");
Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible.");
Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible.");
gCategoryUtilities.openType("experiment", (win) => {
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
is_element_visible(el, "Experiment info is visible on experiment tab.");
run_next_test();
});
});
yield gCategoryUtilities.openType("experiment");
let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
is_element_visible(el, "Experiment info is visible on experiment tab.");
});
add_test(function testExperimentLearnMore() {
add_task(function* testExperimentLearnMore() {
// Actual URL is irrelevant.
Services.prefs.setCharPref("toolkit.telemetry.infoURL",
"http://mochi.test:8888/server.js");
gCategoryUtilities.openType("experiment", (win) => {
let btn = gManagerWindow.document.getElementById("experiments-learn-more");
yield gCategoryUtilities.openType("experiment");
let btn = gManagerWindow.document.getElementById("experiments-learn-more");
if (!gUseInContentUI) {
is_element_hidden(btn, "Learn more button hidden if not using in-content UI.");
Services.prefs.clearUserPref("toolkit.telemetry.infoURL");
if (!gUseInContentUI) {
is_element_hidden(btn, "Learn more button hidden if not using in-content UI.");
Services.prefs.clearUserPref("toolkit.telemetry.infoURL");
run_next_test();
return;
} else {
is_element_visible(btn, "Learn more button visible.");
}
return;
}
window.addEventListener("DOMContentLoaded", function onLoad(event) {
info("Telemetry privacy policy window opened.");
window.removeEventListener("DOMContentLoaded", onLoad, false);
is_element_visible(btn, "Learn more button visible.");
let browser = gBrowser.selectedTab.linkedBrowser;
let expected = Services.prefs.getCharPref("toolkit.telemetry.infoURL");
Assert.equal(browser.currentURI.spec, expected, "New tab should have loaded privacy policy.");
browser.contentWindow.close();
let deferred = Promise.defer();
window.addEventListener("DOMContentLoaded", function onLoad(event) {
info("Telemetry privacy policy window opened.");
window.removeEventListener("DOMContentLoaded", onLoad, false);
Services.prefs.clearUserPref("toolkit.telemetry.infoURL");
let browser = gBrowser.selectedTab.linkedBrowser;
let expected = Services.prefs.getCharPref("toolkit.telemetry.infoURL");
Assert.equal(browser.currentURI.spec, expected, "New tab should have loaded privacy policy.");
browser.contentWindow.close();
run_next_test();
}, false);
Services.prefs.clearUserPref("toolkit.telemetry.infoURL");
info("Opening telemetry privacy policy.");
EventUtils.synthesizeMouseAtCenter(btn, {}, gManagerWindow);
});
deferred.resolve();
}, false);
info("Opening telemetry privacy policy.");
EventUtils.synthesizeMouseAtCenter(btn, {}, gManagerWindow);
yield deferred.promise;
});
add_test(function testOpenPreferences() {
gCategoryUtilities.openType("experiment", (win) => {
let btn = gManagerWindow.document.getElementById("experiments-change-telemetry");
if (!gUseInContentUI) {
is_element_hidden(btn, "Change telemetry button not enabled in out of window UI.");
info("Skipping preferences open test because not using in-content UI.");
run_next_test();
return;
}
add_task(function* testOpenPreferences() {
yield gCategoryUtilities.openType("experiment");
let btn = gManagerWindow.document.getElementById("experiments-change-telemetry");
if (!gUseInContentUI) {
is_element_hidden(btn, "Change telemetry button not enabled in out of window UI.");
info("Skipping preferences open test because not using in-content UI.");
return;
}
is_element_visible(btn, "Change telemetry button visible in in-content UI.");
is_element_visible(btn, "Change telemetry button visible in in-content UI.");
Services.obs.addObserver(function observer(prefWin, topic, data) {
Services.obs.removeObserver(observer, "advanced-pane-loaded");
let deferred = Promise.defer();
Services.obs.addObserver(function observer(prefWin, topic, data) {
Services.obs.removeObserver(observer, "advanced-pane-loaded");
info("Advanced preference pane opened.");
info("Advanced preference pane opened.");
// We want this test to fail if the preferences pane changes.
let el = prefWin.document.getElementById("dataChoicesPanel");
is_element_visible(el);
// We want this test to fail if the preferences pane changes.
let el = prefWin.document.getElementById("dataChoicesPanel");
is_element_visible(el);
prefWin.close();
info("Closed preferences pane.");
prefWin.close();
info("Closed preferences pane.");
run_next_test();
}, "advanced-pane-loaded", false);
deferred.resolve();
}, "advanced-pane-loaded", false);
info("Loading preferences pane.");
EventUtils.synthesizeMouseAtCenter(btn, {}, gManagerWindow);
});
info("Loading preferences pane.");
EventUtils.synthesizeMouseAtCenter(btn, {}, gManagerWindow);
yield deferred.promise;
});
add_test(function testButtonPresence() {
gCategoryUtilities.openType("experiment", (win) => {
let item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
Assert.ok(item, "Got add-on element.");
add_task(function* testButtonPresence() {
yield gCategoryUtilities.openType("experiment");
let item = get_addon_element(gManagerWindow, "test-experiment1@experiments.mozilla.org");
Assert.ok(item, "Got add-on element.");
let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
// Corresponds to the uninstall permission.
is_element_visible(el, "Remove button is visible.");
// Corresponds to lack of disable permission.
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
is_element_hidden(el, "Disable button not visible.");
// Corresponds to lack of enable permission.
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
is_element_hidden(el, "Enable button not visible.");
run_next_test();
});
let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
// Corresponds to the uninstall permission.
is_element_visible(el, "Remove button is visible.");
// Corresponds to lack of disable permission.
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
is_element_hidden(el, "Disable button not visible.");
// Corresponds to lack of enable permission.
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
is_element_hidden(el, "Enable button not visible.");
});
add_task(function* testCleanup() {
for (let addon of gInstalledAddons) {
addon.uninstall();
}
yield close_manager(gManagerWindow);
if ("@mozilla.org/browser/experiments-service;1" in Components.classes) {
yield gContext.Experiments.instance().init();
}
});

View File

@ -283,6 +283,8 @@ function wait_for_manager_load(aManagerWindow, aCallback) {
}
function open_manager(aView, aCallback, aLoadCallback, aLongerTimeout) {
let deferred = Promise.defer();
function setup_manager(aManagerWindow) {
if (aLoadCallback)
log_exceptions(aLoadCallback, aManagerWindow);
@ -299,7 +301,10 @@ function open_manager(aView, aCallback, aLoadCallback, aLongerTimeout) {
// Some functions like synthesizeMouse don't like to be called during
// the load event so ensure that has completed
executeSoon(function() {
log_exceptions(aCallback, aManagerWindow);
if (aCallback) {
log_exceptions(aCallback, aManagerWindow);
}
deferred.resolve(aManagerWindow);
});
}, null, aLongerTimeout);
});
@ -309,7 +314,7 @@ function open_manager(aView, aCallback, aLoadCallback, aLongerTimeout) {
if (gUseInContentUI) {
gBrowser.selectedTab = gBrowser.addTab();
switchToTabHavingURI(MANAGER_URI, true);
// This must be a new load, else the ping/pong would have
// found the window above.
Services.obs.addObserver(function (aSubject, aTopic, aData) {
@ -318,7 +323,7 @@ function open_manager(aView, aCallback, aLoadCallback, aLongerTimeout) {
return;
setup_manager(aSubject);
}, "EM-loaded", false);
return;
return deferred.promise;
}
openDialog(MANAGER_URI);
@ -326,9 +331,12 @@ function open_manager(aView, aCallback, aLoadCallback, aLongerTimeout) {
Services.obs.removeObserver(arguments.callee, aTopic);
setup_manager(aSubject);
}, "EM-loaded", false);
return deferred.promise;
}
function close_manager(aManagerWindow, aCallback, aLongerTimeout) {
let deferred = Promise.defer();
requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2);
ok(aManagerWindow != null, "Should have an add-ons manager window to close");
@ -336,10 +344,15 @@ function close_manager(aManagerWindow, aCallback, aLongerTimeout) {
aManagerWindow.addEventListener("unload", function() {
this.removeEventListener("unload", arguments.callee, false);
log_exceptions(aCallback);
if (aCallback) {
log_exceptions(aCallback);
}
deferred.resolve();
}, false);
aManagerWindow.close();
return deferred.promise;
}
function restart_manager(aManagerWindow, aView, aCallback, aLoadCallback) {
@ -424,17 +437,25 @@ function is_element_hidden(aElement, aMsg) {
* The callback will receive the Addon for the installed add-on.
*/
function install_addon(path, cb, pathPrefix=TESTROOT) {
let deferred = Promise.defer();
AddonManager.getInstallForURL(pathPrefix + path, (install) => {
install.addListener({
onInstallEnded: () => {
executeSoon(() => {
cb(install.addon);
if (cb) {
cb(install.addon);
}
deferred.resolve(install.addon);
});
},
});
install.install();
}, "application/x-xpinstall");
return deferred.promise;
}
function CategoryUtilities(aManagerWindow) {
@ -496,17 +517,26 @@ CategoryUtilities.prototype = {
},
open: function(aCategory, aCallback) {
let deferred = Promise.defer();
isnot(this.window, null, "Should not open category when manager window is not loaded");
ok(this.isVisible(aCategory), "Category should be visible if attempting to open it");
EventUtils.synthesizeMouse(aCategory, 2, 2, { }, this.window);
if (aCallback)
wait_for_view_load(this.window, aCallback);
wait_for_view_load(this.window, (win) => {
if (aCallback) {
log_exceptions(aCallback, win);
}
deferred.resolve(win);
});
return deferred.promise;
},
openType: function(aCategoryType, aCallback) {
this.open(this.get(aCategoryType), aCallback);
return this.open(this.get(aCategoryType), aCallback);
}
}