tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 6c6bf22d0f4dbc52e07bfc254b8dfc19a5c72f9b
parent 3d03d297b47a6ce370c6e5ba7450785ccd989e04
Author: Akhil Pindiprolu <apindiprolu@mozilla.com>
Date:   Mon,  3 Nov 2025 21:19:51 +0000

Bug 1970750 - [WebCompat Reporter v2] Add option to preview report r=android-reviewers,android-l10n-reviewers,delphine,007

Differential Revision: https://phabricator.services.mozilla.com/D268965

Diffstat:
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/middleware/WebCompatReporterSubmissionMiddleware.kt | 132++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/middleware/WebCompatReporterTelemetryMiddleware.kt | 1+
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/store/WebCompatReporterStore.kt | 18++++++++++++++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/ui/WebCompatReporter.kt | 51++++++++++++++++++++++++++++++++++++++++++++++++++-
Amobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/ui/WebCompatReporterPreviewSampleJsonData.kt | 44++++++++++++++++++++++++++++++++++++++++++++
Amobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/ui/WebCompatReporterPreviewSheet.kt | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmobile/android/fenix/app/src/main/res/values/strings.xml | 6++++++
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/webcompat/middleware/WebCompatReporterSubmissionMiddlewareTest.kt | 43+++++++++++++++++++++++++++++++++++++++++++
8 files changed, 405 insertions(+), 35 deletions(-)

diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/middleware/WebCompatReporterSubmissionMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/middleware/WebCompatReporterSubmissionMiddleware.kt @@ -6,11 +6,13 @@ package org.mozilla.fenix.webcompat.middleware import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.serialization.json.Json import mozilla.components.browser.state.selector.selectedTab import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.store.BrowserStore import mozilla.components.lib.state.Middleware import mozilla.components.lib.state.MiddlewareContext +import org.json.JSONObject import org.mozilla.fenix.GleanMetrics.BrokenSiteReport import org.mozilla.fenix.GleanMetrics.BrokenSiteReportBrowserInfo import org.mozilla.fenix.GleanMetrics.BrokenSiteReportBrowserInfoApp @@ -57,50 +59,112 @@ class WebCompatReporterSubmissionMiddleware( when (action) { is WebCompatReporterAction.SendReportClicked -> { scope.launch { - val webCompatInfo = webCompatReporterRetrievalService.retrieveInfo() - - webCompatInfo?.let { - val enteredUrlMatchesTabUrl = context.state.enteredUrl == webCompatInfo.url - if (enteredUrlMatchesTabUrl) { - setTabAntiTrackingMetrics( - antiTracking = webCompatInfo.antitracking, - sendBlockedUrls = context.state.includeEtpBlockedUrls, - ) - setTabFrameworksMetrics(frameworks = webCompatInfo.frameworks) - setTabLanguageMetrics(languages = webCompatInfo.languages) - setTabUserAgentMetrics(userAgent = webCompatInfo.userAgent) - } - - setBrowserInfoMetrics(browserInfo = webCompatInfo.browser) - setDevicePixelRatioMetrics(devicePixelRatio = webCompatInfo.devicePixelRatio) - } - setUrlMetrics(url = context.state.enteredUrl) - setReasonMetrics(reason = context.state.reason) - setDescriptionMetrics(description = context.state.problemDescription) - setExperimentMetrics() - - Pings.brokenSiteReport.submit() - context.store.dispatch(WebCompatReporterAction.ReportSubmitted) - appStore.dispatch(AppAction.WebCompatAction.WebCompatReportSent) + handleSendReport(context) + } + } + is WebCompatReporterAction.OpenPreviewClicked -> { + scope.launch { + handleOpenPreviewClicked(context) } } is WebCompatReporterAction.SendMoreInfoClicked -> { scope.launch { - webCompatReporterMoreInfoSender.sendMoreWebCompatInfo( - reason = context.state.reason, - problemDescription = context.state.problemDescription, - enteredUrl = context.state.enteredUrl, - tabUrl = context.state.tabUrl, - engineSession = browserStore.state.selectedTab?.engineState?.engineSession, - ) - - context.store.dispatch(WebCompatReporterAction.SendMoreInfoSubmitted) + handleSendMoreInfoClicked(context) } } else -> {} } } + private suspend fun handleSendReport(context: MiddlewareContext<WebCompatReporterState, WebCompatReporterAction>) { + val webCompatInfo = webCompatReporterRetrievalService.retrieveInfo() + + webCompatInfo?.let { + val enteredUrlMatchesTabUrl = context.state.enteredUrl == webCompatInfo.url + if (enteredUrlMatchesTabUrl) { + setTabAntiTrackingMetrics( + antiTracking = webCompatInfo.antitracking, + sendBlockedUrls = context.state.includeEtpBlockedUrls, + ) + setTabFrameworksMetrics(frameworks = webCompatInfo.frameworks) + setTabLanguageMetrics(languages = webCompatInfo.languages) + setTabUserAgentMetrics(userAgent = webCompatInfo.userAgent) + } + + setBrowserInfoMetrics(browserInfo = webCompatInfo.browser) + setDevicePixelRatioMetrics(devicePixelRatio = webCompatInfo.devicePixelRatio) + } + setUrlMetrics(url = context.state.enteredUrl) + setReasonMetrics(reason = context.state.reason) + setDescriptionMetrics(description = context.state.problemDescription) + setExperimentMetrics() + + Pings.brokenSiteReport.submit() + context.store.dispatch(WebCompatReporterAction.ReportSubmitted) + appStore.dispatch(AppAction.WebCompatAction.WebCompatReportSent) + } + + private suspend fun handleOpenPreviewClicked( + context: MiddlewareContext<WebCompatReporterState, WebCompatReporterAction>, + ) { + val webCompatInfo = webCompatReporterRetrievalService.retrieveInfo() + + val webCompatJSON = generatePreviewJSON(context.state, webCompatInfo) + + context.store.dispatch(WebCompatReporterAction.PreviewJSONUpdated(webCompatJSON.toString())) + } + + private fun generatePreviewJSON( + state: WebCompatReporterState, + webCompatInfo: WebCompatInfoDto?, + ): JSONObject { + return if (webCompatInfo == null) { + JSONObject().apply { + put("enteredUrl", state.enteredUrl) + put("reason", state.reason) + put("problemDescription", state.problemDescription) + } + } else { + val webCompatString = Json.encodeToString(webCompatInfo) + val webCompatJSON = JSONObject(webCompatString).apply { + put("enteredUrl", state.enteredUrl) + put("reason", state.reason) + put("problemDescription", state.problemDescription) + } + + // Note: we are removing the fields from the JSON here because when the user edits the URL in the + // reporter, the tab-scoped diagnostics we collected (anti-tracking info, detected frameworks, + // page languages, and the tab’s user agent) describe the *currently selected tab*, not the URL + // the user chose to report. Browser/device info is kept because it is not origin-scoped. + // If the entered URL matches the tab URL, we keep these fields since they accurately describe + // the page being reported. + if (state.enteredUrl != webCompatInfo.url) { + webCompatJSON.apply { + remove("antitracking") + remove("frameworks") + remove("languages") + remove("userAgent") + } + } + + webCompatJSON + } + } + + private suspend fun handleSendMoreInfoClicked( + context: MiddlewareContext<WebCompatReporterState, WebCompatReporterAction>, + ) { + webCompatReporterMoreInfoSender.sendMoreWebCompatInfo( + reason = context.state.reason, + problemDescription = context.state.problemDescription, + enteredUrl = context.state.enteredUrl, + tabUrl = context.state.tabUrl, + engineSession = browserStore.state.selectedTab?.engineState?.engineSession, + ) + + context.store.dispatch(WebCompatReporterAction.SendMoreInfoSubmitted) + } + private fun setTabAntiTrackingMetrics( antiTracking: WebCompatInfoDto.WebCompatAntiTrackingDto, sendBlockedUrls: Boolean, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/middleware/WebCompatReporterTelemetryMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/middleware/WebCompatReporterTelemetryMiddleware.kt @@ -10,6 +10,7 @@ import mozilla.telemetry.glean.private.NoExtras import org.mozilla.fenix.GleanMetrics.Webcompatreporting import org.mozilla.fenix.webcompat.store.WebCompatReporterAction import org.mozilla.fenix.webcompat.store.WebCompatReporterState +import org.mozilla.fenix.webcompat.store.WebCompatReporterStore /** * A [Middleware] for recording telemetry based on [WebCompatReporterAction]s that are dispatch to the diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/store/WebCompatReporterStore.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/store/WebCompatReporterStore.kt @@ -23,6 +23,7 @@ import org.mozilla.fenix.R * @property reason Specifies the reason that [enteredUrl] is broken. * @property problemDescription Description of the encountered problem. * @property includeEtpBlockedUrls Checks if the user wants to include ETP-blocked URLs in the report. + * @property previewJSON The JSON data of the WebCompatReporter to be displayed in the preview. */ data class WebCompatReporterState( val tabUrl: String = "", @@ -30,6 +31,7 @@ data class WebCompatReporterState( val reason: BrokenSiteReason? = null, val problemDescription: String = "", val includeEtpBlockedUrls: Boolean = false, + val previewJSON: String = "", ) : State { /** @@ -150,6 +152,18 @@ sealed class WebCompatReporterAction : Action { data object ReportSubmitted : WebCompatReporterAction(), NavigationAction /** + * Dispatched when the WebCompat report "Preview Report" button is clicked. + */ + data object OpenPreviewClicked : WebCompatReporterAction() + + /** + * Dispatched when the preview of the report is opened up. + * + * @property previewJSON The data of the WebCompat Report as a JSON string. + */ + data class PreviewJSONUpdated(val previewJSON: String) : WebCompatReporterAction() + + /** * Dispatched when the WebCompat "Send More Info" report has been submitted. */ data object SendMoreInfoSubmitted : WebCompatReporterAction(), NavigationAction @@ -186,6 +200,10 @@ private fun reduce( is WebCompatReporterAction.ReasonChanged -> state.copy(reason = action.newReason) WebCompatReporterAction.Initialized -> state is WebCompatReporterAction.StateRestored -> action.restoredState + is WebCompatReporterAction.OpenPreviewClicked -> state + is WebCompatReporterAction.PreviewJSONUpdated -> state.copy( + previewJSON = action.previewJSON, + ) is WebCompatReporterAction.NavigationAction -> state WebCompatReporterAction.SendReportClicked -> state WebCompatReporterAction.SendMoreInfoClicked -> state diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/ui/WebCompatReporter.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/ui/WebCompatReporter.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.selection.toggleable import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Checkbox import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -30,6 +31,9 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -84,6 +88,8 @@ fun WebCompatReporter( ) { val state by store.observeAsState(store.state) { it } + var previewSheetVisible by remember { mutableStateOf(false) } + BackHandler { store.dispatch(WebCompatReporterAction.BackPressed) } @@ -231,12 +237,46 @@ fun WebCompatReporter( } } + Spacer(modifier = Modifier.height(8.dp)) + + Row( + modifier = Modifier + .clickable { + previewSheetVisible = true + store.dispatch(WebCompatReporterAction.OpenPreviewClicked) + } + .padding(vertical = 8.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = stringResource(id = R.string.webcompat_reporter_preview_report), + modifier = Modifier, + color = FirefoxTheme.colors.textPrimary, + style = FirefoxTheme.typography.subtitle2, + + ) + + Icon( + painter = painterResource(R.drawable.ic_arrowhead_right), + contentDescription = "", + ) + } + + Spacer(modifier = Modifier.height(4.dp)) + + HorizontalDivider() + + Spacer(modifier = Modifier.height(20.dp)) + Row( modifier = Modifier .fillMaxWidth() .padding(vertical = 16.dp), verticalAlignment = Alignment.CenterVertically, ) { + // Note: the "Add more info" button is not meant for Release, so we're only + // enabling it in Beta and Nightly/Debug if (Config.channel.isBeta || Config.channel.isNightlyOrDebug) { Text( text = stringResource(id = R.string.webcompat_reporter_add_more_info), @@ -281,7 +321,16 @@ fun WebCompatReporter( } } } -} + + if (previewSheetVisible) { + WebCompatReporterPreviewSheet( + previewJSON = state.previewJSON, + onDismissRequest = { previewSheetVisible = false }, + onSendClick = { store.dispatch(WebCompatReporterAction.SendReportClicked) }, + isSendButtonEnabled = state.isSubmitEnabled, + ) + } + } /** * Helper function used to obtain the list of dropdown menu items derived from [BrokenSiteReason]. diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/ui/WebCompatReporterPreviewSampleJsonData.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/ui/WebCompatReporterPreviewSampleJsonData.kt @@ -0,0 +1,44 @@ +/* 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.fenix.webcompat.ui + +/** + * Sample WebCompat Json data used to display in the previews of [WebCompatReporterPreviewSheet] + */ +internal object WebCompatReporterPreviewSampleJsonData { + + const val SAMPLE_WEBCOMPAT_JSON_DATA = "{\"browser_info\":{\"app\":{\"app_default_locales\"" + + ":[\"fr\",\"en-US\"],\"default_useragent_string\":\"Mozilla/5.0 (Windows NT 10.0; " + + "Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0\",\"fission_enabled\":true}," + + "\"graphics\":{\"device_pixel_ratio\":\"1\",\"devices_json\":\"[{\\\"vendorID\\\":" + + "\\\"0x8086\\\",\\\"deviceID\\\":\\\"0x0152\\\",\\\"subsysID\\\":\\\"308417aa\\\"}," + + "{\\\"vendorID\\\":\\\"\\\",\\\"deviceID\\\":\\\"\\\",\\\"subsysID\\\":\\\"\\\"}]\"" + + ",\"drivers_json\":\"[{\\\"renderer\\\":\\\"Google Inc. (Intel) --" + + " ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0, D3D11-10.18.10.4252)" + + "\\\",\\\"version\\\":\\\"OpenGL ES 3.0.0 (ANGLE 2.1.19739 git hash: " + + "419cd2c3213b)\\\"},{\\\"renderer\\\":\\\"Google Inc. " + + "(Intel) -- ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0, " + + "D3D11-10.18.10.4252)\\\",\\\"version\\\":\\\"OpenGL ES 3.0.0" + + " (ANGLE 2.1.19739 git hash: 419cd2c3213b)\\\"}]\",\"features_json\":\"" + + "{\\\"HW_COMPOSITING\\\":\\\"available\\\",\\\"D3D11_COMPOSITING\\\"" + + ":\\\"available\\\",\\\"DIRECT2D\\\":\\\"disabled " + + "(Disabled by default)\\\",\\\"D3D11_HW_ANGLE\\\":\\\"available\\\"," + + "\\\"GPU_PROCESS\\\":\\\"available\\\",\\\"WEBRENDER\\\":\\\"available\\\"" + + ",\\\"WEBRENDER_COMPOSITOR\\\":\\\"available\\\",\\\"WEBRENDER_PARTIAL\\\"" + + ":\\\"available\\\",\\\"WEBRENDER_SHADER_CACHE\\\":\\\"available\\\"" + + ",\\\"WEBRENDER_OPTIMIZED_SHADERS\\\":\\\"available\\\"," + + "\\\"WEBRENDER_ANGLE\\\":\\\"available\\\",\\\"WEBRENDER_DCOMP_PRESENT\\\"" + + ":\\\"available\\\",\\\"WEBRENDER_SCISSORED_CACHE_CLEARS\\\"" + + ":\\\"blocklisted (Blocklisted by gfxInfo)\\\",\\\"WEBGPU\\\"" + + ":\\\"blocked (WebGPU cannot be enabled in release or beta)\\\"" + + ",\\\"WINDOW_OCCLUSION\\\":\\\"available\\\"," + + "\\\"VIDEO_HARDWARE_OVERLAY\\\":\\\"available\\\"," + + "\\\"VIDEO_SOFTWARE_OVERLAY\\\":\\\"available\\\",\\\"HW_DECODED_VIDEO_ZERO_COPY\\\"" + + ":\\\"available\\\",\\\"VP8_HW_DECODE\\\":\\\"available\\\",\\\"VP9_HW_DECODE\\\"" + + ":\\\"available\\\",\\\"REUSE_DECODER_DEVICE\\\":\\\"available\\\",\\\"" + + "BACKDROP_FILTER\\\":\\\"blocklisted (#BLOCKLIST_FEATURE_FAILURE_BUG_1785366)\\\"" + + ",\\\"CANVAS_RENDERER_THREAD\\\":\\\"available\\\",\\\"ACCELERATED_CANVAS2D\\\"" + + ":\\\"available\\\",\\\"REMOTE_CANVAS\\\":\\\"blocked (Disabled without Direct2D)\\\"" + + "}\",\"has_touch_screen\":false,\"monitors_json\":\"[{\\\"screenWidth\\\":1920}" +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/ui/WebCompatReporterPreviewSheet.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/webcompat/ui/WebCompatReporterPreviewSheet.kt @@ -0,0 +1,145 @@ +/* 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.fenix.webcompat.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.traversalIndex +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import mozilla.components.compose.base.button.FilledButton +import org.mozilla.fenix.R +import org.mozilla.fenix.compose.BottomSheetHandle +import org.mozilla.fenix.theme.FirefoxTheme + +private val BottomSheetHandleVerticalPadding = 16.dp + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun WebCompatReporterPreviewSheet( + isSendButtonEnabled: Boolean, + previewJSON: String, + onDismissRequest: () -> Unit, + onSendClick: () -> Unit, +) { + ModalBottomSheet( + onDismissRequest = onDismissRequest, + sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), + dragHandle = { + BottomSheetHandle( + onRequestDismiss = onDismissRequest, + contentDescription = stringResource(R.string.a11y_action_label_collapse), + modifier = Modifier + .width(32.dp) + .padding(vertical = BottomSheetHandleVerticalPadding) + .semantics { traversalIndex = -1f }, + color = FirefoxTheme.colors.borderInverted, + ) + }, + containerColor = FirefoxTheme.colors.layer2, + modifier = Modifier.padding(top = 72.dp), + contentWindowInsets = { WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom) }, + ) { + PreviewSheetContent( + isSendButtonEnabled = isSendButtonEnabled, + previewJSON = previewJSON, + onSendClick = onSendClick, + ) + } + } + +@Composable +private fun PreviewSheetContent( + previewJSON: String, + onSendClick: () -> Unit, + isSendButtonEnabled: Boolean, +) { + Column( + modifier = Modifier + .fillMaxSize() + .background( + color = FirefoxTheme.colors.layer2, + ), + horizontalAlignment = Alignment.CenterHorizontally, + + ) { + Text( + text = stringResource(id = R.string.webcompat_reporter_preview_bottom_sheet_header), + color = FirefoxTheme.colors.textPrimary, + style = FirefoxTheme.typography.headline7, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()) + .padding(horizontal = 16.dp), + ) { + Text( + text = previewJSON, + color = FirefoxTheme.colors.textPrimary, + style = FirefoxTheme.typography.body2, + ) + } + FilledButton( + text = stringResource(id = R.string.webcompat_reporter_preview_bottom_sheet_send), + onClick = onSendClick, + modifier = Modifier + .fillMaxWidth() + .background(color = FirefoxTheme.colors.layer1) + .padding(all = 16.dp), + enabled = isSendButtonEnabled, + ) + } +} + +// Note: run the interactive preview for this composable to see the bottom sheet behaviour of the report preview. +@Composable +@PreviewLightDark +private fun WebCompatReporterSheetPreview() { + FirefoxTheme { + WebCompatReporterPreviewSheet( + isSendButtonEnabled = true, + previewJSON = WebCompatReporterPreviewSampleJsonData.SAMPLE_WEBCOMPAT_JSON_DATA, + onDismissRequest = {}, + onSendClick = {}, + ) + } +} + +@Composable +@PreviewLightDark +private fun SheetContentPreview() { + FirefoxTheme { + PreviewSheetContent( + previewJSON = WebCompatReporterPreviewSampleJsonData.SAMPLE_WEBCOMPAT_JSON_DATA, + onSendClick = {}, + isSendButtonEnabled = true, + ) + } +} diff --git a/mobile/android/fenix/app/src/main/res/values/strings.xml b/mobile/android/fenix/app/src/main/res/values/strings.xml @@ -1574,6 +1574,12 @@ <string name="webcompat_reporter_etp_checkbox_text">Send URLs blocked by tracking protection</string> <!-- Description text for the checkbox on the “Report broken site” screen for sending URLS blocked by Enhanced Tracking Protection. It informs the user when checked, the report will include the list of URLs that were blocked by Enhanced Tracking Protection on the current page.--> <string name="webcompat_reporter_etp_checkbox_description">Enhanced Tracking Protection may block trackers and scripts that some websites need to work properly.</string> + <!-- The button text for the preview report button to view a preview of the report. --> + <string name="webcompat_reporter_preview_report">Preview Report</string> + <!-- The title shown at the top of the WebCompat Report preview panel. This appears after the user taps the “Preview” button before sending a report. --> + <string name="webcompat_reporter_preview_bottom_sheet_header">Report Preview</string> + <!-- Label for the “Send” button shown in the WebCompat Report preview panel, which the user taps to submit the report. --> + <string name="webcompat_reporter_preview_bottom_sheet_send">Send</string> <!-- These reason strings are dropdown options on a WebCompat reporter form, indicating what is broken on the site. --> <!-- Broken site reason text for site doesn’t load --> diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/webcompat/middleware/WebCompatReporterSubmissionMiddlewareTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/webcompat/middleware/WebCompatReporterSubmissionMiddlewareTest.kt @@ -8,6 +8,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.addJsonObject import kotlinx.serialization.json.buildJsonArray @@ -24,6 +25,7 @@ import mozilla.components.support.test.rule.MainCoroutineRule import mozilla.telemetry.glean.private.NoReasonCodes import mozilla.telemetry.glean.private.PingType import mozilla.telemetry.glean.testing.GleanTestRule +import org.json.JSONObject import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNotNull @@ -624,6 +626,47 @@ class WebCompatReporterSubmissionMiddlewareTest { captureActionsMiddleware.assertFirstAction(WebCompatReporterAction.SendMoreInfoSubmitted::class) } + @Test + fun `WHEN open preview is clicked AND enteredUrl matches tab url THEN preview contains full raw JSON plus form fields`() = runTest { + val capture = CaptureActionsMiddleware<WebCompatReporterState, WebCompatReporterAction>() + + val store = WebCompatReporterStore( + initialState = WebCompatReporterState( + tabUrl = "https://www.mozilla.org", + enteredUrl = "https://www.mozilla.org", + reason = WebCompatReporterState.BrokenSiteReason.Slow, + problemDescription = "", + ), + middleware = listOf( + capture, + createMiddleware( + browserStore = BrowserStore( + BrowserState( + tabs = listOf(createTab("https://www.mozilla.org", id = "t1")), selectedTabId = "t1", + ), + ), + service = FakeWebCompatReporterRetrievalService(), + webCompatReporterMoreInfoSender = FakeWebCompatReporterMoreInfoSender(), + ), + ), + ) + + store.dispatch(WebCompatReporterAction.OpenPreviewClicked) + + val actual = store.state.previewJSON + val expected = JSONObject( + Json.encodeToString( + (FakeWebCompatReporterRetrievalService()).retrieveInfo(), + ), + ).apply { + put("enteredUrl", "https://www.mozilla.org") + put("reason", WebCompatReporterState.BrokenSiteReason.Slow) + put("problemDescription", "") + } + + assertEquals(expected.toString(), actual) + } + private fun createStore( enteredUrl: String = "https://www.mozilla.org", service: WebCompatReporterRetrievalService = FakeWebCompatReporterRetrievalService(),