mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 22:05:44 +00:00
Merge f-t to m-c, a=merge
This commit is contained in:
commit
f713acf7ea
@ -838,10 +838,13 @@ sync_java_files = [
|
||||
'fxa/AccountLoader.java',
|
||||
'fxa/activities/FxAccountAbstractActivity.java',
|
||||
'fxa/activities/FxAccountAbstractSetupActivity.java',
|
||||
'fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java',
|
||||
'fxa/activities/FxAccountConfirmAccountActivity.java',
|
||||
'fxa/activities/FxAccountCreateAccountActivity.java',
|
||||
'fxa/activities/FxAccountCreateAccountNotAllowedActivity.java',
|
||||
'fxa/activities/FxAccountFinishMigratingActivity.java',
|
||||
'fxa/activities/FxAccountGetStartedActivity.java',
|
||||
'fxa/activities/FxAccountMigrationFinishedActivity.java',
|
||||
'fxa/activities/FxAccountSignInActivity.java',
|
||||
'fxa/activities/FxAccountStatusActivity.java',
|
||||
'fxa/activities/FxAccountStatusFragment.java',
|
||||
@ -861,6 +864,7 @@ sync_java_files = [
|
||||
'fxa/login/FxAccountLoginStateMachine.java',
|
||||
'fxa/login/FxAccountLoginTransition.java',
|
||||
'fxa/login/Married.java',
|
||||
'fxa/login/MigratedFromSync11.java',
|
||||
'fxa/login/Separated.java',
|
||||
'fxa/login/State.java',
|
||||
'fxa/login/StateFactory.java',
|
||||
@ -879,6 +883,7 @@ sync_java_files = [
|
||||
'fxa/tasks/FxAccountCreateAccountTask.java',
|
||||
'fxa/tasks/FxAccountSetupTask.java',
|
||||
'fxa/tasks/FxAccountSignInTask.java',
|
||||
'fxa/tasks/FxAccountUnlockCodeResender.java',
|
||||
'sync/AlreadySyncingException.java',
|
||||
'sync/BackoffHandler.java',
|
||||
'sync/BadRequiredFieldJSONException.java',
|
||||
|
@ -17,4 +17,5 @@ public interface FxAccountClient {
|
||||
public void keys(byte[] keyFetchToken, RequestDelegate<TwoKeys> requestDelegate);
|
||||
public void sign(byte[] sessionToken, ExtendedJSONObject publicKey, long certificateDurationInMilliseconds, RequestDelegate<String> requestDelegate);
|
||||
public void resendCode(byte[] sessionToken, RequestDelegate<Void> delegate);
|
||||
public void resendUnlockCode(byte[] emailUTF8, RequestDelegate<Void> delegate);
|
||||
}
|
||||
|
@ -770,4 +770,46 @@ public class FxAccountClient10 {
|
||||
};
|
||||
post(resource, new JSONObject(), delegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a fresh unlock code be sent to the account email.
|
||||
* <p>
|
||||
* Since the account can be locked before the device can connect to it, the
|
||||
* only reasonable identifier is the account email. Since the account is
|
||||
* locked out, this request is un-authenticated.
|
||||
*
|
||||
* @param emailUTF8
|
||||
* identifying account.
|
||||
* @param delegate
|
||||
* to invoke callbacks.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void resendUnlockCode(final byte[] emailUTF8, final RequestDelegate<Void> delegate) {
|
||||
final BaseResource resource;
|
||||
final JSONObject body = new JSONObject();
|
||||
try {
|
||||
resource = new BaseResource(new URI(serverURI + "account/unlock/resend_code"));
|
||||
body.put("email", new String(emailUTF8, "UTF-8"));
|
||||
} catch (URISyntaxException e) {
|
||||
invokeHandleError(delegate, e);
|
||||
return;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
invokeHandleError(delegate, e);
|
||||
return;
|
||||
}
|
||||
|
||||
resource.delegate = new ResourceDelegate<Void>(resource, delegate) {
|
||||
@Override
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
|
||||
try {
|
||||
delegate.handleSuccess(null);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
post(resource, body, delegate);
|
||||
}
|
||||
}
|
||||
|
@ -96,6 +96,10 @@ public class FxAccountClientException extends Exception {
|
||||
return apiErrorNumber == FxAccountRemoteError.INCORRECT_EMAIL_CASE;
|
||||
}
|
||||
|
||||
public boolean isAccountLocked() {
|
||||
return apiErrorNumber == FxAccountRemoteError.ACCOUNT_LOCKED;
|
||||
}
|
||||
|
||||
public int getErrorMessageStringResource() {
|
||||
if (isUpgradeRequired()) {
|
||||
return R.string.fxaccount_remote_error_UPGRADE_REQUIRED;
|
||||
@ -111,6 +115,8 @@ public class FxAccountClientException extends Exception {
|
||||
return R.string.fxaccount_remote_error_CLIENT_HAS_SENT_TOO_MANY_REQUESTS;
|
||||
} else if (isServerUnavailable()) {
|
||||
return R.string.fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD;
|
||||
} else if (isAccountLocked()) {
|
||||
return R.string.fxaccount_remote_error_ACCOUNT_LOCKED;
|
||||
} else {
|
||||
return R.string.fxaccount_remote_error_UNKNOWN_ERROR;
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ public interface FxAccountRemoteError {
|
||||
public static final int INVALID_REQUEST_SIGNATURE = 109;
|
||||
public static final int INVALID_AUTHENTICATION_TOKEN = 110;
|
||||
public static final int INVALID_AUTHENTICATION_TIMESTAMP = 111;
|
||||
public static final int INVALID_AUTHENTICATION_NONCE = 115;
|
||||
public static final int CONTENT_LENGTH_HEADER_WAS_NOT_PROVIDED = 112;
|
||||
public static final int REQUEST_BODY_TOO_LARGE = 113;
|
||||
public static final int CLIENT_HAS_SENT_TOO_MANY_REQUESTS = 114;
|
||||
@ -26,6 +25,7 @@ public interface FxAccountRemoteError {
|
||||
public static final int INCORRECT_KEY_RETRIEVAL_METHOD_FOR_THIS_ACCOUNT = 118;
|
||||
public static final int INCORRECT_API_VERSION_FOR_THIS_ACCOUNT = 119;
|
||||
public static final int INCORRECT_EMAIL_CASE = 120;
|
||||
public static final int ACCOUNT_LOCKED = 121;
|
||||
public static final int SERVICE_TEMPORARILY_UNAVAILABLE_DUE_TO_HIGH_LOAD = 201;
|
||||
public static final int UNKNOWN_ERROR = 999;
|
||||
}
|
||||
|
@ -104,6 +104,8 @@ public class Distribution {
|
||||
// Corresponds to the high value in Histograms.json.
|
||||
private static final long MAX_DOWNLOAD_TIME_MSEC = 40000; // 40 seconds.
|
||||
|
||||
// Wait just a little while for the system to send a referrer intent after install.
|
||||
private static final long DELAY_WAIT_FOR_REFERRER_MSEC = 400;
|
||||
|
||||
|
||||
/**
|
||||
@ -385,7 +387,16 @@ public class Distribution {
|
||||
*/
|
||||
private boolean checkIntentDistribution() {
|
||||
if (referrer == null) {
|
||||
return false;
|
||||
// Wait a predetermined time and try again.
|
||||
// Just block the thread, because it's the simplest solution.
|
||||
try {
|
||||
Thread.sleep(DELAY_WAIT_FOR_REFERRER_MSEC);
|
||||
} catch (InterruptedException e) {
|
||||
// Good enough.
|
||||
}
|
||||
if (referrer == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
URI uri = getReferredDistribution(referrer);
|
||||
|
@ -5,6 +5,7 @@
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
@ -23,8 +24,10 @@ import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Engaged;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.ProgressDisplay;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountUnlockCodeResender;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.setup.Constants;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
|
||||
@ -35,9 +38,12 @@ import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.Spannable;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.text.method.SingleLineTransformationMethod;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.util.Patterns;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
@ -154,7 +160,35 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
||||
}
|
||||
|
||||
protected void showClientRemoteException(final FxAccountClientRemoteException e) {
|
||||
remoteErrorTextView.setText(e.getErrorMessageStringResource());
|
||||
if (!e.isAccountLocked()) {
|
||||
remoteErrorTextView.setText(e.getErrorMessageStringResource());
|
||||
return;
|
||||
}
|
||||
|
||||
// This horrible bit of special-casing is because we want this error message
|
||||
// to contain a clickable, extra chunk of text, but we don't want to pollute
|
||||
// the exception class with Android specifics.
|
||||
final int messageId = e.getErrorMessageStringResource();
|
||||
final int clickableId = R.string.fxaccount_resend_unlock_code_button_label;
|
||||
final Spannable span = Utils.interpolateClickableSpan(this, messageId, clickableId, new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
// It would be best to capture the email address sent to the server
|
||||
// and use it here, but this will do for now. If the user modifies
|
||||
// the email address entered, the error text is hidden, so sending a
|
||||
// changed email address would be the result of an unusual race.
|
||||
final String email = emailEdit.getText().toString();
|
||||
byte[] emailUTF8 = null;
|
||||
try {
|
||||
emailUTF8 = email.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// It's okay, we'll fail in the code resender.
|
||||
}
|
||||
FxAccountUnlockCodeResender.resendUnlockCode(FxAccountAbstractSetupActivity.this, getAuthServerEndpoint(), emailUTF8);
|
||||
}
|
||||
});
|
||||
remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
remoteErrorTextView.setText(span);
|
||||
}
|
||||
|
||||
protected void addListeners() {
|
||||
|
@ -0,0 +1,181 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Engaged;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Abstract activity which displays a screen for updating the local password.
|
||||
*/
|
||||
public abstract class FxAccountAbstractUpdateCredentialsActivity extends FxAccountAbstractSetupActivity {
|
||||
protected static final String LOG_TAG = FxAccountAbstractUpdateCredentialsActivity.class.getSimpleName();
|
||||
|
||||
protected AndroidFxAccount fxAccount;
|
||||
|
||||
protected final int layoutResourceId;
|
||||
|
||||
public FxAccountAbstractUpdateCredentialsActivity(int layoutResourceId) {
|
||||
// We want to share code with the other setup activities, but this activity
|
||||
// doesn't create a new Android Account, it modifies an existing one. If you
|
||||
// manage to get an account, and somehow be locked out too, we'll let you
|
||||
// update it.
|
||||
super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
|
||||
this.layoutResourceId = layoutResourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
|
||||
|
||||
super.onCreate(icicle);
|
||||
setContentView(layoutResourceId);
|
||||
|
||||
emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit");
|
||||
passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
|
||||
showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
|
||||
remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view");
|
||||
button = (Button) ensureFindViewById(null, R.id.button, "update credentials");
|
||||
progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar");
|
||||
|
||||
minimumPasswordLength = 1; // Minimal restriction on passwords entered to sign in.
|
||||
createButton();
|
||||
addListeners();
|
||||
updateButtonState();
|
||||
createShowPasswordButton();
|
||||
|
||||
emailEdit.setEnabled(false);
|
||||
|
||||
TextView view = (TextView) findViewById(R.id.forgot_password_link);
|
||||
ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password);
|
||||
|
||||
updateFromIntentExtras();
|
||||
}
|
||||
|
||||
protected class UpdateCredentialsDelegate implements RequestDelegate<LoginResponse> {
|
||||
public final String email;
|
||||
public final String serverURI;
|
||||
public final PasswordStretcher passwordStretcher;
|
||||
|
||||
public UpdateCredentialsDelegate(String email, PasswordStretcher passwordStretcher, String serverURI) {
|
||||
this.email = email;
|
||||
this.serverURI = serverURI;
|
||||
this.passwordStretcher = passwordStretcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(FxAccountClientRemoteException e) {
|
||||
if (e.isUpgradeRequired()) {
|
||||
Logger.error(LOG_TAG, "Got upgrade required from remote server; transitioning Firefox Account to Doghouse state.");
|
||||
final State state = fxAccount.getState();
|
||||
fxAccount.setState(state.makeDoghouseState());
|
||||
// The status activity will say that the user needs to upgrade.
|
||||
redirectToActivity(FxAccountStatusActivity.class);
|
||||
return;
|
||||
}
|
||||
showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccess(LoginResponse result) {
|
||||
Logger.info(LOG_TAG, "Got success signing in.");
|
||||
|
||||
if (fxAccount == null) {
|
||||
this.handleError(new IllegalStateException("fxAccount must not be null"));
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] unwrapkB;
|
||||
try {
|
||||
// It is crucial that we use the email address provided by the server
|
||||
// (rather than whatever the user entered), because the user's keys are
|
||||
// wrapped and salted with the initial email they provided to
|
||||
// /create/account. Of course, we want to pass through what the user
|
||||
// entered locally as much as possible.
|
||||
byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(result.remoteEmail.getBytes("UTF-8"));
|
||||
unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW);
|
||||
} catch (Exception e) {
|
||||
this.handleError(e);
|
||||
return;
|
||||
}
|
||||
fxAccount.setState(new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken));
|
||||
fxAccount.requestSync(FirefoxAccounts.FORCE);
|
||||
|
||||
// For great debugging.
|
||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
fxAccount.dump();
|
||||
}
|
||||
|
||||
setResult(RESULT_OK);
|
||||
|
||||
// Maybe show success activity.
|
||||
final Intent successIntent = makeSuccessIntent(email, result);
|
||||
if (successIntent != null) {
|
||||
startActivity(successIntent);
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateCredentials(String email, String password) {
|
||||
String serverURI = fxAccount.getAccountServerURI();
|
||||
Executor executor = Executors.newSingleThreadExecutor();
|
||||
FxAccountClient client = new FxAccountClient20(serverURI, executor);
|
||||
PasswordStretcher passwordStretcher = makePasswordStretcher(password);
|
||||
try {
|
||||
hideRemoteError();
|
||||
RequestDelegate<LoginResponse> delegate = new UpdateCredentialsDelegate(email, passwordStretcher, serverURI);
|
||||
new FxAccountSignInTask(this, this, email, passwordStretcher, client, delegate).execute();
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception updating credentials for account.", e);
|
||||
showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
|
||||
}
|
||||
}
|
||||
|
||||
protected void createButton() {
|
||||
button.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final String email = emailEdit.getText().toString();
|
||||
final String password = passwordEdit.getText().toString();
|
||||
updateCredentials(email, password);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -7,7 +7,6 @@ package org.mozilla.gecko.fxa.activities;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Engaged;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
@ -141,9 +140,6 @@ public class FxAccountConfirmAccountActivity extends FxAccountAbstractActivity i
|
||||
case NeedsVerification:
|
||||
// This is what we're here to handle.
|
||||
break;
|
||||
case NeedsPassword:
|
||||
case NeedsUpgrade:
|
||||
case None:
|
||||
default:
|
||||
// We're not in the right place! Redirect to status.
|
||||
Logger.warn(LOG_TAG, "No need to verify Firefox Account that needs action " + neededAction.toString() +
|
||||
|
@ -22,6 +22,7 @@ import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClient
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountCreateAccountTask;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
@ -30,7 +31,6 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.view.View;
|
||||
@ -124,13 +124,10 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
||||
// This horrible bit of special-casing is because we want this error message to
|
||||
// contain a clickable, extra chunk of text, but we don't want to pollute
|
||||
// the exception class with Android specifics.
|
||||
final String clickablePart = getString(R.string.fxaccount_sign_in_button_label);
|
||||
final String message = getString(e.getErrorMessageStringResource(), clickablePart);
|
||||
final int clickableStart = message.lastIndexOf(clickablePart);
|
||||
final int clickableEnd = clickableStart + clickablePart.length();
|
||||
final int messageId = e.getErrorMessageStringResource();
|
||||
final int clickableId = R.string.fxaccount_sign_in_button_label;
|
||||
|
||||
final Spannable span = Spannable.Factory.getInstance().newSpannable(message);
|
||||
span.setSpan(new ClickableSpan() {
|
||||
final Spannable span = Utils.interpolateClickableSpan(this, messageId, clickableId, new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
// Pass through the email address that already existed.
|
||||
@ -143,7 +140,7 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
||||
final Bundle extras = makeExtrasBundle(email, password);
|
||||
startActivityInstead(FxAccountSignInActivity.class, CHILD_REQUEST_CODE, extras);
|
||||
}
|
||||
}, clickableStart, clickableEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
});
|
||||
remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
remoteErrorTextView.setText(span);
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.login.State.StateLabel;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
/**
|
||||
* Activity which displays a screen for inputting the password and finishing
|
||||
* migrating to Firefox Accounts / Sync 1.5.
|
||||
*/
|
||||
public class FxAccountFinishMigratingActivity extends FxAccountAbstractUpdateCredentialsActivity {
|
||||
protected static final String LOG_TAG = FxAccountFinishMigratingActivity.class.getSimpleName();
|
||||
|
||||
public FxAccountFinishMigratingActivity() {
|
||||
super(R.layout.fxaccount_finish_migrating);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
this.fxAccount = getAndroidFxAccount();
|
||||
if (fxAccount == null) {
|
||||
Logger.warn(LOG_TAG, "Could not get Firefox Account.");
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
final State state = fxAccount.getState();
|
||||
if (state.getStateLabel() != StateLabel.MigratedFromSync11) {
|
||||
Logger.warn(LOG_TAG, "Cannot finish migrating from Firefox Account in state: " + state.getStateLabel());
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
emailEdit.setText(fxAccount.getEmail());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent makeSuccessIntent(String email, LoginResponse result) {
|
||||
final Intent successIntent = new Intent(this, FxAccountMigrationFinishedActivity.class);
|
||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||
successIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
return successIntent;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.login.State.Action;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
/**
|
||||
* Activity which displays "Upgrade finished" success screen.
|
||||
*/
|
||||
public class FxAccountMigrationFinishedActivity extends FxAccountAbstractActivity {
|
||||
private static final String LOG_TAG = FxAccountMigrationFinishedActivity.class.getSimpleName();
|
||||
|
||||
protected AndroidFxAccount fxAccount;
|
||||
|
||||
public FxAccountMigrationFinishedActivity() {
|
||||
super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
|
||||
|
||||
super.onCreate(icicle);
|
||||
setContentView(R.layout.fxaccount_migration_finished);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
this.fxAccount = getAndroidFxAccount();
|
||||
if (fxAccount == null) {
|
||||
Logger.warn(LOG_TAG, "Could not get Firefox Account.");
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
final State state = fxAccount.getState();
|
||||
if (state.getNeededAction() == Action.NeedsFinishMigrating) {
|
||||
Logger.warn(LOG_TAG, "Firefox Account needs to finish migrating; not displaying migration finished activity.");
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
final View backToBrowsingButton = ensureFindViewById(null, R.id.button, "back to browsing button");
|
||||
backToBrowsingButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
ActivityUtils.openURLInFennec(v.getContext(), null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -5,13 +5,12 @@
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.LocaleAware.LocaleAwareFragmentActivity;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.LocaleAware.LocaleAwareActivity;
|
||||
import org.mozilla.gecko.LocaleAware.LocaleAwareFragmentActivity;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
|
@ -77,6 +77,7 @@ public class FxAccountStatusFragment
|
||||
protected Preference needsVerificationPreference;
|
||||
protected Preference needsMasterSyncAutomaticallyEnabledPreference;
|
||||
protected Preference needsAccountEnabledPreference;
|
||||
protected Preference needsFinishMigratingPreference;
|
||||
|
||||
protected PreferenceCategory syncCategory;
|
||||
|
||||
@ -138,6 +139,7 @@ public class FxAccountStatusFragment
|
||||
needsVerificationPreference = ensureFindPreference("needs_verification");
|
||||
needsMasterSyncAutomaticallyEnabledPreference = ensureFindPreference("needs_master_sync_automatically_enabled");
|
||||
needsAccountEnabledPreference = ensureFindPreference("needs_account_enabled");
|
||||
needsFinishMigratingPreference = ensureFindPreference("needs_finish_migrating");
|
||||
|
||||
syncCategory = (PreferenceCategory) ensureFindPreference("sync_category");
|
||||
|
||||
@ -157,6 +159,7 @@ public class FxAccountStatusFragment
|
||||
needsPasswordPreference.setOnPreferenceClickListener(this);
|
||||
needsVerificationPreference.setOnPreferenceClickListener(this);
|
||||
needsAccountEnabledPreference.setOnPreferenceClickListener(this);
|
||||
needsFinishMigratingPreference.setOnPreferenceClickListener(this);
|
||||
|
||||
bookmarksPreference.setOnPreferenceClickListener(this);
|
||||
historyPreference.setOnPreferenceClickListener(this);
|
||||
@ -204,6 +207,20 @@ public class FxAccountStatusFragment
|
||||
return true;
|
||||
}
|
||||
|
||||
if (preference == needsFinishMigratingPreference) {
|
||||
final Intent intent = new Intent(getActivity(), FxAccountFinishMigratingActivity.class);
|
||||
final Bundle extras = getExtrasForAccount();
|
||||
if (extras != null) {
|
||||
intent.putExtras(extras);
|
||||
}
|
||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
startActivity(intent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (preference == needsVerificationPreference) {
|
||||
FxAccountCodeResender.resendCode(getActivity().getApplicationContext(), fxAccount);
|
||||
|
||||
@ -280,6 +297,7 @@ public class FxAccountStatusFragment
|
||||
this.needsVerificationPreference,
|
||||
this.needsMasterSyncAutomaticallyEnabledPreference,
|
||||
this.needsAccountEnabledPreference,
|
||||
this.needsFinishMigratingPreference,
|
||||
};
|
||||
for (Preference errorPreference : errorPreferences) {
|
||||
final boolean currentlyShown = null != findPreference(errorPreference.getKey());
|
||||
@ -325,6 +343,12 @@ public class FxAccountStatusFragment
|
||||
setCheckboxesEnabled(false);
|
||||
}
|
||||
|
||||
protected void showNeedsFinishMigrating() {
|
||||
syncCategory.setTitle(R.string.fxaccount_status_sync);
|
||||
showOnlyOneErrorPreference(needsFinishMigratingPreference);
|
||||
setCheckboxesEnabled(false);
|
||||
}
|
||||
|
||||
protected void showConnected() {
|
||||
syncCategory.setTitle(R.string.fxaccount_status_sync_enabled);
|
||||
showOnlyOneErrorPreference(null);
|
||||
@ -464,8 +488,12 @@ public class FxAccountStatusFragment
|
||||
case NeedsVerification:
|
||||
showNeedsVerification();
|
||||
break;
|
||||
default:
|
||||
case NeedsFinishMigrating:
|
||||
showNeedsFinishMigrating();
|
||||
break;
|
||||
case None:
|
||||
showConnected();
|
||||
break;
|
||||
}
|
||||
|
||||
// We check for the master setting last, since it is not strictly
|
||||
@ -703,6 +731,11 @@ public class FxAccountStatusFragment
|
||||
State state = fxAccount.getState();
|
||||
fxAccount.setState(state.makeDoghouseState());
|
||||
refresh();
|
||||
} else if ("debug_migrated_from_sync11".equals(key)) {
|
||||
Logger.info(LOG_TAG, "Moving to MigratedFromSync11 state: Requiring password.");
|
||||
State state = fxAccount.getState();
|
||||
fxAccount.setState(state.makeMigratedFromSync11State(null));
|
||||
refresh();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -729,7 +762,8 @@ public class FxAccountStatusFragment
|
||||
"debug_force_sync",
|
||||
"debug_forget_certificate",
|
||||
"debug_require_password",
|
||||
"debug_require_upgrade" };
|
||||
"debug_require_upgrade",
|
||||
"debug_migrated_from_sync11" };
|
||||
for (String debugKey : debugKeys) {
|
||||
final Preference button = ensureFindPreference(debugKey);
|
||||
button.setTitle(debugKey); // Not very friendly, but this is for debugging only!
|
||||
|
@ -4,80 +4,22 @@
|
||||
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Engaged;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.login.State.StateLabel;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.content.Intent;
|
||||
|
||||
/**
|
||||
* Activity which displays a screen for updating the local password.
|
||||
*/
|
||||
public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupActivity {
|
||||
public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractUpdateCredentialsActivity {
|
||||
protected static final String LOG_TAG = FxAccountUpdateCredentialsActivity.class.getSimpleName();
|
||||
|
||||
protected AndroidFxAccount fxAccount;
|
||||
|
||||
public FxAccountUpdateCredentialsActivity() {
|
||||
// We want to share code with the other setup activities, but this activity
|
||||
// doesn't create a new Android Account, it modifies an existing one. If you
|
||||
// manage to get an account, and somehow be locked out too, we'll let you
|
||||
// update it.
|
||||
super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
|
||||
|
||||
super.onCreate(icicle);
|
||||
setContentView(R.layout.fxaccount_update_credentials);
|
||||
|
||||
emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit");
|
||||
passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
|
||||
showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
|
||||
remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view");
|
||||
button = (Button) ensureFindViewById(null, R.id.button, "update credentials");
|
||||
progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar");
|
||||
|
||||
minimumPasswordLength = 1; // Minimal restriction on passwords entered to sign in.
|
||||
createButton();
|
||||
addListeners();
|
||||
updateButtonState();
|
||||
createShowPasswordButton();
|
||||
|
||||
emailEdit.setEnabled(false);
|
||||
|
||||
TextView view = (TextView) findViewById(R.id.forgot_password_link);
|
||||
ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password);
|
||||
|
||||
updateFromIntentExtras();
|
||||
super(R.layout.fxaccount_update_credentials);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -90,7 +32,7 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
State state = fxAccount.getState();
|
||||
final State state = fxAccount.getState();
|
||||
if (state.getStateLabel() != StateLabel.Separated) {
|
||||
Logger.warn(LOG_TAG, "Cannot update credentials from Firefox Account in state: " + state.getStateLabel());
|
||||
setResult(RESULT_CANCELED);
|
||||
@ -100,93 +42,11 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
|
||||
emailEdit.setText(fxAccount.getEmail());
|
||||
}
|
||||
|
||||
protected class UpdateCredentialsDelegate implements RequestDelegate<LoginResponse> {
|
||||
public final String email;
|
||||
public final String serverURI;
|
||||
public final PasswordStretcher passwordStretcher;
|
||||
|
||||
public UpdateCredentialsDelegate(String email, PasswordStretcher passwordStretcher, String serverURI) {
|
||||
this.email = email;
|
||||
this.serverURI = serverURI;
|
||||
this.passwordStretcher = passwordStretcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(FxAccountClientRemoteException e) {
|
||||
if (e.isUpgradeRequired()) {
|
||||
Logger.error(LOG_TAG, "Got upgrade required from remote server; transitioning Firefox Account to Doghouse state.");
|
||||
final State state = fxAccount.getState();
|
||||
fxAccount.setState(state.makeDoghouseState());
|
||||
// The status activity will say that the user needs to upgrade.
|
||||
redirectToActivity(FxAccountStatusActivity.class);
|
||||
return;
|
||||
}
|
||||
showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccess(LoginResponse result) {
|
||||
Logger.info(LOG_TAG, "Got success signing in.");
|
||||
|
||||
if (fxAccount == null) {
|
||||
this.handleError(new IllegalStateException("fxAccount must not be null"));
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] unwrapkB;
|
||||
try {
|
||||
// It is crucial that we use the email address provided by the server
|
||||
// (rather than whatever the user entered), because the user's keys are
|
||||
// wrapped and salted with the initial email they provided to
|
||||
// /create/account. Of course, we want to pass through what the user
|
||||
// entered locally as much as possible.
|
||||
byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(result.remoteEmail.getBytes("UTF-8"));
|
||||
unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW);
|
||||
} catch (Exception e) {
|
||||
this.handleError(e);
|
||||
return;
|
||||
}
|
||||
fxAccount.setState(new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken));
|
||||
fxAccount.requestSync(FirefoxAccounts.FORCE);
|
||||
|
||||
// For great debugging.
|
||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
fxAccount.dump();
|
||||
}
|
||||
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateCredentials(String email, String password) {
|
||||
String serverURI = fxAccount.getAccountServerURI();
|
||||
Executor executor = Executors.newSingleThreadExecutor();
|
||||
FxAccountClient client = new FxAccountClient20(serverURI, executor);
|
||||
PasswordStretcher passwordStretcher = makePasswordStretcher(password);
|
||||
try {
|
||||
hideRemoteError();
|
||||
RequestDelegate<LoginResponse> delegate = new UpdateCredentialsDelegate(email, passwordStretcher, serverURI);
|
||||
new FxAccountSignInTask(this, this, email, passwordStretcher, client, delegate).execute();
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception updating credentials for account.", e);
|
||||
showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
|
||||
}
|
||||
}
|
||||
|
||||
protected void createButton() {
|
||||
button.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final String email = emailEdit.getText().toString();
|
||||
final String password = passwordEdit.getText().toString();
|
||||
updateCredentials(email, password);
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public Intent makeSuccessIntent(String email, LoginResponse result) {
|
||||
// We don't show anything after updating credentials. The updating Activity
|
||||
// sets its result to OK and the user is returned to the previous task,
|
||||
// which is often the Status Activity.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
28
mobile/android/base/fxa/login/MigratedFromSync11.java
Normal file
28
mobile/android/base/fxa/login/MigratedFromSync11.java
Normal file
@ -0,0 +1,28 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.fxa.login;
|
||||
|
||||
import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
|
||||
import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.PasswordRequired;
|
||||
|
||||
public class MigratedFromSync11 extends State {
|
||||
public final String password;
|
||||
|
||||
public MigratedFromSync11(String email, String uid, boolean verified, String password) {
|
||||
super(StateLabel.MigratedFromSync11, email, uid, verified);
|
||||
// Null password is allowed.
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final ExecuteDelegate delegate) {
|
||||
delegate.handleTransition(new PasswordRequired(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action getNeededAction() {
|
||||
return Action.NeedsFinishMigrating;
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
|
||||
public abstract class State {
|
||||
public static final long CURRENT_VERSION = 2L;
|
||||
public static final long CURRENT_VERSION = 3L;
|
||||
|
||||
public enum StateLabel {
|
||||
Engaged,
|
||||
@ -17,12 +17,14 @@ public abstract class State {
|
||||
Married,
|
||||
Separated,
|
||||
Doghouse,
|
||||
MigratedFromSync11,
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
NeedsUpgrade,
|
||||
NeedsPassword,
|
||||
NeedsVerification,
|
||||
NeedsFinishMigrating,
|
||||
None,
|
||||
}
|
||||
|
||||
@ -60,6 +62,10 @@ public abstract class State {
|
||||
return new Doghouse(email, uid, verified);
|
||||
}
|
||||
|
||||
public State makeMigratedFromSync11State(String password) {
|
||||
return new MigratedFromSync11(email, uid, verified, password);
|
||||
}
|
||||
|
||||
public abstract void execute(ExecuteDelegate delegate);
|
||||
|
||||
public abstract Action getNeededAction();
|
||||
|
@ -54,8 +54,11 @@ public class StateFactory {
|
||||
}
|
||||
|
||||
final int v = version.intValue();
|
||||
if (v == 2) {
|
||||
if (v == 3) {
|
||||
// The most common case is the most recent version.
|
||||
return fromJSONObjectV3(stateLabel, o);
|
||||
}
|
||||
if (v == 2) {
|
||||
return fromJSONObjectV2(stateLabel, o);
|
||||
}
|
||||
if (v == 1) {
|
||||
@ -134,6 +137,23 @@ public class StateFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactly the same as {@link fromJSONObjectV2}, except that there's a new
|
||||
* MigratedFromSyncV11 state.
|
||||
*/
|
||||
protected static State fromJSONObjectV3(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
|
||||
switch (stateLabel) {
|
||||
case MigratedFromSync11:
|
||||
return new MigratedFromSync11(
|
||||
o.getString("email"),
|
||||
o.getString("uid"),
|
||||
o.getBoolean("verified"),
|
||||
o.getString("password"));
|
||||
default:
|
||||
return fromJSONObjectV2(stateLabel, o);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void logMigration(State from, State to) {
|
||||
if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
return;
|
||||
|
@ -8,6 +8,7 @@ import org.mozilla.gecko.BrowserLocaleManager;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountFinishMigratingActivity;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountStatusActivity;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
@ -67,12 +68,21 @@ public class FxAccountNotificationManager {
|
||||
BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(context);
|
||||
}
|
||||
|
||||
final String title = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_title);
|
||||
final String text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
|
||||
final String title;
|
||||
final String text;
|
||||
final Intent notificationIntent;
|
||||
if (action == Action.NeedsFinishMigrating) {
|
||||
title = context.getResources().getString(R.string.fxaccount_sync_finish_migrating_notification_title);
|
||||
text = context.getResources().getString(R.string.fxaccount_sync_finish_migrating_notification_text, state.email);
|
||||
notificationIntent = new Intent(context, FxAccountFinishMigratingActivity.class);
|
||||
} else {
|
||||
title = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_title);
|
||||
text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
|
||||
notificationIntent = new Intent(context, FxAccountStatusActivity.class);
|
||||
}
|
||||
Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs action; offering notification with title: " + title);
|
||||
FxAccountUtils.pii(LOG_TAG, "And text: " + text);
|
||||
|
||||
final Intent notificationIntent = new Intent(context, FxAccountStatusActivity.class);
|
||||
final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
|
||||
|
||||
final Builder builder = new NotificationCompat.Builder(context);
|
||||
|
@ -118,6 +118,7 @@ public class FxAccountSchedulePolicy implements SchedulePolicy {
|
||||
switch (needed) {
|
||||
case NeedsPassword:
|
||||
case NeedsUpgrade:
|
||||
case NeedsFinishMigrating:
|
||||
requestPeriodicSync(POLL_INTERVAL_ERROR_STATE_SEC);
|
||||
break;
|
||||
case NeedsVerification:
|
||||
|
105
mobile/android/base/fxa/tasks/FxAccountUnlockCodeResender.java
Normal file
105
mobile/android/base/fxa/tasks/FxAccountUnlockCodeResender.java
Normal file
@ -0,0 +1,105 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.fxa.tasks;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* A helper class that provides a simple interface for requesting a Firefox
|
||||
* Account unlock account email be (re)-sent.
|
||||
*/
|
||||
public class FxAccountUnlockCodeResender {
|
||||
private static final String LOG_TAG = FxAccountUnlockCodeResender.class.getSimpleName();
|
||||
|
||||
private static class FxAccountUnlockCodeTask extends FxAccountSetupTask<Void> {
|
||||
protected static final String LOG_TAG = FxAccountUnlockCodeTask.class.getSimpleName();
|
||||
|
||||
protected final byte[] emailUTF8;
|
||||
|
||||
public FxAccountUnlockCodeTask(Context context, byte[] emailUTF8, FxAccountClient client, RequestDelegate<Void> delegate) {
|
||||
super(context, null, client, delegate);
|
||||
this.emailUTF8 = emailUTF8;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InnerRequestDelegate<Void> doInBackground(Void... arg0) {
|
||||
try {
|
||||
client.resendUnlockCode(emailUTF8, innerDelegate);
|
||||
latch.await();
|
||||
return innerDelegate;
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "Got exception signing in.", e);
|
||||
delegate.handleError(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ResendUnlockCodeDelegate implements RequestDelegate<Void> {
|
||||
public final Context context;
|
||||
|
||||
public ResendUnlockCodeDelegate(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception requesting fresh unlock code; ignoring.", e);
|
||||
Toast.makeText(context, R.string.fxaccount_unlock_code_not_sent, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(FxAccountClientRemoteException e) {
|
||||
handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccess(Void result) {
|
||||
Toast.makeText(context, R.string.fxaccount_unlock_code_sent, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resends the account unlock email, and displays an appropriate toast on both
|
||||
* send success and failure. Note that because the underlying implementation
|
||||
* uses {@link AsyncTask}, the provided context must be UI-capable and this
|
||||
* method called from the UI thread.
|
||||
*
|
||||
* Note that it may actually be possible to run this (and the
|
||||
* {@link AsyncTask}) method from a background thread - but this hasn't been
|
||||
* tested.
|
||||
*
|
||||
* @param context
|
||||
* A UI-capable Android context.
|
||||
* @param authServerURI
|
||||
* to send request to.
|
||||
* @param emailUTF8
|
||||
* bytes of email address identifying account; null indicates a local failure.
|
||||
*/
|
||||
public static void resendUnlockCode(Context context, String authServerURI, byte[] emailUTF8) {
|
||||
RequestDelegate<Void> delegate = new ResendUnlockCodeDelegate(context);
|
||||
|
||||
if (emailUTF8 == null) {
|
||||
delegate.handleError(new IllegalArgumentException("emailUTF8 must not be null"));
|
||||
return;
|
||||
}
|
||||
|
||||
final Executor executor = Executors.newSingleThreadExecutor();
|
||||
final FxAccountClient client = new FxAccountClient20(authServerURI, executor);
|
||||
new FxAccountUnlockCodeTask(context, emailUTF8, client, delegate).execute();
|
||||
}
|
||||
}
|
@ -157,6 +157,8 @@ public class RemoteTabsPanel extends HomeFragment {
|
||||
return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_password);
|
||||
case NeedsUpgrade:
|
||||
return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_upgrade);
|
||||
case NeedsFinishMigrating:
|
||||
return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_finish_migrating);
|
||||
default:
|
||||
// This should never happen, but we're confident we have a Firefox
|
||||
// Account at this point, so let's show the needs password screen.
|
||||
|
@ -10,6 +10,7 @@ import java.util.Locale;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountCreateAccountActivity;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountFinishMigratingActivity;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountUpdateCredentialsActivity;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
|
||||
@ -90,7 +91,8 @@ public class RemoteTabsStaticFragment extends HomeFragment implements OnClickLis
|
||||
R.id.remote_tabs_setup_old_sync_link,
|
||||
R.id.remote_tabs_needs_verification_resend_email,
|
||||
R.id.remote_tabs_needs_verification_help,
|
||||
R.id.remote_tabs_needs_password_sign_in, }) {
|
||||
R.id.remote_tabs_needs_password_sign_in,
|
||||
R.id.remote_tabs_needs_finish_migrating_sign_in, }) {
|
||||
maybeSetOnClickListener(view, resourceId);
|
||||
}
|
||||
}
|
||||
@ -116,10 +118,13 @@ public class RemoteTabsStaticFragment extends HomeFragment implements OnClickLis
|
||||
final EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.noneOf(OnUrlOpenListener.Flags.class);
|
||||
mUrlOpenListener.onUrlOpen(CONFIRM_ACCOUNT_SUPPORT_URL, flags);
|
||||
} else if (id == R.id.remote_tabs_needs_password_sign_in) {
|
||||
// This Activity will redirect to the correct Activity as needed.
|
||||
final Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
} else if (id == R.id.remote_tabs_needs_finish_migrating_sign_in) {
|
||||
final Intent intent = new Intent(getActivity(), FxAccountFinishMigratingActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -429,6 +429,8 @@ size. -->
|
||||
<!ENTITY home_remote_tabs_empty "Your tabs from other devices show up here.">
|
||||
<!ENTITY home_remote_tabs_unable_to_connect "Unable to connect">
|
||||
<!ENTITY home_remote_tabs_need_to_sign_in "Please sign in to reconnect your Firefox Account and continue syncing.">
|
||||
<!ENTITY home_remote_tabs_need_to_finish_migrating "Your new Firefox Account is ready!">
|
||||
|
||||
<!ENTITY home_remote_tabs_trouble_verifying "Trouble verifying your account?">
|
||||
<!ENTITY home_remote_tabs_need_to_verify "Please verify your Firefox Account to start syncing.">
|
||||
|
||||
|
@ -170,6 +170,9 @@
|
||||
<!ENTITY fxaccount_confirm_account_change_email 'Forget this email address?'>
|
||||
<!ENTITY fxaccount_confirm_account_verification_link_sent2 'Verification email sent'>
|
||||
<!ENTITY fxaccount_confirm_account_verification_link_not_sent2 'Couldn\'t send verification email'>
|
||||
<!ENTITY fxaccount_resend_unlock_code_button_label 'Resend unlock email'>
|
||||
<!ENTITY fxaccount_unlock_code_sent 'Account unlock email sent'>
|
||||
<!ENTITY fxaccount_unlock_code_not_sent 'Couldn\'t send account unlock email'>
|
||||
|
||||
<!ENTITY fxaccount_sign_in_sub_header 'Sign in'>
|
||||
<!ENTITY fxaccount_sign_in_button_label 'Sign in'>
|
||||
@ -180,10 +183,16 @@
|
||||
<!ENTITY fxaccount_account_verified_sub_header 'Account verified'>
|
||||
<!ENTITY fxaccount_account_verified_description2 'Your data will begin syncing momentarily.'>
|
||||
|
||||
<!ENTITY fxaccount_migration_finished_header 'Upgrade finished'>
|
||||
|
||||
<!ENTITY fxaccount_update_credentials_header 'Sign in'>
|
||||
<!ENTITY fxaccount_update_credentials_button_label 'Sign in'>
|
||||
<!ENTITY fxaccount_update_credentials_unknown_error 'Could not sign in'>
|
||||
|
||||
<!ENTITY fxaccount_finish_migrating_header 'Sign in to finish upgrading'>
|
||||
<!ENTITY fxaccount_finish_migrating_button_label 'Finish upgrading'>
|
||||
<!ENTITY fxaccount_finish_migrating_description 'Upgrading can transfer a lot of data. It\'s best to be on a WiFi network.'>
|
||||
|
||||
<!ENTITY fxaccount_status_header2 'Firefox Account'>
|
||||
<!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
|
||||
<!ENTITY fxaccount_status_auth_server 'Account server'>
|
||||
@ -198,6 +207,7 @@
|
||||
<!ENTITY fxaccount_status_needs_upgrade 'You need to upgrade &brandShortName; to sign in.'>
|
||||
<!ENTITY fxaccount_status_needs_master_sync_automatically_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Toggle “Auto-sync data” in Android Settings > Data Usage.'>
|
||||
<!ENTITY fxaccount_status_needs_account_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Tap to start syncing.'>
|
||||
<!ENTITY fxaccount_status_needs_finish_migrating 'Tap to sign in to your new Firefox Account.'>
|
||||
<!ENTITY fxaccount_status_bookmarks 'Bookmarks'>
|
||||
<!ENTITY fxaccount_status_history 'History'>
|
||||
<!ENTITY fxaccount_status_passwords 'Passwords'>
|
||||
@ -245,8 +255,14 @@
|
||||
<!ENTITY fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD 'Server busy, try again soon'>
|
||||
<!ENTITY fxaccount_remote_error_UNKNOWN_ERROR 'There was a problem'>
|
||||
<!ENTITY fxaccount_remote_error_COULD_NOT_CONNECT 'Cannot connect to network'>
|
||||
<!ENTITY fxaccount_remote_error_ACCOUNT_LOCKED 'Account is locked. &formatS1;'>
|
||||
|
||||
<!ENTITY fxaccount_sync_sign_in_error_notification_title2 '&syncBrand.shortName.label; is not connected'>
|
||||
<!-- Localization note: the format string below will be replaced
|
||||
with the Firefox Account's email address. -->
|
||||
<!ENTITY fxaccount_sync_sign_in_error_notification_text2 'Tap to sign in as &formatS;'>
|
||||
|
||||
<!ENTITY fxaccount_sync_finish_migrating_notification_title 'Finish upgrading &syncBrand.shortName.label;?'>
|
||||
<!-- Localization note: the format string below will be replaced
|
||||
with the Firefox Account's email address. -->
|
||||
<!ENTITY fxaccount_sync_finish_migrating_notification_text 'Tap to sign in as &formatS;'>
|
||||
|
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:fillViewport="true" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/update_credentials_view"
|
||||
style="@style/FxAccountMiddle" >
|
||||
|
||||
<LinearLayout style="@style/FxAccountSpacer" />
|
||||
|
||||
<TextView
|
||||
style="@style/FxAccountHeaderItem"
|
||||
android:text="@string/fxaccount_finish_migrating_header" />
|
||||
|
||||
<include layout="@layout/fxaccount_custom_server_view" />
|
||||
|
||||
<include layout="@layout/fxaccount_email_password_view" />
|
||||
|
||||
<TextView
|
||||
style="@style/FxAccountTextItem"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/fxaccount_finish_migrating_description" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/remote_error"
|
||||
style="@style/FxAccountErrorItem" />
|
||||
|
||||
<RelativeLayout style="@style/FxAccountButtonLayout" >
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
style="@style/FxAccountProgress" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
style="@style/FxAccountButton"
|
||||
android:text="@string/fxaccount_finish_migrating_button_label" />
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/forgot_password_link"
|
||||
style="@style/FxAccountLinkifiedItem"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/fxaccount_sign_in_forgot_password" />
|
||||
|
||||
<LinearLayout style="@style/FxAccountSpacer" />
|
||||
|
||||
<ImageView
|
||||
style="@style/FxAccountIcon"
|
||||
android:contentDescription="@string/fxaccount_empty_contentDescription" />
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:fillViewport="true" >
|
||||
|
||||
<LinearLayout style="@style/FxAccountMiddle" >
|
||||
|
||||
<TextView
|
||||
style="@style/FxAccountHeaderItem"
|
||||
android:text="@string/fxaccount_migration_finished_header" >
|
||||
</TextView>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="45dp"
|
||||
android:contentDescription="@string/fxaccount_empty_contentDescription"
|
||||
android:src="@drawable/fxaccount_checkbox" >
|
||||
</ImageView>
|
||||
|
||||
<TextView
|
||||
style="@style/FxAccountTextItem"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:text="@string/fxaccount_migration_finished_description"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
style="@style/FxAccountButton"
|
||||
android:text="@string/fxaccount_back_to_browsing" />
|
||||
|
||||
<LinearLayout style="@style/FxAccountSpacer" />
|
||||
|
||||
<ImageView
|
||||
style="@style/FxAccountIcon"
|
||||
android:contentDescription="@string/fxaccount_empty_contentDescription" />
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<LinearLayout style="@style/RemoteTabsPanelFrame" >
|
||||
|
||||
<TextView
|
||||
style="@style/RemoteTabsPanelItem.TextAppearance.Header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/fxaccount_finish_migrating_header" />
|
||||
|
||||
<TextView
|
||||
style="@style/RemoteTabsPanelItem.TextAppearance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/home_remote_tabs_need_to_finish_migrating" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/remote_tabs_needs_finish_migrating_sign_in"
|
||||
style="@style/RemoteTabsPanelItem.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/fxaccount_finish_migrating_button_label" />
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
@ -54,6 +54,13 @@
|
||||
android:layout="@layout/fxaccount_status_error_preference"
|
||||
android:persistent="false"
|
||||
android:title="@string/fxaccount_status_needs_account_enabled" />
|
||||
<Preference
|
||||
android:editable="false"
|
||||
android:icon="@drawable/fxaccount_sync_error"
|
||||
android:key="needs_finish_migrating"
|
||||
android:layout="@layout/fxaccount_status_error_preference"
|
||||
android:persistent="false"
|
||||
android:title="@string/fxaccount_status_needs_finish_migrating" />
|
||||
|
||||
<Preference
|
||||
android:editable="false"
|
||||
@ -125,6 +132,7 @@
|
||||
<Preference android:key="debug_forget_certificate" />
|
||||
<Preference android:key="debug_require_password" />
|
||||
<Preference android:key="debug_require_upgrade" />
|
||||
<Preference android:key="debug_migrated_from_sync11" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
@ -369,6 +369,7 @@
|
||||
<string name="home_remote_tabs_empty">&home_remote_tabs_empty;</string>
|
||||
<string name="home_remote_tabs_unable_to_connect">&home_remote_tabs_unable_to_connect;</string>
|
||||
<string name="home_remote_tabs_need_to_sign_in">&home_remote_tabs_need_to_sign_in;</string>
|
||||
<string name="home_remote_tabs_need_to_finish_migrating">&home_remote_tabs_need_to_finish_migrating;</string>
|
||||
<string name="home_remote_tabs_trouble_verifying">&home_remote_tabs_trouble_verifying;</string>
|
||||
<string name="home_remote_tabs_need_to_verify">&home_remote_tabs_need_to_verify;</string>
|
||||
<string name="home_remote_tabs_one_hidden_device">&home_remote_tabs_one_hidden_device;</string>
|
||||
|
@ -35,6 +35,9 @@ import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.ClickableSpan;
|
||||
|
||||
public class Utils {
|
||||
|
||||
@ -614,4 +617,26 @@ public class Utils {
|
||||
}
|
||||
return language + "-" + country;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a span with a clickable chunk of text interpolated in.
|
||||
*
|
||||
* @param context Android context.
|
||||
* @param messageId of string containing clickable chunk.
|
||||
* @param clickableId of string to make clickable.
|
||||
* @param clickableSpan to activate on click.
|
||||
* @return Spannable.
|
||||
*/
|
||||
public static Spannable interpolateClickableSpan(Context context, int messageId, int clickableId, ClickableSpan clickableSpan) {
|
||||
// This horrible bit of special-casing is because we want this error message to
|
||||
// contain a clickable, extra chunk of text, but we don't want to pollute
|
||||
// the exception class with Android specifics.
|
||||
final String clickablePart = context.getString(clickableId);
|
||||
final String message = context.getString(messageId, clickablePart);
|
||||
final int clickableStart = message.lastIndexOf(clickablePart);
|
||||
final int clickableEnd = clickableStart + clickablePart.length();
|
||||
final Spannable span = Spannable.Factory.getInstance().newSpannable(message);
|
||||
span.setSpan(clickableSpan, clickableStart, clickableEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return span;
|
||||
}
|
||||
}
|
||||
|
@ -67,6 +67,21 @@
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountFinishMigratingActivity"
|
||||
android:configChanges="locale|layoutDirection"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountMigrationFinishedActivity"
|
||||
android:configChanges="locale|layoutDirection"
|
||||
android:noHistory="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateAccountNotAllowedActivity"
|
||||
|
@ -157,6 +157,9 @@
|
||||
<string name="fxaccount_confirm_account_change_email">&fxaccount_confirm_account_change_email;</string>
|
||||
<string name="fxaccount_confirm_account_verification_link_sent">&fxaccount_confirm_account_verification_link_sent2;</string>
|
||||
<string name="fxaccount_confirm_account_verification_link_not_sent">&fxaccount_confirm_account_verification_link_not_sent2;</string>
|
||||
<string name="fxaccount_resend_unlock_code_button_label">&fxaccount_resend_unlock_code_button_label;</string>
|
||||
<string name="fxaccount_unlock_code_sent">&fxaccount_unlock_code_sent;</string>
|
||||
<string name="fxaccount_unlock_code_not_sent">&fxaccount_unlock_code_not_sent;</string>
|
||||
|
||||
<string name="fxaccount_sign_in_sub_header">&fxaccount_sign_in_sub_header;</string>
|
||||
<string name="fxaccount_sign_in_button_label">&fxaccount_sign_in_button_label;</string>
|
||||
@ -167,10 +170,17 @@
|
||||
<string name="fxaccount_account_verified_sub_header">&fxaccount_account_verified_sub_header;</string>
|
||||
<string name="fxaccount_account_verified_description">&fxaccount_account_verified_description2;</string>
|
||||
|
||||
<string name="fxaccount_migration_finished_header">&fxaccount_migration_finished_header;</string>
|
||||
<string name="fxaccount_migration_finished_description">&fxaccount_account_verified_description2;</string>
|
||||
|
||||
<string name="fxaccount_update_credentials_header">&fxaccount_update_credentials_header;</string>
|
||||
<string name="fxaccount_update_credentials_button_label">&fxaccount_update_credentials_button_label;</string>
|
||||
<string name="fxaccount_update_credentials_unknown_error">&fxaccount_update_credentials_unknown_error;</string>
|
||||
|
||||
<string name="fxaccount_finish_migrating_header">&fxaccount_finish_migrating_header;</string>
|
||||
<string name="fxaccount_finish_migrating_button_label">&fxaccount_finish_migrating_button_label;</string>
|
||||
<string name="fxaccount_finish_migrating_description">&fxaccount_finish_migrating_description;</string>
|
||||
|
||||
<string name="fxaccount_status_activity_label">&syncBrand.shortName.label;</string>
|
||||
<string name="fxaccount_status_header">&fxaccount_status_header2;</string>
|
||||
<string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
|
||||
@ -187,6 +197,7 @@
|
||||
<string name="fxaccount_status_needs_upgrade">&fxaccount_status_needs_upgrade;</string>
|
||||
<string name="fxaccount_status_needs_master_sync_automatically_enabled">&fxaccount_status_needs_master_sync_automatically_enabled;</string>
|
||||
<string name="fxaccount_status_needs_account_enabled">&fxaccount_status_needs_account_enabled;</string>
|
||||
<string name="fxaccount_status_needs_finish_migrating">&fxaccount_status_needs_finish_migrating;</string>
|
||||
<string name="fxaccount_status_bookmarks">&fxaccount_status_bookmarks;</string>
|
||||
<string name="fxaccount_status_history">&fxaccount_status_history;</string>
|
||||
<string name="fxaccount_status_passwords">&fxaccount_status_passwords;</string>
|
||||
@ -210,6 +221,7 @@
|
||||
<string name="fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD">&fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD;</string>
|
||||
<string name="fxaccount_remote_error_UNKNOWN_ERROR">&fxaccount_remote_error_UNKNOWN_ERROR;</string>
|
||||
<string name="fxaccount_remote_error_COULD_NOT_CONNECT">&fxaccount_remote_error_COULD_NOT_CONNECT;</string>
|
||||
<string name="fxaccount_remote_error_ACCOUNT_LOCKED">&fxaccount_remote_error_ACCOUNT_LOCKED;</string>
|
||||
|
||||
<string name="fxaccount_sync_sign_in_error_notification_title">&fxaccount_sync_sign_in_error_notification_title2;</string>
|
||||
<string name="fxaccount_sync_sign_in_error_notification_text">&fxaccount_sync_sign_in_error_notification_text2;</string>
|
||||
@ -219,3 +231,6 @@
|
||||
<string name="fxaccount_remove_account_dialog_message">&fxaccount_remove_account_dialog_message;</string>
|
||||
<string name="fxaccount_remove_account_toast">&fxaccount_remove_account_toast;</string>
|
||||
<string name="fxaccount_remove_account_menu_item">&fxaccount_remove_account_menu_item;</string>
|
||||
|
||||
<string name="fxaccount_sync_finish_migrating_notification_title">&fxaccount_sync_finish_migrating_notification_title;</string>
|
||||
<string name="fxaccount_sync_finish_migrating_notification_text">&fxaccount_sync_finish_migrating_notification_text;</string>
|
||||
|
51
testing/xpcshell/dbg-actors.js
Normal file
51
testing/xpcshell/dbg-actors.js
Normal file
@ -0,0 +1,51 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const { RootActor } = devtools.require("devtools/server/actors/root");
|
||||
const { BrowserTabList } = devtools.require("devtools/server/actors/webbrowser");
|
||||
|
||||
/**
|
||||
* xpcshell-test (XPCST) specific actors.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Construct a root actor appropriate for use in a server running xpcshell
|
||||
* tests. <snip boilerplate> :)
|
||||
*/
|
||||
function createRootActor(connection)
|
||||
{
|
||||
let parameters = {
|
||||
tabList: new XPCSTTabList(connection),
|
||||
globalActorFactories: DebuggerServer.globalActorFactories,
|
||||
onShutdown() {
|
||||
// If the user never switches to the "debugger" tab we might get a
|
||||
// shutdown before we've attached.
|
||||
Services.obs.notifyObservers(null, "xpcshell-test-devtools-shutdown", null);
|
||||
}
|
||||
};
|
||||
return new RootActor(connection, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* A "stub" TabList implementation that provides no tabs.
|
||||
*/
|
||||
|
||||
function XPCSTTabList(connection)
|
||||
{
|
||||
BrowserTabList.call(this, connection);
|
||||
}
|
||||
|
||||
XPCSTTabList.prototype = Object.create(BrowserTabList.prototype);
|
||||
|
||||
XPCSTTabList.prototype.constructor = XPCSTTabList;
|
||||
|
||||
XPCSTTabList.prototype.getList = function() {
|
||||
return Promise.resolve([]);
|
||||
};
|
@ -336,7 +336,101 @@ function _register_modules_protocol_handler() {
|
||||
protocolHandler.setSubstitution("testing-common", modulesURI);
|
||||
}
|
||||
|
||||
function _initDebugging(port) {
|
||||
let prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
|
||||
// Always allow remote debugging.
|
||||
prefs.setBoolPref("devtools.debugger.remote-enabled", true);
|
||||
|
||||
// for debugging-the-debugging, let an env var cause log spew.
|
||||
let env = Components.classes["@mozilla.org/process/environment;1"]
|
||||
.getService(Components.interfaces.nsIEnvironment);
|
||||
if (env.get("DEVTOOLS_DEBUGGER_LOG")) {
|
||||
prefs.setBoolPref("devtools.debugger.log", true);
|
||||
}
|
||||
if (env.get("DEVTOOLS_DEBUGGER_LOG_VERBOSE")) {
|
||||
prefs.setBoolPref("devtools.debugger.log.verbose", true);
|
||||
}
|
||||
|
||||
let {DebuggerServer} = Components.utils.import('resource://gre/modules/devtools/dbg-server.jsm', {});
|
||||
DebuggerServer.init(() => true);
|
||||
DebuggerServer.addBrowserActors();
|
||||
DebuggerServer.addActors("resource://testing-common/dbg-actors.js");
|
||||
|
||||
// An observer notification that tells us when we can "resume" script
|
||||
// execution.
|
||||
let obsSvc = Components.classes["@mozilla.org/observer-service;1"].
|
||||
getService(Components.interfaces.nsIObserverService);
|
||||
let initialized = false;
|
||||
|
||||
const TOPICS = ["devtools-thread-resumed", "xpcshell-test-devtools-shutdown"];
|
||||
let observe = function(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "devtools-thread-resumed":
|
||||
// Exceptions in here aren't reported and block the debugger from
|
||||
// resuming, so...
|
||||
try {
|
||||
// Add a breakpoint for the first line in our test files.
|
||||
let threadActor = subject.wrappedJSObject;
|
||||
let location = { line: 1 };
|
||||
for (let file of _TEST_FILE) {
|
||||
let sourceActor = threadActor.sources.source({originalUrl: file});
|
||||
sourceActor.createAndStoreBreakpoint(location);
|
||||
}
|
||||
} catch (ex) {
|
||||
do_print("Failed to initialize breakpoints: " + ex + "\n" + ex.stack);
|
||||
}
|
||||
break;
|
||||
case "xpcshell-test-devtools-shutdown":
|
||||
// the debugger has shutdown before we got a resume event - nothing
|
||||
// special to do here.
|
||||
break;
|
||||
}
|
||||
initialized = true;
|
||||
for (let topicToRemove of TOPICS) {
|
||||
obsSvc.removeObserver(observe, topicToRemove);
|
||||
}
|
||||
};
|
||||
|
||||
for (let topic of TOPICS) {
|
||||
obsSvc.addObserver(observe, topic, false);
|
||||
}
|
||||
|
||||
do_print("");
|
||||
do_print("*******************************************************************");
|
||||
do_print("Waiting for the debugger to connect on port " + port)
|
||||
do_print("")
|
||||
do_print("To connect the debugger, open a Firefox instance, select 'Connect'");
|
||||
do_print("from the Developer menu and specify the port as " + port);
|
||||
do_print("*******************************************************************");
|
||||
do_print("")
|
||||
|
||||
DebuggerServer.openListener(port);
|
||||
|
||||
// spin an event loop until the debugger connects.
|
||||
let thr = Components.classes["@mozilla.org/thread-manager;1"]
|
||||
.getService().currentThread;
|
||||
while (!initialized) {
|
||||
do_print("Still waiting for debugger to connect...");
|
||||
thr.processNextEvent(true);
|
||||
}
|
||||
// NOTE: if you want to debug the harness itself, you can now add a 'debugger'
|
||||
// statement anywhere and it will stop - but we've already added a breakpoint
|
||||
// for the first line of the test scripts, so we just continue...
|
||||
do_print("Debugger connected, starting test execution");
|
||||
}
|
||||
|
||||
function _execute_test() {
|
||||
// _JSDEBUGGER_PORT is dynamically defined by <runxpcshelltests.py>.
|
||||
if (_JSDEBUGGER_PORT) {
|
||||
try {
|
||||
_initDebugging(_JSDEBUGGER_PORT);
|
||||
} catch (ex) {
|
||||
do_print("Failed to initialize debugging: " + ex + "\n" + ex.stack);
|
||||
}
|
||||
}
|
||||
|
||||
_register_protocol_handlers();
|
||||
|
||||
// Override idle service by default.
|
||||
@ -1072,6 +1166,8 @@ function do_load_child_test_harness()
|
||||
+ "const _HEAD_FILES=" + uneval(_HEAD_FILES) + "; "
|
||||
+ "const _TAIL_FILES=" + uneval(_TAIL_FILES) + "; "
|
||||
+ "const _TEST_NAME=" + uneval(_TEST_NAME) + "; "
|
||||
// We'll need more magic to get the debugger working in the child
|
||||
+ "const _JSDEBUGGER_PORT=0; "
|
||||
+ "const _XPCSHELL_PROCESS='child';";
|
||||
|
||||
if (this._TESTING_MODULES_DIR) {
|
||||
|
@ -65,6 +65,7 @@ class XPCShellRunner(MozbuildObject):
|
||||
def run_test(self, test_paths, interactive=False,
|
||||
keep_going=False, sequential=False, shuffle=False,
|
||||
debugger=None, debuggerArgs=None, debuggerInteractive=None,
|
||||
jsDebugger=False, jsDebuggerPort=None,
|
||||
rerun_failures=False, test_objects=None, verbose=False,
|
||||
log=None,
|
||||
# ignore parameters from other platforms' options
|
||||
@ -83,6 +84,7 @@ class XPCShellRunner(MozbuildObject):
|
||||
keep_going=keep_going, shuffle=shuffle, sequential=sequential,
|
||||
debugger=debugger, debuggerArgs=debuggerArgs,
|
||||
debuggerInteractive=debuggerInteractive,
|
||||
jsDebugger=jsDebugger, jsDebuggerPort=jsDebuggerPort,
|
||||
rerun_failures=rerun_failures,
|
||||
verbose=verbose, log=log)
|
||||
return
|
||||
@ -113,6 +115,8 @@ class XPCShellRunner(MozbuildObject):
|
||||
'debugger': debugger,
|
||||
'debuggerArgs': debuggerArgs,
|
||||
'debuggerInteractive': debuggerInteractive,
|
||||
'jsDebugger': jsDebugger,
|
||||
'jsDebuggerPort': jsDebuggerPort,
|
||||
'rerun_failures': rerun_failures,
|
||||
'manifest': manifest,
|
||||
'verbose': verbose,
|
||||
@ -125,6 +129,7 @@ class XPCShellRunner(MozbuildObject):
|
||||
test_path=None, shuffle=False, interactive=False,
|
||||
keep_going=False, sequential=False,
|
||||
debugger=None, debuggerArgs=None, debuggerInteractive=None,
|
||||
jsDebugger=False, jsDebuggerPort=None,
|
||||
rerun_failures=False, verbose=False, log=None):
|
||||
|
||||
# Obtain a reference to the xpcshell test runner.
|
||||
@ -161,6 +166,8 @@ class XPCShellRunner(MozbuildObject):
|
||||
'debugger': debugger,
|
||||
'debuggerArgs': debuggerArgs,
|
||||
'debuggerInteractive': debuggerInteractive,
|
||||
'jsDebugger': jsDebugger,
|
||||
'jsDebuggerPort': jsDebuggerPort,
|
||||
}
|
||||
|
||||
if test_path is not None:
|
||||
@ -417,6 +424,13 @@ class MachCommands(MachCommandBase):
|
||||
dest = "debuggerInteractive",
|
||||
help = "prevents the test harness from redirecting "
|
||||
"stdout and stderr for interactive debuggers")
|
||||
@CommandArgument("--jsdebugger", dest="jsDebugger", action="store_true",
|
||||
help="Waits for a devtools JS debugger to connect before "
|
||||
"starting the test.")
|
||||
@CommandArgument("--jsdebugger-port", dest="jsDebuggerPort",
|
||||
type=int, default=6000,
|
||||
help="The port to listen on for a debugger connection if "
|
||||
"--jsdebugger is specified (default=6000).")
|
||||
@CommandArgument('--interactive', '-i', action='store_true',
|
||||
help='Open an xpcshell prompt before running tests.')
|
||||
@CommandArgument('--keep-going', '-k', action='store_true',
|
||||
|
@ -9,3 +9,7 @@ TEST_DIRS += ['example']
|
||||
PYTHON_UNIT_TESTS += [
|
||||
'selftest.py',
|
||||
]
|
||||
|
||||
TESTING_JS_MODULES += [
|
||||
'dbg-actors.js',
|
||||
]
|
||||
|
@ -19,7 +19,7 @@ import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from collections import deque
|
||||
from collections import deque, namedtuple
|
||||
from distutils import dir_util
|
||||
from multiprocessing import cpu_count
|
||||
from optparse import OptionParser
|
||||
@ -111,6 +111,7 @@ class XPCShellTestThread(Thread):
|
||||
self.xrePath = kwargs.get('xrePath')
|
||||
self.testingModulesDir = kwargs.get('testingModulesDir')
|
||||
self.debuggerInfo = kwargs.get('debuggerInfo')
|
||||
self.jsDebuggerInfo = kwargs.get('jsDebuggerInfo')
|
||||
self.pluginsPath = kwargs.get('pluginsPath')
|
||||
self.httpdManifest = kwargs.get('httpdManifest')
|
||||
self.httpdJSPath = kwargs.get('httpdJSPath')
|
||||
@ -366,10 +367,15 @@ class XPCShellTestThread(Thread):
|
||||
for f in headfiles])
|
||||
cmdT = ", ".join(['"' + f.replace('\\', '/') + '"'
|
||||
for f in tailfiles])
|
||||
|
||||
dbgport = 0 if self.jsDebuggerInfo is None else self.jsDebuggerInfo.port
|
||||
|
||||
return xpcscmd + \
|
||||
['-e', 'const _SERVER_ADDR = "localhost"',
|
||||
'-e', 'const _HEAD_FILES = [%s];' % cmdH,
|
||||
'-e', 'const _TAIL_FILES = [%s];' % cmdT]
|
||||
'-e', 'const _TAIL_FILES = [%s];' % cmdT,
|
||||
'-e', 'const _JSDEBUGGER_PORT = %d;' % dbgport,
|
||||
]
|
||||
|
||||
def getHeadAndTailFiles(self, test_object):
|
||||
"""Obtain the list of head and tail files.
|
||||
@ -632,7 +638,7 @@ class XPCShellTestThread(Thread):
|
||||
testTimeoutInterval *= int(self.test_object['requesttimeoutfactor'])
|
||||
|
||||
testTimer = None
|
||||
if not self.interactive and not self.debuggerInfo:
|
||||
if not self.interactive and not self.debuggerInfo and not self.jsDebuggerInfo:
|
||||
testTimer = Timer(testTimeoutInterval, lambda: self.testTimeout(proc))
|
||||
testTimer.start()
|
||||
|
||||
@ -1004,7 +1010,8 @@ class XPCShellTests(object):
|
||||
profileName=None, mozInfo=None, sequential=False, shuffle=False,
|
||||
testsRootDir=None, testingModulesDir=None, pluginsPath=None,
|
||||
testClass=XPCShellTestThread, failureManifest=None,
|
||||
log=None, stream=None, **otherOptions):
|
||||
log=None, stream=None, jsDebugger=False, jsDebuggerPort=0,
|
||||
**otherOptions):
|
||||
"""Run xpcshell tests.
|
||||
|
||||
|xpcshell|, is the xpcshell executable to use to run the tests.
|
||||
@ -1075,6 +1082,12 @@ class XPCShellTests(object):
|
||||
if debugger:
|
||||
self.debuggerInfo = mozdebug.get_debugger_info(debugger, debuggerArgs, debuggerInteractive)
|
||||
|
||||
self.jsDebuggerInfo = None
|
||||
if jsDebugger:
|
||||
# A namedtuple let's us keep .port instead of ['port']
|
||||
JSDebuggerInfo = namedtuple('JSDebuggerInfo', ['port'])
|
||||
self.jsDebuggerInfo = JSDebuggerInfo(port=jsDebuggerPort)
|
||||
|
||||
self.xpcshell = xpcshell
|
||||
self.xrePath = xrePath
|
||||
self.appPath = appPath
|
||||
@ -1161,6 +1174,7 @@ class XPCShellTests(object):
|
||||
'xrePath': self.xrePath,
|
||||
'testingModulesDir': self.testingModulesDir,
|
||||
'debuggerInfo': self.debuggerInfo,
|
||||
'jsDebuggerInfo': self.jsDebuggerInfo,
|
||||
'pluginsPath': self.pluginsPath,
|
||||
'httpdManifest': self.httpdManifest,
|
||||
'httpdJSPath': self.httpdJSPath,
|
||||
@ -1190,6 +1204,13 @@ class XPCShellTests(object):
|
||||
if self.debuggerInfo.interactive:
|
||||
signal.signal(signal.SIGINT, lambda signum, frame: None)
|
||||
|
||||
if self.jsDebuggerInfo:
|
||||
# The js debugger magic needs more work to do the right thing
|
||||
# if debugging multiple files.
|
||||
if len(self.alltests) != 1:
|
||||
self.log.error("Error: --jsdebugger can only be used with a single test!")
|
||||
return False
|
||||
|
||||
# create a queue of all tests that will run
|
||||
tests_queue = deque()
|
||||
# also a list for the tests that need to be run sequentially
|
||||
@ -1434,6 +1455,13 @@ class XPCShellOptions(OptionParser):
|
||||
action = "store_true", dest = "debuggerInteractive",
|
||||
help = "prevents the test harness from redirecting "
|
||||
"stdout and stderr for interactive debuggers")
|
||||
self.add_option("--jsdebugger", dest="jsDebugger", action="store_true",
|
||||
help="Waits for a devtools JS debugger to connect before "
|
||||
"starting the test.")
|
||||
self.add_option("--jsdebugger-port", type="int", dest="jsDebuggerPort",
|
||||
default=6000,
|
||||
help="The port to listen on for a debugger connection if "
|
||||
"--jsdebugger is specified.")
|
||||
|
||||
def main():
|
||||
parser = XPCShellOptions()
|
||||
|
@ -602,6 +602,9 @@ function ThreadActor(aParent, aGlobal)
|
||||
this.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
|
||||
this.onDebuggerStatement = this.onDebuggerStatement.bind(this);
|
||||
this.onNewScript = this.onNewScript.bind(this);
|
||||
// Set a wrappedJSObject property so |this| can be sent via the observer svc
|
||||
// for the xpcshell harness.
|
||||
this.wrappedJSObject = this;
|
||||
}
|
||||
|
||||
ThreadActor.prototype = {
|
||||
@ -1178,6 +1181,11 @@ ThreadActor.prototype = {
|
||||
|
||||
let packet = this._resumed();
|
||||
this._popThreadPause();
|
||||
// Tell anyone who cares of the resume (as of now, that's the xpcshell
|
||||
// harness)
|
||||
if (Services.obs) {
|
||||
Services.obs.notifyObservers(this, "devtools-thread-resumed", null);
|
||||
}
|
||||
return packet;
|
||||
}, error => {
|
||||
return error instanceof Error
|
||||
@ -1322,7 +1330,7 @@ ThreadActor.prototype = {
|
||||
for (let line = 0, n = offsets.length; line < n; line++) {
|
||||
if (offsets[line]) {
|
||||
let location = { line: line };
|
||||
let resp = sourceActor._createAndStoreBreakpoint(location);
|
||||
let resp = sourceActor.createAndStoreBreakpoint(location);
|
||||
dbg_assert(!resp.actualLocation, "No actualLocation should be returned");
|
||||
if (resp.error) {
|
||||
reportError(new Error("Unable to set breakpoint on event listener"));
|
||||
@ -2516,11 +2524,10 @@ SourceActor.prototype = {
|
||||
let sourceFetched = fetch(this.url, { loadFromCache: !this.source });
|
||||
|
||||
// Record the contentType we just learned during fetching
|
||||
sourceFetched.then(({ contentType }) => {
|
||||
this._contentType = contentType;
|
||||
return sourceFetched.then(result => {
|
||||
this._contentType = result.contentType;
|
||||
return result;
|
||||
});
|
||||
|
||||
return sourceFetched;
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -2848,7 +2855,7 @@ SourceActor.prototype = {
|
||||
|
||||
_createBreakpoint: function(loc, originalLoc, condition) {
|
||||
return resolve(null).then(() => {
|
||||
return this._createAndStoreBreakpoint({
|
||||
return this.createAndStoreBreakpoint({
|
||||
line: loc.line,
|
||||
column: loc.column,
|
||||
condition: condition
|
||||
@ -2915,12 +2922,14 @@ SourceActor.prototype = {
|
||||
* Create a breakpoint at the specified location and store it in the
|
||||
* cache. Takes ownership of `aRequest`. This is the
|
||||
* generated location if this source is sourcemapped.
|
||||
* Used by the XPCShell test harness to set breakpoints in a script before
|
||||
* it has loaded.
|
||||
*
|
||||
* @param Object aRequest
|
||||
* An object of the form { line[, column, condition] }. The
|
||||
* location is in the generated source, if sourcemapped.
|
||||
*/
|
||||
_createAndStoreBreakpoint: function (aRequest) {
|
||||
createAndStoreBreakpoint: function (aRequest) {
|
||||
let bp = update({}, aRequest, { source: this.form() });
|
||||
this.breakpointStore.addBreakpoint(bp);
|
||||
return this._setBreakpoint(aRequest);
|
||||
|
@ -13,10 +13,19 @@ const { Cc, Ci } = require("chrome");
|
||||
Object.defineProperty(this, "addonManager", {
|
||||
get: (function () {
|
||||
let cached;
|
||||
return () => cached
|
||||
? cached
|
||||
: (cached = Cc["@mozilla.org/addons/integration;1"]
|
||||
.getService(Ci.amIAddonManager))
|
||||
return () => {
|
||||
if (cached === undefined) {
|
||||
// catch errors as the addonManager might not exist in this environment
|
||||
// (eg, xpcshell)
|
||||
try {
|
||||
cached = Cc["@mozilla.org/addons/integration;1"]
|
||||
.getService(Ci.amIAddonManager);
|
||||
} catch (ex) {
|
||||
cached = null;
|
||||
}
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
}())
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user