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