@@ -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)
@@ -319,6 +320,24 @@ class ImportGoogleBookmarksWebFlowFragment :
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,11 @@ 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
+ logcat(WARN ) { " Bookmark-import: web flow is ending, ignoring page started" }
381
+ return
382
+ }
383
+
360
384
viewModel.onPageStarted(url)
361
385
lifecycleScope.launch(dispatchers.main()) {
362
386
binding?.let {
@@ -384,6 +408,8 @@ class ImportGoogleBookmarksWebFlowFragment :
384
408
385
409
private fun exitFlowAsSuccess (bookmarkCount : Int ) {
386
410
logcat { " Bookmark-import: Reporting import success with bookmarkCount: $bookmarkCount " }
411
+ onWebFlowEnding()
412
+
387
413
lifecycleScope.launch {
388
414
repeatOnLifecycle(Lifecycle .State .STARTED ) {
389
415
dismissCancellationDialog()
@@ -397,6 +423,7 @@ class ImportGoogleBookmarksWebFlowFragment :
397
423
398
424
private fun exitFlowAsCancellation (stage : String ) {
399
425
logcat { " Bookmark-import: Flow cancelled at stage: $stage " }
426
+ onWebFlowEnding()
400
427
401
428
lifecycleScope.launch {
402
429
repeatOnLifecycle(Lifecycle .State .STARTED ) {
@@ -413,6 +440,7 @@ class ImportGoogleBookmarksWebFlowFragment :
413
440
414
441
private fun exitFlowAsError (reason : UserCannotImportReason ) {
415
442
logcat { " Bookmark-import: Flow error at stage: ${reason.mapToStage()} " }
443
+ onWebFlowEnding()
416
444
417
445
lifecycleScope.launch {
418
446
repeatOnLifecycle(Lifecycle .State .STARTED ) {
@@ -426,6 +454,17 @@ class ImportGoogleBookmarksWebFlowFragment :
426
454
}
427
455
}
428
456
457
+ /* *
458
+ * Does a best-effort to attempt to stop the web flow from any further processing.
459
+ */
460
+ private fun onWebFlowEnding () {
461
+ webFlowIsEnding = true
462
+ binding?.webView?.run {
463
+ stopLoading()
464
+ loadUrl(" about:blank" )
465
+ }
466
+ }
467
+
429
468
private suspend fun showCredentialChooserDialog (
430
469
originalUrl : String ,
431
470
credentials : List <LoginCredentials >,
@@ -438,13 +477,12 @@ class ImportGoogleBookmarksWebFlowFragment :
438
477
return @withContext
439
478
}
440
479
441
- val dialog =
442
- credentialAutofillDialogFactory.autofillSelectCredentialsDialog(
443
- url,
444
- credentials,
445
- triggerType,
446
- CUSTOM_FLOW_TAB_ID ,
447
- )
480
+ val dialog = credentialAutofillDialogFactory.autofillSelectCredentialsDialog(
481
+ url,
482
+ credentials,
483
+ triggerType,
484
+ CUSTOM_FLOW_TAB_ID ,
485
+ )
448
486
dialog.show(childFragmentManager, SELECT_CREDENTIALS_FRAGMENT_TAG )
449
487
}
450
488
}
@@ -548,8 +586,9 @@ class ImportGoogleBookmarksWebFlowFragment :
548
586
549
587
private fun UserCannotImportReason.mapToStage (): String =
550
588
when (this ) {
551
- DownloadError -> " zip-download-error"
552
- ErrorParsingBookmarks -> " zip-parse-error"
553
- Unknown -> " import-error-unknown"
554
- WebViewError -> " webview-error"
589
+ is DownloadError -> " zip-download-error"
590
+ is ErrorParsingBookmarks -> " zip-parse-error"
591
+ is Unknown -> " import-error-unknown"
592
+ is WebViewError -> " webview-error"
593
+ is WebAutomationError -> " web-automation-step-failure-${this .step} "
555
594
}
0 commit comments