@@ -33,6 +33,7 @@ import androidx.lifecycle.lifecycleScope
33
33
import androidx.lifecycle.repeatOnLifecycle
34
34
import androidx.webkit.WebViewCompat
35
35
import com.duckduckgo.anvil.annotations.InjectWith
36
+ import com.duckduckgo.autofill.api.AutofillFeature
36
37
import com.duckduckgo.autofill.api.AutofillFragmentResultsPlugin
37
38
import com.duckduckgo.autofill.api.BrowserAutofill
38
39
import com.duckduckgo.autofill.api.CredentialAutofillDialogFactory
@@ -66,6 +67,7 @@ import com.duckduckgo.autofill.impl.importing.takeout.webflow.ImportGoogleBookma
66
67
import com.duckduckgo.autofill.impl.importing.takeout.webflow.UserCannotImportReason.DownloadError
67
68
import com.duckduckgo.autofill.impl.importing.takeout.webflow.UserCannotImportReason.ErrorParsingBookmarks
68
69
import com.duckduckgo.autofill.impl.importing.takeout.webflow.UserCannotImportReason.Unknown
70
+ import com.duckduckgo.autofill.impl.importing.takeout.webflow.UserCannotImportReason.WebAutomationError
69
71
import com.duckduckgo.autofill.impl.importing.takeout.webflow.UserCannotImportReason.WebViewError
70
72
import com.duckduckgo.autofill.impl.jsbridge.request.SupportedAutofillInputSubType
71
73
import com.duckduckgo.autofill.impl.jsbridge.request.SupportedAutofillInputSubType.PASSWORD
@@ -123,8 +125,12 @@ class ImportGoogleBookmarksWebFlowFragment :
123
125
@Inject
124
126
lateinit var browserAutofillConfigurator: InternalBrowserAutofillConfigurator
125
127
128
+ @Inject
129
+ lateinit var autofillFeature: AutofillFeature
130
+
126
131
private var binding: FragmentImportGoogleBookmarksWebflowBinding ? = null
127
132
private var cancellationDialog: DaxAlertDialog ? = null
133
+ private var webFlowIsEnding = false
128
134
129
135
private val viewModel by lazy {
130
136
ViewModelProvider (requireActivity(), viewModelFactory)[ImportGoogleBookmarksWebFlowViewModel ::class .java]
@@ -195,16 +201,12 @@ class ImportGoogleBookmarksWebFlowFragment :
195
201
// Inject null to indicate no credentials available
196
202
browserAutofill.injectCredentials(null )
197
203
}
198
- is PromptUserToSelectFromStoredCredentials ->
199
- showCredentialChooserDialog(
200
- command.originalUrl,
201
- command.credentials,
202
- command.triggerType,
203
- )
204
- is ExitFlowWithSuccess -> {
205
- logcat { " Bookmark-import: ExitFlowWithSuccess received with count: ${command.importedCount} " }
206
- exitFlowAsSuccess(command.importedCount)
207
- }
204
+ is PromptUserToSelectFromStoredCredentials -> showCredentialChooserDialog(
205
+ command.originalUrl,
206
+ command.credentials,
207
+ command.triggerType,
208
+ )
209
+ is ExitFlowWithSuccess -> exitFlowAsSuccess(command.importedCount)
208
210
is ExitFlowAsFailure -> exitFlowAsError(command.reason)
209
211
is PromptUserToConfirmFlowCancellation -> askUserToConfirmCancellation()
210
212
}
@@ -223,12 +225,11 @@ class ImportGoogleBookmarksWebFlowFragment :
223
225
return @withContext
224
226
}
225
227
226
- val credentials =
227
- LoginCredentials (
228
- domain = url,
229
- username = username,
230
- password = password,
231
- )
228
+ val credentials = LoginCredentials (
229
+ domain = url,
230
+ username = username,
231
+ password = password,
232
+ )
232
233
233
234
logcat { " Injecting re-authentication credentials" }
234
235
browserAutofill.injectCredentials(credentials)
@@ -311,14 +312,32 @@ class ImportGoogleBookmarksWebFlowFragment :
311
312
}
312
313
}
313
314
314
- @SuppressLint(" RequiresFeature" , " AddDocumentStartJavaScriptUsage" )
315
+ @SuppressLint(" RequiresFeature" , " AddDocumentStartJavaScriptUsage" , " AddWebMessageListenerUsage " )
315
316
private suspend fun configureBookmarkImportJavascript (webView : WebView ) {
316
317
if (importBookmarkConfig.getConfig().canInjectJavascript) {
317
318
val script = googleImporterScriptLoader.getScriptForBookmarkImport()
318
319
WebViewCompat .addDocumentStartJavaScript(webView, script, setOf (" *" ))
319
320
} else {
320
321
logcat(WARN ) { " Bookmark-import: Not able to inject bookmark import JavaScript" }
321
322
}
323
+
324
+ val canAddMessageListener = withContext(dispatchers.io()) {
325
+ autofillFeature.canUseWebMessageListenerDuringBookmarkImport().isEnabled()
326
+ }
327
+
328
+ if (canAddMessageListener) {
329
+ WebViewCompat .addWebMessageListener(webView, " ddgBookmarkImport" , setOf (" *" )) { _, message, sourceOrigin, _, _ ->
330
+ if (webFlowIsEnding) {
331
+ logcat(WARN ) { " Bookmark-import: web flow is ending, ignoring message" }
332
+ return @addWebMessageListener
333
+ }
334
+
335
+ val data = message.data ? : return @addWebMessageListener
336
+ viewModel.onWebMessageReceived(data)
337
+ }
338
+ } else {
339
+ logcat(WARN ) { " Bookmark-import: Not able to add WebMessage listener for bookmark import" }
340
+ }
322
341
}
323
342
324
343
private fun initialiseToolbar () {
@@ -357,6 +376,10 @@ class ImportGoogleBookmarksWebFlowFragment :
357
376
private fun getToolbar () = (activity as ImportGoogleBookmarksWebFlowActivity ).binding.includeToolbar.toolbar
358
377
359
378
override fun onPageStarted (url : String? ) {
379
+ if (webFlowIsEnding) {
380
+ return
381
+ }
382
+
360
383
viewModel.onPageStarted(url)
361
384
lifecycleScope.launch(dispatchers.main()) {
362
385
binding?.let {
@@ -384,6 +407,8 @@ class ImportGoogleBookmarksWebFlowFragment :
384
407
385
408
private fun exitFlowAsSuccess (bookmarkCount : Int ) {
386
409
logcat { " Bookmark-import: Reporting import success with bookmarkCount: $bookmarkCount " }
410
+ onWebFlowEnding()
411
+
387
412
lifecycleScope.launch {
388
413
repeatOnLifecycle(Lifecycle .State .STARTED ) {
389
414
dismissCancellationDialog()
@@ -397,6 +422,7 @@ class ImportGoogleBookmarksWebFlowFragment :
397
422
398
423
private fun exitFlowAsCancellation (stage : String ) {
399
424
logcat { " Bookmark-import: Flow cancelled at stage: $stage " }
425
+ onWebFlowEnding()
400
426
401
427
lifecycleScope.launch {
402
428
repeatOnLifecycle(Lifecycle .State .STARTED ) {
@@ -413,6 +439,7 @@ class ImportGoogleBookmarksWebFlowFragment :
413
439
414
440
private fun exitFlowAsError (reason : UserCannotImportReason ) {
415
441
logcat { " Bookmark-import: Flow error at stage: ${reason.mapToStage()} " }
442
+ onWebFlowEnding()
416
443
417
444
lifecycleScope.launch {
418
445
repeatOnLifecycle(Lifecycle .State .STARTED ) {
@@ -426,6 +453,17 @@ class ImportGoogleBookmarksWebFlowFragment :
426
453
}
427
454
}
428
455
456
+ /* *
457
+ * Does a best-effort to attempt to stop the web flow from any further processing.
458
+ */
459
+ private fun onWebFlowEnding () {
460
+ webFlowIsEnding = true
461
+ binding?.webView?.run {
462
+ stopLoading()
463
+ loadUrl(" about:blank" )
464
+ }
465
+ }
466
+
429
467
private suspend fun showCredentialChooserDialog (
430
468
originalUrl : String ,
431
469
credentials : List <LoginCredentials >,
@@ -438,13 +476,12 @@ class ImportGoogleBookmarksWebFlowFragment :
438
476
return @withContext
439
477
}
440
478
441
- val dialog =
442
- credentialAutofillDialogFactory.autofillSelectCredentialsDialog(
443
- url,
444
- credentials,
445
- triggerType,
446
- CUSTOM_FLOW_TAB_ID ,
447
- )
479
+ val dialog = credentialAutofillDialogFactory.autofillSelectCredentialsDialog(
480
+ url,
481
+ credentials,
482
+ triggerType,
483
+ CUSTOM_FLOW_TAB_ID ,
484
+ )
448
485
dialog.show(childFragmentManager, SELECT_CREDENTIALS_FRAGMENT_TAG )
449
486
}
450
487
}
@@ -548,8 +585,9 @@ class ImportGoogleBookmarksWebFlowFragment :
548
585
549
586
private fun UserCannotImportReason.mapToStage (): String =
550
587
when (this ) {
551
- DownloadError -> " zip-download-error"
552
- ErrorParsingBookmarks -> " zip-parse-error"
553
- Unknown -> " import-error-unknown"
554
- WebViewError -> " webview-error"
588
+ is DownloadError -> " zip-download-error"
589
+ is ErrorParsingBookmarks -> " zip-parse-error"
590
+ is Unknown -> " import-error-unknown"
591
+ is WebViewError -> " webview-error"
592
+ is WebAutomationError -> " web-automation-step-failure-${this .step} "
555
593
}
0 commit comments