merge fx-team to mozilla-central

This commit is contained in:
Carsten "Tomcat" Book 2014-01-24 13:09:46 +01:00
commit 9af03dcbd3
65 changed files with 285 additions and 98 deletions

View File

@ -34,8 +34,12 @@ var FeedHandler = {
return false;
}
while (container.firstChild)
container.removeChild(container.firstChild);
for (let i = container.childNodes.length - 1; i >= 0; --i) {
let node = container.childNodes[i];
if (isSubview && node.localName == "label")
continue;
container.removeChild(node);
}
if (!feeds || feeds.length <= 1)
return false;

View File

@ -283,7 +283,13 @@ let gSyncUI = {
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
if (xps.fxAccountsEnabled) {
switchToTabHavingURI("about:accounts", true);
fxAccounts.getSignedInUser().then(userData => {
if (userData) {
this.openPrefs();
} else {
switchToTabHavingURI("about:accounts", true);
}
});
} else {
let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
if (win)

View File

@ -123,7 +123,9 @@
<panelview id="PanelUI-socialapi" flex="1"/>
<panelview id="PanelUI-feeds" flex="1" oncommand="FeedHandler.subscribeToFeed(null, event);"></panelview>
<panelview id="PanelUI-feeds" flex="1" oncommand="FeedHandler.subscribeToFeed(null, event);">
<label value="&feedsMenu.label;" class="panel-subview-header"/>
</panelview>
<panelview id="PanelUI-helpView" flex="1">
<label value="&helpMenu.label;" class="panel-subview-header"/>

View File

@ -84,11 +84,11 @@
.getService(Components.interfaces.nsIObserverService);
os.addObserver(this, "browser-search-engine-modified", false);
this._addedObserver = true;
this._initialized = true;
this.searchService.init((function search_init_cb(aStatus) {
// Bail out if the binding's been destroyed
if (this._destroyed)
if (!this._initialized)
return;
if (Components.isSuccessCode(aStatus)) {
@ -101,13 +101,12 @@
]]></constructor>
<destructor><![CDATA[
this._destroyed = true;
if (this._initialized) {
this._initialized = false;
if (this._addedObserver) {
var os = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
os.removeObserver(this, "browser-search-engine-modified");
this._addedObserver = false;
}
// Make sure to break the cycle from _textbox to us. Otherwise we leak

View File

@ -11,9 +11,9 @@ const {Cc, Ci, Cu} = require("chrome");
let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
let Heritage = require("sdk/core/heritage");
loader.lazyGetter(this, "promise", () => require("sdk/core/promise"));
loader.lazyGetter(this, "Telemetry", () => require("devtools/shared/telemetry"));
loader.lazyGetter(this, "WebConsoleFrame", () => require("devtools/webconsole/webconsole").WebConsoleFrame);
loader.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
@ -110,7 +110,6 @@ HUD_SERVICE.prototype =
function HS_openBrowserConsole(aTarget, aIframeWindow, aChromeWindow)
{
let hud = new BrowserConsole(aTarget, aIframeWindow, aChromeWindow);
this._browserConsoleID = hud.hudId;
this.consoles.set(hud.hudId, hud);
return hud.init();
},
@ -242,6 +241,7 @@ HUD_SERVICE.prototype =
connect().then(getTarget).then(openWindow).then((aWindow) => {
this.openBrowserConsole(target, aWindow, aWindow)
.then((aBrowserConsole) => {
this._browserConsoleID = aBrowserConsole.hudId;
this._browserConsoleDefer.resolve(aBrowserConsole);
this._browserConsoleDefer = null;
})

View File

@ -6,7 +6,7 @@
const {Cc, Ci, Cu} = require("chrome");
loader.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
loader.lazyGetter(this, "promise", () => require("sdk/core/promise"));
loader.lazyGetter(this, "HUDService", () => require("devtools/webconsole/hudservice"));
loader.lazyGetter(this, "EventEmitter", () => require("devtools/shared/event-emitter"));

View File

@ -49,10 +49,7 @@ function test()
EventUtils.synthesizeKey(c, {}, hud.iframeWindow);
}
hud.jsterm.execute(null, () => {
// executeSoon() is needed to get out of the execute() event loop.
executeSoon(onReadProperty.bind(null, msg));
});
hud.jsterm.execute(null, onReadProperty.bind(null, msg));
}
function onReadProperty(deadObjectMessage)
@ -72,7 +69,6 @@ function test()
function onFetched()
{
ok(true, "variables view fetched");
hud.jsterm.execute("delete window.foobarzTezt; 2013-26", onCalcResult);
}

View File

@ -95,24 +95,37 @@ function testGen() {
testNext();
};
EventUtils.synthesizeKey("VK_END", {});
yield undefined;
yield;
let oldScrollTop = scrollBox.scrollTop;
content.console.log("test message 151");
scrollBox.onscroll = () => {
if (scrollBox.scrollTop == oldScrollTop) {
// Wait for scroll to change.
return;
}
scrollBox.onscroll = null;
isnot(scrollBox.scrollTop, oldScrollTop, "scroll location updated (moved to bottom again)");
hud = testDriver = null;
finishTest();
};
waitForMessages({
webconsole: hud,
messages: [{
text: "test message 151",
category: CATEGORY_WEBDEV,
severity: SEVERITY_LOG,
}],
}).then(() => {
scrollBox.onscroll = () => {
if (scrollBox.scrollTop == oldScrollTop) {
// Wait for scroll to change.
return;
}
scrollBox.onscroll = null;
isnot(scrollBox.scrollTop, oldScrollTop, "scroll location updated (moved to bottom again)");
testNext();
};
});
yield undefined;
hud = testDriver = null;
finishTest();
yield undefined;
}
function test() {

View File

@ -6,7 +6,7 @@
let WebConsoleUtils, TargetFactory, require;
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
(() => {

View File

@ -14,7 +14,7 @@ loader.lazyServiceGetter(this, "clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1",
"nsIClipboardHelper");
loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
loader.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
loader.lazyGetter(this, "promise", () => require("sdk/core/promise"));
loader.lazyGetter(this, "EventEmitter", () => require("devtools/shared/event-emitter"));
loader.lazyGetter(this, "AutocompletePopup",
() => require("devtools/shared/autocomplete-popup").AutocompletePopup);

View File

@ -129,6 +129,7 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!ENTITY shareSelectCmd.accesskey "s">
<!ENTITY shareVideoCmd.label "Share This Video">
<!ENTITY shareVideoCmd.accesskey "s">
<!ENTITY feedsMenu.label "Subscribe">
<!ENTITY subscribeToPageMenupopup.label "Subscribe to This Page">
<!ENTITY subscribeToPageMenuitem.label "Subscribe to This Page…">
<!ENTITY addCurPagesCmd.label "Bookmark All Tabs…">

View File

@ -161,11 +161,6 @@ var BrowserUI = {
Util.dumpLn("Exception in delay load module:", ex.message);
}
if (WindowsPrefSync) {
// Pulls in Desktop controlled prefs and pushes out Metro controlled prefs
WindowsPrefSync.init();
}
// check for left over crash reports and submit them if found.
BrowserUI.startupCrashCheck();

View File

@ -185,8 +185,7 @@ var Browser = {
// Should we restore the previous session (crash or some other event)
let ss = Cc["@mozilla.org/browser/sessionstore;1"]
.getService(Ci.nsISessionStore);
let shouldRestore = ss.shouldRestore()
|| (3 == Services.prefs.getIntPref("browser.startup.page"));
let shouldRestore = ss.shouldRestore();
if (shouldRestore) {
let bringFront = false;
// First open any commandline URLs, except the homepage

View File

@ -9,6 +9,7 @@ const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/WindowsPrefSync.jsm");
#ifdef MOZ_CRASHREPORTER
XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
@ -199,6 +200,10 @@ SessionStore.prototype = {
break;
case "final-ui-startup":
observerService.removeObserver(this, "final-ui-startup");
if (WindowsPrefSync) {
// Pulls in Desktop controlled prefs and pushes out Metro controlled prefs
WindowsPrefSync.init();
}
this.init();
break;
case "domwindowopened":
@ -340,9 +345,12 @@ SessionStore.prototype = {
this._lastSaveTime = Date.now();
// Nothing to restore, notify observers things are complete
if (!this._shouldRestore) {
if (!this.shouldRestore()) {
this._clearCache();
Services.obs.notifyObservers(null, "sessionstore-windows-restored", "");
// If nothing is being restored, we only have our single Metro window.
this._orderedWindows.push(aWindow.__SSID);
}
}
@ -726,7 +734,7 @@ SessionStore.prototype = {
},
shouldRestore: function ss_shouldRestore() {
return this._shouldRestore;
return this._shouldRestore || (3 == Services.prefs.getIntPref("browser.startup.page"));
},
restoreLastSession: function ss_restoreLastSession(aBringToFront) {

View File

@ -818,7 +818,7 @@ pref("browser.snippets.geoUrl", "https://geo.mozilla.org/country.json");
pref("browser.snippets.statsUrl", "https://snippets-stats.mozilla.org/mobile");
// These prefs require a restart to take effect.
pref("browser.snippets.enabled", false);
pref("browser.snippets.enabled", true);
pref("browser.snippets.syncPromo.enabled", false);
#ifdef MOZ_ANDROID_SYNTHAPKS

View File

@ -716,6 +716,8 @@ public abstract class GeckoApp
message.optString("className"), message.optString("action"), message.optString("title"));
} else if (event.equals("Locale:Set")) {
setLocale(message.getString("locale"));
} else if (event.equals("SystemUI:Visibility")) {
setSystemUiVisible(message.getBoolean("visible"));
}
} catch (Exception e) {
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
@ -1580,6 +1582,7 @@ public abstract class GeckoApp
registerEventListener("Intent:Open");
registerEventListener("Intent:GetHandlers");
registerEventListener("Locale:Set");
registerEventListener("SystemUI:Visibility");
if (SmsManager.getInstance() != null) {
SmsManager.getInstance().start();
@ -2109,6 +2112,7 @@ public abstract class GeckoApp
unregisterEventListener("Intent:Open");
unregisterEventListener("Intent:GetHandlers");
unregisterEventListener("Locale:Set");
unregisterEventListener("SystemUI:Visibility");
deleteTempFiles();
@ -2815,4 +2819,17 @@ public abstract class GeckoApp
}
});
}
private void setSystemUiVisible(final boolean visible) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
if (visible) {
mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
} else {
mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
}
}
});
}
}

View File

@ -168,8 +168,11 @@ public final class HomeConfig {
mLayoutType = panelConfig.mLayoutType;
mViews = new ArrayList<ViewConfig>();
for (ViewConfig viewConfig : panelConfig.mViews) {
mViews.add(new ViewConfig(viewConfig));
List<ViewConfig> viewConfigs = panelConfig.mViews;
if (viewConfigs != null) {
for (ViewConfig viewConfig : viewConfigs) {
mViews.add(new ViewConfig(viewConfig));
}
}
mFlags = panelConfig.mFlags.clone();

View File

@ -80,8 +80,12 @@ public abstract class CustomListPreference extends Preference implements View.On
protected abstract int getPreferenceLayoutResource();
/**
* Set whether this object's UI should display this as the default item. To ensure proper ordering,
* this method should only be called after this Preference is added to the PreferenceCategory.
* Set whether this object's UI should display this as the default item.
* Note: This must be called from the UI thread because it touches the view hierarchy.
*
* To ensure proper ordering, this method should only be called after this Preference
* is added to the PreferenceCategory.
*
* @param isDefault Flag indicating if this represents the default list item.
*/
public void setIsDefault(boolean isDefault) {

View File

@ -16,6 +16,7 @@ import org.json.JSONObject;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
public class SearchPreferenceCategory extends CustomListCategory implements GeckoEventListener {
public static final String LOGTAG = "SearchPrefCategory";
@ -81,7 +82,7 @@ public class SearchPreferenceCategory extends CustomListCategory implements Geck
JSONObject engineJSON = engines.getJSONObject(i);
final String engineName = engineJSON.getString("name");
SearchEnginePreference enginePreference = new SearchEnginePreference(getContext(), this);
final SearchEnginePreference enginePreference = new SearchEnginePreference(getContext(), this);
enginePreference.setSearchEngineFromJSON(engineJSON);
enginePreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
@ -100,7 +101,12 @@ public class SearchPreferenceCategory extends CustomListCategory implements Geck
// We set this here, not in setSearchEngineFromJSON, because it allows us to
// keep a reference to the default engine to use when the AlertDialog
// callbacks are used.
enginePreference.setIsDefault(true);
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
enginePreference.setIsDefault(true);
}
});
mDefaultReference = enginePreference;
}
} catch (JSONException e) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 737 B

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 B

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 915 B

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 B

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

View File

@ -81,19 +81,23 @@
</style>
<style name="GeckoActionBar.Title" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
<item name="android:drawableLeft">?android:attr/actionModeCloseDrawable</item>
<item name="android:drawableLeft">@drawable/ab_done</item>
<item name="android:background">@android:color/transparent</item>
<item name="android:paddingLeft">15dp</item>
<item name="android:paddingRight">15dp</item>
</style>
<style name="GeckoActionBar.Button" parent="android:style/Widget.Holo.Light.ActionButton">
<item name="android:padding">12dp</item>
<item name="android:padding">8dip</item>
<!-- The default implementation doesn't do any image scaling. Our custom menus mean we can't just use the same image
in both menus and the actionbar without doing some scaling though. -->
<item name="android:scaleType">centerInside</item>
</style>
<style name="GeckoActionBar.Button.MenuButton" parent="android:style/Widget.Holo.Light.ActionButton.Overflow">
<item name="android:scaleType">center</item>
<item name="android:background">@android:color/transparent</item>
<item name="android:src">@drawable/menu_light</item>
</style>
</resources>

View File

@ -53,10 +53,10 @@
<item name="menuItemActionModeStyle">@style/GeckoActionBar.Button</item>
<item name="android:actionModeStyle">@style/GeckoActionBar</item>
<item name="android:actionButtonStyle">@style/GeckoActionBar.Button</item>
<item name="android:actionModeCutDrawable">@drawable/cut</item>
<item name="android:actionModeCopyDrawable">@drawable/copy</item>
<item name="android:actionModePasteDrawable">@drawable/paste</item>
<item name="android:actionModeSelectAllDrawable">@drawable/select_all</item>
<item name="android:actionModeCutDrawable">@drawable/ab_cut</item>
<item name="android:actionModeCopyDrawable">@drawable/ab_copy</item>
<item name="android:actionModePasteDrawable">@drawable/ab_paste</item>
<item name="android:actionModeSelectAllDrawable">@drawable/ab_select_all</item>
</style>
</resources>

View File

@ -30,6 +30,11 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.Xml;
/**
* Mozilla: Extra imports.
*/
import android.content.pm.ApplicationInfo;
/**
* Mozilla: Unused import.
*/
@ -257,6 +262,11 @@ public class ActivityChooserModel extends DataSetObservable {
*/
//private final PackageMonitor mPackageMonitor = new DataModelPackageMonitor();
/**
* Mozilla: Count to monitor added and removed packages.
*/
private int mApplicationsCount;
/**
* Context for accessing resources.
*/
@ -732,6 +742,15 @@ public class ActivityChooserModel extends DataSetObservable {
* @return Whether loading was performed.
*/
private boolean loadActivitiesIfNeeded() {
/**
* Mozilla: Hack to find change in the installed/uninstalled applications.
*/
List<ApplicationInfo> applications = mContext.getPackageManager().getInstalledApplications(0);
if (applications != null && applications.size() != mApplicationsCount) {
mApplicationsCount = applications.size();
mReloadActivities = true;
}
if (mReloadActivities && mIntent != null) {
mReloadActivities = false;
mActivities.clear();

View File

@ -405,18 +405,18 @@ var SelectionHandler = {
SELECT_ALL: {
label: Strings.browser.GetStringFromName("contextmenu.selectAll"),
id: "selectall_action",
icon: "drawable://select_all",
icon: "drawable://ab_select_all",
action: function(aElement) {
SelectionHandler.selectAll(aElement);
},
selector: ClipboardHelper.selectAllContext,
order: 1,
order: 5,
},
CUT: {
label: Strings.browser.GetStringFromName("contextmenu.cut"),
id: "cut_action",
icon: "drawable://cut",
icon: "drawable://ab_cut",
action: function(aElement) {
let start = aElement.selectionStart;
let end = aElement.selectionEnd;
@ -427,31 +427,31 @@ var SelectionHandler = {
// copySelection closes the selection. Show a caret where we just cut the text.
SelectionHandler.attachCaret(aElement);
},
order: 1,
order: 4,
selector: ClipboardHelper.cutContext,
},
COPY: {
label: Strings.browser.GetStringFromName("contextmenu.copy"),
id: "copy_action",
icon: "drawable://copy",
icon: "drawable://ab_copy",
action: function() {
SelectionHandler.copySelection();
},
order: 1,
order: 3,
selector: ClipboardHelper.getCopyContext(false)
},
PASTE: {
label: Strings.browser.GetStringFromName("contextmenu.paste"),
id: "paste_action",
icon: "drawable://paste",
icon: "drawable://ab_paste",
action: function(aElement) {
ClipboardHelper.paste(aElement);
SelectionHandler._positionHandles();
SelectionHandler._updateMenu();
},
order: 1,
order: 2,
selector: ClipboardHelper.pasteContext,
},
@ -470,11 +470,12 @@ var SelectionHandler = {
return Strings.browser.formatStringFromName("contextmenu.search", [Services.search.defaultEngine.name], 1);
},
id: "search_action",
icon: "drawable://ic_url_bar_search",
icon: "drawable://ab_search",
action: function() {
SelectionHandler.searchSelection();
SelectionHandler._closeSelection();
},
order: 1,
selector: ClipboardHelper.searchWithContext,
},

View File

@ -266,8 +266,9 @@ AboutReader.prototype = {
break;
case "scroll":
if (!this._scrolled) {
this._scrolled = true;
this._setToolbarVisibility(false);
let isScrollingUp = this._scrollOffset > aEvent.pageY;
this._setToolbarVisibility(isScrollingUp);
this._scrollOffset = aEvent.pageY;
}
break;
case "popstate":
@ -501,6 +502,7 @@ AboutReader.prototype = {
return;
this._toolbarElement.classList.toggle("toolbar-hidden");
this._setSystemUIVisibility(visible);
if (!visible && !this._hasUsedToolbar) {
this._hasUsedToolbar = Services.prefs.getBoolPref("reader.has_used_toolbar");
@ -517,6 +519,13 @@ AboutReader.prototype = {
this._setToolbarVisibility(!this._getToolbarVisibility());
},
_setSystemUIVisibility: function Reader_setSystemUIVisibility(visible) {
gChromeWin.sendMessageToJava({
type: "SystemUI:Visibility",
visible: visible
});
},
_loadFromURL: function Reader_loadFromURL(url) {
this._showProgressDelayed();

View File

@ -6793,7 +6793,7 @@ var SearchEngines = {
SelectionHandler.addAction({
id: "search_add_action",
label: Strings.browser.GetStringFromName("contextmenu.addSearchEngine"),
icon: "drawable://ic_url_bar_search",
icon: "drawable://ab_add_search_engine",
selector: filter,
action: function(aElement) {
SearchEngines.addEngine(aElement);

View File

@ -39,6 +39,10 @@ li:not(:last-of-type),
#errorLongDesc,
#errorLongContent {
padding-bottom: 10px;
}
/* Push the #ignoreWarningButton to the bottom on the blocked site page */
.blockedsite > #errorPageContainer > #errorLongContent {
flex: 1;
}

View File

@ -339,13 +339,24 @@ InternalMethods.prototype = {
return Promise.resolve(data);
}
if (!this.whenVerifiedPromise) {
this.whenVerifiedPromise = Promise.defer();
log.debug("whenVerified promise starts polling for verified email");
this.pollEmailStatus(data.sessionToken, "start");
}
return this.whenVerifiedPromise.promise;
},
/**
* Resend the verification email to the logged-in user.
*
* @return Promise
* fulfilled: json data returned from xhr call
* rejected: error
*/
resendVerificationEmail: function(data) {
this.pollEmailStatus(data.sessionToken, "start");
return this.fxAccountsClient.resendVerificationEmail(data.sessionToken);
},
notifyObservers: function(topic) {
log.debug("Notifying observers of " + topic);
Services.obs.notifyObservers(null, topic, null);
@ -364,12 +375,13 @@ InternalMethods.prototype = {
let myGenerationCount = this.generationCount;
log.debug("entering pollEmailStatus: " + why + " " + myGenerationCount);
if (why == "start") {
if (this.currentTimer) {
// safety check - this case should have been caught on
// entry with setSignedInUser
throw new Error("Already polling for email status");
}
// If we were already polling, stop and start again. This could happen
// if the user requested the verification email to be resent while we
// were already polling for receipt of an earlier email.
this.pollTimeRemaining = this.POLL_SESSION;
if (!this.whenVerifiedPromise) {
this.whenVerifiedPromise = Promise.defer();
}
}
this.checkEmailStatus(sessionToken)
@ -487,7 +499,7 @@ this.FxAccounts.prototype = Object.freeze({
log.debug("setSignedInUser - aborting any existing flows");
internal.abortExistingFlow();
let record = {version: this.version, accountData: credentials };
let record = {version: this.version, accountData: credentials};
// Cache a clone of the credentials object.
internal.signedInUser = JSON.parse(JSON.stringify(record));
@ -533,6 +545,22 @@ this.FxAccounts.prototype = Object.freeze({
});
},
/**
* Resend the verification email fot the currently signed-in user.
*
*/
resendVerificationEmail: function resendVerificationEmail() {
return this.getSignedInUser().then((data) => {
// If the caller is asking for verification to be re-sent, and there is
// no signed-in user to begin with, this is probably best regarded as an
// error.
if (data) {
return internal.resendVerificationEmail(data);
}
throw new Error("Cannot resend verification email; no signed-in user");
});
},
/**
* returns a promise that fires with the assertion. If there is no verified
* signed-in user, fires with null.

View File

@ -101,7 +101,7 @@ this.FxAccountsClient.prototype = {
* Destroy the current session with the Firefox Account API server
*
* @param sessionTokenHex
* The session token endcoded in hex
* The session token encoded in hex
* @return Promise
*/
signOut: function (sessionTokenHex) {
@ -113,7 +113,7 @@ this.FxAccountsClient.prototype = {
* Check the verification status of the user's FxA email address
*
* @param sessionTokenHex
* The current session token endcoded in hex
* The current session token encoded in hex
* @return Promise
*/
recoveryEmailStatus: function (sessionTokenHex) {
@ -121,6 +121,18 @@ this.FxAccountsClient.prototype = {
this._deriveHawkCredentials(sessionTokenHex, "sessionToken"));
},
/**
* Resend the verification email for the user
*
* @param sessionTokenHex
* The current token encoded in hex
* @return Promise
*/
resendVerificationEmail: function(sessionTokenHex) {
return this._request("/recovery_email/resend_code", "POST",
this._deriveHawkCredentials(sessionTokenHex, "sessionToken"));
},
/**
* Retrieve encryption keys
*
@ -170,7 +182,7 @@ this.FxAccountsClient.prototype = {
* Sends a public key to the FxA API server and returns a signed certificate
*
* @param sessionTokenHex
* The current session token endcoded in hex
* The current session token encoded in hex
* @param serializedPublicKey
* A public key (usually generated by jwcrypto)
* @param lifetime
@ -223,7 +235,7 @@ this.FxAccountsClient.prototype = {
* (e.g. sessionToken vs. keyFetchToken).
*
* @param tokenHex
* The current session token endcoded in hex
* The current session token encoded in hex
* @param context
* A context for the credentials
* @param size

View File

@ -64,6 +64,11 @@ function MockFxAccountsClient() {
return deferred.promise;
};
this.resendVerificationEmail = function(sessionToken) {
// Return the session token to show that we received it in the first place
return Promise.resolve(sessionToken);
};
this.signCertificate = function() { throw "no" };
FxAccountsClient.apply(this);
@ -168,9 +173,7 @@ add_task(function test_get_signed_in_user_initially_unset() {
do_check_eq(result, null);
});
/*
* Sanity-check that our mocked client is working correctly
*/
// Sanity-check that our mocked client is working correctly
add_test(function test_client_mock() {
do_test_pending();
@ -188,12 +191,10 @@ add_test(function test_client_mock() {
});
});
/*
* Sign in a user, and after a little while, verify the user's email.
* Right after signing in the user, we should get the 'onlogin' notification.
* Polling should detect that the email is verified, and eventually
* 'onverified' should be observed
*/
// Sign in a user, and after a little while, verify the user's email.
// Right after signing in the user, we should get the 'onlogin' notification.
// Polling should detect that the email is verified, and eventually
// 'onverified' should be observed
add_test(function test_verification_poll() {
do_test_pending();
@ -224,7 +225,7 @@ add_test(function test_verification_poll() {
// The user is signing in, but email has not been verified yet
do_check_eq(user.verified, false);
do_timeout(200, function() {
// Mock email verification ...
log.debug("Mocking verification of francine's email");
fxa.internal.fxAccountsClient._email = test_user.email;
fxa.internal.fxAccountsClient._verified = true;
});
@ -232,11 +233,9 @@ add_test(function test_verification_poll() {
});
});
/*
* Sign in the user, but never verify the email. The check-email
* poll should time out. No verifiedlogin event should be observed, and the
* internal whenVerified promise should be rejected
*/
// Sign in the user, but never verify the email. The check-email
// poll should time out. No verifiedlogin event should be observed, and the
// internal whenVerified promise should be rejected
add_test(function test_polling_timeout() {
do_test_pending();
@ -303,9 +302,7 @@ add_test(function test_getKeys() {
});
});
/*
* getKeys with no keyFetchToken should trigger signOut
*/
// getKeys with no keyFetchToken should trigger signOut
add_test(function test_getKeys_no_token() {
do_test_pending();
@ -326,11 +323,9 @@ add_test(function test_getKeys_no_token() {
});
});
/*
* Alice (User A) signs up but never verifies her email. Then Bob (User B)
* signs in with a verified email. Ensure that no sign-in events are triggered
* on Alice's behalf. In the end, Bob should be the signed-in user.
*/
// Alice (User A) signs up but never verifies her email. Then Bob (User B)
// signs in with a verified email. Ensure that no sign-in events are triggered
// on Alice's behalf. In the end, Bob should be the signed-in user.
add_test(function test_overlapping_signins() {
do_test_pending();
@ -457,6 +452,64 @@ add_task(function test_getAssertion() {
_("----- DONE ----\n");
});
add_task(function test_resend_email_not_signed_in() {
let fxa = new MockFxAccounts();
try {
yield fxa.resendVerificationEmail();
} catch(err) {
do_check_eq(err.message,
"Cannot resend verification email; no signed-in user");
do_test_finished();
run_next_test();
return;
}
do_throw("Should not be able to resend email when nobody is signed in");
});
add_task(function test_resend_email() {
do_test_pending();
let fxa = new MockFxAccounts();
let alice = getTestUser("alice");
do_check_eq(fxa.internal.generationCount, 0);
// Alice is the user signing in; her email is unverified.
fxa.setSignedInUser(alice).then(() => {
log.debug("Alice signing in");
// We're polling for the first email
do_check_eq(fxa.internal.generationCount, 1);
// The polling timer is ticking
do_check_true(fxa.internal.currentTimer > 0);
fxa.internal.getUserAccountData().then(user => {
do_check_eq(user.email, alice.email);
do_check_eq(user.verified, false);
log.debug("Alice wants verification email resent");
fxa.resendVerificationEmail().then((result) => {
// Mock server response; ensures that the session token actually was
// passed to the client to make the hawk call
do_check_eq(result, "alice's session token");
// Timer was not restarted
do_check_eq(fxa.internal.generationCount, 1);
// Timer is still ticking
do_check_true(fxa.internal.currentTimer > 0);
// Ok abort polling before we go on to the next test
fxa.internal.abortExistingFlow();
do_test_finished();
run_next_test();
});
});
});
});
/*
* End of tests.
* Utility functions follow.

View File

@ -21,6 +21,10 @@ menulist:-moz-focusring:not([open="true"]):not(.menulist-compact) > .menulist-la
-moz-margin-start: 0 !important;
}
.menulist-description {
-moz-margin-start: 1ex !important;
}
menulist:not([editable="true"]) > .menulist-dropmarker {
margin-top: -2px;
-moz-margin-start: 3px;