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

[Do not merge, experimental] chore:bundle android-for-frames separately #1070

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10,662 changes: 10,662 additions & 0 deletions build/firefox/inject.js

Large diffs are not rendered by default.

21,215 changes: 21,215 additions & 0 deletions build/integration/contentScope.js

Large diffs are not rendered by default.

16,149 changes: 16,149 additions & 0 deletions build/windows/contentScope.js

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
"postinstall": "npm run copy-sjcl",
"copy-sjcl": "node scripts/generateSJCL.js",
"bundle-config": "node scripts/bundleConfig.mjs",
"build": "npm run build-locales && npm run build-types && npm run bundle-trackers && npm run build-firefox && npm run build-chrome && npm run build-apple && npm run build-android && npm run build-windows && npm run build-integration && npm run build-chrome-mv3",
"build": "npm run build-locales && npm run build-types && npm run bundle-trackers && npm run build-firefox && npm run build-chrome && npm run build-apple && npm run build-android && npm run build-windows && npm run build-integration && npm run build-chrome-mv3 && npm run build-android-password-import",
"build-locales": "node scripts/buildLocales.js",
"build-firefox": "node scripts/inject.js --platform firefox",
"build-chrome": "node scripts/inject.js --platform chrome",
"build-chrome-mv3": "node scripts/inject.js --platform chrome-mv3",
"build-apple": "node scripts/inject.js --platform apple && node scripts/inject.js --platform apple-isolated",
"build-android-password-import": "node scripts/inject.js --platform android-password-import --password-import",
"build-android": "node scripts/inject.js --platform android",
"build-windows": "node scripts/inject.js --platform windows",
"build-integration": "node scripts/inject.js --platform integration",
Expand Down
4 changes: 4 additions & 0 deletions scripts/inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ const builds = {
input: 'inject/apple.js',
output: ['Sources/ContentScopeScripts/dist/contentScopeIsolated.js']
},
'android-password-import': {
input: 'inject/android.js',
output: ['build/android/passwordImport.js']
},
android: {
input: 'inject/android.js',
output: ['build/android/contentScope.js']
Expand Down
6 changes: 5 additions & 1 deletion src/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const otherFeatures = /** @type {const} */([
'windowsPermissionUsage',
'brokerProtection',
'performanceMetrics',
'breakageReporting'
'breakageReporting',
'passwordImport'
])

/** @typedef {baseFeatures[number]|otherFeatures[number]} FeatureName */
Expand All @@ -38,6 +39,9 @@ export const platformSupport = {
'performanceMetrics',
'clickToLoad'
],
'android-password-import': [
'passwordImport'
],
android: [
...baseFeatures,
'webCompat',
Expand Down
213 changes: 213 additions & 0 deletions src/features/password-import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import ContentFeature from '../content-feature'
import { DDGProxy, DDGReflect, withExponentialBackoff } from '../utils'

const ANIMATION_DURATION_MS = 1000
const ANIMATION_ITERATIONS = Infinity

export default class PasswordImport extends ContentFeature {
#exportButtonSettings = {
labelTexts: ['Export'] // TODO: should be configurable, can have multiple languages
}

#settingsButtonSettings = {}
#signInButtonSettings = {}
SUPPORTED_PATHS = {
SIGNIN: '/intro',
EXPORT: '/options',
SETTINGS: '/'
}

/**
* @param {string} path
* @returns {Promise<{element: HTMLElement|Element, style: any, shouldTap: boolean}|null>}
*/
async getElementAndStyleFromPath (path) {
if (path === this.SUPPORTED_PATHS.SETTINGS) {
const element = await this.findSettingsElement()
return element != null
? {
style: {
scale: 1,
backgroundColor: 'rgba(0, 39, 142, 0.5)'
},
element,
shouldTap: this.#settingsButtonSettings.autotap?.enabled ?? false
}
: null
} else if (path === this.SUPPORTED_PATHS.EXPORT) {
const element = await this.findExportElement()
return element != null
? {
style: {
scale: 1.01,
backgroundColor: 'rgba(0, 39, 142, 0.5)'
},
element,
shouldTap: this.#exportButtonSettings.autotap?.enabled ?? false
}
: null
} else if (path === this.SUPPORTED_PATHS.SIGNIN) {
const element = await this.findSignInButton()
return element != null
? {
style: {
scale: 1.5,
backgroundColor: 'rgba(0, 39, 142, 0.5)'
},
element,
shouldTap: this.#signInButtonSettings.autotap?.enabled ?? false
}
: null
} else {
return null
}
}

/**
*
* @param {HTMLElement|Element} element
* @param {any} style
*/
animateElement (element, style) {
element.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
]

// Define the animation options
const options = {
duration: ANIMATION_DURATION_MS, // 1 second, should be configurable
iterations: ANIMATION_ITERATIONS // Infinite, should be configurable
}

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

autotapElement (element) {
element.click()
}

/**
* @returns {Promise<HTMLElement|Element|null>}
*/
async findExportElement () {
const findInContainer = () => {
const exportButtonContainer = document.querySelector(this.exportButtonContainerSelector)
return exportButtonContainer && exportButtonContainer.querySelectorAll('button')[1]
}

const findWithLabel = () => {
return document.querySelector(this.exportButtonLabelTextSelector)
}

return await withExponentialBackoff(() => findInContainer() ?? findWithLabel())
}

/**
* @returns {Promise<HTMLElement|Element|null>}
*/
async findSettingsElement () {
const fn = () => {
const settingsButton = document.querySelector(this.settingsButtonSelectors[0])
return settingsButton
}
return await withExponentialBackoff(fn)
}

/**
* @returns {Promise<HTMLElement|Element|null>}
*/
async findSignInButton () {
return await withExponentialBackoff(() => document.querySelector(this.signinButtonSelectors[0]))
}

/**
*
* @param {string} path
*/
async handleElementForPath (path) {
// FIXME: This is a workaround, we need to check if the path is supported, otherwise
// for some reason google doesn't wait to proceed with the signin step.
// Not too sure why this is happening, there are no errors on the console.

if (Object.values(this.SUPPORTED_PATHS).includes(path)) {
const { element, style, shouldTap } = await this.getElementAndStyleFromPath(path) ?? {}
if (element != null) {
shouldTap ? this.autotapElement(element) : this.animateElement(element, style)
}
}
}

/**
* @returns {string}
*/
get exportButtonContainerSelector () {
return 'c-wiz[data-node-index*="2;0"][data-p*="options"], c-wiz[data-p*="options"][jsdata="deferred-i4"]'
// return this.#exportButtonSettings?.containerSelectors?.join(',')
}

/**
* @returns {string}
*/
get exportButtonLabelTextSelector () {
return this.#exportButtonSettings?.labelTexts
.map(text => `button[arial-label="${text}"]`)
.join(',')
}

/**
* @returns {Array<string>}
*/
get signinButtonSelectors () {
return ['a[href*="ServiceLogin"]:not([target="_top"]']
}

/**
* @returns {Array<string>}
*/
get settingsButtonSelectors () {
return ['a[href*="options"]']
}

/**
*
* @param {any} settings
*/
setButtonSettings (settings) {
this.#exportButtonSettings = settings?.exportButton
this.#settingsButtonSettings = settings?.settingsButton
this.#signInButtonSettings = settings?.signInButton
}

init (args) {
this.setButtonSettings(args?.featureSettings?.passwordImport || {})

// TODO: this is stolen from element-hiding.js, we would need a global util that would do this,
// single page applications don't have a DOMContentLoaded event on navigations, so
// we use proxy/reflect on history.pushState to find elements on page navigations
const handleElementForPath = this.handleElementForPath.bind(this)
const historyMethodProxy = new DDGProxy(this, History.prototype, 'pushState', {
async apply (target, thisArg, args) {
const path = args[2].split('?')[0]
await handleElementForPath(path)
return DDGReflect.apply(target, thisArg, args)
}
})
historyMethodProxy.overload()
// listen for popstate events in order to run on back/forward navigations
window.addEventListener('popstate', async () => {
await handleElementForPath(window.location.pathname)
})

document.addEventListener('DOMContentLoaded', async () => {
await handleElementForPath(window.location.pathname)
})
}
}
2 changes: 1 addition & 1 deletion src/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface ImportMeta {
env: 'production' | 'development'
platform?: 'windows' | 'macos' | 'android' | 'ios'
// this represents the different build artifact names
injectName?: 'firefox' | 'apple' | 'apple-isolated' | 'android' | 'windows' | 'integration' | 'chrome-mv3' | 'chrome'
injectName?: 'firefox' | 'apple' | 'apple-isolated' | 'android' | 'android-password-import' | 'windows' | 'integration' | 'chrome-mv3' | 'chrome'
trackerLookup?: Record<string, unknown>
pageName?: string
}
Expand Down
34 changes: 34 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,40 @@ export function computeLimitedSiteObject () {
}
}

/**
*
* @param {() => Element|HTMLElement|null} fn
* @param {number} maxAttempts
* @param {number} delay
* @returns {Promise<Element|HTMLElement|null>}
*/
export function withExponentialBackoff (fn, maxAttempts = 4, delay = 500) {
return new Promise((resolve, reject) => {
let attempts = 0
const tryFn = () => {
attempts += 1
const error = new Error('Element not found')
try {
const element = fn()
if (element) {
resolve(element)
} else if (attempts < maxAttempts) {
setTimeout(tryFn, delay * Math.pow(2, attempts))
} else {
reject(error)
}
} catch {
if (attempts < maxAttempts) {
setTimeout(tryFn, delay * Math.pow(2, attempts))
} else {
reject(error)
}
}
}
tryFn()
})
}

/**
* Expansion point to add platform specific versioning logic
* @param {UserPreferences} preferences
Expand Down
Loading