зеркало из https://github.com/mozilla/gecko-dev.git
merge fx-team to mozilla-central a=merge
This commit is contained in:
Коммит
8262976cf1
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
};
|
|
@ -22,7 +22,6 @@ EXTRA_JS_MODULES += [
|
|||
'Messaging.jsm',
|
||||
'NetErrorHelper.jsm',
|
||||
'Notifications.jsm',
|
||||
'OrderedBroadcast.jsm',
|
||||
'PageActions.jsm',
|
||||
'Prompt.jsm',
|
||||
'RuntimePermissions.jsm',
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 Loader’s 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);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче