Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Autofill password import] Misc fixes #1184

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
130 changes: 111 additions & 19 deletions injected/src/features/autofill-password-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { DDGProxy, DDGReflect, withExponentialBackoff } from '../utils'

const ANIMATION_DURATION_MS = 1000
const ANIMATION_ITERATIONS = Infinity
const BACKGROUND_COLOR_START = 'rgba(85, 127, 243, 0.10)'
const BACKGROUND_COLOR_END = 'rgba(85, 127, 243, 0.25)'
const OVERLAY_ID = 'ddg-password-import-overlay'

/**
* This feature is responsible for animating some buttons passwords.google.com,
Expand All @@ -21,8 +24,14 @@ export default class AutofillPasswordImport extends ContentFeature {
*/
get settingsButtonStyle () {
return {
scale: 1,
backgroundColor: 'rgba(0, 39, 142, 0.5)'
transform: {
start: 'scale(0.90)',
mid: 'scale(0.96)'
},
zIndex: '984',
borderRadius: '100%',
offsetLeft: 0.03,
offsetTop: 0.03
}
}

Expand All @@ -31,8 +40,14 @@ export default class AutofillPasswordImport extends ContentFeature {
*/
get exportButtonStyle () {
return {
scale: 1.01,
backgroundColor: 'rgba(0, 39, 142, 0.5)'
transform: {
start: 'scale(1)',
mid: 'scale(1.01)'
},
zIndex: '984',
borderRadius: '100%',
offsetLeft: 0,
offsetTop: 0
}
}

Expand All @@ -41,15 +56,21 @@ export default class AutofillPasswordImport extends ContentFeature {
*/
get signInButtonStyle () {
return {
scale: 1.5,
backgroundColor: 'rgba(0, 39, 142, 0.5)'
transform: {
start: 'scale(1)',
mid: 'scale(1.3, 1.5)'
},
zIndex: '999',
borderRadius: '2px',
offsetLeft: 0.08,
offsetTop: 0.05
}
}

/**
* Takes a path and returns the element and style to animate.
* @param {string} path
* @returns {Promise<{element: HTMLElement|Element, style: any, shouldTap: boolean}|null>}
* @returns {Promise<{element: HTMLElement|Element, style: any, shouldTap: boolean, shouldWatchForRemoval: boolean}|null>}
*/
async getElementAndStyleFromPath (path) {
if (path === '/') {
Expand All @@ -58,7 +79,8 @@ export default class AutofillPasswordImport extends ContentFeature {
? {
style: this.settingsButtonStyle,
element,
shouldTap: this.#settingsButtonSettings?.shouldAutotap ?? false
shouldTap: this.#settingsButtonSettings?.shouldAutotap ?? false,
shouldWatchForRemoval: false
}
: null
} else if (path === '/options') {
Expand All @@ -67,7 +89,8 @@ export default class AutofillPasswordImport extends ContentFeature {
? {
style: this.exportButtonStyle,
element,
shouldTap: this.#exportButtonSettings?.shouldAutotap ?? false
shouldTap: this.#exportButtonSettings?.shouldAutotap ?? false,
shouldWatchForRemoval: true
}
: null
} else if (path === '/intro') {
Expand All @@ -76,29 +99,88 @@ export default class AutofillPasswordImport extends ContentFeature {
? {
style: this.signInButtonStyle,
element,
shouldTap: this.#signInButtonSettings?.shouldAutotap ?? false
shouldTap: this.#signInButtonSettings?.shouldAutotap ?? false,
shouldWatchForRemoval: false
}
: null
} else {
return null
}
}

hasNoOtherSiblings (element) {
return element.parentNode && element.parentNode.children.length === 1
}

removeOverlayIfNeeded () {
const existingOverlay = document.getElementById(OVERLAY_ID)
if (existingOverlay != null) {
existingOverlay.style.display = 'none'
existingOverlay.remove()
}
}

insertOverlayElement (mainElement, style) {
this.removeOverlayIfNeeded()

const overlay = document.createElement('div')
overlay.setAttribute('id', OVERLAY_ID)
const svgElement = mainElement.parentNode?.querySelector('svg') ?? mainElement.querySelector('svg')

const isRound = style.borderRadius === '100%'
jonathanKingston marked this conversation as resolved.
Show resolved Hide resolved
const elementToCenterOn = isRound ? svgElement : mainElement
const { top, left, width, height } = elementToCenterOn.getBoundingClientRect()
overlay.style.position = 'absolute'

overlay.style.top = `calc(${top}px + ${window.scrollY}px - ${isRound ? height / 2 : 0}px - 1px - ${style.offsetTop}em)`
overlay.style.left = `calc(${left}px + ${window.scrollX}px - ${isRound ? width / 2 : 0}px - 1px - ${style.offsetLeft}em)`

const mainElementRect = mainElement.getBoundingClientRect()
overlay.style.width = `${mainElementRect.width}px`
overlay.style.height = `${mainElementRect.height}px`
overlay.style.zIndex = style.zIndex

// Ensure overlay is non-interactive
overlay.style.pointerEvents = 'none'

// insert in document.body
document.body.appendChild(overlay)
return overlay
}

observeElementRemoval (element, onRemoveCallback) {
// Set up the mutation observer
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
// Check if the element has been removed from its parent
if (mutation.type === 'childList' && !document.contains(element)) {
// Element has been removed
onRemoveCallback()
observer.disconnect() // Stop observing
}
})
})

// Start observing the parent node for child list changes
observer.observe(document.body, { childList: true, subtree: true })
}

/**
* Moves the element into view and animates it.
* @param {HTMLElement|Element} element
* @param {any} style
*/
animateElement (element, style) {
element.scrollIntoView({
const overlay = this.insertOverlayElement(element, style)
overlay.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center'
}) // Scroll into view
const keyframes = [
{ backgroundColor: 'rgba(0, 0, 255, 0)', offset: 0, borderRadius: '2px' }, // Start: transparent
{ backgroundColor: style.backgroundColor, offset: 0.5, borderRadius: '2px', transform: `scale(${style.scale})` }, // Midpoint: blue with 50% opacity
{ backgroundColor: 'rgba(0, 0, 255, 0)', borderRadius: '2px', offset: 1 } // End: transparent
{ backgroundColor: BACKGROUND_COLOR_START, offset: 0, borderRadius: style.borderRadius, border: `1px solid ${BACKGROUND_COLOR_START}`, transform: style.transform.start }, // Start: 10% blue
{ backgroundColor: BACKGROUND_COLOR_END, offset: 0.5, borderRadius: style.borderRadius, border: `1px solid ${BACKGROUND_COLOR_END}`, transform: style.transform.mid, transformOrigin: 'center' }, // Middle: 25% blue
{ backgroundColor: BACKGROUND_COLOR_START, offset: 1, borderRadius: style.borderRadius, border: `1px solid ${BACKGROUND_COLOR_START}`, transform: style.transform.start } // End: 10% blue
]

// Define the animation options
Expand All @@ -108,7 +190,7 @@ export default class AutofillPasswordImport extends ContentFeature {
}

// Apply the animation to the element
element.animate(keyframes, options)
overlay.animate(keyframes, options)
}

autotapElement (element) {
Expand Down Expand Up @@ -162,12 +244,22 @@ export default class AutofillPasswordImport extends ContentFeature {
this.#settingsButtonSettings?.path,
this.#signInButtonSettings?.path
]
if (supportedPaths.indexOf(path)) {
this.removeOverlayIfNeeded()
if (supportedPaths.includes(path)) {
try {
const { element, style, shouldTap } = await this.getElementAndStyleFromPath(path) ?? {}
const { element, style, shouldTap, shouldWatchForRemoval } = await this.getElementAndStyleFromPath(path) ?? {}
if (element != null) {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
shouldTap ? this.autotapElement(element) : this.animateElement(element, style)
if (shouldTap) {
this.autotapElement(element)
} else {
setTimeout(() => this.animateElement(element, style), 300)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make this 300 a constant, mainly to document what the wait is for a little bit (comment is probably unnecessary with a well named var).

}
if (shouldWatchForRemoval) {
// Sometimes navigation events are not triggered, then we need to watch for removal
this.observeElementRemoval(element, () => {
this.removeOverlayIfNeeded()
})
}
}
} catch {
console.error('password-import: handleElementForPath failed for path:', path)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class UpdateNotificationService {
*/
dismiss () {
this.ntp.messaging.notify('updateNotification_dismiss')
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some new eslint changes seem to have forced this when I did a lint-fix

this.dataService.update(_old => {
return { content: null }
})
Expand Down
Loading