@@ -20,7 +20,7 @@ export const DELAY_BEFORE_ANIMATION = 300;
2020/**
2121 * @typedef ElementConfig
2222 * @property {HTMLElement|Element|SVGElement } element
23- * @property {ButtonAnimationStyle } animationStyle
23+ * @property {ButtonAnimationStyle|null } animationStyle
2424 * @property {boolean } shouldTap
2525 * @property {boolean } shouldWatchForRemoval
2626 * @property {boolean } tapOnce
@@ -40,6 +40,8 @@ export default class AutofillPasswordImport extends ContentFeature {
4040
4141 #signInButtonSettings;
4242
43+ #exportConfirmButtonSettings;
44+
4345 /** @type {HTMLElement|Element|SVGElement|null } */
4446 #elementToCenterOn;
4547
@@ -130,6 +132,50 @@ export default class AutofillPasswordImport extends ContentFeature {
130132 return this . #domLoaded;
131133 }
132134
135+ /**
136+ * @returns {Promise<Element|HTMLElement|null> }
137+ */
138+ async runWithRetry ( fn ) {
139+ try {
140+ return await withExponentialBackoff ( fn ) ;
141+ } catch {
142+ return null ;
143+ }
144+ }
145+
146+ /**
147+ * @returns {Promise<ElementConfig | null> }
148+ */
149+ async getExportConfirmElementAndStyle ( ) {
150+ const exportConfirmElement = await this . findExportConfirmElement ( ) ;
151+ const shouldAutotap = this . #exportConfirmButtonSettings?. shouldAutotap && exportConfirmElement != null ;
152+ return shouldAutotap
153+ ? {
154+ animationStyle : null ,
155+ element : exportConfirmElement ,
156+ shouldTap : true ,
157+ shouldWatchForRemoval : false ,
158+ tapOnce : false ,
159+ }
160+ : null ;
161+ }
162+
163+ /**
164+ * @returns {Promise<ElementConfig | null> }
165+ */
166+ async getExportElementAndStyle ( ) {
167+ const element = await this . findExportElement ( ) ;
168+ return element != null
169+ ? {
170+ animationStyle : this . exportButtonAnimationStyle ,
171+ element,
172+ shouldTap : this . #exportButtonSettings?. shouldAutotap ?? false ,
173+ shouldWatchForRemoval : true ,
174+ tapOnce : true ,
175+ }
176+ : null ;
177+ }
178+
133179 /**
134180 * Takes a path and returns the element and style to animate.
135181 * @param {string } path
@@ -148,16 +194,14 @@ export default class AutofillPasswordImport extends ContentFeature {
148194 }
149195 : null ;
150196 } else if ( path === '/options' ) {
151- const element = await this . findExportElement ( ) ;
152- return element != null
153- ? {
154- animationStyle : this . exportButtonAnimationStyle ,
155- element,
156- shouldTap : this . #exportButtonSettings?. shouldAutotap ?? false ,
157- shouldWatchForRemoval : true ,
158- tapOnce : true ,
159- }
160- : null ;
197+ // If we have found the popup element, then we return that early.
198+ const isExportButtonTapped =
199+ this . currentElementConfig ?. element != null && this . #tappedElements. has ( this . currentElementConfig ?. element ) ;
200+ if ( isExportButtonTapped ) {
201+ return await this . getExportConfirmElementAndStyle ( ) ;
202+ } else {
203+ return await this . getExportElementAndStyle ( ) ;
204+ }
161205 } else if ( path === '/intro' ) {
162206 const element = await this . findSignInButton ( ) ;
163207 return element != null
@@ -338,6 +382,10 @@ export default class AutofillPasswordImport extends ContentFeature {
338382 element . click ( ) ;
339383 }
340384
385+ async findExportConfirmElement ( ) {
386+ return await this . runWithRetry ( ( ) => document . querySelector ( this . exportConfirmButtonSelector ) ) ;
387+ }
388+
341389 /**
342390 * On passwords.google.com the export button is in a container that is quite ambiguious.
343391 * To solve for that we first try to find the container and then the button inside it.
@@ -354,7 +402,7 @@ export default class AutofillPasswordImport extends ContentFeature {
354402 return document . querySelector ( this . exportButtonLabelTextSelector ) ;
355403 } ;
356404
357- return await withExponentialBackoff ( ( ) => findInContainer ( ) ?? findWithLabel ( ) ) ;
405+ return await this . runWithRetry ( ( ) => findInContainer ( ) ?? findWithLabel ( ) ) ;
358406 }
359407
360408 /**
@@ -365,14 +413,14 @@ export default class AutofillPasswordImport extends ContentFeature {
365413 const settingsButton = document . querySelector ( this . settingsButtonSelector ) ;
366414 return settingsButton ;
367415 } ;
368- return await withExponentialBackoff ( fn ) ;
416+ return await this . runWithRetry ( fn ) ;
369417 }
370418
371419 /**
372420 * @returns {Promise<HTMLElement|Element|null> }
373421 */
374422 async findSignInButton ( ) {
375- return await withExponentialBackoff ( ( ) => document . querySelector ( this . signinButtonSelector ) ) ;
423+ return await this . runWithRetry ( ( ) => document . querySelector ( this . signinButtonSelector ) ) ;
376424 }
377425
378426 /**
@@ -390,7 +438,8 @@ export default class AutofillPasswordImport extends ContentFeature {
390438 setCurrentElementConfig ( config ) {
391439 if ( config != null ) {
392440 this . #currentElementConfig = config ;
393- this . setElementToCenterOn ( config . element , config . animationStyle ) ;
441+
442+ if ( config . animationStyle != null ) this . setElementToCenterOn ( config . element , config . animationStyle ) ;
394443 }
395444 }
396445
@@ -400,7 +449,12 @@ export default class AutofillPasswordImport extends ContentFeature {
400449 * @returns {boolean }
401450 */
402451 isSupportedPath ( path ) {
403- return [ this . #exportButtonSettings?. path , this . #settingsButtonSettings?. path , this . #signInButtonSettings?. path ] . includes ( path ) ;
452+ return [
453+ this . #exportButtonSettings?. path ,
454+ this . #settingsButtonSettings?. path ,
455+ this . #signInButtonSettings?. path ,
456+ this . #exportConfirmButtonSettings?. path ,
457+ ] . includes ( path ) ;
404458 }
405459
406460 async handlePath ( path ) {
@@ -426,12 +480,14 @@ export default class AutofillPasswordImport extends ContentFeature {
426480 */
427481 async animateOrTapElement ( ) {
428482 const { element, animationStyle, shouldTap, shouldWatchForRemoval } = this . currentElementConfig ?? { } ;
429- if ( element != null && animationStyle != null ) {
483+ if ( element != null ) {
430484 if ( shouldTap ) {
431485 this . autotapElement ( element ) ;
432486 } else {
433- await this . domLoaded ;
434- this . animateElement ( element , animationStyle ) ;
487+ if ( animationStyle != null ) {
488+ await this . domLoaded ;
489+ this . animateElement ( element , animationStyle ) ;
490+ }
435491 }
436492 if ( shouldWatchForRemoval ) {
437493 // Sometimes navigation events are not triggered, then we need to watch for removal
@@ -449,6 +505,13 @@ export default class AutofillPasswordImport extends ContentFeature {
449505 return this . #exportButtonSettings?. selectors ?. join ( ',' ) ;
450506 }
451507
508+ /**
509+ * @returns {string }
510+ */
511+ get exportConfirmButtonSelector ( ) {
512+ return this . #exportConfirmButtonSettings?. selectors ?. join ( ',' ) ;
513+ }
514+
452515 /**
453516 * @returns {string }
454517 */
@@ -488,6 +551,7 @@ export default class AutofillPasswordImport extends ContentFeature {
488551 this . #exportButtonSettings = this . getFeatureSetting ( 'exportButton' ) ;
489552 this . #signInButtonSettings = this . getFeatureSetting ( 'signInButton' ) ;
490553 this . #settingsButtonSettings = this . getFeatureSetting ( 'settingsButton' ) ;
554+ this . #exportConfirmButtonSettings = this . getFeatureSetting ( 'exportConfirmButton' ) ;
491555 }
492556
493557 urlChanged ( ) {
0 commit comments