Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
beta: 6eb5656ccc7f71defee17d186f75bd0c2596bdcb
main: 446c6e609dbd7c355c2fb27209dfe4833211991f
main: 2309b7e182a6e3f0586422d62d857d21be9d633d
release: 2aac0427df5aa5ba72fb831b201b08bd62125b0a
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ beta: 6eb5656ccc7f71defee17d186f75bd0c2596bdcb
esr115: 862d3ee269d2d3099a18554136803f655b6dc12c
esr128: ed38f9209e39bd7ad247c81a7c20c99c874e0a62
esr140: c566b4226224dd882d1c8ea7e22b2675d73d142c
main: 446c6e609dbd7c355c2fb27209dfe4833211991f
main: 2309b7e182a6e3f0586422d62d857d21be9d633d
release: 2aac0427df5aa5ba72fb831b201b08bd62125b0a
Original file line number Diff line number Diff line change
Expand Up @@ -4540,6 +4540,7 @@ opaqueResponseBlocking:
owner: farre@mozilla.com
hasExposure: true
exposureDescription: Exposure is sent when a response is blocked
isEarlyStartup: true
variables:
enabled:
description: Whether ORB is enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4540,6 +4540,7 @@ opaqueResponseBlocking:
owner: farre@mozilla.com
hasExposure: true
exposureDescription: Exposure is sent when a response is blocked
isEarlyStartup: true
variables:
enabled:
description: Whether ORB is enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ firefox-v152.0: df0ceea8ef301835448e61fb2ed08a8bd30f8bbf
firefox-v152.1: 45946659b75375808307c513118b35e93304c7ed
firefox-v152.3: 8fcef7a828e9624be221e1a1a933977ae119b977
firefox-v152.4: 1991f2b9f99100ccb509a397e84dcf6501adc0bb
main: 758175bcf7a1b367365f58c7dc215f229e7c47a7
firefox-v153.0: 504a01f30ea2e545c0363596fe4223bc85247dc8
main: 1797693b1a2a431299ab1af1a37bc769bdeb70f8
release/v117: 43b690c450066d4dde1b2ccb93ed3714d244d502
release/v118: 89a7ea3c3372e5a4ef5b3c5b85499d26198d0524
release/v119: 7dc381f991c6d2a983c3ba7f0cffd880e8c29e7b
Expand Down Expand Up @@ -167,7 +168,8 @@ release/v152.1: 088e9563e87b3b8cd8946ae2e021da701ec30f61
release/v152.2: f82a0b1e4e3a45628b70048beaa49be6655a5b57
release/v152.3: cc7a99d2a73118ac6afcf69036bab4ddce8bc677
release/v152.4: 1eff9040a8675e3d24abfb5bc5d6984f989fc8c4
release/v153.0: 504a01f30ea2e545c0363596fe4223bc85247dc8
release/v153.0: 453747b5552eb3ee005bda2ea1a7de4dd6c95bda
release/v153.1: daf4097fe1e3f502d7f759a8775071b171b833fd
v117.0: 3b275ab1e4ccef769d84437033c0c68acfd2df8a
v117.1: 55ddcf473a65d106d43f6d2ae5af4076d6d03688
v117.2: 5cc367aa8857199cd8a54d53e8faf6696c5614c3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ features:
enabled:
description: If true the SnapKit removal refactor version will be used
type: Boolean
default: false
default: true
spotlight-search:
description: Add pages as items findable with Spotlight.
variables:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import Common
import Foundation
import Glean
import Shared

import class MozillaAppServices.NimbusGleanPings
import func MozillaAppServices.getCalculatedAttributes
import func MozillaAppServices.getLocaleTag
import struct MozillaAppServices.JsonObject
import protocol MozillaAppServices.RecordedContext
import MozillaRustComponents

private extension Double? {
func toInt64() -> Int64? {
guard let self = self else { return nil }
return Int64(self)
}
}

extension Int32? {
func toInt64() -> Int64? {
guard let self = self else { return nil }
return Int64(self)
}
}

/// TODO(FXIOS-12942): Implement proper thread-safety
final class RecordedNimbusContext: RecordedContext, @unchecked Sendable {
/**
* The following constants are string constants of the keys that appear in the [EVENT_QUERIES] map.
*/
static let DAYS_OPENED_IN_LAST_28 = "days_opened_in_last_28"

/**
* [EVENT_QUERIES] is a map of keys to Nimbus SDK EventStore queries.
*/
static let EVENT_QUERIES = [
DAYS_OPENED_IN_LAST_28: "'events.app_opened'|eventCountNonZero('Days', 28, 0)",
]

var isFirstRun: Bool
var isPhone: Bool
var isDefaultBrowser: Bool
var isBottomToolbarUser: Bool
var hasEnabledTipsNotifications: Bool
var hasAcceptedTermsOfUse: Bool
var userDisabledAi: Bool
var isAppleIntelligenceAvailable: Bool
var cannotUseAppleIntelligence: Bool
var appVersion: String?
var region: String?
var language: String?
var locale: String
var daysSinceInstall: Int32?
var daysSinceUpdate: Int32?
var touExperiencePoints: Int32?

private var eventQueries: [String: String]
private var eventQueryValues: [String: Double] = [:]

private var logger: Logger

init(isFirstRun: Bool,
isDefaultBrowser: Bool,
isBottomToolbarUser: Bool,
hasEnabledTipsNotifications: Bool,
hasAcceptedTermsOfUse: Bool,
userDisabledAi: Bool,
isAppleIntelligenceAvailable: Bool,
cannotUseAppleIntelligence: Bool,
eventQueries: [String: String] = RecordedNimbusContext.EVENT_QUERIES,
isPhone: Bool = UIDeviceDetails.userInterfaceIdiom == .phone,
bundle: Bundle = Bundle.main,
logger: Logger = DefaultLogger.shared) {
self.logger = logger
logger.log("init start", level: .debug, category: .experiments)
self.eventQueries = eventQueries

self.isFirstRun = isFirstRun
self.isPhone = isPhone
self.isDefaultBrowser = isDefaultBrowser
self.isBottomToolbarUser = isBottomToolbarUser
self.hasEnabledTipsNotifications = hasEnabledTipsNotifications
self.hasAcceptedTermsOfUse = hasAcceptedTermsOfUse
self.userDisabledAi = userDisabledAi
self.isAppleIntelligenceAvailable = isAppleIntelligenceAvailable
self.cannotUseAppleIntelligence = cannotUseAppleIntelligence

let info = bundle.infoDictionary ?? [:]
appVersion = info["CFBundleShortVersionString"] as? String

locale = getLocaleTag()
var inferredDateInstalledOn: Date? {
guard
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last,
let attributes = try? FileManager.default.attributesOfItem(atPath: documentsURL.path)
else { return nil }
return attributes[.creationDate] as? Date
}
let installationDateSinceEpoch = inferredDateInstalledOn.map {
Int64(($0.timeIntervalSince1970 * 1000).rounded())
}
guard let dbPath = Experiments.dbPath else {
self.logger.log("Unable to obtain dbPath, skipping calculating attributes",
level: .warning,
category: .experiments)
return
}
guard let calculatedAttributes = try? getCalculatedAttributes(installationDate: installationDateSinceEpoch,
dbPath: dbPath,
locale: locale)
else { return }

daysSinceInstall = calculatedAttributes.daysSinceInstall
daysSinceUpdate = calculatedAttributes.daysSinceUpdate
language = calculatedAttributes.language
region = calculatedAttributes.region
touExperiencePoints = Experiments.touExperiencePoints(region: region)
self.logger.log("init end", level: .debug, category: .experiments)
}

/**
* [getEventQueries] is called by the Nimbus SDK Rust code to retrieve the map of event
* queries. The are then executed against the Nimbus SDK's EventStore to retrieve their values.
*
* @return Map<String, String>
*/
func getEventQueries() -> [String: String] {
logger.log("getEventQueries", level: .debug, category: .experiments)
return eventQueries
}

/**
* [record] is called when experiment enrollments are evolved. It should apply the
* [RecordedNimbusContext]'s values to a [NimbusSystem.RecordedNimbusContextObject] instance,
* and use that instance to record the values to Glean.
*/
func record() {
logger.log("record start", level: .debug, category: .experiments)

// Bring the ping into scope so that Glean knows it exists and includes NimbusSystem.recordedNimbusContext
_ = NimbusGleanPings.nimbusTargetingContext

let eventQueryValuesObject = GleanMetrics.NimbusSystem.RecordedNimbusContextObjectItemEventQueryValuesObject(
daysOpenedInLast28: eventQueryValues[RecordedNimbusContext.DAYS_OPENED_IN_LAST_28].toInt64()
)

GleanMetrics.NimbusSystem.recordedNimbusContext.set(
GleanMetrics.NimbusSystem.RecordedNimbusContextObject(
isFirstRun: isFirstRun,
eventQueryValues: eventQueryValuesObject,
isPhone: isPhone,
appVersion: appVersion,
locale: locale,
daysSinceInstall: daysSinceInstall.toInt64(),
daysSinceUpdate: daysSinceUpdate.toInt64(),
language: language,
region: region,
isDefaultBrowser: isDefaultBrowser,
isBottomToolbarUser: isBottomToolbarUser,
hasEnabledTipsNotifications: hasEnabledTipsNotifications,
hasAcceptedTermsOfUse: hasAcceptedTermsOfUse,
userDisabledAi: userDisabledAi,
isAppleIntelligenceAvailable: isAppleIntelligenceAvailable,
cannotUseAppleIntelligence: cannotUseAppleIntelligence,
touExperiencePoints: touExperiencePoints.toInt64()
)
)
logger.log("record end", level: .debug, category: .experiments)
}

/**
* [setEventQueryValues] is called by the Nimbus SDK Rust code after the event queries have been
* executed. The [eventQueryValues] should be written back to the Kotlin object.
*
* @param [eventQueryValues] The values for each query after they have been executed in the
* Nimbus SDK Rust environment.
*/
func setEventQueryValues(eventQueryValues: [String: Double]) {
logger.log("setEventQueryValues", level: .debug, category: .experiments)
self.eventQueryValues = eventQueryValues
}

/**
* [toJson] is called by the Nimbus SDK Rust code after the event queries have been executed,
* and before experiment enrollments have been evolved. The value returned from this method
* will be applied directly to the Nimbus targeting context, and its keys/values take
* precedence over those in the main Nimbus targeting context.
*
* @return JsonObject
*/
func toJson() -> JsonObject {
logger.log("toJson start", level: .debug, category: .experiments)
guard let data = try? JSONSerialization.data(withJSONObject: [
"is_first_run": isFirstRun,
"isFirstRun": "\(isFirstRun)",
"is_phone": isPhone,
"events": eventQueryValues,
"app_version": appVersion as Any,
"region": region as Any,
"language": language as Any,
"locale": locale as Any,
"days_since_install": daysSinceInstall as Any,
"days_since_update": daysSinceUpdate as Any,
"is_default_browser": isDefaultBrowser,
"is_bottom_toolbar_user": isBottomToolbarUser,
"has_enabled_tips_notifications": hasEnabledTipsNotifications,
"has_accepted_terms_of_use": hasAcceptedTermsOfUse,
"user_disabled_ai": userDisabledAi,
"is_apple_intelligence_available": isAppleIntelligenceAvailable,
"cannot_use_apple_intelligence": cannotUseAppleIntelligence,
"tou_experience_points": touExperiencePoints as Any
]),
let jsonString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as? String
else {
logger.log("toJson error thrown while creating JSON string", level: .warning, category: .experiments)
return "{}"
}
logger.log("toJson end", level: .debug, category: .experiments, extra: ["json": jsonString])
return jsonString
}
}
Loading
Loading