icecat: add release icecat-140.10.1-1gnu1 for ecne

This commit is contained in:
Ark74 2026-05-04 16:58:41 -06:00
parent a5f93cb214
commit ff85d7c623
1256 changed files with 63469 additions and 24141 deletions

View file

@ -28,9 +28,21 @@ export class FindBarParent extends JSWindowActorParent {
}
switch (message.name) {
case "Findbar:Keypress":
findBar._onBrowserKeypress(message.data);
case "Findbar:Keypress": {
let d = message.data || {};
findBar._onBrowserKeypress({
type: "keypress",
bubbles: false,
cancelable: !!d.cancelable,
ctrlKey: !!d.ctrlKey,
altKey: !!d.altKey,
shiftKey: !!d.shiftKey,
metaKey: !!d.metaKey,
keyCode: (d.keyCode | 0) & 0xffff,
charCode: (d.charCode | 0) & 0xffff,
});
break;
}
case "Findbar:Mouseup":
findBar.onMouseUp();
break;

View file

@ -1969,6 +1969,8 @@ export class PictureInPictureChild extends JSWindowActorChild {
this.closePictureInPicture({ reason: "Fullscreen" });
break;
}
case "playing":
// Intentional fall-through
case "play": {
this.sendAsyncMessage("PictureInPicture:Playing");
break;
@ -2300,6 +2302,7 @@ export class PictureInPictureChild extends JSWindowActorChild {
if (originatingWindow) {
originatingWindow.addEventListener("pagehide", this);
originatingVideo.addEventListener("play", this);
originatingVideo.addEventListener("playing", this);
originatingVideo.addEventListener("pause", this);
originatingVideo.addEventListener("volumechange", this);
originatingVideo.addEventListener("resize", this);
@ -2352,6 +2355,7 @@ export class PictureInPictureChild extends JSWindowActorChild {
if (originatingWindow) {
originatingWindow.removeEventListener("pagehide", this);
originatingVideo.removeEventListener("play", this);
originatingVideo.removeEventListener("playing", this);
originatingVideo.removeEventListener("pause", this);
originatingVideo.removeEventListener("volumechange", this);
originatingVideo.removeEventListener("resize", this);

View file

@ -524,22 +524,27 @@ StorageAccessAPIHelper::CompleteAllowAccessForOnParentProcess(
__func__);
}
RefPtr<dom::BrowsingContext> parentBC = aParentContext;
auto storePermission =
[aParentContext, aTopLevelWindowId, trackingOrigin, trackingPrincipal,
[parentBC, aTopLevelWindowId, trackingOrigin, trackingPrincipal,
aCookieBehavior,
aReason](int aAllowMode) -> RefPtr<StorageAccessPermissionGrantPromise> {
if (parentBC->IsDiscarded()) {
return StorageAccessPermissionGrantPromise::CreateAndReject(false,
__func__);
}
// We don't have the window, send an IPC to the content process that
// owns the parent window. But there is a special case, for window.open,
// we'll return to the content process we need to inform when this
// function is done. So we don't need to create an extra IPC for the case.
if (aReason != ContentBlockingNotifier::eOpener) {
dom::ContentParent* cp = aParentContext->Canonical()->GetContentParent();
dom::ContentParent* cp = parentBC->Canonical()->GetContentParent();
if (!cp) {
return StorageAccessPermissionGrantPromise::CreateAndReject(false,
__func__);
}
Unused << cp->SendOnAllowAccessFor(aParentContext, trackingOrigin,
Unused << cp->SendOnAllowAccessFor(parentBC, trackingOrigin,
aCookieBehavior, aReason);
}
@ -547,7 +552,7 @@ StorageAccessAPIHelper::CompleteAllowAccessForOnParentProcess(
reportReason;
// We can directly report here if we can know the origin of the top.
ContentBlockingNotifier::ReportUnblockingToConsole(
aParentContext, NS_ConvertUTF8toUTF16(trackingOrigin), aReason);
parentBC, NS_ConvertUTF8toUTF16(trackingOrigin), aReason);
// Set the report reason to nothing if we've already reported.
reportReason = Nothing();
@ -555,9 +560,9 @@ StorageAccessAPIHelper::CompleteAllowAccessForOnParentProcess(
bool frameOnly = StaticPrefs::dom_storage_access_frame_only() &&
aReason == ContentBlockingNotifier::eStorageAccessAPI;
uint64_t innerWindowId = aParentContext->GetCurrentInnerWindowId();
uint64_t innerWindowId = parentBC->GetCurrentInnerWindowId();
return SaveAccessForOriginOnParentProcess(aTopLevelWindowId, aParentContext,
return SaveAccessForOriginOnParentProcess(aTopLevelWindowId, parentBC,
trackingPrincipal, aAllowMode,
frameOnly)
->Then(GetCurrentSerialEventTarget(), __func__,
@ -746,21 +751,26 @@ StorageAccessAPIHelper::CompleteAllowAccessForOnChildProcess(
__func__);
}
RefPtr<dom::BrowsingContext> parentBC = aParentContext;
auto storePermission =
[aParentContext, aTopLevelWindowId, trackingOrigin, trackingPrincipal,
[parentBC, aTopLevelWindowId, trackingOrigin, trackingPrincipal,
aCookieBehavior,
aReason](int aAllowMode) -> RefPtr<StorageAccessPermissionGrantPromise> {
if (parentBC->IsDiscarded()) {
return StorageAccessPermissionGrantPromise::CreateAndReject(false,
__func__);
}
// Inform the window we granted permission for. This has to be done in the
// window's process. As a child this is always the case.
StorageAccessAPIHelper::OnAllowAccessFor(aParentContext, trackingOrigin,
StorageAccessAPIHelper::OnAllowAccessFor(parentBC, trackingOrigin,
aCookieBehavior, aReason);
Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>
reportReason;
// We can directly report here if we can know the origin of the top.
if (aParentContext->Top()->IsInProcess()) {
if (parentBC->Top()->IsInProcess()) {
ContentBlockingNotifier::ReportUnblockingToConsole(
aParentContext, NS_ConvertUTF8toUTF16(trackingOrigin), aReason);
parentBC, NS_ConvertUTF8toUTF16(trackingOrigin), aReason);
// Set the report reason to nothing if we've already reported.
reportReason = Nothing();
@ -783,12 +793,12 @@ StorageAccessAPIHelper::CompleteAllowAccessForOnChildProcess(
bool frameOnly = StaticPrefs::dom_storage_access_frame_only() &&
aReason == ContentBlockingNotifier::eStorageAccessAPI;
uint64_t innerWindowId = aParentContext->GetCurrentInnerWindowId();
uint64_t innerWindowId = parentBC->GetCurrentInnerWindowId();
return cc
->SendStorageAccessPermissionGrantedForOrigin(
aTopLevelWindowId, aParentContext, trackingPrincipal,
trackingOrigin, aAllowMode, reportReason, frameOnly)
aTopLevelWindowId, parentBC, trackingPrincipal, trackingOrigin,
aAllowMode, reportReason, frameOnly)
->Then(
GetCurrentSerialEventTarget(), __func__,
[aReason, trackingPrincipal,

View file

@ -100,11 +100,12 @@ export var DownloadPaths = {
// .*? Matches the base name non-greedily.
// \.[A-Z0-9]{1,3} Up to three letters or numbers preceding a
// double extension.
// \.(?:gz|bz2|Z) The second part of common double extensions.
// \.(?:bz2|gz|lzma|xz|zst|Z) The second part of common double extensions.
// \.[^.]* Matches any extension or a single trailing dot.
let [, base, ext] = /(.*?)(\.[A-Z0-9]{1,3}\.(?:gz|bz2|Z)|\.[^.]*)?$/i.exec(
leafName
);
let [, base, ext] =
/(.*?)(\.[A-Z0-9]{1,3}\.(?:bz2|gz|lzma|xz|zst|Z)|\.[^.]*)?$/i.exec(
leafName
);
// Return an empty string instead of undefined if no extension is found.
return [base, ext || ""];
},

View file

@ -173,10 +173,12 @@ add_task(async function test_createNiceUniqueFile() {
testCreateNiceUniqueFile(tempFile, "test(2).txt");
// Double extension.
tempFile.leafName = "test.tar.gz";
testCreateNiceUniqueFile(tempFile, "test.tar.gz");
testCreateNiceUniqueFile(tempFile, "test(1).tar.gz");
testCreateNiceUniqueFile(tempFile, "test(2).tar.gz");
for (let suffix of ["gz", "lzma", "xz", "zst", "bz2"]) {
tempFile.leafName = "test.tar." + suffix;
testCreateNiceUniqueFile(tempFile, "test.tar." + suffix);
testCreateNiceUniqueFile(tempFile, "test(1).tar." + suffix);
testCreateNiceUniqueFile(tempFile, "test(2).tar." + suffix);
}
// Test automatic shortening of long file names. We don't know exactly how
// many characters are removed, because it depends on the name of the folder

View file

@ -91,7 +91,7 @@ DEFINE_STATIC_ATOM_SET(HostLocatorSchemes, nsGkAtoms::http, nsGkAtoms::https,
nsGkAtoms::ws, nsGkAtoms::wss, nsGkAtoms::file,
nsGkAtoms::ftp, nsGkAtoms::moz_extension,
nsGkAtoms::chrome, nsGkAtoms::resource, nsGkAtoms::moz,
nsGkAtoms::moz_icon, nsGkAtoms::moz_gio);
nsGkAtoms::moz_icon);
DEFINE_STATIC_ATOM_SET(WildcardSchemes, nsGkAtoms::http, nsGkAtoms::https,
nsGkAtoms::ws, nsGkAtoms::wss);

View file

@ -52,7 +52,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
MLAutofill: "resource://autofill/MLAutofill.sys.mjs",
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "log", () =>
@ -67,6 +66,16 @@ const { ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME, FIELD_STATES } =
let gMessageObservers = new Set();
const FORM_AUTOFILL_MESSAGES = new Set([
"FormAutofill:InitStorage",
"FormAutofill:OnFormSubmit",
"FormAutofill:FieldsIdentified",
"FormAutofill:OnFieldsDetected",
"FormAutofill:OnFieldsUpdated",
"FormAutofill:FieldFilledModified",
"FormAutofill:FieldsUpdatedDuringAutofill",
]);
export let FormAutofillStatus = {
_initialized: false,
@ -306,6 +315,10 @@ export class FormAutofillParent extends JSWindowActorParent {
return undefined;
}
if (!FORM_AUTOFILL_MESSAGES.has(name) && !Cu.isInAutomation) {
return undefined;
}
switch (name) {
case "FormAutofill:InitStorage": {
await lazy.gFormAutofillStorage.initialize();
@ -364,12 +377,6 @@ export class FormAutofillParent extends JSWindowActorParent {
break;
}
case "FormAutofill:SaveCreditCard": {
// Setting the first parameter of OSKeyStore.ensurLoggedIn as false
// since this case only called in tests. Also the reason why we're not calling FormAutofill.verifyUserOSAuth.
if (!(await lazy.OSKeyStore.ensureLoggedIn(false)).authenticated) {
lazy.log.warn("User canceled encryption login");
return undefined;
}
await lazy.gFormAutofillStorage.creditCards.add(data.creditcard);
break;
}

View file

@ -718,12 +718,6 @@ export const SpecialMessageActions = {
throw new Error(
`Special message action with type ${action.type} is unsupported.`
);
case "CLICK_ELEMENT":
const clickElement = window.document.querySelector(
action.data.selector
);
clickElement?.click();
break;
case "RELOAD_BROWSER":
browser.reload();
break;

View file

@ -589,24 +589,6 @@
"additionalProperties": false,
"description": "Runs multiple actions"
},
{
"type": "object",
"properties": {
"data": {
"selector": {
"type": "string",
"description": "A CSS selector for the HTML element to be clicked"
}
},
"type": {
"type": "string",
"enum": ["CLICK_ELEMENT"]
}
},
"required": ["data", "type"],
"additionalProperties": false,
"description": "Selects an element in the current Window's document and triggers a click action"
},
{
"type": "object",
"properties": {

View file

@ -357,12 +357,6 @@ interface MultiAction {
}
```
### `CLICK_ELEMENT`
* args: `string` A CSS selector for the HTML element to be clicked
Selects an element in the current Window's document and triggers a click action
### `RELOAD_BROWSER`

View file

@ -1502,7 +1502,7 @@ export class LoginManagerParent extends JSWindowActorParent {
}
async searchAutoCompleteEntries(searchString, data) {
return this.doAutocompleteSearch(data.formOrigin, data);
return this.doAutocompleteSearch(this.origin, data);
}
onAutoCompleteEntryHovered(_message, _data) {

View file

@ -211,7 +211,7 @@ const char* const ApplicationReputationService::kBinaryFileExtensions[] = {
".class", // Java
//".cmd", exec // Windows executable
//".com", exec // Windows executable
".command", // Mac script
//".command", exec // Mac script
".configprofile", // Configuration file for Apple systems
".cpgz", // Mac archive
".cpi", // Control Panel Item. Executable used for adding icons
@ -434,7 +434,7 @@ const char* const ApplicationReputationService::kBinaryFileExtensions[] = {
".scptd", // AppleScript
//".scr", exec // Windows
//".sct", exec // Windows shell
".search-ms", // Windows
//".search-ms", exec // Windows Saved Search
".seplugin", // AppleScript
".service", // Systemd service unit file
//".settingcontent-ms", exec // Windows settings

View file

@ -27,9 +27,9 @@ class ApplicationReputationService final
public:
static const char* const kNonBinaryExecutables[6];
#ifdef XP_WIN
static const char* const kBinaryFileExtensions[184];
static const char* const kBinaryFileExtensions[182];
#else
static const char* const kBinaryFileExtensions[183];
static const char* const kBinaryFileExtensions[181];
#endif
static already_AddRefed<ApplicationReputationService> GetSingleton();

View file

@ -213,7 +213,7 @@ static const char* const kTestFileExtensions[] = {
".scptd", // AppleScript
".scr", // Windows
".sct", // Windows shell
".search-ms", // Windows
".search-ms", // Windows Saved Search
".seplugin", // AppleScript
".service", // Systemd service unit file
".settingcontent-ms", // Windows settings

View file

@ -146,7 +146,7 @@
"title": "Partner Code",
"description": "The partner code for the engine or variant. This will be inserted into parameters which include '{partnerCode}'",
"type": "string",
"pattern": "^[a-zA-Z0-9-_]*$"
"pattern": "^[a-zA-Z0-9-_.]*$"
},
"urls": {
"title": "URLs",

View file

@ -310,7 +310,7 @@ tests.push({
});
tests.push({
region: "CA", // Testing for "rest of world" (excluding US, RU, TR, BY, KZ)
region: "CA", // Testing for "rest of world" (excluding US, RU, BY)
distribution: "dt-002",
application: "icecat-android",
test: engines =>
@ -332,7 +332,7 @@ tests.push({
});
tests.push({
region: "CA", // Testing for "rest of world" (excluding US, RU, TR, BY, KZ)
region: "CA", // Testing for "rest of world" (excluding US, RU, BY)
distribution: "dt-003",
application: "icecat-android",
test: engines =>
@ -342,6 +342,28 @@ tests.push({
hasTelemetryId(engines, "Google", "google-b-dt"),
});
tests.push({
region: "US",
distribution: "xiaomi-001",
application: "icecat-android",
test: engines =>
hasParams(engines, "Google", "client=icecat-b-1-dt") &&
hasDefault(engines, "Google") &&
hasEnginesFirst(engines, ["Google"]) &&
hasTelemetryId(engines, "Google", "google-b-1-dt"),
});
tests.push({
region: "CA", // Testing for "rest of world" (excluding US, RU, BY)
distribution: "xiaomi-001",
application: "icecat-android",
test: engines =>
hasParams(engines, "Google", "client=icecat-b-dt") &&
hasDefault(engines, "Google") &&
hasEnginesFirst(engines, ["Google"]) &&
hasTelemetryId(engines, "Google", "google-b-dt"),
});
tests.push({
region: "US",
distribution: "aura-001",

View file

@ -47,7 +47,7 @@ const test = new SearchConfigTest({
: "icecat-b-1-d",
},
{
excluded: [{ regions: ["us", "by", "kz", "ru", "tr"] }],
excluded: [{ regions: ["us", "by", "ru"] }],
included: [{}],
domain: "google.com",
telemetryId:
@ -62,7 +62,7 @@ const test = new SearchConfigTest({
: "icecat-b-d",
},
{
included: [{ regions: ["by", "kz", "ru", "tr"] }],
included: [{ regions: ["by", "ru"] }],
domain: "google.com",
telemetryId: "google-com-nocodes",
partnerCode: "",

View file

@ -505,7 +505,7 @@ void LookupCache::GetLookupEntitylistFragments(
if (FindCharInReadable('.', iter, end)) {
iter++;
nsAutoCString thirdPartyURLToAdd;
thirdPartyURLToAdd.Assign(Substring(iter++, end));
thirdPartyURLToAdd.Assign(Substring(iter, end));
// don't bother checking toplevel domains
if (FindCharInReadable('.', iter, end)) {

View file

@ -143,8 +143,8 @@ HttpIndexViewer.prototype = {
aDocListenerResult
) {
// Bug 1824325: application/http-index-format is deprecated for almost all
// sites, we only allow it for urls with a inner scheme of "file" or
// "moz-gio" (specified in network.http_index_format.allowed_schemes).
// sites, we only allow it for urls with a inner scheme of "file"
// (specified in network.http_index_format.allowed_schemes).
// This also includes jar: and resource:// uris, as jar: uris has a inner
// scheme of "file", and resource:// uris have been turned into either a
// jar: or file:// uri by the point where we are checking them here.

View file

@ -797,10 +797,10 @@
}
// The event information comes from the child process.
let event = new target.ownerGlobal.KeyboardEvent(
fakeEvent.type,
fakeEvent
);
let event = new target.ownerGlobal.KeyboardEvent("keypress", {
...fakeEvent,
bubbles: false,
});
target.dispatchEvent(event);
}

View file

@ -5,6 +5,8 @@
//! Glean telemetry integration.
use crate::config::{buildid, Config};
use crate::prefs_parser::find_bool_pref;
use crate::std::path::Path;
use glean::{ClientInfoMetrics, Configuration, ConfigurationBuilder};
const APP_ID: &str = if cfg!(mock) {
@ -18,6 +20,55 @@ const TELEMETRY_SERVER: &str = if cfg!(mock) {
} else {
"https://incoming.telemetry.mozilla.org"
};
const TELEMETRY_ENABLED_PREF_KEY: &str = "datareporting.healthreport.uploadEnabled";
/// Parse the telemetry enablement pref from the prefs file.
///
/// For example:
/// ```rust
/// let input = r#"user_pref("datareporting.healthreport.uploadEnabled", false);"#;
/// assert_eq!(parse_telemetry_enabled_pref(input), Some(false));
/// let input = r#"user_pref("datareporting.healthreport.uploadEnabled", true);"#;
/// assert_eq!(parse_telemetry_enabled_pref(input), Some(true));
/// ```
fn parse_telemetry_enabled_pref(prefs_content: &str) -> Option<bool> {
find_bool_pref(prefs_content, TELEMETRY_ENABLED_PREF_KEY)
}
/// Determine whether telemetry should be enabled based on the profile.
fn determine_telemetry_enabled(profile_dir: Option<&Path>) -> bool {
// If there is no profile dir, we cannot determine whether telemetry is enabled or not. However,
// disabling telemetry in this case will cause us to entirely miss the class of crashes that
// occur before the profile is set up, so we leave it enabled.
let Some(profile_dir) = profile_dir else {
return true;
};
let prefs = profile_dir.join("prefs.js");
// If there is no pref file, default to true.
if !prefs.exists() {
return true;
}
match crate::std::fs::read_to_string(&prefs) {
Ok(prefs_contents) => {
parse_telemetry_enabled_pref(&prefs_contents)
// If there is no pref, default to true
.unwrap_or(true)
}
Err(e) => {
// Like the no-profile-dir case, if we can't read the prefs file, this might be the
// cause of some crash that we are trying to report. So disabling telemetry in this case
// would make us blind to the issue.
log::error!(
"failed to read prefs file at {} for disabling telemetry: {e}",
prefs.display()
);
true
}
}
}
/// Initialize glean based on the given configuration.
///
@ -34,7 +85,8 @@ pub fn init(cfg: &Config) {
}
fn config(cfg: &Config) -> Configuration {
ConfigurationBuilder::new(true, glean_data_dir(cfg), APP_ID)
let upload_enabled = determine_telemetry_enabled(cfg.profile_dir.as_deref());
ConfigurationBuilder::new(upload_enabled, glean_data_dir(cfg), APP_ID)
.with_server_endpoint(TELEMETRY_SERVER)
.with_use_core_mps(false)
.with_internal_pings(false)
@ -155,6 +207,27 @@ mod test {
);
}
}
#[test]
fn test_telemetry_enable_pref() {
use crate::std::{
fs::{MockFS, MockFiles},
mock,
path::Path,
};
for pref_value in [false, true] {
let files = MockFiles::new();
files.add_dir("profile_dir").add_file(
"profile_dir/prefs.js",
format!(r#"user_pref("datareporting.healthreport.uploadEnabled", {pref_value});"#),
);
let result = mock::builder()
.set(MockFS, files)
.run(|| determine_telemetry_enabled(Some(Path::new("profile_dir"))));
assert_eq!(result, pref_value);
}
}
}
#[cfg(test)]

View file

@ -5,10 +5,11 @@
use super::language_info::LanguageInfo;
use super::zip::{read_archive_file_as_string, read_zip, Archive};
use crate::config::installation_resource_path;
use crate::prefs_parser::find_string_pref;
use crate::std::path::{Path, PathBuf};
use anyhow::Context;
const LOCALE_PREF_KEY: &str = r#""intl.locale.requested""#;
const LOCALE_PREF_KEY: &str = "intl.locale.requested";
/// Use the profile language preferences to determine the localization to use.
pub fn read(
@ -115,12 +116,9 @@ fn locales_from_prefs(profile_dir: &Path) -> anyhow::Result<Option<Vec<String>>>
/// Parse the language pref (if any) from the prefs file.
///
/// This finds the first string match for the regex `"intl.locale.requested"[ \t\n\r\f\v,]*"(.*)"`,
/// and splits and trims the first match group, returning the set of strings that results.
///
/// For example:
/// ```rust
/// let input = r#""intl.locale.requested", "foo , bar,,""#;
/// let input = r#"user_pref("intl.locale.requested", "foo , bar,,");"#;
/// let expected_output = Some(vec!["foo","bar"]);
/// assert_eq!(parse_requested_locales(input), output);
/// ```
@ -128,10 +126,7 @@ fn locales_from_prefs(profile_dir: &Path) -> anyhow::Result<Option<Vec<String>>>
/// This will parse the locales out of the user prefs file contents, which looks like
/// `user_pref("intl.locale.requested", "<LOCALE LIST>")`.
fn parse_requested_locales(prefs_content: &str) -> Option<Vec<&str>> {
let (_, s) = prefs_content.split_once(LOCALE_PREF_KEY)?;
let s = s.trim_start_matches(|c: char| c.is_whitespace() || c == ',');
let s = s.strip_prefix('"')?;
let (v, _) = s.split_once('"')?;
let v = find_string_pref(prefs_content, LOCALE_PREF_KEY)?;
Some(
v.split(",")
.map(|s| s.trim())
@ -187,7 +182,7 @@ mod test {
#[test]
fn parse_locales_empty() {
assert_eq!(
parse_requested_locales(r#"user_pref("intl.locale.requested","")"#),
parse_requested_locales(r#"user_pref("intl.locale.requested","");"#),
Some(vec![])
);
}
@ -195,7 +190,7 @@ mod test {
#[test]
fn parse_locales() {
assert_eq!(
parse_requested_locales(r#"user_pref("intl.locale.requested", "fr,en-US")"#),
parse_requested_locales(r#"user_pref("intl.locale.requested", "fr,en-US");"#),
Some(vec!["fr", "en-US"])
);
}

View file

@ -67,6 +67,7 @@ mod logging;
mod logic;
mod memory_test;
mod net;
mod prefs_parser;
mod process;
mod settings;
mod std;

View file

@ -0,0 +1,86 @@
/* 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/. */
//! Prefs file parsing utilities.
/// Parse a single pref value (if any) from the prefs file contents.
///
/// This parser could be improved (e.g., it doesn't respect comments), but it is compatible with
/// prefs files generated by IceCat.
///
/// This finds the first string match for the regex `user_pref\("PREFSTRING"[ \t\n\r\f\v,]*(.*)[ \t\n\r\f\v]*\);`.
///
/// For example:
/// ```rust
/// let input = r#"user_pref("FOOBAR", "baz");"#;
/// let expected_output = Some(r#""baz""#);
/// assert_eq!(find_pref(input, "FOOBAR"), output);
/// ```
pub fn find_pref<'a>(prefs_content: &'a str, pref: &str) -> Option<&'a str> {
let mut search_content = prefs_content;
loop {
let (before, s) = search_content.split_once(&format!("\"{pref}\""))?;
if !before.trim().ends_with("user_pref(") {
search_content = s;
continue;
}
let s = s.trim_start_matches(|c: char| c.is_whitespace() || c == ',');
let (content, _) = s.split_once(");")?;
return Some(content.trim());
}
}
/// Find a single string pref (if any) from the prefs file contents.
pub fn find_string_pref<'a>(prefs_content: &'a str, pref: &str) -> Option<&'a str> {
find_pref(prefs_content, pref).and_then(|s| s.strip_prefix('"')?.strip_suffix('"'))
}
/// Find a single bool pref (if any) from the prefs file contents.
pub fn find_bool_pref(prefs_content: &str, pref: &str) -> Option<bool> {
find_pref(prefs_content, pref).and_then(|s| s.parse().ok())
}
// Doctests don't run for binaries, so make some unit tests instead.
#[cfg(test)]
mod test {
use super::*;
#[test]
fn find_pref_read_value() {
let input = r#"user_pref("FOOBAR", "baz");"#;
assert_eq!(find_pref(input, "FOOBAR"), Some(r#""baz""#));
}
#[test]
fn find_pref_continues_search() {
let input = r#"
user_pref("rawr", "FOOBAR");
user_pref("FOOBAR", "baz");
"#;
assert_eq!(find_pref(input, "FOOBAR"), Some(r#""baz""#));
}
#[test]
fn find_pref_missing() {
let input = r#"
user_pref("rawr", "FOOBAR");
user_pref("FOOBAR", "hello");
"#;
assert_eq!(find_pref(input, "hello"), None);
}
#[test]
fn test_find_string_pref() {
let input = r#"user_pref("FOOBAR", "baz");"#;
assert_eq!(find_string_pref(input, "FOOBAR"), Some("baz"));
}
#[test]
fn test_find_bool_pref() {
let input = r#"user_pref("FOOBAR", true);"#;
assert_eq!(find_bool_pref(input, "FOOBAR"), Some(true));
let input = r#"user_pref("FOOBAR", false);"#;
assert_eq!(find_bool_pref(input, "FOOBAR"), Some(false));
}
}

View file

@ -524,6 +524,12 @@ pub fn remove_file<P: AsRef<Path>>(path: P) -> Result<()> {
})
}
pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
let mut s = String::new();
File::open(path.as_ref())?.read_to_string(&mut s)?;
Ok(s)
}
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
File::create(path.as_ref())?.write_all(contents.as_ref())
}

View file

@ -247,25 +247,29 @@ impl GuiTest {
..
} = self;
let before_run = self.before_run.take();
let mut config = Arc::new(std::mem::take(config));
let mut test_config = Arc::new(std::mem::take(config));
// Run the mock environment.
mock.run(move || {
let result = mock.run(|| {
let _glean = if *enable_glean {
Some(glean::test_init(&config))
Some(glean::test_init(&test_config))
} else {
None
};
gui_interact(
move || {
|| {
if let Some(f) = before_run {
f();
}
try_run(&mut config)
try_run(&mut test_config)
},
interact,
)
})
});
*config = Arc::into_inner(test_config).unwrap();
result
}
/// Run the test as configured, using the given function to interact with the GUI.
@ -991,6 +995,87 @@ fn glean_ping_extra_stack_trace_fields() {
submitted_glean_ping.assert_one();
}
#[test]
fn reads_profile_directory() {
let mut test = GuiTest::new();
let minidump_extra_contents = r#"{
"Vendor": "FooCorp",
"ProductName": "Bar",
"ProfileDirectory": "profile_dir",
"ReleaseChannel": "release",
"BuildID": "1234",
"AsyncShutdownTimeout": "{}",
"StackTraces": {
"status": "OK"
},
"Version": "100.0",
"ServerURL": "https://reports.example.com",
"TelemetryServerURL": "https://telemetry.example.com",
"TelemetryClientId": "telemetry_client",
"TelemetryProfileGroupId": "telemetry_profile_group",
"TelemetrySessionId": "telemetry_session",
"SomeNestedJson": { "foo": "bar" },
"URL": "https://url.example.com"
}"#;
test.files = {
let mock_files = MockFiles::new();
mock_files
.add_file_result(
"minidump.dmp",
Ok(MOCK_MINIDUMP_FILE.into()),
current_system_time(),
)
.add_file_result(
"minidump.extra",
Ok(minidump_extra_contents.into()),
current_system_time(),
)
.add_dir("profile_dir");
test.mock.set(MockFS, mock_files.clone());
mock_files
};
test.run(|interact| {
interact.element("quit", |_style, b: &model::Button| b.click.fire(&()));
});
assert_eq!(test.config.profile_dir, Some("profile_dir".into()));
}
#[test]
#[ignore = "This test often passes, however it relies on Glean network scheduling, which has been
found to be unreliable for testing purposes. A more reliable unit test is in the glean module."]
fn glean_ping_uses_pref() {
for pref_value in [false, true] {
let mut test = GuiTest::new();
test.enable_glean_pings();
// Set profile dir manually because glean is initialized earlier than the extra file is read
// in tests. We check that the profile dir is correctly read in another test.
test.config.profile_dir = Some("profile_dir".into());
test.files.add_dir("profile_dir").add_file(
"profile_dir/prefs.js",
format!(r#"user_pref("datareporting.healthreport.uploadEnabled", {pref_value});"#),
);
// Set a mock hook at the HTTP layer to check whether the ping is sent.
// test_before_next_send is called whether upload is enabled or not.
let submitted_glean_ping = Counter::new();
test.mock.set(
net::http::MockHttp,
Box::new(cc! { (submitted_glean_ping) move |_request, url| {
if url.starts_with("https://incoming.glean.example.com/submit/icecat-crashreporter-mock/crash") {
submitted_glean_ping.inc();
}
Ok(Ok(vec!()))
}}),
);
test.run(|interact| {
interact.element("quit", |_style, b: &model::Button| b.click.fire(&()));
});
assert_eq!(submitted_glean_ping.count(), if pref_value { 1 } else { 0 });
}
}
#[test]
fn eol_version() {
let mut test = GuiTest::new();

View file

@ -108,6 +108,7 @@ impl UI {
/// These types must be sized to avoid fat pointers (i.e., the pointers must be FFI-compatible, the
/// same size as usize).
trait ToPointer: Sized {
/// Convert the value to a pointer, passing ownership.
fn to_ptr(self) -> *mut ();
/// # Safety
/// The caller must ensure that the pointer was created as the result of `to_ptr` on the same
@ -186,6 +187,33 @@ impl<T: Sized> ToPointer for &mut T {
}
}
/// An owned GLib Source.
///
/// Dropping will remove the source.
#[repr(transparent)]
struct GSource(u32);
impl ToPointer for GSource {
fn to_ptr(self) -> *mut () {
let ptr = self.0 as _;
// to_ptr passes ownership
std::mem::forget(self);
ptr
}
unsafe fn from_ptr(ptr: *mut ()) -> Self {
GSource(ptr as _)
}
}
impl Drop for GSource {
fn drop(&mut self) {
unsafe {
gtk::g_source_remove(self.0);
}
}
}
/// Connect a GTK+ object signal to a function, providing an additional context value (by
/// reference).
macro_rules! connect_signal {
@ -748,6 +776,11 @@ fn render_element_type(element_type: &model::ElementType) -> Option<*mut gtk::Gt
property_read_only! {
property amount;
fn set(value: &Option<f32>) {
// FIXME: The logic here assumes that if `amount` is None, it will remain that
// way. Specifically, if we were to change back and forth, additional pulse
// callbacks would be registered, and they are not be removed when changing to
// a specific fraction. As this property is currently used, we don't encounter
// this case.
match &*value {
Some(v) => unsafe {
gtk::gtk_progress_bar_set_fraction(
@ -758,29 +791,16 @@ fn render_element_type(element_type: &model::ElementType) -> Option<*mut gtk::Gt
None => unsafe {
gtk::gtk_progress_bar_pulse(progress_ptr as *mut _);
fn auto_pulse_progress_bar(progress: *mut gtk::GtkProgressBar) {
unsafe extern "C" fn pulse(progress: *mut std::ffi::c_void) -> gtk::gboolean {
if gtk::gtk_widget_is_visible(progress as _) == 0 {
false.into()
} else {
gtk::gtk_progress_bar_pulse(progress as _);
true.into()
}
}
unsafe {
gtk::g_timeout_add(100, Some(pulse as unsafe extern "C" fn(*mut std::ffi::c_void) -> gtk::gboolean), progress as _);
}
unsafe extern "C" fn pulse(progress: *mut std::ffi::c_void) -> gtk::gboolean {
gtk::gtk_progress_bar_pulse(progress as _);
true.into()
}
connect_signal! {
object progress_ptr;
with std::ptr::null_mut();
signal show(_user_data: &(), progress: *mut gtk::GtkWidget) {
auto_pulse_progress_bar(progress as *mut _);
}
}
auto_pulse_progress_bar(progress_ptr as *mut _);
// This will call even when the progress bar is hidden, but it's of
// little consequence.
let source = GSource(gtk::g_timeout_add(100, Some(pulse), progress_ptr as _));
source.drop_with_widget(progress_ptr);
}
}
}

View file

@ -19,7 +19,7 @@ fn main() {
.clang_args(GTK_CFLAGS)
.allowlist_function("gtk_.*")
.allowlist_function(
"g_(application|main_context|memory_input_stream|object|signal|timeout)_.*",
"g_(application|main_context|memory_input_stream|object|signal|source|timeout)_.*",
)
.allowlist_function("gdk_pixbuf_new_from_stream")
.allowlist_function("pango_attr_.*")

View file

@ -23,7 +23,7 @@ static_prefs = { path = "../../../../modules/libpref/init/static_prefs" }
profiler_helper = { path = "../../../../tools/profiler/rust-helper", optional = true }
mozurl = { path = "../../../../netwerk/base/mozurl" }
webrender_bindings = { path = "../../../../gfx/webrender_bindings" }
cubeb-coreaudio = { git = "https://github.com/mozilla/cubeb-coreaudio-rs", rev = "579b75af21c040700eee6a1d8520e222699fe4cd", optional = true }
cubeb-coreaudio = { git = "https://github.com/mozilla/cubeb-coreaudio-rs", rev = "bebaa23317332c95734df76e25193c24a83a6840", optional = true }
cubeb-pulse = { git = "https://github.com/mozilla/cubeb-pulse-rs", rev="8678dcab1c287de79c4c184ccc2e065bc62b70e2", optional = true, features=["pulse-dlopen"] }
cubeb-sys = { version = "0.13", optional = true, features=["gecko-in-tree"] }
audioipc2-client = { git = "https://github.com/mozilla/audioipc", rev = "e6f44a2bd1e57d11dfc737632a9e849077632330", optional = true }

View file

@ -100,7 +100,7 @@ bool PerformInstallationFromDMG(int argc, char** argv);
struct UpdateServerThreadArgs {
int argc;
const NS_tchar** argv;
const char* marChannelID;
const char* marChannelID = "";
};
#endif
@ -3004,16 +3004,17 @@ static int ReadMARChannelIDsFromBuffer(char* aChannels,
* `OK` on success, `UPDATE_SETTINGS_FILE_CHANNEL` on failure.
*/
static int PopulategMARStrings() {
if (gMARStrings.MARChannelID && gMARStrings.MARChannelID[0] != '\0') {
return OK;
}
int rv = UPDATE_SETTINGS_FILE_CHANNEL;
# ifdef XP_MACOSX
if (gInvocation == UpdaterInvocation::Second) {
// An elevated update process will have already populated gMARStrings when
// it connected to the unelevated update process to obtain the command line
// args. See `ObtainUpdaterArguments`.
rv = OK;
} else if (auto marChannels =
UpdateSettingsUtil::GetAcceptedMARChannelsValue()) {
rv = ReadMARChannelIDsFromBuffer(marChannels->data(), &gMARStrings);
if (gInvocation != UpdaterInvocation::Second) {
if (std::optional<std::string> marChannels =
UpdateSettingsUtil::GetAcceptedMARChannelsValue()) {
rv = ReadMARChannelIDsFromBuffer(marChannels->data(), &gMARStrings);
}
}
# else
NS_tchar updateSettingsPath[MAXPATHLEN];
@ -3698,17 +3699,33 @@ int NS_main(int argc, NS_tchar** argv) {
UpdateServerThreadArgs threadArgs;
threadArgs.argc = suiArgc;
threadArgs.argv = suiArgv.get();
threadArgs.marChannelID = gMARStrings.MARChannelID.get();
threadArgs.marChannelID = "";
bool shouldServeElevatedUpdate = true;
Thread t1;
if (t1.Run(ServeElevatedUpdateThreadFunc, &threadArgs) == 0) {
// Show an indeterminate progress bar while an elevated update is in
// progress.
if (!isDMGInstall) {
ShowProgressUI(true);
}
# ifdef MOZ_VERIFY_MAR_SIGNATURE
int rv = PopulategMARStrings();
if (rv != OK) {
shouldServeElevatedUpdate = false;
WriteStatusFile(UPDATE_SETTINGS_FILE_CHANNEL);
fprintf(stderr,
"Unable to start unelevated update process to serve elevated "
"updater due to inability to retrieve MAR channels.");
} else {
threadArgs.marChannelID = gMARStrings.MARChannelID.get();
}
# endif // MOZ_VERIFY_MAR_SIGNATURE
if (shouldServeElevatedUpdate) {
Thread t1;
if (t1.Run(ServeElevatedUpdateThreadFunc, &threadArgs) == 0) {
// Show an indeterminate progress bar while an elevated update is in
// progress.
if (!isDMGInstall) {
ShowProgressUI(true);
}
}
t1.Join();
}
t1.Join();
}
LaunchCallbackAndPostProcessApps(argc, argv, std::move(umaskContext));