diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js
index 93f16e8b2072..7ae45d856d12 100644
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -476,6 +476,11 @@ pref("security.alternate_certificate_error_page", "certerror");
pref("security.warn_viewing_mixed", false); // Warning is disabled. See Bug 616712.
+#ifdef NIGHTLY_BUILD
+// Block insecure active content on https pages
+pref("security.mixed_content.block_active_content", true);
+#endif
+
// Override some named colors to avoid inverse OS themes
pref("ui.-moz-dialog", "#efebe7");
pref("ui.-moz-dialogtext", "#101010");
diff --git a/mobile/android/base/BrowserToolbar.java b/mobile/android/base/BrowserToolbar.java
index 667bc41e60c5..ae366613548f 100644
--- a/mobile/android/base/BrowserToolbar.java
+++ b/mobile/android/base/BrowserToolbar.java
@@ -960,16 +960,9 @@ public class BrowserToolbar implements Tabs.OnTabsChangedListener,
}
private void setSecurityMode(String mode) {
- mShowSiteSecurity = true;
-
- if (mode.equals(SiteIdentityPopup.IDENTIFIED)) {
- mSiteSecurity.setImageLevel(1);
- } else if (mode.equals(SiteIdentityPopup.VERIFIED)) {
- mSiteSecurity.setImageLevel(2);
- } else {
- mSiteSecurity.setImageLevel(0);
- mShowSiteSecurity = false;
- }
+ int imageLevel = SiteIdentityPopup.getSecurityImageLevel(mode);
+ mSiteSecurity.setImageLevel(imageLevel);
+ mShowSiteSecurity = (imageLevel != SiteIdentityPopup.LEVEL_UKNOWN);
setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
}
diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in
index 41e428593c3f..420e6fcc79e5 100644
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -688,6 +688,8 @@ RES_DRAWABLE_MDPI = \
res/drawable-mdpi/menu_item_check.png \
res/drawable-mdpi/menu_item_more.png \
res/drawable-mdpi/menu_item_uncheck.png \
+ res/drawable-mdpi/site_security_blocked_mixed_content.png \
+ res/drawable-mdpi/site_security_loaded_mixed_content.png \
res/drawable-mdpi/site_security_identified.png \
res/drawable-mdpi/site_security_verified.png \
res/drawable-mdpi/tabs_normal.png \
@@ -792,6 +794,8 @@ RES_DRAWABLE_HDPI = \
res/drawable-hdpi/menu_item_check.png \
res/drawable-hdpi/menu_item_more.png \
res/drawable-hdpi/menu_item_uncheck.png \
+ res/drawable-hdpi/site_security_blocked_mixed_content.png \
+ res/drawable-hdpi/site_security_loaded_mixed_content.png \
res/drawable-hdpi/site_security_identified.png \
res/drawable-hdpi/site_security_verified.png \
res/drawable-hdpi/tabs_normal.png \
@@ -888,6 +892,8 @@ RES_DRAWABLE_XHDPI = \
res/drawable-xhdpi/tab_indicator_divider.9.png \
res/drawable-xhdpi/tab_indicator_selected.9.png \
res/drawable-xhdpi/tab_indicator_selected_focused.9.png \
+ res/drawable-xhdpi/site_security_blocked_mixed_content.png \
+ res/drawable-xhdpi/site_security_loaded_mixed_content.png \
res/drawable-xhdpi/site_security_identified.png \
res/drawable-xhdpi/site_security_verified.png \
res/drawable-xhdpi/tabs_normal.png \
diff --git a/mobile/android/base/SiteIdentityPopup.java b/mobile/android/base/SiteIdentityPopup.java
index eeda8a229471..72cd8ab031df 100644
--- a/mobile/android/base/SiteIdentityPopup.java
+++ b/mobile/android/base/SiteIdentityPopup.java
@@ -21,12 +21,26 @@ import android.widget.TextView;
* SiteIdentityPopup is a singleton class that displays site identity data in
* an arrow panel popup hanging from the lock icon in the browser toolbar.
*/
-public class SiteIdentityPopup extends ArrowPopup {
+public class SiteIdentityPopup extends ArrowPopup
+ implements DoorHanger.OnButtonClickListener {
private static final String LOGTAG = "GeckoSiteIdentityPopup";
public static final String UNKNOWN = "unknown";
public static final String VERIFIED = "verified";
public static final String IDENTIFIED = "identified";
+ public static final String MIXED_CONTENT_BLOCKED = "mixed_content_blocked";
+ public static final String MIXED_CONTENT_LOADED = "mixed_content_loaded";
+
+ // Security states corresponding to image levels in site_security_level.xml
+ public static final int LEVEL_UKNOWN = 0;
+ public static final int LEVEL_IDENTIFIED = 1;
+ public static final int LEVEL_VERIFIED = 2;
+ public static final int LEVEL_MIXED_CONTENT_BLOCKED = 3;
+ public static final int LEVEL_MIXED_CONTENT_LOADED = 4;
+
+ // FIXME: Update this URL for mobile. See bug 885923.
+ private static final String MIXED_CONTENT_SUPPORT_URL =
+ "https://support.mozilla.org/kb/how-does-content-isnt-secure-affect-my-safety";
private Resources mResources;
@@ -37,12 +51,30 @@ public class SiteIdentityPopup extends ArrowPopup {
private TextView mEncrypted;
private ImageView mLarry;
+ private DoorHanger mMixedContentNotification;
+
SiteIdentityPopup(BrowserApp aActivity) {
super(aActivity, null);
mResources = aActivity.getResources();
}
+ public static int getSecurityImageLevel(String mode) {
+ if (IDENTIFIED.equals(mode)) {
+ return LEVEL_IDENTIFIED;
+ }
+ if (VERIFIED.equals(mode)) {
+ return LEVEL_VERIFIED;
+ }
+ if (MIXED_CONTENT_BLOCKED.equals(mode)) {
+ return LEVEL_MIXED_CONTENT_BLOCKED;
+ }
+ if (MIXED_CONTENT_LOADED.equals(mode)) {
+ return LEVEL_MIXED_CONTENT_LOADED;
+ }
+ return LEVEL_UKNOWN;
+ }
+
@Override
protected void init() {
super.init();
@@ -61,26 +93,7 @@ public class SiteIdentityPopup extends ArrowPopup {
mLarry = (ImageView) layout.findViewById(R.id.larry);
}
- /*
- * @param identityData A JSONObject that holds the current tab's identity data.
- */
- public void updateIdentity(JSONObject identityData) {
- String mode;
- try {
- mode = identityData.getString("mode");
- } catch (JSONException e) {
- Log.e(LOGTAG, "Exception trying to get identity mode", e);
- return;
- }
-
- if (!mode.equals(VERIFIED) && !mode.equals(IDENTIFIED)) {
- Log.e(LOGTAG, "Can't show site identity popup in non-identified state");
- return;
- }
-
- if (!mInflated)
- init();
-
+ private void setIdentity(JSONObject identityData) {
try {
String host = identityData.getString("host");
mHost.setText(host);
@@ -99,19 +112,110 @@ public class SiteIdentityPopup extends ArrowPopup {
mVerifier.setText(verifier + "\n" + encrypted);
} catch (JSONException e) {
Log.e(LOGTAG, "Exception trying to get identity data", e);
+ }
+ }
+
+ @Override
+ public void onButtonClick(DoorHanger dh, String tag) {
+ if (tag.equals("disable")) {
+ // To disable mixed content blocking, reload the page with a flag to load mixed content.
+ try {
+ JSONObject data = new JSONObject();
+ data.put("allowMixedContent", true);
+ GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", data.toString());
+ GeckoAppShell.sendEventToGecko(e);
+ } catch (JSONException e) {
+ Log.e(LOGTAG, "Exception creating message to allow mixed content", e);
+ }
+ } else if (tag.equals("enable")) {
+ // To enable mixed content blocking, reload the page without any flags.
+ GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", "");
+ GeckoAppShell.sendEventToGecko(e);
+ }
+
+ dismiss();
+ }
+
+ private void addMixedContentNotification(boolean blocked) {
+ // Remove any exixting mixed content notification.
+ removeMixedContentNotification();
+ mMixedContentNotification = new DoorHanger(mActivity);
+
+ String message;
+ if (blocked) {
+ message = mActivity.getString(R.string.blocked_mixed_content_message_top) + "\n\n" +
+ mActivity.getString(R.string.blocked_mixed_content_message_bottom);
+ } else {
+ message = mActivity.getString(R.string.loaded_mixed_content_message);
+ }
+ mMixedContentNotification.setMessage(message);
+ mMixedContentNotification.addLink(mActivity.getString(R.string.learn_more), MIXED_CONTENT_SUPPORT_URL, "\n\n");
+
+ if (blocked) {
+ mMixedContentNotification.addButton(mActivity.getString(R.string.disable_protection), "disable", this);
+ mMixedContentNotification.addButton(mActivity.getString(R.string.keep_blocking), "keepBlocking", this);
+ } else {
+ mMixedContentNotification.addButton(mActivity.getString(R.string.enable_protection), "enable", this);
+ }
+ mMixedContentNotification.hideDivider();
+ mMixedContentNotification.setBackgroundColor(0xFFDDE4EA);
+
+ mContent.addView(mMixedContentNotification);
+ }
+
+ private void removeMixedContentNotification() {
+ if (mMixedContentNotification != null) {
+ mContent.removeView(mMixedContentNotification);
+ mMixedContentNotification = null;
+ }
+ }
+
+ /*
+ * @param identityData A JSONObject that holds the current tab's identity data.
+ */
+ public void updateIdentity(JSONObject identityData) {
+ String mode;
+ try {
+ mode = identityData.getString("mode");
+ } catch (JSONException e) {
+ Log.e(LOGTAG, "Exception trying to get identity mode", e);
return;
}
- if (mode.equals(VERIFIED)) {
+ if (UNKNOWN.equals(mode)) {
+ Log.e(LOGTAG, "Can't show site identity popup in non-identified state");
+ return;
+ }
+
+ if (!mInflated)
+ init();
+
+ setIdentity(identityData);
+
+ if (VERIFIED.equals(mode)) {
// Use a blue theme for SSL
mLarry.setImageResource(R.drawable.larry_blue);
mHost.setTextColor(mResources.getColor(R.color.identity_verified));
mOwner.setTextColor(mResources.getColor(R.color.identity_verified));
- } else {
+ } else if (IDENTIFIED.equals(mode)) {
// Use a green theme for EV
mLarry.setImageResource(R.drawable.larry_green);
mHost.setTextColor(mResources.getColor(R.color.identity_identified));
mOwner.setTextColor(mResources.getColor(R.color.identity_identified));
+ } else {
+ // Use a gray theme for sites with mixed content
+ // FIXME: Get a gray larry
+ mLarry.setImageResource(R.drawable.larry_blue);
+ mHost.setTextColor(mResources.getColor(R.color.identity_mixed_content));
+ mOwner.setTextColor(mResources.getColor(R.color.identity_mixed_content));
+
+ addMixedContentNotification(MIXED_CONTENT_BLOCKED.equals(mode));
}
}
+
+ @Override
+ public void dismiss() {
+ super.dismiss();
+ removeMixedContentNotification();
+ }
}
diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd
index 8688da1a3704..0435017d8204 100644
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -288,6 +288,15 @@ with that structure, consider a translation which ignores the preceding domain a
just addresses the organization to follow, e.g. "This site is run by " -->
+
+
+
+
+
+
+
+
+
diff --git a/mobile/android/base/resources/drawable-hdpi/site_security_blocked_mixed_content.png b/mobile/android/base/resources/drawable-hdpi/site_security_blocked_mixed_content.png
new file mode 100644
index 000000000000..c2e49b3a7fb6
Binary files /dev/null and b/mobile/android/base/resources/drawable-hdpi/site_security_blocked_mixed_content.png differ
diff --git a/mobile/android/base/resources/drawable-hdpi/site_security_loaded_mixed_content.png b/mobile/android/base/resources/drawable-hdpi/site_security_loaded_mixed_content.png
new file mode 100644
index 000000000000..83ba37c2461f
Binary files /dev/null and b/mobile/android/base/resources/drawable-hdpi/site_security_loaded_mixed_content.png differ
diff --git a/mobile/android/base/resources/drawable-mdpi/site_security_blocked_mixed_content.png b/mobile/android/base/resources/drawable-mdpi/site_security_blocked_mixed_content.png
new file mode 100644
index 000000000000..7cf33ec4c5ac
Binary files /dev/null and b/mobile/android/base/resources/drawable-mdpi/site_security_blocked_mixed_content.png differ
diff --git a/mobile/android/base/resources/drawable-mdpi/site_security_loaded_mixed_content.png b/mobile/android/base/resources/drawable-mdpi/site_security_loaded_mixed_content.png
new file mode 100644
index 000000000000..83ba37c2461f
Binary files /dev/null and b/mobile/android/base/resources/drawable-mdpi/site_security_loaded_mixed_content.png differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/site_security_blocked_mixed_content.png b/mobile/android/base/resources/drawable-xhdpi/site_security_blocked_mixed_content.png
new file mode 100644
index 000000000000..cac441514076
Binary files /dev/null and b/mobile/android/base/resources/drawable-xhdpi/site_security_blocked_mixed_content.png differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/site_security_loaded_mixed_content.png b/mobile/android/base/resources/drawable-xhdpi/site_security_loaded_mixed_content.png
new file mode 100644
index 000000000000..83ba37c2461f
Binary files /dev/null and b/mobile/android/base/resources/drawable-xhdpi/site_security_loaded_mixed_content.png differ
diff --git a/mobile/android/base/resources/drawable/site_security_level.xml b/mobile/android/base/resources/drawable/site_security_level.xml
index a8bf23f36e96..7c865ed3196b 100644
--- a/mobile/android/base/resources/drawable/site_security_level.xml
+++ b/mobile/android/base/resources/drawable/site_security_level.xml
@@ -8,5 +8,7 @@
+
+
diff --git a/mobile/android/base/resources/values/colors.xml b/mobile/android/base/resources/values/colors.xml
index 7366ec158a3d..6fff98de52ee 100644
--- a/mobile/android/base/resources/values/colors.xml
+++ b/mobile/android/base/resources/values/colors.xml
@@ -68,6 +68,7 @@
#ffffff
#FF3298FF
#FF89C450
+ #FF000000
#FFFF9500
#FFD06BFF
#dddddd
diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in
index 64f7fab37ad3..f4aaa0a3b130 100644
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -262,6 +262,13 @@
&identity_connected_to;
&identity_run_by;
+ &loaded_mixed_content_message;
+ &blocked_mixed_content_message_top;
+ &blocked_mixed_content_message_bottom;
+ &learn_more;
+ &enable_protection;
+ &disable_protection;
+ &keep_blocking;
&private_data_success;
diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js
index ff5018859c47..d98131a360bb 100644
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1316,11 +1316,18 @@ var BrowserApp = {
browser.goForward();
break;
- case "Session:Reload":
+ case "Session:Reload": {
+ let allowMixedContent = false;
+ if (aData) {
+ let data = JSON.parse(aData);
+ allowMixedContent = data.allowMixedContent;
+ }
+
// Try to use the session history to reload so that framesets are
// handled properly. If the window has no session history, fall back
// to using the web navigation's reload method.
- let flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
+ let flags = allowMixedContent ? Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT :
+ Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
let webNav = browser.webNavigation;
try {
let sh = webNav.sessionHistory;
@@ -1329,15 +1336,17 @@ var BrowserApp = {
} catch (e) {}
webNav.reload(flags);
break;
+ }
case "Session:Stop":
browser.stop();
break;
- case "Session:ShowHistory":
+ case "Session:ShowHistory": {
let data = JSON.parse(aData);
this.showHistory(data.fromIndex, data.toIndex, data.selIndex);
break;
+ }
case "Tab:Load": {
let data = JSON.parse(aData);
@@ -6034,10 +6043,24 @@ var CharacterEncoding = {
};
var IdentityHandler = {
- // Mode strings used to control CSS display
- IDENTITY_MODE_IDENTIFIED : "identified", // High-quality identity information
- IDENTITY_MODE_DOMAIN_VERIFIED : "verified", // Minimal SSL CA-signed domain verification
- IDENTITY_MODE_UNKNOWN : "unknown", // No trusted identity information
+ // No trusted identity information. No site identity icon is shown.
+ IDENTITY_MODE_UNKNOWN: "unknown",
+
+ // Minimal SSL CA-signed domain verification. Blue lock icon is shown.
+ IDENTITY_MODE_DOMAIN_VERIFIED: "verified",
+
+ // High-quality identity information. Green lock icon is shown.
+ IDENTITY_MODE_IDENTIFIED: "identified",
+
+ // The following mixed content modes are only used if "security.mixed_content.block_active_content"
+ // is enabled. Even though the mixed content state and identitity state are orthogonal,
+ // our Java frontend coalesces them into one indicator.
+
+ // Blocked active mixed content. Shield icon is shown, with a popup option to load content.
+ IDENTITY_MODE_MIXED_CONTENT_BLOCKED: "mixed_content_blocked",
+
+ // Loaded active mixed content. Yellow triangle icon is shown.
+ IDENTITY_MODE_MIXED_CONTENT_LOADED: "mixed_content_loaded",
// Cache the most recent SSLStatus and Location seen in getIdentityStrings
_lastStatus : null,
@@ -6077,6 +6100,14 @@ var IdentityHandler = {
},
getIdentityMode: function getIdentityMode(aState) {
+ if (aState & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT)
+ return this.IDENTITY_MODE_MIXED_CONTENT_BLOCKED;
+
+ // Only show an indicator for loaded mixed content if the pref to block it is enabled
+ if ((aState & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) &&
+ Services.prefs.getBoolPref("security.mixed_content.block_active_content"))
+ return this.IDENTITY_MODE_MIXED_CONTENT_LOADED;
+
if (aState & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL)
return this.IDENTITY_MODE_IDENTIFIED;
@@ -6126,7 +6157,7 @@ var IdentityHandler = {
result.verifier = Strings.browser.formatStringFromName("identity.identified.verifier", [iData.caOrg], 1);
// If the cert is identified, then we can populate the results with credentials
- if (mode == this.IDENTITY_MODE_IDENTIFIED) {
+ if (aState & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) {
result.owner = iData.subjectOrg;
// Build an appropriate supplemental block out of whatever location data we have
@@ -6145,7 +6176,7 @@ var IdentityHandler = {
}
// Otherwise, we don't know the cert owner
- result.owner = Strings.browser.GetStringFromName("identity.ownerUnknown2");
+ result.owner = Strings.browser.GetStringFromName("identity.ownerUnknown3");
// Cache the override service the first time we need to check it
if (!this._overrideService)
diff --git a/mobile/android/locales/en-US/chrome/browser.properties b/mobile/android/locales/en-US/chrome/browser.properties
index a760cc208e7b..d2cd594054a6 100644
--- a/mobile/android/locales/en-US/chrome/browser.properties
+++ b/mobile/android/locales/en-US/chrome/browser.properties
@@ -72,7 +72,7 @@ identity.identified.verified_by_you=You have added a security exception for this
identity.identified.state_and_country=%S, %S
identity.identified.title_with_country=%S (%S)
identity.encrypted2=Encrypted
-identity.ownerUnknown2=(unknown)
+identity.ownerUnknown3=unknown
# Geolocation UI
geolocation.allow=Share