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]