merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2016-07-12 10:57:34 +02:00
commit 8262976cf1
40 changed files with 539 additions and 921 deletions

View File

@ -6848,33 +6848,31 @@ var gIdentityHandler = {
this._identityBox.classList.add("mixedActiveBlocked");
}
// If it's identified, then we can populate the dialog with credentials
let iData = this.getIdentityData();
tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
[iData.caOrg]);
icon_label = iData.subjectOrg;
if (iData.country)
icon_country_label = "(" + iData.country + ")";
if (!this._isCertUserOverridden) {
// If it's identified, then we can populate the dialog with credentials
let iData = this.getIdentityData();
tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
[iData.caOrg]);
icon_label = iData.subjectOrg;
if (iData.country)
icon_country_label = "(" + iData.country + ")";
// If the organization name starts with an RTL character, then
// swap the positions of the organization and country code labels.
// The Unicode ranges reflect the definition of the UCS2_CHAR_IS_BIDI
// macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets
// fixed, this test should be replaced by one adhering to the
// Unicode Bidirectional Algorithm proper (at the paragraph level).
icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ?
"rtl" : "ltr";
// If the organization name starts with an RTL character, then
// swap the positions of the organization and country code labels.
// The Unicode ranges reflect the definition of the UCS2_CHAR_IS_BIDI
// macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets
// fixed, this test should be replaced by one adhering to the
// Unicode Bidirectional Algorithm proper (at the paragraph level).
icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ?
"rtl" : "ltr";
}
} else if (this._uriHasHost && this._isSecure) {
this._identityBox.className = "verifiedDomain";
if (this._isMixedActiveContentBlocked) {
this._identityBox.classList.add("mixedActiveBlocked");
}
if (this._isCertUserOverridden) {
this._identityBox.classList.add("certUserOverridden");
// Cert is trusted because of a security exception, verifier is a special string.
tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
} else {
if (!this._isCertUserOverridden) {
// It's a normal cert, verifier is the CA Org.
tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
[this.getIdentityData().caOrg]);
@ -6900,6 +6898,12 @@ var gIdentityHandler = {
tooltip = gNavigatorBundle.getString("identity.unknown.tooltip");
}
if (this._isCertUserOverridden) {
this._identityBox.classList.add("certUserOverridden");
// Cert is trusted because of a security exception, verifier is a special string.
tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
}
if (SitePermissions.hasGrantedPermissions(this._uri)) {
this._identityBox.classList.add("grantedPermissions");
}
@ -7063,7 +7067,7 @@ var gIdentityHandler = {
}
// Fill in the CA name if we have a valid TLS certificate.
if (this._isSecure) {
if (this._isSecure || this._isCertUserOverridden) {
verifier = this._identityBox.tooltipText;
}

View File

@ -328,6 +328,7 @@ skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (
subsuite = clipboard
[browser_minimize.js]
[browser_misused_characters_in_strings.js]
[browser_mixed_content_cert_override.js]
[browser_mixedcontent_securityflags.js]
tags = mcb
[browser_offlineQuotaNotification.js]

View File

@ -9,49 +9,15 @@
// using the button contained therein to load the certificate exception
// dialog, using that to add an exception, and finally successfully visiting
// the site, including showing the right identity box and control center icons.
function test() {
waitForExplicitFinish();
whenNewTabLoaded(window, loadBadCertPage);
}
// Attempt to load https://expired.example.com (which has an expired cert).
function loadBadCertPage() {
Services.obs.addObserver(certExceptionDialogObserver,
"cert-exception-ui-ready", false);
let startedLoad = BrowserTestUtils.loadURI(gBrowser.selectedBrowser,
"https://expired.example.com");
startedLoad.then(() => promiseErrorPageLoaded(gBrowser.selectedBrowser)).then(function() {
ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
content.document.getElementById("exceptionDialogButton").click();
});
});
}
// When the certificate exception dialog has opened, click the button to add
// an exception.
const EXCEPTION_DIALOG_URI = "chrome://pippki/content/exceptionDialog.xul";
var certExceptionDialogObserver = {
observe: function(aSubject, aTopic, aData) {
if (aTopic == "cert-exception-ui-ready") {
Services.obs.removeObserver(this, "cert-exception-ui-ready");
let certExceptionDialog = getDialog(EXCEPTION_DIALOG_URI);
ok(certExceptionDialog, "found exception dialog");
executeSoon(function() {
BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(realPageLoaded);
certExceptionDialog.documentElement.getButton("extra1").click();
});
}
}
};
// Finally, we should successfully load https://expired.example.com.
function realPageLoaded() {
add_task(function* () {
yield BrowserTestUtils.openNewForegroundTab(gBrowser);
yield loadBadCertPage("https://expired.example.com");
checkControlPanelIcons();
let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
.getService(Ci.nsICertOverrideService);
certOverrideService.clearValidityOverride("expired.example.com", -1);
BrowserTestUtils.removeTab(gBrowser.selectedTab).then(finish);
};
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
// Check for the correct icons in the identity box and control center.
function checkControlPanelIcons() {
@ -82,30 +48,3 @@ function checkControlPanelIcons() {
gIdentityHandler._identityPopup.hidden = true;
}
// Utility function to get a handle on the certificate exception dialog.
// Modified from toolkit/components/passwordmgr/test/prompt_common.js
function getDialog(aLocation) {
let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
let enumerator = wm.getXULWindowEnumerator(null);
while (enumerator.hasMoreElements()) {
let win = enumerator.getNext();
let windowDocShell = win.QueryInterface(Ci.nsIXULWindow).docShell;
let containedDocShells = windowDocShell.getDocShellEnumerator(
Ci.nsIDocShellTreeItem.typeChrome,
Ci.nsIDocShell.ENUMERATE_FORWARDS);
while (containedDocShells.hasMoreElements()) {
// Get the corresponding document for this docshell
let childDocShell = containedDocShells.getNext();
let childDoc = childDocShell.QueryInterface(Ci.nsIDocShell)
.contentViewer
.DOMDocument;
if (childDoc.location.href == aLocation) {
return childDoc;
}
}
}
}

View File

@ -0,0 +1,51 @@
/*
* Bug 1253771 - check mixed content blocking in combination with overriden certificates
*/
"use strict";
const MIXED_CONTENT_URL = "https://self-signed.example.com/browser/browser/base/content/test/general/test-mixedcontent-securityerrors.html";
function getConnectionState() {
return document.getElementById("identity-popup").getAttribute("connection");
}
function getPopupContentVerifier() {
return document.getElementById("identity-popup-content-verifier");
}
function getConnectionIcon() {
return window.getComputedStyle(document.getElementById("connection-icon")).listStyleImage;
}
function checkIdentityPopup(icon) {
gIdentityHandler.refreshIdentityPopup();
is(getConnectionIcon(), `url("chrome://browser/skin/${icon}.svg")`);
is(getConnectionState(), "secure-cert-user-overridden");
isnot(getPopupContentVerifier().style.display, "none", "Overridden certificate warning is shown");
ok(getPopupContentVerifier().textContent.includes("security exception"), "Text shows overridden certificate warning.");
}
add_task(function* () {
// check that a warning is shown when loading a page with mixed content and an overridden certificate
yield loadBadCertPage(MIXED_CONTENT_URL);
checkIdentityPopup("identity-mixed-passive-loaded");
// check that the crossed out icon is shown when disabling mixed content protection
gIdentityHandler.disableMixedContentProtection();
yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
checkIdentityPopup("identity-mixed-active-loaded");
// check that a warning is shown even without mixed content
yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://self-signed.example.com");
yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
checkIdentityPopup("identity-mixed-passive-loaded");
// remove cert exception
let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
.getService(Ci.nsICertOverrideService);
certOverrideService.clearValidityOverride("self-signed.example.com", -1);
});

View File

@ -1100,3 +1100,62 @@ function promiseErrorPageLoaded(browser) {
}, false, true);
});
}
function* loadBadCertPage(url) {
const EXCEPTION_DIALOG_URI = "chrome://pippki/content/exceptionDialog.xul";
let exceptionDialogResolved = new Promise(function(resolve) {
// When the certificate exception dialog has opened, click the button to add
// an exception.
let certExceptionDialogObserver = {
observe: function(aSubject, aTopic, aData) {
if (aTopic == "cert-exception-ui-ready") {
Services.obs.removeObserver(this, "cert-exception-ui-ready");
let certExceptionDialog = getCertExceptionDialog(EXCEPTION_DIALOG_URI);
ok(certExceptionDialog, "found exception dialog");
executeSoon(function() {
certExceptionDialog.documentElement.getButton("extra1").click();
resolve();
});
}
}
};
Services.obs.addObserver(certExceptionDialogObserver,
"cert-exception-ui-ready", false);
});
yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser, url);
yield promiseErrorPageLoaded(gBrowser.selectedBrowser);
yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
content.document.getElementById("exceptionDialogButton").click();
});
yield exceptionDialogResolved;
yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
}
// Utility function to get a handle on the certificate exception dialog.
// Modified from toolkit/components/passwordmgr/test/prompt_common.js
function getCertExceptionDialog(aLocation) {
let enumerator = Services.wm.getXULWindowEnumerator(null);
while (enumerator.hasMoreElements()) {
let win = enumerator.getNext();
let windowDocShell = win.QueryInterface(Ci.nsIXULWindow).docShell;
let containedDocShells = windowDocShell.getDocShellEnumerator(
Ci.nsIDocShellTreeItem.typeChrome,
Ci.nsIDocShell.ENUMERATE_FORWARDS);
while (containedDocShells.hasMoreElements()) {
// Get the corresponding document for this docshell
let childDocShell = containedDocShells.getNext();
let childDoc = childDocShell.QueryInterface(Ci.nsIDocShell)
.contentViewer
.DOMDocument;
if (childDoc.location.href == aLocation) {
return childDoc;
}
}
}
}

View File

@ -112,6 +112,92 @@ add_task(function* tabsSendMessageReply() {
yield extension.unload();
});
add_task(function* tabsSendHidden() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["tabs"],
"content_scripts": [{
"matches": ["http://example.com/content*"],
"js": ["content-script.js"],
"run_at": "document_start",
}],
},
background: function() {
let resolveContent;
browser.runtime.onMessage.addListener((msg, sender) => {
if (msg[0] == "content-ready") {
resolveContent(msg[1]);
}
});
let awaitContent = url => {
return new Promise(resolve => {
resolveContent = resolve;
}).then(result => {
browser.test.assertEq(url, result, "Expected content script URL");
});
};
const URL1 = "http://example.com/content1.html";
const URL2 = "http://example.com/content2.html";
browser.tabs.create({url: URL1}).then(tab => {
return awaitContent(URL1).then(() => {
return browser.tabs.sendMessage(tab.id, URL1);
}).then(url => {
browser.test.assertEq(URL1, url, "Should get response from expected content window");
return browser.tabs.update(tab.id, {url: URL2});
}).then(() => {
return awaitContent(URL2);
}).then(() => {
return browser.tabs.sendMessage(tab.id, URL2);
}).then(url => {
browser.test.assertEq(URL2, url, "Should get response from expected content window");
// Repeat once just to be sure the first message was processed by all
// listeners before we exit the test.
return browser.tabs.sendMessage(tab.id, URL2);
}).then(url => {
browser.test.assertEq(URL2, url, "Should get response from expected content window");
return browser.tabs.remove(tab.id);
});
}).then(() => {
browser.test.notifyPass("contentscript-bfcache-window");
}).catch(error => {
browser.test.fail(`Error: ${error} :: ${error.stack}`);
browser.test.notifyFail("contentscript-bfcache-window");
});
},
files: {
"content-script.js": function() {
// Store this in a local variable to make sure we don't touch any
// properties of the possibly-hidden content window.
let href = window.location.href;
browser.runtime.onMessage.addListener((msg, sender) => {
browser.test.assertEq(href, msg, "Should be in the expected content window");
return Promise.resolve(href);
});
browser.runtime.sendMessage(["content-ready", href]);
},
},
});
yield extension.startup();
yield extension.awaitFinish("contentscript-bfcache-window");
yield extension.unload();
});
add_task(function* tabsSendMessageNoExceptionOnNonExistentTab() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -141,6 +141,11 @@
visibility: visible;
}
#urlbar[pageproxystate="valid"] > #identity-box.certUserOverridden > #connection-icon {
list-style-image: url(chrome://browser/skin/identity-mixed-passive-loaded.svg);
visibility: visible;
}
#urlbar[pageproxystate="valid"] > #identity-box.insecureLoginForms > #connection-icon,
#urlbar[pageproxystate="valid"] > #identity-box.mixedActiveContent > #connection-icon {
list-style-image: url(chrome://browser/skin/identity-mixed-active-loaded.svg);
@ -153,8 +158,3 @@
list-style-image: url(chrome://browser/skin/identity-mixed-passive-loaded.svg);
visibility: visible;
}
#urlbar[pageproxystate="valid"] > #identity-box.certUserOverridden > #connection-icon {
list-style-image: url(chrome://browser/skin/identity-mixed-passive-loaded.svg);
visibility: visible;
}

View File

@ -118,17 +118,18 @@ public:
// Bug 1182551 - before changing the security state to broken, check
// that the root is actually secure.
if (mRootHasSecureConnection) {
// reset state security flag
state = state >> 4 << 4;
// set state security flag to broken, since there is mixed content
state |= nsIWebProgressListener::STATE_IS_BROKEN;
// If mixed display content is loaded, make sure to include that in the state.
if (rootDoc->GetHasMixedDisplayContentLoaded()) {
eventSink->OnSecurityChange(mContext,
(nsIWebProgressListener::STATE_IS_BROKEN |
nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT |
nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
} else {
eventSink->OnSecurityChange(mContext,
(nsIWebProgressListener::STATE_IS_BROKEN |
nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
state |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
}
eventSink->OnSecurityChange(mContext,
(state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
} else {
// root not secure, mixed active content loaded in an https subframe
if (NS_SUCCEEDED(stateRV)) {
@ -149,16 +150,18 @@ public:
// Bug 1182551 - before changing the security state to broken, check
// that the root is actually secure.
if (mRootHasSecureConnection) {
// If mixed active content is loaded, make sure to include that in the state.
// reset state security flag
state = state >> 4 << 4;
// set state security flag to broken, since there is mixed content
state |= nsIWebProgressListener::STATE_IS_BROKEN;
// If mixed active content is loaded, make sure to include that in the state.
if (rootDoc->GetHasMixedActiveContentLoaded()) {
eventSink->OnSecurityChange(mContext,
(nsIWebProgressListener::STATE_IS_BROKEN |
nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT |
nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
} else {
eventSink->OnSecurityChange(mContext, (nsIWebProgressListener::STATE_IS_BROKEN |
nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
state |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
}
eventSink->OnSecurityChange(mContext,
(state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
} else {
// root not secure, mixed display content loaded in an https subframe
if (NS_SUCCEEDED(stateRV)) {
@ -841,23 +844,24 @@ nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect,
rootDoc->SetHasMixedDisplayContentLoaded(true);
if (rootHasSecureConnection) {
// reset state security flag
state = state >> 4 << 4;
// set state security flag to broken, since there is mixed content
state |= nsIWebProgressListener::STATE_IS_BROKEN;
// If mixed active content is loaded, make sure to include that in the state.
if (rootDoc->GetHasMixedActiveContentLoaded()) {
// If mixed active content is loaded, make sure to include that in the state.
eventSink->OnSecurityChange(aRequestingContext,
(nsIWebProgressListener::STATE_IS_BROKEN |
nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT |
nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
} else {
eventSink->OnSecurityChange(aRequestingContext,
(nsIWebProgressListener::STATE_IS_BROKEN |
nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
state |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
}
eventSink->OnSecurityChange(aRequestingContext,
(state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
} else {
// User has overriden the pref and the root is not https;
// mixed display content was allowed on an https subframe.
if (NS_SUCCEEDED(stateRV)) {
eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
}
}
}
} else {
*aDecision = nsIContentPolicy::REJECT_REQUEST;
@ -882,18 +886,19 @@ nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect,
rootDoc->SetHasMixedActiveContentLoaded(true);
if (rootHasSecureConnection) {
// User has decided to override the pref and the root is https, so change the Security State.
// reset state security flag
state = state >> 4 << 4;
// set state security flag to broken, since there is mixed content
state |= nsIWebProgressListener::STATE_IS_BROKEN;
// If mixed display content is loaded, make sure to include that in the state.
if (rootDoc->GetHasMixedDisplayContentLoaded()) {
// If mixed display content is loaded, make sure to include that in the state.
eventSink->OnSecurityChange(aRequestingContext,
(nsIWebProgressListener::STATE_IS_BROKEN |
nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT |
nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
} else {
eventSink->OnSecurityChange(aRequestingContext,
(nsIWebProgressListener::STATE_IS_BROKEN |
nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
state |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
}
eventSink->OnSecurityChange(aRequestingContext,
(state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
return NS_OK;
} else {
// User has already overriden the pref and the root is not https;

View File

@ -17,16 +17,6 @@
(potentially) of the push feature. -->
#include GcmAndroidManifest_permissions.xml.in
<!-- A signature level permission specific to each Firefox version (Android
package name, e.g., org.mozilla.firefox). Use this permission to
broadcast securely within a single Firefox version. This needs to
agree with GlobalConstants.PER_ANDROID_PACKAGE_PERMISSION. -->
<permission
android:name="@ANDROID_PACKAGE_NAME@.permission.PER_ANDROID_PACKAGE"
android:protectionLevel="signature"/>
<uses-permission android:name="@ANDROID_PACKAGE_NAME@.permission.PER_ANDROID_PACKAGE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

View File

@ -814,7 +814,6 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
'browserid/verifier/BrowserIDVerifierException.java',
'browserid/VerifyingPublicKey.java',
'fxa/AccountLoader.java',
'fxa/AccountLoaderNative.java',
'fxa/activities/CustomColorPreference.java',
'fxa/activities/FxAccountAbstractActivity.java',
'fxa/activities/FxAccountConfirmAccountActivityWeb.java',
@ -848,7 +847,6 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
'fxa/login/State.java',
'fxa/login/StateFactory.java',
'fxa/login/TokensAndKeysState.java',
'fxa/receivers/FxAccountDeletedReceiver.java',
'fxa/receivers/FxAccountDeletedService.java',
'fxa/receivers/FxAccountUpgradeReceiver.java',
'fxa/sync/FxAccountNotificationManager.java',

View File

@ -289,8 +289,6 @@ public class BrowserApp extends GeckoApp
private SharedPreferencesHelper mSharedPreferencesHelper;
private OrderedBroadcastHelper mOrderedBroadcastHelper;
private ReadingListHelper mReadingListHelper;
private AccountsHelper mAccountsHelper;
@ -699,7 +697,6 @@ public class BrowserApp extends GeckoApp
JavaAddonManager.getInstance().init(appContext);
mSharedPreferencesHelper = new SharedPreferencesHelper(appContext);
mOrderedBroadcastHelper = new OrderedBroadcastHelper(appContext);
mReadingListHelper = new ReadingListHelper(appContext, profile);
mAccountsHelper = new AccountsHelper(appContext, profile);
@ -1364,11 +1361,6 @@ public class BrowserApp extends GeckoApp
mSharedPreferencesHelper = null;
}
if (mOrderedBroadcastHelper != null) {
mOrderedBroadcastHelper.uninit();
mOrderedBroadcastHelper = null;
}
if (mReadingListHelper != null) {
mReadingListHelper.uninit();
mReadingListHelper = null;

View File

@ -142,6 +142,8 @@ public abstract class GeckoApp
public static final String ACTION_LOAD = "org.mozilla.gecko.LOAD";
public static final String ACTION_INIT_PW = "org.mozilla.gecko.INIT_PW";
public static final String INTENT_REGISTER_STUMBLER_LISTENER = "org.mozilla.gecko.STUMBLER_REGISTER_LOCAL_LISTENER";
public static final String EXTRA_STATE_BUNDLE = "stateBundle";
public static final String PREFS_ALLOW_STATE_BUNDLE = "allowStateBundle";
@ -1128,6 +1130,13 @@ public abstract class GeckoApp
Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e);
}
// Tell Stumbler to register a local broadcast listener to listen for preference intents.
// We do this via intents since we can't easily access Stumbler directly,
// as it might be compiled outside of Fennec.
getApplicationContext().sendBroadcast(
new Intent(INTENT_REGISTER_STUMBLER_LISTENER)
);
// Did the OS locale change while we were backgrounded? If so,
// we need to die so that Gecko will re-init add-ons that touch
// the UI.

View File

@ -1,128 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.GeckoEventListener;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
/**
* Helper class to send Android Ordered Broadcasts.
*/
public final class OrderedBroadcastHelper
implements GeckoEventListener
{
public static final String LOGTAG = "GeckoOrdBroadcast";
public static final String SEND_EVENT = "OrderedBroadcast:Send";
protected final Context mContext;
public OrderedBroadcastHelper(Context context) {
mContext = context;
EventDispatcher dispatcher = EventDispatcher.getInstance();
if (dispatcher == null) {
Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
return;
}
dispatcher.registerGeckoThreadListener(this, SEND_EVENT);
}
public synchronized void uninit() {
EventDispatcher dispatcher = EventDispatcher.getInstance();
if (dispatcher == null) {
Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
return;
}
dispatcher.unregisterGeckoThreadListener(this, SEND_EVENT);
}
@Override
public void handleMessage(String event, JSONObject message) {
if (!SEND_EVENT.equals(event)) {
Log.e(LOGTAG, "OrderedBroadcastHelper got unexpected message " + event);
return;
}
try {
final String action = message.getString("action");
if (action == null) {
Log.e(LOGTAG, "action must not be null");
return;
}
final String responseEvent = message.getString("responseEvent");
if (responseEvent == null) {
Log.e(LOGTAG, "responseEvent must not be null");
return;
}
// It's fine if the caller-provided token is missing or null.
final JSONObject token = (message.has("token") && !message.isNull("token")) ?
message.getJSONObject("token") : null;
// A missing (undefined) permission means the intent will be limited
// to the current package. A null means no permission, so any
// package can receive the intent.
final String permission = message.has("permission") ?
(message.isNull("permission") ? null : message.getString("permission")) :
GlobalConstants.PER_ANDROID_PACKAGE_PERMISSION;
final BroadcastReceiver resultReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int code = getResultCode();
if (code == Activity.RESULT_OK) {
String data = getResultData();
JSONObject res = new JSONObject();
try {
res.put("action", action);
res.put("token", token);
res.put("data", data);
} catch (JSONException e) {
Log.e(LOGTAG, "Got exception in onReceive handling action " + action, e);
return;
}
GeckoAppShell.notifyObservers(responseEvent, res.toString());
}
}
};
Intent intent = new Intent(action);
// OrderedBroadcast.jsm adds its callback ID to the caller's token;
// this unwraps that wrapping.
if (token != null && token.has("data")) {
intent.putExtra("token", token.getString("data"));
}
mContext.sendOrderedBroadcast(intent,
permission,
resultReceiver,
null,
Activity.RESULT_OK,
null,
null);
} catch (JSONException e) {
Log.e(LOGTAG, "Got exception in handleMessage handling event " + event, e);
return;
}
}
}

View File

@ -17,7 +17,7 @@ import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.TelemetryContract.Method;
import org.mozilla.gecko.fxa.AccountLoaderNative;
import org.mozilla.gecko.fxa.AccountLoader;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import android.accounts.Account;
@ -270,7 +270,7 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
private class AccountLoaderCallbacks implements LoaderManager.LoaderCallbacks<Account> {
@Override
public Loader<Account> onCreateLoader(int id, Bundle args) {
return new AccountLoaderNative(getActivity());
return new AccountLoader(getActivity());
}
@Override

View File

@ -75,6 +75,7 @@ import android.preference.SwitchPreference;
import android.preference.TwoStatePreference;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TextInputLayout;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.ActionBar;
import android.text.Editable;
import android.text.InputType;
@ -163,7 +164,7 @@ OnSharedPreferenceChangeListener
public static final String PREFS_APP_UPDATE_LAST_BUILD_ID = "app.update.last_build_id";
public static final String PREFS_READ_PARTNER_CUSTOMIZATIONS_PROVIDER = NON_PREF_PREFIX + "distribution.read_partner_customizations_provider";
private static final String ACTION_STUMBLER_UPLOAD_PREF = AppConstants.ANDROID_PACKAGE_NAME + ".STUMBLER_PREF";
private static final String ACTION_STUMBLER_UPLOAD_PREF = "STUMBLER_PREF";
// This isn't a Gecko pref, even if it looks like one.
@ -960,7 +961,7 @@ OnSharedPreferenceChangeListener
public static void broadcastAction(final Context context, final Intent intent) {
fillIntentWithProfileInfo(context, intent);
context.sendBroadcast(intent, GlobalConstants.PER_ANDROID_PACKAGE_PERMISSION);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
private static void fillIntentWithProfileInfo(final Context context, final Intent intent) {

View File

@ -487,7 +487,6 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'notifications/WhatsNewReceiver.java',
'NotificationService.java',
'NSSBridge.java',
'OrderedBroadcastHelper.java',
'overlays/OverlayConstants.java',
'overlays/service/OverlayActionService.java',
'overlays/service/ShareData.java',

View File

@ -1,80 +0,0 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* 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";
this.EXPORTED_SYMBOLS = ["sendOrderedBroadcast"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
// For adding observers.
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");
var _callbackId = 1;
/**
* Send an ordered broadcast to Java.
*
* Internally calls Context.sendOrderedBroadcast.
*
* action {String} should be a string with a qualified name (like
* org.mozilla.gecko.action) that will be broadcast.
*
* token {Object} is a piece of arbitrary data that will be given as
* a parameter to the callback (possibly null).
*
* callback {function} should accept three arguments: the data
* returned from Java as an Object; the specified token; and the
* specified action.
*
* permission {String} is an optional string with an Android permission
* that packages must have to respond to the ordered broadcast. A null
* value allows any package to respond. If the parameter is omitted (or
* {undefined}), then the intent is restricted to the current package.
*/
function sendOrderedBroadcast(action, token, callback, permission) {
let callbackId = _callbackId++;
let responseEvent = "OrderedBroadcast:Response:" + callbackId;
let observer = {
callbackId: callbackId,
callback: callback,
observe: function observe(subject, topic, data) {
if (topic != responseEvent) {
return;
}
// Unregister observer as soon as possible.
Services.obs.removeObserver(observer, responseEvent);
let msg = JSON.parse(data);
if (!msg.action || !msg.token || !msg.token.callbackId)
return;
let theToken = msg.token.data;
let theAction = msg.action;
let theData = msg.data ? JSON.parse(msg.data) : null;
let theCallback = this.callback;
if (!theCallback)
return;
// This is called from within a notified observer, so we don't
// need to take special pains to catch exceptions.
theCallback(theData, theToken, theAction);
},
};
Services.obs.addObserver(observer, responseEvent, false);
Messaging.sendRequest({
type: "OrderedBroadcast:Send",
action: action,
responseEvent: responseEvent,
token: { callbackId: callbackId, data: token || null },
permission: permission,
});
};

View File

@ -22,7 +22,6 @@ EXTRA_JS_MODULES += [
'Messaging.jsm',
'NetErrorHelper.jsm',
'Notifications.jsm',
'OrderedBroadcast.jsm',
'PageActions.jsm',
'Prompt.jsm',
'RuntimePermissions.jsm',

View File

@ -18,14 +18,6 @@
</intent-filter>
</activity>
<receiver
android:name="org.mozilla.gecko.fxa.receivers.FxAccountDeletedReceiver"
android:permission="@ANDROID_PACKAGE_NAME@_fxaccount.permission.PER_ACCOUNT_TYPE">
<intent-filter>
<action android:name="@ANDROID_PACKAGE_NAME@_fxaccount.accounts.ACCOUNT_DELETED_ACTION"/>
</intent-filter>
</receiver>
<receiver
android:name="org.mozilla.gecko.fxa.receivers.FxAccountUpgradeReceiver">
<intent-filter>

View File

@ -14,13 +14,6 @@ public class GlobalConstants {
public static final String BROWSER_INTENT_PACKAGE = AppConstants.ANDROID_PACKAGE_NAME;
public static final String BROWSER_INTENT_CLASS = AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS;
/**
* Bug 800244: this signing-level permission protects broadcast intents that
* should be received only by the Firefox versions with the given Android
* package name.
*/
public static final String PER_ANDROID_PACKAGE_PERMISSION = AppConstants.ANDROID_PACKAGE_NAME + ".permission.PER_ANDROID_PACKAGE";
public static final int SHARED_PREFERENCES_MODE = 0;
// Common time values.

View File

@ -13,7 +13,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.support.v4.content.AsyncTaskLoader;
import android.os.Looper;
import android.content.AsyncTaskLoader;
import android.support.v4.content.LocalBroadcastManager;
import java.lang.ref.WeakReference;
/**
* A Loader that queries and updates based on the existence of Firefox and
@ -35,7 +39,11 @@ public class AccountLoader extends AsyncTaskLoader<Account> {
protected Account account = null;
protected BroadcastReceiver broadcastReceiver = null;
public AccountLoader(Context context) {
// Hold a weak reference to AccountLoader instance in this Runnable to avoid potentially leaking it
// after posting to a Handler in the BroadcastReceiver returned from makeNewObserver.
private final BroadcastReceiverRunnable broadcastReceiverRunnable = new BroadcastReceiverRunnable(this);
public AccountLoader(final Context context) {
super(context);
}
@ -82,7 +90,8 @@ public class AccountLoader extends AsyncTaskLoader<Account> {
// Begin monitoring the underlying data source.
if (broadcastReceiver == null) {
broadcastReceiver = makeNewObserver();
registerObserver(broadcastReceiver);
registerLocalObserver(getContext(), broadcastReceiver);
registerSystemObserver(getContext(), broadcastReceiver);
}
if (takeContentChanged() || account == null) {
@ -122,12 +131,12 @@ public class AccountLoader extends AsyncTaskLoader<Account> {
if (broadcastReceiver != null) {
final BroadcastReceiver observer = broadcastReceiver;
broadcastReceiver = null;
unregisterObserver(observer);
unregisterObserver(getContext(), observer);
}
}
@Override
public void onCanceled(Account data) {
public void onCanceled(final Account data) {
// Attempt to cancel the current asynchronous load.
super.onCanceled(data);
@ -136,42 +145,83 @@ public class AccountLoader extends AsyncTaskLoader<Account> {
releaseResources(data);
}
// Observer which receives notifications when the data changes.
protected BroadcastReceiver makeNewObserver() {
return new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// onContentChanged must be called on the main thread.
// If we're already on the main thread, call it directly.
if (Looper.myLooper() == Looper.getMainLooper()) {
onContentChanged();
return;
}
// Otherwise, post a Runnable to a Handler bound to the main thread's message loop.
final Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(broadcastReceiverRunnable);
}
};
}
private static class BroadcastReceiverRunnable implements Runnable {
private final WeakReference<AccountLoader> accountLoaderWeakReference;
public BroadcastReceiverRunnable(final AccountLoader accountLoader) {
accountLoaderWeakReference = new WeakReference<>(accountLoader);
}
@Override
public void run() {
final AccountLoader accountLoader = accountLoaderWeakReference.get();
if (accountLoader != null) {
accountLoader.onContentChanged();
}
}
}
private void releaseResources(Account data) {
// For a simple List, there is nothing to do. For something like a Cursor, we
// would close it in this method. All resources associated with the Loader
// should be released here.
}
// Observer which receives notifications when the data changes.
protected BroadcastReceiver makeNewObserver() {
final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Must be called on the main thread of the process. We register the
// broadcast receiver with a null Handler (see registerObserver), which
// ensures we're on the main thread when we receive this intent.
onContentChanged();
}
};
return broadcastReceiver;
}
protected void registerObserver(BroadcastReceiver observer) {
/**
* Register provided observer with the LocalBroadcastManager to listen for internal events.
*
* @param context <code>Context</code> to use for obtaining LocalBroadcastManager instance.
* @param observer <code>BroadcastReceiver</code> which will handle local events.
*/
protected static void registerLocalObserver(final Context context, final BroadcastReceiver observer) {
final IntentFilter intentFilter = new IntentFilter();
// Android Account added or removed.
intentFilter.addAction(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
// Firefox Account internal state changed.
intentFilter.addAction(FxAccountConstants.ACCOUNT_STATE_CHANGED_ACTION);
// Firefox Account profile state changed.
intentFilter.addAction(FxAccountConstants.ACCOUNT_PROFILE_JSON_UPDATED_ACTION);
// null means: "the main thread of the process will be used." We must call
// onContentChanged on the main thread of the process; this ensures we do.
final Handler handler = null;
getContext().registerReceiver(observer, intentFilter, FxAccountConstants.PER_ACCOUNT_TYPE_PERMISSION, handler);
LocalBroadcastManager.getInstance(context).registerReceiver(observer, intentFilter);
}
protected void unregisterObserver(BroadcastReceiver observer) {
getContext().unregisterReceiver(observer);
/**
* Register provided observer for handling system-wide broadcasts.
*
* @param context <code>Context</code> to use for registering a receiver.
* @param observer <code>BroadcastReceiver</code> which will handle system events.
*/
protected static void registerSystemObserver(final Context context, final BroadcastReceiver observer) {
context.registerReceiver(observer,
// Android Account added or removed.
new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION),
// No broadcast permissions required.
null,
// Null handler ensures that broadcasts will be handled on the main thread.
null
);
}
protected static void unregisterObserver(final Context context, final BroadcastReceiver observer) {
LocalBroadcastManager.getInstance(context).unregisterReceiver(observer);
context.unregisterReceiver(observer);
}
}

View File

@ -1,182 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.fxa;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.content.AsyncTaskLoader;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
/**
* A Loader that queries and updates based on the existence of only Firefox Android Account.
*
* The loader is similar to @Link{AccountLoader} that is intended to be used with native LoaderManager.
* Note: This loader available only on devices running Honeycomb or later Android version.
*
* The loader returns an Android Account (of either Account type) if an account
* exists, and null to indicate no Account is present.
*
* The loader listens for Accounts added and deleted, and also Accounts being
* updated by Sync or another Activity, via the use of
* {@link AndroidFxAccount#setState(org.mozilla.gecko.fxa.login.State)}.
* Be careful of message loops if you update the account state from an activity
* that uses this loader.
*
* This implementation is based on
* <a href="http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html">http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html</a>.
*/
public class AccountLoaderNative extends AsyncTaskLoader<Account> {
protected Account account = null;
protected BroadcastReceiver broadcastReceiver = null;
@TargetApi(11)
public AccountLoaderNative(Context context) {
super(context);
}
// Task that performs the asynchronous load **/
@Override
public Account loadInBackground() {
final Context context = getContext();
return FirefoxAccounts.getFirefoxAccount(context);
}
// Deliver the results to the registered listener.
@Override
public void deliverResult(Account data) {
if (isReset()) {
// The Loader has been reset; ignore the result and invalidate the data.
releaseResources(data);
return;
}
// Hold a reference to the old data so it doesn't get garbage collected.
// We must protect it until the new data has been delivered.
Account oldData = account;
account = data;
if (isStarted()) {
// If the Loader is in a started state, deliver the results to the
// client. The superclass method does this for us.
super.deliverResult(data);
}
// Invalidate the old data as we don't need it any more.
if (oldData != null && oldData != data) {
releaseResources(oldData);
}
}
// The Loaders state-dependent behavior.
@Override
protected void onStartLoading() {
if (account != null) {
// Deliver any previously loaded data immediately.
deliverResult(account);
}
// Begin monitoring the underlying data source.
if (broadcastReceiver == null) {
broadcastReceiver = makeNewObserver();
registerObserver(broadcastReceiver);
}
if (takeContentChanged() || account == null) {
// When the observer detects a change, it should call onContentChanged()
// on the Loader, which will cause the next call to takeContentChanged()
// to return true. If this is ever the case (or if the current data is
// null), we force a new load.
forceLoad();
}
}
@Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
// Note that we leave the observer as is. Loaders in a stopped state
// should still monitor the data source for changes so that the Loader
// will know to force a new load if it is ever started again.
}
@Override
protected void onReset() {
// Ensure the loader has been stopped. In CursorLoader and the template
// this code follows (see the class comment), this is onStopLoading, which
// appears to not set the started flag (see Loader itself).
stopLoading();
// At this point we can release the resources associated with 'mData'.
if (account != null) {
releaseResources(account);
account = null;
}
// The Loader is being reset, so we should stop monitoring for changes.
if (broadcastReceiver != null) {
final BroadcastReceiver observer = broadcastReceiver;
broadcastReceiver = null;
unregisterObserver(observer);
}
}
@Override
public void onCanceled(Account data) {
// Attempt to cancel the current asynchronous load.
super.onCanceled(data);
// The load has been canceled, so we should release the resources
// associated with 'data'.
releaseResources(data);
}
private void releaseResources(Account data) {
// For a simple List, there is nothing to do. For something like a Cursor, we
// would close it in this method. All resources associated with the Loader
// should be released here.
}
// Observer which receives notifications when the data changes.
protected BroadcastReceiver makeNewObserver() {
final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Must be called on the main thread of the process. We register the
// broadcast receiver with a null Handler (see registerObserver), which
// ensures we're on the main thread when we receive this intent.
onContentChanged();
}
};
return broadcastReceiver;
}
protected void registerObserver(BroadcastReceiver observer) {
final IntentFilter intentFilter = new IntentFilter();
// Android Account added or removed.
intentFilter.addAction(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
// Firefox Account internal state changed.
intentFilter.addAction(FxAccountConstants.ACCOUNT_STATE_CHANGED_ACTION);
// Firefox Account profile state changed.
intentFilter.addAction(FxAccountConstants.ACCOUNT_PROFILE_JSON_UPDATED_ACTION);
// null means: "the main thread of the process will be used." We must call
// onContentChanged on the main thread of the process; this ensures we do.
final Handler handler = null;
getContext().registerReceiver(observer, intentFilter, FxAccountConstants.PER_ACCOUNT_TYPE_PERMISSION, handler);
}
protected void unregisterObserver(BroadcastReceiver observer) {
getContext().unregisterReceiver(observer);
}
}

View File

@ -42,23 +42,6 @@ public class FxAccountConstants {
public static final String ACCOUNT_PICKLE_FILENAME = "fxa.account.json";
/**
* This action is broadcast when an Android Firefox Account is deleted.
* This allows each installed Firefox to delete any Firefox Account pickle
* file.
* <p>
* It is protected by signing-level permission PER_ACCOUNT_TYPE_PERMISSION and
* can be received only by Firefox channels sharing the same Android Firefox
* Account type.
* <p>
* See {@link org.mozilla.gecko.fxa.AndroidFxAccount#makeDeletedAccountIntent()}
* for contents of the intent.
*
* See bug 790931 for additional information in the context of Sync.
*/
public static final String ACCOUNT_DELETED_ACTION = AppConstants.MOZ_ANDROID_SHARED_FXACCOUNT_TYPE + ".accounts.ACCOUNT_DELETED_ACTION";
/**
* Version number of contents of SYNC_ACCOUNT_DELETED_ACTION intent.
*/
@ -69,12 +52,6 @@ public class FxAccountConstants {
public static final String ACCOUNT_OAUTH_SERVICE_ENDPOINT_KEY = "account_oauth_service_endpoint";
public static final String ACCOUNT_DELETED_INTENT_ACCOUNT_AUTH_TOKENS = "account_deleted_intent_auth_tokens";
/**
* This signing-level permission protects broadcast intents that should be
* received only by Firefox channels sharing the same Android Firefox Account type.
*/
public static final String PER_ACCOUNT_TYPE_PERMISSION = AppConstants.MOZ_ANDROID_SHARED_FXACCOUNT_TYPE + ".permission.PER_ACCOUNT_TYPE";
/**
* This action is broadcast when an Android Firefox Account's internal state
* is changed.

View File

@ -577,7 +577,7 @@ public class AndroidFxAccount {
protected void broadcastAccountStateChangedIntent() {
final Intent intent = new Intent(FxAccountConstants.ACCOUNT_STATE_CHANGED_ACTION);
intent.putExtra(Constants.JSON_KEY_ACCOUNT, account.name);
context.sendBroadcast(intent, FxAccountConstants.PER_ACCOUNT_TYPE_PERMISSION);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
public synchronized State getState() {
@ -637,12 +637,12 @@ public class AndroidFxAccount {
}
/**
* Create an intent announcing that a Firefox account will be deleted.
* Populate an intent used for starting FxAccountDeletedService service.
*
* @return <code>Intent</code> to broadcast.
* @param intent Intent to populate with necessary extras
* @return <code>Intent</code> with a deleted action and account/OAuth information extras
*/
public Intent makeDeletedAccountIntent() {
final Intent intent = new Intent(FxAccountConstants.ACCOUNT_DELETED_ACTION);
public Intent populateDeletedAccountIntent(final Intent intent) {
final List<String> tokens = new ArrayList<>();
intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_VERSION_KEY,

View File

@ -30,6 +30,7 @@ import org.mozilla.gecko.fxa.login.Married;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.fxa.login.State.StateLabel;
import org.mozilla.gecko.fxa.login.StateFactory;
import org.mozilla.gecko.fxa.receivers.FxAccountDeletedService;
import org.mozilla.gecko.fxa.sync.FxAccountNotificationManager;
import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
import org.mozilla.gecko.util.ThreadUtils;
@ -363,10 +364,12 @@ public class FxAccountAuthenticator extends AbstractAccountAuthenticator {
"deleting saved pickle file '" + FxAccountConstants.ACCOUNT_PICKLE_FILENAME + "'.");
deletePickle();
final Intent intent = androidFxAccount.makeDeletedAccountIntent();
final Intent serviceIntent = androidFxAccount.populateDeletedAccountIntent(
new Intent(context, FxAccountDeletedService.class)
);
Logger.info(LOG_TAG, "Account named " + account.name + " being removed; " +
"broadcasting secure intent " + intent.getAction() + ".");
context.sendBroadcast(intent, FxAccountConstants.PER_ACCOUNT_TYPE_PERMISSION);
"starting FxAccountDeletedService with action: " + serviceIntent.getAction() + ".");
context.startService(serviceIntent);
return result;
}

View File

@ -1,33 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.fxa.receivers;
import org.mozilla.gecko.background.common.log.Logger;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class FxAccountDeletedReceiver extends BroadcastReceiver {
public static final String LOG_TAG = FxAccountDeletedReceiver.class.getSimpleName();
/**
* This receiver can be killed as soon as it returns, but we have things to do
* that can't be done on the main thread (network activity). Therefore we
* start a service to do our clean up work for us, with Android doing the
* heavy lifting for the service's lifecycle.
* <p>
* See <a href="http://developer.android.com/reference/android/content/BroadcastReceiver.html#ReceiverLifecycle">the Android documentation</a>
* for details.
*/
@Override
public void onReceive(final Context context, Intent broadcastIntent) {
Logger.debug(LOG_TAG, "FxAccount deleted broadcast received.");
Intent serviceIntent = new Intent(context, FxAccountDeletedService.class);
serviceIntent.putExtras(broadcastIntent);
context.startService(serviceIntent);
}
}

View File

@ -12,23 +12,15 @@ import android.util.Log;
import org.mozilla.mozstumbler.service.AppGlobals;
import org.mozilla.mozstumbler.service.stumblerthread.StumblerService;
/* Starts the StumblerService, an Intent service, which by definition runs on its own thread.
* Registered as a receiver in the AndroidManifest.xml.
/**
* Starts the StumblerService, an Intent service, which by definition runs on its own thread.
* Registered as a local broadcast receiver in SafeReceiver.
* Starts the StumblerService in passive listening mode.
*
* The received intent serves these purposes:
* 1) The host application enables (or disables) the StumblerService.
* Enabling requires passing in the upload API key. Both the enabled state, and the API key are stored in prefs.
*
* 2) If previously enabled by (1), notify the service to start (such as with a BOOT Intent).
* The StumblerService is where the enabled state is checked, and if not enabled, the
* service stops immediately.
*
* 3) Upload notification: onReceive intents are used to tell the StumblerService to check for upload.
* In the Fennec host app use, startup and pause are used as indicators to the StumblerService that now
* is a good time to try upload, as it is likely that the network is in use.
* The received intent contains enabled state, upload API key and user agent,
* and is used to initialize the StumblerService.
*/
public class PassiveServiceReceiver extends BroadcastReceiver {
public class LocalPreferenceReceiver extends BroadcastReceiver {
// This allows global debugging logs to be enabled by doing
// |adb shell setprop log.tag.PassiveStumbler DEBUG|
static final String LOG_TAG = "PassiveStumbler";
@ -44,19 +36,10 @@ public class PassiveServiceReceiver extends BroadcastReceiver {
// This does not guard against dumping PII (PII in stumbler is location, wifi BSSID, cell tower details).
AppGlobals.isDebug = Log.isLoggable(LOG_TAG, Log.DEBUG);
final String action = intent.getAction();
final boolean isIntentFromHostApp = (action != null) && action.contains(".STUMBLER_PREF");
if (!isIntentFromHostApp) {
Log.d(LOG_TAG, "Stumbler: received intent external to host app");
Intent startServiceIntent = new Intent(context, StumblerService.class);
startServiceIntent.putExtra(StumblerService.ACTION_NOT_FROM_HOST_APP, true);
context.startService(startServiceIntent);
return;
}
StumblerService.sFirefoxStumblingEnabled.set(intent.getBooleanExtra("enabled", false));
if (!StumblerService.sFirefoxStumblingEnabled.get()) {
Log.d(LOG_TAG, "Stopping StumblerService | isDebug:" + AppGlobals.isDebug);
// This calls the service's onDestroy(), and the service's onHandleIntent(...) is not called
context.stopService(new Intent(context, StumblerService.class));
// For testing service messages were received
@ -67,14 +50,20 @@ public class PassiveServiceReceiver extends BroadcastReceiver {
// For testing service messages were received
context.sendBroadcast(new Intent(AppGlobals.ACTION_TEST_SETTING_ENABLED));
Log.d(LOG_TAG, "Stumbler: Sending passive start message | isDebug:" + AppGlobals.isDebug);
Log.d(LOG_TAG, "Sending passive start message | isDebug:" + AppGlobals.isDebug);
final Intent startServiceIntent = new Intent(context, StumblerService.class);
startServiceIntent.putExtra(StumblerService.ACTION_START_PASSIVE, true);
final String mozApiKey = intent.getStringExtra("moz_mozilla_api_key");
startServiceIntent.putExtra(StumblerService.ACTION_EXTRA_MOZ_API_KEY, mozApiKey);
final String userAgent = intent.getStringExtra("user_agent");
startServiceIntent.putExtra(StumblerService.ACTION_EXTRA_USER_AGENT, userAgent);
startServiceIntent.putExtra(
StumblerService.ACTION_EXTRA_MOZ_API_KEY,
intent.getStringExtra("moz_mozilla_api_key")
);
startServiceIntent.putExtra(
StumblerService.ACTION_EXTRA_USER_AGENT,
intent.getStringExtra("user_agent")
);
context.startService(startServiceIntent);
}
}

View File

@ -0,0 +1,43 @@
/* 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.mozstumbler.service.mainthread;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
/**
* Responsible for registering LocalPreferenceReceiver as a receiver with LocalBroadcastManager.
* This receiver is registered in the AndroidManifest.xml
*/
public class SafeReceiver extends BroadcastReceiver {
static final String LOG_TAG = "StumblerSafeReceiver";
static final String PREFERENCE_INTENT_FILTER = "STUMBLER_PREF";
private boolean registeredLocalReceiver = false;
@Override
public synchronized void onReceive(Context context, Intent intent) {
if (intent == null) {
return;
}
if (registeredLocalReceiver) {
return;
}
LocalBroadcastManager.getInstance(context).registerReceiver(
new LocalPreferenceReceiver(),
new IntentFilter(PREFERENCE_INTENT_FILTER)
);
Log.d(LOG_TAG, "Registered local preference listener");
registeredLocalReceiver = true;
}
}

View File

@ -0,0 +1,41 @@
/* 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.mozstumbler.service.mainthread;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
import org.mozilla.mozstumbler.service.stumblerthread.StumblerService;
/**
* Responsible for starting StumblerService in response to
* BOOT_COMPLETE and EXTERNAL_APPLICATIONS_AVAILABLE system intents.
*/
public class SystemReceiver extends BroadcastReceiver {
static final String LOG_TAG = "StumblerSystemReceiver";
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) {
return;
}
final String action = intent.getAction();
if (!TextUtils.equals(action, Intent.ACTION_BOOT_COMPLETED) && !TextUtils.equals(action, Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE)) {
// This is not the broadcast you are looking for.
return;
}
final Intent startServiceIntent = new Intent(context, StumblerService.class);
startServiceIntent.putExtra(StumblerService.ACTION_NOT_FROM_HOST_APP, true);
context.startService(startServiceIntent);
Log.d(LOG_TAG, "Responded to a system intent");
}
}

View File

@ -6,10 +6,27 @@
<receiver android:name="org.mozilla.mozstumbler.service.uploadthread.UploadAlarmReceiver" />
<service android:name="org.mozilla.mozstumbler.service.uploadthread.UploadAlarmReceiver$UploadAlarmService" />
<receiver android:name="org.mozilla.mozstumbler.service.mainthread.PassiveServiceReceiver">
<!-- How Fennec and Stumbler interact:
- On start, Fennec broadcasts an empty STUMBLER_REGISTER_LOCAL_LISTENER intent, indicating that Stumbler should
start listening for a locally-broadcast Stumbler preferences.
- In response, Stumbler's SafeReceiver registers LocalPreferenceReceiver to listen for broadcasts
sent over LocalBroadcastManager which contain sensitive information.
- This registration happens only once, and SafeReceiver can't unregister the listener.
- LocalPreferenceReceiver responds to internal broadcasts with sensitive information,
and is able to start/stop StumblerService.
- Fennec startup (if stumbling is enabled) or Fennec stumbling preference adjustment will trigger
a local preference intent, and Stumbler's internal state will be adjusted via LocalPreferenceReceiver.
-->
<receiver android:exported="false" android:name="org.mozilla.mozstumbler.service.mainthread.SafeReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE" />
<action android:name="@ANDROID_PACKAGE_NAME@.STUMBLER_PREF" />
<action android:name="org.mozilla.gecko.STUMBLER_REGISTER_LOCAL_LISTENER" />
</intent-filter>
</receiver>
<receiver android:exported="true" android:name="org.mozilla.mozstumbler.service.mainthread.SystemReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE" />
</intent-filter>
</receiver>

View File

@ -6,7 +6,9 @@
stumbler_sources = [
'java/org/mozilla/mozstumbler/service/AppGlobals.java',
'java/org/mozilla/mozstumbler/service/mainthread/PassiveServiceReceiver.java',
'java/org/mozilla/mozstumbler/service/mainthread/LocalPreferenceReceiver.java',
'java/org/mozilla/mozstumbler/service/mainthread/SafeReceiver.java',
'java/org/mozilla/mozstumbler/service/mainthread/SystemReceiver.java',
'java/org/mozilla/mozstumbler/service/Prefs.java',
'java/org/mozilla/mozstumbler/service/stumblerthread/blocklist/BSSIDBlockList.java',
'java/org/mozilla/mozstumbler/service/stumblerthread/blocklist/SSIDBlockList.java',

View File

@ -18,12 +18,11 @@ package org.mozilla.gecko.background.fxa;
import android.accounts.Account;
import android.content.Context;
import android.content.Loader;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v4.content.Loader;
import android.support.v4.content.Loader.OnLoadCompleteListener;
import org.mozilla.gecko.background.sync.AndroidSyncTestCaseWithAccounts;
import org.mozilla.gecko.fxa.AccountLoader;
@ -93,7 +92,7 @@ public class TestAccountLoader extends AndroidSyncTestCaseWithAccounts {
// This callback runs on the "main" thread and unblocks the test thread
// when it puts the result into the blocking queue
final OnLoadCompleteListener<T> listener = new OnLoadCompleteListener<T>() {
final Loader.OnLoadCompleteListener<T> listener = new Loader.OnLoadCompleteListener<T>() {
@Override
public void onLoadComplete(Loader<T> completedLoader, T data) {
// Shut the loader down
@ -144,7 +143,7 @@ public class TestAccountLoader extends AndroidSyncTestCaseWithAccounts {
final boolean firefoxAccountsExist = FirefoxAccounts.firefoxAccountsExist(context);
if (firefoxAccountsExist) {
assertFirefoxAccount(getLoaderResultSynchronously(loader));
assertFirefoxAccount(getLoaderResultSynchronously((Loader<Account>) loader));
return;
}
@ -154,7 +153,7 @@ public class TestAccountLoader extends AndroidSyncTestCaseWithAccounts {
TEST_USERNAME, TEST_PROFILE, TEST_AUTH_SERVER_URI, TEST_TOKEN_SERVER_URI, TEST_PROFILE_SERVER_URI,
state, AndroidSyncTestCaseWithAccounts.TEST_SYNC_AUTOMATICALLY_MAP_WITH_ALL_AUTHORITIES_DISABLED);
assertNotNull(account);
assertFirefoxAccount(getLoaderResultSynchronously(loader));
assertFirefoxAccount(getLoaderResultSynchronously((Loader<Account>) loader));
}
protected void assertFirefoxAccount(Account account) {

View File

@ -78,7 +78,6 @@ skip-if = android_version == "18"
[src/org/mozilla/gecko/tests/testFilePicker.java]
[src/org/mozilla/gecko/tests/testHistoryService.java]
# [src/org/mozilla/gecko/tests/testMozPay.java] # see bug 945675
[src/org/mozilla/gecko/tests/testOrderedBroadcast.java]
[src/org/mozilla/gecko/tests/testOSLocale.java]
# disabled on 4.3: Bug 1124494
skip-if = android_version == "18"

View File

@ -1,69 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.tests;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
public class testOrderedBroadcast extends JavascriptTest {
protected BroadcastReceiver mReceiver;
public testOrderedBroadcast() {
super("testOrderedBroadcast.js");
}
@Override
public void setUp() throws Exception {
super.setUp();
mAsserter.dumpLog("Registering org.mozilla.gecko.test.receiver broadcast receiver");
IntentFilter filter = new IntentFilter();
filter.addAction("org.mozilla.gecko.test.receiver");
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
try {
JSONObject o = new JSONObject();
o.put("c", "efg");
o.put("d", 456);
// Feed the received token back to the sender.
o.put("token", intent.getStringExtra("token"));
String data = o.toString();
setResultCode(Activity.RESULT_OK);
setResultData(data);
} catch (JSONException e) {
setResultCode(Activity.RESULT_CANCELED);
setResultData(null);
}
}
};
// We must register the receiver in a Fennec context to avoid a
// SecurityException.
getActivity().getApplicationContext().registerReceiver(mReceiver, filter);
}
@Override
public void tearDown() throws Exception {
super.tearDown();
mAsserter.dumpLog("Unregistering org.mozilla.gecko.test.receiver broadcast receiver");
if (mReceiver != null) {
getActivity().getApplicationContext().unregisterReceiver(mReceiver);
mReceiver = null;
}
}
}

View File

@ -1,154 +0,0 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* 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/. */
Components.utils.import("resource://gre/modules/OrderedBroadcast.jsm");
Components.utils.import("resource://gre/modules/Promise.jsm");
var _observerId = 0;
function makeObserver() {
let deferred = Promise.defer();
let ret = {
id: _observerId++,
count: 0,
promise: deferred.promise,
callback: function(data, token, action) {
ret.count += 1;
deferred.resolve({ data: data,
token: token,
action: action });
},
};
return ret;
};
add_task(function test_send() {
let deferred = Promise.defer();
let observer = makeObserver();
let token = { a: "bcde", b: 1234 };
sendOrderedBroadcast("org.mozilla.gecko.test.receiver",
token, observer.callback);
let value = yield observer.promise;
do_check_eq(observer.count, 1);
// We get back the correct action and token.
do_check_neq(value, null);
do_check_matches(value.token, token);
do_check_eq(value.action, "org.mozilla.gecko.test.receiver");
// Data is provided by testOrderedBroadcast.java.in.
do_check_neq(value.data, null);
do_check_eq(value.data.c, "efg");
do_check_eq(value.data.d, 456);
// And the provided token is returned to us (as a string) by
// testOrderedBroadcast.java.in.
do_check_neq(value.data.token, null);
do_check_eq(typeof(value.data.token), "string");
do_check_matches(JSON.parse(value.data.token), token);
});
add_task(function test_null_token() {
let deferred = Promise.defer();
let observer = makeObserver();
sendOrderedBroadcast("org.mozilla.gecko.test.receiver",
null, observer.callback);
let value = yield observer.promise;
do_check_eq(observer.count, 1);
// We get back the correct action and token.
do_check_neq(value, null);
do_check_eq(value.token, null);
do_check_eq(value.action, "org.mozilla.gecko.test.receiver");
// Data is provided by testOrderedBroadcast.java.in.
do_check_neq(value.data, null);
do_check_eq(value.data.c, "efg");
do_check_eq(value.data.d, 456);
});
add_task(function test_string_token() {
let deferred = Promise.defer();
let observer = makeObserver();
let token = "string_token";
let permission = null; // means any receiver can listen for our intent
sendOrderedBroadcast("org.mozilla.gecko.test.receiver",
token, observer.callback, permission);
let value = yield observer.promise;
do_check_eq(observer.count, 1);
// We get back the correct action and token.
do_check_neq(value, null);
do_check_eq(value.token, token);
do_check_eq(typeof(value.token), "string");
do_check_eq(value.action, "org.mozilla.gecko.test.receiver");
// Data is provided by testOrderedBroadcast.java.in.
do_check_neq(value.data, null);
do_check_eq(value.data.c, "efg");
do_check_eq(value.data.d, 456);
do_check_eq(value.data.token, token);
});
add_task(function test_permission() {
let deferred = Promise.defer();
let observer = makeObserver();
sendOrderedBroadcast("org.mozilla.gecko.test.receiver",
null, observer.callback,
"org.mozilla.gecko.fake.permission");
let value = yield observer.promise;
do_check_eq(observer.count, 1);
// We get back the correct action and token.
do_check_neq(value, null);
do_check_eq(value.token, null);
do_check_eq(value.action, "org.mozilla.gecko.test.receiver");
// Data would be provided by testOrderedBroadcast.java.in, except
// the no package has the permission, so no responder exists.
do_check_eq(value.data, null);
});
add_task(function test_send_no_receiver() {
let deferred = Promise.defer();
let observer = makeObserver();
sendOrderedBroadcast("org.mozilla.gecko.test.no.receiver",
{ a: "bcd", b: 123 }, observer.callback);
let value = yield observer.promise;
do_check_eq(observer.count, 1);
// We get back the correct action and token, but the default is to
// return no data.
do_check_neq(value, null);
do_check_neq(value.token, null);
do_check_eq(value.token.a, "bcd");
do_check_eq(value.token.b, 123);
do_check_eq(value.action, "org.mozilla.gecko.test.no.receiver");
do_check_eq(value.data, null);
});
run_next_test();

View File

@ -226,9 +226,6 @@ nsSecureBrowserUIImpl::MapInternalToExternalState(uint32_t* aState, lockIconStat
if (ev) {
*aState |= nsIWebProgressListener::STATE_IDENTITY_EV_TOPLEVEL;
}
if (mCertUserOverridden) {
*aState |= nsIWebProgressListener::STATE_CERT_USER_OVERRIDDEN;
}
}
// * If so, the state should be broken or insecure; overriding the previous
// state set by the lock parameter.
@ -251,6 +248,10 @@ nsSecureBrowserUIImpl::MapInternalToExternalState(uint32_t* aState, lockIconStat
nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
}
if (mCertUserOverridden) {
*aState |= nsIWebProgressListener::STATE_CERT_USER_OVERRIDDEN;
}
// Has Mixed Content Been Blocked in nsMixedContentBlocker?
if (docShell->GetHasMixedActiveContentBlocked())
*aState |= nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT;

View File

@ -324,6 +324,10 @@ class ExtensionContext extends BaseContext {
this.extension = ExtensionManager.get(extensionId);
this.extensionId = extensionId;
this.contentWindow = contentWindow;
this.windowId = getInnerWindowID(contentWindow);
contentWindow.addEventListener("pageshow", this, true);
contentWindow.addEventListener("pagehide", this, true);
let frameId = WebNavigationFrames.getFrameId(contentWindow);
this.frameId = frameId;
@ -371,7 +375,7 @@ class ExtensionContext extends BaseContext {
// the content script to be associated with both the extension and
// the tab holding the content page.
let metadata = {
"inner-window-id": getInnerWindowID(contentWindow),
"inner-window-id": this.windowId,
addonId: attrs.addonId,
};
@ -430,6 +434,14 @@ class ExtensionContext extends BaseContext {
}
}
handleEvent(event) {
if (event.type == "pageshow") {
this.active = true;
} else if (event.type == "pagehide") {
this.active = false;
}
}
get cloneScope() {
return this.sandbox;
}
@ -462,6 +474,11 @@ class ExtensionContext extends BaseContext {
close() {
super.unload();
if (this.windowId === getInnerWindowID(this.contentWindow)) {
this.contentWindow.removeEventListener("pageshow", this, true);
this.contentWindow.removeEventListener("pagehide", this, true);
}
for (let script of this.scripts) {
if (script.requiresCleanup) {
script.cleanup(this.contentWindow);

View File

@ -153,6 +153,7 @@ class BaseContext {
this.unloaded = false;
this.extensionId = extensionId;
this.jsonSandbox = null;
this.active = true;
}
get cloneScope() {
@ -1004,7 +1005,10 @@ Port.prototype = {
}).api(),
onMessage: new EventManager(this.context, "Port.onMessage", fire => {
let listener = ({data}) => {
if (!this.disconnected) {
if (!this.context.active) {
// TODO: Send error as a response.
Cu.reportError("Message received on port for an inactive content script");
} else if (!this.disconnected) {
fire(data);
}
};
@ -1119,6 +1123,10 @@ Messenger.prototype = {
messageFilterPermissive: this.filter,
receiveMessage: ({target, data: message, sender, recipient}) => {
if (!this.context.active) {
return;
}
if (this.delegate) {
this.delegate.getSender(this.context, target, sender);
}