summaryrefslogtreecommitdiffstats
path: root/toolkit
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit')
-rw-r--r--toolkit/components/glean/bindings/private/TimingDistribution.cpp9
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/templates/gifft.jinja212
-rw-r--r--toolkit/components/glean/tests/pytest/gifft_output_Histogram11
-rw-r--r--toolkit/components/glean/tests/pytest/gifft_output_Scalar1
-rw-r--r--toolkit/components/normandy/actions/ShowHeartbeatAction.sys.mjs5
-rw-r--r--toolkit/components/normandy/lib/LegacyHeartbeat.sys.mjs1
-rw-r--r--toolkit/components/normandy/test/browser/browser_LegacyHeartbeat.js139
-rw-r--r--toolkit/components/pdfjs/content/build/pdf.js109
-rw-r--r--toolkit/components/prompts/src/PromptUtils.sys.mjs6
-rw-r--r--toolkit/components/reputationservice/ApplicationReputation.cpp28
-rw-r--r--toolkit/components/reputationservice/ApplicationReputation.h4
-rw-r--r--toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.cc15
-rw-r--r--toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.h3
-rw-r--r--toolkit/modules/GMPInstallManager.sys.mjs48
-rw-r--r--toolkit/modules/GMPUtils.sys.mjs1
-rw-r--r--toolkit/modules/tests/xpcshell/test_GMPInstallManager.js54
-rw-r--r--toolkit/mozapps/extensions/internal/ProductAddonChecker.sys.mjs35
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js15
-rw-r--r--toolkit/xre/nsAppRunner.cpp4
-rw-r--r--toolkit/xre/nsEmbedFunctions.cpp2
20 files changed, 435 insertions, 67 deletions
diff --git a/toolkit/components/glean/bindings/private/TimingDistribution.cpp b/toolkit/components/glean/bindings/private/TimingDistribution.cpp
index 78aad6d4cd..5a1a6d3daf 100644
--- a/toolkit/components/glean/bindings/private/TimingDistribution.cpp
+++ b/toolkit/components/glean/bindings/private/TimingDistribution.cpp
@@ -25,7 +25,7 @@ extern "C" NS_EXPORT void GIFFT_TimingDistributionStart(
auto mirrorId = mozilla::glean::HistogramIdForMetric(aMetricId);
if (mirrorId) {
mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) {
- auto tuple = std::make_tuple(aMetricId, aTimerId);
+ auto tuple = mozilla::glean::MetricTimerTuple{aMetricId, aTimerId};
// It should be all but impossible for anyone to have already inserted
// this timer for this metric given the monotonicity of timer ids.
(void)NS_WARN_IF(lock.ref()->Remove(tuple));
@@ -40,7 +40,8 @@ extern "C" NS_EXPORT void GIFFT_TimingDistributionStopAndAccumulate(
auto mirrorId = mozilla::glean::HistogramIdForMetric(aMetricId);
if (mirrorId) {
mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) {
- auto optStart = lock.ref()->Extract(std::make_tuple(aMetricId, aTimerId));
+ auto tuple = mozilla::glean::MetricTimerTuple{aMetricId, aTimerId};
+ auto optStart = lock.ref()->Extract(tuple);
// The timer might not be in the map to be removed if it's already been
// cancelled or stop_and_accumulate'd.
if (!NS_WARN_IF(!optStart)) {
@@ -67,8 +68,8 @@ extern "C" NS_EXPORT void GIFFT_TimingDistributionCancel(
mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) {
// The timer might not be in the map to be removed if it's already been
// cancelled or stop_and_accumulate'd.
- (void)NS_WARN_IF(
- !lock.ref()->Remove(std::make_tuple(aMetricId, aTimerId)));
+ auto tuple = mozilla::glean::MetricTimerTuple{aMetricId, aTimerId};
+ (void)NS_WARN_IF(!lock.ref()->Remove(tuple));
});
}
}
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/gifft.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/gifft.jinja2
index 4a58ef5411..9d0c10cdf6 100644
--- a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/gifft.jinja2
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/gifft.jinja2
@@ -35,7 +35,10 @@ using Telemetry::{{ probe_type }}ID;
using MetricId = uint32_t; // Same type as in api/src/private/mod.rs
using TimerId = uint64_t; // Same as in TimingDistribution.h.
-using MetricTimerTuple = std::tuple<MetricId, TimerId>;
+struct MetricTimerTuple {
+ MetricId mMetricId;
+ TimerId mTimerId;
+};
class MetricTimerTupleHashKey : public PLDHashEntryHdr {
public:
using KeyType = const MetricTimerTuple&;
@@ -49,15 +52,17 @@ class MetricTimerTupleHashKey : public PLDHashEntryHdr {
KeyType GetKey() const { return mValue; }
bool KeyEquals(KeyTypePointer aKey) const {
- return std::get<0>(*aKey) == std::get<0>(mValue) && std::get<1>(*aKey) == std::get<1>(mValue);
+ return aKey->mMetricId == mValue.mMetricId &&
+ aKey->mTimerId == mValue.mTimerId;
}
static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
static PLDHashNumber HashKey(KeyTypePointer aKey) {
// Chosen because this is how nsIntegralHashKey does it.
- return HashGeneric(std::get<0>(*aKey), std::get<1>(*aKey));
+ return HashGeneric(aKey->mMetricId, aKey->mTimerId);
}
enum { ALLOW_MEMMOVE = true };
+ static_assert(std::is_trivially_copyable_v<MetricTimerTuple>);
private:
const MetricTimerTuple mValue;
@@ -170,6 +175,7 @@ class ScalarIDHashKey : public PLDHashEntryHdr {
return static_cast<std::underlying_type<ScalarID>::type>(*aKey);
}
enum { ALLOW_MEMMOVE = true };
+ static_assert(std::is_trivially_copyable_v<ScalarID>);
private:
const ScalarID mValue;
diff --git a/toolkit/components/glean/tests/pytest/gifft_output_Histogram b/toolkit/components/glean/tests/pytest/gifft_output_Histogram
index b13df96594..f0e4c3d047 100644
--- a/toolkit/components/glean/tests/pytest/gifft_output_Histogram
+++ b/toolkit/components/glean/tests/pytest/gifft_output_Histogram
@@ -28,7 +28,10 @@ using Telemetry::HistogramID;
using MetricId = uint32_t; // Same type as in api/src/private/mod.rs
using TimerId = uint64_t; // Same as in TimingDistribution.h.
-using MetricTimerTuple = std::tuple<MetricId, TimerId>;
+struct MetricTimerTuple {
+ MetricId mMetricId;
+ TimerId mTimerId;
+};
class MetricTimerTupleHashKey : public PLDHashEntryHdr {
public:
using KeyType = const MetricTimerTuple&;
@@ -42,15 +45,17 @@ class MetricTimerTupleHashKey : public PLDHashEntryHdr {
KeyType GetKey() const { return mValue; }
bool KeyEquals(KeyTypePointer aKey) const {
- return std::get<0>(*aKey) == std::get<0>(mValue) && std::get<1>(*aKey) == std::get<1>(mValue);
+ return aKey->mMetricId == mValue.mMetricId &&
+ aKey->mTimerId == mValue.mTimerId;
}
static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
static PLDHashNumber HashKey(KeyTypePointer aKey) {
// Chosen because this is how nsIntegralHashKey does it.
- return HashGeneric(std::get<0>(*aKey), std::get<1>(*aKey));
+ return HashGeneric(aKey->mMetricId, aKey->mTimerId);
}
enum { ALLOW_MEMMOVE = true };
+ static_assert(std::is_trivially_copyable_v<MetricTimerTuple>);
private:
const MetricTimerTuple mValue;
diff --git a/toolkit/components/glean/tests/pytest/gifft_output_Scalar b/toolkit/components/glean/tests/pytest/gifft_output_Scalar
index 66d25a2629..1ea7517407 100644
--- a/toolkit/components/glean/tests/pytest/gifft_output_Scalar
+++ b/toolkit/components/glean/tests/pytest/gifft_output_Scalar
@@ -92,6 +92,7 @@ class ScalarIDHashKey : public PLDHashEntryHdr {
return static_cast<std::underlying_type<ScalarID>::type>(*aKey);
}
enum { ALLOW_MEMMOVE = true };
+ static_assert(std::is_trivially_copyable_v<ScalarID>);
private:
const ScalarID mValue;
diff --git a/toolkit/components/normandy/actions/ShowHeartbeatAction.sys.mjs b/toolkit/components/normandy/actions/ShowHeartbeatAction.sys.mjs
index d571e46167..1f6be470aa 100644
--- a/toolkit/components/normandy/actions/ShowHeartbeatAction.sys.mjs
+++ b/toolkit/components/normandy/actions/ShowHeartbeatAction.sys.mjs
@@ -76,10 +76,7 @@ export class ShowHeartbeatAction extends BaseAction {
learnMoreUrl,
postAnswerUrl: await this.generatePostAnswerURL(recipe),
flowId: lazy.NormandyUtils.generateUuid(),
- // Recipes coming from Nimbus won't have a revision_id.
- ...(Object.hasOwn(recipe, "revision_id")
- ? { surveyVersion: recipe.revision_id }
- : {}),
+ surveyVersion: recipe.revision_id,
});
heartbeat.eventEmitter.once(
diff --git a/toolkit/components/normandy/lib/LegacyHeartbeat.sys.mjs b/toolkit/components/normandy/lib/LegacyHeartbeat.sys.mjs
index 501c9f70af..93c24faf5d 100644
--- a/toolkit/components/normandy/lib/LegacyHeartbeat.sys.mjs
+++ b/toolkit/components/normandy/lib/LegacyHeartbeat.sys.mjs
@@ -43,6 +43,7 @@ export const LegacyHeartbeat = {
capabilities: ["action.show-heartbeat"],
filter_expression: "true",
use_only_baseline_capabilities: true,
+ revision_id: "1", // Required for the Heartbeat telemetry ping.
};
},
};
diff --git a/toolkit/components/normandy/test/browser/browser_LegacyHeartbeat.js b/toolkit/components/normandy/test/browser/browser_LegacyHeartbeat.js
index 465e5c1040..a40fa7350e 100644
--- a/toolkit/components/normandy/test/browser/browser_LegacyHeartbeat.js
+++ b/toolkit/components/normandy/test/browser/browser_LegacyHeartbeat.js
@@ -9,6 +9,9 @@ const { BaseAction } = ChromeUtils.importESModule(
const { ClientEnvironment } = ChromeUtils.importESModule(
"resource://normandy/lib/ClientEnvironment.sys.mjs"
);
+const { EventEmitter } = ChromeUtils.importESModule(
+ "resource://normandy/lib/EventEmitter.sys.mjs"
+);
const { Heartbeat } = ChromeUtils.importESModule(
"resource://normandy/lib/Heartbeat.sys.mjs"
);
@@ -27,6 +30,9 @@ const { RecipeRunner } = ChromeUtils.importESModule(
const { RemoteSettings } = ChromeUtils.importESModule(
"resource://services-settings/remote-settings.sys.mjs"
);
+const { JsonSchema } = ChromeUtils.importESModule(
+ "resource://gre/modules/JsonSchema.sys.mjs"
+);
const SURVEY = {
surveyId: "a survey",
@@ -39,9 +45,80 @@ const SURVEY = {
repeatOption: "once",
};
+// See properties.payload in
+// https://github.com/mozilla-services/mozilla-pipeline-schemas/blob/main/schemas/telemetry/heartbeat/heartbeat.4.schema.json
+
+const PAYLOAD_SCHEMA = {
+ additionalProperties: false,
+ anyOf: [
+ {
+ required: ["closedTS"],
+ },
+ {
+ required: ["windowClosedTS"],
+ },
+ ],
+ properties: {
+ closedTS: {
+ minimum: 0,
+ type: "integer",
+ },
+ engagedTS: {
+ minimum: 0,
+ type: "integer",
+ },
+ expiredTS: {
+ minimum: 0,
+ type: "integer",
+ },
+ flowId: {
+ pattern:
+ "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$",
+ type: "string",
+ },
+ learnMoreTS: {
+ minimum: 0,
+ type: "integer",
+ },
+ offeredTS: {
+ minimum: 0,
+ type: "integer",
+ },
+ score: {
+ minimum: 1,
+ type: "integer",
+ },
+ surveyId: {
+ type: "string",
+ },
+ surveyVersion: {
+ pattern: "^([0-9]+|[a-fA-F0-9]{64})$",
+ type: "string",
+ },
+ testing: {
+ type: "boolean",
+ },
+ version: {
+ maximum: 1,
+ minimum: 1,
+ type: "number",
+ },
+ votedTS: {
+ minimum: 0,
+ type: "integer",
+ },
+ windowClosedTS: {
+ minimum: 0,
+ type: "integer",
+ },
+ },
+ required: ["version", "flowId", "offeredTS", "surveyId", "surveyVersion"],
+ type: "object",
+};
+
function assertSurvey(actual, expected) {
for (const key of Object.keys(actual)) {
- if (["postAnswerUrl", "flowId"].includes(key)) {
+ if (["flowId", "postAnswerUrl", "surveyVersion"].includes(key)) {
continue;
}
@@ -52,6 +129,7 @@ function assertSurvey(actual, expected) {
);
}
+ Assert.equal(actual.surveyVersion, "1");
Assert.ok(actual.postAnswerUrl.startsWith(expected.postAnswerUrl));
}
@@ -86,3 +164,62 @@ decorate_task(
}
}
);
+
+decorate_task(
+ withClearStorage(),
+ async function testLegacyHeartbeatPingPayload() {
+ const sandbox = sinon.createSandbox();
+
+ const cleanupEnrollment = await ExperimentFakes.enrollWithFeatureConfig({
+ featureId: "legacyHeartbeat",
+ value: {
+ survey: SURVEY,
+ },
+ });
+
+ const client = RemoteSettings("normandy-recipes-capabilities");
+ sandbox.stub(client, "get").resolves([]);
+
+ // Override Heartbeat so we can get the instance and manipulate it directly.
+ let resolveHeartbeatPromise;
+ const heartbeatPromise = new Promise(resolve => {
+ resolveHeartbeatPromise = resolve;
+ });
+ class TestHeartbeat extends Heartbeat {
+ constructor(...args) {
+ super(...args);
+ resolveHeartbeatPromise(this);
+ }
+ }
+ ShowHeartbeatAction.overrideHeartbeatForTests(TestHeartbeat);
+
+ try {
+ await RecipeRunner.run();
+ const heartbeat = await heartbeatPromise;
+ // We are going to simulate the timer timing out, so we do not want it to
+ // *actually* time out.
+ heartbeat.endTimerIfPresent("surveyEndTimer");
+
+ const telemetrySentPromise = new Promise(resolve => {
+ heartbeat.eventEmitter.once("TelemetrySent", payload =>
+ resolve(payload)
+ );
+ });
+
+ // This method would be triggered when the timer timed out. This will
+ // trigger telemetry to be submitted.
+ heartbeat.close();
+
+ const payload = await telemetrySentPromise;
+
+ const result = JsonSchema.validate(payload, PAYLOAD_SCHEMA);
+ Assert.ok(result.valid);
+ Assert.equal(payload.surveyVersion, "1");
+
+ await cleanupEnrollment();
+ } finally {
+ ShowHeartbeatAction.overrideHeartbeatForTests();
+ sandbox.restore();
+ }
+ }
+);
diff --git a/toolkit/components/pdfjs/content/build/pdf.js b/toolkit/components/pdfjs/content/build/pdf.js
index f59bc41651..017a45914e 100644
--- a/toolkit/components/pdfjs/content/build/pdf.js
+++ b/toolkit/components/pdfjs/content/build/pdf.js
@@ -9375,11 +9375,13 @@ exports.FreeTextEditor = void 0;
var _util = __w_pdfjs_require__(1);
var _tools = __w_pdfjs_require__(5);
var _editor = __w_pdfjs_require__(4);
+const EOL_PATTERN = /\r\n?|\n/g;
class FreeTextEditor extends _editor.AnnotationEditor {
#boundEditorDivBlur = this.editorDivBlur.bind(this);
#boundEditorDivFocus = this.editorDivFocus.bind(this);
#boundEditorDivInput = this.editorDivInput.bind(this);
#boundEditorDivKeydown = this.editorDivKeydown.bind(this);
+ #boundEditorDivPaste = this.editorDivPaste.bind(this);
#color;
#content = "";
#editorDivId = `${this.id}-editor`;
@@ -9495,6 +9497,7 @@ class FreeTextEditor extends _editor.AnnotationEditor {
this.editorDiv.addEventListener("focus", this.#boundEditorDivFocus);
this.editorDiv.addEventListener("blur", this.#boundEditorDivBlur);
this.editorDiv.addEventListener("input", this.#boundEditorDivInput);
+ this.editorDiv.addEventListener("paste", this.#boundEditorDivPaste);
}
disableEditMode() {
if (!this.isInEditMode()) {
@@ -9510,6 +9513,7 @@ class FreeTextEditor extends _editor.AnnotationEditor {
this.editorDiv.removeEventListener("focus", this.#boundEditorDivFocus);
this.editorDiv.removeEventListener("blur", this.#boundEditorDivBlur);
this.editorDiv.removeEventListener("input", this.#boundEditorDivInput);
+ this.editorDiv.removeEventListener("paste", this.#boundEditorDivPaste);
this.div.focus({
preventScroll: true
});
@@ -9539,13 +9543,10 @@ class FreeTextEditor extends _editor.AnnotationEditor {
super.remove();
}
#extractText() {
- const divs = this.editorDiv.getElementsByTagName("div");
- if (divs.length === 0) {
- return this.editorDiv.innerText;
- }
const buffer = [];
- for (const div of divs) {
- buffer.push(div.innerText.replace(/\r\n?|\n/, ""));
+ this.editorDiv.normalize();
+ for (const child of this.editorDiv.childNodes) {
+ buffer.push(FreeTextEditor.#getNodeContent(child));
}
return buffer.join("\n");
}
@@ -9658,6 +9659,102 @@ class FreeTextEditor extends _editor.AnnotationEditor {
}
return this.div;
}
+ static #getNodeContent(node) {
+ return (node.nodeType === Node.TEXT_NODE ? node.nodeValue : node.innerText).replaceAll(EOL_PATTERN, "");
+ }
+ editorDivPaste(event) {
+ const clipboardData = event.clipboardData || window.clipboardData;
+ const {
+ types
+ } = clipboardData;
+ if (types.length === 1 && types[0] === "text/plain") {
+ return;
+ }
+ event.preventDefault();
+ const paste = FreeTextEditor.#deserializeContent(clipboardData.getData("text") || "").replaceAll(EOL_PATTERN, "\n");
+ if (!paste) {
+ return;
+ }
+ const selection = window.getSelection();
+ if (!selection.rangeCount) {
+ return;
+ }
+ this.editorDiv.normalize();
+ selection.deleteFromDocument();
+ const range = selection.getRangeAt(0);
+ if (!paste.includes("\n")) {
+ range.insertNode(document.createTextNode(paste));
+ this.editorDiv.normalize();
+ selection.collapseToStart();
+ return;
+ }
+ const {
+ startContainer,
+ startOffset
+ } = range;
+ const bufferBefore = [];
+ const bufferAfter = [];
+ if (startContainer.nodeType === Node.TEXT_NODE) {
+ const parent = startContainer.parentElement;
+ bufferAfter.push(startContainer.nodeValue.slice(startOffset).replaceAll(EOL_PATTERN, ""));
+ if (parent !== this.editorDiv) {
+ let buffer = bufferBefore;
+ for (const child of this.editorDiv.childNodes) {
+ if (child === parent) {
+ buffer = bufferAfter;
+ continue;
+ }
+ buffer.push(FreeTextEditor.#getNodeContent(child));
+ }
+ }
+ bufferBefore.push(startContainer.nodeValue.slice(0, startOffset).replaceAll(EOL_PATTERN, ""));
+ } else if (startContainer === this.editorDiv) {
+ let buffer = bufferBefore;
+ let i = 0;
+ for (const child of this.editorDiv.childNodes) {
+ if (i++ === startOffset) {
+ buffer = bufferAfter;
+ }
+ buffer.push(FreeTextEditor.#getNodeContent(child));
+ }
+ }
+ this.#content = `${bufferBefore.join("\n")}${paste}${bufferAfter.join("\n")}`;
+ this.#setContent();
+ const newRange = new Range();
+ let beforeLength = bufferBefore.reduce((acc, line) => acc + line.length, 0);
+ for (const {
+ firstChild
+ } of this.editorDiv.childNodes) {
+ if (firstChild.nodeType === Node.TEXT_NODE) {
+ const length = firstChild.nodeValue.length;
+ if (beforeLength <= length) {
+ newRange.setStart(firstChild, beforeLength);
+ newRange.setEnd(firstChild, beforeLength);
+ break;
+ }
+ beforeLength -= length;
+ }
+ }
+ selection.removeAllRanges();
+ selection.addRange(newRange);
+ }
+ #setContent() {
+ this.editorDiv.replaceChildren();
+ if (!this.#content) {
+ return;
+ }
+ for (const line of this.#content.split("\n")) {
+ const div = document.createElement("div");
+ div.append(line ? document.createTextNode(line) : document.createElement("br"));
+ this.editorDiv.append(div);
+ }
+ }
+ #serializeContent() {
+ return this.#content.replaceAll("\xa0", " ");
+ }
+ static #deserializeContent(content) {
+ return content.replaceAll(" ", "\xa0");
+ }
get contentDiv() {
return this.editorDiv;
}
diff --git a/toolkit/components/prompts/src/PromptUtils.sys.mjs b/toolkit/components/prompts/src/PromptUtils.sys.mjs
index 7a15802b84..8c1b0ff992 100644
--- a/toolkit/components/prompts/src/PromptUtils.sys.mjs
+++ b/toolkit/components/prompts/src/PromptUtils.sys.mjs
@@ -76,6 +76,12 @@ export var EnableDelayHelper = function ({
this.focusTarget.addEventListener("keydown", this, true);
this.focusTarget.document.addEventListener("unload", this);
+ // If we're not part of the active window, don't even start the timer yet.
+ let topWin = focusTarget.browsingContext.top.window;
+ if (topWin != Services.focus.activeWindow) {
+ return;
+ }
+
this.startOnFocusDelay();
};
diff --git a/toolkit/components/reputationservice/ApplicationReputation.cpp b/toolkit/components/reputationservice/ApplicationReputation.cpp
index cc4045d5ad..8973fc85bb 100644
--- a/toolkit/components/reputationservice/ApplicationReputation.cpp
+++ b/toolkit/components/reputationservice/ApplicationReputation.cpp
@@ -519,20 +519,20 @@ const char* const ApplicationReputationService::kBinaryFileExtensions[] = {
".xlam", // MS Excel
".xldm", // MS Excel
//".xll", exec // MS Excel
- ".xlm", // MS Excel
- ".xls", // MS Excel
- ".xlsb", // MS Excel
- ".xlsm", // MS Excel
- ".xlsx", // MS Excel
- ".xlt", // MS Excel
- ".xltm", // MS Excel
- ".xltx", // MS Excel
- ".xlw", // MS Excel
- ".xml", // MS Excel
- ".xnk", // MS Exchange
- ".xrm-ms", // Windows
- ".xsd", // XML schema definition
- ".xsl", // XML Stylesheet
+ ".xlm", // MS Excel
+ ".xls", // MS Excel
+ ".xlsb", // MS Excel
+ ".xlsm", // MS Excel
+ ".xlsx", // MS Excel
+ ".xlt", // MS Excel
+ ".xltm", // MS Excel
+ ".xltx", // MS Excel
+ ".xlw", // MS Excel
+ ".xml", // MS Excel
+ ".xnk", // MS Exchange
+ //".xrm-ms", exec // Windows
+ ".xsd", // XML schema definition
+ ".xsl", // XML Stylesheet
//".xxe",
".xz", // Linux archive (xz)
".z", // InstallShield
diff --git a/toolkit/components/reputationservice/ApplicationReputation.h b/toolkit/components/reputationservice/ApplicationReputation.h
index f708c6eb71..5ea5b825f7 100644
--- a/toolkit/components/reputationservice/ApplicationReputation.h
+++ b/toolkit/components/reputationservice/ApplicationReputation.h
@@ -27,9 +27,9 @@ class ApplicationReputationService final
public:
static const char* const kNonBinaryExecutables[5];
#ifdef XP_WIN
- static const char* const kBinaryFileExtensions[185];
-#else
static const char* const kBinaryFileExtensions[184];
+#else
+ static const char* const kBinaryFileExtensions[183];
#endif
static already_AddRefed<ApplicationReputationService> GetSingleton();
diff --git a/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.cc b/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.cc
index e13e4509b0..64c01bdce4 100644
--- a/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.cc
+++ b/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.cc
@@ -83,7 +83,7 @@ MinidumpGenerator::MinidumpGenerator()
cpu_type_(DynamicImages::GetNativeCPUType()),
dyldImageLoadAddress_(NULL),
dyldSlide_(0),
- dyldPath_(),
+ dyldPath_(nullptr),
task_context_(NULL),
dynamic_images_(NULL),
memory_blocks_(&allocator_) {
@@ -105,7 +105,7 @@ MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task,
cpu_type_(DynamicImages::GetNativeCPUType()),
dyldImageLoadAddress_(NULL),
dyldSlide_(0),
- dyldPath_(),
+ dyldPath_(nullptr),
task_context_(NULL),
dynamic_images_(NULL),
memory_blocks_(&allocator_) {
@@ -250,7 +250,7 @@ void MinidumpGenerator::GatherCurrentProcessDyldInformation() {
return;
}
dyldImageLoadAddress_ = mh;
- dyldPath_ = string(aii->dyldPath);
+ dyldPath_ = aii->dyldPath;
dyldSlide_ = GetCurrentProcessModuleSlide(mh, aii->sharedCacheSlide);
}
@@ -1464,7 +1464,7 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
if (index == INT_MAX) {
dyld_or_in_dyld_shared_cache = true;
slide = dyldSlide_;
- name = dyldPath_.c_str();
+ name = dyldPath_;
} else {
dyld_or_in_dyld_shared_cache =
((header->flags & MH_SHAREDCACHE) != 0);
@@ -1993,7 +1993,7 @@ bool MinidumpGenerator::WriteCrashInfoStream(
bool dyld_or_in_dyld_shared_cache;
if (i == image_count - 1) {
slide = dyldSlide_;
- module_path = dyldPath_.c_str();
+ module_path = dyldPath_;
dyld_or_in_dyld_shared_cache = true;
} else {
slide = _dyld_get_image_vmaddr_slide(i);
@@ -2048,7 +2048,10 @@ bool MinidumpGenerator::WriteBootargsStream(
int rv = sysctlbyname("kern.bootargs", NULL, &size, NULL, 0);
if ((rv != 0) || (size == 0))
size = 1;
- vector<uint8_t> bootargs(size);
+
+ wasteful_vector<uint8_t> bootargs(&this->allocator_, size);
+ bootargs.resize(size, 0);
+
bootargs[0] = 0;
if (rv == 0)
sysctlbyname("kern.bootargs", &bootargs[0], &size, NULL, 0);
diff --git a/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.h b/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.h
index aba067cc04..77c250ccd5 100644
--- a/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.h
+++ b/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.h
@@ -266,7 +266,8 @@ class MinidumpGenerator {
// process has crashed.
breakpad_mach_header* dyldImageLoadAddress_;
ptrdiff_t dyldSlide_;
- string dyldPath_;
+ // We don't own this pointer, this is owned by macOS internal structures.
+ const char* dyldPath_;
// Context of the task to dump.
breakpad_ucontext_t *task_context_;
diff --git a/toolkit/modules/GMPInstallManager.sys.mjs b/toolkit/modules/GMPInstallManager.sys.mjs
index 9cb4802e58..421a843c71 100644
--- a/toolkit/modules/GMPInstallManager.sys.mjs
+++ b/toolkit/modules/GMPInstallManager.sys.mjs
@@ -43,6 +43,16 @@ const LOCAL_GMP_SOURCES = [
},
];
+function getLocalSources() {
+ if (GMPPrefs.getBool(GMPPrefs.KEY_ALLOW_LOCAL_SOURCES, true)) {
+ return LOCAL_GMP_SOURCES;
+ }
+
+ let log = getScopedLogger("GMPInstallManager.downloadLocalConfig");
+ log.info("ignoring local sources");
+ return [];
+}
+
function downloadJSON(uri) {
let log = getScopedLogger("GMPInstallManager.checkForAddons");
log.info("fetching config from: " + uri);
@@ -70,7 +80,7 @@ function downloadJSON(uri) {
function downloadLocalConfig() {
let log = getScopedLogger("GMPInstallManager.downloadLocalConfig");
return Promise.all(
- LOCAL_GMP_SOURCES.map(conf => {
+ getLocalSources().map(conf => {
return downloadJSON(conf.src).then(addons => {
let platforms = addons.vendors[conf.id].platforms;
let target = Services.appinfo.OS + "_" + lazy.UpdateUtils.ABI;
@@ -146,6 +156,36 @@ GMPInstallManager.prototype = {
},
/**
+ * Determines the root to use for verifying content signatures.
+ * @param url
+ * The Balrog URL, i.e. the return value of _getURL().
+ */
+ _getContentSignatureRootForURL(url) {
+ // The prod and stage URLs of Balrog are documented at:
+ // https://mozilla-balrog.readthedocs.io/en/latest/infrastructure.html
+ // Note: we are matching by prefix without the full domain nor slash, to
+ // enable us to move to a different host name in the future if desired.
+ if (url.startsWith("https://aus")) {
+ return Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot;
+ }
+ if (url.startsWith("https://stage.")) {
+ return Ci.nsIContentSignatureVerifier.ContentSignatureStageRoot;
+ }
+ if (Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")) {
+ return Ci.nsIX509CertDB.AppXPCShellRoot;
+ }
+ // When content signature verification for GMP was added (bug 1714621), a
+ // pref existed to configure an arbitrary root, which enabled local testing.
+ // This pref was removed later in bug 1769669, and replaced with hard-coded
+ // roots (prod and tests only). Support for testing against the stage server
+ // was restored in bug 1771992.
+ // Note: other verifiers ultimately fall back to ContentSignatureLocalRoot,
+ // to support local development. Here we use ContentSignatureProdRoot to
+ // minimize risk (and the unclear demand for "local" development).
+ return Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot;
+ },
+
+ /**
* Records telemetry results on if fetching update.xml from Balrog succeeded
* when content signature was used to verify the response from Balrog.
* @param didGetAddonList
@@ -318,15 +358,17 @@ GMPInstallManager.prototype = {
}
let url = await this._getURL();
+ let trustedContentSignatureRoot = this._getContentSignatureRootForURL(url);
log.info(
- `Fetching product addon list url=${url}, allowNonBuiltIn=${allowNonBuiltIn}, certs=${certs}, checkContentSignature=${checkContentSignature}`
+ `Fetching product addon list url=${url}, allowNonBuiltIn=${allowNonBuiltIn}, certs=${certs}, checkContentSignature=${checkContentSignature}, trustedContentSignatureRoot=${trustedContentSignatureRoot}`
);
let addonPromise = ProductAddonChecker.getProductAddonList(
url,
allowNonBuiltIn,
certs,
- checkContentSignature
+ checkContentSignature,
+ trustedContentSignatureRoot
)
.then(res => {
if (checkContentSignature) {
diff --git a/toolkit/modules/GMPUtils.sys.mjs b/toolkit/modules/GMPUtils.sys.mjs
index 488a024d13..c21576e0a9 100644
--- a/toolkit/modules/GMPUtils.sys.mjs
+++ b/toolkit/modules/GMPUtils.sys.mjs
@@ -124,6 +124,7 @@ export var GMPPrefs = {
KEY_PLUGIN_ABI: "media.{0}.abi",
KEY_PLUGIN_FORCE_SUPPORTED: "media.{0}.forceSupported",
KEY_PLUGIN_ALLOW_X64_ON_ARM64: "media.{0}.allow-x64-plugin-on-arm64",
+ KEY_ALLOW_LOCAL_SOURCES: "media.gmp-manager.allowLocalSources",
KEY_URL: "media.gmp-manager.url",
KEY_URL_OVERRIDE: "media.gmp-manager.url.override",
KEY_CERT_CHECKATTRS: "media.gmp-manager.cert.checkAttributes",
diff --git a/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js
index 87de57efaf..e755690e6c 100644
--- a/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js
+++ b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js
@@ -802,6 +802,60 @@ add_task(async function test_checkForAddons_contentSignatureFailure() {
});
/**
+ * Tests that the signature verification URL is as expected.
+ */
+add_task(async function test_checkForAddons_get_verifier_url() {
+ const previousUrlOverride = setupContentSigTestPrefs();
+
+ let installManager = new GMPInstallManager();
+ // checkForAddons() calls _getContentSignatureRootForURL() with the return
+ // value of _getURL(), which is effectively KEY_URL_OVERRIDE or KEY_URL
+ // followed by some normalization.
+ const rootForUrl = async () => {
+ const url = await installManager._getURL();
+ return installManager._getContentSignatureRootForURL(url);
+ };
+
+ Assert.equal(
+ await rootForUrl(),
+ Ci.nsIX509CertDB.AppXPCShellRoot,
+ "XPCShell root used by default in xpcshell test"
+ );
+
+ const defaultPrefs = Services.prefs.getDefaultBranch("");
+ const defaultUrl = defaultPrefs.getStringPref(GMPPrefs.KEY_URL);
+ Preferences.set(GMPPrefs.KEY_URL_OVERRIDE, defaultUrl);
+ Assert.equal(
+ await rootForUrl(),
+ Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot,
+ "Production cert should be used for the default Balrog URL: " + defaultUrl
+ );
+
+ // The current Balrog endpoint is at aus5.mozilla.org. Confirm that the prod
+ // cert is used even if we bump the version (e.g. aus6):
+ const potentialProdUrl = "https://aus1337.mozilla.org/potential/prod/URL";
+ Preferences.set(GMPPrefs.KEY_URL_OVERRIDE, potentialProdUrl);
+ Assert.equal(
+ await rootForUrl(),
+ Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot,
+ "Production cert should be used for: " + potentialProdUrl
+ );
+
+ // Stage URL documented at https://mozilla-balrog.readthedocs.io/en/latest/infrastructure.html
+ const stageUrl = "https://stage.balrog.nonprod.cloudops.mozgcp.net/etc.";
+ Preferences.set(GMPPrefs.KEY_URL_OVERRIDE, stageUrl);
+ Assert.equal(
+ await rootForUrl(),
+ Ci.nsIContentSignatureVerifier.ContentSignatureStageRoot,
+ "Stage cert should be used with the stage URL: " + stageUrl
+ );
+
+ installManager.uninit();
+
+ revertContentSigTestPrefs(previousUrlOverride);
+});
+
+/**
* Tests that checkForAddons() works as expected when certificate pinning
* checking is enabled. We plan to move away from cert pinning in favor of
* content signature checks, but part of doing this is comparing the telemetry
diff --git a/toolkit/mozapps/extensions/internal/ProductAddonChecker.sys.mjs b/toolkit/mozapps/extensions/internal/ProductAddonChecker.sys.mjs
index 1615a551c8..f76ebf0d30 100644
--- a/toolkit/mozapps/extensions/internal/ProductAddonChecker.sys.mjs
+++ b/toolkit/mozapps/extensions/internal/ProductAddonChecker.sys.mjs
@@ -118,12 +118,18 @@ async function conservativeFetch(input) {
* @param contentSignatureHeader
* The contents of the 'content-signature' header received along with
* `data`.
+ * @param trustedRoot
+ * The identifier of the trusted root to use for certificate validation.
* @return A promise that will resolve to nothing if the signature verification
* succeeds, or rejects on failure, with an Error that sets its
* addonCheckerErr property disambiguate failure cases and a message
* explaining the error.
*/
-async function verifyGmpContentSignature(data, contentSignatureHeader) {
+async function verifyGmpContentSignature(
+ data,
+ contentSignatureHeader,
+ trustedRoot
+) {
if (!contentSignatureHeader) {
logger.warn(
"Unexpected missing content signature header during content signature validation"
@@ -186,13 +192,6 @@ async function verifyGmpContentSignature(data, contentSignatureHeader) {
"@mozilla.org/security/contentsignatureverifier;1"
].createInstance(Ci.nsIContentSignatureVerifier);
- // See bug 1771992. In the future, this may need to handle staging and dev
- // environments in addition to just production and testing.
- let root = Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot;
- if (Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")) {
- root = Ci.nsIX509CertDB.AppXPCShellRoot;
- }
-
let valid;
try {
valid = await verifier.asyncVerifyContentSignature(
@@ -200,7 +199,7 @@ async function verifyGmpContentSignature(data, contentSignatureHeader) {
signature,
certChain,
"aus.content-signature.mozilla.org",
- root
+ trustedRoot
);
} catch (err) {
logger.warn(`Unexpected error while validating content signature: ${err}`);
@@ -329,6 +328,9 @@ function downloadXMLWithRequest(
* @param verifyContentSignature
* When true, will verify the content signature information from the
* response header. Failure to verify will result in an error.
+ * @param trustedContentSignatureRoot
+ * The trusted root to use for certificate validation.
+ * Must be set if verifyContentSignature is true.
* @return a promise that resolves to the DOM document downloaded or rejects
* with a JS exception in case of error.
*/
@@ -336,7 +338,8 @@ async function downloadXML(
url,
allowNonBuiltIn = false,
allowedCerts = null,
- verifyContentSignature = false
+ verifyContentSignature = false,
+ trustedContentSignatureRoot = null
) {
let request = await downloadXMLWithRequest(
url,
@@ -346,7 +349,8 @@ async function downloadXML(
if (verifyContentSignature) {
await verifyGmpContentSignature(
request.response,
- request.getResponseHeader("content-signature")
+ request.getResponseHeader("content-signature"),
+ trustedContentSignatureRoot
);
}
return request.responseXML;
@@ -535,6 +539,9 @@ export const ProductAddonChecker = {
* @param verifyContentSignature
* When true, will verify the content signature information from the
* response header. Failure to verify will result in an error.
+ * @param trustedContentSignatureRoot
+ * The trusted root to use for certificate validation.
+ * Must be set if verifyContentSignature is true.
* @return a promise that resolves to an object containing the list of add-ons
* and whether the local fallback was used, or rejects with a JS
* exception in case of error. In the case of an error, a best effort
@@ -545,13 +552,15 @@ export const ProductAddonChecker = {
url,
allowNonBuiltIn = false,
allowedCerts = null,
- verifyContentSignature = false
+ verifyContentSignature = false,
+ trustedContentSignatureRoot = null
) {
return downloadXML(
url,
allowNonBuiltIn,
allowedCerts,
- verifyContentSignature
+ verifyContentSignature,
+ trustedContentSignatureRoot
).then(parseXML);
},
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js b/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js
index 5ae61568ef..93602ffac9 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_ProductAddonChecker_signatures.js
@@ -98,7 +98,8 @@ add_task(async function test_valid_content_signature() {
signedBaseUri + goodXmlPath + "?" + validSignatureQuery,
/*allowNonBuiltIn*/ false,
/*allowedCerts*/ false,
- /*verifyContentSignature*/ true
+ /*verifyContentSignature*/ true,
+ /*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot
);
Assert.ok(true, "Should successfully get addon list");
@@ -122,7 +123,8 @@ add_task(async function test_invalid_content_signature() {
signedBaseUri + goodXmlPath + "?" + invalidSignatureQuery,
/*allowNonBuiltIn*/ false,
/*allowedCerts*/ false,
- /*verifyContentSignature*/ true
+ /*verifyContentSignature*/ true,
+ /*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot
);
Assert.ok(false, "Should fail to get addon list");
} catch (e) {
@@ -143,7 +145,8 @@ add_task(async function test_missing_content_signature_header() {
signedBaseUri + goodXmlPath + "?" + missingSignatureQuery,
/*allowNonBuiltIn*/ false,
/*allowedCerts*/ false,
- /*verifyContentSignature*/ true
+ /*verifyContentSignature*/ true,
+ /*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot
);
Assert.ok(false, "Should fail to get addon list");
} catch (e) {
@@ -165,7 +168,8 @@ add_task(async function test_incomplete_content_signature_header() {
signedBaseUri + goodXmlPath + "?" + incompleteSignatureQuery,
/*allowNonBuiltIn*/ false,
/*allowedCerts*/ false,
- /*verifyContentSignature*/ true
+ /*verifyContentSignature*/ true,
+ /*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot
);
Assert.ok(false, "Should fail to get addon list");
} catch (e) {
@@ -187,7 +191,8 @@ add_task(async function test_bad_x5u_content_signature_header() {
signedBaseUri + goodXmlPath + "?" + badX5uSignatureQuery,
/*allowNonBuiltIn*/ false,
/*allowedCerts*/ false,
- /*verifyContentSignature*/ true
+ /*verifyContentSignature*/ true,
+ /*trustedContentSignatureRoot*/ Ci.nsIX509CertDB.AppXPCShellRoot
);
Assert.ok(false, "Should fail to get addon list");
} catch (e) {
diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp
index 14b8c98dc8..ec23a4d39f 100644
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -4206,7 +4206,9 @@ int XREMain::XRE_mainInit(bool* aExitFlag) {
#if defined(MOZ_SANDBOX) && defined(XP_WIN)
if (mAppData->sandboxBrokerServices) {
- SandboxBroker::Initialize(mAppData->sandboxBrokerServices);
+ nsAutoString binDirPath;
+ MOZ_ALWAYS_SUCCEEDS(xreBinDirectory->GetPath(binDirPath));
+ SandboxBroker::Initialize(mAppData->sandboxBrokerServices, binDirPath);
} else {
# if defined(MOZ_SANDBOX)
// If we're sandboxing content and we fail to initialize, then crashing here
diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp
index e3d975ca18..d2cc2506aa 100644
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -529,7 +529,7 @@ nsresult XRE_InitChildProcess(int aArgc, char* aArgv[],
#if defined(XP_WIN)
# if defined(MOZ_SANDBOX)
if (aChildData->sandboxBrokerServices) {
- SandboxBroker::Initialize(aChildData->sandboxBrokerServices);
+ SandboxBroker::Initialize(aChildData->sandboxBrokerServices, u""_ns);
SandboxBroker::GeckoDependentInitialize();
}
# endif // defined(MOZ_SANDBOX)