diff --git a/browser/base/content/browser-fullScreen.js b/browser/base/content/browser-fullScreen.js
index c71d77aa4367..6b5a70f89b87 100644
--- a/browser/base/content/browser-fullScreen.js
+++ b/browser/base/content/browser-fullScreen.js
@@ -95,6 +95,11 @@ var FullScreen = {
// TabsInTitlebar._update() and bug 1173768.
TabsInTitlebar.updateAppearance(true);
}
+
+ if (enterFS) {
+ Services.telemetry.getHistogramById("FX_BROWSER_FULLSCREEN_USED")
+ .add(1);
+ }
},
exitDomFullScreen : function() {
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 433cf7394d5b..a9093ff3e571 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7533,8 +7533,6 @@ function switchToTabHavingURI(aURI, aOpenNew, aOpenParams={}) {
aWindow.focus();
if (ignoreFragment) {
let spec = aURI.spec;
- if (!aURI.ref)
- spec += "#";
browser.loadURI(spec);
}
aWindow.gBrowser.tabContainer.selectedIndex = i;
diff --git a/browser/base/content/test/general/browser_bug1025195_switchToTabHavingURI_aOpenParams.js b/browser/base/content/test/general/browser_bug1025195_switchToTabHavingURI_aOpenParams.js
index fff36eeeea8e..cdd2f2be4e0d 100644
--- a/browser/base/content/test/general/browser_bug1025195_switchToTabHavingURI_aOpenParams.js
+++ b/browser/base/content/test/general/browser_bug1025195_switchToTabHavingURI_aOpenParams.js
@@ -25,6 +25,12 @@ add_task(function test_ignoreFragment() {
switchTab("about:home#1", false);
isnot(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should not be initial about:blank tab");
is(gBrowser.tabs.length, numTabsAtStart + 1, "Should have one new tab opened");
+ switchTab("about:mozilla", true);
+ switchTab("about:home", true, {ignoreFragment: true});
+ yield promiseWaitForCondition(function() {
+ return tabRefAboutHome.linkedBrowser.currentURI.spec == "about:home";
+ });
+ is(tabRefAboutHome.linkedBrowser.currentURI.spec, "about:home", "about:home shouldn't have hash");
switchTab("about:about", false, { ignoreFragment: true });
cleanupTestTabs();
});
diff --git a/browser/components/migration/EdgeProfileMigrator.js b/browser/components/migration/EdgeProfileMigrator.js
index a40564d14b1e..a6df737e7f4e 100644
--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -5,8 +5,7 @@
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
Cu.import("resource:///modules/MSMigrationUtils.jsm");
@@ -23,6 +22,16 @@ EdgeProfileMigrator.prototype.getResources = function() {
return resources.filter(r => r.exists);
};
+/* Somewhat counterintuitively, this returns:
+ * - |null| to indicate "There is only 1 (default) profile" (on win10+)
+ * - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid using this migrator.
+ * See MigrationUtils.jsm for slightly more info on how sourceProfiles is used.
+ */
+EdgeProfileMigrator.prototype.__defineGetter__("sourceProfiles", function() {
+ let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10.0");
+ return isWin10OrHigher ? null : [];
+});
+
EdgeProfileMigrator.prototype.classDescription = "Edge Profile Migrator";
EdgeProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=edge";
EdgeProfileMigrator.prototype.classID = Components.ID("{62e8834b-2d17-49f5-96ff-56344903a2ae}");
diff --git a/browser/components/migration/nsEdgeReadingListExtractor.cpp b/browser/components/migration/nsEdgeReadingListExtractor.cpp
index 3dfaa45dedcf..c3fa2bec19dc 100644
--- a/browser/components/migration/nsEdgeReadingListExtractor.cpp
+++ b/browser/components/migration/nsEdgeReadingListExtractor.cpp
@@ -32,6 +32,10 @@ nsEdgeReadingListExtractor::Extract(const nsAString& aDBPath, nsIArray** aItems)
nsresult rv = NS_OK;
*aItems = nullptr;
+ if (!aDBPath.Length()) {
+ return NS_ERROR_FAILURE;
+ }
+
JET_ERR err;
JET_INSTANCE instance;
JET_SESID sesid;
@@ -50,11 +54,9 @@ nsEdgeReadingListExtractor::Extract(const nsAString& aDBPath, nsIArray** aItems)
// the right things
bool instanceCreated, sessionCreated, dbOpened, tableOpened;
- char16ptr_t dbPath = ToNewUnicode(aDBPath);
-
// Check for the right page size and initialize with that
unsigned long pageSize;
- err = JetGetDatabaseFileInfoW(dbPath, &pageSize, sizeof(pageSize), JET_DbInfoPageSize);
+ err = JetGetDatabaseFileInfoW(aDBPath.BeginReading(), &pageSize, sizeof(pageSize), JET_DbInfoPageSize);
NS_HANDLE_JET_ERROR(err)
err = JetSetSystemParameter(&instance, NULL, JET_paramDatabasePageSize, pageSize, NULL);
NS_HANDLE_JET_ERROR(err)
@@ -77,10 +79,10 @@ nsEdgeReadingListExtractor::Extract(const nsAString& aDBPath, nsIArray** aItems)
sessionCreated = true;
// Actually open the DB, and make sure to do so readonly:
- err = JetAttachDatabaseW(sesid, dbPath, JET_bitDbReadOnly);
+ err = JetAttachDatabaseW(sesid, aDBPath.BeginReading(), JET_bitDbReadOnly);
NS_HANDLE_JET_ERROR(err)
dbOpened = true;
- err = JetOpenDatabaseW(sesid, dbPath, NULL, &dbid, JET_bitDbReadOnly);
+ err = JetOpenDatabaseW(sesid, aDBPath.BeginReading(), NULL, &dbid, JET_bitDbReadOnly);
NS_HANDLE_JET_ERROR(err)
// Open the readinglist table and get information on the columns we are interested in:
diff --git a/browser/devtools/webconsole/console-output.js b/browser/devtools/webconsole/console-output.js
index 1180f770a8f8..019041ccaa67 100644
--- a/browser/devtools/webconsole/console-output.js
+++ b/browser/devtools/webconsole/console-output.js
@@ -675,6 +675,7 @@ Messages.NavigationMarker.prototype = Heritage.extend(Messages.BaseMessage.proto
* handler.
* - location: object that tells the message source: url, line, column
* and lineText.
+ * - stack: array that tells the message source stack.
* - className: (string) additional element class names for styling
* purposes.
* - private: (boolean) mark this as a private message.
@@ -688,6 +689,7 @@ Messages.Simple = function(message, options = {})
this.category = options.category;
this.severity = options.severity;
this.location = options.location;
+ this.stack = options.stack;
this.timestamp = options.timestamp || Date.now();
this.prefix = options.prefix;
this.private = !!options.private;
@@ -697,6 +699,8 @@ Messages.Simple = function(message, options = {})
this._link = options.link;
this._linkCallback = options.linkCallback;
this._filterDuplicates = options.filterDuplicates;
+
+ this._onClickCollapsible = this._onClickCollapsible.bind(this);
};
Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype,
@@ -719,6 +723,14 @@ Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype,
*/
location: null,
+ /**
+ * Holds the stackframes received from the server.
+ *
+ * @private
+ * @type array
+ */
+ stack: null,
+
/**
* Message prefix
* @type string|null
@@ -810,6 +822,20 @@ Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype,
return this;
},
+ /**
+ * Tells if the message can be expanded/collapsed.
+ * @type boolean
+ */
+ collapsible: false,
+
+ /**
+ * Getter that tells if this message is collapsed - no details are shown.
+ * @type boolean
+ */
+ get collapsed() {
+ return this.collapsible && this.element && !this.element.hasAttribute("open");
+ },
+
_initRepeatID: function()
{
if (!this._filterDuplicates) {
@@ -854,6 +880,9 @@ Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype,
let icon = this.document.createElementNS(XHTML_NS, "span");
icon.className = "icon";
icon.title = l10n.getStr("severity." + this._severityNameCompat);
+ if (this.stack) {
+ icon.addEventListener("click", this._onClickCollapsible);
+ }
let prefixNode;
if (this.prefix) {
@@ -886,6 +915,18 @@ Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype,
if (prefixNode) {
this.element.appendChild(prefixNode);
}
+
+ if (this.stack) {
+ let twisty = this.document.createElementNS(XHTML_NS, "a");
+ twisty.className = "theme-twisty";
+ twisty.href = "#";
+ twisty.title = l10n.getStr("messageToggleDetails");
+ twisty.addEventListener("click", this._onClickCollapsible);
+ this.element.appendChild(twisty);
+ this.collapsible = true;
+ this.element.setAttribute("collapsible", true);
+ }
+
this.element.appendChild(body);
if (repeatNode) {
this.element.appendChild(repeatNode);
@@ -893,6 +934,7 @@ Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype,
if (location) {
this.element.appendChild(location);
}
+
this.element.appendChild(this.document.createTextNode("\n"));
this.element.clipboardText = this.element.textContent;
@@ -944,6 +986,12 @@ Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype,
container.textContent = this._message;
}
+ if (this.stack) {
+ let stack = new Widgets.Stacktrace(this, this.stack).render().element;
+ body.appendChild(this.document.createTextNode("\n"));
+ body.appendChild(stack);
+ }
+
return body;
},
@@ -988,6 +1036,36 @@ Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype,
line: line,
column: column});
},
+
+ /**
+ * The click event handler for the message expander arrow element. This method
+ * toggles the display of message details.
+ *
+ * @private
+ * @param nsIDOMEvent ev
+ * The DOM event object.
+ * @see this.toggleDetails()
+ */
+ _onClickCollapsible: function(ev)
+ {
+ ev.preventDefault();
+ this.toggleDetails();
+ },
+
+ /**
+ * Expand/collapse message details.
+ */
+ toggleDetails: function()
+ {
+ let twisty = this.element.querySelector(".theme-twisty");
+ if (this.element.hasAttribute("open")) {
+ this.element.removeAttribute("open");
+ twisty.removeAttribute("open");
+ } else {
+ this.element.setAttribute("open", true);
+ twisty.setAttribute("open", true);
+ }
+ },
}); // Messages.Simple.prototype
@@ -1330,30 +1408,13 @@ Messages.ConsoleGeneric = function(packet)
this._repeatID.consoleApiLevel = packet.level;
this._repeatID.styles = packet.styles;
- this._stacktrace = this._repeatID.stacktrace = packet.stacktrace;
+ this.stack = this._repeatID.stacktrace = packet.stacktrace;
this._styles = packet.styles || [];
-
- this._onClickCollapsible = this._onClickCollapsible.bind(this);
};
Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype,
{
_styles: null,
- _stacktrace: null,
-
- /**
- * Tells if the message can be expanded/collapsed.
- * @type boolean
- */
- collapsible: false,
-
- /**
- * Getter that tells if this message is collapsed - no details are shown.
- * @type boolean
- */
- get collapsed() {
- return this.collapsible && this.element && !this.element.hasAttribute("open");
- },
_renderBodyPieceSeparator: function()
{
@@ -1373,25 +1434,9 @@ Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype,
location.target = "jsdebugger";
}
- let stack = null;
- let twisty = null;
- if (this._stacktrace && this._stacktrace.length > 0) {
- stack = new Widgets.Stacktrace(this, this._stacktrace).render().element;
-
- twisty = this.document.createElementNS(XHTML_NS, "a");
- twisty.className = "theme-twisty";
- twisty.href = "#";
- twisty.title = l10n.getStr("messageToggleDetails");
- twisty.addEventListener("click", this._onClickCollapsible);
- }
-
let flex = this.document.createElementNS(XHTML_NS, "span");
flex.className = "message-flex-body";
- if (twisty) {
- flex.appendChild(twisty);
- }
-
flex.appendChild(msg);
if (repeatNode) {
@@ -1404,24 +1449,11 @@ Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype,
let result = this.document.createDocumentFragment();
result.appendChild(flex);
- if (stack) {
- result.appendChild(this.document.createTextNode("\n"));
- result.appendChild(stack);
- }
-
this._message = result;
this._stacktrace = null;
Messages.Simple.prototype.render.call(this);
- if (stack) {
- this.collapsible = true;
- this.element.setAttribute("collapsible", true);
-
- let icon = this.element.querySelector(".icon");
- icon.addEventListener("click", this._onClickCollapsible);
- }
-
return this;
},
@@ -1484,36 +1516,6 @@ Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype,
_renderLocation: function() { },
_renderRepeatNode: function() { },
- /**
- * Expand/collapse message details.
- */
- toggleDetails: function()
- {
- let twisty = this.element.querySelector(".theme-twisty");
- if (this.element.hasAttribute("open")) {
- this.element.removeAttribute("open");
- twisty.removeAttribute("open");
- } else {
- this.element.setAttribute("open", true);
- twisty.setAttribute("open", true);
- }
- },
-
- /**
- * The click event handler for the message expander arrow element. This method
- * toggles the display of message details.
- *
- * @private
- * @param nsIDOMEvent ev
- * The DOM event object.
- * @see this.toggleDetails()
- */
- _onClickCollapsible: function(ev)
- {
- ev.preventDefault();
- this.toggleDetails();
- },
-
/**
* Given a style attribute value, return a cleaned up version of the string
* such that:
diff --git a/browser/devtools/webconsole/test/browser.ini b/browser/devtools/webconsole/test/browser.ini
index 05cf59d1abc4..f517927dad8b 100644
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -129,6 +129,7 @@ support-files =
test-bug_1050691_click_function_to_source.html
test-bug_1050691_click_function_to_source.js
test-console-api-stackframe.html
+ test-exception-stackframe.html
test_bug_1010953_cspro.html^headers^
test_bug_1010953_cspro.html
test_bug1045902_console_csp_ignore_reflected_xss_message.html^headers^
@@ -383,6 +384,7 @@ skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
[browser_webconsole_autocomplete_crossdomain_iframe.js]
[browser_webconsole_console_custom_styles.js]
[browser_webconsole_console_api_stackframe.js]
+[browser_webconsole_exception_stackframe.js]
[browser_webconsole_column_numbers.js]
[browser_console_open_or_focus.js]
[browser_webconsole_bug_922212_console_dirxml.js]
diff --git a/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js b/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js
index e7fcc1c498bb..282176d42ad1 100644
--- a/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js
+++ b/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js
@@ -57,7 +57,7 @@ function test() {
let msg = [...result.matched][0];
ok(msg, "message element found");
- let locationNode = msg.querySelector(".message-location");
+ let locationNode = msg.querySelector(".message > .message-location");
ok(locationNode, "message location element found");
let title = locationNode.getAttribute("title");
@@ -78,7 +78,7 @@ function test() {
browserconsole.iframeWindow);
info("wait for click on locationNode");
- yield clickPromise;
+ yield clickPromise.promise;
info("view-source url: " + URL);
ok(URL, "we have some source URL after the click");
diff --git a/browser/devtools/webconsole/test/browser_console_error_source_click.js b/browser/devtools/webconsole/test/browser_console_error_source_click.js
index d2a66e73e7bb..ca34a936864a 100644
--- a/browser/devtools/webconsole/test/browser_console_error_source_click.js
+++ b/browser/devtools/webconsole/test/browser_console_error_source_click.js
@@ -58,7 +58,7 @@ function test() {
let msg = [...results[0].matched][0];
ok(msg, "message element found for: " + result.text);
- let locationNode = msg.querySelector(".message-location");
+ let locationNode = msg.querySelector(".message > .message-location");
ok(locationNode, "message location element found");
EventUtils.synthesizeMouse(locationNode, 2, 2, {}, hud.iframeWindow);
diff --git a/browser/devtools/webconsole/test/browser_webconsole_exception_stackframe.js b/browser/devtools/webconsole/test/browser_webconsole_exception_stackframe.js
new file mode 100644
index 000000000000..5ef63a0ed90a
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_exception_stackframe.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the console receive exceptions include a stackframe.
+// See bug 1184172.
+
+// On e10s, the exception is triggered in child process
+// and is ignored by test harness
+if (!Services.appinfo.browserTabsRemoteAutostart) {
+ expectUncaughtException();
+}
+
+function test() {
+ let hud;
+
+ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" +
+ "test/test-exception-stackframe.html";
+ const TEST_FILE = TEST_URI.substr(TEST_URI.lastIndexOf("/"));
+
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ const {tab} = yield loadTab(TEST_URI);
+ hud = yield openConsole(tab);
+
+ const stack = [{
+ file: TEST_FILE,
+ fn: "thirdCall",
+ line: 21,
+ }, {
+ file: TEST_FILE,
+ fn: "secondCall",
+ line: 17,
+ }, {
+ file: TEST_FILE,
+ fn: "firstCall",
+ line: 12,
+ }];
+
+ let results = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "nonExistingMethodCall is not defined",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ collapsible: true,
+ stacktrace: stack,
+ }],
+ });
+
+ let elem = [...results[0].matched][0];
+ ok(elem, "message element");
+
+ let msg = elem._messageObject;
+ ok(msg, "message object");
+
+ ok(msg.collapsed, "message is collapsed");
+
+ msg.toggleDetails();
+
+ ok(!msg.collapsed, "message is not collapsed");
+
+ msg.toggleDetails();
+
+ ok(msg.collapsed, "message is collapsed");
+
+ yield closeConsole(tab);
+ }
+}
diff --git a/browser/devtools/webconsole/test/head.js b/browser/devtools/webconsole/test/head.js
index a4a2a5188f72..4b6f2f717bfc 100644
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -1079,7 +1079,7 @@ function waitForMessages(options) {
let file = frame.querySelector(".message-location").title;
if (!checkText(expected.file, file)) {
ok(false, "frame #" + i + " does not match file name: " +
- expected.file);
+ expected.file + " != " + file);
displayErrorContext(rule, element);
return false;
}
@@ -1089,7 +1089,7 @@ function waitForMessages(options) {
let fn = frame.querySelector(".function").textContent;
if (!checkText(expected.fn, fn)) {
ok(false, "frame #" + i + " does not match the function name: " +
- expected.fn);
+ expected.fn + " != " + fn);
displayErrorContext(rule, element);
return false;
}
@@ -1099,7 +1099,7 @@ function waitForMessages(options) {
let line = frame.querySelector(".message-location").sourceLine;
if (!checkText(expected.line, line)) {
ok(false, "frame #" + i + " does not match the line number: " +
- expected.line);
+ expected.line + " != " + line);
displayErrorContext(rule, element);
return false;
}
diff --git a/browser/devtools/webconsole/test/test-exception-stackframe.html b/browser/devtools/webconsole/test/test-exception-stackframe.html
new file mode 100644
index 000000000000..f52c5ed2404c
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-exception-stackframe.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+ Test for bug 1184172 - stacktraces for exceptions
+
+
+
+ Hello world!
+
+
diff --git a/browser/devtools/webconsole/webconsole.js b/browser/devtools/webconsole/webconsole.js
index 47259ba87aab..5bd841a09bf3 100644
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -1528,6 +1528,7 @@ WebConsoleFrame.prototype = {
line: aScriptError.lineNumber,
column: aScriptError.columnNumber
},
+ stack: aScriptError.stacktrace,
category: category,
severity: severity,
timestamp: aScriptError.timeStamp,
diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css
index cffc47d15fe3..eff80d079599 100644
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1900,6 +1900,7 @@ richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-
list-style-image: url("chrome://browser/skin/reload-stop-go.png");
padding: 0 9px;
margin-inline-start: 2px;
+ border-inline-end-style: none;
border-inline-start: 1px solid var(--urlbar-separator-color);
border-image: linear-gradient(transparent 15%,
var(--urlbar-separator-color) 15%,
diff --git a/build/macosx/universal/flight.mk b/build/macosx/universal/flight.mk
index ec55ba8af3cd..71672d441570 100644
--- a/build/macosx/universal/flight.mk
+++ b/build/macosx/universal/flight.mk
@@ -29,29 +29,3 @@ postflight_all:
# actually does a universal staging with both OBJDIR_ARCH_1 and OBJDIR_ARCH_2.
$(MAKE) -C $(OBJDIR_ARCH_1)/$(MOZ_BUILD_APP)/installer \
PKG_SKIP_STRIP=1 stage-package
-ifdef ENABLE_TESTS
-# Now, repeat the process for the test package.
- $(MAKE) -C $(OBJDIR_ARCH_1) UNIVERSAL_BINARY= CHROME_JAR= package-tests
- $(MAKE) -C $(OBJDIR_ARCH_2) UNIVERSAL_BINARY= CHROME_JAR= package-tests
- rm -rf $(DIST_UNI)/test-stage
-# automation.py differs because it hardcodes a path to
-# dist/bin. It doesn't matter which one we use.
- if test -d $(DIST_ARCH_1)/test-stage -a \
- -d $(DIST_ARCH_2)/test-stage; then \
- cp $(DIST_ARCH_1)/test-stage/mochitest/automation.py \
- $(DIST_ARCH_2)/test-stage/mochitest/; \
- cp -RL $(DIST_ARCH_1)/test-stage/mochitest/extensions/specialpowers \
- $(DIST_ARCH_2)/test-stage/mochitest/extensions/; \
- cp $(DIST_ARCH_1)/test-stage/xpcshell/automation.py \
- $(DIST_ARCH_2)/test-stage/xpcshell/; \
- cp $(DIST_ARCH_1)/test-stage/reftest/automation.py \
- $(DIST_ARCH_2)/test-stage/reftest/; \
- cp -RL $(DIST_ARCH_1)/test-stage/reftest/specialpowers \
- $(DIST_ARCH_2)/test-stage/reftest/; \
- $(TOPSRCDIR)/build/macosx/universal/unify \
- --unify-with-sort "\.manifest$$" \
- --unify-with-sort "all-test-dirs\.list$$" \
- $(DIST_ARCH_1)/test-stage \
- $(DIST_ARCH_2)/test-stage \
- $(DIST_UNI)/test-stage; fi
-endif
diff --git a/build/macosx/universal/mozconfig b/build/macosx/universal/mozconfig
index fd1b3ee92267..32ab66f2dfbf 100644
--- a/build/macosx/universal/mozconfig
+++ b/build/macosx/universal/mozconfig
@@ -6,6 +6,6 @@
# As used here, arguments in $MOZ_BUILD_PROJECTS are suitable as arguments
# to gcc's -arch parameter.
-mk_add_options MOZ_BUILD_PROJECTS="i386 x86_64"
+mk_add_options MOZ_BUILD_PROJECTS="x86_64 i386"
. $topsrcdir/build/macosx/universal/mozconfig.common
diff --git a/dom/media/webm/IntelWebMVideoDecoder.cpp b/dom/media/webm/IntelWebMVideoDecoder.cpp
index afd511ac7d74..25332145bf2e 100644
--- a/dom/media/webm/IntelWebMVideoDecoder.cpp
+++ b/dom/media/webm/IntelWebMVideoDecoder.cpp
@@ -366,7 +366,7 @@ IntelWebMVideoDecoder::PopSample()
}
MOZ_ASSERT(!mSampleQueue.empty());
- sample = mSampleQueue.front();
+ sample = mSampleQueue.front().forget();
mSampleQueue.pop_front();
return sample.forget();
}
diff --git a/dom/media/webm/NesteggPacketHolder.h b/dom/media/webm/NesteggPacketHolder.h
index d014dd6f53b3..425f6adf3e6e 100644
--- a/dom/media/webm/NesteggPacketHolder.h
+++ b/dom/media/webm/NesteggPacketHolder.h
@@ -89,7 +89,7 @@ class WebMPacketQueue {
}
already_AddRefed PopFront() {
- nsRefPtr result = mQueue.front();
+ nsRefPtr result = mQueue.front().forget();
mQueue.pop_front();
return result.forget();
}
diff --git a/dom/media/webm/WebMDemuxer.h b/dom/media/webm/WebMDemuxer.h
index 3d28568b8c73..d984066b2f81 100644
--- a/dom/media/webm/WebMDemuxer.h
+++ b/dom/media/webm/WebMDemuxer.h
@@ -33,7 +33,7 @@ class MediaRawDataQueue {
}
already_AddRefed PopFront() {
- nsRefPtr result = mQueue.front();
+ nsRefPtr result = mQueue.front().forget();
mQueue.pop_front();
return result.forget();
}
diff --git a/dom/mobileconnection/tests/marionette/manifest.ini b/dom/mobileconnection/tests/marionette/manifest.ini
index 6467b88f60de..8de5cebee96d 100644
--- a/dom/mobileconnection/tests/marionette/manifest.ini
+++ b/dom/mobileconnection/tests/marionette/manifest.ini
@@ -36,3 +36,4 @@ qemu = true
[test_mobile_clir_radio_off.js]
[test_mobile_neighboring_cell_ids.js]
[test_mobile_cell_Info_list.js]
+skip-if = android_version < '19'
diff --git a/dom/mobileconnection/tests/marionette/test_mobile_cell_Info_list.js b/dom/mobileconnection/tests/marionette/test_mobile_cell_Info_list.js
index f845ada4e580..6c4dc9246a67 100644
--- a/dom/mobileconnection/tests/marionette/test_mobile_cell_Info_list.js
+++ b/dom/mobileconnection/tests/marionette/test_mobile_cell_Info_list.js
@@ -4,37 +4,22 @@
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = "head_chrome.js";
-function getAndroidVersion() {
- return runEmulatorShellCmdSafe(["getprop", "ro.build.version.sdk"])
- .then(aResults => aResults[0]);
-}
-
// Start test.
startTestBase(function() {
- return getAndroidVersion().
- then((aVersion) => {
- if (aVersion < "19") {
- // Only emulator-kk supports REQUEST_GET_CELL_INFO_LIST, so we skip this
- // test if in older android version.
- log("Skip test: AndroidVersion: " + aVersion);
- return;
- }
+ return getCellInfoList()
+ .then((aResults) => {
+ // Cell Info are hard-coded in hardware/ril/reference-ril/reference-ril.c.
+ is(aResults.length, 1, "Check number of cell Info");
- return getCellInfoList()
- .then((aResults) => {
- // Cell Info are hard-coded in hardware/ril/reference-ril/reference-ril.c.
- is(aResults.length, 1, "Check number of cell Info");
+ let cell = aResults[0];
+ is(cell.type, Ci.nsICellInfo.CELL_INFO_TYPE_GSM, "Check cell.type");
+ is(cell.registered, true, "Check cell.registered");
- let cell = aResults[0];
- is(cell.type, Ci.nsICellInfo.CELL_INFO_TYPE_GSM, "Check cell.type");
- is(cell.registered, true, "Check cell.registered");
+ ok(cell instanceof Ci.nsIGsmCellInfo,
+ "cell.constructor is " + cell.constructor);
- ok(cell instanceof Ci.nsIGsmCellInfo,
- "cell.constructor is " + cell.constructor);
-
- // The data hard-coded in hardware/ril/reference-ril/reference-ril.c
- // isn't correct (missing timeStampType), so we skip to check other
- // attributes first until we fix it.
- });
- });
-});
+ // The data hard-coded in hardware/ril/reference-ril/reference-ril.c
+ // isn't correct (missing timeStampType), so we skip to check other
+ // attributes first until we fix it.
+ });
+});
\ No newline at end of file
diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp
index 76c059e2c70a..0d6c9378a94c 100644
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -983,6 +983,14 @@ GeckoChildProcessHost::PerformAsyncLaunchInternal(std::vector& aExt
#endif
{
base::LaunchApp(cmdLine, false, false, &process);
+
+ // We need to be able to duplicate handles to non-sandboxed content
+ // processes, so add it as a target peer.
+ if (mProcessType == GeckoProcessType_Content) {
+ if (!mSandboxBroker.AddTargetPeer(process)) {
+ NS_WARNING("Failed to add content process as target peer.");
+ }
+ }
}
#else
diff --git a/media/mtransport/nriceresolver.cpp b/media/mtransport/nriceresolver.cpp
index f5e74eec2b3e..62bffc995988 100644
--- a/media/mtransport/nriceresolver.cpp
+++ b/media/mtransport/nriceresolver.cpp
@@ -154,7 +154,7 @@ int NrIceResolver::resolve(nr_resolver_resource *resource,
if (resource->transport_protocol != IPPROTO_UDP &&
resource->transport_protocol != IPPROTO_TCP) {
- MOZ_MTLOG(ML_ERROR, "Only UDP and TCP are is supported.");
+ MOZ_MTLOG(ML_ERROR, "Only UDP and TCP are supported.");
ABORT(R_NOT_FOUND);
}
pr = new PendingResolution(sts_thread_,
diff --git a/media/mtransport/nriceresolver.h b/media/mtransport/nriceresolver.h
index 91ec277f5253..c3a379e71614 100644
--- a/media/mtransport/nriceresolver.h
+++ b/media/mtransport/nriceresolver.h
@@ -71,6 +71,10 @@ class NrIceResolver
void DestroyResolver();
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrIceResolver)
+ int resolve(nr_resolver_resource *resource,
+ int (*cb)(void *cb_arg, nr_transport_addr *addr),
+ void *cb_arg, void **handle);
+
private:
// Implementations of vtbl functions
static int destroy(void **objp);
@@ -80,10 +84,6 @@ class NrIceResolver
static void resolve_cb(NR_SOCKET s, int how, void *cb_arg);
static int cancel(void *obj, void *handle);
- int resolve(nr_resolver_resource *resource,
- int (*cb)(void *cb_arg, nr_transport_addr *addr),
- void *cb_arg, void **handle);
-
class PendingResolution : public nsIDNSListener
{
public:
diff --git a/media/mtransport/test/ice_unittest.cpp b/media/mtransport/test/ice_unittest.cpp
index 615e29c444a2..abe8415f8e1b 100644
--- a/media/mtransport/test/ice_unittest.cpp
+++ b/media/mtransport/test/ice_unittest.cpp
@@ -50,6 +50,7 @@
extern "C" {
#include "r_data.h"
+#include "util.h"
}
#define GTEST_HAS_RTTI 0
@@ -65,7 +66,6 @@ static unsigned int kDefaultTimeout = 7000;
//TODO(nils@mozilla.com): This should get replaced with some non-external
//solution like discussed in bug 860775.
-const std::string kDefaultStunServerAddress((char *)"52.27.56.60");
const std::string kDefaultStunServerHostname(
(char *)"global.stun.twilio.com");
const std::string kBogusStunServerHostname(
@@ -77,7 +77,7 @@ const std::string kBogusIceCandidate(
const std::string kUnreachableHostIceCandidate(
(char *)"candidate:0 1 UDP 2113601790 192.168.178.20 50769 typ host");
-std::string g_stun_server_address(kDefaultStunServerAddress);
+std::string g_stun_server_address;
std::string g_stun_server_hostname(kDefaultStunServerHostname);
std::string g_turn_server;
std::string g_turn_user;
@@ -3060,6 +3060,48 @@ static std::string get_environment(const char *name) {
return value;
}
+// DNS resolution helper code
+static std::string
+Resolve(const std::string& fqdn, int address_family)
+{
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = address_family;
+ hints.ai_protocol = IPPROTO_UDP;
+ struct addrinfo *res;
+ int err = getaddrinfo(fqdn.c_str(), nullptr, &hints, &res);
+ if (err) {
+ std::cerr << "Error in getaddrinfo: " << err << std::endl;
+ return "";
+ }
+
+ char str_addr[64] = {0};
+ switch (res->ai_family) {
+ case AF_INET:
+ inet_ntop(
+ AF_INET,
+ &reinterpret_cast(res->ai_addr)->sin_addr,
+ str_addr,
+ sizeof(str_addr));
+ case AF_INET6:
+ inet_ntop(
+ AF_INET6,
+ &reinterpret_cast(res->ai_addr)->sin6_addr,
+ str_addr,
+ sizeof(str_addr));
+ default:
+ std::cerr << "Got unexpected address family in DNS lookup: "
+ << res->ai_family << std::endl;
+ return "";
+ }
+
+ if (!strlen(str_addr)) {
+ std::cerr << "inet_ntop failed" << std::endl;
+ }
+
+ return str_addr;
+}
+
int main(int argc, char **argv)
{
#ifdef ANDROID
@@ -3071,6 +3113,7 @@ int main(int argc, char **argv)
g_turn_user = get_environment("TURN_SERVER_USER");
g_turn_password = get_environment("TURN_SERVER_PASSWORD");
+
if (g_turn_server.empty() ||
g_turn_user.empty(),
g_turn_password.empty()) {
@@ -3104,6 +3147,12 @@ int main(int argc, char **argv)
NSS_NoDB_Init(nullptr);
NSS_SetDomesticPolicy();
+ // If only a STUN server FQDN was provided, look up its IP address for the
+ // address-only tests.
+ if (g_stun_server_address.empty() && !g_stun_server_hostname.empty()) {
+ g_stun_server_address = Resolve(g_stun_server_hostname, AF_INET);
+ }
+
// Start the tests
::testing::InitGoogleTest(&argc, argv);
diff --git a/media/mtransport/third_party/nICEr/src/net/nr_resolver.h b/media/mtransport/third_party/nICEr/src/net/nr_resolver.h
index 602454c53d98..376ba9998bc3 100644
--- a/media/mtransport/third_party/nICEr/src/net/nr_resolver.h
+++ b/media/mtransport/third_party/nICEr/src/net/nr_resolver.h
@@ -42,7 +42,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#define NR_RESOLVE_PROTOCOL_TURN 2
typedef struct nr_resolver_resource_ {
- char *domain_name;
+ const char *domain_name;
UINT2 port;
int stun_turn;
UCHAR transport_protocol;
diff --git a/mobile/android/base/home/SearchEngineRow.java b/mobile/android/base/home/SearchEngineRow.java
index b2ed08da7f10..c07f382dc9d3 100644
--- a/mobile/android/base/home/SearchEngineRow.java
+++ b/mobile/android/base/home/SearchEngineRow.java
@@ -5,6 +5,7 @@
package org.mozilla.gecko.home;
+import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
@@ -274,7 +275,9 @@ class SearchEngineRow extends AnimatedHeightLayout {
if (suggestionsEnabled) {
final int recycledSuggestionCount = mSuggestionView.getChildCount();
final int suggestionViewCount = updateFromSearchEngine(searchEngine, animate, recycledSuggestionCount);
- updateFromSavedSearches(searchTerm, animate, suggestionViewCount, recycledSuggestionCount);
+ if (AppConstants.NIGHTLY_BUILD) {
+ updateFromSavedSearches(searchTerm, animate, suggestionViewCount, recycledSuggestionCount);
+ }
}
}
diff --git a/mobile/android/base/util/DrawableUtil.java b/mobile/android/base/util/DrawableUtil.java
index ce45324af8a6..6ff1e85eb077 100644
--- a/mobile/android/base/util/DrawableUtil.java
+++ b/mobile/android/base/util/DrawableUtil.java
@@ -18,8 +18,7 @@ import android.support.v4.graphics.drawable.DrawableCompat;
public class DrawableUtil {
/**
- * Tints the given drawable with the given color and returns it. Note that this
- * transformation does not occur in place on pre-Lollipop devices (bug 1193950).
+ * Tints the given drawable with the given color and returns it.
*/
@CheckResult
public static Drawable tintDrawable(@NonNull final Context context, @DrawableRes final int drawableID,
@@ -31,8 +30,10 @@ public class DrawableUtil {
}
/**
- * Tints the given drawable with the given tint list and returns it. Note that this
- * transformation does not occur in place on pre-Lollipop devices (bug 1193950).
+ * Tints the given drawable with the given tint list and returns it. Note that you
+ * should no longer use the argument Drawable because the argument is not mutated
+ * on pre-Lollipop devices but is mutated on L+ due to differences in the Support
+ * Library implementation (bug 1193950).
*/
@CheckResult
public static Drawable tintDrawableWithStateList(@NonNull final Drawable drawable,
diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js
index e1227bb05979..6f413b2f6bab 100644
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -325,10 +325,6 @@ const kStateActive = 0x00000001; // :active pseudoclass for elements
const kXLinkNamespace = "http://www.w3.org/1999/xlink";
-const kDefaultCSSViewportWidth = 980;
-
-const kViewportRemeasureThrottle = 500;
-
function fuzzyEquals(a, b) {
return (Math.abs(a - b) < 1e-6);
}
@@ -1059,9 +1055,6 @@ var BrowserApp = {
return;
aTab.setActive(true);
- if (!AppConstants.MOZ_ANDROID_APZ) {
- aTab.setResolution(aTab._zoom, true);
- }
this.contentDocumentChanged();
this.deck.selectedPanel = aTab.browser;
// Focus the browser so that things like selection will be styled correctly.
@@ -3722,18 +3715,11 @@ Tab.prototype = {
bottom: aDisplayPort.bottom - (scrolly + gScreenHeight)
};
- if (this._oldDisplayPortMargins == null ||
- !fuzzyEquals(displayPortMargins.left, this._oldDisplayPortMargins.left) ||
- !fuzzyEquals(displayPortMargins.top, this._oldDisplayPortMargins.top) ||
- !fuzzyEquals(displayPortMargins.right, this._oldDisplayPortMargins.right) ||
- !fuzzyEquals(displayPortMargins.bottom, this._oldDisplayPortMargins.bottom)) {
- cwu.setDisplayPortMarginsForElement(displayPortMargins.left,
- displayPortMargins.top,
- displayPortMargins.right,
- displayPortMargins.bottom,
- element, 0);
- }
- this._oldDisplayPortMargins = displayPortMargins;
+ cwu.setDisplayPortMarginsForElement(displayPortMargins.left,
+ displayPortMargins.top,
+ displayPortMargins.right,
+ displayPortMargins.bottom,
+ element, 0);
},
setScrollClampingSize: function(zoom) {
diff --git a/modules/zlib/src/inflate.c b/modules/zlib/src/inflate.c
index 870f89bb4d36..4fd3f3c1809b 100644
--- a/modules/zlib/src/inflate.c
+++ b/modules/zlib/src/inflate.c
@@ -1504,9 +1504,10 @@ z_streamp strm;
{
struct inflate_state FAR *state;
- if (strm == Z_NULL || strm->state == Z_NULL) return -1L << 16;
+ if (strm == Z_NULL || strm->state == Z_NULL)
+ return (long)(((unsigned long)0 - 1) << 16);
state = (struct inflate_state FAR *)strm->state;
- return ((long)(state->back) << 16) +
+ return (long)(((unsigned long)((long)state->back)) << 16) +
(state->mode == COPY ? state->length :
(state->mode == MATCH ? state->was - state->length : 0));
}
diff --git a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
index 48f578232730..b3acaa4ac8a5 100644
--- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
+++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
@@ -439,6 +439,13 @@ SandboxBroker::AllowDirectory(wchar_t const *dir)
return (sandbox::SBOX_ALL_OK == result);
}
+bool
+SandboxBroker::AddTargetPeer(HANDLE aPeerProcess)
+{
+ sandbox::ResultCode result = sBrokerService->AddTargetPeer(aPeerProcess);
+ return (sandbox::SBOX_ALL_OK == result);
+}
+
SandboxBroker::~SandboxBroker()
{
if (mPolicy) {
diff --git a/security/sandbox/win/src/sandboxbroker/sandboxBroker.h b/security/sandbox/win/src/sandboxbroker/sandboxBroker.h
index 79a217ff742a..3a54c7836479 100644
--- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.h
+++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.h
@@ -14,6 +14,7 @@
#endif
#include
+#include
namespace sandbox {
class BrokerServices;
@@ -45,6 +46,9 @@ public:
bool AllowReadWriteFile(wchar_t const *file);
bool AllowDirectory(wchar_t const *dir);
+ // Exposes AddTargetPeer from broker services, so that none sandboxed
+ // processes can be added as handle duplication targets.
+ bool AddTargetPeer(HANDLE aPeerProcess);
private:
static sandbox::BrokerServices *sBrokerService;
sandbox::TargetPolicy *mPolicy;
diff --git a/testing/docker/desktop-build/REGISTRY b/testing/docker/desktop-build/REGISTRY
index 8d8449526f96..cb1e1bb482a2 100644
--- a/testing/docker/desktop-build/REGISTRY
+++ b/testing/docker/desktop-build/REGISTRY
@@ -1 +1 @@
-quay.io/djmitche
+taskcluster
diff --git a/testing/docker/desktop-build/VERSION b/testing/docker/desktop-build/VERSION
index 17e51c385ea3..d917d3e26adc 100644
--- a/testing/docker/desktop-build/VERSION
+++ b/testing/docker/desktop-build/VERSION
@@ -1 +1 @@
-0.1.1
+0.1.2
diff --git a/testing/docker/desktop-build/bin/checkout-sources.sh b/testing/docker/desktop-build/bin/checkout-sources.sh
index 93ebd2ce39c8..fe16dbec1413 100644
--- a/testing/docker/desktop-build/bin/checkout-sources.sh
+++ b/testing/docker/desktop-build/bin/checkout-sources.sh
@@ -25,10 +25,6 @@ set -x -e
: TOOLS_HEAD_REF ${TOOLS_HEAD_REF:=${TOOLS_HEAD_REV}}
: TOOLS_DISABLE ${TOOLS_DISABLE:=false}
-: MH_CUSTOM_BUILD_VARIANT_CFG ${MH_CUSTOM_BUILD_VARIANT_CFG}
-: MH_BRANCH ${MH_BRANCH:=mozilla-central}
-: MH_BUILD_POOL ${MH_BUILD_POOL:=staging}
-
: WORKSPACE ${WORKSPACE:=/home/worker/workspace}
set -v
diff --git a/testing/marionette/client/marionette/runner/base.py b/testing/marionette/client/marionette/runner/base.py
index 5741573812fe..4890d130e03b 100644
--- a/testing/marionette/client/marionette/runner/base.py
+++ b/testing/marionette/client/marionette/runner/base.py
@@ -743,6 +743,17 @@ setReq.onerror = function() {
self.marionette.baseurl = self.server_root
self.logger.info("using remote content from %s" % self.marionette.baseurl)
+ device_info = None
+ if self.capabilities['device'] != 'desktop' and self.capabilities['browserName'] == 'B2G':
+ dm = get_dm(self.marionette)
+ device_info = dm.getInfo()
+ # Add Android version (SDK level) to mozinfo so that manifest entries
+ # can be conditional on android_version.
+ androidVersion = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
+ self.logger.info(
+ "Android sdk version '%s'; will use this to filter manifests" % androidVersion)
+ mozinfo.info['android_version'] = androidVersion
+
for test in tests:
self.add_test(test)
@@ -762,11 +773,6 @@ setReq.onerror = function() {
adb_host=self.marionette.adb_host,
adb_port=self.marionette.adb_port)
- device_info = None
- if self.capabilities['device'] != 'desktop' and self.capabilities['browserName'] == 'B2G':
- dm = get_dm(self.marionette)
- device_info = dm.getInfo()
-
self.logger.suite_start(self.tests,
version_info=version_info,
device_info=device_info)
@@ -985,6 +991,8 @@ setReq.onerror = function() {
self.todo += len(results.expectedFailures)
self.mixin_run_tests = []
+ for result in self.results:
+ result.result_modifiers = []
def run_test_set(self, tests):
if self.shuffle:
diff --git a/testing/mozharness/configs/builds/releng_base_mac_64_builds.py b/testing/mozharness/configs/builds/releng_base_mac_64_builds.py
index b471fbb1ff1c..9f10c17a93bc 100644
--- a/testing/mozharness/configs/builds/releng_base_mac_64_builds.py
+++ b/testing/mozharness/configs/builds/releng_base_mac_64_builds.py
@@ -32,7 +32,7 @@ config = {
'purge_basedirs': [],
'enable_ccache': True,
'vcs_share_base': '/builds/hg-shared',
- 'objdir': 'obj-firefox/i386',
+ 'objdir': 'obj-firefox/x86_64',
'tooltool_script': ["/builds/tooltool.py"],
'tooltool_bootstrap': "setup.sh",
'enable_count_ctors': False,
diff --git a/testing/taskcluster/tasks/branches/try/job_flags.yml b/testing/taskcluster/tasks/branches/try/job_flags.yml
index a587935386bd..e950f90af2c0 100644
--- a/testing/taskcluster/tasks/branches/try/job_flags.yml
+++ b/testing/taskcluster/tasks/branches/try/job_flags.yml
@@ -134,9 +134,9 @@ builds:
- Linux64
types:
opt:
- task: tasks/builds/opt_linux64.yml
+ task: tasks/builds/opt_linux64_clobber.yml
debug:
- task: tasks/builds/dbg_linux64.yml
+ task: tasks/builds/dbg_linux64_clobber.yml
sm-plain:
platforms:
- Linux64
diff --git a/testing/taskcluster/tasks/builds/dbg_linux64.yml b/testing/taskcluster/tasks/builds/dbg_linux64.yml
index 7d431be2412d..05998fa3d818 100644
--- a/testing/taskcluster/tasks/builds/dbg_linux64.yml
+++ b/testing/taskcluster/tasks/builds/dbg_linux64.yml
@@ -1,22 +1,10 @@
$inherits:
- from: 'tasks/builds/opt_linux64.yml'
- variables:
- build_name: 'linux64'
- build_type: 'dbg'
+ from: 'tasks/builds/dbg_linux64_clobber.yml'
task:
- metadata:
- name: '[TC] Linux64 Dbg'
- description: 'Linux64 Dbg'
-
- workerType: dbg-linux64
+ # same as clobber, but with a cached workspace
+ scopes:
+ - 'docker-worker:cache:build-linux64-c6-workspace'
payload:
- env:
- MH_CUSTOM_BUILD_VARIANT_CFG: 'debug'
- extra:
- treeherder:
- groupSymbol: tc
- groupName: Submitted by taskcluster
- symbol: B
- collection:
- debug: true
+ cache:
+ build-linux64-c6-workspace: '/home/worker/workspace'
diff --git a/testing/taskcluster/tasks/builds/dbg_linux64_clobber.yml b/testing/taskcluster/tasks/builds/dbg_linux64_clobber.yml
new file mode 100644
index 000000000000..567d7d453d39
--- /dev/null
+++ b/testing/taskcluster/tasks/builds/dbg_linux64_clobber.yml
@@ -0,0 +1,24 @@
+$inherits:
+ from: 'tasks/builds/linux64_clobber.yml'
+ variables:
+ build_name: 'linux64'
+ build_type: 'dbg'
+task:
+ metadata:
+ name: '[TC] Linux64 Dbg'
+ description: 'Linux64 Dbg'
+
+ workerType: dbg-linux64
+
+ payload:
+ env:
+ MH_CUSTOM_BUILD_VARIANT_CFG: 'debug'
+
+ extra:
+ treeherder:
+ groupSymbol: tc
+ groupName: Submitted by taskcluster
+ symbol: B
+ collection:
+ debug: true
+
diff --git a/testing/taskcluster/tasks/builds/linux64_clobber.yml b/testing/taskcluster/tasks/builds/linux64_clobber.yml
new file mode 100644
index 000000000000..0a567b26c32c
--- /dev/null
+++ b/testing/taskcluster/tasks/builds/linux64_clobber.yml
@@ -0,0 +1,53 @@
+$inherits:
+ from: 'tasks/builds/firefox_base.yml'
+ variables:
+ build_name: 'linux64'
+task:
+ #workerType: ..
+
+ routes:
+ - 'index.buildbot.branches.{{project}}.linux64'
+ - 'index.buildbot.revisions.{{head_rev}}.{{project}}.linux64'
+
+ scopes:
+ - 'docker-worker:cache:tooltool-cache'
+ - 'docker-worker:relengapi-proxy:tooltool.download.public'
+
+ payload:
+ image: '{{#docker_image}}desktop-build{{/docker_image}}'
+ cache:
+ # "clobber" means no workspace cache; non-clobber subclasses should add that
+ tooltool-cache: '/home/worker/tooltool-cache'
+
+ features:
+ relengAPIProxy: true
+
+ env:
+ MOZHARNESS_SCRIPT: 'mozharness/scripts/fx_desktop_build.py'
+ MOZHARNESS_CONFIG: 'builds/releng_base_linux_64_builds.py balrog/production.py'
+ MH_BRANCH: {{project}}
+ MH_BUILD_POOL: taskcluster
+ # image paths
+ TOOLTOOL_CACHE: '/home/worker/tooltool-cache'
+ NEED_XVFB: true
+ DIST_UPLOADS: 'jsshell-linux-x86_64.zip'
+ DIST_TARGET_UPLOADS: 'x-test.linux-x86_64.tar.bz2 linux-x86_64.tar.bz2 linux-x86_64.json tests.zip crashreporter-symbols.zip'
+
+ maxRunTime: 36000
+
+ command: ["/bin/bash", "bin/build.sh"]
+
+ extra:
+ treeherderEnv:
+ - production
+ - staging
+ treeherder:
+ machine:
+ # see https://github.com/mozilla/treeherder/blob/master/ui/js/values.js
+ platform: linux64
+ # Rather then enforcing particular conventions we require that all build
+ # tasks provide the "build" extra field to specify where the build and tests
+ # files are located.
+ locations:
+ build: 'public/build/target.linux-x86_64.tar.bz2'
+ tests: 'public/build/target.tests.zip'
diff --git a/testing/taskcluster/tasks/builds/opt_linux64.yml b/testing/taskcluster/tasks/builds/opt_linux64.yml
index 3ca895ce7830..6631fb6af4d0 100644
--- a/testing/taskcluster/tasks/builds/opt_linux64.yml
+++ b/testing/taskcluster/tasks/builds/opt_linux64.yml
@@ -1,60 +1,10 @@
$inherits:
- from: 'tasks/builds/firefox_base.yml'
- variables:
- build_name: 'linux64'
- build_type: 'opt'
+ from: 'tasks/builds/opt_linux64_clobber.yml'
task:
- metadata:
- name: '[TC] Linux64 Opt'
- description: 'Linux64 Opt'
-
- workerType: opt-linux64
-
- routes:
- - 'index.buildbot.branches.{{project}}.linux64'
- - 'index.buildbot.revisions.{{head_rev}}.{{project}}.linux64'
-
+ # same as clobber, but with a cached workspace
scopes:
- 'docker-worker:cache:build-linux64-c6-workspace'
- - 'docker-worker:cache:tooltool-cache'
- - 'docker-worker:relengapi-proxy:tooltool.download.public'
payload:
- image: '{{#docker_image}}desktop-build{{/docker_image}}'
cache:
build-linux64-c6-workspace: '/home/worker/workspace'
- tooltool-cache: '/home/worker/tooltool-cache'
-
- features:
- relengAPIProxy: true
-
- env:
- MOZHARNESS_SCRIPT: 'mozharness/scripts/fx_desktop_build.py'
- MOZHARNESS_CONFIG: 'builds/releng_base_linux_64_builds.py balrog/production.py'
- MH_BRANCH: {{project}}
- MH_BUILD_POOL: taskcluster
- # image paths
- TOOLTOOL_CACHE: '/home/worker/tooltool-cache'
- RELENGAPI_TOKEN: 'TODO' # 1164612: encrypt this secret
- NEED_XVFB: true
- DIST_UPLOADS: 'jsshell-linux-x86_64.zip'
- DIST_TARGET_UPLOADS: 'x-test.linux-x86_64.tar.bz2 linux-x86_64.tar.bz2 linux-x86_64.json tests.zip crashreporter-symbols.zip'
-
- maxRunTime: 36000
-
- command: ["/bin/bash", "bin/build.sh"]
-
- extra:
- treeherderEnv:
- - production
- - staging
- treeherder:
- machine:
- # see https://github.com/mozilla/treeherder/blob/master/ui/js/values.js
- platform: linux64
- # Rather then enforcing particular conventions we require that all build
- # tasks provide the "build" extra field to specify where the build and tests
- # files are located.
- locations:
- build: 'public/build/target.linux-x86_64.tar.bz2'
- tests: 'public/build/target.tests.zip'
diff --git a/testing/taskcluster/tasks/builds/opt_linux64_clobber.yml b/testing/taskcluster/tasks/builds/opt_linux64_clobber.yml
new file mode 100644
index 000000000000..d79a73526f3a
--- /dev/null
+++ b/testing/taskcluster/tasks/builds/opt_linux64_clobber.yml
@@ -0,0 +1,18 @@
+$inherits:
+ from: 'tasks/builds/linux64_clobber.yml'
+ variables:
+ build_name: 'linux64'
+ build_type: 'opt'
+task:
+ metadata:
+ name: '[TC] Linux64 Opt'
+ description: 'Linux64 Opt'
+
+ workerType: opt-linux64
+
+ extra:
+ treeherder:
+ groupSymbol: tc
+ groupName: Submitted by taskcluster
+ symbol: B
+
diff --git a/testing/testsuite-targets.mk b/testing/testsuite-targets.mk
index dc879009ebf5..dafe0666c0f8 100644
--- a/testing/testsuite-targets.mk
+++ b/testing/testsuite-targets.mk
@@ -369,8 +369,8 @@ pgo-profile-run:
# Package up the tests and test harnesses
include $(topsrcdir)/toolkit/mozapps/installer/package-name.mk
-ifndef UNIVERSAL_BINARY
PKG_STAGE = $(DIST)/test-stage
+
package-tests: \
stage-config \
stage-mach \
@@ -393,10 +393,6 @@ package-tests: \
ifdef MOZ_WEBRTC
package-tests: stage-steeplechase
endif
-else
-# This staging area has been built for us by universal/flight.mk
-PKG_STAGE = $(DIST)/universal/test-stage
-endif
TEST_PKGS := \
cppunittest \
@@ -410,9 +406,7 @@ PKG_ARG = --$(1) '$(PKG_BASENAME).$(1).tests.zip'
test-packages-manifest-tc:
@rm -f $(MOZ_TEST_PACKAGES_FILE_TC)
-ifndef UNIVERSAL_BINARY
$(NSINSTALL) -D $(dir $(MOZ_TEST_PACKAGES_FILE_TC))
-endif
$(PYTHON) $(topsrcdir)/build/gen_test_packages_manifest.py \
--jsshell $(JSSHELL_NAME) \
--dest-file $(MOZ_TEST_PACKAGES_FILE_TC) \
@@ -422,9 +416,7 @@ endif
test-packages-manifest:
@rm -f $(MOZ_TEST_PACKAGES_FILE)
-ifndef UNIVERSAL_BINARY
$(NSINSTALL) -D $(dir $(MOZ_TEST_PACKAGES_FILE))
-endif
$(PYTHON) $(topsrcdir)/build/gen_test_packages_manifest.py \
--jsshell $(JSSHELL_NAME) \
--dest-file $(MOZ_TEST_PACKAGES_FILE) \
@@ -433,9 +425,7 @@ endif
package-tests:
@rm -f '$(DIST)/$(PKG_PATH)$(TEST_PACKAGE)'
-ifndef UNIVERSAL_BINARY
$(NSINSTALL) -D $(DIST)/$(PKG_PATH)
-endif
# Exclude harness specific directories when generating the common zip.
$(MKDIR) -p $(abspath $(DIST))/$(PKG_PATH) && \
cd $(PKG_STAGE) && \
diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json
index 16a089d8ec2c..d86f23ea04de 100644
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4062,6 +4062,11 @@
"extended_statistics_ok": true,
"description": "Firefox: Time to initialize the bookmarks toolbar view (ms)"
},
+ "FX_BROWSER_FULLSCREEN_USED": {
+ "expires_in_version": "46",
+ "kind": "count",
+ "description": "The number of times that a session enters browser fullscreen (f11-fullscreen)"
+ },
"FX_NEW_WINDOW_MS": {
"expires_in_version": "default",
"kind": "exponential",
diff --git a/toolkit/devtools/Loader.jsm b/toolkit/devtools/Loader.jsm
index 66c5ed8c4883..c9c933bbbd65 100644
--- a/toolkit/devtools/Loader.jsm
+++ b/toolkit/devtools/Loader.jsm
@@ -141,6 +141,7 @@ SrcdirProvider.prototype = {
let devtoolsURI = this.fileURI(devtoolsDir);
let toolkitURI = this.fileURI(toolkitDir);
let serverURI = this.fileURI(OS.Path.join(toolkitDir, "server"));
+ let webideURI = this.fileURI(OS.Path.join(devtoolsDir, "webide", "modules"));
let webconsoleURI = this.fileURI(OS.Path.join(toolkitDir, "webconsole"));
let appActorURI = this.fileURI(OS.Path.join(toolkitDir, "apps", "app-actor-front.js"));
let cssLogicURI = this.fileURI(OS.Path.join(toolkitDir, "styleinspector", "css-logic"));
@@ -152,7 +153,7 @@ SrcdirProvider.prototype = {
let asyncUtilsURI = this.fileURI(OS.Path.join(toolkitDir, "async-utils.js"));
let contentObserverURI = this.fileURI(OS.Path.join(toolkitDir), "content-observer.js");
let gcliURI = this.fileURI(OS.Path.join(toolkitDir, "gcli", "source", "lib", "gcli"));
- let projecteditorURI = this.fileURI(OS.Path.join(devtoolsDir, "projecteditor"));
+ let projecteditorURI = this.fileURI(OS.Path.join(devtoolsDir, "projecteditor", "lib"));
let promiseURI = this.fileURI(OS.Path.join(modulesDir, "Promise-backend.js"));
let acornURI = this.fileURI(OS.Path.join(toolkitDir, "acorn"));
let acornWalkURI = OS.Path.join(acornURI, "walk.js");
@@ -168,6 +169,7 @@ SrcdirProvider.prototype = {
"devtools": devtoolsURI,
"devtools/toolkit": toolkitURI,
"devtools/server": serverURI,
+ "devtools/webide": webideURI,
"devtools/toolkit/webconsole": webconsoleURI,
"devtools/app-actor-front": appActorURI,
"devtools/styleinspector/css-logic": cssLogicURI,
diff --git a/toolkit/devtools/gcli/commands/calllog.js b/toolkit/devtools/gcli/commands/calllog.js
index d57943f56d7e..5ba19048c840 100644
--- a/toolkit/devtools/gcli/commands/calllog.js
+++ b/toolkit/devtools/gcli/commands/calllog.js
@@ -9,6 +9,8 @@ const {TargetFactory} = require("devtools/framework/target");
const l10n = require("gcli/l10n");
const gcli = require("gcli/index");
+loader.lazyRequireGetter(this, "TargetFactory", "devtools/framework/target", true);
+
loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
loader.lazyGetter(this, "Debugger", () => {
diff --git a/toolkit/devtools/gcli/commands/paintflashing.js b/toolkit/devtools/gcli/commands/paintflashing.js
index a292b6453a8a..252f2bc17b17 100644
--- a/toolkit/devtools/gcli/commands/paintflashing.js
+++ b/toolkit/devtools/gcli/commands/paintflashing.js
@@ -5,8 +5,8 @@
"use strict";
const { Ci } = require("chrome");
-const { getOuterId } = require("sdk/window/utils");
-const { getBrowserForTab } = require("sdk/tabs/utils");
+loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
+loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
let telemetry;
try {
diff --git a/toolkit/devtools/gcli/commands/rulers.js b/toolkit/devtools/gcli/commands/rulers.js
index f67dcd0c7b4b..82a2a6094062 100644
--- a/toolkit/devtools/gcli/commands/rulers.js
+++ b/toolkit/devtools/gcli/commands/rulers.js
@@ -7,8 +7,8 @@
const EventEmitter = require("devtools/toolkit/event-emitter");
const eventEmitter = new EventEmitter();
const events = require("sdk/event/core");
-const { getOuterId } = require("sdk/window/utils");
-const { getBrowserForTab } = require("sdk/tabs/utils");
+loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
+loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
const l10n = require("gcli/l10n");
require("devtools/server/actors/inspector");
diff --git a/toolkit/devtools/gcli/source/lib/gcli/util/filesystem.js b/toolkit/devtools/gcli/source/lib/gcli/util/filesystem.js
index 9dcebc46f029..a7b22a8f754c 100644
--- a/toolkit/devtools/gcli/source/lib/gcli/util/filesystem.js
+++ b/toolkit/devtools/gcli/source/lib/gcli/util/filesystem.js
@@ -32,9 +32,14 @@ exports.join = OS.Path.join;
exports.sep = OS.Path.sep;
exports.dirname = OS.Path.dirname;
-var dirService = Cc['@mozilla.org/file/directory_service;1']
- .getService(Ci.nsIProperties);
-exports.home = dirService.get('Home', Ci.nsIFile).path;
+// On B2G, there is no home folder
+var home = null;
+try {
+ var dirService = Cc['@mozilla.org/file/directory_service;1']
+ .getService(Ci.nsIProperties);
+ home = dirService.get('Home', Ci.nsIFile).path;
+} catch(e) {}
+exports.home = home;
if ('winGetDrive' in OS.Path) {
exports.sep = '\\';
diff --git a/toolkit/devtools/server/actors/webconsole.js b/toolkit/devtools/server/actors/webconsole.js
index 840249ae9d32..9a02331d8f6b 100644
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -1288,6 +1288,21 @@ WebConsoleActor.prototype =
*/
preparePageErrorForRemote: function WCA_preparePageErrorForRemote(aPageError)
{
+ let stack = null;
+ // Convert stack objects to the JSON attributes expected by client code
+ if (aPageError.stack) {
+ stack = [];
+ let s = aPageError.stack;
+ while (s !== null) {
+ stack.push({
+ filename: s.source,
+ lineNumber: s.line,
+ columnNumber: s.column,
+ functionName: s.functionDisplayName
+ });
+ s = s.parent;
+ }
+ }
let lineText = aPageError.sourceLine;
if (lineText && lineText.length > DebuggerServer.LONG_STRING_INITIAL_LENGTH) {
lineText = lineText.substr(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
@@ -1307,6 +1322,7 @@ WebConsoleActor.prototype =
strict: !!(aPageError.flags & aPageError.strictFlag),
info: !!(aPageError.flags & aPageError.infoFlag),
private: aPageError.isFromPrivateWindow,
+ stacktrace: stack
};
},
diff --git a/toolkit/devtools/server/content-server.jsm b/toolkit/devtools/server/content-server.jsm
index 7398b40b2718..97770615fc58 100644
--- a/toolkit/devtools/server/content-server.jsm
+++ b/toolkit/devtools/server/content-server.jsm
@@ -31,6 +31,7 @@ function init(msg) {
if (!DebuggerServer.initialized) {
DebuggerServer.init();
+ DebuggerServer.isInChildProcess = true;
}
// In case of apps being loaded in parent process, DebuggerServer is already
@@ -45,6 +46,7 @@ function init(msg) {
// Connect both parent/child processes debugger servers RDP via message managers
let conn = DebuggerServer.connectToParent(prefix, mm);
+ conn.parentMessageManager = mm;
let { ChildProcessActor } = devtools.require("devtools/server/actors/child-process");
let actor = new ChildProcessActor(conn);
diff --git a/toolkit/devtools/tests/mochitest/test_loader_paths.html b/toolkit/devtools/tests/mochitest/test_loader_paths.html
index 159f73c25b37..6e2877344b00 100644
--- a/toolkit/devtools/tests/mochitest/test_loader_paths.html
+++ b/toolkit/devtools/tests/mochitest/test_loader_paths.html
@@ -43,8 +43,8 @@
srcdir.load();
is(builtin.loader.mapping.length,
- srcdir.loader.mapping.length + 1,
- "The built-in loader should have only one more mapping for testing.");
+ srcdir.loader.mapping.length,
+ "The built-in loader should have the same number of mapping than testing.");
Services.prefs.clearUserPref(SRCDIR_PREF);
diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm
index 77573e405d4f..29b5aca7a89b 100644
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -1646,6 +1646,7 @@ var AddonManagerInternal = {
if (gStartupComplete)
return;
+ logger.debug("Registering startup change '" + aType + "' for " + aID);
// Ensure that an ID is only listed in one type of change
for (let type in this.startupChanges)
diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
index 81f8d2c9a6da..e0af60f82926 100644
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -82,7 +82,6 @@ const PREF_EM_UPDATE_BACKGROUND_URL = "extensions.update.background.url";
const PREF_EM_ENABLED_ADDONS = "extensions.enabledAddons";
const PREF_EM_EXTENSION_FORMAT = "extensions.";
const PREF_EM_ENABLED_SCOPES = "extensions.enabledScopes";
-const PREF_EM_AUTO_DISABLED_SCOPES = "extensions.autoDisableScopes";
const PREF_EM_SHOW_MISMATCH_UI = "extensions.showMismatchUI";
const PREF_XPI_ENABLED = "xpinstall.enabled";
const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required";
@@ -99,6 +98,7 @@ const PREF_INSTALL_DISTRO_ADDONS = "extensions.installDistroAddons";
const PREF_BRANCH_INSTALLED_ADDON = "extensions.installedDistroAddon.";
const PREF_SHOWN_SELECTION_UI = "extensions.shownSelectionUI";
const PREF_INTERPOSITION_ENABLED = "extensions.interposition.enabled";
+const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet";
const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion";
const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
@@ -116,6 +116,7 @@ const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/exte
const STRING_TYPE_NAME = "type.%ID%.name";
const DIR_EXTENSIONS = "extensions";
+const DIR_SYSTEM_ADDONS = "features";
const DIR_STAGE = "staged";
const DIR_TRASH = "trash";
@@ -131,6 +132,8 @@ const KEY_TEMPDIR = "TmpD";
const KEY_APP_DISTRIBUTION = "XREAppDist";
const KEY_APP_PROFILE = "app-profile";
+const KEY_APP_SYSTEM_ADDONS = "app-system-addons";
+const KEY_APP_SYSTEM_DEFAULTS = "app-system-defaults";
const KEY_APP_GLOBAL = "app-global";
const KEY_APP_SYSTEM_LOCAL = "app-system-local";
const KEY_APP_SYSTEM_SHARE = "app-system-share";
@@ -158,12 +161,6 @@ const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"];
const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"];
-// Properties that should be migrated where possible from an old database. These
-// shouldn't include properties that can be read directly from install.rdf files
-// or calculated
-const DB_MIGRATE_METADATA= ["installDate", "userDisabled", "softDisabled",
- "sourceURI", "applyBackgroundUpdates",
- "releaseNotesURI", "foreignInstall", "syncGUID"];
// Properties to cache and reload when an addon installation is pending
const PENDING_INSTALL_METADATA =
["syncGUID", "targetApplications", "userDisabled", "softDisabled",
@@ -264,17 +261,35 @@ const LOGGER_ID = "addons.xpi";
// (Requires AddonManager.jsm)
let logger = Log.repository.getLogger(LOGGER_ID);
-const LAZY_OBJECTS = ["XPIDatabase"];
+const LAZY_OBJECTS = ["XPIDatabase", "XPIDatabaseReconcile"];
var gLazyObjectsLoaded = false;
function loadLazyObjects() {
- let scope = {};
- scope.AddonInternal = AddonInternal;
- scope.XPIProvider = XPIProvider;
- scope.XPIStates = XPIStates;
- Services.scriptloader.loadSubScript("resource://gre/modules/addons/XPIProviderUtils.js",
- scope);
+ let uri = "resource://gre/modules/addons/XPIProviderUtils.js";
+ let scope = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), {
+ sandboxName: uri,
+ wantGlobalProperties: ["TextDecoder"],
+ });
+
+ let shared = {
+ ADDON_SIGNING,
+ SIGNED_TYPES,
+ BOOTSTRAP_REASONS,
+ AddonInternal,
+ XPIProvider,
+ XPIStates,
+ syncLoadManifestFromFile,
+ isUsableAddon,
+ recordAddonTelemetry,
+ applyBlocklistChanges,
+ flushStartupCache,
+ }
+
+ for (let key of Object.keys(shared))
+ scope[key] = shared[key];
+
+ Services.scriptloader.loadSubScript(uri, scope);
for (let name of LAZY_OBJECTS) {
delete gGlobalScope[name];
@@ -284,7 +299,7 @@ function loadLazyObjects() {
return scope;
}
-for (let name of LAZY_OBJECTS) {
+LAZY_OBJECTS.forEach(name => {
Object.defineProperty(gGlobalScope, name, {
get: function lazyObjectGetter() {
let objs = loadLazyObjects();
@@ -292,7 +307,7 @@ for (let name of LAZY_OBJECTS) {
},
configurable: true
});
-}
+});
function findMatchingStaticBlocklistItem(aAddon) {
@@ -1957,7 +1972,7 @@ this.XPIStates = {
for (let location of XPIProvider.installLocations) {
// The list of add-on like file/directory names in the install location.
- let addons = location.addonLocations;
+ let addons = location.getAddonLocations();
// The results of scanning this location.
let foundAddons = new SerializableMap();
@@ -1969,9 +1984,7 @@ this.XPIStates = {
delete oldState[location.name];
}
- for (let file of addons) {
- let id = location.getIDForLocation(file);
-
+ for (let [id, file] of addons) {
if (!(id in locState)) {
logger.debug("New add-on ${id} in ${location}", {id: id, location: location.name});
let xpiState = new XPIState({d: file.persistentDescriptor});
@@ -1991,6 +2004,9 @@ this.XPIStates = {
if (changed) {
logger.debug("Changed add-on ${id} in ${location}", {id: id, location: location.name});
}
+ else {
+ logger.debug("Existing add-on ${id} in ${location}", {id: id, location: location.name});
+ }
foundAddons.set(id, xpiState);
}
XPIProvider.setTelemetry(id, "location", location.name);
@@ -2300,7 +2316,8 @@ this.XPIProvider = {
}
try {
- var location = new DirectoryInstallLocation(aName, dir, aScope, aLocked);
+ var location = aLocked ? new DirectoryInstallLocation(aName, dir, aScope)
+ : new MutableDirectoryInstallLocation(aName, dir, aScope);
}
catch (e) {
logger.warn("Failed to add directory install location " + aName, e);
@@ -2311,6 +2328,28 @@ this.XPIProvider = {
XPIProvider.installLocationsByName[location.name] = location;
}
+ function addSystemAddonInstallLocation(aName, aKey, aPaths, aScope) {
+ try {
+ var dir = FileUtils.getDir(aKey, aPaths);
+ }
+ catch (e) {
+ // Some directories aren't defined on some platforms, ignore them
+ logger.debug("Skipping unavailable install location " + aName);
+ return;
+ }
+
+ try {
+ var location = new SystemAddonInstallLocation(aName, dir, aScope, aAppChanged !== false);
+ }
+ catch (e) {
+ logger.warn("Failed to add system add-on install location " + aName, e);
+ return;
+ }
+
+ XPIProvider.installLocations.push(location);
+ XPIProvider.installLocationsByName[location.name] = location;
+ }
+
function addRegistryInstallLocation(aName, aRootkey, aScope) {
try {
var location = new WinRegInstallLocation(aName, aRootkey, aScope);
@@ -2353,6 +2392,14 @@ this.XPIProvider = {
[DIR_EXTENSIONS],
AddonManager.SCOPE_PROFILE, false);
+ addSystemAddonInstallLocation(KEY_APP_SYSTEM_ADDONS, KEY_PROFILEDIR,
+ [DIR_SYSTEM_ADDONS],
+ AddonManager.SCOPE_PROFILE);
+
+ addDirectoryInstallLocation(KEY_APP_SYSTEM_DEFAULTS, KEY_APP_DISTRIBUTION,
+ [DIR_SYSTEM_ADDONS],
+ AddonManager.SCOPE_PROFILE, true);
+
if (enabledScopes & AddonManager.SCOPE_USER) {
addDirectoryInstallLocation(KEY_APP_SYSTEM_USER, "XREUSysExt",
[Services.appinfo.ID],
@@ -2930,9 +2977,9 @@ this.XPIProvider = {
logger.debug("Processing install of " + id + " in " + aLocation.name);
if (existingAddonID in this.bootstrappedAddons) {
try {
- var existingAddon = aLocation.getLocationForID(existingAddonID);
- if (this.bootstrappedAddons[existingAddonID].descriptor ==
- existingAddon.persistentDescriptor) {
+ var existingAddon = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ existingAddon.persistentDescriptor = this.bootstrappedAddons[existingAddonID].descriptor;
+ if (existingAddon.exists()) {
oldBootstrap = this.bootstrappedAddons[existingAddonID];
// We'll be replacing a currently active bootstrapped add-on so
@@ -3089,7 +3136,7 @@ this.XPIProvider = {
// Install the add-on
try {
- profileLocation.installAddon(id, entry, null, true);
+ addon._sourceBundle = profileLocation.installAddon(id, entry, null, true);
logger.debug("Installed distribution add-on " + id);
Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true)
@@ -3112,681 +3159,6 @@ this.XPIProvider = {
return changed;
},
- /**
- * Compares the add-ons that are currently installed to those that were
- * known to be installed when the application last ran and applies any
- * changes found to the database. Also sends "startupcache-invalidate" signal to
- * observerservice if it detects that data may have changed.
- * Always called after XPIProviderUtils.js and extensions.json have been loaded.
- *
- * @param aManifests
- * A dictionary of cached AddonInstalls for add-ons that have been
- * installed
- * @param aUpdateCompatibility
- * true to update add-ons appDisabled property when the application
- * version has changed
- * @param aOldAppVersion
- * The version of the application last run with this profile or null
- * if it is a new profile or the version is unknown
- * @param aOldPlatformVersion
- * The version of the platform last run with this profile or null
- * if it is a new profile or the version is unknown
- * @return a boolean indicating if a change requiring flushing the caches was
- * detected
- */
- processFileChanges: function XPI_processFileChanges(aManifests,
- aUpdateCompatibility,
- aOldAppVersion,
- aOldPlatformVersion) {
- let visibleAddons = {};
- let oldBootstrappedAddons = this.bootstrappedAddons;
- this.bootstrappedAddons = {};
-
- /**
- * Updates an add-on's metadata and determines if a restart of the
- * application is necessary. This is called when either the add-on's
- * install directory path or last modified time has changed.
- *
- * @param aInstallLocation
- * The install location containing the add-on
- * @param aOldAddon
- * The AddonInternal as it appeared the last time the application
- * ran
- * @param aAddonState
- * The new state of the add-on
- * @return a boolean indicating if flushing caches is required to complete
- * changing this add-on
- */
- function updateMetadata(aInstallLocation, aOldAddon, aAddonState) {
- logger.debug("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name);
-
- // Check if there is an updated install manifest for this add-on
- let newAddon = aManifests[aInstallLocation.name][aOldAddon.id];
-
- try {
- // If not load it
- if (!newAddon) {
- let file = aInstallLocation.getLocationForID(aOldAddon.id);
- newAddon = syncLoadManifestFromFile(file);
- applyBlocklistChanges(aOldAddon, newAddon);
-
- // Carry over any pendingUninstall state to add-ons modified directly
- // in the profile. This is important when the attempt to remove the
- // add-on in processPendingFileChanges failed and caused an mtime
- // change to the add-ons files.
- newAddon.pendingUninstall = aOldAddon.pendingUninstall;
- }
-
- // The ID in the manifest that was loaded must match the ID of the old
- // add-on.
- if (newAddon.id != aOldAddon.id)
- throw new Error("Incorrect id in install manifest for existing add-on " + aOldAddon.id);
- }
- catch (e) {
- logger.warn("updateMetadata: Add-on " + aOldAddon.id + " is invalid", e);
- XPIDatabase.removeAddonMetadata(aOldAddon);
- XPIStates.removeAddon(aOldAddon.location, aOldAddon.id);
- if (!aInstallLocation.locked)
- aInstallLocation.uninstallAddon(aOldAddon.id);
- else
- logger.warn("Could not uninstall invalid item from locked install location");
- // If this was an active add-on then we must force a restart
- if (aOldAddon.active)
- return true;
-
- return false;
- }
-
- // Set the additional properties on the new AddonInternal
- newAddon._installLocation = aInstallLocation;
- newAddon.updateDate = aAddonState.mtime;
- newAddon.visible = !(newAddon.id in visibleAddons);
-
- // Update the database
- let newDBAddon = XPIDatabase.updateAddonMetadata(aOldAddon, newAddon,
- aAddonState.descriptor);
- if (newDBAddon.visible) {
- visibleAddons[newDBAddon.id] = newDBAddon;
- // Remember add-ons that were changed during startup
- AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
- newDBAddon.id);
- if (aOldAddon.active == newDBAddon.disabled) {
- let change = aOldAddon.active ? AddonManager.STARTUP_CHANGE_DISABLED
- : AddonManager.STARTUP_CHANGE_ENABLED;
- AddonManagerPrivate.addStartupChange(change, newDBAddon.id);
- }
-
- // If this was the active theme and it is now disabled then enable the
- // default theme
- if (aOldAddon.active && newDBAddon.disabled)
- XPIProvider.enableDefaultTheme();
-
- // If the new add-on is bootstrapped and active then call its install method
- if (newDBAddon.active && newDBAddon.bootstrap) {
- // Startup cache must be flushed before calling the bootstrap script
- flushStartupCache();
-
- let installReason = Services.vc.compare(aOldAddon.version, newDBAddon.version) < 0 ?
- BOOTSTRAP_REASONS.ADDON_UPGRADE :
- BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
-
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- file.persistentDescriptor = aAddonState.descriptor;
- XPIProvider.callBootstrapMethod(newDBAddon, file, "install",
- installReason, { oldVersion: aOldAddon.version });
- return false;
- }
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Updates an add-on's descriptor for when the add-on has moved in the
- * filesystem but hasn't changed in any other way.
- *
- * @param aInstallLocation
- * The install location containing the add-on
- * @param aOldAddon
- * The AddonInternal as it appeared the last time the application
- * ran
- * @param aAddonState
- * The new state of the add-on
- * @return a boolean indicating if flushing caches is required to complete
- * changing this add-on
- */
- function updateDescriptor(aInstallLocation, aOldAddon, aAddonState) {
- logger.debug("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor);
-
- aOldAddon.descriptor = aAddonState.descriptor;
- aOldAddon.visible = !(aOldAddon.id in visibleAddons);
- XPIDatabase.saveChanges();
-
- if (aOldAddon.visible) {
- visibleAddons[aOldAddon.id] = aOldAddon;
-
- if (aOldAddon.bootstrap && aOldAddon.active) {
- let bootstrap = oldBootstrappedAddons[aOldAddon.id];
- bootstrap.descriptor = aAddonState.descriptor;
- XPIProvider.bootstrappedAddons[aOldAddon.id] = bootstrap;
- }
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Called when no change has been detected for an add-on's metadata. The
- * add-on may have become visible due to other add-ons being removed or
- * the add-on may need to be updated when the application version has
- * changed.
- *
- * @param aInstallLocation
- * The install location containing the add-on
- * @param aOldAddon
- * The AddonInternal as it appeared the last time the application
- * ran
- * @param aAddonState
- * The new state of the add-on
- * @return a boolean indicating if flushing caches is required to complete
- * changing this add-on
- */
- function updateVisibilityAndCompatibility(aInstallLocation, aOldAddon,
- aAddonState) {
- let changed = false;
-
- // This add-ons metadata has not changed but it may have become visible
- if (!(aOldAddon.id in visibleAddons)) {
- visibleAddons[aOldAddon.id] = aOldAddon;
-
- if (!aOldAddon.visible) {
- // Remember add-ons that were changed during startup.
- AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
- aOldAddon.id);
- XPIDatabase.makeAddonVisible(aOldAddon);
-
- if (aOldAddon.bootstrap) {
- // The add-on is bootstrappable so call its install script
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- file.persistentDescriptor = aAddonState.descriptor;
- XPIProvider.callBootstrapMethod(aOldAddon, file,
- "install",
- BOOTSTRAP_REASONS.ADDON_INSTALL);
-
- // If it should be active then mark it as active otherwise unload
- // its scope
- if (!aOldAddon.disabled) {
- XPIDatabase.updateAddonActive(aOldAddon, true);
- }
- else {
- XPIProvider.unloadBootstrapScope(newAddon.id);
- }
- }
- else {
- // Otherwise a restart is necessary
- changed = true;
- }
- }
- }
-
- // App version changed, we may need to update the appDisabled property.
- if (aUpdateCompatibility) {
- let wasDisabled = aOldAddon.disabled;
- let wasAppDisabled = aOldAddon.appDisabled;
- let wasUserDisabled = aOldAddon.userDisabled;
- let wasSoftDisabled = aOldAddon.softDisabled;
- let updateDB = false;
-
- // If updating from a version of the app that didn't support signedState
- // then fetch that property now
- if (aOldAddon.signedState === undefined && ADDON_SIGNING &&
- SIGNED_TYPES.has(aOldAddon.type)) {
- let file = aInstallLocation.getLocationForID(aOldAddon.id);
- let manifest = syncLoadManifestFromFile(file);
- aOldAddon.signedState = manifest.signedState;
- updateDB = true;
- }
- // This updates the addon's JSON cached data in place
- applyBlocklistChanges(aOldAddon, aOldAddon, aOldAppVersion,
- aOldPlatformVersion);
- aOldAddon.appDisabled = !isUsableAddon(aOldAddon);
-
- let isDisabled = aOldAddon.disabled;
-
- // If either property has changed update the database.
- if (updateDB || wasAppDisabled != aOldAddon.appDisabled ||
- wasUserDisabled != aOldAddon.userDisabled ||
- wasSoftDisabled != aOldAddon.softDisabled) {
- logger.debug("Add-on " + aOldAddon.id + " changed appDisabled state to " +
- aOldAddon.appDisabled + ", userDisabled state to " +
- aOldAddon.userDisabled + " and softDisabled state to " +
- aOldAddon.softDisabled);
- XPIDatabase.saveChanges();
- }
-
- // If this is a visible add-on and it has changed disabled state then we
- // may need a restart or to update the bootstrap list.
- if (aOldAddon.visible && wasDisabled != isDisabled) {
- // Remember add-ons that became disabled or enabled by the application
- // change
- let change = isDisabled ? AddonManager.STARTUP_CHANGE_DISABLED
- : AddonManager.STARTUP_CHANGE_ENABLED;
- AddonManagerPrivate.addStartupChange(change, aOldAddon.id);
- if (aOldAddon.bootstrap) {
- // Update the add-ons active state
- XPIDatabase.updateAddonActive(aOldAddon, !isDisabled);
- }
- else {
- changed = true;
- }
- }
- }
-
- if (aOldAddon.visible && aOldAddon.active && aOldAddon.bootstrap) {
- XPIProvider.bootstrappedAddons[aOldAddon.id] = {
- version: aOldAddon.version,
- type: aOldAddon.type,
- descriptor: aAddonState.descriptor,
- multiprocessCompatible: aOldAddon.multiprocessCompatible
- };
- }
-
- return changed;
- }
-
- /**
- * Called when an add-on has been removed.
- *
- * @param aOldAddon
- * The AddonInternal as it appeared the last time the application
- * ran
- * @return a boolean indicating if flushing caches is required to complete
- * changing this add-on
- */
- function removeMetadata(aOldAddon) {
- // This add-on has disappeared
- logger.debug("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location);
- XPIDatabase.removeAddonMetadata(aOldAddon);
-
- // Remember add-ons that were uninstalled during startup
- if (aOldAddon.visible) {
- AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED,
- aOldAddon.id);
- }
- else if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
- .indexOf(aOldAddon.id) != -1) {
- AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
- aOldAddon.id);
- }
-
- if (aOldAddon.active) {
- // Enable the default theme if the previously active theme has been
- // removed
- if (aOldAddon.type == "theme")
- XPIProvider.enableDefaultTheme();
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Called to add the metadata for an add-on in one of the install locations
- * to the database. This can be called in three different cases. Either an
- * add-on has been dropped into the location from outside of Firefox, or
- * an add-on has been installed through the application, or the database
- * has been upgraded or become corrupt and add-on data has to be reloaded
- * into it.
- *
- * @param aInstallLocation
- * The install location containing the add-on
- * @param aId
- * The ID of the add-on
- * @param aAddonState
- * The new state of the add-on
- * @param aMigrateData
- * If during startup the database had to be upgraded this will
- * contain data that used to be held about this add-on
- * @return a boolean indicating if flushing caches is required to complete
- * changing this add-on
- */
- function addMetadata(aInstallLocation, aId, aAddonState, aMigrateData) {
- logger.debug("New add-on " + aId + " installed in " + aInstallLocation.name);
-
- let newAddon = null;
- let sameVersion = false;
- // Check the updated manifests lists for the install location, If there
- // is no manifest for the add-on ID then newAddon will be undefined
- if (aInstallLocation.name in aManifests)
- newAddon = aManifests[aInstallLocation.name][aId];
-
- // If we had staged data for this add-on or we aren't recovering from a
- // corrupt database and we don't have migration data for this add-on then
- // this must be a new install.
- let isNewInstall = (!!newAddon) || (!XPIDatabase.activeBundles && !aMigrateData);
-
- // If it's a new install and we haven't yet loaded the manifest then it
- // must be something dropped directly into the install location
- let isDetectedInstall = isNewInstall && !newAddon;
-
- // Load the manifest if necessary and sanity check the add-on ID
- try {
- if (!newAddon) {
- // Load the manifest from the add-on.
- let file = aInstallLocation.getLocationForID(aId);
- newAddon = syncLoadManifestFromFile(file);
- }
- // The add-on in the manifest should match the add-on ID.
- if (newAddon.id != aId) {
- throw new Error("Invalid addon ID: expected addon ID " + aId +
- ", found " + newAddon.id + " in manifest");
- }
- }
- catch (e) {
- logger.warn("addMetadata: Add-on " + aId + " is invalid", e);
-
- // Remove the invalid add-on from the install location if the install
- // location isn't locked, no restart will be necessary
- if (!aInstallLocation.locked)
- aInstallLocation.uninstallAddon(aId);
- else
- logger.warn("Could not uninstall invalid item from locked install location");
- return false;
- }
-
- // Update the AddonInternal properties.
- newAddon._installLocation = aInstallLocation;
- newAddon.visible = !(newAddon.id in visibleAddons);
- newAddon.installDate = aAddonState.mtime;
- newAddon.updateDate = aAddonState.mtime;
- newAddon.foreignInstall = isDetectedInstall;
-
- // appDisabled depends on whether the add-on is a foreignInstall so update
- newAddon.appDisabled = !isUsableAddon(newAddon);
-
- if (aMigrateData) {
- // If there is migration data then apply it.
- logger.debug("Migrating data from old database");
-
- DB_MIGRATE_METADATA.forEach(function(aProp) {
- // A theme's disabled state is determined by the selected theme
- // preference which is read in loadManifestFromRDF
- if (aProp == "userDisabled" && newAddon.type == "theme")
- return;
-
- if (aProp in aMigrateData)
- newAddon[aProp] = aMigrateData[aProp];
- });
-
- // Force all non-profile add-ons to be foreignInstalls since they can't
- // have been installed through the API
- newAddon.foreignInstall |= aInstallLocation.name != KEY_APP_PROFILE;
-
- // Some properties should only be migrated if the add-on hasn't changed.
- // The version property isn't a perfect check for this but covers the
- // vast majority of cases.
- if (aMigrateData.version == newAddon.version) {
- logger.debug("Migrating compatibility info");
- sameVersion = true;
- if ("targetApplications" in aMigrateData)
- newAddon.applyCompatibilityUpdate(aMigrateData, true);
- }
-
- // Since the DB schema has changed make sure softDisabled is correct
- applyBlocklistChanges(newAddon, newAddon, aOldAppVersion,
- aOldPlatformVersion);
- }
-
- // The default theme is never a foreign install
- if (newAddon.type == "theme" && newAddon.internalName == XPIProvider.defaultSkin)
- newAddon.foreignInstall = false;
-
- if (isDetectedInstall && newAddon.foreignInstall) {
- // If the add-on is a foreign install and is in a scope where add-ons
- // that were dropped in should default to disabled then disable it
- let disablingScopes = Preferences.get(PREF_EM_AUTO_DISABLED_SCOPES, 0);
- if (aInstallLocation.scope & disablingScopes) {
- logger.warn("Disabling foreign installed add-on " + newAddon.id + " in "
- + aInstallLocation.name);
- newAddon.userDisabled = true;
- }
- }
-
- // If we have a list of what add-ons should be marked as active then use
- // it to guess at migration data.
- if (!isNewInstall && XPIDatabase.activeBundles) {
- // For themes we know which is active by the current skin setting
- if (newAddon.type == "theme")
- newAddon.active = newAddon.internalName == XPIProvider.currentSkin;
- else
- newAddon.active = XPIDatabase.activeBundles.indexOf(aAddonState.descriptor) != -1;
-
- // If the add-on wasn't active and it isn't already disabled in some way
- // then it was probably either softDisabled or userDisabled
- if (!newAddon.active && newAddon.visible && !newAddon.disabled) {
- // If the add-on is softblocked then assume it is softDisabled
- if (newAddon.blocklistState == Blocklist.STATE_SOFTBLOCKED)
- newAddon.softDisabled = true;
- else
- newAddon.userDisabled = true;
- }
- }
- else {
- newAddon.active = (newAddon.visible && !newAddon.disabled);
- }
-
- let newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor);
-
- if (newDBAddon.visible) {
- // Remember add-ons that were first detected during startup.
- if (isDetectedInstall) {
- // If a copy from a higher priority location was removed then this
- // add-on has changed
- if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_UNINSTALLED)
- .indexOf(newDBAddon.id) != -1) {
- AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
- newDBAddon.id);
- }
- else {
- AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED,
- newDBAddon.id);
- }
- }
-
- // Note if any visible add-on is not in the application install location
- if (newDBAddon._installLocation.name != KEY_APP_GLOBAL)
- XPIProvider.allAppGlobal = false;
-
- visibleAddons[newDBAddon.id] = newDBAddon;
-
- let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
- let extraParams = {};
-
- // Copy add-on details (enabled, bootstrap, version, etc) to XPIState.
- aAddonState.syncWithDB(newDBAddon);
-
- // If we're hiding a bootstrapped add-on then call its uninstall method
- if (newDBAddon.id in oldBootstrappedAddons) {
- let oldBootstrap = oldBootstrappedAddons[newDBAddon.id];
- extraParams.oldVersion = oldBootstrap.version;
- XPIProvider.bootstrappedAddons[newDBAddon.id] = oldBootstrap;
-
- // If the old version is the same as the new version, or we're
- // recovering from a corrupt DB, don't call uninstall and install
- // methods.
- if (sameVersion || !isNewInstall) {
- logger.debug("addMetadata: early return, sameVersion " + sameVersion
- + ", isNewInstall " + isNewInstall);
- return false;
- }
-
- installReason = Services.vc.compare(oldBootstrap.version, newDBAddon.version) < 0 ?
- BOOTSTRAP_REASONS.ADDON_UPGRADE :
- BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
-
- let oldAddonFile = Cc["@mozilla.org/file/local;1"].
- createInstance(Ci.nsIFile);
- oldAddonFile.persistentDescriptor = oldBootstrap.descriptor;
-
- XPIProvider.callBootstrapMethod(createAddonDetails(newDBAddon.id, oldBootstrap),
- oldAddonFile, "uninstall", installReason,
- { newVersion: newDBAddon.version });
-
- XPIProvider.unloadBootstrapScope(newDBAddon.id);
-
- // If the new add-on is bootstrapped then we must flush the caches
- // before calling the new bootstrap script
- if (newDBAddon.bootstrap)
- flushStartupCache();
- }
-
- if (!newDBAddon.bootstrap)
- return true;
-
- // Visible bootstrapped add-ons need to have their install method called
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- file.persistentDescriptor = aAddonState.descriptor;
- XPIProvider.callBootstrapMethod(newDBAddon, file,
- "install", installReason, extraParams);
- if (!newDBAddon.active)
- XPIProvider.unloadBootstrapScope(newDBAddon.id);
- }
-
- return false;
- }
-
- let changed = false;
-
- // Get all the add-ons in the existing DB and Map them into Sets by install location
- let allDBAddons = new Map();
- for (let a of XPIDatabase.getAddons()) {
- let locationSet = allDBAddons.get(a.location);
- if (!locationSet) {
- locationSet = new Set();
- allDBAddons.set(a.location, locationSet);
- }
- locationSet.add(a);
- }
-
- for (let installLocation of this.installLocations) {
- // Get all the on-disk XPI states for this location, and keep track of which
- // ones we see in the database.
- let states = XPIStates.getLocation(installLocation.name);
- let seen = new Set();
- // Iterate through the add-ons installed the last time the application
- // ran
- let dbAddons = allDBAddons.get(installLocation.name);
- if (dbAddons) {
- // we've processed this location
- allDBAddons.delete(installLocation.name);
-
- logger.debug("processFileChanges reconciling DB for location ${l} state ${s} db ${d}",
- {l: installLocation.name, s: states, d: [for (a of dbAddons) a.id]});
- for (let aOldAddon of dbAddons) {
- // If a version of this add-on has been installed in an higher
- // priority install location then count it as changed
- if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
- .indexOf(aOldAddon.id) != -1) {
- AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
- aOldAddon.id);
- }
-
- // Check if the add-on is still installed
- let xpiState = states && states.get(aOldAddon.id);
- if (xpiState) {
- // in this block, the add-on is in both XPIStates and the DB
- seen.add(xpiState);
-
- recordAddonTelemetry(aOldAddon);
-
- // Check if the add-on has been changed outside the XPI provider
- if (aOldAddon.updateDate != xpiState.mtime) {
- // Did time change in the wrong direction?
- if (xpiState.mtime < aOldAddon.updateDate) {
- this.setTelemetry(aOldAddon.id, "olderFile", {
- name: this._mostRecentlyModifiedFile[aOldAddon.id],
- mtime: xpiState.mtime,
- oldtime: aOldAddon.updateDate
- });
- } else {
- this.setTelemetry(aOldAddon.id, "modifiedFile",
- this._mostRecentlyModifiedFile[aOldAddon.id]);
- }
- }
-
- // The add-on has changed if the modification time has changed, or
- // we have an updated manifest for it. Also reload the metadata for
- // add-ons in the application directory when the application version
- // has changed
- if (aOldAddon.id in aManifests[installLocation.name] ||
- aOldAddon.updateDate != xpiState.mtime ||
- (aUpdateCompatibility && installLocation.name == KEY_APP_GLOBAL)) {
- changed = updateMetadata(installLocation, aOldAddon, xpiState) ||
- changed;
- }
- else if (aOldAddon.descriptor != xpiState.descriptor) {
- changed = updateDescriptor(installLocation, aOldAddon, xpiState) ||
- changed;
- }
- else {
- changed = updateVisibilityAndCompatibility(installLocation,
- aOldAddon, xpiState) ||
- changed;
- }
- if (aOldAddon.visible && aOldAddon._installLocation.name != KEY_APP_GLOBAL)
- XPIProvider.allAppGlobal = false;
- // Copy add-on details (enabled, bootstrap, version, etc) to XPIState.
- xpiState.syncWithDB(aOldAddon);
- }
- else {
- // The add-on is in the DB, but not in xpiState (and thus not on disk).
- changed = removeMetadata(aOldAddon) || changed;
- }
- }
- }
-
- // Any add-on in our current location that we haven't seen needs to
- // be added to the database.
- // Get the migration data for this install location so we can include that as
- // we add, in case this is a database upgrade or rebuild.
- let locMigrateData = {};
- if (XPIDatabase.migrateData && installLocation.name in XPIDatabase.migrateData)
- locMigrateData = XPIDatabase.migrateData[installLocation.name];
- if (states) {
- for (let [id, xpiState] of states) {
- if (!seen.has(xpiState)) {
- changed = addMetadata(installLocation, id, xpiState,
- (locMigrateData[id] || null)) || changed;
- }
- }
- }
- }
-
- // Anything left in allDBAddons is a location where the database contains add-ons,
- // but the browser is no longer configured to use that location. The metadata for those
- // add-ons must be removed from the database.
- for (let [locationName, addons] of allDBAddons) {
- logger.debug("Removing orphaned DB add-on entries from " + locationName);
- for (let a of addons) {
- logger.debug("Remove ${location}:${id}", a);
- changed = removeMetadata(a) || changed;
- }
- }
-
- XPIStates.save();
- this.persistBootstrappedAddons();
-
- // Clear out any cached migration data.
- XPIDatabase.migrateData = null;
-
- return changed;
- },
-
/**
* Imports the xpinstall permissions from preferences into the permissions
* manager for the user to change later.
@@ -3921,10 +3293,10 @@ this.XPIProvider = {
AddonManagerPrivate.recordSimpleMeasure("XPIDB_startup_load_reasons", updateReasons);
XPIDatabase.syncLoadDB(false);
try {
- extensionListChanged = this.processFileChanges(manifests,
- aAppChanged,
- aOldAppVersion,
- aOldPlatformVersion);
+ extensionListChanged = XPIDatabaseReconcile.processFileChanges(manifests,
+ aAppChanged,
+ aOldAppVersion,
+ aOldPlatformVersion);
}
catch (e) {
logger.error("Failed to process extension changes at startup", e);
@@ -4901,8 +4273,7 @@ this.XPIProvider = {
XPIDatabase.updateAddonActive(aAddon, !isDisabled);
if (isDisabled) {
if (aAddon.bootstrap) {
- let file = aAddon._installLocation.getLocationForID(aAddon.id);
- this.callBootstrapMethod(aAddon, file, "shutdown",
+ this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
BOOTSTRAP_REASONS.ADDON_DISABLE);
this.unloadBootstrapScope(aAddon.id);
}
@@ -4910,8 +4281,7 @@ this.XPIProvider = {
}
else {
if (aAddon.bootstrap) {
- let file = aAddon._installLocation.getLocationForID(aAddon.id);
- this.callBootstrapMethod(aAddon, file, "startup",
+ this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup",
BOOTSTRAP_REASONS.ADDON_ENABLE);
}
AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
@@ -5005,12 +4375,11 @@ this.XPIProvider = {
}
if (aAddon.bootstrap) {
- let file = aAddon._installLocation.getLocationForID(aAddon.id);
- XPIProvider.callBootstrapMethod(aAddon, file,
+ XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle,
"install", BOOTSTRAP_REASONS.ADDON_INSTALL);
if (aAddon.active) {
- XPIProvider.callBootstrapMethod(aAddon, file,
+ XPIProvider.callBootstrapMethod(aAddon, aAddon._sourceBundle,
"startup", BOOTSTRAP_REASONS.ADDON_INSTALL);
}
else {
@@ -5032,13 +4401,12 @@ this.XPIProvider = {
if (!requiresRestart) {
if (aAddon.bootstrap) {
- let file = aAddon._installLocation.getLocationForID(aAddon.id);
if (aAddon.active) {
- this.callBootstrapMethod(aAddon, file, "shutdown",
+ this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
BOOTSTRAP_REASONS.ADDON_UNINSTALL);
}
- this.callBootstrapMethod(aAddon, file, "uninstall",
+ this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "uninstall",
BOOTSTRAP_REASONS.ADDON_UNINSTALL);
this.unloadBootstrapScope(aAddon.id);
flushStartupCache();
@@ -6112,8 +5480,7 @@ AddonInstall.prototype = {
reason = BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
if (this.existingAddon.bootstrap) {
- let file = this.existingAddon._installLocation
- .getLocationForID(this.existingAddon.id);
+ let file = this.existingAddon._sourceBundle;
if (this.existingAddon.active) {
XPIProvider.callBootstrapMethod(this.existingAddon, file,
"shutdown", reason,
@@ -7420,33 +6787,22 @@ function AddonWrapper(aAddon) {
* containing the add-ons files. The directory or text file must have the same
* name as the add-on's ID.
*
- * There may also a special directory named "staged" which can contain
- * directories with the same name as an add-on ID. If the directory is empty
- * then it means the add-on will be uninstalled from this location during the
- * next startup. If the directory contains the add-on's files then they will be
- * installed during the next startup.
- *
* @param aName
* The string identifier for the install location
* @param aDirectory
* The nsIFile directory for the install location
* @param aScope
* The scope of add-ons installed in this location
- * @param aLocked
- * true if add-ons cannot be installed, uninstalled or upgraded in the
- * install location
*/
-function DirectoryInstallLocation(aName, aDirectory, aScope, aLocked) {
+function DirectoryInstallLocation(aName, aDirectory, aScope) {
this._name = aName;
- this.locked = aLocked;
+ this.locked = true;
this._directory = aDirectory;
this._scope = aScope
this._IDToFileMap = {};
- this._FileToIDMap = {};
this._linkedAddons = [];
- this._stagingDirLock = 0;
- if (!aDirectory.exists())
+ if (!aDirectory || !aDirectory.exists())
return;
if (!aDirectory.isDirectory())
throw new Error("Location must be a directory.");
@@ -7458,7 +6814,6 @@ DirectoryInstallLocation.prototype = {
_name : "",
_directory : null,
_IDToFileMap : null, // mapping from add-on ID to nsIFile
- _FileToIDMap : null, // mapping from add-on path to add-on ID
/**
* Reads a directory linked to in a file.
@@ -7551,7 +6906,6 @@ DirectoryInstallLocation.prototype = {
}
this._IDToFileMap[id] = entry;
- this._FileToIDMap[entry.path] = id;
XPIProvider._addURIMapping(id, entry);
}
},
@@ -7573,14 +6927,59 @@ DirectoryInstallLocation.prototype = {
/**
* Gets an array of nsIFiles for add-ons installed in this location.
*/
- get addonLocations() {
- let locations = [];
+ getAddonLocations: function() {
+ let locations = new Map();
for (let id in this._IDToFileMap) {
- locations.push(this._IDToFileMap[id].clone());
+ locations.set(id, this._IDToFileMap[id].clone());
}
return locations;
},
+ /**
+ * Gets the directory that the add-on with the given ID is installed in.
+ *
+ * @param aId
+ * The ID of the add-on
+ * @return The nsIFile
+ * @throws if the ID does not match any of the add-ons installed
+ */
+ getLocationForID: function DirInstallLocation_getLocationForID(aId) {
+ if (aId in this._IDToFileMap)
+ return this._IDToFileMap[aId].clone();
+ throw new Error("Unknown add-on ID " + aId);
+ },
+
+ /**
+ * Returns true if the given addon was installed in this location by a text
+ * file pointing to its real path.
+ *
+ * @param aId
+ * The ID of the addon
+ */
+ isLinkedAddon: function DirInstallLocation__isLinkedAddon(aId) {
+ return this._linkedAddons.indexOf(aId) != -1;
+ }
+};
+
+/**
+ * An extension of DirectoryInstallLocation which adds methods to installing
+ * and removing add-ons from the directory at runtime.
+ *
+ * @param aName
+ * The string identifier for the install location
+ * @param aDirectory
+ * The nsIFile directory for the install location
+ * @param aScope
+ * The scope of add-ons installed in this location
+ */
+function MutableDirectoryInstallLocation(aName, aDirectory, aScope) {
+ DirectoryInstallLocation.call(this, aName, aDirectory, aScope);
+ this.locked = false;
+ this._stagingDirLock = 0;
+}
+
+MutableDirectoryInstallLocation.prototype = Object.create(DirectoryInstallLocation.prototype);
+Object.assign(MutableDirectoryInstallLocation.prototype, {
/**
* Gets the staging directory to put add-ons that are pending install and
* uninstall into.
@@ -7781,13 +7180,11 @@ DirectoryInstallLocation.prototype = {
} catch (e) {
logger.warn("failed to set lastModifiedTime on " + newFile.path, e);
}
- this._FileToIDMap[newFile.path] = aId;
this._IDToFileMap[aId] = newFile;
XPIProvider._addURIMapping(aId, newFile);
if (aExistingAddonID && aExistingAddonID != aId &&
aExistingAddonID in this._IDToFileMap) {
- delete this._FileToIDMap[this._IDToFileMap[aExistingAddonID]];
delete this._IDToFileMap[aExistingAddonID];
}
@@ -7818,7 +7215,6 @@ DirectoryInstallLocation.prototype = {
logger.warn("Attempted to remove " + aId + " from " +
this._name + " but it was already gone");
- delete this._FileToIDMap[file.path];
delete this._IDToFileMap[aId];
return;
}
@@ -7846,49 +7242,131 @@ DirectoryInstallLocation.prototype = {
}
}
- delete this._FileToIDMap[file.path];
delete this._IDToFileMap[aId];
},
+});
- /**
- * Gets the ID of the add-on installed in the given nsIFile.
- *
- * @param aFile
- * The nsIFile to look in
- * @return the ID
- * @throws if the file does not represent an installed add-on
- */
- getIDForLocation: function DirInstallLocation_getIDForLocation(aFile) {
- if (aFile.path in this._FileToIDMap)
- return this._FileToIDMap[aFile.path];
- throw new Error("Unknown add-on location " + aFile.path);
- },
+/**
+ * An object which identifies a directory install location for system add-ons.
+ * The location consists of a directory which contains the add-ons installed in
+ * the location.
+ *
+ * @param aName
+ * The string identifier for the install location
+ * @param aDirectory
+ * The nsIFile directory for the install location
+ * @param aScope
+ * The scope of add-ons installed in this location
+ * @param aResetSet
+ * True to throw away the current add-on set
+ */
+function SystemAddonInstallLocation(aName, aDirectory, aScope, aResetSet) {
+ this._baseDir = aDirectory;
- /**
- * Gets the directory that the add-on with the given ID is installed in.
- *
- * @param aId
- * The ID of the add-on
- * @return The nsIFile
- * @throws if the ID does not match any of the add-ons installed
- */
- getLocationForID: function DirInstallLocation_getLocationForID(aId) {
- if (aId in this._IDToFileMap)
- return this._IDToFileMap[aId].clone();
- throw new Error("Unknown add-on ID " + aId);
- },
-
- /**
- * Returns true if the given addon was installed in this location by a text
- * file pointing to its real path.
- *
- * @param aId
- * The ID of the addon
- */
- isLinkedAddon: function DirInstallLocation__isLinkedAddon(aId) {
- return this._linkedAddons.indexOf(aId) != -1;
+ if (aResetSet) {
+ this._addonSet = { schema: 1, addons: {} };
+ this._saveAddonSet(this._addonSet);
}
-};
+ else {
+ this._addonSet = this._loadAddonSet();
+ }
+
+ this._directory = null;
+ if (this._addonSet.directory) {
+ this._directory = aDirectory.clone();
+ this._directory.append(this._addonSet.directory);
+ logger.info("SystemAddonInstallLocation scanning directory " + this._directory.path);
+ }
+ else {
+ logger.info("SystemAddonInstallLocation directory is missing");
+ }
+
+ DirectoryInstallLocation.call(this, aName, this._directory, aScope);
+ this.locked = true;
+}
+
+SystemAddonInstallLocation.prototype = Object.create(DirectoryInstallLocation.prototype);
+Object.assign(SystemAddonInstallLocation.prototype, {
+ /**
+ * Reads the current set of system add-ons
+ */
+ _loadAddonSet: function() {
+ try {
+ let setStr = Preferences.get(PREF_SYSTEM_ADDON_SET, null);
+ if (setStr) {
+ let addonSet = JSON.parse(setStr);
+ if ((typeof addonSet == "object") && addonSet.schema == 1)
+ return addonSet;
+ }
+ }
+ catch (e) {
+ logger.error("Malformed system add-on set, resetting.");
+ }
+
+ return { schema: 1, addons: {} };
+ },
+
+ /**
+ * Saves the current set of system add-ons
+ */
+ _saveAddonSet: function(aAddonSet) {
+ Preferences.set(PREF_SYSTEM_ADDON_SET, JSON.stringify(aAddonSet));
+ },
+
+ getAddonLocations: function() {
+ let addons = DirectoryInstallLocation.prototype.getAddonLocations.call(this);
+
+ // Strip out any unexpected add-ons from the list
+ for (let id of addons.keys()) {
+ if (!(id in this._addonSet.addons))
+ addons.delete(id);
+ }
+
+ return addons;
+ },
+
+ /**
+ * Tests whether updated system add-ons are expected.
+ */
+ isActive: function() {
+ return this._directory != null;
+ },
+
+ /**
+ * Tests whether the loaded add-on information matches what is expected.
+ */
+ isValid: function(aAddons) {
+ for (let id of Object.keys(this._addonSet.addons)) {
+ if (!aAddons.has(id)) {
+ logger.warn("Expected add-on " + id + " is missing from the system add-on location.");
+ return false;
+ }
+
+ let addon = aAddons.get(id);
+ if (addon.appDisabled) {
+ logger.warn("System add-on " + id + " isn't compatible with the application.");
+ return false;
+ }
+
+ if (addon.unpack) {
+ logger.warn("System add-on " + id + " isn't a packed add-on.");
+ return false;
+ }
+
+ if (!addon.bootstrap) {
+ logger.warn("System add-on " + id + " isn't restartless.");
+ return false;
+ }
+
+ if (addon.version != this._addonSet.addons[id].version) {
+ logger.warn("System add-on " + id + " wasn't the correct version.");
+ return false;
+ }
+ }
+
+ return true;
+ },
+});
#ifdef XP_WIN
/**
@@ -7909,7 +7387,6 @@ function WinRegInstallLocation(aName, aRootKey, aScope) {
this._rootKey = aRootKey;
this._scope = aScope;
this._IDToFileMap = {};
- this._FileToIDMap = {};
let path = this._appKeyPath + "\\Extensions";
let key = Cc["@mozilla.org/windows-registry-key;1"].
@@ -7933,7 +7410,6 @@ WinRegInstallLocation.prototype = {
_rootKey : null,
_scope : null,
_IDToFileMap : null, // mapping from ID to nsIFile
- _FileToIDMap : null, // mapping from path to ID
/**
* Retrieves the path of this Application's data key in the registry.
@@ -7977,7 +7453,6 @@ WinRegInstallLocation.prototype = {
}
this._IDToFileMap[id] = file;
- this._FileToIDMap[file.path] = id;
XPIProvider._addURIMapping(id, file);
}
},
@@ -7999,41 +7474,14 @@ WinRegInstallLocation.prototype = {
/**
* Gets an array of nsIFiles for add-ons installed in this location.
*/
- get addonLocations() {
- let locations = [];
+ getAddonLocations: function() {
+ let locations = new Map();
for (let id in this._IDToFileMap) {
- locations.push(this._IDToFileMap[id].clone());
+ locations.set(id, this._IDToFileMap[id].clone());
}
return locations;
},
- /**
- * Gets the ID of the add-on installed in the given nsIFile.
- *
- * @param aFile
- * The nsIFile to look in
- * @return the ID
- * @throws if the file does not represent an installed add-on
- */
- getIDForLocation: function RegInstallLocation_getIDForLocation(aFile) {
- if (aFile.path in this._FileToIDMap)
- return this._FileToIDMap[aFile.path];
- throw new Error("Unknown add-on location");
- },
-
- /**
- * Gets the nsIFile that the add-on with the given ID is installed in.
- *
- * @param aId
- * The ID of the add-on
- * @return the nsIFile
- */
- getLocationForID: function RegInstallLocation_getLocationForID(aId) {
- if (aId in this._IDToFileMap)
- return this._IDToFileMap[aId].clone();
- throw new Error("Unknown add-on ID");
- },
-
/**
* @see DirectoryInstallLocation
*/
diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
index 063a76705fab..eca104eeb73a 100644
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -12,6 +12,7 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
"resource://gre/modules/addons/AddonRepository.jsm");
@@ -23,6 +24,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "Blocklist",
+ "@mozilla.org/extensions/blocklist;1",
+ Ci.nsIBlocklistService);
Cu.import("resource://gre/modules/Log.jsm");
const LOGGER_ID = "addons.xpi-utils";
@@ -46,6 +50,12 @@ const PREF_DB_SCHEMA = "extensions.databaseSchema";
const PREF_PENDING_OPERATIONS = "extensions.pendingOperations";
const PREF_EM_ENABLED_ADDONS = "extensions.enabledAddons";
const PREF_EM_DSS_ENABLED = "extensions.dss.enabled";
+const PREF_EM_AUTO_DISABLED_SCOPES = "extensions.autoDisableScopes";
+
+const KEY_APP_PROFILE = "app-profile";
+const KEY_APP_SYSTEM_ADDONS = "app-system-addons";
+const KEY_APP_SYSTEM_DEFAULTS = "app-system-defaults";
+const KEY_APP_GLOBAL = "app-global";
// Properties that only exist in the database
const DB_METADATA = ["syncGUID",
@@ -72,6 +82,13 @@ const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type",
"strictCompatibility", "locales", "targetApplications",
"targetPlatforms", "multiprocessCompatible", "signedState"];
+// Properties that should be migrated where possible from an old database. These
+// shouldn't include properties that can be read directly from install.rdf files
+// or calculated
+const DB_MIGRATE_METADATA= ["installDate", "userDisabled", "softDisabled",
+ "sourceURI", "applyBackgroundUpdates",
+ "releaseNotesURI", "foreignInstall", "syncGUID"];
+
// Time to wait before async save of XPI JSON database, in milliseconds
const ASYNC_SAVE_DELAY_MS = 20;
@@ -314,13 +331,15 @@ function DBAddonInternal(aLoaded) {
this._key = this.location + ":" + this.id;
- try {
- this._sourceBundle = this._installLocation.getLocationForID(this.id);
+ if (aLoaded._sourceBundle) {
+ this._sourceBundle = aLoaded._sourceBundle;
}
- catch (e) {
- // An exception will be thrown if the add-on appears in the database but
- // not on disk. In general this should only happen during startup as
- // this change is being detected.
+ else if (aLoaded.descriptor) {
+ this._sourceBundle = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ this._sourceBundle.persistentDescriptor = aLoaded.descriptor;
+ }
+ else {
+ throw new Error("Expected passed argument to contain a descriptor");
}
XPCOMUtils.defineLazyGetter(this, "pendingUpgrade",
@@ -785,7 +804,7 @@ this.XPIDatabase = {
if (aRebuildOnError) {
logger.warn("Rebuilding add-ons database from installed extensions.");
try {
- XPIProvider.processFileChanges({}, false);
+ XPIDatabaseReconcile.processFileChanges({}, false);
}
catch (e) {
logger.error("Failed to rebuild XPI database from installed extensions", e);
@@ -1084,7 +1103,7 @@ this.XPIDatabase = {
})
.then(null,
error => {
- logger.error("getAddon failed", e);
+ logger.error("getAddon failed", error);
makeSafe(aCallback)(null);
});
},
@@ -1301,6 +1320,7 @@ this.XPIDatabase = {
if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) {
logger.debug("Hide addon " + otherAddon._key);
otherAddon.visible = false;
+ otherAddon.active = false;
}
}
aAddon.visible = true;
@@ -1488,3 +1508,638 @@ this.XPIDatabase = {
return true;
}
};
+
+this.XPIDatabaseReconcile = {
+ /**
+ * Returns a map of ID -> add-on. When the same add-on ID exists in multiple
+ * install locations the highest priority location is chosen.
+ */
+ flattenByID(addonMap, hideLocation) {
+ let map = new Map();
+
+ for (let installLocation of XPIProvider.installLocations) {
+ if (installLocation.name == hideLocation)
+ continue;
+
+ let locationMap = addonMap.get(installLocation.name);
+ if (!locationMap)
+ continue;
+
+ for (let [id, addon] of locationMap) {
+ if (!map.has(id))
+ map.set(id, addon);
+ }
+ }
+
+ return map;
+ },
+
+ /**
+ * Finds the visible add-ons from the map.
+ */
+ getVisibleAddons(addonMap) {
+ let map = new Map();
+
+ for (let [location, addons] of addonMap) {
+ for (let [id, addon] of addons) {
+ if (!addon.visible)
+ continue;
+
+ if (map.has(id)) {
+ logger.warn("Previous database listed more than one visible add-on with id " + id);
+ continue;
+ }
+
+ map.set(id, addon);
+ }
+ }
+
+ return map;
+ },
+
+ /**
+ * Called to add the metadata for an add-on in one of the install locations
+ * to the database. This can be called in three different cases. Either an
+ * add-on has been dropped into the location from outside of Firefox, or
+ * an add-on has been installed through the application, or the database
+ * has been upgraded or become corrupt and add-on data has to be reloaded
+ * into it.
+ *
+ * @param aInstallLocation
+ * The install location containing the add-on
+ * @param aId
+ * The ID of the add-on
+ * @param aAddonState
+ * The new state of the add-on
+ * @param aNewAddon
+ * The manifest for the new add-on if it has already been loaded
+ * @param aOldAppVersion
+ * The version of the application last run with this profile or null
+ * if it is a new profile or the version is unknown
+ * @param aOldPlatformVersion
+ * The version of the platform last run with this profile or null
+ * if it is a new profile or the version is unknown
+ * @param aMigrateData
+ * If during startup the database had to be upgraded this will
+ * contain data that used to be held about this add-on
+ * @return a boolean indicating if flushing caches is required to complete
+ * changing this add-on
+ */
+ addMetadata(aInstallLocation, aId, aAddonState, aNewAddon, aOldAppVersion,
+ aOldPlatformVersion, aMigrateData) {
+ logger.debug("New add-on " + aId + " installed in " + aInstallLocation.name);
+
+ // If we had staged data for this add-on or we aren't recovering from a
+ // corrupt database and we don't have migration data for this add-on then
+ // this must be a new install.
+ let isNewInstall = (!!aNewAddon) || (!XPIDatabase.activeBundles && !aMigrateData);
+
+ // If it's a new install and we haven't yet loaded the manifest then it
+ // must be something dropped directly into the install location
+ let isDetectedInstall = isNewInstall && !aNewAddon;
+
+ // Load the manifest if necessary and sanity check the add-on ID
+ try {
+ if (!aNewAddon) {
+ // Load the manifest from the add-on.
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.persistentDescriptor = aAddonState.descriptor;
+ aNewAddon = syncLoadManifestFromFile(file);
+ }
+ // The add-on in the manifest should match the add-on ID.
+ if (aNewAddon.id != aId) {
+ throw new Error("Invalid addon ID: expected addon ID " + aId +
+ ", found " + aNewAddon.id + " in manifest");
+ }
+ }
+ catch (e) {
+ logger.warn("addMetadata: Add-on " + aId + " is invalid", e);
+
+ // Remove the invalid add-on from the install location if the install
+ // location isn't locked, no restart will be necessary
+ if (!aInstallLocation.locked)
+ aInstallLocation.uninstallAddon(aId);
+ else
+ logger.warn("Could not uninstall invalid item from locked install location");
+ return null;
+ }
+
+ // Update the AddonInternal properties.
+ aNewAddon._installLocation = aInstallLocation;
+ aNewAddon.installDate = aAddonState.mtime;
+ aNewAddon.updateDate = aAddonState.mtime;
+
+ // Assume that add-ons in the system add-ons install location aren't
+ // foreign and should default to enabled.
+ aNewAddon.foreignInstall = isDetectedInstall &&
+ aInstallLocation.name != KEY_APP_SYSTEM_ADDONS &&
+ aInstallLocation.name != KEY_APP_SYSTEM_DEFAULTS;
+
+ // appDisabled depends on whether the add-on is a foreignInstall so update
+ aNewAddon.appDisabled = !isUsableAddon(aNewAddon);
+
+ if (aMigrateData) {
+ // If there is migration data then apply it.
+ logger.debug("Migrating data from old database");
+
+ DB_MIGRATE_METADATA.forEach(function(aProp) {
+ // A theme's disabled state is determined by the selected theme
+ // preference which is read in loadManifestFromRDF
+ if (aProp == "userDisabled" && aNewAddon.type == "theme")
+ return;
+
+ if (aProp in aMigrateData)
+ aNewAddon[aProp] = aMigrateData[aProp];
+ });
+
+ // Force all non-profile add-ons to be foreignInstalls since they can't
+ // have been installed through the API
+ aNewAddon.foreignInstall |= aInstallLocation.name != KEY_APP_PROFILE;
+
+ // Some properties should only be migrated if the add-on hasn't changed.
+ // The version property isn't a perfect check for this but covers the
+ // vast majority of cases.
+ if (aMigrateData.version == aNewAddon.version) {
+ logger.debug("Migrating compatibility info");
+ if ("targetApplications" in aMigrateData)
+ aNewAddon.applyCompatibilityUpdate(aMigrateData, true);
+ }
+
+ // Since the DB schema has changed make sure softDisabled is correct
+ applyBlocklistChanges(aNewAddon, aNewAddon, aOldAppVersion,
+ aOldPlatformVersion);
+ }
+
+ // The default theme is never a foreign install
+ if (aNewAddon.type == "theme" && aNewAddon.internalName == XPIProvider.defaultSkin)
+ aNewAddon.foreignInstall = false;
+
+ if (isDetectedInstall && aNewAddon.foreignInstall) {
+ // If the add-on is a foreign install and is in a scope where add-ons
+ // that were dropped in should default to disabled then disable it
+ let disablingScopes = Preferences.get(PREF_EM_AUTO_DISABLED_SCOPES, 0);
+ if (aInstallLocation.scope & disablingScopes) {
+ logger.warn("Disabling foreign installed add-on " + aNewAddon.id + " in "
+ + aInstallLocation.name);
+ aNewAddon.userDisabled = true;
+ }
+ }
+
+ return XPIDatabase.addAddonMetadata(aNewAddon, aAddonState.descriptor);
+ },
+
+ /**
+ * Called when an add-on has been removed.
+ *
+ * @param aOldAddon
+ * The AddonInternal as it appeared the last time the application
+ * ran
+ * @return a boolean indicating if flushing caches is required to complete
+ * changing this add-on
+ */
+ removeMetadata(aOldAddon) {
+ // This add-on has disappeared
+ logger.debug("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location);
+ XPIDatabase.removeAddonMetadata(aOldAddon);
+ },
+
+ /**
+ * Updates an add-on's metadata and determines if a restart of the
+ * application is necessary. This is called when either the add-on's
+ * install directory path or last modified time has changed.
+ *
+ * @param aInstallLocation
+ * The install location containing the add-on
+ * @param aOldAddon
+ * The AddonInternal as it appeared the last time the application
+ * ran
+ * @param aAddonState
+ * The new state of the add-on
+ * @param aNewAddon
+ * The manifest for the new add-on if it has already been loaded
+ * @return a boolean indicating if flushing caches is required to complete
+ * changing this add-on
+ */
+ updateMetadata(aInstallLocation, aOldAddon, aAddonState, aNewAddon) {
+ logger.debug("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name);
+
+ try {
+ // If there isn't an updated install manifest for this add-on then load it.
+ if (!aNewAddon) {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.persistentDescriptor = aAddonState.descriptor;
+ aNewAddon = syncLoadManifestFromFile(file);
+ applyBlocklistChanges(aOldAddon, aNewAddon);
+
+ // Carry over any pendingUninstall state to add-ons modified directly
+ // in the profile. This is important when the attempt to remove the
+ // add-on in processPendingFileChanges failed and caused an mtime
+ // change to the add-ons files.
+ aNewAddon.pendingUninstall = aOldAddon.pendingUninstall;
+ }
+
+ // The ID in the manifest that was loaded must match the ID of the old
+ // add-on.
+ if (aNewAddon.id != aOldAddon.id)
+ throw new Error("Incorrect id in install manifest for existing add-on " + aOldAddon.id);
+ }
+ catch (e) {
+ logger.warn("updateMetadata: Add-on " + aOldAddon.id + " is invalid", e);
+ XPIDatabase.removeAddonMetadata(aOldAddon);
+ XPIStates.removeAddon(aOldAddon.location, aOldAddon.id);
+ if (!aInstallLocation.locked)
+ aInstallLocation.uninstallAddon(aOldAddon.id);
+ else
+ logger.warn("Could not uninstall invalid item from locked install location");
+
+ return null;
+ }
+
+ // Set the additional properties on the new AddonInternal
+ aNewAddon._installLocation = aInstallLocation;
+ aNewAddon.updateDate = aAddonState.mtime;
+
+ // Update the database
+ return XPIDatabase.updateAddonMetadata(aOldAddon, aNewAddon, aAddonState.descriptor);
+ },
+
+ /**
+ * Updates an add-on's descriptor for when the add-on has moved in the
+ * filesystem but hasn't changed in any other way.
+ *
+ * @param aInstallLocation
+ * The install location containing the add-on
+ * @param aOldAddon
+ * The AddonInternal as it appeared the last time the application
+ * ran
+ * @param aAddonState
+ * The new state of the add-on
+ * @return a boolean indicating if flushing caches is required to complete
+ * changing this add-on
+ */
+ updateDescriptor(aInstallLocation, aOldAddon, aAddonState) {
+ logger.debug("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor);
+ aOldAddon.descriptor = aAddonState.descriptor;
+ aOldAddon._sourceBundle.persistentDescriptor = aAddonState.descriptor;
+
+ return aOldAddon;
+ },
+
+ /**
+ * Called when no change has been detected for an add-on's metadata but the
+ * application has changed so compatibility may have changed.
+ *
+ * @param aInstallLocation
+ * The install location containing the add-on
+ * @param aOldAddon
+ * The AddonInternal as it appeared the last time the application
+ * ran
+ * @param aAddonState
+ * The new state of the add-on
+ * @param aOldAppVersion
+ * The version of the application last run with this profile or null
+ * if it is a new profile or the version is unknown
+ * @param aOldPlatformVersion
+ * The version of the platform last run with this profile or null
+ * if it is a new profile or the version is unknown
+ * @return a boolean indicating if flushing caches is required to complete
+ * changing this add-on
+ */
+ updateCompatibility(aInstallLocation, aOldAddon, aAddonState, aOldAppVersion, aOldPlatformVersion) {
+ logger.debug("Updating compatibility for add-on " + aOldAddon.id + " in " + aInstallLocation.name);
+
+ // If updating from a version of the app that didn't support signedState
+ // then fetch that property now
+ if (aOldAddon.signedState === undefined && ADDON_SIGNING &&
+ SIGNED_TYPES.has(aOldAddon.type)) {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.persistentDescriptor = aAddonState.descriptor;
+ let manifest = syncLoadManifestFromFile(file);
+ aOldAddon.signedState = manifest.signedState;
+ }
+ // This updates the addon's JSON cached data in place
+ applyBlocklistChanges(aOldAddon, aOldAddon, aOldAppVersion,
+ aOldPlatformVersion);
+ aOldAddon.appDisabled = !isUsableAddon(aOldAddon);
+
+ return aOldAddon;
+ },
+
+ /**
+ * Compares the add-ons that are currently installed to those that were
+ * known to be installed when the application last ran and applies any
+ * changes found to the database. Also sends "startupcache-invalidate" signal to
+ * observerservice if it detects that data may have changed.
+ * Always called after XPIProviderUtils.js and extensions.json have been loaded.
+ *
+ * @param aManifests
+ * A dictionary of cached AddonInstalls for add-ons that have been
+ * installed
+ * @param aUpdateCompatibility
+ * true to update add-ons appDisabled property when the application
+ * version has changed
+ * @param aOldAppVersion
+ * The version of the application last run with this profile or null
+ * if it is a new profile or the version is unknown
+ * @param aOldPlatformVersion
+ * The version of the platform last run with this profile or null
+ * if it is a new profile or the version is unknown
+ * @return a boolean indicating if a change requiring flushing the caches was
+ * detected
+ */
+ processFileChanges(aManifests, aUpdateCompatibility, aOldAppVersion, aOldPlatformVersion) {
+ let loadedManifest = (aInstallLocation, aId) => {
+ if (!(aInstallLocation.name in aManifests))
+ return null;
+ if (!(aId in aManifests[aInstallLocation.name]))
+ return null;
+ return aManifests[aInstallLocation.name][aId];
+ };
+
+ // Get the previous add-ons from the database and put them into maps by location
+ let previousAddons = new Map();
+ for (let a of XPIDatabase.getAddons()) {
+ let locationAddonMap = previousAddons.get(a.location);
+ if (!locationAddonMap) {
+ locationAddonMap = new Map();
+ previousAddons.set(a.location, locationAddonMap);
+ }
+ locationAddonMap.set(a.id, a);
+ }
+
+ // Build the list of current add-ons into similar maps. When add-ons are still
+ // present we re-use the add-on objects from the database and update their
+ // details directly
+ let currentAddons = new Map();
+ for (let installLocation of XPIProvider.installLocations) {
+ let locationAddonMap = new Map();
+ currentAddons.set(installLocation.name, locationAddonMap);
+
+ // Get all the on-disk XPI states for this location, and keep track of which
+ // ones we see in the database.
+ let states = XPIStates.getLocation(installLocation.name);
+
+ // Iterate through the add-ons installed the last time the application
+ // ran
+ let dbAddons = previousAddons.get(installLocation.name);
+ if (dbAddons) {
+ for (let [id, oldAddon] of dbAddons) {
+ // Check if the add-on is still installed
+ let xpiState = states && states.get(id);
+ if (xpiState) {
+ // Here the add-on was present in the database and on disk
+ recordAddonTelemetry(oldAddon);
+
+ // Check if the add-on has been changed outside the XPI provider
+ if (oldAddon.updateDate != xpiState.mtime) {
+ // Did time change in the wrong direction?
+ if (xpiState.mtime < oldAddon.updateDate) {
+ XPIProvider.setTelemetry(oldAddon.id, "olderFile", {
+ name: XPIProvider._mostRecentlyModifiedFile[id],
+ mtime: xpiState.mtime,
+ oldtime: oldAddon.updateDate
+ });
+ } else {
+ XPIProvider.setTelemetry(oldAddon.id, "modifiedFile",
+ XPIProvider._mostRecentlyModifiedFile[id]);
+ }
+ }
+
+ // The add-on has changed if the modification time has changed, or
+ // we have an updated manifest for it. Also reload the metadata for
+ // add-ons in the application directory when the application version
+ // has changed
+ let newAddon = loadedManifest(installLocation, id);
+ if (newAddon || oldAddon.updateDate != xpiState.mtime ||
+ (aUpdateCompatibility && (installLocation.name == KEY_APP_GLOBAL ||
+ installLocation.name == KEY_APP_SYSTEM_DEFAULTS))) {
+ newAddon = this.updateMetadata(installLocation, oldAddon, xpiState, newAddon);
+ }
+ else if (oldAddon.descriptor != xpiState.descriptor) {
+ newAddon = this.updateDescriptor(installLocation, oldAddon, xpiState);
+ }
+ else if (aUpdateCompatibility) {
+ newAddon = this.updateCompatibility(installLocation, oldAddon, xpiState,
+ aOldAppVersion, aOldPlatformVersion);
+ }
+ else {
+ // No change
+ newAddon = oldAddon;
+ }
+
+ if (newAddon)
+ locationAddonMap.set(newAddon.id, newAddon);
+ }
+ else {
+ // The add-on is in the DB, but not in xpiState (and thus not on disk).
+ this.removeMetadata(oldAddon);
+ }
+ }
+ }
+
+ // Any add-on in our current location that we haven't seen needs to
+ // be added to the database.
+ // Get the migration data for this install location so we can include that as
+ // we add, in case this is a database upgrade or rebuild.
+ let locMigrateData = {};
+ if (XPIDatabase.migrateData && installLocation.name in XPIDatabase.migrateData)
+ locMigrateData = XPIDatabase.migrateData[installLocation.name];
+
+ if (states) {
+ for (let [id, xpiState] of states) {
+ if (locationAddonMap.has(id))
+ continue;
+ let migrateData = id in locMigrateData ? locMigrateData[id] : null;
+ let newAddon = loadedManifest(installLocation, id);
+ let addon = this.addMetadata(installLocation, id, xpiState, newAddon,
+ aOldAppVersion, aOldPlatformVersion, migrateData);
+ if (addon)
+ locationAddonMap.set(addon.id, addon);
+ }
+ }
+ }
+
+ // previousAddons may contain locations where the database contains add-ons
+ // but the browser is no longer configured to use that location. The metadata
+ // for those add-ons must be removed from the database.
+ for (let [locationName, addons] of previousAddons) {
+ if (!currentAddons.has(locationName)) {
+ for (let [id, oldAddon] of addons)
+ this.removeMetadata(oldAddon);
+ }
+ }
+
+ // Validate the updated system add-ons
+ let systemAddonLocation = XPIProvider.installLocationsByName[KEY_APP_SYSTEM_ADDONS];
+ let addons = currentAddons.get(KEY_APP_SYSTEM_ADDONS) || new Map();
+
+ let hideLocation;
+ if (systemAddonLocation.isActive() && systemAddonLocation.isValid(addons)) {
+ // Hide the system add-on defaults
+ logger.info("Hiding the default system add-ons.");
+ hideLocation = KEY_APP_SYSTEM_DEFAULTS;
+ }
+ else {
+ // Hide the system add-on updates
+ logger.info("Hiding the updated system add-ons.");
+ hideLocation = KEY_APP_SYSTEM_ADDONS;
+ }
+
+ let previousVisible = this.getVisibleAddons(previousAddons);
+ let currentVisible = this.flattenByID(currentAddons, hideLocation);
+ let sawActiveTheme = false;
+ XPIProvider.bootstrappedAddons = {};
+
+ // Pass over the new set of visible add-ons, record any changes that occured
+ // during startup and call bootstrap install/uninstall scripts as necessary
+ for (let [id, currentAddon] of currentVisible) {
+ let previousAddon = previousVisible.get(id);
+
+ // Note if any visible add-on is not in the application install location
+ if (currentAddon._installLocation.name != KEY_APP_GLOBAL)
+ XPIProvider.allAppGlobal = false;
+
+ let isActive = !currentAddon.disabled;
+ let wasActive = previousAddon ? previousAddon.active : currentAddon.active
+
+ if (!previousAddon) {
+ // If we had a manifest for this add-on it was a staged install and
+ // so wasn't something recovered from a corrupt database
+ let wasStaged = !!loadedManifest(currentAddon._installLocation, id);
+
+ // We might be recovering from a corrupt database, if so use the
+ // list of known active add-ons to update the new add-on
+ if (!wasStaged && XPIDatabase.activeBundles) {
+ // For themes we know which is active by the current skin setting
+ if (currentAddon.type == "theme")
+ isActive = currentAddon.internalName == XPIProvider.currentSkin;
+ else
+ isActive = XPIDatabase.activeBundles.indexOf(currentAddon.descriptor) != -1;
+
+ // If the add-on wasn't active and it isn't already disabled in some way
+ // then it was probably either softDisabled or userDisabled
+ if (!isActive && !currentAddon.disabled) {
+ // If the add-on is softblocked then assume it is softDisabled
+ if (currentAddon.blocklistState == Blocklist.STATE_SOFTBLOCKED)
+ currentAddon.softDisabled = true;
+ else
+ currentAddon.userDisabled = true;
+ }
+ }
+ else {
+ // This is a new install
+ if (currentAddon.foreignInstall)
+ AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED, id);
+
+ if (currentAddon.bootstrap) {
+ // Visible bootstrapped add-ons need to have their install method called
+ XPIProvider.callBootstrapMethod(currentAddon, currentAddon._sourceBundle,
+ "install", BOOTSTRAP_REASONS.ADDON_INSTALL);
+ if (!isActive)
+ XPIProvider.unloadBootstrapScope(currentAddon.id);
+ }
+ }
+ }
+ else {
+ if (previousAddon !== currentAddon) {
+ // This is an add-on that has changed, either the metadata was reloaded
+ // or the version in a different location has become visible
+ AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, id);
+
+ let installReason = Services.vc.compare(previousAddon.version, currentAddon.version) < 0 ?
+ BOOTSTRAP_REASONS.ADDON_UPGRADE :
+ BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
+
+ // If the previous add-on was in a different location, bootstrapped
+ // and still exists then call its uninstall method.
+ if (previousAddon.bootstrap && previousAddon._installLocation &&
+ currentAddon._installLocation != previousAddon._installLocation &&
+ previousAddon._sourceBundle.exists()) {
+
+ XPIProvider.callBootstrapMethod(previousAddon, previousAddon._sourceBundle,
+ "uninstall", installReason,
+ { newVersion: currentAddon.version });
+ XPIProvider.unloadBootstrapScope(previousAddon.id);
+ }
+
+ // Make sure to flush the cache when an old add-on has gone away
+ flushStartupCache();
+
+ if (currentAddon.bootstrap) {
+ // Visible bootstrapped add-ons need to have their install method called
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.persistentDescriptor = currentAddon._sourceBundle.persistentDescriptor;
+ XPIProvider.callBootstrapMethod(currentAddon, file,
+ "install", installReason,
+ { oldVersion: previousAddon.version });
+ if (currentAddon.disabled)
+ XPIProvider.unloadBootstrapScope(currentAddon.id);
+ }
+ }
+
+ if (isActive != wasActive) {
+ let change = isActive ? AddonManager.STARTUP_CHANGE_ENABLED
+ : AddonManager.STARTUP_CHANGE_DISABLED;
+ AddonManagerPrivate.addStartupChange(change, id);
+ }
+ }
+
+ XPIDatabase.makeAddonVisible(currentAddon);
+ currentAddon.active = isActive;
+
+ // Make sure the bootstrap information is up to date for this ID
+ if (currentAddon.bootstrap && currentAddon.active) {
+ XPIProvider.bootstrappedAddons[id] = {
+ version: currentAddon.version,
+ type: currentAddon.type,
+ descriptor: currentAddon._sourceBundle.persistentDescriptor,
+ multiprocessCompatible: currentAddon.multiprocessCompatible
+ };
+ }
+
+ if (currentAddon.active && currentAddon.internalName == XPIProvider.selectedSkin)
+ sawActiveTheme = true;
+ }
+
+ // Pass over the set of previously visible add-ons that have now gone away
+ // and record the change.
+ for (let [id, previousAddon] of previousVisible) {
+ if (currentVisible.has(id))
+ continue;
+
+ // This add-on vanished
+ AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED, id);
+ }
+
+ // Make sure add-ons from hidden locations are marked invisible and inactive
+ let locationAddonMap = currentAddons.get(hideLocation);
+ if (locationAddonMap) {
+ for (let addon of locationAddonMap.values()) {
+ addon.visible = false;
+ addon.active = false;
+ }
+ }
+
+ // None of the active add-ons match the selected theme, enable the default.
+ if (!sawActiveTheme) {
+ XPIProvider.enableDefaultTheme();
+ }
+
+ // Finally update XPIStates to match everything
+ for (let [locationName, locationAddonMap] of currentAddons) {
+ for (let [id, addon] of locationAddonMap) {
+ let xpiState = XPIStates.getAddon(locationName, id);
+ xpiState.syncWithDB(addon);
+ }
+ }
+ XPIStates.save();
+
+ XPIProvider.persistBootstrappedAddons();
+
+ // Clear out any cached migration data.
+ XPIDatabase.migrateData = null;
+ XPIDatabase.saveChanges();
+
+ return true;
+ },
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app0/empty b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app0/empty
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system1@tests.mozilla.org.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system1@tests.mozilla.org.xpi
new file mode 100644
index 000000000000..838b1b6584a8
Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system1@tests.mozilla.org.xpi differ
diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system2@tests.mozilla.org.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system2@tests.mozilla.org.xpi
new file mode 100644
index 000000000000..c346cf3b7d07
Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system2@tests.mozilla.org.xpi differ
diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/bootstrap.js
new file mode 100644
index 000000000000..c5d862f45a73
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/bootstrap.js
@@ -0,0 +1,18 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const ID = "system1@tests.mozilla.org";
+const VERSION = "1.0";
+
+function install(data, reason) {
+}
+
+function startup(data, reason) {
+ Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION);
+}
+
+function shutdown(data, reason) {
+ Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version");
+}
+
+function uninstall(data, reason) {
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/install.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/install.rdf
new file mode 100644
index 000000000000..5ec174d04400
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_1_1/install.rdf
@@ -0,0 +1,23 @@
+
+
+
+
+
+ system1@tests.mozilla.org
+ 1.0
+ true
+
+
+ System Add-on 1
+
+
+
+ xpcshell@tests.mozilla.org
+ 1
+ 5
+
+
+
+
+
diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/bootstrap.js
new file mode 100644
index 000000000000..e9449de6ec96
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/bootstrap.js
@@ -0,0 +1,18 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const ID = "system2@tests.mozilla.org";
+const VERSION = "1.0";
+
+function install(data, reason) {
+}
+
+function startup(data, reason) {
+ Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION);
+}
+
+function shutdown(data, reason) {
+ Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version");
+}
+
+function uninstall(data, reason) {
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/install.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/install.rdf
new file mode 100644
index 000000000000..b6b4efc82ed3
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app1/features/system_2_1/install.rdf
@@ -0,0 +1,23 @@
+
+
+
+
+
+ system2@tests.mozilla.org
+ 1.0
+ true
+
+
+ System Add-on 2
+
+
+
+ xpcshell@tests.mozilla.org
+ 1
+ 5
+
+
+
+
+
diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system1@tests.mozilla.org.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system1@tests.mozilla.org.xpi
new file mode 100644
index 000000000000..f82f607f9c76
Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system1@tests.mozilla.org.xpi differ
diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system3@tests.mozilla.org.xpi b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system3@tests.mozilla.org.xpi
new file mode 100644
index 000000000000..7fa67926282e
Binary files /dev/null and b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system3@tests.mozilla.org.xpi differ
diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/bootstrap.js
new file mode 100644
index 000000000000..7a18c8ca3fa3
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/bootstrap.js
@@ -0,0 +1,18 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const ID = "system1@tests.mozilla.org";
+const VERSION = "2.0";
+
+function install(data, reason) {
+}
+
+function startup(data, reason) {
+ Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION);
+}
+
+function shutdown(data, reason) {
+ Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version");
+}
+
+function uninstall(data, reason) {
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/install.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/install.rdf
new file mode 100644
index 000000000000..75a89f52d567
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_1_2/install.rdf
@@ -0,0 +1,23 @@
+
+
+
+
+
+ system1@tests.mozilla.org
+ 2.0
+ true
+
+
+ System Add-on 1
+
+
+
+ xpcshell@tests.mozilla.org
+ 1
+ 5
+
+
+
+
+
diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/bootstrap.js
new file mode 100644
index 000000000000..198946e6ea97
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/bootstrap.js
@@ -0,0 +1,18 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const ID = "system3@tests.mozilla.org";
+const VERSION = "1.0";
+
+function install(data, reason) {
+}
+
+function startup(data, reason) {
+ Services.prefs.setCharPref("bootstraptest." + ID + ".active_version", VERSION);
+}
+
+function shutdown(data, reason) {
+ Services.prefs.clearUserPref("bootstraptest." + ID + ".active_version");
+}
+
+function uninstall(data, reason) {
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/install.rdf b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/install.rdf
new file mode 100644
index 000000000000..83be8dcc9120
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/system_addons/app2/features/system_3_1/install.rdf
@@ -0,0 +1,23 @@
+
+
+
+
+
+ system3@tests.mozilla.org
+ 1.0
+ true
+
+
+ System Add-on 3
+
+
+
+ xpcshell@tests.mozilla.org
+ 1
+ 5
+
+
+
+
+
diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
index 6a4ac99bcf61..e6a72cd2fbe4 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -1026,7 +1026,7 @@ function getFileForAddon(aDir, aId) {
function registerDirectory(aKey, aDir) {
var dirProvider = {
getFile: function(aProp, aPersistent) {
- aPersistent.value = true;
+ aPersistent.value = false;
if (aProp == aKey)
return aDir.clone();
return null;
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
index e8d12c1fa676..5825cde45571 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js
@@ -1067,9 +1067,8 @@ function run_test_21() {
do_check_eq(getUninstallReason(), -1);
do_check_eq(getUninstallNewVersion(), -1);
- // TODO this reason should probably be ADDON_DOWNGRADE (bug 607818)
- do_check_eq(getInstallReason(), ADDON_INSTALL);
- do_check_eq(getInstallOldVersion(), 0);
+ do_check_eq(getInstallReason(), ADDON_DOWNGRADE);
+ do_check_eq(getInstallOldVersion(), 2);
do_check_eq(getStartupReason(), APP_STARTUP);
do_check_eq(getStartupOldVersion(), 0);
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js b/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
new file mode 100644
index 000000000000..159ba2f15b2d
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js
@@ -0,0 +1,200 @@
+// Tests that we reset to the default system add-ons correctly when switching
+// application versions
+const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet";
+
+const featureDir = gProfD.clone();
+featureDir.append("features");
+
+const distroDir = do_get_file("data/system_addons/app0");
+registerDirectory("XREAppDist", distroDir);
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "0");
+
+function makeUUID() {
+ let uuidGen = AM_Cc["@mozilla.org/uuid-generator;1"].
+ getService(AM_Ci.nsIUUIDGenerator);
+ return uuidGen.generateUUID().toString();
+}
+
+function* check_installed(inProfile, ...versions) {
+ let expectedDir;
+ if (inProfile) {
+ expectedDir = featureDir;
+ }
+ else {
+ expectedDir = distroDir.clone();
+ expectedDir.append("features");
+ }
+
+ for (let i = 0; i < versions.length; i++) {
+ let id = "system" + (i + 1) + "@tests.mozilla.org";
+ let addon = yield promiseAddonByID(id);
+
+ if (versions[i]) {
+ // Add-on should be installed
+ do_check_neq(addon, null);
+ do_check_eq(addon.version, versions[i]);
+ do_check_true(addon.isActive);
+ do_check_false(addon.foreignInstall);
+
+ // Verify the add-ons file is in the right place
+ let file = expectedDir.clone();
+ file.append(id + ".xpi");
+ do_check_true(file.exists());
+ do_check_true(file.isFile());
+
+ let uri = addon.getResourceURI(null);
+ do_check_true(uri instanceof AM_Ci.nsIFileURL);
+ do_check_eq(uri.file.path, file.path);
+
+ // Verify the add-on actually started
+ let installed = Services.prefs.getCharPref("bootstraptest." + id + ".active_version");
+ do_check_eq(installed, versions[i]);
+ }
+ else {
+ // Add-on should not be installed
+ do_check_eq(addon, null);
+
+ try {
+ Services.prefs.getCharPref("bootstraptest." + id + ".active_version");
+ do_throw("Expected pref to be missing");
+ }
+ catch (e) {
+ }
+ }
+ }
+}
+
+// Test with a missing features directory
+add_task(function* test_missing_app_dir() {
+ startupManager();
+
+ yield check_installed(false, null, null, null);
+
+ do_check_false(featureDir.exists());
+
+ yield promiseShutdownManager();
+});
+
+// Add some features in a new version
+add_task(function* test_new_version() {
+ gAppInfo.version = "1";
+ distroDir.leafName = "app1";
+ startupManager();
+
+ yield check_installed(false, "1.0", "1.0", null);
+
+ do_check_false(featureDir.exists());
+
+ yield promiseShutdownManager();
+});
+
+// Another new version swaps one feature and upgrades another
+add_task(function* test_upgrade() {
+ gAppInfo.version = "2";
+ distroDir.leafName = "app2";
+ startupManager();
+
+ yield check_installed(false, "2.0", null, "1.0");
+
+ do_check_false(featureDir.exists());
+
+ yield promiseShutdownManager();
+});
+
+// Downgrade
+add_task(function* test_downgrade() {
+ gAppInfo.version = "1";
+ distroDir.leafName = "app1";
+ startupManager();
+
+ yield check_installed(false, "1.0", "1.0", null);
+
+ do_check_false(featureDir.exists());
+
+ yield promiseShutdownManager();
+});
+
+// Fake a mid-cycle install
+add_task(function* test_updated() {
+ // Create a random dir to install into
+ let dirname = makeUUID();
+ FileUtils.getDir("ProfD", ["features", dirname], true);
+ featureDir.append(dirname);
+
+ // Copy in the system add-ons
+ let file = do_get_file("data/system_addons/app1/features/system2@tests.mozilla.org.xpi");
+ file.copyTo(featureDir, file.leafName);
+ file = do_get_file("data/system_addons/app2/features/system3@tests.mozilla.org.xpi");
+ file.copyTo(featureDir, file.leafName);
+
+ // Inject it into the system set
+ let addonSet = {
+ schema: 1,
+ directory: dirname,
+ addons: {
+ "system2@tests.mozilla.org": {
+ version: "1.0"
+ },
+ "system3@tests.mozilla.org": {
+ version: "1.0"
+ },
+ }
+ };
+ Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, JSON.stringify(addonSet));
+
+ startupManager(false);
+
+ yield check_installed(true, null, "1.0", "1.0");
+
+ yield promiseShutdownManager();
+});
+
+// An additional add-on in the directory should be ignored
+add_task(function* test_skips_additional() {
+ // Copy in the system add-ons
+ let file = do_get_file("data/system_addons/app1/features/system1@tests.mozilla.org.xpi");
+ file.copyTo(featureDir, file.leafName);
+
+ startupManager(false);
+
+ yield check_installed(true, null, "1.0", "1.0");
+
+ yield promiseShutdownManager();
+});
+
+// Missing add-on should revert to the default set
+add_task(function* test_revert() {
+ manuallyUninstall(featureDir, "system2@tests.mozilla.org");
+
+ startupManager(false);
+
+ // With system add-on 2 gone the updated set is now invalid so it reverts to
+ // the default set which is system add-ons 1 and 2.
+ yield check_installed(false, "1.0", "1.0", null);
+
+ yield promiseShutdownManager();
+});
+
+// Putting it back will make the set work again
+add_task(function* test_reuse() {
+ let file = do_get_file("data/system_addons/app1/features/system2@tests.mozilla.org.xpi");
+ file.copyTo(featureDir, file.leafName);
+
+ startupManager(false);
+
+ yield check_installed(true, null, "1.0", "1.0");
+
+ yield promiseShutdownManager();
+});
+
+// Making the pref corrupt should revert to the default set
+add_task(function* test_corrupt_pref() {
+ Services.prefs.setCharPref(PREF_SYSTEM_ADDON_SET, "foo");
+
+ startupManager(false);
+
+ yield check_installed(false, "1.0", "1.0", null);
+
+ yield promiseShutdownManager();
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
index 03041c8f9f1b..a87c4ac399e3 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -24,6 +24,7 @@ skip-if = appname != "firefox"
[test_provider_unsafe_access_shutdown.js]
[test_provider_unsafe_access_startup.js]
[test_shutdown.js]
+[test_system_reset.js]
[test_XPIcancel.js]
[test_XPIStates.js]