author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Thu, 26 Nov 2015 11:57:05 +0100 | |
changeset 274192 | c321d84038519dcf1670d59fd2c5c00ad8a85a55 |
parent 274160 | 6beb4e02f810bbcecdd12f73de0008fb7f5dbfb5 (current diff) |
parent 274191 | 23eb26d374c6bb374cf7f2b23a77a7147f7a5371 (diff) |
child 274194 | 6924a22877b3ca56160aead6c59434824c23774c |
push id | 29724 |
push user | cbook@mozilla.com |
push date | Thu, 26 Nov 2015 10:57:21 +0000 |
treeherder | mozilla-central@c321d8403851 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 45.0a1 |
first release with | nightly linux32
c321d8403851
/
45.0a1
/
20151126030226
/
files
nightly linux64
c321d8403851
/
45.0a1
/
20151126030226
/
files
nightly mac
c321d8403851
/
45.0a1
/
20151126030226
/
files
nightly win32
c321d8403851
/
45.0a1
/
20151126030226
/
files
nightly win64
c321d8403851
/
45.0a1
/
20151126030226
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
45.0a1
/
20151126030226
/
pushlog to previous
nightly linux64
45.0a1
/
20151126030226
/
pushlog to previous
nightly mac
45.0a1
/
20151126030226
/
pushlog to previous
nightly win32
45.0a1
/
20151126030226
/
pushlog to previous
nightly win64
45.0a1
/
20151126030226
/
pushlog to previous
|
devtools/client/debugger/views/sources-view.js | file | annotate | diff | comparison | revisions | |
devtools/client/shared/redux/reducers.js | file | annotate | diff | comparison | revisions |
--- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -169,16 +169,17 @@ @RESPATH@/components/docshell.xpt @RESPATH@/components/dom.xpt @RESPATH@/components/dom_activities.xpt @RESPATH@/components/dom_apps.xpt @RESPATH@/components/dom_newapps.xpt @RESPATH@/components/dom_audiochannel.xpt @RESPATH@/components/dom_base.xpt @RESPATH@/components/dom_system.xpt +@RESPATH@/components/dom_workers.xpt #ifdef MOZ_WIDGET_GONK @RESPATH@/components/dom_wifi.xpt @RESPATH@/components/dom_system_gonk.xpt #endif #ifdef MOZ_B2G_RIL @RESPATH@/components/dom_wappush.xpt @RESPATH@/components/dom_mobileconnection.xpt #endif
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1473,24 +1473,24 @@ pref("network.disable.ipc.security", tru // CustomizableUI debug logging. pref("browser.uiCustomization.debug", false); // CustomizableUI state of the browser's user interface pref("browser.uiCustomization.state", ""); // The remote content URL shown for FxA signup. Must use HTTPS. -pref("identity.fxaccounts.remote.signup.uri", "https://accounts.firefox.com/signup?service=sync&context=fx_desktop_v1"); +pref("identity.fxaccounts.remote.signup.uri", "https://accounts.firefox.com/signup?service=sync&context=fx_desktop_v2"); // The URL where remote content that forces re-authentication for Firefox Accounts // should be fetched. Must use HTTPS. -pref("identity.fxaccounts.remote.force_auth.uri", "https://accounts.firefox.com/force_auth?service=sync&context=fx_desktop_v1"); +pref("identity.fxaccounts.remote.force_auth.uri", "https://accounts.firefox.com/force_auth?service=sync&context=fx_desktop_v2"); // The remote content URL shown for signin in. Must use HTTPS. -pref("identity.fxaccounts.remote.signin.uri", "https://accounts.firefox.com/signin?service=sync&context=fx_desktop_v1"); +pref("identity.fxaccounts.remote.signin.uri", "https://accounts.firefox.com/signin?service=sync&context=fx_desktop_v2"); // The remote content URL where FxAccountsWebChannel messages originate. pref("identity.fxaccounts.remote.webchannel.uri", "https://accounts.firefox.com/"); // The URL we take the user to when they opt to "manage" their Firefox Account. // Note that this will always need to be in the same TLD as the // "identity.fxaccounts.remote.signup.uri" pref. pref("identity.fxaccounts.settings.uri", "https://accounts.firefox.com/settings");
--- a/browser/components/distribution.js +++ b/browser/components/distribution.js @@ -197,16 +197,41 @@ DistributionCustomizer.prototype = { if (item.description) { let bmId = yield PlacesUtils.promiseItemId(bm.guid); PlacesUtils.annotations.setItemAnnotation(bmId, "bookmarkProperties/description", item.description, 0, PlacesUtils.annotations.EXPIRE_NEVER); } + if (item.icon && item.iconData) { + try { + let faviconURI = this._makeURI(item.icon); + PlacesUtils.favicons.replaceFaviconDataFromDataURL( + faviconURI, item.iconData, 0, + Services.scriptSecurityManager.getSystemPrincipal()); + + PlacesUtils.favicons.setAndFetchFaviconForPage( + this._makeURI(item.link), faviconURI, false, + PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null, + Services.scriptSecurityManager.getSystemPrincipal()); + } catch(e) { + Cu.reportError(e); + } + } + + if (item.keyword) { + try { + yield PlacesUtils.keywords.insert({ keyword: item.keyword, + url: item.link }); + } catch(e) { + Cu.reportError(e); + } + } + break; } } }), _customizationsApplied: false, applyCustomizations: function DIST_applyCustomizations() { this._customizationsApplied = true;
--- a/browser/components/extensions/ext-utils.js +++ b/browser/components/extensions/ext-utils.js @@ -1,105 +1,112 @@ /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"); Cu.import("resource://gre/modules/ExtensionUtils.jsm"); +Cu.import("resource://gre/modules/AddonManager.jsm"); + +const INTEGER = /^[1-9]\d*$/; + var { EventManager, } = ExtensionUtils; // This file provides some useful code for the |tabs| and |windows| // modules. All of the code is installed on |global|, which is a scope // shared among the different ext-*.js scripts. // Manages icon details for toolbar buttons in the |pageAction| and // |browserAction| APIs. global.IconDetails = { - // Accepted icon sizes. - SIZES: ["19", "38"], - // Normalizes the various acceptable input formats into an object - // with two properties, "19" and "38", containing icon URLs. + // with icon size as key and icon URL as value. + // + // If a context is specified (function is called from an extension): + // Throws an error if an invalid icon size was provided or the + // extension is not allowed to load the specified resources. + // + // If no context is specified, instead of throwing an error, this + // function simply logs a warning message. normalize(details, extension, context=null, localize=false) { let result = {}; - if (details.imageData) { - let imageData = details.imageData; + try { + if (details.imageData) { + let imageData = details.imageData; - if (imageData instanceof Cu.getGlobalForObject(imageData).ImageData) { - imageData = {"19": imageData}; - } + if (imageData instanceof Cu.getGlobalForObject(imageData).ImageData) { + imageData = {"19": imageData}; + } - for (let size of this.SIZES) { - if (size in imageData) { + for (let size of Object.keys(imageData)) { + if (!INTEGER.test(size)) { + throw new Error(`Invalid icon size ${size}, must be an integer`); + } + result[size] = this.convertImageDataToPNG(imageData[size], context); } } - } + + if (details.path) { + let path = details.path; + if (typeof path != "object") { + path = {"19": path}; + } - if (details.path) { - let path = details.path; - if (typeof path != "object") { - path = {"19": path}; - } + let baseURI = context ? context.uri : extension.baseURI; - let baseURI = context ? context.uri : extension.baseURI; + for (let size of Object.keys(path)) { + if (!INTEGER.test(size)) { + throw new Error(`Invalid icon size ${size}, must be an integer`); + } - for (let size of this.SIZES) { - if (size in path) { let url = path[size]; if (localize) { url = extension.localize(url); } url = baseURI.resolve(path[size]); // The Chrome documentation specifies these parameters as // relative paths. We currently accept absolute URLs as well, // which means we need to check that the extension is allowed - // to load them. - try { - Services.scriptSecurityManager.checkLoadURIStrWithPrincipal( - extension.principal, url, - Services.scriptSecurityManager.DISALLOW_SCRIPT); - } catch (e) { - if (context) { - throw e; - } - // If there's no context, it's because we're handling this - // as a manifest directive. Log a warning rather than - // raising an error, but don't accept the URL in any case. - extension.manifestError(`Access to URL '${url}' denied`); - continue; - } + // to load them. This will throw an error if it's not allowed. + Services.scriptSecurityManager.checkLoadURIStrWithPrincipal( + extension.principal, url, + Services.scriptSecurityManager.DISALLOW_SCRIPT); result[size] = url; } } + } catch (e) { + // Function is called from extension code, delegate error. + if (context) { + throw e; + } + // If there's no context, it's because we're handling this + // as a manifest directive. Log a warning rather than + // raising an error. + extension.manifestError(`Invalid icon data: ${e}`); } return result; }, // Returns the appropriate icon URL for the given icons object and the // screen resolution of the given window. getURL(icons, window, extension) { const DEFAULT = "chrome://browser/content/extension.svg"; - // Use the higher resolution image if we're doing any up-scaling - // for high resolution monitors. - let res = window.devicePixelRatio; - let size = res > 1 ? "38" : "19"; - - return icons[size] || icons["19"] || icons["38"] || DEFAULT; + return AddonManager.getPreferredIconURL({icons: icons}, 18, window) || DEFAULT; }, convertImageDataToPNG(imageData, context) { let document = context.contentWindow.document; let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); canvas.width = imageData.width; canvas.height = imageData.height; canvas.getContext("2d").putImageData(imageData, 0, 0);
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js +++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js @@ -96,16 +96,39 @@ add_task(function* testDetailsObjects() "1": browser.runtime.getURL("data/a.png"), "2": imageData.red.url, } }, { details: { "path": { "38": "a.png" }, "imageData": imageData.red.imageData, }, resolutions: { "1": imageData.red.url, "2": browser.runtime.getURL("data/a.png"), } }, + + // Various resolutions + { details: { "path": { "18": "a.png", "32": "a-x2.png" } }, + resolutions: { + "1": browser.runtime.getURL("data/a.png"), + "2": browser.runtime.getURL("data/a-x2.png"), } }, + { details: { "path": { "16": "16.png", "100": "100.png" } }, + resolutions: { + "1": browser.runtime.getURL("data/100.png"), + "2": browser.runtime.getURL("data/100.png"), } }, + { details: { "path": { "2": "2.png"} }, + resolutions: { + "1": browser.runtime.getURL("data/2.png"), + "2": browser.runtime.getURL("data/2.png"), } }, + { details: { "path": { + "6": "6.png", + "18": "18.png", + "32": "32.png", + "48": "48.png", + "128": "128.png" } }, + resolutions: { + "1": browser.runtime.getURL("data/18.png"), + "2": browser.runtime.getURL("data/48.png"), } }, ]; // Allow serializing ImageData objects for logging. ImageData.prototype.toJSON = () => "<ImageData>"; var tabId; browser.test.onMessage.addListener((msg, test) => { @@ -192,16 +215,72 @@ add_task(function* testDetailsObjects() let pageActionImage = document.getElementById(pageActionId); is(pageActionImage.src, imageURL, "page action has the correct image"); } yield extension.unload(); }); +// Test that an error is thrown when providing invalid icon sizes +add_task(function *testInvalidIconSizes() { + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + "browser_action": {}, + "page_action": {}, + }, + + background: function () { + browser.tabs.query({ active: true, currentWindow: true }, tabs => { + var tabId = tabs[0].id; + + for (var api of ["pageAction", "browserAction"]) { + // helper function to run setIcon and check if it fails + let assertSetIconThrows = function(detail, error, message) { + try { + detail.tabId = tabId; + browser[api].setIcon(detail); + + browser.test.fail("Expected an error on invalid icon size."); + browser.test.notifyFail("setIcon with invalid icon size"); + return; + } catch (e) { + browser.test.succeed("setIcon with invalid icon size"); + } + } + + // test invalid icon size inputs + for (var type of ["path", "imageData"]) { + assertSetIconThrows({ [type]: { "abcdef": "test.png" } }); + assertSetIconThrows({ [type]: { "48px": "test.png" } }); + assertSetIconThrows({ [type]: { "20.5": "test.png" } }); + assertSetIconThrows({ [type]: { "5.0": "test.png" } }); + assertSetIconThrows({ [type]: { "-300": "test.png" } }); + assertSetIconThrows({ [type]: { + "abc": "test.png", + "5": "test.png" + }}); + } + + assertSetIconThrows({ imageData: { "abcdef": "test.png" }, path: {"5": "test.png"} }); + assertSetIconThrows({ path: { "abcdef": "test.png" }, imageData: {"5": "test.png"} }); + } + + browser.test.notifyPass("setIcon with invalid icon size"); + }); + } + }); + + yield Promise.all([extension.startup(), extension.awaitFinish("setIcon with invalid icon size")]); + + yield extension.unload(); +}); + + // Test that default icon details in the manifest.json file are handled // correctly. add_task(function *testDefaultDetails() { // TODO: Test localized variants. let icons = [ "foo/bar.png", "/foo/bar.png", { "19": "foo/bar.png" }, @@ -289,32 +368,32 @@ add_task(function* testSecureURLsDenied( browser.test.notifyPass("setIcon security tests"); }); }, }); yield extension.startup(); - yield extension.awaitFinish(); + yield extension.awaitFinish("setIcon security tests"); yield extension.unload(); // Test URLs included in the manifest. let urls = ["chrome://browser/content/browser.xul", "javascript:true"]; let matchURLForbidden = url => ({ - message: new RegExp(`Loading extension.*Access to.*'${url}' denied`), + message: new RegExp(`Loading extension.*Invalid icon data: NS_ERROR_DOM_BAD_URI`), }); + // Because the underlying method throws an error on invalid data, + // only the first invalid URL of each component will be logged. let messages = [matchURLForbidden(urls[0]), - matchURLForbidden(urls[1]), - matchURLForbidden(urls[0]), matchURLForbidden(urls[1])]; let waitForConsole = new Promise(resolve => { // Not necessary in browser-chrome tests, but monitorConsole gripes // if we don't call it. SimpleTest.waitForExplicitFinish(); SimpleTest.monitorConsole(resolve, messages); @@ -325,18 +404,18 @@ add_task(function* testSecureURLsDenied( "browser_action": { "default_icon": { "19": urls[0], "38": urls[1], }, }, "page_action": { "default_icon": { - "19": urls[0], - "38": urls[1], + "19": urls[1], + "38": urls[0], }, }, }, }); yield extension.startup(); yield extension.unload();
--- a/browser/components/loop/content/js/panel.js +++ b/browser/components/loop/content/js/panel.js @@ -435,17 +435,26 @@ loop.panel = (function(_, mozL10n) { }, handleClickEntry: function(event) { event.preventDefault(); this.props.dispatcher.dispatch(new sharedActions.OpenRoom({ roomToken: this.props.room.roomToken })); - this.closeWindow(); + + // Open url if needed. + loop.request("getSelectedTabMetadata").then(function(metadata) { + var contextURL = this.props.room.decryptedContext.urls && + this.props.room.decryptedContext.urls[0].location; + if (contextURL && metadata.url !== contextURL) { + loop.request("OpenURL", contextURL); + } + this.closeWindow(); + }.bind(this)); }, handleClick: function(e) { e.preventDefault(); e.stopPropagation(); this.setState({ eventPosY: e.pageY
--- a/browser/components/loop/content/js/panel.jsx +++ b/browser/components/loop/content/js/panel.jsx @@ -435,17 +435,26 @@ loop.panel = (function(_, mozL10n) { }, handleClickEntry: function(event) { event.preventDefault(); this.props.dispatcher.dispatch(new sharedActions.OpenRoom({ roomToken: this.props.room.roomToken })); - this.closeWindow(); + + // Open url if needed. + loop.request("getSelectedTabMetadata").then(function(metadata) { + var contextURL = this.props.room.decryptedContext.urls && + this.props.room.decryptedContext.urls[0].location; + if (contextURL && metadata.url !== contextURL) { + loop.request("OpenURL", contextURL); + } + this.closeWindow(); + }.bind(this)); }, handleClick: function(e) { e.preventDefault(); e.stopPropagation(); this.setState({ eventPosY: e.pageY
--- a/browser/components/loop/test/desktop-local/panel_test.js +++ b/browser/components/loop/test/desktop-local/panel_test.js @@ -77,17 +77,20 @@ describe("loop.panel", function() { GetUserProfile: function() { return null; } }); roomName = "First Room Name"; roomData = { roomToken: "QzBbvGmIZWU", roomUrl: "http://sample/QzBbvGmIZWU", decryptedContext: { - roomName: roomName + roomName: roomName, + urls: [{ + location: "http://testurl.com" + }] }, maxSize: 2, participants: [{ displayName: "Alexis", account: "alexis@example.com", roomConnectionId: "2a1787a6-4a73-43b5-ae3e-906ec1e763cb" }, { displayName: "Adam", @@ -660,22 +663,33 @@ describe("loop.panel", function() { TestUtils.Simulate.click(node, fakeEvent); expect(view.state.showMenu).to.eql(!prevState); }); }); describe("Copy button", function() { - var roomEntry; + var roomEntry, openURLStub; beforeEach(function() { // Stub to prevent warnings where no stores are set up to handle the // actions we are testing. sandbox.stub(dispatcher, "dispatch"); + openURLStub = sinon.stub(); + + LoopMochaUtils.stubLoopRequest({ + GetSelectedTabMetadata: function() { + return { + url: "http://invalid.com", + description: "fakeSite" + }; + }, + OpenURL: openURLStub + }); roomEntry = mountRoomEntry({ deleteRoom: sandbox.stub(), isOpenedRoom: false, room: new loop.store.Room(roomData) }); }); @@ -712,16 +726,42 @@ describe("loop.panel", function() { isOpenedRoom: true, room: new loop.store.Room(roomData) }); TestUtils.Simulate.click(roomEntry.refs.roomEntry.getDOMNode()); sinon.assert.notCalled(dispatcher.dispatch); }); + + it("should open a new tab with the room context if it is not the same as the currently open tab", function() { + TestUtils.Simulate.click(roomEntry.refs.roomEntry.getDOMNode()); + sinon.assert.calledOnce(openURLStub); + sinon.assert.calledWithExactly(openURLStub, "http://testurl.com"); + }); + + it("should not open a new tab if the context is the same as the currently open tab", function() { + LoopMochaUtils.stubLoopRequest({ + GetSelectedTabMetadata: function() { + return { + url: "http://testurl.com", + description: "fakeSite" + }; + } + }); + + roomEntry = mountRoomEntry({ + deleteRoom: sandbox.stub(), + isOpenedRoom: false, + room: new loop.store.Room(roomData) + }); + + TestUtils.Simulate.click(roomEntry.refs.roomEntry.getDOMNode()); + sinon.assert.notCalled(openURLStub); + }); }); }); describe("Context Indicator", function() { var roomEntry; function mountEntryForContext() { return mountRoomEntry({
--- a/browser/components/migration/EdgeProfileMigrator.js +++ b/browser/components/migration/EdgeProfileMigrator.js @@ -2,24 +2,26 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ 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/AppConstants.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); Cu.import("resource:///modules/MigrationUtils.jsm"); Cu.import("resource:///modules/MSMigrationUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); const kEdgeRegistryRoot = "SOFTWARE\\Classes\\Local Settings\\Software\\" + "Microsoft\\Windows\\CurrentVersion\\AppContainer\\Storage\\" + "microsoft.microsoftedge_8wekyb3d8bbwe\\MicrosoftEdge"; +const kEdgeReadingListPath = "AC\\MicrosoftEdge\\User\\Default\\DataStore\\Data\\"; function EdgeTypedURLMigrator() { } EdgeTypedURLMigrator.prototype = { type: MigrationUtils.resourceTypes.HISTORY, get _typedURLs() { @@ -74,26 +76,133 @@ EdgeTypedURLMigrator.prototype = { handleError: function() {}, handleCompletion: function() { aCallback(this._success); } }); }, } +function EdgeReadingListMigrator() { +} + +EdgeReadingListMigrator.prototype = { + type: MigrationUtils.resourceTypes.BOOKMARKS, + + get exists() { + return !!MSMigrationUtils.getEdgeLocalDataFolder(); + }, + + migrate(callback) { + this._migrateReadingList(PlacesUtils.bookmarks.menuGuid).then( + () => callback(true), + ex => { + Cu.reportError(ex); + callback(false); + } + ); + }, + + _migrateReadingList: Task.async(function*(parentGuid) { + let edgeDir = MSMigrationUtils.getEdgeLocalDataFolder(); + if (!edgeDir) { + return; + } + this._readingListExtractor = Cc["@mozilla.org/profile/migrator/edgereadinglistextractor;1"]. + createInstance(Ci.nsIEdgeReadingListExtractor); + edgeDir.appendRelativePath(kEdgeReadingListPath); + let errorProduced = null; + if (edgeDir.exists() && edgeDir.isReadable() && edgeDir.isDirectory()) { + let expectedDir = edgeDir.clone(); + expectedDir.appendRelativePath("nouser1\\120712-0049"); + if (expectedDir.exists() && expectedDir.isReadable() && expectedDir.isDirectory()) { + yield this._migrateReadingListDB(expectedDir, parentGuid).catch(ex => { + if (!errorProduced) + errorProduced = ex; + }); + } else { + let getSubdirs = someDir => { + let subdirs = someDir.directoryEntries; + let rv = []; + while (subdirs.hasMoreElements()) { + let subdir = subdirs.getNext().QueryInterface(Ci.nsIFile); + if (subdir.isDirectory() && subdir.isReadable()) { + rv.push(subdir); + } + } + return rv; + }; + let dirs = getSubdirs(edgeDir).map(getSubdirs); + for (let dir of dirs) { + yield this._migrateReadingListDB(dir, parentGuid).catch(ex => { + if (!errorProduced) + errorProduced = ex; + }); + } + } + } + if (errorProduced) { + throw errorProduced; + } + }), + _migrateReadingListDB: Task.async(function*(dbFile, parentGuid) { + dbFile.appendRelativePath("DBStore\\spartan.edb"); + + if (!dbFile.exists() || !dbFile.isReadable() || !dbFile.isFile()) { + return; + } + let readingListItems; + try { + readingListItems = this._readingListExtractor.extract(dbFile.path); + } catch (ex) { + Cu.reportError("Failed to extract Edge reading list information from " + + "the database at " + dbFile.path + " due to the following error: " + ex); + // Deliberately make this fail so we expose failure in the UI: + throw ex; + return; + } + if (!readingListItems.length) { + return; + } + let destFolderGuid = yield this._ensureReadingListFolder(parentGuid); + for (let i = 0; i < readingListItems.length; i++) { + let readingListItem = readingListItems.queryElementAt(i, Ci.nsIPropertyBag2); + let url = readingListItem.get("uri"); + let title = readingListItem.get("title"); + let time = readingListItem.get("time"); + // time is a PRTime, which is microseconds (since unix epoch), or null. + // We need milliseconds for the date constructor, so divide by 1000: + let dateAdded = time ? new Date(time / 1000) : new Date(); + yield PlacesUtils.bookmarks.insert({ + parentGuid: destFolderGuid, url: url, title, dateAdded + }); + } + }), + + _ensureReadingListFolder: Task.async(function*(parentGuid) { + if (!this.__readingListFolderGuid) { + let folderTitle = MigrationUtils.getLocalizedString("importedEdgeReadingList"); + let folderSpec = {type: PlacesUtils.bookmarks.TYPE_FOLDER, parentGuid, title: folderTitle}; + this.__readingListFolderGuid = (yield PlacesUtils.bookmarks.insert(folderSpec)).guid; + } + return this.__readingListFolderGuid; + }), +}; + function EdgeProfileMigrator() { } EdgeProfileMigrator.prototype = Object.create(MigratorPrototype); EdgeProfileMigrator.prototype.getResources = function() { let resources = [ MSMigrationUtils.getBookmarksMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE), MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE), new EdgeTypedURLMigrator(), + new EdgeReadingListMigrator(), ]; let windowsVaultFormPasswordsMigrator = MSMigrationUtils.getWindowsVaultFormPasswordsMigrator(); windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords"; resources.push(windowsVaultFormPasswordsMigrator); return resources.filter(r => r.exists); };
--- a/browser/components/migration/MSMigrationUtils.jsm +++ b/browser/components/migration/MSMigrationUtils.jsm @@ -21,17 +21,16 @@ XPCOMUtils.defineLazyModuleGetter(this, XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry", "resource://gre/modules/WindowsRegistry.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ctypes", "resource://gre/modules/ctypes.jsm"); const EDGE_COOKIE_PATH_OPTIONS = ["", "#!001\\", "#!002\\"]; const EDGE_COOKIES_SUFFIX = "MicrosoftEdge\\Cookies"; const EDGE_FAVORITES = "AC\\MicrosoftEdge\\User\\Default\\Favorites"; -const EDGE_READINGLIST = "AC\\MicrosoftEdge\\User\\Default\\DataStore\\Data\\"; const FREE_CLOSE_FAILED = 0; const INTERNET_EXPLORER_EDGE_GUID = [0x3CCD5499, 0x4B1087A8, 0x886015A2, 0x553BDD88]; const RESULT_SUCCESS = 0; const VAULT_ENUMERATE_ALL_ITEMS = 512; const WEB_CREDENTIALS_VAULT_ID = [0x4BF4C442, @@ -371,30 +370,27 @@ Bookmarks.prototype = { return Task.spawn(function* () { // Import to the bookmarks menu. let folderGuid = PlacesUtils.bookmarks.menuGuid; if (!MigrationUtils.isStartupMigration) { folderGuid = yield MigrationUtils.createImportedBookmarksFolder(this.importedAppLabel, folderGuid); } yield this._migrateFolder(this._favoritesFolder, folderGuid); - - if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE) { - yield this._migrateEdgeReadingList(PlacesUtils.bookmarks.menuGuid); - } }.bind(this)).then(() => aCallback(true), e => { Cu.reportError(e); aCallback(false) }); }, _migrateFolder: Task.async(function* (aSourceFolder, aDestFolderGuid) { // TODO (bug 741993): the favorites order is stored in the Registry, at // HCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MenuOrder\Favorites // for IE, and in a similar location for Edge. // Until we support it, bookmarks are imported in alphabetical order. let entries = aSourceFolder.directoryEntries; + let succeeded = true; while (entries.hasMoreElements()) { let entry = entries.getNext().QueryInterface(Ci.nsIFile); try { // Make sure that entry.path == entry.target to not follow .lnk folder // shortcuts which could lead to infinite cycles. // Don't use isSymlink(), since it would throw for invalid // lnk files pointing to URLs or to unresolvable paths. if (entry.path == entry.target && entry.isDirectory()) { @@ -434,92 +430,24 @@ Bookmarks.prototype = { yield PlacesUtils.bookmarks.insert({ parentGuid: aDestFolderGuid, url: uri, title }); } } } catch (ex) { Components.utils.reportError("Unable to import " + this.importedAppLabel + " favorite (" + entry.leafName + "): " + ex); + succeeded = false; } } + if (!succeeded) { + throw new Error("Failed to import all bookmarks correctly."); + } }), - _migrateEdgeReadingList: Task.async(function*(parentGuid) { - let edgeDir = getEdgeLocalDataFolder(); - if (!edgeDir) { - return; - } - - this._readingListExtractor = Cc["@mozilla.org/profile/migrator/edgereadinglistextractor;1"]. - createInstance(Ci.nsIEdgeReadingListExtractor); - edgeDir.appendRelativePath(EDGE_READINGLIST); - if (edgeDir.exists() && edgeDir.isReadable() && edgeDir.isDirectory()) { - let expectedDir = edgeDir.clone(); - expectedDir.appendRelativePath("nouser1\\120712-0049"); - if (expectedDir.exists() && expectedDir.isReadable() && expectedDir.isDirectory()) { - yield this._migrateEdgeReadingListDB(expectedDir, parentGuid); - } else { - let getSubdirs = someDir => { - let subdirs = someDir.directoryEntries; - let rv = []; - while (subdirs.hasMoreElements()) { - let subdir = subdirs.getNext().QueryInterface(Ci.nsIFile); - if (subdir.isDirectory() && subdir.isReadable()) { - rv.push(subdir); - } - } - return rv; - }; - let dirs = getSubdirs(edgeDir).map(getSubdirs); - for (let dir of dirs) { - yield this._migrateEdgeReadingListDB(dir, parentGuid); - } - } - } - }), - _migrateEdgeReadingListDB: Task.async(function*(dbFile, parentGuid) { - dbFile.appendRelativePath("DBStore\\spartan.edb"); - if (!dbFile.exists() || !dbFile.isReadable() || !dbFile.isFile()) { - return; - } - let readingListItems; - try { - readingListItems = this._readingListExtractor.extract(dbFile.path); - } catch (ex) { - Cu.reportError("Failed to extract Edge reading list information from " + - "the database at " + dbPath + " due to the following error: " + ex); - return; - } - if (!readingListItems.length) { - return; - } - let destFolderGuid = yield this._ensureEdgeReadingListFolder(parentGuid); - for (let i = 0; i < readingListItems.length; i++) { - let readingListItem = readingListItems.queryElementAt(i, Ci.nsIPropertyBag2); - let url = readingListItem.get("uri"); - let title = readingListItem.get("title"); - let time = readingListItem.get("time"); - // time is a PRTime, which is microseconds (since unix epoch), or null. - // We need milliseconds for the date constructor, so divide by 1000: - let dateAdded = time ? new Date(time / 1000) : new Date(); - yield PlacesUtils.bookmarks.insert({ - parentGuid: destFolderGuid, url: url, title, dateAdded - }); - } - }), - - _ensureEdgeReadingListFolder: Task.async(function*(parentGuid) { - if (!this.__edgeReadingListFolderGuid) { - let folderTitle = MigrationUtils.getLocalizedString("importedEdgeReadingList"); - let folderSpec = {type: PlacesUtils.bookmarks.TYPE_FOLDER, parentGuid, title: folderTitle}; - this.__edgeReadingListFolderGuid = (yield PlacesUtils.bookmarks.insert(folderSpec)).guid; - } - return this.__edgeReadingListFolderGuid; - }), }; function Cookies(migrationType) { this._migrationType = migrationType; } Cookies.prototype = { type: MigrationUtils.resourceTypes.COOKIES, @@ -943,9 +871,10 @@ var MSMigrationUtils = { }, getCookiesMigrator(migrationType = this.MIGRATION_TYPE_IE) { return new Cookies(migrationType); }, getWindowsVaultFormPasswordsMigrator() { return new WindowsVaultFormPasswords(); }, getTypedURLs, + getEdgeLocalDataFolder, };
--- a/browser/components/migration/nsEdgeReadingListExtractor.cpp +++ b/browser/components/migration/nsEdgeReadingListExtractor.cpp @@ -192,13 +192,19 @@ nsEdgeReadingListExtractor::ConvertJETEr return NS_ERROR_FILE_IS_LOCKED; case JET_errPermissionDenied: case JET_errAccessDenied: return NS_ERROR_FILE_ACCESS_DENIED; case JET_errInvalidFilename: return NS_ERROR_FILE_INVALID_PATH; case JET_errFileNotFound: return NS_ERROR_FILE_NOT_FOUND; + case JET_errDatabaseDirtyShutdown: + return NS_ERROR_FILE_CORRUPTED; default: + nsCOMPtr<nsIConsoleService> consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID); + wchar_t* msg = new wchar_t[80]; + swprintf(msg, 80, MOZ_UTF16("Unexpected JET error from ESE database: %ld"), aError); + consoleService->LogStringMessage(msg); return NS_ERROR_FAILURE; } }
--- a/browser/components/places/tests/unit/distribution.ini +++ b/browser/components/places/tests/unit/distribution.ini @@ -3,19 +3,25 @@ [Global] id=516444 version=1.0 about=Test distribution file [BookmarksToolbar] item.1.title=Toolbar Link Before -item.1.link=http://mozilla.com/ +item.1.link=https://example.org/toolbar/before/ +item.1.keyword=e:t:b +item.1.icon=https://example.org/favicon.png +item.1.iconData=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg== item.2.type=default item.3.title=Toolbar Link After -item.3.link=http://mozilla.com/ +item.3.link=https://example.org/toolbar/after/ +item.3.keyword=e:t:a [BookmarksMenu] item.1.title=Menu Link Before -item.1.link=http://mozilla.com/ +item.1.link=https://example.org/menu/before/ +item.1.icon=https://example.org/favicon.png +item.1.iconData=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg== item.2.type=default item.3.title=Menu Link After -item.3.link=http://mozilla.com/ \ No newline at end of file +item.3.link=https://example.org/menu/after/
--- a/browser/components/places/tests/unit/head_bookmarks.js +++ b/browser/components/places/tests/unit/head_bookmarks.js @@ -114,8 +114,39 @@ function rebuildSmartBookmarks() { }); Cc["@mozilla.org/browser/browserglue;1"] .getService(Ci.nsIObserver) .observe(null, "browser-glue-test", "smart-bookmarks-init"); return promiseTopicObserved("test-smart-bookmarks-done").then(() => { Services.console.unregisterListener(consoleListener); }); } + +const SINGLE_TRY_TIMEOUT = 100; +const NUMBER_OF_TRIES = 30; + +/** + * Similar to waitForConditionPromise, but poll for an asynchronous value + * every SINGLE_TRY_TIMEOUT ms, for no more than tryCount times. + * + * @param promiseFn + * A function to generate a promise, which resolves to the expected + * asynchronous value. + * @param timeoutMsg + * The reason to reject the returned promise with. + * @param [optional] tryCount + * Maximum times to try before rejecting the returned promise with + * timeoutMsg, defaults to NUMBER_OF_TRIES. + * @return {Promise} + * @resolves to the asynchronous value being polled. + * @rejects if the asynchronous value is not available after tryCount attempts. + */ +var waitForResolvedPromise = Task.async(function* (promiseFn, timeoutMsg, tryCount=NUMBER_OF_TRIES) { + let tries = 0; + do { + try { + let value = yield promiseFn(); + return value; + } catch (ex) {} + yield new Promise(resolve => do_timeout(SINGLE_TRY_TIMEOUT, resolve)); + } while (++tries <= tryCount); + throw(timeoutMsg); +});
--- a/browser/components/places/tests/unit/test_browserGlue_distribution.js +++ b/browser/components/places/tests/unit/test_browserGlue_distribution.js @@ -71,23 +71,51 @@ add_task(function* () { Assert.equal(menuItem.title, "Menu Link Before"); menuItem = yield PlacesUtils.bookmarks.fetch({ parentGuid: PlacesUtils.bookmarks.menuGuid, index: 1 + DEFAULT_BOOKMARKS_ON_MENU }); Assert.equal(menuItem.title, "Menu Link After"); + // Check no favicon or keyword exists for this bookmark + yield Assert.rejects(waitForResolvedPromise(() => { + return PlacesUtils.promiseFaviconData(menuItem.url.href); + }, "Favicon not found", 10), /Favicon\snot\sfound/, "Favicon not found"); + + let keywordItem = yield PlacesUtils.keywords.fetch({ + url: menuItem.url.href + }); + Assert.strictEqual(keywordItem, null); + // Check the custom bookmarks exist on toolbar. let toolbarItem = yield PlacesUtils.bookmarks.fetch({ parentGuid: PlacesUtils.bookmarks.toolbarGuid, index: 0 }); Assert.equal(toolbarItem.title, "Toolbar Link Before"); + // Check the custom favicon and keyword exist for this bookmark + let faviconItem = yield waitForResolvedPromise(() => { + return PlacesUtils.promiseFaviconData(toolbarItem.url.href); + }, "Favicon not found", 10); + Assert.equal(faviconItem.uri.spec, "https://example.org/favicon.png"); + Assert.greater(faviconItem.dataLen, 0); + Assert.equal(faviconItem.mimeType, "image/png"); + + let base64Icon = "data:image/png;base64," + + base64EncodeString(String.fromCharCode.apply(String, faviconItem.data)); + Assert.equal(base64Icon, SMALLPNG_DATA_URI.spec); + + keywordItem = yield PlacesUtils.keywords.fetch({ + url: toolbarItem.url.href + }); + Assert.notStrictEqual(keywordItem, null); + Assert.equal(keywordItem.keyword, "e:t:b"); + toolbarItem = yield PlacesUtils.bookmarks.fetch({ parentGuid: PlacesUtils.bookmarks.toolbarGuid, index: 1 + DEFAULT_BOOKMARKS_ON_TOOLBAR }); Assert.equal(toolbarItem.title, "Toolbar Link After"); // Check the bmprocessed pref has been created. Assert.ok(Services.prefs.getBoolPref(PREF_BMPROCESSED));
--- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -1245,32 +1245,45 @@ var SessionStoreInternal = { // Store the window's close date to figure out when each individual tab // was closed. This timestamp should allow re-arranging data based on how // recently something was closed. winData.closedAt = Date.now(); // we don't want to save the busy state delete winData.busy; + // When closing windows one after the other until Firefox quits, we + // will move those closed in series back to the "open windows" bucket + // before writing to disk. If however there is only a single window + // with tabs we deem not worth saving then we might end up with a + // random closed or even a pop-up window re-opened. To prevent that + // we explicitly allow saving an "empty" window state. + let isLastWindow = + Object.keys(this._windows).length == 1 && + !this._closedWindows.some(win => win._shouldRestore || false); + + // clear this window from the list, since it has definitely been closed. + delete this._windows[aWindow.__SSi]; + // Now we have to figure out if this window is worth saving in the _closedWindows // Object. // // We're about to flush the tabs from this window, but it's possible that we // might never hear back from the content process(es) in time before the user // chooses to restore the closed window. So we do the following: // // 1) Use the tab state cache to determine synchronously if the window is // worth stashing in _closedWindows. // 2) Flush the window. // 3) When the flush is complete, revisit our decision to store the window // in _closedWindows, and add/remove as necessary. if (!winData.isPrivate) { // Remove any open private tabs the window may contain. PrivacyFilter.filterPrivateTabs(winData); - this.maybeSaveClosedWindow(winData); + this.maybeSaveClosedWindow(winData, isLastWindow); } // The tabbrowser binding will go away once the window is closed, // so we'll hold a reference to the browsers in the closure here. let browsers = tabbrowser.browsers; TabStateFlusher.flushWindow(aWindow).then(() => { // At this point, aWindow is closed! You should probably not try to @@ -1287,38 +1300,35 @@ var SessionStoreInternal = { } // Save non-private windows if they have at // least one saveable tab or are the last window. if (!winData.isPrivate) { // It's possible that a tab switched its privacy state at some point // before our flush, so we need to filter again. PrivacyFilter.filterPrivateTabs(winData); - this.maybeSaveClosedWindow(winData); + this.maybeSaveClosedWindow(winData, isLastWindow); } - // clear this window from the list - delete this._windows[aWindow.__SSi]; // Update the tabs data now that we've got the most // recent information. this.cleanUpWindow(aWindow, winData); // save the state without this window to disk this.saveStateDelayed(); }); } else { this.cleanUpWindow(aWindow, winData); } for (let i = 0; i < tabbrowser.tabs.length; i++) { this.onTabRemove(aWindow, tabbrowser.tabs[i], true); } }, - /** * Clean up the message listeners on a window that has finally * gone away. Call this once you're sure you don't want to hear * from any of this windows tabs from here forward. * * @param aWindow * The browser window we're cleaning up. * @param winData @@ -1340,32 +1350,29 @@ var SessionStoreInternal = { * Decides whether or not a closed window should be put into the * _closedWindows Object. This might be called multiple times per * window, and will do the right thing of moving the window data * in or out of _closedWindows if the winData indicates that our * need for saving it has changed. * * @param winData * The data for the closed window that we might save. + * @param isLastWindow + * Whether or not the window being closed is the last + * browser window. Callers of this function should pass + * in the value of SessionStoreInternal.atLastWindow for + * this argument, and pass in the same value if they happen + * to call this method again asynchronously (for example, after + * a window flush). */ - maybeSaveClosedWindow(winData) { + maybeSaveClosedWindow(winData, isLastWindow) { if (RunState.isRunning) { // Determine whether the window has any tabs worth saving. let hasSaveableTabs = winData.tabs.some(this._shouldSaveTabState); - // When closing windows one after the other until Firefox quits, we - // will move those closed in series back to the "open windows" bucket - // before writing to disk. If however there is only a single window - // with tabs we deem not worth saving then we might end up with a - // random closed or even a pop-up window re-opened. To prevent that - // we explicitly allow saving an "empty" window state. - let isLastWindow = - Object.keys(this._windows).length == 1 && - !this._closedWindows.some(win => win._shouldRestore || false); - // Note that we might already have this window stored in // _closedWindows from a previous call to this function. let winIndex = this._closedWindows.indexOf(winData); let alreadyStored = (winIndex != -1); let shouldStore = (hasSaveableTabs || isLastWindow); if (shouldStore && !alreadyStored) { let index = this._closedWindows.findIndex(win => {
--- a/browser/components/sessionstore/test/browser.ini +++ b/browser/components/sessionstore/test/browser.ini @@ -212,8 +212,9 @@ skip-if = true # Disabled on OS X: [browser_625016.js] skip-if = os == "mac" [browser_911547.js] [browser_send_async_message_oom.js] [browser_multiple_navigateAndRestore.js] run-if = e10s +[browser_async_window_flushing.js]
new file mode 100644 --- /dev/null +++ b/browser/components/sessionstore/test/browser_async_window_flushing.js @@ -0,0 +1,38 @@ +/** + * Tests that when we close a window, it is immediately removed from the + * _windows array. + */ +add_task(function* test_synchronously_remove_window_state() { + // Depending on previous tests, we might already have some closed + // windows stored. We'll use its length to determine whether or not + // the window was added or not. + let state = JSON.parse(ss.getBrowserState()); + ok(state, "Make sure we can get the state"); + let initialWindows = state.windows.length; + + // Open a new window and send the first tab somewhere + // interesting. + let newWin = yield BrowserTestUtils.openNewBrowserWindow(); + let browser = newWin.gBrowser.selectedBrowser; + browser.loadURI("http://example.com"); + yield BrowserTestUtils.browserLoaded(browser); + yield TabStateFlusher.flush(browser); + + state = JSON.parse(ss.getBrowserState()); + is(state.windows.length, initialWindows + 1, + "The new window to be in the state"); + + // Now close the window, and make sure that the window was removed + // from the windows list from the SessionState. We're specifically + // testing the case where the window is _not_ removed in between + // the close-initiated flush request and the flush response. + let windowClosed = BrowserTestUtils.windowClosed(newWin); + newWin.close(); + + state = JSON.parse(ss.getBrowserState()); + is(state.windows.length, initialWindows, + "The new window should have been removed from the state"); + + // Wait for our window to go away + yield windowClosed; +});
--- a/devtools/client/aboutdebugging/aboutdebugging.css +++ b/devtools/client/aboutdebugging/aboutdebugging.css @@ -7,17 +7,18 @@ html, body { width: 100%; } h2, h3, h4 { margin-bottom: 5px; } button { - width: 100px; + padding-left: 20px; + padding-right: 20px; } #body { display: flex; flex-direction: row; } /* Category tabs */ @@ -75,8 +76,17 @@ label { .inverted-icons .target-icon { filter: invert(30%); } .target-details { flex: 1; } + +.addon-controls { + display: flex; + flex-direction: row; +} + +.addon-options { + flex: 1; +}
--- a/devtools/client/aboutdebugging/aboutdebugging.js +++ b/devtools/client/aboutdebugging/aboutdebugging.js @@ -3,32 +3,41 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* eslint-env browser */ /* global AddonsComponent, DebuggerClient, DebuggerServer, React, RuntimesComponent, WorkersComponent */ "use strict"; +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; const { loader } = Components.utils.import( "resource://devtools/shared/Loader.jsm", {}); loader.lazyRequireGetter(this, "AddonsComponent", "devtools/client/aboutdebugging/components/addons", true); loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true); loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true); loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry"); loader.lazyRequireGetter(this, "WorkersComponent", "devtools/client/aboutdebugging/components/workers", true); loader.lazyRequireGetter(this, "Services"); +loader.lazyImporter(this, "AddonManager", + "resource://gre/modules/AddonManager.jsm"); + +const Strings = Services.strings.createBundle( + "chrome://devtools/locale/aboutdebugging.properties"); + var AboutDebugging = { + _prefListeners: [], + _categories: null, get categories() { // If needed, initialize the list of available categories. if (!this._categories) { let elements = document.querySelectorAll(".category"); this._categories = Array.map(elements, element => { let value = element.getAttribute("value"); element.addEventListener("click", this.showTab.bind(this, value)); @@ -67,43 +76,79 @@ var AboutDebugging = { // Link checkboxes to prefs. let elements = document.querySelectorAll("input[type=checkbox][data-pref]"); Array.map(elements, element => { let pref = element.dataset.pref; let updatePref = () => { Services.prefs.setBoolPref(pref, element.checked); }; + element.addEventListener("change", updatePref, false); let updateCheckbox = () => { element.checked = Services.prefs.getBoolPref(pref); }; - element.addEventListener("change", updatePref, false); Services.prefs.addObserver(pref, updateCheckbox, false); + this._prefListeners.push([pref, updateCheckbox]); updateCheckbox(); }); + // Link buttons to their associated actions. + let loadAddonButton = document.getElementById("load-addon-from-file"); + loadAddonButton.addEventListener("click", this.loadAddonFromFile); + if (!DebuggerServer.initialized) { DebuggerServer.init(); DebuggerServer.addBrowserActors(); } DebuggerServer.allowChromeProcess = true; let client = new DebuggerClient(DebuggerServer.connectPipe()); client.connect(() => { React.render(React.createElement(AddonsComponent, { client }), document.querySelector("#addons")); React.render(React.createElement(WorkersComponent, { client }), document.querySelector("#workers")); }); }, + loadAddonFromFile() { + let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); + fp.init(window, + Strings.GetStringFromName("selectAddonFromFile"), + Ci.nsIFilePicker.modeOpen); + let res = fp.show(); + if (res == Ci.nsIFilePicker.returnCancel || !fp.file) { + return; + } + let file = fp.file; + // AddonManager.installTemporaryAddon accepts either + // addon directory or final xpi file. + if (!file.isDirectory() && !file.leafName.endsWith(".xpi")) { + file = file.parent; + } + try { + AddonManager.installTemporaryAddon(file); + } catch(e) { + alert("Error while installing the addon:\n" + e.message + "\n"); + throw e; + } + }, + destroy() { let telemetry = this._telemetry; telemetry.toolClosed("aboutdebugging"); telemetry.destroy(); + + this._prefListeners.forEach(([pref, listener]) => { + Services.prefs.removeObserver(pref, listener); + }); + this._prefListeners = []; + + React.unmountComponentAtNode(document.querySelector("#addons")); + React.unmountComponentAtNode(document.querySelector("#workers")); }, }; window.addEventListener("DOMContentLoaded", function load() { window.removeEventListener("DOMContentLoaded", load); AboutDebugging.init(); });
--- a/devtools/client/aboutdebugging/aboutdebugging.xhtml +++ b/devtools/client/aboutdebugging/aboutdebugging.xhtml @@ -29,18 +29,23 @@ <div class="category-name">&aboutDebugging.workers;</div> </div> </div> <div class="main-content"> <div id="tab-addons" class="tab active"> <div class="header"> <h1 class="header-name">&aboutDebugging.addons;</h1> </div> - <input id="enable-addon-debugging" type="checkbox" data-pref="devtools.chrome.enabled"/> - <label for="enable-addon-debugging" title="&aboutDebugging.addonDebugging.tooltip;">&aboutDebugging.addonDebugging.label;</label> + <div class="addon-controls"> + <div class="addon-options"> + <input id="enable-addon-debugging" type="checkbox" data-pref="devtools.chrome.enabled"/> + <label for="enable-addon-debugging" title="&aboutDebugging.addonDebugging.tooltip;">&aboutDebugging.addonDebugging.label;</label> + </div> + <button id="load-addon-from-file">&aboutDebugging.loadTemporaryAddon;</button> + </div> <div id="addons"></div> </div> <div id="tab-workers" class="tab"> <div class="header"> <h1 class="header-name">&aboutDebugging.workers;</h1> </div> <input id="enable-worker-debugging" type="checkbox" data-pref="devtools.debugger.workers"/> <label for="enable-worker-debugging" title="&options.enableWorkers.tooltip;">&options.enableWorkers.label;</label>
copy from devtools/client/locales/en-US/aboutdebugging.properties copy to devtools/client/aboutdebugging/moz.build --- a/devtools/client/locales/en-US/aboutdebugging.properties +++ b/devtools/client/aboutdebugging/moz.build @@ -1,12 +1,13 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -debug = Debug +DIRS += [ + 'components', +] -extensions = Extensions -serviceWorkers = Service Workers -sharedWorkers = Shared Workers -otherWorkers = Other Workers - -nothing = Nothing yet. +BROWSER_CHROME_MANIFESTS += [ + 'test/browser.ini' +]
new file mode 100644 --- /dev/null +++ b/devtools/client/aboutdebugging/test/addons/unpacked/bootstrap.js @@ -0,0 +1,7 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); +function startup() { + Services.obs.notifyObservers(null, "test-devtools", null); +} +function shutdown() {} +function install() {} +function uninstall() {}
new file mode 100644 --- /dev/null +++ b/devtools/client/aboutdebugging/test/addons/unpacked/install.rdf @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<!-- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +--> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + <Description about="urn:mozilla:install-manifest" + em:id="test-devtools@mozilla.org" + em:name="test-devtools" + em:version="1.0" + em:type="2" + em:creator="Mozilla"> + + <em:bootstrap>true</em:bootstrap> + <em:targetApplication> + <Description> + <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> + <em:minVersion>44.0a1</em:minVersion> + <em:maxVersion>*</em:maxVersion> + </Description> + </em:targetApplication> + </Description> +</RDF>
new file mode 100644 --- /dev/null +++ b/devtools/client/aboutdebugging/test/browser.ini @@ -0,0 +1,9 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +support-files = + head.js + addons/unpacked/bootstrap.js + addons/unpacked/install.rdf + +[browser_addons_install.js]
new file mode 100644 --- /dev/null +++ b/devtools/client/aboutdebugging/test/browser_addons_install.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +var {AddonManager} = Cu.import("resource://gre/modules/AddonManager.jsm", {}); + +const ADDON_ID = "test-devtools@mozilla.org"; +const ADDON_NAME = "test-devtools"; + +add_task(function *() { + let { tab, document } = yield openAboutDebugging("addons"); + + // Mock the file picker to select a test addon + let MockFilePicker = SpecialPowers.MockFilePicker; + MockFilePicker.init(null); + let file = get_supports_file("addons/unpacked/install.rdf"); + MockFilePicker.returnFiles = [file.file]; + + // Wait for a message sent by the addon's bootstrap.js file + let promise = new Promise(done => { + Services.obs.addObserver(function listener() { + Services.obs.removeObserver(listener, "test-devtools", false); + ok(true, "Addon installed and running its bootstrap.js file"); + done(); + }, "test-devtools", false); + }); + // Trigger the file picker by clicking on the button + document.getElementById("load-addon-from-file").click(); + + // Wait for the addon execution + yield promise; + + // Check that the addon appears in the UI + let names = [...document.querySelectorAll("#addons .target-name")]; + names = names.map(element => element.textContent); + ok(names.includes(ADDON_NAME), "The addon name appears in the list of addons: " + names); + + // Now uninstall this addon + yield new Promise(done => { + AddonManager.getAddonByID(ADDON_ID, addon => { + let listener = { + onUninstalled: function(aUninstalledAddon) { + if (aUninstalledAddon != addon) { + return; + } + AddonManager.removeAddonListener(listener); + done(); + } + }; + AddonManager.addAddonListener(listener); + addon.uninstall(); + }); + }); + + // Ensure that the UI removes the addon from the list + names = [...document.querySelectorAll("#addons .target-name")]; + names = names.map(element => element.textContent); + ok(!names.includes(ADDON_NAME), "After uninstall, the addon name disappears from the list of addons: " + names); + + yield closeAboutDebugging(tab); +});
new file mode 100644 --- /dev/null +++ b/devtools/client/aboutdebugging/test/head.js @@ -0,0 +1,78 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var {utils: Cu, classes: Cc, interfaces: Ci} = Components; + +const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); +const Services = require("Services"); +const DevToolsUtils = require("devtools/shared/DevToolsUtils"); +DevToolsUtils.testing = true; + +const CHROME_ROOT = gTestPath.substr(0, gTestPath.lastIndexOf("/") + 1); + +registerCleanupFunction(() => { + DevToolsUtils.testing = false; +}); + +function openAboutDebugging() { + info("opening about:debugging"); + return addTab("about:debugging").then(tab => { + let browser = tab.linkedBrowser; + return { + tab, + document: browser.contentDocument, + window: browser.contentWindow + }; + }); +} + +function closeAboutDebugging(tab) { + info("Closing about:debugging"); + return removeTab(tab); +} + +function addTab(aUrl, aWindow) { + info("Adding tab: " + aUrl); + + return new Promise(done => { + let targetWindow = aWindow || window; + let targetBrowser = targetWindow.gBrowser; + + targetWindow.focus(); + let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl); + let linkedBrowser = tab.linkedBrowser; + + linkedBrowser.addEventListener("load", function onLoad() { + linkedBrowser.removeEventListener("load", onLoad, true); + info("Tab added and finished loading: " + aUrl); + done(tab); + }, true); + }); +} + +function removeTab(aTab, aWindow) { + info("Removing tab."); + + return new Promise(done => { + let targetWindow = aWindow || window; + let targetBrowser = targetWindow.gBrowser; + let tabContainer = targetBrowser.tabContainer; + + tabContainer.addEventListener("TabClose", function onClose(aEvent) { + tabContainer.removeEventListener("TabClose", onClose, false); + info("Tab removed and finished closing."); + done(); + }, false); + + targetBrowser.removeTab(aTab); + }); +} + +function get_supports_file(path) { + let cr = Cc["@mozilla.org/chrome/chrome-registry;1"]. + getService(Ci.nsIChromeRegistry); + let fileurl = cr.convertChromeURL(Services.io.newURI(CHROME_ROOT + path, null, null)); + return fileurl.QueryInterface(Ci.nsIFileURL); +}
--- a/devtools/client/locales/en-US/aboutdebugging.dtd +++ b/devtools/client/locales/en-US/aboutdebugging.dtd @@ -1,9 +1,10 @@ <!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> <!ENTITY aboutDebugging.title "about:debugging"> <!ENTITY aboutDebugging.addons "Add-ons"> <!ENTITY aboutDebugging.addonDebugging.label "Enable add-on debugging"> <!ENTITY aboutDebugging.addonDebugging.tooltip "Turning this on will allow you to debug add-ons and various other parts of the browser chrome"> +<!ENTITY aboutDebugging.loadTemporaryAddon "Load Temporary Add-on"> <!ENTITY aboutDebugging.workers "Workers">
--- a/devtools/client/locales/en-US/aboutdebugging.properties +++ b/devtools/client/locales/en-US/aboutdebugging.properties @@ -1,12 +1,13 @@ # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. debug = Debug extensions = Extensions +selectAddonFromFile = Select Add-on Directory or XPI File serviceWorkers = Service Workers sharedWorkers = Shared Workers otherWorkers = Other Workers nothing = Nothing yet.
--- a/devtools/client/moz.build +++ b/devtools/client/moz.build @@ -2,17 +2,17 @@ # vim: set filetype=python: # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. include('../templates.mozbuild') DIRS += [ - 'aboutdebugging/components', + 'aboutdebugging', 'animationinspector', 'canvasdebugger', 'commandline', 'debugger', 'eyedropper', 'fontinspector', 'framework', 'inspector',
--- a/devtools/server/actors/webconsole.js +++ b/devtools/server/actors/webconsole.js @@ -153,18 +153,22 @@ WebConsoleActor.prototype = * @type boolean */ get _parentIsContentActor() { return "ContentActor" in DebuggerServer && this.parentActor instanceof DebuggerServer.ContentActor; }, /** - * The window we work with. - * @type nsIDOMWindow + * The window or sandbox we work with. + * Note that even if it is named `window` it refers to the current + * global we are debugging, which can be a Sandbox for addons + * or browser content toolbox. + * + * @type nsIDOMWindow or Sandbox */ get window() { if (this.parentActor.isRootActor) { return this._getWindowForBrowserConsole(); } return this.parentActor.window; }, @@ -731,17 +735,18 @@ WebConsoleActor.prototype = while (types.length > 0) { let type = types.shift(); switch (type) { case "ConsoleAPI": { if (!this.consoleAPIListener) { break; } - let requestStartTime = this.window ? + // See `window` definition. It isn't always a DOM Window. + let requestStartTime = this.window && this.window.performance ? this.window.performance.timing.requestStart : 0; let cache = this.consoleAPIListener .getCachedMessages(!this.parentActor.isRootActor); cache.forEach((aMessage) => { // Filter out messages that came from a ServiceWorker but happened // before the page was requested. if (aMessage.innerID === "ServiceWorker" &&
--- a/devtools/shared/heapsnapshot/HeapSnapshot.cpp +++ b/devtools/shared/heapsnapshot/HeapSnapshot.cpp @@ -52,30 +52,17 @@ using ::google::protobuf::io::ArrayInput using ::google::protobuf::io::CodedInputStream; using ::google::protobuf::io::GzipInputStream; using ::google::protobuf::io::ZeroCopyInputStream; using JS::ubi::AtomOrTwoByteChars; /*** Cycle Collection Boilerplate *****************************************************************/ -NS_IMPL_CYCLE_COLLECTION_CLASS(HeapSnapshot) - -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HeapSnapshot) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) -NS_IMPL_CYCLE_COLLECTION_UNLINK_END - -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HeapSnapshot) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END - -NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(HeapSnapshot) - NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER -NS_IMPL_CYCLE_COLLECTION_TRACE_END +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HeapSnapshot, mParent) NS_IMPL_CYCLE_COLLECTING_ADDREF(HeapSnapshot) NS_IMPL_CYCLE_COLLECTING_RELEASE(HeapSnapshot) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HeapSnapshot) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END
--- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -93,16 +93,33 @@ struct nsIMediaDevice::COMTypeInfo<mozil }; const nsIID nsIMediaDevice::COMTypeInfo<mozilla::VideoDevice, void>::kIID = NS_IMEDIADEVICE_IID; template<> struct nsIMediaDevice::COMTypeInfo<mozilla::AudioDevice, void> { static const nsIID kIID; }; const nsIID nsIMediaDevice::COMTypeInfo<mozilla::AudioDevice, void>::kIID = NS_IMEDIADEVICE_IID; +namespace { +already_AddRefed<nsIAsyncShutdownClient> GetShutdownPhase() { + nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown(); + MOZ_RELEASE_ASSERT(svc); + + nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase; + nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(shutdownPhase)); + if (!shutdownPhase) { + // We are probably in a content process. + rv = svc->GetContentChildShutdown(getter_AddRefs(shutdownPhase)); + } + MOZ_RELEASE_ASSERT(shutdownPhase); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + return shutdownPhase.forget(); +} +} + namespace mozilla { #ifdef LOG #undef LOG #endif LogModule* GetMediaManagerLog() @@ -1555,44 +1572,38 @@ MediaManager::Get() { prefs->AddObserver("media.navigator.video.default_width", sSingleton, false); prefs->AddObserver("media.navigator.video.default_height", sSingleton, false); prefs->AddObserver("media.navigator.video.default_fps", sSingleton, false); prefs->AddObserver("media.navigator.video.default_minfps", sSingleton, false); } // Prepare async shutdown - nsCOMPtr<nsIAsyncShutdownClient> profileBeforeChange; - { - nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown(); - MOZ_RELEASE_ASSERT(svc); - nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(profileBeforeChange)); - MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); - } + nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetShutdownPhase(); class Blocker : public media::ShutdownBlocker { public: Blocker() : media::ShutdownBlocker(NS_LITERAL_STRING( "Media shutdown: blocking on media thread")) {} - NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient* aProfileBeforeChange) override + NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient*) override { MOZ_RELEASE_ASSERT(MediaManager::GetIfExists()); MediaManager::GetIfExists()->Shutdown(); return NS_OK; } }; sSingleton->mShutdownBlocker = new Blocker(); - nsresult rv = profileBeforeChange->AddBlocker(sSingleton->mShutdownBlocker, - NS_LITERAL_STRING(__FILE__), - __LINE__, - NS_LITERAL_STRING("Media shutdown")); + nsresult rv = shutdownPhase->AddBlocker(sSingleton->mShutdownBlocker, + NS_LITERAL_STRING(__FILE__), + __LINE__, + NS_LITERAL_STRING("Media shutdown")); MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); #ifdef MOZ_B2G // Init MediaPermissionManager before sending out any permission requests. (void) MediaPermissionManager::GetInstance(); #endif //MOZ_B2G } return sSingleton; } @@ -2649,24 +2660,18 @@ MediaManager::Shutdown() media::NewRunnableFrom([this, that]() mutable { LOG(("MediaManager shutdown lambda running, releasing MediaManager singleton and thread")); if (mMediaThread) { mMediaThread->Stop(); } // Remove async shutdown blocker - nsCOMPtr<nsIAsyncShutdownClient> profileBeforeChange; - { - nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdown(); - MOZ_RELEASE_ASSERT(svc); - nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(profileBeforeChange)); - MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); - } - profileBeforeChange->RemoveBlocker(sSingleton->mShutdownBlocker); + nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetShutdownPhase(); + shutdownPhase->RemoveBlocker(sSingleton->mShutdownBlocker); // we hold a ref to 'that' which is the same as sSingleton sSingleton = nullptr; return NS_OK; }))); }
--- a/editor/composer/nsComposeTxtSrvFilter.cpp +++ b/editor/composer/nsComposeTxtSrvFilter.cpp @@ -40,16 +40,17 @@ nsComposeTxtSrvFilter::Skip(nsIDOMNode* if (!*_retval) { *_retval = content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::_class, nsGkAtoms::mozsignature, eCaseMatters); } } } else if (content->IsAnyOfHTMLElements(nsGkAtoms::script, nsGkAtoms::textarea, nsGkAtoms::select, + nsGkAtoms::style, nsGkAtoms::map)) { *_retval = true; } else if (content->IsHTMLElement(nsGkAtoms::table)) { if (mIsForMail) { *_retval = content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::_class, NS_LITERAL_STRING("moz-email-headers-table"), eCaseMatters);
--- a/editor/composer/test/chrome.ini +++ b/editor/composer/test/chrome.ini @@ -6,8 +6,9 @@ skip-if = buildapp == 'b2g' || os == 'an [test_bug434998.xul] [test_bug678842.html] [test_bug697981.html] [test_bug717433.html] [test_bug1204147.html] [test_bug1200533.html] [test_bug1205983.html] [test_bug1209414.html] +[test_bug1219928.html]
new file mode 100644 --- /dev/null +++ b/editor/composer/test/test_bug1219928.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1219928 +--> +<head> + <title>Test for Bug 1219928</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1219928">Mozilla Bug 1219928</a> +<p id="display"></p> + +<div contenteditable id="en-US" lang="en-US"> +<p>And here a missspelled word</p> +<style> +<!-- and here another onnee in a style comment --> +</style> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 1219928 **/ +/* Very simple test to check that <style> blocks are skipped in the spell check */ + +var spellchecker; + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(function() { + Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm"); + + var elem = document.getElementById('en-US'); + elem.focus(); + + onSpellCheck(elem, function () { + var Ci = Components.interfaces; + var editingSession = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIEditingSession); + var editor = editingSession.getEditorForWindow(window); + var selcon = editor.selectionController; + var sel = selcon.getSelection(selcon.SELECTION_SPELLCHECK); + + is(sel.toString(), "missspelled", "one misspelled word expected: missspelled"); + + spellchecker = Components.classes['@mozilla.org/editor/editorspellchecker;1'].createInstance(Components.interfaces.nsIEditorSpellCheck); + var filterContractId = "@mozilla.org/editor/txtsrvfilter;1"; + spellchecker.setFilter(Components.classes[filterContractId].createInstance(Components.interfaces.nsITextServicesFilter)); + spellchecker.InitSpellChecker(editor, false, spellCheckStarted); + }); +}); + +function spellCheckStarted() { + var misspelledWord = spellchecker.GetNextMisspelledWord(); + is(misspelledWord, "missspelled", "first misspelled word expected: missspelled"); + + // Without the fix, the next misspelled word was 'onnee', so we check that we don't get it. + misspelledWord = spellchecker.GetNextMisspelledWord(); + isnot(misspelledWord, "onnee", "second misspelled word should not be: onnee"); + + spellchecker = ""; + + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html>
--- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -78,16 +78,29 @@ using namespace mozilla::layout; static mozilla::LazyLogModule sRefreshDriverLog("nsRefreshDriver"); #define LOG(...) MOZ_LOG(sRefreshDriverLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) #define DEFAULT_THROTTLED_FRAME_RATE 1 #define DEFAULT_RECOMPUTE_VISIBILITY_INTERVAL_MS 1000 // after 10 minutes, stop firing off inactive timers #define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600 +namespace { + // `true` if we are currently in jank-critical mode. + // + // In jank-critical mode, any iteration of the event loop that takes + // more than 16ms to compute will cause an ongoing animation to miss + // frames. + // + // For simplicity, the current implementation assumes that we are in + // jank-critical mode if and only if at least one vsync driver has + // at least one observer. + static uint64_t sActiveVsyncTimers = 0; +} + namespace mozilla { /* * The base class for all global refresh driver timers. It takes care * of managing the list of refresh drivers attached to them and * provides interfaces for querying/setting the rate and actually * running a timer 'Tick'. Subclasses must implement StartTimer(), * StopTimer(), and ScheduleNextTick() -- the first two just @@ -498,34 +511,45 @@ private: // Detach current vsync timer from this VsyncObserver. The observer will no // longer tick this timer. mVsyncObserver->Shutdown(); mVsyncObserver = nullptr; } virtual void StartTimer() override { + // Protect updates to `sActiveVsyncTimers`. + MOZ_ASSERT(NS_IsMainThread()); + mLastFireEpoch = JS_Now(); mLastFireTime = TimeStamp::Now(); if (XRE_IsParentProcess()) { mVsyncDispatcher->SetParentRefreshTimer(mVsyncObserver); } else { Unused << mVsyncChild->SendObserve(); mVsyncObserver->OnTimerStart(); } + + ++sActiveVsyncTimers; } virtual void StopTimer() override { + // Protect updates to `sActiveVsyncTimers`. + MOZ_ASSERT(NS_IsMainThread()); + if (XRE_IsParentProcess()) { mVsyncDispatcher->SetParentRefreshTimer(nullptr); } else { Unused << mVsyncChild->SendUnobserve(); } + + MOZ_ASSERT(sActiveVsyncTimers > 0); + --sActiveVsyncTimers; } virtual void ScheduleNextTick(TimeStamp aNowTime) override { // Do nothing since we just wait for the next vsync from // RefreshDriverVsyncObserver. } @@ -2123,9 +2147,16 @@ nsRefreshDriver::CancelPendingEvents(nsI { for (auto i : Reversed(MakeRange(mPendingEvents.Length()))) { if (mPendingEvents[i].mTarget->OwnerDoc() == aDocument) { mPendingEvents.RemoveElementAt(i); } } } +/* static */ bool +nsRefreshDriver::IsJankCritical() +{ + MOZ_ASSERT(NS_IsMainThread()); + return sActiveVsyncTimers > 0; +} + #undef LOG
--- a/layout/base/nsRefreshDriver.h +++ b/layout/base/nsRefreshDriver.h @@ -425,11 +425,22 @@ private: void BeginRefreshingImages(RequestTable& aEntries, mozilla::TimeStamp aDesired); friend class mozilla::RefreshDriverTimer; // turn on or turn off high precision based on various factors void ConfigureHighPrecision(); void SetHighPrecisionTimersEnabled(bool aEnable); + + // `true` if we are currently in jank-critical mode. + // + // In jank-critical mode, any iteration of the event loop that takes + // more than 16ms to compute will cause an ongoing animation to miss + // frames. + // + // For simplicity, the current implementation assumes that we are + // in jank-critical mode if and only if the vsync driver has at least + // one observer. + static bool IsJankCritical(); }; #endif /* !defined(nsRefreshDriver_h_) */
--- a/layout/generic/nsContainerFrame.cpp +++ b/layout/generic/nsContainerFrame.cpp @@ -589,18 +589,18 @@ GetPresContextContainerWidget(nsPresCont } static bool IsTopLevelWidget(nsIWidget* aWidget) { nsWindowType windowType = aWidget->WindowType(); return windowType == eWindowType_toplevel || windowType == eWindowType_dialog || + windowType == eWindowType_popup || windowType == eWindowType_sheet; - // popups aren't toplevel so they're not handled here } void nsContainerFrame::SyncWindowProperties(nsPresContext* aPresContext, nsIFrame* aFrame, nsView* aView, nsRenderingContext* aRC, uint32_t aFlags)
--- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -554,16 +554,17 @@ pref("ui.dragThresholdX", 25); pref("ui.dragThresholdY", 25); pref("layers.acceleration.disabled", false); pref("layers.offmainthreadcomposition.enabled", true); pref("layers.async-video.enabled", true); #ifdef MOZ_ANDROID_APZ pref("layers.async-pan-zoom.enabled", true); pref("apz.axis_lock.mode", 1); +pref("apz.fling_stop_on_tap_threshold", "0.08"); #endif pref("apz.allow_zooming", true); pref("layers.progressive-paint", true); pref("layers.low-precision-buffer", true); pref("layers.low-precision-resolution", "0.25"); pref("layers.low-precision-opacity", "1.0"); // We want to limit layers for two reasons: // 1) We can't scroll smoothly if we have to many draw calls
--- a/mobile/android/b2gdroid/installer/package-manifest.in +++ b/mobile/android/b2gdroid/installer/package-manifest.in @@ -159,16 +159,17 @@ @BINPATH@/components/dom_security.xpt @BINPATH@/components/dom_settings.xpt @BINPATH@/components/dom_permissionsettings.xpt @BINPATH@/components/dom_sidebar.xpt @BINPATH@/components/dom_mobilemessage.xpt @BINPATH@/components/dom_storage.xpt @BINPATH@/components/dom_stylesheets.xpt @BINPATH@/components/dom_system.xpt +@BINPATH@/components/dom_workers.xpt @BINPATH@/components/dom_threads.xpt @BINPATH@/components/dom_traversal.xpt @BINPATH@/components/dom_tv.xpt @BINPATH@/components/dom_views.xpt #ifdef MOZ_WEBSPEECH @BINPATH@/components/dom_webspeechrecognition.xpt #endif @BINPATH@/components/dom_xbl.xpt
--- a/python/mozboot/mozboot/android.py +++ b/python/mozboot/mozboot/android.py @@ -4,17 +4,17 @@ # If we add unicode_literals, Python 2.6.1 (required for OS X 10.6) breaks. from __future__ import print_function import errno import os import stat import subprocess - +import sys # These are the platform and build-tools versions for building # mobile/android, respectively. Try to keep these in synch with the # build system and Mozilla's automation. ANDROID_TARGET_SDK = '23' ANDROID_BUILD_TOOLS_VERSION = '23.0.1' # These are the "Android packages" needed for building Firefox for Android. @@ -204,17 +204,16 @@ def ensure_android_packages(android_tool ''' Use the given android tool (like 'android') to install required Android packages. ''' if not packages: packages = ANDROID_PACKAGES - # Bug 1171232: The |android| tool behaviour has changed; we no longer can # see what packages are installed easily. Force installing everything until # we find a way to actually see the missing packages. missing = packages if not missing: print(NOT_INSTALLING_ANDROID_PACKAGES % ', '.join(packages)) return @@ -231,16 +230,19 @@ def ensure_android_packages(android_tool failing = [] if failing: raise Exception(MISSING_ANDROID_PACKAGES % (', '.join(missing), ', '.join(failing))) def suggest_mozconfig(sdk_path=None, ndk_path=None): print(MOBILE_ANDROID_MOZCONFIG_TEMPLATE % (sdk_path, ndk_path)) -def android_ndk_url(os_name, ver='r10e') : - from sys import maxsize - base_url = 'https://dl.google.com/android/ndk/android-ndk-' + +def android_ndk_url(os_name, ver='r10e'): + # Produce a URL like 'https://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin'. + base_url = 'https://dl.google.com/android/ndk/android-ndk' - if maxsize > 2**32 : - return (base_url+ver+'-'+os_name+'-x86_64.bin') - else : - return (base_url+ver+'-'+os_name+'-x86.bin') \ No newline at end of file + if sys.maxsize > 2**32: + arch = 'x86_64' + else: + arch = 'x86' + + return '%s-%s-%s-%s.bin' % (base_url, ver, os_name, arch)
--- a/python/mozboot/mozboot/archlinux.py +++ b/python/mozboot/mozboot/archlinux.py @@ -108,17 +108,17 @@ class ArchlinuxBootstrapper(BaseBootstra # 2. The user may have an external Android SDK (in which case we save # them a lengthy download), or they may have already completed the # download. We unpack to ~/.mozbuild/{android-sdk-linux, android-ndk-r10e}. mozbuild_path = os.environ.get('MOZBUILD_STATE_PATH', os.path.expanduser(os.path.join('~', '.mozbuild'))) self.sdk_path = os.environ.get('ANDROID_SDK_HOME', os.path.join(mozbuild_path, 'android-sdk-linux')) self.ndk_path = os.environ.get('ANDROID_NDK_HOME', os.path.join(mozbuild_path, 'android-ndk-r10e')) self.sdk_url = 'https://dl.google.com/android/android-sdk_r24.0.1-linux.tgz' - self.ndk_url = android_ndk_url('linux') + self.ndk_url = android.android_ndk_url('linux') android.ensure_android_sdk_and_ndk(path=mozbuild_path, sdk_path=self.sdk_path, sdk_url=self.sdk_url, ndk_path=self.ndk_path, ndk_url=self.ndk_url) android_tool = os.path.join(self.sdk_path, 'tools', 'android') android.ensure_android_packages(android_tool=android_tool) def suggest_mobile_android_mozconfig(self):
--- a/python/mozboot/mozboot/debian.py +++ b/python/mozboot/mozboot/debian.py @@ -123,17 +123,17 @@ class DebianBootstrapper(BaseBootstrappe # 2. The user may have an external Android SDK (in which case we save # them a lengthy download), or they may have already completed the # download. We unpack to ~/.mozbuild/{android-sdk-linux, android-ndk-r10e}. mozbuild_path = os.environ.get('MOZBUILD_STATE_PATH', os.path.expanduser(os.path.join('~', '.mozbuild'))) self.sdk_path = os.environ.get('ANDROID_SDK_HOME', os.path.join(mozbuild_path, 'android-sdk-linux')) self.ndk_path = os.environ.get('ANDROID_NDK_HOME', os.path.join(mozbuild_path, 'android-ndk-r10e')) self.sdk_url = 'https://dl.google.com/android/android-sdk_r24.0.1-linux.tgz' - self.ndk_url = android_ndk_url('linux') + self.ndk_url = android.android_ndk_url('linux') android.ensure_android_sdk_and_ndk(path=mozbuild_path, sdk_path=self.sdk_path, sdk_url=self.sdk_url, ndk_path=self.ndk_path, ndk_url=self.ndk_url) # 3. We expect the |android| tool to at # ~/.mozbuild/android-sdk-linux/tools/android. android_tool = os.path.join(self.sdk_path, 'tools', 'android')
--- a/python/mozboot/mozboot/osx.py +++ b/python/mozboot/mozboot/osx.py @@ -350,30 +350,30 @@ class OSXBootstrapper(BaseBootstrapper): # them a lengthy download), or they may have already completed the # download. We unpack to ~/.mozbuild/{android-sdk-linux, android-ndk-r10e}. mozbuild_path = os.environ.get('MOZBUILD_STATE_PATH', os.path.expanduser(os.path.join('~', '.mozbuild'))) self.sdk_path = os.environ.get('ANDROID_SDK_HOME', os.path.join(mozbuild_path, 'android-sdk-macosx')) self.ndk_path = os.environ.get('ANDROID_NDK_HOME', os.path.join(mozbuild_path, 'android-ndk-r10e')) self.sdk_url = 'https://dl.google.com/android/android-sdk_r24.0.1-macosx.zip' is_64bits = sys.maxsize > 2**32 if is_64bits: - self.ndk_url = android_ndk_url('darwin') + self.ndk_url = android.android_ndk_url('darwin') else: raise Exception('You need a 64-bit version of Mac OS X to build Firefox for Android.') android.ensure_android_sdk_and_ndk(path=mozbuild_path, sdk_path=self.sdk_path, sdk_url=self.sdk_url, ndk_path=self.ndk_path, ndk_url=self.ndk_url) # 3. We expect the |android| tool to at # ~/.mozbuild/android-sdk-macosx/tools/android. android_tool = os.path.join(self.sdk_path, 'tools', 'android') android.ensure_android_packages(android_tool=android_tool) - def suggest_mobile_android_mozconfig(self): + def suggest_homebrew_mobile_android_mozconfig(self): import android android.suggest_mozconfig(sdk_path=self.sdk_path, ndk_path=self.ndk_path) def _ensure_macports_packages(self, packages): self.port = self.which('port') assert self.port is not None
--- a/services/fxaccounts/FxAccountsWebChannel.jsm +++ b/services/fxaccounts/FxAccountsWebChannel.jsm @@ -17,16 +17,18 @@ Cu.import("resource://gre/modules/XPCOMU Cu.import("resource://gre/modules/FxAccountsCommon.js"); XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "WebChannel", "resource://gre/modules/WebChannel.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts", "resource://gre/modules/FxAccounts.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Weave", + "resource://services-sync/main.js"); const COMMAND_PROFILE_CHANGE = "profile:change"; const COMMAND_CAN_LINK_ACCOUNT = "fxaccounts:can_link_account"; const COMMAND_LOGIN = "fxaccounts:login"; const COMMAND_LOGOUT = "fxaccounts:logout"; const COMMAND_DELETE = "fxaccounts:delete"; const PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash"; @@ -201,31 +203,44 @@ this.FxAccountsWebChannelHelpers.prototy * * Save a bit into prefs that is read on verification to see whether * to show the list of data types that can be saved. */ setShowCustomizeSyncPref(showCustomizeSyncPref) { Services.prefs.setBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION, showCustomizeSyncPref); }, - getShowCustomizeSyncPref(showCustomizeSyncPref) { + getShowCustomizeSyncPref() { return Services.prefs.getBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION); }, /** * stores sync login info it in the fxaccounts service * * @param accountData the user's account data and credentials */ login(accountData) { if (accountData.customizeSync) { this.setShowCustomizeSyncPref(true); delete accountData.customizeSync; } + if (accountData.declinedSyncEngines) { + let declinedSyncEngines = accountData.declinedSyncEngines; + log.debug("Received declined engines", declinedSyncEngines); + Weave.Service.engineManager.setDeclined(declinedSyncEngines); + declinedSyncEngines.forEach(engine => { + Services.prefs.setBoolPref("services.sync.engine." + engine, false); + }); + + // if we got declinedSyncEngines that means we do not need to show the customize screen. + this.setShowCustomizeSyncPref(false); + delete accountData.declinedSyncEngines; + } + // the user has already been shown the "can link account" // screen. No need to keep this data around. delete accountData.verifiedCanLinkAccount; // Remember who it was so we can log out next time. this.setPreviousAccountNameHashPref(accountData.email); // A sync-specific hack - we want to ensure sync has been initialized @@ -234,17 +249,17 @@ this.FxAccountsWebChannelHelpers.prototy .getService(Ci.nsISupports) .wrappedJSObject; return xps.whenLoaded().then(() => { return this._fxAccounts.setSignedInUser(accountData); }); }, /** - * logoust the fxaccounts service + * logout the fxaccounts service * * @param the uid of the account which have been logged out */ logout(uid) { return fxAccounts.getSignedInUser().then(userData => { if (userData.uid === uid) { return fxAccounts.signOut(); }
--- a/services/fxaccounts/tests/xpcshell/test_web_channel.js +++ b/services/fxaccounts/tests/xpcshell/test_web_channel.js @@ -235,16 +235,58 @@ add_test(function test_helpers_login_wit helpers.login({ email: 'testuser@testuser.com', verifiedCanLinkAccount: true, customizeSync: true }); }); +add_test(function test_helpers_login_with_customize_sync_and_declined_engines() { + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + setSignedInUser: function(accountData) { + // ensure fxAccounts is informed of the new user being signed in. + do_check_eq(accountData.email, 'testuser@testuser.com'); + + // customizeSync should be stripped in the data. + do_check_false('customizeSync' in accountData); + do_check_false('declinedSyncEngines' in accountData); + do_check_eq(Services.prefs.getBoolPref("services.sync.engine.addons"), false); + do_check_eq(Services.prefs.getBoolPref("services.sync.engine.bookmarks"), true); + do_check_eq(Services.prefs.getBoolPref("services.sync.engine.history"), true); + do_check_eq(Services.prefs.getBoolPref("services.sync.engine.passwords"), true); + do_check_eq(Services.prefs.getBoolPref("services.sync.engine.prefs"), false); + do_check_eq(Services.prefs.getBoolPref("services.sync.engine.tabs"), true); + + // the customizeSync pref should be disabled + do_check_false(helpers.getShowCustomizeSyncPref()); + + run_next_test(); + } + } + }); + + // the customize sync pref should be overwritten + helpers.setShowCustomizeSyncPref(true); + + do_check_eq(Services.prefs.getBoolPref("services.sync.engine.addons"), true); + do_check_eq(Services.prefs.getBoolPref("services.sync.engine.bookmarks"), true); + do_check_eq(Services.prefs.getBoolPref("services.sync.engine.history"), true); + do_check_eq(Services.prefs.getBoolPref("services.sync.engine.passwords"), true); + do_check_eq(Services.prefs.getBoolPref("services.sync.engine.prefs"), true); + do_check_eq(Services.prefs.getBoolPref("services.sync.engine.tabs"), true); + helpers.login({ + email: 'testuser@testuser.com', + verifiedCanLinkAccount: true, + customizeSync: true, + declinedSyncEngines: ['addons', 'prefs'] + }); +}); + function run_test() { run_next_test(); } function makeObserver(aObserveTopic, aObserveFunc) { let callback = function (aSubject, aTopic, aData) { log.debug("observed " + aTopic + " " + aData); if (aTopic == aObserveTopic) {
--- a/services/healthreport/healthreport-prefs.js +++ b/services/healthreport/healthreport-prefs.js @@ -29,10 +29,10 @@ pref("datareporting.healthreport.service "healthreport-js-provider-default" #elif MOZ_UPDATE_CHANNEL == default "healthreport-js-provider-default" #else "healthreport-js-provider-default,healthreport-js-provider-@MOZ_UPDATE_CHANNEL@" #endif ); -pref("datareporting.healthreport.about.reportUrl", "https://fhr.cdn.mozilla.net/%LOCALE%/"); +pref("datareporting.healthreport.about.reportUrl", "https://fhr.cdn.mozilla.net/%LOCALE%/v4/"); pref("datareporting.healthreport.about.reportUrlUnified", "https://fhr.cdn.mozilla.net/%LOCALE%/v4/");
--- a/toolkit/components/asyncshutdown/AsyncShutdown.jsm +++ b/toolkit/components/asyncshutdown/AsyncShutdown.jsm @@ -61,16 +61,22 @@ Object.defineProperty(this, "gCrashRepor return this.gCrashReporter = reporter; } catch (ex) { return this.gCrashReporter = null; } }, configurable: true }); +// `true` if this is a content process, `false` otherwise. +// It would be nicer to go through `Services.appInfo`, but some tests need to be +// able to replace that field with a custom implementation before it is first +// called. +const isContent = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT; + // Display timeout warnings after 10 seconds const DELAY_WARNING_MS = 10 * 1000; // Crash the process if shutdown is really too long // (allowing for sleep). const PREF_DELAY_CRASH_MS = "toolkit.asyncshutdown.crash_timeout"; var DELAY_CRASH_MS = 60 * 1000; // One minute @@ -974,18 +980,29 @@ Barrier.prototype = Object.freeze({ // List of well-known phases // Ideally, phases should be registered from the component that decides // when they start/stop. For compatibility with existing startup/shutdown // mechanisms, we register a few phases here. -this.AsyncShutdown.profileChangeTeardown = getPhase("profile-change-teardown"); -this.AsyncShutdown.profileBeforeChange = getPhase("profile-before-change"); -this.AsyncShutdown.placesClosingInternalConnection = getPhase("places-will-close-connection"); -this.AsyncShutdown.sendTelemetry = getPhase("profile-before-change2"); +// Parent process +if (!isContent) { + this.AsyncShutdown.profileChangeTeardown = getPhase("profile-change-teardown"); + this.AsyncShutdown.profileBeforeChange = getPhase("profile-before-change"); + this.AsyncShutdown.placesClosingInternalConnection = getPhase("places-will-close-connection"); + this.AsyncShutdown.sendTelemetry = getPhase("profile-before-change2"); +} + + +// Content process +if (isContent) { + this.AsyncShutdown.contentChildShutdown = getPhase("content-child-shutdown"); +} + +// All processes this.AsyncShutdown.webWorkersShutdown = getPhase("web-workers-shutdown"); this.AsyncShutdown.xpcomThreadsShutdown = getPhase("xpcom-threads-shutdown"); this.AsyncShutdown.Barrier = Barrier; Object.freeze(this.AsyncShutdown);
--- a/toolkit/components/asyncshutdown/nsAsyncShutdown.js +++ b/toolkit/components/asyncshutdown/nsAsyncShutdown.js @@ -221,27 +221,35 @@ nsAsyncShutdownBarrier.prototype = { QueryInterface : XPCOMUtils.generateQI([Ci.nsIAsyncShutdownBarrier]), classID: Components.ID("{29a0e8b5-9111-4c09-a0eb-76cd02bf20fa}"), }; function nsAsyncShutdownService() { // Cache for the getters for (let _k of - ["profileBeforeChange", + [// Parent process + "profileBeforeChange", "profileChangeTeardown", "sendTelemetry", + + // Child processes + "contentChildShutdown", + + // All processes "webWorkersShutdown", - "xpcomThreadsShutdown"]) { + "xpcomThreadsShutdown", + ]) { let k = _k; Object.defineProperty(this, k, { configurable: true, get: function() { delete this[k]; - let result = new nsAsyncShutdownClient(AsyncShutdown[k]); + let wrapped = AsyncShutdown[k]; // May be undefined, if we're on the wrong process. + let result = wrapped ? new nsAsyncShutdownClient(wrapped) : undefined; Object.defineProperty(this, k, { value: result }); return result; } }); } @@ -261,9 +269,8 @@ nsAsyncShutdownService.prototype = { }; this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ nsAsyncShutdownService, nsAsyncShutdownBarrier, nsAsyncShutdownClient, ]); -
--- a/toolkit/components/asyncshutdown/nsIAsyncShutdown.idl +++ b/toolkit/components/asyncshutdown/nsIAsyncShutdown.idl @@ -155,53 +155,61 @@ interface nsIAsyncShutdownBarrier: nsISu * The callback always receives NS_OK. */ void wait(in nsIAsyncShutdownCompletionCallback aOnReady); }; /** * A service that allows registering shutdown-time dependencies. */ -[scriptable, uuid(8a9a0859-0404-4d50-9e76-10a4f56dfb51)] +[scriptable, uuid(10f51032-dcc9-4732-bec0-c0b7d6596622)] interface nsIAsyncShutdownService: nsISupports { /** * Create a new barrier. * * By convention, the name should respect the following format: * "MyModuleName: Doing something while it's time" * e.g. * "OS.File: Waiting for clients to flush before shutting down" * * This attribute is uploaded as part of crash reports. */ nsIAsyncShutdownBarrier makeBarrier(in AString aName); - // Barriers for global shutdown stages + + // Barriers for global shutdown stages in the parent process. /** * Barrier for notification profile-before-change. */ readonly attribute nsIAsyncShutdownClient profileBeforeChange; /** * Barrier for notification profile-change-teardown. */ readonly attribute nsIAsyncShutdownClient profileChangeTeardown; /** * Barrier for notification profile-before-change2. */ readonly attribute nsIAsyncShutdownClient sendTelemetry; + // Barriers for global shutdown stages in the content processes. + + readonly attribute nsIAsyncShutdownClient contentChildShutdown; + + // Barriers for global shutdown stages in all processes. + /** * Barrier for notification web-workers-shutdown. */ readonly attribute nsIAsyncShutdownClient webWorkersShutdown; /** * Barrier for notification xpcom-threads-shutdown. */ readonly attribute nsIAsyncShutdownClient xpcomThreadsShutdown; + }; %{C++ #define NS_ASYNCSHUTDOWNSERVICE_CONTRACTID "@mozilla.org/async-shutdown-service;1" %}
--- a/toolkit/components/osfile/modules/osfile_async_front.jsm +++ b/toolkit/components/osfile/modules/osfile_async_front.jsm @@ -1458,21 +1458,26 @@ this.OS.Path = Path; // Returns a resolved promise when all the queued operation have been completed. Object.defineProperty(OS.File, "queue", { get: function() { return Scheduler.queue; } }); +// `true` if this is a content process, `false` otherwise. +// It would be nicer to go through `Services.appInfo`, but some tests need to be +// able to replace that field with a custom implementation before it is first +// called. +const isContent = Components.classes["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT; + /** * Shutdown barriers, to let clients register to be informed during shutdown. */ var Barriers = { - profileBeforeChange: new AsyncShutdown.Barrier("OS.File: Waiting for clients before profile-before-shutdown"), shutdown: new AsyncShutdown.Barrier("OS.File: Waiting for clients before full shutdown"), /** * Return the shutdown state of OS.File */ getDetails: function() { let result = { launched: Scheduler.launched, shutdown: Scheduler.shutdown, @@ -1490,30 +1495,40 @@ var Barriers = { if (result[key] && typeof result[key][0] == "number") { result[key][0] = Date(result[key][0]); } } return result; } }; -File.profileBeforeChange = Barriers.profileBeforeChange.client; -File.shutdown = Barriers.shutdown.client; +function setupShutdown(phaseName) { + Barriers[phaseName] = new AsyncShutdown.Barrier(`OS.File: Waiting for clients before ${phaseName}`), + File[phaseName] = Barriers[phaseName].client; + + // Auto-flush OS.File during `phaseName`. This ensures that any I/O + // that has been queued *before* `phaseName` is properly completed. + // To ensure that I/O queued *during* `phaseName` change is completed, + // clients should register using AsyncShutdown.addBlocker. + AsyncShutdown[phaseName].addBlocker( + `OS.File: flush I/O queued before ${phaseName}`, + Task.async(function*() { + // Give clients a last chance to enqueue requests. + yield Barriers[phaseName].wait({crashAfterMS: null}); -// Auto-flush OS.File during profile-before-change. This ensures that any I/O -// that has been queued *before* profile-before-change is properly completed. -// To ensure that I/O queued *during* profile-before-change is completed, -// clients should register using AsyncShutdown.addBlocker. -AsyncShutdown.profileBeforeChange.addBlocker( - "OS.File: flush I/O queued before profile-before-change", - Task.async(function*() { - // Give clients a last chance to enqueue requests. - yield Barriers.profileBeforeChange.wait({crashAfterMS: null}); + // Wait until all currently enqueued requests are completed. + yield Scheduler.queue; + }), + () => { + let details = Barriers.getDetails(); + details.clients = Barriers[phaseName].state; + return details; + } + ); +} - // Wait until all currently enqueued requests are completed. - yield Scheduler.queue; - }), - () => { - let details = Barriers.getDetails(); - details.clients = Barriers.profileBeforeChange.state; - return details; - } -); + +if (isContent) { + setupShutdown("contentChildShutdown"); +} else { + setupShutdown("profileBeforeChange") +} +File.shutdown = Barriers.shutdown.client;
--- a/toolkit/modules/Troubleshoot.jsm +++ b/toolkit/modules/Troubleshoot.jsm @@ -80,16 +80,21 @@ const PREFS_WHITELIST = [ "network.", "permissions.default.image", "places.", "plugin.", "plugins.", "print.", "privacy.", "security.", + "services.sync.declinedEngines", + "services.sync.lastPing", + "services.sync.lastSync", + "services.sync.numClients", + "services.sync.engine.", "social.enabled", "storage.vacuum.last.", "svg.", "toolkit.startup.recent_crashes", "ui.osk.enabled", "ui.osk.detect_physical_keyboard", "ui.osk.require_tablet_mode", "ui.osk.debug.keyboardDisplayReason",
--- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -79,16 +79,17 @@ XPCOMUtils.defineLazyModuleGetter(this, "resource://gre/modules/FileUtils.jsm"); XPCOMUtils.defineLazyGetter(this, "CertUtils", function certUtilsLazyGetter() { let certUtils = {}; Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils); return certUtils; }); +const INTEGER = /^[1-9]\d*$/; this.EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ]; const CATEGORY_PROVIDER_MODULE = "addon-provider-module"; // A list of providers to load by default const DEFAULT_PROVIDERS = [ "resource://gre/modules/addons/XPIProvider.jsm", @@ -2341,17 +2342,20 @@ var AddonManagerInternal = { * that is closest to the specified size. * * The optional window parameter will be used to determine * the screen resolution and select a more appropriate icon. * Calling this method with 48px on retina screens will try to * match an icon of size 96px. * * @param aAddon - * The addon to find an icon for + * An addon object, meaning: + * An object with either an icons property that is a key-value + * list of icon size and icon URL, or an object having an iconURL + * and icon64URL property. * @param aSize * Ideal icon size in pixels * @param aWindow * Optional window object for determining the correct scale. * @return {String} The absolute URL of the icon or null if the addon doesn't have icons */ getPreferredIconURL: function AMI_getPreferredIconURL(aAddon, aSize, aWindow = undefined) { if (aWindow && aWindow.devicePixelRatio) { @@ -2375,22 +2379,23 @@ var AddonManagerInternal = { // quick return if the exact size was found if (icons[aSize]) { return icons[aSize]; } let bestSize = null; for (let size of Object.keys(icons)) { - size = parseInt(size, 10); - if (isNaN(size)) { + if (!INTEGER.test(size)) { throw Components.Exception("Invalid icon size, must be an integer", Cr.NS_ERROR_ILLEGAL_VALUE); } + size = parseInt(size, 10); + if (!bestSize) { bestSize = size; continue; } if (size > aSize && bestSize > aSize) { // If both best size and current size are larger than the wanted size then choose // the one closest to the wanted size
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -236,16 +236,17 @@ const SIGNED_TYPES = new Set([ // Whether add-on signing is required. function mustSign(aType) { if (!SIGNED_TYPES.has(aType)) return false; return REQUIRE_SIGNING || Preferences.get(PREF_XPI_SIGNATURES_REQUIRED, false); } +const INTEGER = /^[1-9]\d*$/; // Keep track of where we are in startup for telemetry // event happened during XPIDatabase.startup() const XPI_STARTING = "XPIStarting"; // event happened after startup() but before the final-ui-startup event const XPI_BEFORE_UI_STARTUP = "BeforeFinalUIStartup"; // event happened after final-ui-startup const XPI_AFTER_UI_STARTUP = "AfterFinalUIStartup"; @@ -907,18 +908,18 @@ var loadManifestFromWebManifest = Task.a addon.iconURL = null; addon.icon64URL = null; addon.icons = {}; let icons = getOptionalProp('icons'); if (icons) { // filter out invalid (non-integer) size keys Object.keys(icons) + .filter((size) => INTEGER.test(size)) .map((size) => parseInt(size, 10)) - .filter((size) => !isNaN(size)) .forEach((size) => addon.icons[size] = icons[size]); } addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; function getLocale(aLocale) { let result = { name: extension.localize(getProp("name"), aLocale),
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_icons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_icons.js @@ -82,16 +82,19 @@ add_task(function*() { applications: { gecko: { id: ID } }, icons: { 32: "icon32.png", banana: "bananana.png", + "20.5": "icon20.5.png", + "20.0": "also invalid", + "123banana": "123banana.png", 64: "icon64.png" } }, profileDir); yield promiseRestartManager(); let addon = yield promiseAddonByID(ID); do_check_neq(addon, null);
--- a/toolkit/themes/windows/global/alerts/alert.css +++ b/toolkit/themes/windows/global/alerts/alert.css @@ -5,16 +5,21 @@ /* ===== alert.css ===================================================== == Styles specific to the alerts dialog. ======================================================================= */ @import url("chrome://global/skin/alerts/alert-common.css"); @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); +#alertNotification { + -moz-appearance: none; + background: transparent; +} + #alertBox { border: 1px solid ThreeDShadow; border-radius: 1px; background-color: -moz-Dialog; color: -moz-DialogText; } .alertCloseButton {