ma1 pushed to branch mullvad-browser-128.5.0esr-14.0-1 at The Tor Project / Applications / Mullvad Browser
Commits: 437543c6 by Arturo Mejia at 2024-11-25T15:21:36+01:00 Bug 1836921 - Improve dialogs a=dmeehan
Original Revision: https://phabricator.services.mozilla.com/D226961
Differential Revision: https://phabricator.services.mozilla.com/D228842 - - - - - 33f6b33b by Malte Juergens at 2024-11-25T15:21:38+01:00 Bug 1909396 - Remove HTTPS-Only exception button in iframes r=freddyb,fluent-reviewers
Rationale for this can be read in Bug 1909396, but the main reason is that the iframe will get blocked regardless by mixed content blocking.
Differential Revision: https://phabricator.services.mozilla.com/D220257 - - - - - 412bde12 by Makoto Kato at 2024-11-25T15:21:40+01:00 Bug 1776646 - Support EXTRA_IS_SENSITIVE for clipboard. r=geckoview-reviewers,owlish
When nsITransferable.isPrivateData is true, such as coping password or private mode, we should set EXTRA_IS_SENSITIVE to ClipData.
AndroidJunit test runner doesn't often get `ClipDescription.extras` from clipboard service in test runner. So we cannot write a unit test using AndroidJUnit test runner.
Differential Revision: https://phabricator.services.mozilla.com/D225326 - - - - - 866130a7 by Cathy Lu at 2024-11-25T15:21:41+01:00 Bug 1914797 - Part 1 + 2 + partial backout (details below)
Bug 1914797 - Part 1 - Revert bug 1868469 r=android-reviewers,jonalmeida, a=dmeehan
Differential Revision: https://phabricator.services.mozilla.com/D226431
Bug 1914797 - Part 2 - Add url change during onPageStart for slow loading sites r=android-reviewers,jonalmeida, a=dmeehan
Differential Revision: https://phabricator.services.mozilla.com/D226432
Backed out 1 changesets (bug 1914797) for causing Bug 1929028
Backed out changeset a79554879d7b (bug 1914797)
- - - - -
16 changed files:
- dom/ipc/WindowGlobalParent.cpp - dom/security/test/https-only/browser.toml - + dom/security/test/https-only/browser_iframe_buttons.js - + dom/security/test/https-only/file_iframe_buttons.html - mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/SimpleRedirectDialogFragment.kt - mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/SimpleRedirectDialogFragmentTest.kt - mobile/android/android-components/components/feature/session/src/main/java/mozilla/components/feature/session/SessionUseCases.kt - mobile/android/android-components/components/feature/session/src/test/java/mozilla/components/feature/session/SessionUseCasesTest.kt - mobile/android/android-components/docs/changelog.md - mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/CrashReportingTest.kt - mobile/android/geckoview/src/main/java/org/mozilla/gecko/Clipboard.java - mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoInputConnection.java - toolkit/components/httpsonlyerror/content/errorpage.html - toolkit/components/httpsonlyerror/content/errorpage.js - toolkit/locales/en-US/toolkit/about/aboutHttpsOnlyError.ftl - widget/android/nsClipboard.cpp
Changes:
===================================== dom/ipc/WindowGlobalParent.cpp ===================================== @@ -1378,18 +1378,11 @@ mozilla::ipc::IPCResult WindowGlobalParent::RecvReloadWithHttpsOnlyException() { return IPC_FAIL(this, "HTTPS-only mode: Illegal state"); }
- // If the error page is within an iFrame, we create an exception for whatever - // scheme the top-level site is currently on, because the user wants to - // unbreak the iFrame and not the top-level page. When the error page shows up - // on a top-level request, then we replace the scheme with http, because the - // user wants to unbreak the whole page. + // We replace the scheme with http, because the user wants to unbreak the + // whole page. nsCOMPtr<nsIURI> newURI; - if (!BrowsingContext()->IsTop()) { - newURI = innerURI; - } else { - Unused << NS_MutateURI(innerURI).SetScheme("http"_ns).Finalize( - getter_AddRefs(newURI)); - } + Unused << NS_MutateURI(innerURI).SetScheme("http"_ns).Finalize( + getter_AddRefs(newURI));
OriginAttributes originAttributes = TopWindowContext()->DocumentPrincipal()->OriginAttributesRef();
===================================== dom/security/test/https-only/browser.toml ===================================== @@ -29,6 +29,9 @@ support-files = [ ["browser_httpsonly_speculative_connect.js"] support-files = ["file_httpsonly_speculative_connect.html"]
+["browser_iframe_buttons.js"] +support-files = ["file_iframe_buttons.html"] + ["browser_iframe_test.js"] skip-if = [ "os == 'linux' && bits == 64", # Bug 1735565
===================================== dom/security/test/https-only/browser_iframe_buttons.js ===================================== @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Ensure the buttons at the buttom of the HTTPS-Only error page do not get +// displayed in an iframe (Bug 1909396). + +add_task(async function test_iframe_buttons() { + await BrowserTestUtils.withNewTab( + "https://example.com/browser/dom/security/test/https-only/file_iframe_buttons...", + async function (browser) { + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_only_mode", true]], + }); + + await SpecialPowers.spawn(browser, [], async function () { + const iframe = content.document.getElementById("iframe"); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + iframe.src = "http://nocert.example.com"; + + await ContentTaskUtils.waitForCondition( + () => iframe.contentWindow.document.readyState === "interactive", + "Iframe error page should have loaded" + ); + + ok( + !!iframe.contentWindow.document.getElementById("explanation-iframe"), + "#explanation-iframe should exist" + ); + + is( + iframe.contentWindow.document + .getElementById("explanation-iframe") + .getAttribute("hidden"), + null, + "#explanation-iframe should not be hidden" + ); + + for (const id of ["explanation-continue", "goBack", "openInsecure"]) { + is( + iframe.contentWindow.document.getElementById(id), + null, + `#${id} should have been removed` + ); + } + }); + } + ); +});
===================================== dom/security/test/https-only/file_iframe_buttons.html ===================================== @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> +</head> +<body> + <iframe id="iframe" frameborder="0"></iframe> +</body> +</html>
===================================== mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/SimpleRedirectDialogFragment.kt ===================================== @@ -11,6 +11,7 @@ import androidx.annotation.StringRes import androidx.annotation.StyleRes import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AlertDialog +import mozilla.components.support.ktx.util.PromptAbuserDetector import mozilla.components.ui.widgets.withCenterAlignedButtons
/** @@ -23,6 +24,10 @@ import mozilla.components.ui.widgets.withCenterAlignedButtons */ class SimpleRedirectDialogFragment : RedirectDialogFragment() {
+ @VisibleForTesting + internal var promptAbuserDetector = + PromptAbuserDetector(maxSuccessiveDialogSecondsLimit = TIME_SHOWN_OFFSET_SECONDS) + @VisibleForTesting internal var testingContext: Context? = null
@@ -32,6 +37,8 @@ class SimpleRedirectDialogFragment : RedirectDialogFragment() { return if (themeID == 0) AlertDialog.Builder(context) else AlertDialog.Builder(context, themeID) }
+ promptAbuserDetector.updateJSDialogAbusedState() + return with(requireBundle()) { val dialogTitleText = getInt(KEY_TITLE_TEXT, R.string.mozac_feature_applinks_normal_confirm_dialog_title) val dialogMessageString = getString(KEY_MESSAGE_STRING, "") @@ -40,18 +47,29 @@ class SimpleRedirectDialogFragment : RedirectDialogFragment() { val themeResId = getInt(KEY_THEME_ID, 0) val cancelable = getBoolean(KEY_CANCELABLE, false)
- getBuilder(themeResId) + val dialog = getBuilder(themeResId) .setTitle(dialogTitleText) .setMessage(dialogMessageString) - .setPositiveButton(positiveButtonText) { _, _ -> - onConfirmRedirect() - } + .setPositiveButton(positiveButtonText) { _, _ -> } .setNegativeButton(negativeButtonText) { _, _ -> onCancelRedirect() } .setCancelable(cancelable) .create() - .withCenterAlignedButtons() + + dialog.withCenterAlignedButtons() + dialog.setOnShowListener { + val okButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE) + okButton.setOnClickListener { + if (promptAbuserDetector.areDialogsBeingAbused()) { + promptAbuserDetector.updateJSDialogAbusedState() + } else { + onConfirmRedirect() + dialog.dismiss() + } + } + } + dialog } }
@@ -101,6 +119,7 @@ class SimpleRedirectDialogFragment : RedirectDialogFragment() { const val KEY_THEME_ID = "KEY_THEME_ID"
const val KEY_CANCELABLE = "KEY_CANCELABLE" + private const val TIME_SHOWN_OFFSET_SECONDS = 1 }
private fun requireBundle(): Bundle {
===================================== mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/SimpleRedirectDialogFragmentTest.kt ===================================== @@ -13,6 +13,7 @@ import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.doNothing @@ -27,6 +28,7 @@ class SimpleRedirectDialogFragmentTest { private val themeResId = appcompatR.style.Theme_AppCompat_Light
@Test + @Ignore("This will be addressed in another follow up ticket") fun `Dialog confirmed callback is called correctly`() { var onConfirmCalled = false var onCancelCalled = false @@ -104,6 +106,7 @@ class SimpleRedirectDialogFragmentTest { assertFalse(onCancelCalled) }
+ @Suppress("unused") private fun mockFragmentManager(): FragmentManager { val fragmentManager: FragmentManager = mock() val transaction: FragmentTransaction = mock()
===================================== mobile/android/android-components/components/feature/session/src/main/java/mozilla/components/feature/session/SessionUseCases.kt ===================================== @@ -4,7 +4,6 @@
package mozilla.components.feature.session
-import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.action.CrashAction import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.action.LastAccessAction @@ -94,13 +93,6 @@ class SessionUseCases( flags = flags, additionalHeaders = additionalHeaders, ) - // Update the url in content immediately until the engine updates with any new changes to the state. - store.dispatch( - ContentAction.UpdateUrlAction( - loadSessionId, - url, - ), - ) store.dispatch( EngineAction.OptimizedLoadUrlTriggeredAction( loadSessionId,
===================================== mobile/android/android-components/components/feature/session/src/test/java/mozilla/components/feature/session/SessionUseCasesTest.kt ===================================== @@ -11,7 +11,6 @@ import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.engine.EngineMiddleware import mozilla.components.browser.state.selector.findTab -import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.createCustomTab @@ -81,7 +80,6 @@ class SessionUseCasesTest { assertEquals("mozilla", action.tabId) assertEquals("https://getpocket.com", action.url) } - assertEquals("https://getpocket.com", store.state.selectedTab?.content?.url)
useCases.loadUrl("https://www.mozilla.org", LoadUrlFlags.select(LoadUrlFlags.EXTERNAL)) store.waitUntilIdle() @@ -95,7 +93,6 @@ class SessionUseCasesTest { assertEquals("https://www.mozilla.org", action.url) assertEquals(LoadUrlFlags.select(LoadUrlFlags.EXTERNAL), action.flags) } - assertEquals("https://www.mozilla.org", store.state.selectedTab?.content?.url)
useCases.loadUrl("https://firefox.com", store.state.selectedTabId) store.waitUntilIdle() @@ -105,7 +102,6 @@ class SessionUseCasesTest { assertEquals("mozilla", action.tabId) assertEquals("https://firefox.com", action.url) } - assertEquals("https://firefox.com", store.state.selectedTab?.content?.url)
useCases.loadUrl.invoke( "https://developer.mozilla.org",
===================================== mobile/android/android-components/docs/changelog.md ===================================== @@ -109,9 +109,6 @@ permalink: /changelog/ * **feature-customtabs** * Fallback behaviour when failing to open a new window in custom tab will now be loading the URL directly in the same custom tab. [Bug 1832357](https://bugzilla.mozilla.org/show_bug.cgi?id=1832357)
-* **feature-session** - * Update URL in the store immediately when using the optimized load URL code path. - * **tooling-lint** * Added a lint rule to detect when `Response#close` may not have been called. Note: Currently, this rule only runs on Android Components.
===================================== mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/CrashReportingTest.kt ===================================== @@ -83,7 +83,7 @@ class CrashReportingTest : TestSetup() { verifyPageContent(tabCrashMessage) }.openTabDrawer(activityTestRule) { verifyExistingOpenTabs(firstWebPage.title) - verifyExistingOpenTabs("about:crashcontent") + verifyExistingOpenTabs(secondWebPage.title) }.closeTabDrawer { }.goToHomescreen { verifyExistingTopSitesList()
===================================== mobile/android/geckoview/src/main/java/org/mozilla/gecko/Clipboard.java ===================================== @@ -11,6 +11,7 @@ import android.content.ClipboardManager.OnPrimaryClipChangedListener; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.os.Build; +import android.os.PersistableBundle; import android.text.TextUtils; import android.util.Log; import java.io.ByteArrayOutputStream; @@ -133,8 +134,9 @@ public final class Clipboard { * @return true if copy is successful. */ @WrapForJNI(calledFrom = "gecko") - public static boolean setText(final Context context, final CharSequence text) { - return setData(context, ClipData.newPlainText("text", text)); + public static boolean setText( + final Context context, final CharSequence text, final boolean isPrivateData) { + return setData(context, ClipData.newPlainText("text", text), isPrivateData); }
/** @@ -147,8 +149,11 @@ public final class Clipboard { */ @WrapForJNI(calledFrom = "gecko") private static boolean setHTML( - final Context context, final CharSequence text, final String htmlText) { - return setData(context, ClipData.newHtmlText("html", text, htmlText)); + final Context context, + final CharSequence text, + final String htmlText, + final boolean isPrivateData) { + return setData(context, ClipData.newHtmlText("html", text, htmlText), isPrivateData); }
/** @@ -158,11 +163,17 @@ public final class Clipboard { * @param clipData a {@link android.content.ClipData} to set to clipboard * @return true if copy is successful. */ - private static boolean setData(final Context context, final ClipData clipData) { + private static boolean setData( + final Context context, final ClipData clipData, final boolean isPrivateData) { // In API Level 11 and above, CLIPBOARD_SERVICE returns android.content.ClipboardManager, // which is a subclass of android.text.ClipboardManager. final ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + if (isPrivateData && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + final PersistableBundle extras = new PersistableBundle(); + extras.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true); + clipData.getDescription().setExtras(extras); + } try { cm.setPrimaryClip(clipData); } catch (final NullPointerException e) { @@ -228,7 +239,7 @@ public final class Clipboard { @WrapForJNI(calledFrom = "gecko") private static void clear(final Context context) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { - setText(context, null); + setText(context, null, false); return; } // Although we don't know more details of https://crbug.com/1203377, Blink doesn't use
===================================== mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoInputConnection.java ===================================== @@ -70,6 +70,7 @@ import org.mozilla.gecko.util.ThreadUtils; private ExtractedTextRequest mUpdateRequest; private final InputConnection mKeyInputConnection; private CursorAnchorInfo.Builder mCursorAnchorInfoBuilder; + private boolean mIsPrivateBrowsing;
public static SessionTextInput.InputConnectionClient create( final GeckoSession session, @@ -208,12 +209,13 @@ import org.mozilla.gecko.util.ThreadUtils; // If selection is empty, we'll select everything if (selStart == selEnd) { // Fill the clipboard - Clipboard.setText(view.getContext(), editable); + Clipboard.setText(view.getContext(), editable, mIsPrivateBrowsing); editable.clear(); } else { Clipboard.setText( view.getContext(), - editable.subSequence(Math.min(selStart, selEnd), Math.max(selStart, selEnd))); + editable.subSequence(Math.min(selStart, selEnd), Math.max(selStart, selEnd)), + mIsPrivateBrowsing); editable.delete(selStart, selEnd); } break; @@ -231,7 +233,7 @@ import org.mozilla.gecko.util.ThreadUtils; : editable .toString() .substring(Math.min(selStart, selEnd), Math.max(selStart, selEnd)); - Clipboard.setText(view.getContext(), copiedText); + Clipboard.setText(view.getContext(), copiedText, mIsPrivateBrowsing); break; } return true; @@ -603,6 +605,9 @@ import org.mozilla.gecko.util.ThreadUtils; outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_FULLSCREEN; }
+ mIsPrivateBrowsing = + ((outAttrs.imeOptions & InputMethods.IME_FLAG_NO_PERSONALIZED_LEARNING) != 0); + if (DEBUG) { Log.d( LOGTAG,
===================================== toolkit/components/httpsonlyerror/content/errorpage.html ===================================== @@ -70,6 +70,16 @@ inert ></button> </div> + + <p id="explanation-iframe" hidden> + <span data-l10n-id="about-httpsonly-explanation-iframe"></span> + <a + id="mixedContentLearnMoreLink" + target="_blank" + data-l10n-id="about-httpsonly-link-learn-more" + ></a> + </p> + <div class="suggestion-box" hidden> <h2 data-l10n-id="about-httpsonly-suggestion-box-header"></h2> </div>
===================================== toolkit/components/httpsonlyerror/content/errorpage.js ===================================== @@ -29,28 +29,35 @@ function initPage() { document .getElementById("learnMoreLink") .setAttribute("href", baseSupportURL + "https-only-prefs"); + document + .getElementById("mixedContentLearnMoreLink") + .setAttribute("href", baseSupportURL + "mixed-content"); + + const isTopLevel = window.top == window; + if (!isTopLevel) { + for (const id of ["explanation-continue", "goBack", "openInsecure"]) { + document.getElementById(id).remove(); + } + document.getElementById("explanation-iframe").removeAttribute("hidden"); + return; + }
document .getElementById("openInsecure") .addEventListener("click", onOpenInsecureButtonClick); + document + .getElementById("goBack") + .addEventListener("click", onReturnButtonClick);
const delay = RPMGetIntPref("security.dialog_enable_delay", 1000); setTimeout(() => { document.getElementById("openInsecure").removeAttribute("inert"); }, delay);
- if (window.top == window) { - document - .getElementById("goBack") - .addEventListener("click", onReturnButtonClick); - addAutofocus("#goBack", "beforeend"); - } else { - document.getElementById("goBack").remove(); - } + addAutofocus("#goBack", "beforeend");
- const isTopLevel = window.top == window; const hasWWWPrefix = pageUrl.href.startsWith("https://www."); - if (isTopLevel && !hasWWWPrefix) { + if (!hasWWWPrefix) { // HTTPS-Only generally simply replaces http: with https:; // here we additionally try to add www and see if that allows to upgrade the connection if it is top level
===================================== toolkit/locales/en-US/toolkit/about/aboutHttpsOnlyError.ftl ===================================== @@ -12,6 +12,7 @@ about-httpsonly-explanation-question = What could be causing this? about-httpsonly-explanation-nosupport = Most likely, the website simply does not support HTTPS. about-httpsonly-explanation-risk = It’s also possible that an attacker is involved. If you decide to visit the website, you should not enter any sensitive information like passwords, emails, or credit card details. about-httpsonly-explanation-continue = If you continue, HTTPS-Only Mode will be turned off temporarily for this site. +about-httpsonly-explanation-iframe = Due to mixed content blocking, it is not possible to manually allow this frame to load.
about-httpsonly-button-continue-to-site = Continue to HTTP Site about-httpsonly-button-go-back = Go Back
===================================== widget/android/nsClipboard.cpp ===================================== @@ -92,14 +92,16 @@ nsClipboard::SetNativeClipboardData(nsITransferable* aTransferable, return rv; }
+ bool isPrivate = aTransferable->GetIsPrivateData(); + if (!html.IsEmpty() && java::Clipboard::SetHTML(java::GeckoAppShell::GetApplicationContext(), - text, html)) { + text, html, isPrivate)) { return NS_OK; } if (!text.IsEmpty() && java::Clipboard::SetText(java::GeckoAppShell::GetApplicationContext(), - text)) { + text, isPrivate)) { return NS_OK; }
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/f28...