author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Mon, 28 Sep 2015 14:10:50 +0200 | |
changeset 264718 | 3323f5c35ed310823c2febe01bbe748c62055634 |
parent 264680 | 6b022a1c49b64fece41a83890561faf6b2615baa (current diff) |
parent 264717 | 7e9fe56d9e96fc4a51c55aa50896506cb2c28bc6 (diff) |
child 264719 | 031db40e2b558c7e4dd0b4c565db4a992c1627c8 |
push id | 65707 |
push user | cbook@mozilla.com |
push date | Mon, 28 Sep 2015 12:18:34 +0000 |
treeherder | mozilla-inbound@2c0e60a8f736 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 44.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/base/content/browser-trackingprotection.js +++ b/browser/base/content/browser-trackingprotection.js @@ -25,16 +25,17 @@ var TrackingProtection = { Services.prefs.addObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this, false); this.activeTooltipText = gNavigatorBundle.getString("trackingProtection.icon.activeTooltip"); this.disabledTooltipText = gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip"); this.enabledHistogramAdd(this.enabledGlobally); + this.disabledPBMHistogramAdd(!this.enabledInPrivateWindows); }, uninit() { Services.prefs.removeObserver(this.PREF_ENABLED_GLOBALLY, this); Services.prefs.removeObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this); }, observe() { @@ -57,16 +58,23 @@ var TrackingProtection = { enabledHistogramAdd(value) { if (PrivateBrowsingUtils.isWindowPrivate(window)) { return; } Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").add(value); }, + disabledPBMHistogramAdd(value) { + if (PrivateBrowsingUtils.isWindowPrivate(window)) { + return; + } + Services.telemetry.getHistogramById("TRACKING_PROTECTION_PBM_DISABLED").add(value); + }, + eventsHistogramAdd(value) { if (PrivateBrowsingUtils.isWindowPrivate(window)) { return; } Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS").add(value); }, shieldHistogramAdd(value) {
--- a/browser/base/content/newtab/grid.js +++ b/browser/base/content/newtab/grid.js @@ -47,17 +47,17 @@ var gGrid = { * Initializes the grid. * @param aSelector The query selector of the grid. */ init: function Grid_init() { this._node = document.getElementById("newtab-grid"); this._createSiteFragment(); gLinks.populateCache(() => { - this.refresh(); + this._refreshGrid(); this._ready = true; // If fetching links took longer than loading the page itself then // we need to resize the grid as that was blocked until now. // We also want to resize now if the page was already loaded when // initializing the grid (the user toggled the page). this._resizeGrid(); @@ -104,19 +104,30 @@ var gGrid = { /** * Unlocks the grid to allow all pointer events. */ unlock: function Grid_unlock() { this.node.removeAttribute("locked"); }, /** + * Renders and resizes the gird. _resizeGrid() call is needed to ensure + * that scrollbar disappears when the bottom row becomes empty following + * the block action, or tile display is turmed off via cog menu + */ + + refresh() { + this._refreshGrid(); + this._resizeGrid(); + }, + + /** * Renders the grid, including cells and sites. */ - refresh() { + _refreshGrid() { let cell = document.createElementNS(HTML_NAMESPACE, "div"); cell.classList.add("newtab-cell"); // Creates all the cells up to the maximum let fragment = document.createDocumentFragment(); for (let i = 0; i < gGridPrefs.gridColumns * gGridPrefs.gridRows; i++) { fragment.appendChild(cell.cloneNode(true)); }
--- a/browser/base/content/newtab/sites.js +++ b/browser/base/content/newtab/sites.js @@ -45,23 +45,29 @@ Site.prototype = { get cell() { let parentNode = this.node.parentNode; return parentNode && parentNode._newtabCell; }, /** * Pins the site on its current or a given index. * @param aIndex The pinned index (optional). + * @return true if link changed type after pin */ pin: function Site_pin(aIndex) { if (typeof aIndex == "undefined") aIndex = this.cell.index; this._updateAttributes(true); - gPinnedLinks.pin(this._link, aIndex); + let changed = gPinnedLinks.pin(this._link, aIndex); + if (changed) { + // render site again to remove suggested/sponsored tags + this._render(); + } + return changed; }, /** * Unpins the site and calls the given callback when done. */ unpin: function Site_unpin() { if (this.isPinned()) { this._updateAttributes(false); @@ -175,16 +181,20 @@ Site.prototype = { this.node.setAttribute("type", this.link.type); let titleNode = this._querySelector(".newtab-title"); titleNode.textContent = title; if (this.link.titleBgColor) { titleNode.style.backgroundColor = this.link.titleBgColor; } + // remove "suggested" attribute to avoid showing "suggested" tag + // after site was pinned or dropped + this.node.removeAttribute("suggested"); + if (this.link.targetedSite) { if (this.node.getAttribute("type") != "sponsored") { this._querySelector(".newtab-sponsored").textContent = newTabString("suggested.tag"); } this.node.setAttribute("suggested", true); let explanation = this._getSuggestedTileExplanation(); @@ -365,17 +375,20 @@ Site.prototype = { this._toggleLegalText(".newtab-sponsored", ".sponsored-explain"); action = "sponsored"; } else if (pinned && target.classList.contains("newtab-control-pin")) { this.unpin(); action = "unpin"; } else if (!pinned && target.classList.contains("newtab-control-pin")) { - this.pin(); + if (this.pin()) { + // suggested link has changed - update rest of the pages + gAllPages.update(gPage); + } action = "pin"; } } // Report all link click actions if (action) { DirectoryLinksProvider.reportSitesAction(gGrid.sites, action, tileIndex); }
--- a/browser/components/loop/content/shared/css/conversation.css +++ b/browser/components/loop/content/shared/css/conversation.css @@ -459,21 +459,21 @@ html[dir="rtl"] .settings-menu.dropdown- } .feedback-button-container { flex: 0 1 auto; margin: 30px; align-self: center; } -.feedback-button-container button { +.feedback-button-container > button { margin: 0 30px; padding: .5em 2em; border: none; - background: #4E92DF; + background: #00A9DC; color: #fff; cursor: pointer; } /* * For any audio-only streams, we want to display our own background */ .avatar {
--- a/browser/components/loop/content/shared/img/helloicon.svg +++ b/browser/components/loop/content/shared/img/helloicon.svg @@ -1,1 +1,1 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill-rule="evenodd" clip-rule="evenodd" fill="#4E92DF" d="M32 0C14.3 0 0 12.6 0 28.1c0 7.7 3.6 14.7 9.3 19.8-1 3.5-3 8.3-6.9 12.9.7 1.2 11.7-3 19.4-6.1 3.2.9 6.6 1.5 10.2 1.5 17.7 0 32-12.6 32-28.1S49.7 0 32 0zm9.6 16.9c2.3 0 4.2 1.9 4.2 4.2 0 2.3-1.9 4.2-4.2 4.2-2.3 0-4.2-1.9-4.2-4.2-.1-2.3 1.8-4.2 4.2-4.2zm-19.3 0c2.3 0 4.2 1.9 4.2 4.2 0 2.3-1.9 4.2-4.2 4.2-2.3 0-4.2-1.9-4.2-4.2-.1-2.3 1.8-4.2 4.2-4.2zM32 47.7h-.1-.1c-8.6 0-18.1-5.5-20.3-14.9 5.8 2.7 13.8 3.8 20.4 3.8 6.6 0 14.7-1.2 20.4-3.8-2.2 9.3-11.7 14.9-20.3 14.9z"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill-rule="evenodd" clip-rule="evenodd" fill="#00A9DC" d="M32 0C14.3 0 0 12.6 0 28.1c0 7.7 3.6 14.7 9.3 19.8-1 3.5-3 8.3-6.9 12.9.7 1.2 11.7-3 19.4-6.1 3.2.9 6.6 1.5 10.2 1.5 17.7 0 32-12.6 32-28.1S49.7 0 32 0zm9.6 16.9c2.3 0 4.2 1.9 4.2 4.2 0 2.3-1.9 4.2-4.2 4.2-2.3 0-4.2-1.9-4.2-4.2-.1-2.3 1.8-4.2 4.2-4.2zm-19.3 0c2.3 0 4.2 1.9 4.2 4.2 0 2.3-1.9 4.2-4.2 4.2-2.3 0-4.2-1.9-4.2-4.2-.1-2.3 1.8-4.2 4.2-4.2zM32 47.7h-.2c-8.6 0-18.1-5.5-20.3-14.9 5.8 2.7 13.8 3.8 20.4 3.8 6.6 0 14.7-1.2 20.4-3.8-2.2 9.3-11.7 14.9-20.3 14.9z"/></svg> \ No newline at end of file
--- a/browser/components/loop/content/shared/js/otSdkDriver.js +++ b/browser/components/loop/content/shared/js/otSdkDriver.js @@ -33,23 +33,19 @@ loop.OTSdkDriver = (function() { if (!options.mozLoop) { throw new Error("Missing option mozLoop"); } this.mozLoop = options.mozLoop; } this.connections = {}; - // Metrics object to keep track of the number of connections we have + // Setup the metrics object to keep track of the number of connections we have // and the amount of streams. - this._metrics = { - connections: 0, - sendStreams: 0, - recvStreams: 0 - }; + this._resetMetrics(); this.dispatcher.register(this, [ "setupStreamElements", "setMute" ]); // Set loop.debug.twoWayMediaTelemetry to true in the browser // by changing the hidden pref loop.debug.twoWayMediaTelemetry using @@ -104,16 +100,27 @@ loop.OTSdkDriver = (function() { // always send on the publisher channel, and receive on the subscriber // channel. text: {} } }; }, /** + * Resets the metrics for the driver. + */ + _resetMetrics: function() { + this._metrics = { + connections: 0, + sendStreams: 0, + recvStreams: 0 + }; + }, + + /** * Handles the setupStreamElements action. Saves the required data and * kicks off the initialising of the publisher. * * @param {sharedActions.SetupStreamElements} actionData The data associated * with the action. See action.js. */ setupStreamElements: function(actionData) { this.publisherConfig = actionData.publisherConfig; @@ -288,16 +295,19 @@ loop.OTSdkDriver = (function() { } if (this.publisher) { this.publisher.off("accessAllowed accessDenied accessDialogOpened " + "streamCreated streamDestroyed"); this.publisher.destroy(); delete this.publisher; } + // Now reset the metrics as well. + this._resetMetrics(); + this._noteConnectionLengthIfNeeded(this._getTwoWayMediaStartTime(), performance.now()); // Also, tidy these variables ready for next time. delete this._sessionConnected; delete this._publisherReady; delete this._publishedLocalStream; delete this._subscribedRemoteStream; delete this._mockPublisherEl;
--- a/browser/components/loop/test/shared/otSdkDriver_test.js +++ b/browser/components/loop/test/shared/otSdkDriver_test.js @@ -108,16 +108,29 @@ describe("loop.OTSdkDriver", function () }).to.Throw(/dispatcher/); }); it("should throw an error if the sdk is missing", function() { expect(function() { new loop.OTSdkDriver({dispatcher: dispatcher}); }).to.Throw(/sdk/); }); + + it("should set the metrics to zero", function() { + driver = new loop.OTSdkDriver({ + dispatcher: dispatcher, + sdk: sdk + }); + + expect(driver._metrics).eql({ + connections: 0, + sendStreams: 0, + recvStreams: 0 + }); + }); }); describe("#setupStreamElements", function() { it("should call initPublisher", function() { driver.setupStreamElements(new sharedActions.SetupStreamElements({ publisherConfig: publisherConfig })); @@ -469,16 +482,32 @@ describe("loop.OTSdkDriver", function () }); }); driver.disconnectSession(); expect(subscribedEvents).eql([]); }); + it("should reset the metrics to zero", function() { + driver._metrics = { + connections: 1, + sendStreams: 2, + recvStreams: 3 + }; + + driver.disconnectSession(); + + expect(driver._metrics).eql({ + connections: 0, + sendStreams: 0, + recvStreams: 0 + }); + }); + it("should dispatch a DataChannelsAvailable action with available = false", function() { driver.disconnectSession(); sinon.assert.called(dispatcher.dispatch); sinon.assert.calledWithExactly(dispatcher.dispatch, new sharedActions.DataChannelsAvailable({ available: false }));
--- a/browser/components/loop/ui/ui-showcase.js +++ b/browser/components/loop/ui/ui-showcase.js @@ -22,16 +22,17 @@ var ContactDropdown = loop.contacts.ContactDropdown; var ContactDetail = loop.contacts.ContactDetail; var GettingStartedView = loop.panel.GettingStartedView; // 1.2. Conversation Window var AcceptCallView = loop.conversationViews.AcceptCallView; var DesktopPendingConversationView = loop.conversationViews.PendingConversationView; var OngoingConversationView = loop.conversationViews.OngoingConversationView; var DirectCallFailureView = loop.conversationViews.DirectCallFailureView; + var DesktopRoomEditContextView = loop.roomViews.DesktopRoomEditContextView; var RoomFailureView = loop.roomViews.RoomFailureView; var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView; // 2. Standalone webapp var HomeView = loop.webapp.HomeView; var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView; var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView; var StandaloneRoomView = loop.standaloneRoomViews.StandaloneRoomView; @@ -1390,16 +1391,33 @@ localPosterUrl: "sample-img/video-screen-local.png", mozLoop: navigator.mozLoop, onCallTerminated: function(){}, roomState: ROOM_STATES.INIT, roomStore: invitationRoomStore}) ) ), + React.createElement(FramedExample, {height: 278.6, + onContentsRendered: invitationRoomStore.activeRoomStore.forcedUpdate, + summary: "Desktop room Edit Context w/Error", + width: 298}, + React.createElement("div", {className: "fx-embedded room-invitation-overlay"}, + React.createElement(DesktopRoomEditContextView, { + dispatcher: dispatcher, + error: {}, + mozLoop: navigator.mozLoop, + onClose: function(){}, + roomData: {}, + savingContext: false, + show: true} + ) + ) + ), + React.createElement(FramedExample, {dashed: true, height: 394, onContentsRendered: desktopRoomStoreLoading.activeRoomStore.forcedUpdate, summary: "Desktop room conversation (loading)", width: 298}, /* Hide scrollbars here. Rotating loading div overflows and causes scrollbars to appear */ React.createElement("div", {className: "fx-embedded overflow-hidden"},
--- a/browser/components/loop/ui/ui-showcase.jsx +++ b/browser/components/loop/ui/ui-showcase.jsx @@ -22,16 +22,17 @@ var ContactDropdown = loop.contacts.ContactDropdown; var ContactDetail = loop.contacts.ContactDetail; var GettingStartedView = loop.panel.GettingStartedView; // 1.2. Conversation Window var AcceptCallView = loop.conversationViews.AcceptCallView; var DesktopPendingConversationView = loop.conversationViews.PendingConversationView; var OngoingConversationView = loop.conversationViews.OngoingConversationView; var DirectCallFailureView = loop.conversationViews.DirectCallFailureView; + var DesktopRoomEditContextView = loop.roomViews.DesktopRoomEditContextView; var RoomFailureView = loop.roomViews.RoomFailureView; var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView; // 2. Standalone webapp var HomeView = loop.webapp.HomeView; var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView; var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView; var StandaloneRoomView = loop.standaloneRoomViews.StandaloneRoomView; @@ -1390,16 +1391,33 @@ localPosterUrl="sample-img/video-screen-local.png" mozLoop={navigator.mozLoop} onCallTerminated={function(){}} roomState={ROOM_STATES.INIT} roomStore={invitationRoomStore} /> </div> </FramedExample> + <FramedExample height={278.6} + onContentsRendered={invitationRoomStore.activeRoomStore.forcedUpdate} + summary="Desktop room Edit Context w/Error" + width={298}> + <div className="fx-embedded room-invitation-overlay"> + <DesktopRoomEditContextView + dispatcher={dispatcher} + error={{}} + mozLoop={navigator.mozLoop} + onClose={function(){}} + roomData={{}} + savingContext={false} + show={true} + /> + </div> + </FramedExample> + <FramedExample dashed={true} height={394} onContentsRendered={desktopRoomStoreLoading.activeRoomStore.forcedUpdate} summary="Desktop room conversation (loading)" width={298}> {/* Hide scrollbars here. Rotating loading div overflows and causes scrollbars to appear */} <div className="fx-embedded overflow-hidden">
--- a/browser/components/uitour/test/browser_UITour_heartbeat.js +++ b/browser/components/uitour/test/browser_UITour_heartbeat.js @@ -4,16 +4,17 @@ "use strict"; var gTestTab; var gContentAPI; var gContentWindow; function test() { UITourTest(); + requestLongerTimeout(2); } function getHeartbeatNotification(aId, aChromeWindow = window) { let notificationBox = aChromeWindow.document.getElementById("high-priority-global-notificationbox"); // UITour.jsm prefixes the notification box ID with "heartbeat-" to prevent collisions. return notificationBox.getNotificationWithValue("heartbeat-" + aId); }
--- a/browser/components/uitour/test/head.js +++ b/browser/components/uitour/test/head.js @@ -246,38 +246,41 @@ function UITourTest() { gBrowser.removeTab(gTestTab); delete window.gTestTab; Services.prefs.clearUserPref("browser.uitour.enabled", true); Services.perms.remove(testHttpsUri, "uitour"); Services.perms.remove(testHttpUri, "uitour"); }); function done() { + info("== Done test, doing shared checks before teardown =="); executeSoon(() => { if (gTestTab) gBrowser.removeTab(gTestTab); gTestTab = null; let highlight = document.getElementById("UITourHighlightContainer"); is_element_hidden(highlight, "Highlight should be closed/hidden after UITour tab is closed"); let tooltip = document.getElementById("UITourTooltip"); is_element_hidden(tooltip, "Tooltip should be closed/hidden after UITour tab is closed"); ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up"); ok(!PanelUI.panel.hasAttribute("panelopen"), "The panel shouldn't have @panelopen"); isnot(PanelUI.panel.state, "open", "The panel shouldn't be open"); is(document.getElementById("PanelUI-menu-button").hasAttribute("open"), false, "Menu button should know that the menu is closed"); + info("Done shared checks"); executeSoon(nextTest); }); } function nextTest() { if (tests.length == 0) { + info("finished tests in this file"); finish(); return; } let test = tests.shift(); info("Starting " + test.name); waitForFocus(function() { loadUITourTestPage(function() { test(done);
--- a/browser/locales/en-US/chrome/browser/devtools/performance.dtd +++ b/browser/locales/en-US/chrome/browser/devtools/performance.dtd @@ -30,17 +30,22 @@ - when running on a build that can run multiprocess Firefox, but just is not enabled. --> <!ENTITY performanceUI.disabledRealTime.disabledE10S "Enable multiprocess Firefox in preferences for rendering recording data in realtime."> <!-- LOCALIZATION NOTE (performanceUI.bufferStatusFull): This string - is displayed when the profiler's circular buffer has started to overlap. --> <!ENTITY performanceUI.bufferStatusFull "The buffer is full. Older samples are now being overwritten."> <!-- LOCALIZATION NOTE (performanceUI.loadingNotice): This is the label shown - - in the call list view while loading a profile. --> + - in the details view while the profiler is unavailable, for example, while + - in Private Browsing mode. --> +<!ENTITY performanceUI.unavailableNoticePB "Recording a profile is currently unavailable. Please close all private browsing windows and try again."> + +<!-- LOCALIZATION NOTE (performanceUI.loadingNotice): This is the label shown + - in the details view while loading a profile. --> <!ENTITY performanceUI.loadingNotice "Loading…"> <!-- LOCALIZATION NOTE (performanceUI.recordButton): This string is displayed - on a button that starts a new profile. --> <!ENTITY performanceUI.recordButton.tooltip "Toggle the recording state of a performance recording."> <!-- LOCALIZATION NOTE (performanceUI.importButton): This string is displayed - on a button that opens a dialog to import a saved profile data file. -->
--- a/browser/themes/shared/tabs.inc.css +++ b/browser/themes/shared/tabs.inc.css @@ -432,30 +432,28 @@ .tabbrowser-tab::before { width: 1px; -moz-margin-start: -1px; background-image: linear-gradient(transparent 5px, currentColor 5px, currentColor calc(100% - 4px), transparent calc(100% - 4px)); opacity: 0.2; - content: ""; - display: -moz-box; - visibility: hidden; } #TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-tab::before, #TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-tab::after { opacity: 0.4; } /* Also show separators beside the selected tab when dragging it. */ #tabbrowser-tabs[movingtab] > .tabbrowser-tab[beforeselected]:not([last-visible-tab])::after, .tabbrowser-tab:not([visuallyselected]):not([afterselected-visible]):not([afterhovered]):not([first-visible-tab]):not(:hover)::before, #tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([visuallyselected]):not([beforehovered]):not(:hover)::after { - visibility: visible; + content: ""; + display: -moz-box; } /* New tab button */ .tabs-newtab-button { width: calc(36px + @tabCurveWidth@); }
--- a/devtools/client/framework/target.js +++ b/devtools/client/framework/target.js @@ -312,20 +312,38 @@ TabTarget.prototype = { get tab() { return this._tab; }, get form() { return this._form; }, + // Get a promise of the root form returned by a listTabs request. This promise + // is cached. get root() { + if (!this._root) { + this._root = this._getRoot(); + } return this._root; }, + _getRoot: function () { + return new Promise((resolve, reject) => { + this.client.listTabs(response => { + if (response.error) { + reject(new Error(response.error + ": " + response.message)); + return; + } + + resolve(response); + }); + }); + }, + get client() { return this._client; }, // Tells us if we are debugging content document // or if we are debugging chrome stuff. // Allows to controls which features are available against // a chrome or a content document.
--- a/devtools/client/framework/test/shared-head.js +++ b/devtools/client/framework/test/shared-head.js @@ -1,19 +1,28 @@ /* 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/. */ // This shared-head.js file is used for multiple directories in devtools. + const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; -const {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); -const {gDevTools} = Cu.import("resource:///modules/devtools/client/framework/gDevTools.jsm", {}); -const {console} = Cu.import("resource://gre/modules/devtools/shared/Console.jsm", {}); -const {ScratchpadManager} = Cu.import("resource:///modules/devtools/client/scratchpad/scratchpad-manager.jsm", {}); -const {require} = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {}); + +function scopedCuImport(path) { + const scope = {}; + Cu.import(path, scope); + return scope; +} + +const {Services} = scopedCuImport("resource://gre/modules/Services.jsm"); +const {gDevTools} = scopedCuImport("resource:///modules/devtools/client/framework/gDevTools.jsm"); +const {console} = scopedCuImport("resource://gre/modules/devtools/shared/Console.jsm"); +const {ScratchpadManager} = scopedCuImport("resource:///modules/devtools/client/scratchpad/scratchpad-manager.jsm"); +const {require} = scopedCuImport("resource://gre/modules/devtools/shared/Loader.jsm"); + const {TargetFactory} = require("devtools/client/framework/target"); const DevToolsUtils = require("devtools/shared/DevToolsUtils"); const promise = require("promise"); const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/")); const CHROME_URL_ROOT = TEST_DIR + "/"; const URL_ROOT = CHROME_URL_ROOT.replace("chrome://mochitests/content/", "http://example.com/"); @@ -64,16 +73,35 @@ function addTab(url) { gBrowser.selectedBrowser.removeEventListener("load", onload, true); info("URL '" + url + "' loading complete"); def.resolve(tab); }, true); return def.promise; } +/** + * Remove the given tab. + * @param {Object} tab The tab to be removed. + * @return Promise<undefined> resolved when the tab is successfully removed. + */ +function removeTab(tab) { + info("Removing tab."); + return new Promise(resolve => { + let tabContainer = gBrowser.tabContainer; + tabContainer.addEventListener("TabClose", function onClose(aEvent) { + tabContainer.removeEventListener("TabClose", onClose, false); + info("Tab removed and finished closing."); + resolve(); + }, false); + + gBrowser.removeTab(tab); + }); +} + function synthesizeKeyFromKeyTag(aKeyId, document) { let key = document.getElementById(aKeyId); isnot(key, null, "Successfully retrieved the <key> node"); let modifiersAttr = key.getAttribute("modifiers"); let name = null;
--- a/devtools/client/jar.mn +++ b/devtools/client/jar.mn @@ -34,42 +34,42 @@ devtools.jar: content/layoutview/view.js (layoutview/view.js) content/layoutview/view.xhtml (layoutview/view.xhtml) content/fontinspector/font-inspector.js (fontinspector/font-inspector.js) content/fontinspector/font-inspector.xhtml (fontinspector/font-inspector.xhtml) content/fontinspector/font-inspector.css (fontinspector/font-inspector.css) content/animationinspector/animation-controller.js (animationinspector/animation-controller.js) content/animationinspector/animation-panel.js (animationinspector/animation-panel.js) content/animationinspector/animation-inspector.xhtml (animationinspector/animation-inspector.xhtml) - content/sourceeditor/codemirror/codemirror.js (sourceeditor/codemirror/codemirror.js) - content/sourceeditor/codemirror/codemirror.css (sourceeditor/codemirror/codemirror.css) + content/sourceeditor/codemirror/comment/comment.js (sourceeditor/codemirror/addon/comment/comment.js) + content/sourceeditor/codemirror/edit/trailingspace.js (sourceeditor/codemirror/addon/edit/trailingspace.js) + content/sourceeditor/codemirror/edit/matchbrackets.js (sourceeditor/codemirror/addon/edit/matchbrackets.js) + content/sourceeditor/codemirror/edit/closebrackets.js (sourceeditor/codemirror/addon/edit/closebrackets.js) + content/sourceeditor/codemirror/dialog/dialog.js (sourceeditor/codemirror/addon/dialog/dialog.js) + content/sourceeditor/codemirror/dialog/dialog.css (sourceeditor/codemirror/addon/dialog/dialog.css) + content/sourceeditor/codemirror/fold/foldcode.js (sourceeditor/codemirror/addon/fold/foldcode.js) + content/sourceeditor/codemirror/fold/brace-fold.js (sourceeditor/codemirror/addon/fold/brace-fold.js) + content/sourceeditor/codemirror/fold/comment-fold.js (sourceeditor/codemirror/addon/fold/comment-fold.js) + content/sourceeditor/codemirror/fold/xml-fold.js (sourceeditor/codemirror/addon/fold/xml-fold.js) + content/sourceeditor/codemirror/fold/foldgutter.js (sourceeditor/codemirror/addon/fold/foldgutter.js) + content/sourceeditor/codemirror/hint/show-hint.js (sourceeditor/codemirror/addon/hint/show-hint.js) + content/sourceeditor/codemirror/search/search.js (sourceeditor/codemirror/addon/search/search.js) + content/sourceeditor/codemirror/search/searchcursor.js (sourceeditor/codemirror/addon/search/searchcursor.js) + content/sourceeditor/codemirror/selection/active-line.js (sourceeditor/codemirror/addon/selection/active-line.js) + content/sourceeditor/codemirror/tern/tern.js (sourceeditor/codemirror/addon/tern/tern.js) + content/sourceeditor/codemirror/codemirror.js (sourceeditor/codemirror/lib/codemirror.js) + content/sourceeditor/codemirror/codemirror.css (sourceeditor/codemirror/lib/codemirror.css) content/sourceeditor/codemirror/mode/javascript.js (sourceeditor/codemirror/mode/javascript.js) content/sourceeditor/codemirror/mode/xml.js (sourceeditor/codemirror/mode/xml.js) content/sourceeditor/codemirror/mode/css.js (sourceeditor/codemirror/mode/css.js) content/sourceeditor/codemirror/mode/htmlmixed.js (sourceeditor/codemirror/mode/htmlmixed.js) content/sourceeditor/codemirror/mode/clike.js (sourceeditor/codemirror/mode/clike.js) - content/sourceeditor/codemirror/selection/active-line.js (sourceeditor/codemirror/selection/active-line.js) - content/sourceeditor/codemirror/edit/trailingspace.js (sourceeditor/codemirror/edit/trailingspace.js) - content/sourceeditor/codemirror/edit/matchbrackets.js (sourceeditor/codemirror/edit/matchbrackets.js) - content/sourceeditor/codemirror/edit/closebrackets.js (sourceeditor/codemirror/edit/closebrackets.js) - content/sourceeditor/codemirror/comment/comment.js (sourceeditor/codemirror/comment/comment.js) - content/sourceeditor/codemirror/search/searchcursor.js (sourceeditor/codemirror/search/searchcursor.js) - content/sourceeditor/codemirror/search/search.js (sourceeditor/codemirror/search/search.js) - content/sourceeditor/codemirror/dialog/dialog.js (sourceeditor/codemirror/dialog/dialog.js) - content/sourceeditor/codemirror/dialog/dialog.css (sourceeditor/codemirror/dialog/dialog.css) content/sourceeditor/codemirror/keymap/emacs.js (sourceeditor/codemirror/keymap/emacs.js) content/sourceeditor/codemirror/keymap/sublime.js (sourceeditor/codemirror/keymap/sublime.js) content/sourceeditor/codemirror/keymap/vim.js (sourceeditor/codemirror/keymap/vim.js) - content/sourceeditor/codemirror/fold/foldcode.js (sourceeditor/codemirror/fold/foldcode.js) - content/sourceeditor/codemirror/fold/brace-fold.js (sourceeditor/codemirror/fold/brace-fold.js) - content/sourceeditor/codemirror/fold/comment-fold.js (sourceeditor/codemirror/fold/comment-fold.js) - content/sourceeditor/codemirror/fold/xml-fold.js (sourceeditor/codemirror/fold/xml-fold.js) - content/sourceeditor/codemirror/fold/foldgutter.js (sourceeditor/codemirror/fold/foldgutter.js) - content/sourceeditor/codemirror/tern/tern.js (sourceeditor/codemirror/tern/tern.js) - content/sourceeditor/codemirror/hint/show-hint.js (sourceeditor/codemirror/hint/show-hint.js) content/sourceeditor/codemirror/mozilla.css (sourceeditor/codemirror/mozilla.css) content/debugger/debugger.xul (debugger/debugger.xul) content/debugger/debugger.css (debugger/debugger.css) content/debugger/debugger-controller.js (debugger/debugger-controller.js) content/debugger/debugger-view.js (debugger/debugger-view.js) content/debugger/views/workers-view.js (debugger/views/workers-view.js) content/debugger/views/sources-view.js (debugger/views/sources-view.js) content/debugger/views/variable-bubble-view.js (debugger/views/variable-bubble-view.js) @@ -106,17 +106,17 @@ devtools.jar: content/performance/views/details-waterfall.js (performance/views/details-waterfall.js) content/performance/views/details-js-call-tree.js (performance/views/details-js-call-tree.js) content/performance/views/details-js-flamegraph.js (performance/views/details-js-flamegraph.js) content/performance/views/details-memory-call-tree.js (performance/views/details-memory-call-tree.js) content/performance/views/details-memory-flamegraph.js (performance/views/details-memory-flamegraph.js) content/performance/views/optimizations-list.js (performance/views/optimizations-list.js) content/performance/views/recordings.js (performance/views/recordings.js) content/memory/memory.xhtml (memory/memory.xhtml) - content/memory/controller.js (memory/controller.js) + content/memory/initializer.js (memory/initializer.js) content/promisedebugger/promise-controller.js (promisedebugger/promise-controller.js) content/promisedebugger/promise-panel.js (promisedebugger/promise-panel.js) content/promisedebugger/promise-debugger.xhtml (promisedebugger/promise-debugger.xhtml) content/commandline/commandline.css (commandline/commandline.css) content/commandline/commandlineoutput.xhtml (commandline/commandlineoutput.xhtml) content/commandline/commandlinetooltip.xhtml (commandline/commandlinetooltip.xhtml) * content/framework/toolbox-window.xul (framework/toolbox-window.xul) content/framework/toolbox-options.xul (framework/toolbox-options.xul)
new file mode 100644 --- /dev/null +++ b/devtools/client/memory/actions/moz.build @@ -0,0 +1,8 @@ +# 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/. + +DevToolsModules( + 'snapshot.js', +)
new file mode 100644 --- /dev/null +++ b/devtools/client/memory/actions/snapshot.js @@ -0,0 +1,14 @@ +/* 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/. */ +"use strict"; + +const { PROMISE } = require("devtools/client/shared/redux/middleware/promise"); +const { actions } = require("../constants"); + +const takeSnapshot = exports.takeSnapshot = function takeSnapshot (front) { + return { + type: actions.TAKE_SNAPSHOT, + [PROMISE]: front.saveHeapSnapshot() + }; +};
new file mode 100644 --- /dev/null +++ b/devtools/client/memory/constants.js @@ -0,0 +1,9 @@ +/* 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/. */ +"use strict"; + +const actions = exports.actions = {}; + +// Fired by UI to request a snapshot from the actor. +actions.TAKE_SNAPSHOT = "take-snapshot";
--- a/devtools/client/memory/controller.js +++ b/devtools/client/memory/controller.js @@ -1,28 +1,28 @@ /* 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/. */ "use strict"; -const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; -const { loader, require } = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {}); - const { Task } = require("resource://gre/modules/Task.jsm"); -const { Heritage, ViewHelpers, WidgetMethods } = require("resource:///modules/devtools/client/shared/widgets/ViewHelpers.jsm"); +const Store = require("./store"); /** * The current target, toolbox and MemoryFront, set by this tool's host. */ var gToolbox, gTarget, gFront; -/** - * Initializes the profiler controller and views. - */ -const MemoryController = { - initialize: Task.async(function *() { +const REDUX_METHODS_TO_PIPE = ["dispatch", "subscribe", "getState"]; - }), +const MemoryController = exports.MemoryController = function ({ toolbox, target, front }) { + this.store = Store(); + this.toolbox = toolbox; + this.target = target; + this.front = front; +}; - destroy: Task.async(function *() { +REDUX_METHODS_TO_PIPE.map(m => + MemoryController.prototype[m] = function (...args) { return this.store[m](...args); }); - }) +MemoryController.prototype.destroy = function () { + this.store = this.toolbox = this.target = this.front = null; };
new file mode 100644 --- /dev/null +++ b/devtools/client/memory/initializer.js @@ -0,0 +1,30 @@ +/* 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/. */ +"use strict"; + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; +const { require } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); +const { Task } = require("resource://gre/modules/Task.jsm"); +const { MemoryController } = require("devtools/client/memory/controller"); + +/** + * The current target, toolbox and MemoryFront, set by this tool's host. + */ +let gToolbox, gTarget, gFront; + +/** + * Initializes the profiler controller and views. + */ +var controller = null; +function initialize () { + return Task.spawn(function *() { + controller = new MemoryController({ toolbox: gToolbox, target: gTarget, front: gFront }); + }); +} + +function destroy () { + return Task.spawn(function *() { + controller.destroy(); + }); +}
--- a/devtools/client/memory/memory.xhtml +++ b/devtools/client/memory/memory.xhtml @@ -7,33 +7,31 @@ ]> <!-- 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/. --> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <link rel="stylesheet" href="chrome://browser/skin/" type="text/css"/> - <link rel="stylesheet" href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"/> - <link rel="stylesheet" href="chrome://devtools/skin/themes/common.css" type="text/css"/> - <link rel="stylesheet" href="chrome://devtools/skin/themes/widgets.css" type="text/css"/> - <link rel="stylesheet" href="chrome://devtools/skin/themes/memory.css" type="text/css"/> + <link rel="stylesheet" href="chrome://browser/content/devtools/widgets.css" type="text/css"/> + <link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/> + <link rel="stylesheet" href="chrome://browser/skin/devtools/widgets.css" type="text/css"/> + <link rel="stylesheet" href="chrome://browser/skin/devtools/memory.css" type="text/css"/> <script type="application/javascript;version=1.8" - src="chrome://devtools/content/shared/theme-switching.js"></script> + src="chrome://devtools/content/shared/theme-switching.js"/> <script type="application/javascript;version=1.8" - src="controller.js"></script> + src="initializer.js"></script> </head> <body class="theme-body"> - <toolbar class="devtools-toolbar"> - <toolbarbutton id="snapshot-button" class="devtools-toolbarbutton" - tabindex="0"/> - <spacer flex="1"></spacer> - </toolbar> - <splitter class="devtools-horizontal-splitter"/> + <div class="devtools-toolbar"> + <div id="snapshot-button" class="devtools-toolbarbutton" /> + </div> + <div class="devtools-horizontal-splitter"></div> <div id="memory-content" class="devtools-responsive-container" flex="1"> <toolbar class="devtools-toolbar"> <spacer flex="1"></spacer> </toolbar> <hbox flex="1"> </hbox>
--- a/devtools/client/memory/modules/census-view.js +++ b/devtools/client/memory/modules/census-view.js @@ -4,17 +4,16 @@ "use strict"; /** * This file contains the tree view, displaying all the samples and frames * received from the proviler in a tree-like structure. */ const { Cc, Ci, Cu, Cr } = require("chrome"); -const { L10N } = require("devtools/client/performance/modules/global"); const { Heritage } = require("resource:///modules/devtools/client/shared/widgets/ViewHelpers.jsm"); const { AbstractTreeItem } = require("resource:///modules/devtools/client/shared/widgets/AbstractTreeItem.jsm"); const INDENTATION = exports.INDENTATION = 16; // px const DEFAULT_AUTO_EXPAND_DEPTH = 2; const COURSE_TYPES = ["objects", "scripts", "strings", "other"]; /**
--- a/devtools/client/memory/moz.build +++ b/devtools/client/memory/moz.build @@ -1,14 +1,22 @@ # 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/. DIRS += [ + 'actions', 'modules', + 'reducers', ] DevToolsModules( + 'constants.js', + 'controller.js', + 'initializer.js', 'panel.js', + 'reducers.js', + 'store.js', ) MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini'] +XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
--- a/devtools/client/memory/panel.js +++ b/devtools/client/memory/panel.js @@ -21,40 +21,47 @@ function MemoryPanel (iframeWindow, tool MemoryPanel.prototype = { open: Task.async(function *() { if (this._opening) { return this._opening; } this.panelWin.gToolbox = this._toolbox; this.panelWin.gTarget = this.target; - this.panelWin.gFront = new MemoryFront(this.target.client, this.target.form); - console.log(this.panelWin, this.panelWin.MemoryController); - return this._opening = this.panelWin.MemoryController.initialize().then(() => { + const rootForm = yield this.target.root; + this.panelWin.gFront = new MemoryFront(this.target.client, + this.target.form, + rootForm); + + yield this.panelWin.gFront.attach(); + return this._opening = this.panelWin.initialize().then(() => { this.isReady = true; this.emit("ready"); return this; }); + return this._opening; }), // DevToolPanel API get target() { return this._toolbox.target; }, - destroy: function () { + destroy: Task.async(function *() { // Make sure this panel is not already destroyed. if (this._destroyer) { return this._destroyer; } - return this._destroyer = this.panelWin.MemoryController.destroy().then(() => { + yield this.panelWin.gFront.detach(); + return this._destroyer = this.panelWin.destroy().then(() => { // Destroy front to ensure packet handler is removed from client this.panelWin.gFront.destroy(); + this.panelWin = null; this.emit("destroyed"); return this; }); - } + }) }; exports.MemoryPanel = MemoryPanel;
new file mode 100644 --- /dev/null +++ b/devtools/client/memory/reducers.js @@ -0,0 +1,1 @@ +exports.snapshots = require("./reducers/snapshot");
new file mode 100644 --- /dev/null +++ b/devtools/client/memory/reducers/moz.build @@ -0,0 +1,8 @@ +# 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/. + +DevToolsModules( + 'snapshot.js', +)
new file mode 100644 --- /dev/null +++ b/devtools/client/memory/reducers/snapshot.js @@ -0,0 +1,37 @@ +const { actions } = require("../constants"); +const { PROMISE } = require("devtools/client/shared/redux/middleware/promise"); +const DevToolsUtils = require("devtools/shared/DevToolsUtils"); + +function handleTakeSnapshot (state, action) { + switch (action.status) { + + case "start": + return [...state, { + id: action.seqId, + status: action.status + }]; + + case "done": + let snapshot = state.find(s => s.id === action.seqId); + if (!snapshot) { + DevToolsUtils.reportException(`No snapshot with id "${action.seqId}" for TAKE_SNAPSHOT`); + break; + } + snapshot.status = "done"; + snapshot.snapshotId = action.value; + return [...state]; + + case "error": + DevToolsUtils.reportException(`No async state found for ${action.type}`); + } + return [...state]; +} + +module.exports = function (state=[], action) { + switch (action.type) { + case actions.TAKE_SNAPSHOT: + return handleTakeSnapshot(state, action); + } + + return state; +};
new file mode 100644 --- /dev/null +++ b/devtools/client/memory/store.js @@ -0,0 +1,8 @@ +const { combineReducers } = require("../shared/vendor/redux"); +const createStore = require("../shared/redux/create-store"); +const reducers = require("./reducers"); +const DevToolsUtils = require("devtools/shared/DevToolsUtils"); + +module.exports = function () { + return createStore({ log: DevToolsUtils.testing })(combineReducers(reducers), {}); +};
new file mode 100644 --- /dev/null +++ b/devtools/client/memory/test/browser/browser.ini @@ -0,0 +1,7 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +support-files = + head.js + +[browser_memory_transferHeapSnapshot_e10s_01.js]
new file mode 100644 --- /dev/null +++ b/devtools/client/memory/test/browser/browser_memory_transferHeapSnapshot_e10s_01.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that we can save a heap snapshot and transfer it over the RDP in e10s +// where the child process is sandboxed and so we have to use +// HeapSnapshotFileActor to get the heap snapshot file. + +"use strict"; + +const TEST_URL = "data:text/html,<html><body></body></html>"; + +this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) { + const memoryFront = panel.panelWin.gFront; + ok(memoryFront, "Should get the MemoryFront"); + + const snapshotFilePath = yield memoryFront.saveHeapSnapshot({ + // Force a copy so that we go through the HeapSnapshotFileActor's + // transferHeapSnapshot request and exercise this code path on e10s. + forceCopy: true + }); + + ok(!!(yield OS.File.stat(snapshotFilePath)), + "Should have the heap snapshot file"); + + const snapshot = ChromeUtils.readHeapSnapshot(snapshotFilePath); + ok(snapshot instanceof HeapSnapshot, + "And we should be able to read a HeapSnapshot instance from the file"); +});
new file mode 100644 --- /dev/null +++ b/devtools/client/memory/test/browser/head.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Load the shared test helpers into this compartment. +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", + this); + +Services.prefs.setBoolPref("devtools.memory.enabled", true); + +/** + * Open the memory panel for the given tab. + */ +this.openMemoryPanel = Task.async(function* (tab) { + info("Opening memory panel."); + const target = TargetFactory.forTab(tab); + const toolbox = yield gDevTools.showToolbox(target, "memory"); + info("Memory panel shown successfully."); + let panel = toolbox.getCurrentPanel(); + return { tab, panel }; +}); + +/** + * Close the memory panel for the given tab. + */ +this.closeMemoryPanel = Task.async(function* (tab) { + info("Closing memory panel."); + const target = TargetFactory.forTab(tab); + const toolbox = gDevTools.getToolbox(target); + yield toolbox.destroy(); + info("Closed memory panel successfully."); +}); + +/** + * Return a test function that adds a tab with the given url, opens the memory + * panel, runs the given generator, closes the memory panel, removes the tab, + * and finishes. + * + * Example usage: + * + * this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) { + * // Your tests go here... + * }); + */ +function makeMemoryTest(url, generator) { + return Task.async(function* () { + waitForExplicitFinish(); + + const tab = yield addTab(url); + const results = yield openMemoryPanel(tab); + + try { + yield* generator(results); + } catch (err) { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(err)); + } + + yield closeMemoryPanel(tab); + yield removeTab(tab); + + finish(); + }); +}
--- a/devtools/client/memory/test/mochitest/head.js +++ b/devtools/client/memory/test/mochitest/head.js @@ -4,9 +4,9 @@ "use strict"; const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; const Cr = Components.results; const CC = Components.Constructor; -const { require } = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {}); +const { require } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
--- a/devtools/client/memory/test/mochitest/test_census-view-01.html +++ b/devtools/client/memory/test/mochitest/test_census-view-01.html @@ -2,21 +2,21 @@ <html> <!-- Bug 1067491 - Test taking a census over the RDP. --> <head> <meta charset="utf-8"> <title>Census Tree 01</title> <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> - <link href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css" /> - <link href="chrome://devtools/skin/themes/light-theme.css" type="text/css" /> - <link href="chrome://devtools/skin/themes/common.css" type="text/css" /> - <link href="chrome://devtools/skin/themes/widgets.css" type="text/css" /> - <link href="chrome://devtools/skin/themes/memory.css" type="text/css" /> + <link href="chrome://browser/content/devtools/widgets.css" type="text/css" /> + <link href="chrome://browser/skin/devtools/light-theme.css" type="text/css" /> + <link href="chrome://browser/skin/devtools/common.css" type="text/css" /> + <link href="chrome://browser/skin/devtools/widgets.css" type="text/css" /> + <link href="chrome://browser/skin/devtools/memory.css" type="text/css" /> </head> <body> <ul id="container" style="width:100%;height:300px;"></ul> <pre id="test"> <script src="head.js" type="application/javascript;version=1.8"></script> <script> window.onload = function() { var { CensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
new file mode 100644 --- /dev/null +++ b/devtools/client/memory/test/unit/head.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; +var { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); +var { gDevTools } = Cu.import("resource:///modules/devtools/client/framework/gDevTools.jsm", {}); +var { console } = Cu.import("resource://gre/modules/devtools/shared/Console.jsm", {}); +var { require } = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {}); +var { TargetFactory } = require("devtools/client/framework/target"); +var DevToolsUtils = require("devtools/shared/DevToolsUtils"); +var promise = require("promise"); +var { Task } = Cu.import("resource://gre/modules/Task.jsm", {}); +var { MemoryController } = require("devtools/client/memory/controller"); +var { expectState } = require("devtools/server/actors/common"); +var HeapSnapshotFileUtils = require("devtools/shared/heapsnapshot/HeapSnapshotFileUtils"); +var { addDebuggerToGlobal } = require("resource://gre/modules/jsdebugger.jsm"); +var SYSTEM_PRINCIPAL = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); +var { setTimeout } = require("sdk/timers"); + +DevToolsUtils.testing = true; + +function initDebugger () { + let global = new Cu.Sandbox(SYSTEM_PRINCIPAL, { freshZone: true }); + addDebuggerToGlobal(global); + return new global.Debugger(); +} + +function StubbedMemoryFront () { + this.dbg = initDebugger(); +} + +StubbedMemoryFront.prototype.attach = Task.async(function *() { + this.state = "attached"; +}); + +StubbedMemoryFront.prototype.detach = Task.async(function *() { + this.state = "detached"; +}); + +StubbedMemoryFront.prototype.saveHeapSnapshot = expectState("attached", Task.async(function *() { + let path = ThreadSafeChromeUtils.saveHeapSnapshot({ debugger: this.dbg }); + return HeapSnapshotFileUtils.getSnapshotIdFromPath(path); +}), "saveHeapSnapshot"); + +function waitUntilState (store, predicate) { + let deferred = promise.defer(); + let unsubscribe = store.subscribe(() => { + if (predicate(store.getState())) { + unsubscribe(); + deferred.resolve() + } + }); + return deferred.promise; +}
new file mode 100644 --- /dev/null +++ b/devtools/client/memory/test/unit/test_action-take-snapshot.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests the async action creator `takeSnapshot(front)` + */ + +let actions = require("devtools/client/memory/actions/snapshot"); + +function run_test() { + run_next_test(); +} + +add_task(function *() { + let front = new StubbedMemoryFront(); + yield front.attach(); + let controller = new MemoryController({ toolbox: {}, target: {}, front }); + + let unsubscribe = controller.subscribe(checkState); + + let foundPendingState = false; + let foundDoneState = false; + + function checkState () { + let state = controller.getState(); + if (state.snapshots.length === 1 && state.snapshots[0].status === "start") { + foundPendingState = true; + ok(foundPendingState, "Got state change for pending heap snapshot request"); + ok(!(state.snapshots[0].snapshotId), "Snapshot does not yet have a snapshotId"); + } + if (state.snapshots.length === 1 && state.snapshots[0].status === "done") { + foundDoneState = true; + ok(foundDoneState, "Got state change for completed heap snapshot request"); + ok(state.snapshots[0].snapshotId, "Snapshot fetched with a snapshotId"); + } + if (state.snapshots.lenght === 1 && state.snapshots[0].status === "error") { + ok(false, "takeSnapshot's promise returned with an error"); + } + } + + controller.dispatch(actions.takeSnapshot(front)); + yield waitUntilState(controller, () => foundPendingState && foundDoneState); + + unsubscribe(); +});
new file mode 100644 --- /dev/null +++ b/devtools/client/memory/test/unit/xpcshell.ini @@ -0,0 +1,8 @@ +[DEFAULT] +tags = devtools +head = head.js +tail = +firefox-appdir = browser +skip-if = toolkit == 'android' || toolkit == 'gonk' + +[test_action-take-snapshot.js]
--- a/devtools/client/performance/events.js +++ b/devtools/client/performance/events.js @@ -25,16 +25,19 @@ module.exports = { // Emitted by the PerformanceView on import button click UI_IMPORT_RECORDING: "Performance:UI:ImportRecording", // Emitted by the RecordingsView on export button click UI_EXPORT_RECORDING: "Performance:UI:ExportRecording", // When a new recording is being tracked in the panel. NEW_RECORDING: "Performance:NewRecording", + // When a new recording can't be successfully created when started. + NEW_RECORDING_FAILED: "Performance:NewRecordingFailed", + // When a recording is started or stopped or stopping via the PerformanceController RECORDING_STATE_CHANGE: "Performance:RecordingStateChange", // Emitted by the PerformanceController or RecordingView // when a recording model is selected RECORDING_SELECTED: "Performance:RecordingSelected", // When recordings have been cleared out
--- a/devtools/client/performance/performance-controller.js +++ b/devtools/client/performance/performance-controller.js @@ -84,24 +84,26 @@ const BRANCH_NAME = "devtools.performanc var gToolbox, gTarget, gFront; /** * Initializes the profiler controller and views. */ var startupPerformance = Task.async(function*() { yield PerformanceController.initialize(); yield PerformanceView.initialize(); + PerformanceController.enableFrontEventListeners(); }); /** * Destroys the profiler controller and views. */ var shutdownPerformance = Task.async(function*() { yield PerformanceController.destroy(); yield PerformanceView.destroy(); + PerformanceController.disableFrontEventListeners(); }); /** * Functions handling target-related lifetime events and * UI interaction. */ var PerformanceController = { _recordings: [], @@ -126,17 +128,16 @@ var PerformanceController = { // Store data regarding if e10s is enabled. this._e10s = Services.appinfo.browserTabsRemoteAutostart; this._setMultiprocessAttributes(); this._prefs = require("devtools/client/performance/modules/global").PREFS; this._prefs.on("pref-changed", this._onPrefChanged); - gFront.on("*", this._onFrontEvent); ToolbarView.on(EVENTS.PREF_CHANGED, this._onPrefChanged); PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording); PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording); PerformanceView.on(EVENTS.UI_IMPORT_RECORDING, this.importRecording); PerformanceView.on(EVENTS.UI_CLEAR_RECORDINGS, this.clearRecordings); RecordingsView.on(EVENTS.UI_EXPORT_RECORDING, this.exportRecording); RecordingsView.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView); DetailsView.on(EVENTS.DETAILS_VIEW_SELECTED, this._pipe); @@ -146,30 +147,50 @@ var PerformanceController = { /** * Remove events handled by the PerformanceController */ destroy: function() { this._telemetry.destroy(); this._prefs.off("pref-changed", this._onPrefChanged); - gFront.off("*", this._onFrontEvent); ToolbarView.off(EVENTS.PREF_CHANGED, this._onPrefChanged); PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording); PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording); PerformanceView.off(EVENTS.UI_IMPORT_RECORDING, this.importRecording); PerformanceView.off(EVENTS.UI_CLEAR_RECORDINGS, this.clearRecordings); RecordingsView.off(EVENTS.UI_EXPORT_RECORDING, this.exportRecording); RecordingsView.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView); DetailsView.off(EVENTS.DETAILS_VIEW_SELECTED, this._pipe); gDevTools.off("pref-changed", this._onThemeChanged); }, /** + * Enables front event listeners. + * + * The rationale behind this is given by the async intialization of all the + * frontend components. Even though the panel is considered "open" only after + * both the controller and the view are created, and even though their + * initialization is sequential (controller, then view), the controller might + * start handling backend events before the view finishes if the event + * listeners are added too soon. + */ + enableFrontEventListeners: function() { + gFront.on("*", this._onFrontEvent); + }, + + /** + * Disables front event listeners. + */ + disableFrontEventListeners: function() { + gFront.off("*", this._onFrontEvent); + }, + + /** * Returns the current devtools theme. */ getTheme: function () { return Services.prefs.getCharPref("devtools.theme"); }, /** * Get a boolean preference setting from `prefName` via the underlying @@ -201,32 +222,59 @@ var PerformanceController = { * @param string prefName * @param any prefValue */ setPref: function (prefName, prefValue) { this._prefs[prefName] = prefValue; }, /** + * Checks whether or not a new recording is supported by the PerformanceFront. + * @return Promise:boolean + */ + canCurrentlyRecord: Task.async(function*() { + // If we're testing the legacy front, the performance actor will exist, + // with `canCurrentlyRecord` method; this ensures we test the legacy path. + if (gFront.LEGACY_FRONT) { + return true; + } + let hasActor = yield gTarget.hasActor("performance"); + if (!hasActor) { + return true; + } + let actorCanCheck = yield gTarget.actorHasMethod("performance", "canCurrentlyRecord"); + if (!actorCanCheck) { + return true; + } + return (yield gFront.canCurrentlyRecord()).success; + }), + + /** * Starts recording with the PerformanceFront. */ startRecording: Task.async(function *() { let options = { withMarkers: true, withMemory: this.getOption("enable-memory"), withTicks: this.getOption("enable-framerate"), withJITOptimizations: this.getOption("enable-jit-optimizations"), withAllocations: this.getOption("enable-allocations"), allocationsSampleProbability: this.getPref("memory-sample-probability"), allocationsMaxLogLength: this.getPref("memory-max-log-length"), bufferSize: this.getPref("profiler-buffer-size"), sampleFrequency: this.getPref("profiler-sample-frequency") }; - yield gFront.startRecording(options); + // In some cases, like when the target has a private browsing tab, + // recording is not currently supported because of the profiler module. + // Present a notification in this case alerting the user of this issue. + if (!(yield gFront.startRecording(options))) { + this.emit(EVENTS.NEW_RECORDING_FAILED); + PerformanceView.setState("unavailable"); + } }), /** * Stops recording with the PerformanceFront. */ stopRecording: Task.async(function *() { let recording = this.getLatestManualRecording(); yield gFront.stopRecording(recording);
--- a/devtools/client/performance/performance-view.js +++ b/devtools/client/performance/performance-view.js @@ -9,37 +9,40 @@ var PerformanceView = { _state: null, // Set to true if the front emits a "buffer-status" event, indicating // that the server has support for determining buffer status. _bufferStatusSupported: false, - // Mapping of state to selectors for different panes - // of the main profiler view. Used in `PerformanceView.setState()` + // Mapping of state to selectors for different properties and their values, + // from the main profiler view. Used in `PerformanceView.setState()` states: { - empty: [ - { deck: "#performance-view", pane: "#empty-notice" } + "unavailable": [ + { sel: "#performance-view", opt: "selectedPanel", val: () => $("#unavailable-notice") }, ], - recording: [ - { deck: "#performance-view", pane: "#performance-view-content" }, - { deck: "#details-pane-container", pane: "#recording-notice" } + "empty": [ + { sel: "#performance-view", opt: "selectedPanel", val: () => $("#empty-notice") } + ], + "recording": [ + { sel: "#performance-view", opt: "selectedPanel", val: () => $("#performance-view-content") }, + { sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#recording-notice") } ], "console-recording": [ - { deck: "#performance-view", pane: "#performance-view-content" }, - { deck: "#details-pane-container", pane: "#console-recording-notice" } + { sel: "#performance-view", opt: "selectedPanel", val: () => $("#performance-view-content") }, + { sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#console-recording-notice") } ], - recorded: [ - { deck: "#performance-view", pane: "#performance-view-content" }, - { deck: "#details-pane-container", pane: "#details-pane" } + "recorded": [ + { sel: "#performance-view", opt: "selectedPanel", val: () => $("#performance-view-content") }, + { sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#details-pane") } ], - loading: [ - { deck: "#performance-view", pane: "#performance-view-content" }, - { deck: "#details-pane-container", pane: "#loading-notice" } + "loading": [ + { sel: "#performance-view", opt: "selectedPanel", val: () => $("#performance-view-content") }, + { sel: "#details-pane-container", opt: "selectedPanel", val: () => $("#loading-notice") } ] }, /** * Sets up the view with event binding and main subviews. */ initialize: Task.async(function* () { this._recordButton = $("#main-record-button"); @@ -47,30 +50,36 @@ var PerformanceView = { this._clearButton = $("#clear-button"); this._onRecordButtonClick = this._onRecordButtonClick.bind(this); this._onImportButtonClick = this._onImportButtonClick.bind(this); this._onClearButtonClick = this._onClearButtonClick.bind(this); this._onRecordingSelected = this._onRecordingSelected.bind(this); this._onProfilerStatusUpdated = this._onProfilerStatusUpdated.bind(this); this._onRecordingStateChange = this._onRecordingStateChange.bind(this); + this._onNewRecordingFailed = this._onNewRecordingFailed.bind(this); for (let button of $$(".record-button")) { button.addEventListener("click", this._onRecordButtonClick); } this._importButton.addEventListener("click", this._onImportButtonClick); this._clearButton.addEventListener("click", this._onClearButtonClick); // Bind to controller events to unlock the record button PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected); PerformanceController.on(EVENTS.PROFILER_STATUS_UPDATED, this._onProfilerStatusUpdated); PerformanceController.on(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStateChange); PerformanceController.on(EVENTS.NEW_RECORDING, this._onRecordingStateChange); + PerformanceController.on(EVENTS.NEW_RECORDING_FAILED, this._onNewRecordingFailed); - this.setState("empty"); + if (yield PerformanceController.canCurrentlyRecord()) { + this.setState("empty"); + } else { + this.setState("unavailable"); + } // Initialize the ToolbarView first, because other views may need access // to the OptionsView via the controller, to read prefs. yield ToolbarView.initialize(); yield RecordingsView.initialize(); yield OverviewView.initialize(); yield DetailsView.initialize(); }), @@ -84,41 +93,45 @@ var PerformanceView = { } this._importButton.removeEventListener("click", this._onImportButtonClick); this._clearButton.removeEventListener("click", this._onClearButtonClick); PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected); PerformanceController.off(EVENTS.PROFILER_STATUS_UPDATED, this._onProfilerStatusUpdated); PerformanceController.off(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStateChange); PerformanceController.off(EVENTS.NEW_RECORDING, this._onRecordingStateChange); + PerformanceController.off(EVENTS.NEW_RECORDING_FAILED, this._onNewRecordingFailed); yield ToolbarView.destroy(); yield RecordingsView.destroy(); yield OverviewView.destroy(); yield DetailsView.destroy(); }), /** - * Sets the state of the profiler view. Possible options are "empty", - * "recording", "console-recording", "recorded". + * Sets the state of the profiler view. Possible options are "unavailable", + * "empty", "recording", "console-recording", "recorded". */ setState: function (state) { let viewConfig = this.states[state]; if (!viewConfig) { throw new Error(`Invalid state for PerformanceView: ${state}`); } - for (let { deck, pane } of viewConfig) { - $(deck).selectedPanel = $(pane); + for (let { sel, opt, val } of viewConfig) { + for (let el of $$(sel)) { + el[opt] = val(); + } } this._state = state; if (state === "console-recording") { let recording = PerformanceController.getCurrentRecording(); let label = recording.getLabel() || ""; + // Wrap the label in quotes if it exists for the commands. label = label ? `"${label}"` : ""; let startCommand = $(".console-profile-recording-notice .console-profile-command"); let stopCommand = $(".console-profile-stop-notice .console-profile-command"); startCommand.value = `console.profile(${label})`; stopCommand.value = `console.profileEnd(${label})`; @@ -187,63 +200,71 @@ var PerformanceView = { }, /* * Toggles the `checked` attribute on the record buttons based * on `activate`. * * @param {boolean} activate */ - _activateRecordButtons: function (activate) { + _toggleRecordButtons: function (activate) { for (let button of $$(".record-button")) { if (activate) { button.setAttribute("checked", "true"); } else { button.removeAttribute("checked"); } } }, /** * When a recording has started. */ _onRecordingStateChange: function () { let currentRecording = PerformanceController.getCurrentRecording(); let recordings = PerformanceController.getRecordings(); - this._activateRecordButtons(recordings.find(r => !r.isConsole() && r.isRecording())); + this._toggleRecordButtons(recordings.find(r => !r.isConsole() && r.isRecording())); this._lockRecordButtons(recordings.find(r => !r.isConsole() && r.isFinalizing())); if (currentRecording && currentRecording.isFinalizing()) { this.setState("loading"); } if (currentRecording && currentRecording.isCompleted()) { this.setState("recorded"); } if (currentRecording && currentRecording.isRecording()) { this.updateBufferStatus(); } }, /** + * When starting a recording has failed. + */ + _onNewRecordingFailed: function (e) { + this._lockRecordButtons(false); + this._toggleRecordButtons(false); + }, + + /** * Handler for clicking the clear button. */ _onClearButtonClick: function (e) { this.emit(EVENTS.UI_CLEAR_RECORDINGS); }, /** * Handler for clicking the record button. */ _onRecordButtonClick: function (e) { if (this._recordButton.hasAttribute("checked")) { this.emit(EVENTS.UI_STOP_RECORDING); } else { this._lockRecordButtons(true); - this._activateRecordButtons(true); + this._toggleRecordButtons(true); this.emit(EVENTS.UI_START_RECORDING); } }, /** * Handler for clicking the import button. */ _onImportButtonClick: function(e) {
--- a/devtools/client/performance/performance.xul +++ b/devtools/client/performance/performance.xul @@ -152,16 +152,40 @@ popup="performance-options-menupopup" tooltiptext="&performanceUI.options.gear.tooltiptext;"/> </hbox> </toolbar> <!-- Recording contents and general notice messages --> <deck id="performance-view" flex="1"> + <!-- A default notice, shown while initially opening the tool. + Keep this element the first child of #performance-view. --> + <hbox id="tool-loading-notice" + class="notice-container" + flex="1"> + </hbox> + + <!-- "Unavailable" notice, shown when the entire tool is disabled, + for example, when in private browsing mode. --> + <vbox id="unavailable-notice" + class="notice-container" + align="center" + pack="center" + flex="1"> + <hbox class="devtools-toolbarbutton-group" + pack="center"> + <toolbarbutton class="devtools-toolbarbutton record-button" + label="&performanceUI.startRecording;" + standalone="true"/> + </hbox> + <label class="tool-disabled-message" + value="&performanceUI.unavailableNoticePB;"/> + </vbox> + <!-- "Empty" notice, shown when there's no recordings available --> <hbox id="empty-notice" class="notice-container" align="center" pack="center" flex="1"> <hbox class="devtools-toolbarbutton-group" pack="center">
--- a/devtools/client/performance/test/browser.ini +++ b/devtools/client/performance/test/browser.ini @@ -3,19 +3,16 @@ tags = devtools subsuite = devtools support-files = doc_allocs.html doc_innerHTML.html doc_markers.html doc_simple-test.html head.js -# Commented out tests are profiler tests -# that need to be moved over to performance tool - [browser_aaa-run-first-leaktest.js] [browser_perf-categories-js-calltree.js] [browser_perf-clear-01.js] [browser_perf-clear-02.js] [browser_perf-columns-js-calltree.js] [browser_perf-columns-memory-calltree.js] [browser_perf-console-record-01.js] [browser_perf-console-record-02.js] @@ -76,16 +73,17 @@ skip-if = os == 'linux' # Bug 1172120 [browser_perf-overview-render-02.js] [browser_perf-overview-render-03.js] [browser_perf-overview-render-04.js] skip-if = os == 'linux' # bug 1186322 [browser_perf-overview-selection-01.js] [browser_perf-overview-selection-02.js] [browser_perf-overview-selection-03.js] [browser_perf-overview-time-interval.js] +[browser_perf-private-browsing.js] [browser_perf-states.js] skip-if = debug # bug 1203888 [browser_perf-refresh.js] [browser_perf-ui-recording.js] skip-if = os == 'linux' # bug 1186322 [browser_perf-recording-notices-01.js] skip-if = os == 'linux' # bug 1186322 [browser_perf-recording-notices-02.js]
--- a/devtools/client/performance/test/browser_perf-console-record-01.js +++ b/devtools/client/performance/test/browser_perf-console-record-01.js @@ -17,17 +17,17 @@ function* spawnTest() { yield profileStart; busyWait(WAIT_TIME); let profileEnd = once(front, "recording-stopped"); console.profileEnd("rust"); yield profileEnd; yield gDevTools.showToolbox(target, "performance"); - let panel = toolbox.getCurrentPanel(); + let panel = yield toolbox.getCurrentPanel().open(); let { panelWin: { PerformanceController, RecordingsView }} = panel; let recordings = PerformanceController.getRecordings(); yield waitUntil(() => PerformanceController.getRecordings().length === 1); is(recordings.length, 1, "one recording found in the performance panel."); is(recordings[0].isConsole(), true, "recording came from console.profile."); is(recordings[0].getLabel(), "rust", "correct label in the recording model.");
--- a/devtools/client/performance/test/browser_perf-console-record-02.js +++ b/devtools/client/performance/test/browser_perf-console-record-02.js @@ -1,31 +1,30 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** * Tests if the profiler is populated by in-progress console recordings * when it is opened. */ -var WAIT_TIME = 10; - function* spawnTest() { let { target, toolbox, console } = yield initConsole(SIMPLE_URL); let front = toolbox.performance; let profileStart = once(front, "recording-started"); console.profile("rust"); yield profileStart; + profileStart = once(front, "recording-started"); console.profile("rust2"); yield profileStart; yield gDevTools.showToolbox(target, "performance"); - let panel = toolbox.getCurrentPanel(); + let panel = yield toolbox.getCurrentPanel().open(); let { panelWin: { PerformanceController, RecordingsView }} = panel; yield waitUntil(() => PerformanceController.getRecordings().length === 2); let recordings = PerformanceController.getRecordings(); is(recordings.length, 2, "two recordings found in the performance panel."); is(recordings[0].isConsole(), true, "recording came from console.profile (1)."); is(recordings[0].getLabel(), "rust", "correct label in the recording model (1)."); is(recordings[0].isRecording(), true, "recording is still recording (1)."); @@ -34,15 +33,16 @@ function* spawnTest() { is(recordings[1].isRecording(), true, "recording is still recording (2)."); is(RecordingsView.selectedItem.attachment, recordings[0], "The first console recording should be selected."); let profileEnd = once(front, "recording-stopped"); console.profileEnd("rust"); yield profileEnd; + profileEnd = once(front, "recording-stopped"); console.profileEnd("rust2"); yield profileEnd; yield teardown(panel); finish(); }
--- a/devtools/client/performance/test/browser_perf-console-record-03.js +++ b/devtools/client/performance/test/browser_perf-console-record-03.js @@ -1,18 +1,16 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** * Tests if the profiler is populated by in-progress console recordings, and * also console recordings that have finished before it was opened. */ -var WAIT_TIME = 10; - function* spawnTest() { let { target, toolbox, console } = yield initConsole(SIMPLE_URL); let front = toolbox.performance; let profileStart = once(front, "recording-started"); console.profile("rust"); yield profileStart; @@ -20,17 +18,17 @@ function* spawnTest() { console.profileEnd("rust"); yield profileEnd; profileStart = once(front, "recording-started"); console.profile("rust2"); yield profileStart; yield gDevTools.showToolbox(target, "performance"); - let panel = toolbox.getCurrentPanel(); + let panel = yield toolbox.getCurrentPanel().open(); let { panelWin: { PerformanceController, RecordingsView }} = panel; yield waitUntil(() => PerformanceController.getRecordings().length === 2); let recordings = PerformanceController.getRecordings(); is(recordings.length, 2, "two recordings found in the performance panel."); is(recordings[0].isConsole(), true, "recording came from console.profile (1)."); is(recordings[0].getLabel(), "rust", "correct label in the recording model (1)."); is(recordings[0].isRecording(), false, "recording is still recording (1).");
--- a/devtools/client/performance/test/browser_perf-highlighted.js +++ b/devtools/client/performance/test/browser_perf-highlighted.js @@ -23,17 +23,17 @@ function* spawnTest() { let profileEnd = once(front, "recording-stopped"); console.profileEnd("rust"); yield profileEnd; ok(!tab.hasAttribute("highlighted"), "performance tab is no longer highlighted when console.profile recording finishes"); yield gDevTools.showToolbox(target, "performance"); - let panel = toolbox.getCurrentPanel(); + let panel = yield toolbox.getCurrentPanel().open(); let { panelWin: { PerformanceController, RecordingsView }} = panel; yield startRecording(panel); ok(tab.hasAttribute("highlighted"), "performance tab is highlighted during recording while in performance tool"); yield stopRecording(panel);
--- a/devtools/client/performance/test/browser_perf-legacy-front-06.js +++ b/devtools/client/performance/test/browser_perf-legacy-front-06.js @@ -15,17 +15,17 @@ function* spawnTest() { let profileStart = once(front, "recording-started"); console.profile("rust"); yield profileStart; profileStart = once(front, "recording-started"); console.profile("rust2"); yield profileStart; yield gDevTools.showToolbox(target, "performance"); - let panel = toolbox.getCurrentPanel(); + let panel = yield toolbox.getCurrentPanel().open(); let { panelWin: { PerformanceController, RecordingsView }} = panel; yield waitUntil(() => PerformanceController.getRecordings().length === 2); let recordings = PerformanceController.getRecordings(); is(recordings.length, 2, "two recordings found in the performance panel."); is(recordings[0].isConsole(), true, "recording came from console.profile (1)."); is(recordings[0].getLabel(), "rust", "correct label in the recording model (1)."); is(recordings[0].isRecording(), true, "recording is still recording (1).");
new file mode 100644 --- /dev/null +++ b/devtools/client/performance/test/browser_perf-private-browsing.js @@ -0,0 +1,93 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that disables the frontend when in private browsing mode. + */ + +let gPanelWinTuples = []; + +function* spawnTest() { + yield testNormalWindow(); + yield testPrivateWindow(); + yield testRecordingFailingInWindow(0); + yield testRecordingFailingInWindow(1); + yield teardownPerfInWindow(1); + yield testRecordingSucceedingInWindow(0); + yield teardownPerfInWindow(0); + + gPanelWinTuples = null; + finish(); +} + +function* createPanelInWindow(options) { + let win = yield addWindow(options); + let tab = yield addTab(SIMPLE_URL, win); + let target = TargetFactory.forTab(tab); + yield target.makeRemote(); + + let toolbox = yield gDevTools.showToolbox(target, "performance"); + yield toolbox.initPerformance(); + + let panel = yield toolbox.getCurrentPanel().open(); + gPanelWinTuples.push({ panel, win }); + + return { panel, win }; +} + +function* testNormalWindow() { + let { panel } = yield createPanelInWindow({ private: false }); + let { PerformanceView } = panel.panelWin; + + is(PerformanceView.getState(), "empty", + "The initial state of the performance panel view is correct (1)."); +} + +function* testPrivateWindow() { + let { panel } = yield createPanelInWindow({ private: true }); + let { PerformanceView } = panel.panelWin; + + is(PerformanceView.getState(), "unavailable", + "The initial state of the performance panel view is correct (2)."); +} + +function* testRecordingFailingInWindow(index) { + let { panel } = gPanelWinTuples[index]; + let { EVENTS, PerformanceController } = panel.panelWin; + + let onRecordingStarted = () => { + ok(false, "Recording should not start while a private window is present."); + }; + + PerformanceController.on(EVENTS.RECORDING_STARTED, onRecordingStarted); + + let whenFailed = once(PerformanceController, EVENTS.NEW_RECORDING_FAILED); + PerformanceController.startRecording(); + yield whenFailed; + ok(true, "Recording has failed."); + + PerformanceController.off(EVENTS.RECORDING_STARTED, onRecordingStarted); +} + +function* testRecordingSucceedingInWindow(index) { + let { panel } = gPanelWinTuples[index]; + let { EVENTS, PerformanceController } = panel.panelWin; + + let onRecordingFailed = () => { + ok(false, "Recording should start while now private windows are present."); + }; + + PerformanceController.on(EVENTS.NEW_RECORDING_FAILED, onRecordingFailed); + + yield startRecording(panel); + yield stopRecording(panel); + ok(true, "Recording has succeeded."); + + PerformanceController.off(EVENTS.RECORDING_STARTED, onRecordingFailed); +} + +function* teardownPerfInWindow(index) { + let { panel, win } = gPanelWinTuples[index]; + yield teardown(panel, win); + win.close(); +}
--- a/devtools/client/performance/test/browser_timeline-waterfall-generic.js +++ b/devtools/client/performance/test/browser_timeline-waterfall-generic.js @@ -1,16 +1,19 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** * Tests if the waterfall is properly built after finishing a recording. */ function* spawnTest() { + // This test seems to take a long time to cleanup on Ubuntu VMs. + requestLongerTimeout(2); + let { target, panel } = yield initPerformance(SIMPLE_URL); let { $, $$, EVENTS, PerformanceController, OverviewView, WaterfallView, DetailsView } = panel.panelWin; let { WATERFALL_MARKER_SIDEBAR_SAFE_BOUNDS } = require("devtools/client/performance/modules/widgets/marker-view"); yield startRecording(panel); ok(true, "Recording has started."); let updated = 0;
--- a/devtools/client/performance/test/head.js +++ b/devtools/client/performance/test/head.js @@ -100,16 +100,39 @@ registerCleanupFunction(() => { // Rollback any pref changes Object.keys(DEFAULT_PREFS).forEach(pref => { Preferences.set(pref, DEFAULT_PREFS[pref]); }); Cu.forceGC(); }); + +function whenDelayedStartupFinished(aWindow, aCallback) { + Services.obs.addObserver(function observer(aSubject, aTopic) { + if (aWindow == aSubject) { + Services.obs.removeObserver(observer, aTopic); + executeSoon(aCallback); + } + }, "browser-delayed-startup-finished", false); +} + +function addWindow(windowOptions) { + let deferred = Promise.defer(); + let win = OpenBrowserWindow(windowOptions); + + whenDelayedStartupFinished(win, () => { + executeSoon(() => { + deferred.resolve(win); + }); + }); + + return deferred.promise; +} + function addTab(aUrl, aWindow) { info("Adding tab: " + aUrl); let deferred = Promise.defer(); let targetWindow = aWindow || window; let targetBrowser = targetWindow.gBrowser; targetWindow.focus(); @@ -228,16 +251,18 @@ function initPerformance(aUrl, tool="per // TEST_PROFILER_FILTER_STATUS = array merge(target, targetOps); let toolbox = yield gDevTools.showToolbox(target, tool); // Wait for the performance tool to be spun up yield toolbox.initPerformance(); + // Panel is already initialized after `showToolbox` and `initPerformance`, + // no need to wait for `open` here. let panel = toolbox.getCurrentPanel(); return { target, panel, toolbox }; }); } /** * Initializes a webconsole panel. Returns a target, panel and toolbox reference. * Also returns a console property that allows calls to `profile` and `profileEnd`. @@ -271,22 +296,22 @@ function consoleExecute (console, method resolve(); return; } } } return promise; } -function* teardown(panel) { +function* teardown(panel, win = window) { info("Destroying the performance tool."); let tab = panel.target.tab; yield panel._toolbox.destroy(); - yield removeTab(tab); + yield removeTab(tab, win); } function idleWait(time) { return DevToolsUtils.waitForTime(time); } function busyWait(time) { let start = Date.now();
--- a/devtools/client/shared/redux/create-store.js +++ b/devtools/client/shared/redux/create-store.js @@ -2,30 +2,32 @@ * 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/. */ "use strict"; const { createStore, applyMiddleware } = require("devtools/client/shared/vendor/redux"); const { thunk } = require("./middleware/thunk"); const { waitUntilService } = require("./middleware/wait-service"); const { log } = require("./middleware/log"); +const { promise } = require("./middleware/promise"); /** * This creates a dispatcher with all the standard middleware in place * that all code requires. It can also be optionally configured in * various ways, such as logging and recording. * * @param {object} opts - boolean configuration flags * - log: log all dispatched actions to console * - middleware: array of middleware to be included in the redux store */ module.exports = (opts={}) => { const middleware = [ thunk, - waitUntilService + waitUntilService, + promise, ]; if (opts.log) { middleware.push(log); } if (opts.middleware) { opts.middleware.forEach(fn => middleware.push(fn));
--- a/devtools/client/shared/redux/middleware/moz.build +++ b/devtools/client/shared/redux/middleware/moz.build @@ -1,11 +1,12 @@ # -*- 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/. DevToolsModules( 'log.js', + 'promise.js', 'thunk.js', 'wait-service.js', )
new file mode 100644 --- /dev/null +++ b/devtools/client/shared/redux/middleware/promise.js @@ -0,0 +1,52 @@ +/* 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/. */ +"use strict"; + +const uuidgen = require("sdk/util/uuid").uuid; +const { + entries, toObject, reportException, executeSoon +} = require("devtools/shared/DevToolsUtils"); +const PROMISE = exports.PROMISE = "@@dispatch/promise"; + +function promiseMiddleware ({ dispatch, getState }) { + return next => action => { + if (!(PROMISE in action)) { + return next(action); + } + + const promise = action[PROMISE]; + const seqId = uuidgen().toString(); + + // Create a new action that doesn't have the promise field and has + // the `seqId` field that represents the sequence id + action = Object.assign( + toObject(entries(action).filter(pair => pair[0] !== PROMISE)), { seqId } + ); + + dispatch(Object.assign({}, action, { status: "start" })); + + promise.then(value => { + executeSoon(() => { + dispatch(Object.assign({}, action, { + status: "done", + value: value + })); + }); + }).catch(error => { + executeSoon(() => { + dispatch(Object.assign({}, action, { + status: "error", + error + })); + }); + reportException(`@@redux/middleware/promise#${action.type}`, error); + }); + + // Return the promise so action creators can still compose if they + // want to. + return promise; + }; +} + +exports.promise = promiseMiddleware;
rename from devtools/client/sourceeditor/codemirror/comment/comment.js rename to devtools/client/sourceeditor/codemirror/addon/comment/comment.js
rename from devtools/client/sourceeditor/codemirror/comment/continuecomment.js rename to devtools/client/sourceeditor/codemirror/addon/comment/continuecomment.js
rename from devtools/client/sourceeditor/codemirror/dialog/dialog.css rename to devtools/client/sourceeditor/codemirror/addon/dialog/dialog.css
rename from devtools/client/sourceeditor/codemirror/dialog/dialog.js rename to devtools/client/sourceeditor/codemirror/addon/dialog/dialog.js
rename from devtools/client/sourceeditor/codemirror/edit/closebrackets.js rename to devtools/client/sourceeditor/codemirror/addon/edit/closebrackets.js
rename from devtools/client/sourceeditor/codemirror/edit/closetag.js rename to devtools/client/sourceeditor/codemirror/addon/edit/closetag.js
rename from devtools/client/sourceeditor/codemirror/edit/continuelist.js rename to devtools/client/sourceeditor/codemirror/addon/edit/continuelist.js
rename from devtools/client/sourceeditor/codemirror/edit/matchbrackets.js rename to devtools/client/sourceeditor/codemirror/addon/edit/matchbrackets.js
rename from devtools/client/sourceeditor/codemirror/edit/matchtags.js rename to devtools/client/sourceeditor/codemirror/addon/edit/matchtags.js
rename from devtools/client/sourceeditor/codemirror/edit/trailingspace.js rename to devtools/client/sourceeditor/codemirror/addon/edit/trailingspace.js
rename from devtools/client/sourceeditor/codemirror/fold/brace-fold.js rename to devtools/client/sourceeditor/codemirror/addon/fold/brace-fold.js
rename from devtools/client/sourceeditor/codemirror/fold/comment-fold.js rename to devtools/client/sourceeditor/codemirror/addon/fold/comment-fold.js
rename from devtools/client/sourceeditor/codemirror/fold/foldcode.js rename to devtools/client/sourceeditor/codemirror/addon/fold/foldcode.js
rename from devtools/client/sourceeditor/codemirror/fold/foldgutter.css rename to devtools/client/sourceeditor/codemirror/addon/fold/foldgutter.css
rename from devtools/client/sourceeditor/codemirror/fold/foldgutter.js rename to devtools/client/sourceeditor/codemirror/addon/fold/foldgutter.js
rename from devtools/client/sourceeditor/codemirror/fold/indent-fold.js rename to devtools/client/sourceeditor/codemirror/addon/fold/indent-fold.js
rename from devtools/client/sourceeditor/codemirror/fold/markdown-fold.js rename to devtools/client/sourceeditor/codemirror/addon/fold/markdown-fold.js
rename from devtools/client/sourceeditor/codemirror/fold/xml-fold.js rename to devtools/client/sourceeditor/codemirror/addon/fold/xml-fold.js
rename from devtools/client/sourceeditor/codemirror/hint/show-hint.js rename to devtools/client/sourceeditor/codemirror/addon/hint/show-hint.js
rename from devtools/client/sourceeditor/codemirror/search/match-highlighter.js rename to devtools/client/sourceeditor/codemirror/addon/search/match-highlighter.js
rename from devtools/client/sourceeditor/codemirror/search/search.js rename to devtools/client/sourceeditor/codemirror/addon/search/search.js
rename from devtools/client/sourceeditor/codemirror/search/searchcursor.js rename to devtools/client/sourceeditor/codemirror/addon/search/searchcursor.js
rename from devtools/client/sourceeditor/codemirror/selection/active-line.js rename to devtools/client/sourceeditor/codemirror/addon/selection/active-line.js
rename from devtools/client/sourceeditor/codemirror/selection/mark-selection.js rename to devtools/client/sourceeditor/codemirror/addon/selection/mark-selection.js
rename from devtools/client/sourceeditor/codemirror/tern/tern.css rename to devtools/client/sourceeditor/codemirror/addon/tern/tern.css
rename from devtools/client/sourceeditor/codemirror/tern/tern.js rename to devtools/client/sourceeditor/codemirror/addon/tern/tern.js
rename from devtools/client/sourceeditor/codemirror/codemirror.css rename to devtools/client/sourceeditor/codemirror/lib/codemirror.css
rename from devtools/client/sourceeditor/codemirror/codemirror.js rename to devtools/client/sourceeditor/codemirror/lib/codemirror.js
--- a/devtools/client/sourceeditor/test/browser.ini +++ b/devtools/client/sourceeditor/test/browser.ini @@ -1,32 +1,33 @@ [DEFAULT] tags = devtools subsuite = devtools support-files = - cm_comment_test.js - cm_doc_test.js - cm_driver.js - cm_emacs_test.js - cm_mode_test.css - cm_mode_test.js - cm_multi_test.js - cm_mode_ruby.js - cm_script_injection_test.js - cm_search_test.js - cm_sublime_test.js - cm_test.js - cm_vim_test.js - codemirror.html + codemirror/comment_test.js + codemirror/doc_test.js + codemirror/driver.js + codemirror/emacs_test.js + codemirror/mode_test.css + codemirror/mode_test.js + codemirror/multi_test.js + codemirror/search_test.js + codemirror/sublime_test.js + codemirror/test.js + codemirror/vim_test.js + codemirror/codemirror.html + codemirror/vimemacs.html + codemirror/mode/javascript/test.js css_statemachine_testcases.css css_statemachine_tests.json css_autocompletion_tests.json - vimemacs.html head.js helper_codemirror_runner.js + cm_mode_ruby.js + cm_script_injection_test.js [browser_editor_autocomplete_basic.js] [browser_editor_autocomplete_events.js] [browser_editor_autocomplete_js.js] [browser_editor_basic.js] [browser_editor_cursor.js] [browser_editor_find_again.js] [browser_editor_goto_line.js]
--- a/devtools/client/sourceeditor/test/browser_codemirror.js +++ b/devtools/client/sourceeditor/test/browser_codemirror.js @@ -1,15 +1,15 @@ /* vim: set ts=2 et sw=2 tw=80: */ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; -const URI = "chrome://mochitests/content/browser/devtools/client/sourceeditor/test/codemirror.html"; +const URI = "chrome://mochitests/content/browser/devtools/client/sourceeditor/test/codemirror/codemirror.html"; loadHelperScript("helper_codemirror_runner.js"); function test() { requestLongerTimeout(3); waitForExplicitFinish(); let tab = gBrowser.addTab(); gBrowser.selectedTab = tab;
--- a/devtools/client/sourceeditor/test/browser_vimemacs.js +++ b/devtools/client/sourceeditor/test/browser_vimemacs.js @@ -1,14 +1,14 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; -const URI = "chrome://mochitests/content/browser/devtools/client/sourceeditor/test/vimemacs.html"; +const URI = "chrome://mochitests/content/browser/devtools/client/sourceeditor/test/codemirror/vimemacs.html"; loadHelperScript("helper_codemirror_runner.js"); function test() { requestLongerTimeout(3); waitForExplicitFinish(); let tab = gBrowser.addTab(); gBrowser.selectedTab = tab;
rename from devtools/client/sourceeditor/test/codemirror.html rename to devtools/client/sourceeditor/test/codemirror/codemirror.html --- a/devtools/client/sourceeditor/test/codemirror.html +++ b/devtools/client/sourceeditor/test/codemirror/codemirror.html @@ -53,26 +53,26 @@ <div style="border: 1px solid black; padding: 1px; max-width: 700px;"> <div style="width: 0px;" id=progress><div style="padding: 3px;">Ran <span id="progress_ran">0</span><span id="progress_total"> of 0</span> tests</div></div> </div> <p id=status>Please enable JavaScript...</p> <div id=output></div> <div id=testground></div> - <script src="cm_driver.js"></script> - <script src="cm_test.js"></script> - <script src="cm_comment_test.js"></script> - <script src="cm_doc_test.js"></script> - <script src="cm_driver.js"></script> - <script src="cm_emacs_test.js"></script> - <script src="cm_mode_test.js"></script> - <script src="cm_mode_javascript_test.js"></script> - <script src="cm_multi_test.js"></script> - <script src="cm_search_test.js"></script> + <script src="driver.js"></script> + <script src="test.js"></script> + <script src="comment_test.js"></script> + <script src="doc_test.js"></script> + <script src="driver.js"></script> + <script src="emacs_test.js"></script> + <script src="mode_test.js"></script> + <script src="mode/javascript/test.js"></script> + <script src="multi_test.js"></script> + <script src="search_test.js"></script> <!-- VIM and Emacs mode tests are in vimemacs.html <script src="cm_sublime_test.js"></script> <script src="cm_vim_test.js"></script> <script src="cm_emacs_test.js"></script> --> <!-- These modes/addons are not used by Editor
rename from devtools/client/sourceeditor/test/cm_comment_test.js rename to devtools/client/sourceeditor/test/codemirror/comment_test.js
rename from devtools/client/sourceeditor/test/cm_doc_test.js rename to devtools/client/sourceeditor/test/codemirror/doc_test.js
rename from devtools/client/sourceeditor/test/cm_driver.js rename to devtools/client/sourceeditor/test/codemirror/driver.js
rename from devtools/client/sourceeditor/test/cm_emacs_test.js rename to devtools/client/sourceeditor/test/codemirror/emacs_test.js
rename from devtools/client/sourceeditor/test/cm_mode_javascript_test.js rename to devtools/client/sourceeditor/test/codemirror/mode/javascript/test.js
rename from devtools/client/sourceeditor/test/cm_mode_test.css rename to devtools/client/sourceeditor/test/codemirror/mode_test.css
rename from devtools/client/sourceeditor/test/cm_mode_test.js rename to devtools/client/sourceeditor/test/codemirror/mode_test.js
rename from devtools/client/sourceeditor/test/cm_multi_test.js rename to devtools/client/sourceeditor/test/codemirror/multi_test.js
rename from devtools/client/sourceeditor/test/cm_search_test.js rename to devtools/client/sourceeditor/test/codemirror/search_test.js
rename from devtools/client/sourceeditor/test/cm_sublime_test.js rename to devtools/client/sourceeditor/test/codemirror/sublime_test.js
rename from devtools/client/sourceeditor/test/cm_test.js rename to devtools/client/sourceeditor/test/codemirror/test.js
rename from devtools/client/sourceeditor/test/cm_vim_test.js rename to devtools/client/sourceeditor/test/codemirror/vim_test.js
rename from devtools/client/sourceeditor/test/vimemacs.html rename to devtools/client/sourceeditor/test/codemirror/vimemacs.html --- a/devtools/client/sourceeditor/test/vimemacs.html +++ b/devtools/client/sourceeditor/test/codemirror/vimemacs.html @@ -53,20 +53,20 @@ <div style="border: 1px solid black; padding: 1px; max-width: 700px;"> <div style="width: 0px;" id=progress><div style="padding: 3px;">Ran <span id="progress_ran">0</span><span id="progress_total"> of 0</span> tests</div></div> </div> <p id=status>Please enable JavaScript...</p> <div id=output></div> <div id=testground></div> - <script src="cm_driver.js"></script> - <script src="cm_sublime_test.js"></script> - <script src="cm_vim_test.js"></script> - <script src="cm_emacs_test.js"></script> + <script src="driver.js"></script> + <script src="sublime_test.js"></script> + <script src="vim_test.js"></script> + <script src="emacs_test.js"></script> <!-- Basic tests are in codemirror.html <script src="cm_driver.js"></script> <script src="cm_test.js"></script> <script src="cm_comment_test.js"></script> <script src="cm_doc_test.js"></script> <script src="cm_driver.js"></script> <script src="cm_emacs_test.js"></script>
new file mode 100644 --- /dev/null +++ b/devtools/server/actors/heap-snapshot-file.js @@ -0,0 +1,74 @@ +/* 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/. */ + +"use strict"; + +const protocol = require("devtools/server/protocol"); +const { method, Arg } = protocol; +const Services = require("Services"); + +loader.lazyRequireGetter(this, "DevToolsUtils", + "devtools/shared/DevToolsUtils"); +loader.lazyRequireGetter(this, "OS", "resource://gre/modules/osfile.jsm", true); +loader.lazyRequireGetter(this, "Task", "resource://gre/modules/Task.jsm", true); +loader.lazyRequireGetter(this, "HeapSnapshotFileUtils", + "devtools/shared/heapsnapshot/HeapSnapshotFileUtils"); + +/** + * The HeapSnapshotFileActor handles transferring heap snapshot files from the + * server to the client. This has to be a global actor in the parent process + * because child processes are sandboxed and do not have access to the file + * system. + */ +exports.HeapSnapshotFileActor = protocol.ActorClass({ + typeName: "heapSnapshotFile", + + initialize: function (conn, parent) { + if (Services.appInfo && + (Services.appInfo.processType !== + Services.appInfo.PROCESS_TYPE_DEFAULT)) { + const err = new Error("Attempt to create a HeapSnapshotFileActor in a " + + "child process! The HeapSnapshotFileActor *MUST* " + + "be in the parent process!"); + DevToolsUtils.reportException( + "HeapSnapshotFileActor.prototype.initialize", err); + return; + } + + protocol.Actor.prototype.initialize.call(this, conn, parent); + }, + + /** + * @see MemoryFront.prototype.transferHeapSnapshot + */ + transferHeapSnapshot: method(Task.async(function* (snapshotId) { + const snapshotFilePath = + HeapSnapshotFileUtils.getHeapSnapshotTempFilePath(snapshotId); + if (!snapshotFilePath) { + throw new Error(`No heap snapshot with id: ${snapshotId}`); + } + + const streamPromise = DevToolsUtils.openFileStream(snapshotFilePath); + + const { size } = yield OS.File.stat(snapshotFilePath); + const bulkPromise = this.conn.startBulkSend({ + actor: this.actorID, + type: "heap-snapshot", + length: size + }); + + const [bulk, stream] = yield Promise.all([bulkPromise, streamPromise]); + + try { + yield bulk.copyFrom(stream); + } finally { + stream.close(); + } + }), { + request: { + snapshotId: Arg(0, "string") + } + }), + +});
--- a/devtools/server/actors/memory.js +++ b/devtools/server/actors/memory.js @@ -1,28 +1,26 @@ /* 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/. */ "use strict"; const { Cc, Ci, Cu, components } = require("chrome"); -const { openFileStream } = require("devtools/shared/DevToolsUtils"); const protocol = require("devtools/server/protocol"); const { method, RetVal, Arg, types } = protocol; const { Memory } = require("devtools/shared/shared/memory"); const { actorBridge } = require("devtools/server/actors/common"); loader.lazyRequireGetter(this, "events", "sdk/event/core"); loader.lazyRequireGetter(this, "StackFrameCache", "devtools/server/actors/utils/stack", true); loader.lazyRequireGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm", true); loader.lazyRequireGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm", true); loader.lazyRequireGetter(this, "Task", "resource://gre/modules/Task.jsm", true); -loader.lazyRequireGetter(this, "OS", "resource://gre/modules/osfile.jsm", true); loader.lazyRequireGetter(this, "HeapSnapshotFileUtils", "devtools/shared/heapsnapshot/HeapSnapshotFileUtils"); loader.lazyRequireGetter(this, "ThreadSafeChromeUtils"); types.addDictType("AllocationsRecordingOptions", { // The probability we sample any given allocation when recording // allocations. Must be between 0.0 and 1.0. Defaults to 1.0, or sampling // every allocation. @@ -110,45 +108,16 @@ var MemoryActor = exports.MemoryActor = saveHeapSnapshot: method(function () { return this.bridge.saveHeapSnapshot(); }, { response: { snapshotId: RetVal("string") } }), - transferHeapSnapshot: method(Task.async(function* (snapshotId) { - const snapshotFilePath = - HeapSnapshotFileUtils.getHeapSnapshotTempFilePath(snapshotId); - if (!snapshotFilePath) { - throw new Error(`No heap snapshot with id: ${snapshotId}`); - } - - const streamPromise = openFileStream(snapshotFilePath); - - const { size } = yield OS.File.stat(snapshotFilePath); - const bulkPromise = this.conn.startBulkSend({ - actor: this.actorID, - type: "heap-snapshot", - length: size - }); - - const [bulk, stream] = yield Promise.all([bulkPromise, streamPromise]); - - try { - yield bulk.copyFrom(stream); - } finally { - stream.close(); - } - }), { - request: { - snapshotId: Arg(0, "string") - } - }), - takeCensus: actorBridge("takeCensus", { request: {}, response: RetVal("json") }), startRecordingAllocations: actorBridge("startRecordingAllocations", { request: { options: Arg(0, "nullable:AllocationsRecordingOptions") @@ -208,31 +177,33 @@ var MemoryActor = exports.MemoryActor = _onAllocations: function (data) { if (this.conn.transport) { events.emit(this, "allocations", data); } }, }); exports.MemoryFront = protocol.FrontClass(MemoryActor, { - initialize: function(client, form) { + initialize: function(client, form, rootForm = null) { protocol.Front.prototype.initialize.call(this, client, form); this._client = client; this.actorID = form.memoryActor; + this.heapSnapshotFileActorID = rootForm + ? rootForm.heapSnapshotFileActor + : null; this.manage(this); }, /** * Save a heap snapshot, transfer it from the server to the client if the * server and client do not share a file system, and return the local file * path to the heap snapshot. * - * NB: This will not work with sandboxed child processes, as they do not have - * access to the filesystem and the hep snapshot APIs do not support that use - * case yet. + * Note that this is safe to call for actors inside sandoxed child processes, + * as we jump through the correct IPDL hoops. * * @params Boolean options.forceCopy * Always force a bulk data copy of the saved heap snapshot, even when * the server and client share a file system. * * @returns Promise<String> */ saveHeapSnapshot: protocol.custom(Task.async(function* (options = {}) { @@ -253,18 +224,22 @@ exports.MemoryFront = protocol.FrontClas * heap snapshot file to the client. The path to the client's local file is * returned. * * @param {String} snapshotId * * @returns Promise<String> */ transferHeapSnapshot: protocol.custom(function (snapshotId) { + if (!this.heapSnapshotFileActorID) { + throw new Error("MemoryFront initialized without a rootForm"); + } + const request = this._client.request({ - to: this.actorID, + to: this.heapSnapshotFileActorID, type: "transferHeapSnapshot", snapshotId }); return new Promise((resolve, reject) => { const outFilePath = HeapSnapshotFileUtils.getNewUniqueHeapSnapshotTempFilePath(); const outFile = new FileUtils.File(outFilePath);
--- a/devtools/server/actors/moz.build +++ b/devtools/server/actors/moz.build @@ -21,16 +21,17 @@ DevToolsModules( 'common.js', 'csscoverage.js', 'device.js', 'director-manager.js', 'director-registry.js', 'eventlooplag.js', 'framerate.js', 'gcli.js', + 'heap-snapshot-file.js', 'highlighters.css', 'highlighters.js', 'inspector.js', 'layout.js', 'memory.js', 'memprof.js', 'monitor.js', 'object.js',
--- a/devtools/server/actors/performance.js +++ b/devtools/server/actors/performance.js @@ -102,35 +102,48 @@ var PerformanceActor = exports.Performan connect: method(function (config) { this.bridge.connect({ systemClient: config.systemClient }); return { traits: this.traits }; }, { request: { options: Arg(0, "nullable:json") }, response: RetVal("json") }), + canCurrentlyRecord: method(function() { + return this.bridge.canCurrentlyRecord(); + }, { + response: { value: RetVal("json") } + }), + startRecording: method(Task.async(function *(options={}) { + if (!this.bridge.canCurrentlyRecord().success) { + return null; + } + let normalizedOptions = normalizePerformanceFeatures(options, this.traits.features); let recording = yield this.bridge.startRecording(normalizedOptions); - this.manage(recording); return recording; }), { request: { options: Arg(0, "nullable:json"), }, - response: RetVal("performance-recording"), + response: { + recording: RetVal("nullable:performance-recording") + } }), stopRecording: actorBridge("stopRecording", { request: { options: Arg(0, "performance-recording"), }, - response: RetVal("performance-recording"), + response: { + recording: RetVal("performance-recording") + } }), isRecording: actorBridge("isRecording", { response: { isRecording: RetVal("boolean") } }), getRecordings: actorBridge("getRecordings", { response: { recordings: RetVal("array:performance-recording") }
--- a/devtools/server/main.js +++ b/devtools/server/main.js @@ -408,16 +408,21 @@ var DebuggerServer = { constructor: "DeviceActor", type: { global: true } }); this.registerModule("devtools/server/actors/director-registry", { prefix: "directorRegistry", constructor: "DirectorRegistryActor", type: { global: true } }); + this.registerModule("devtools/server/actors/heap-snapshot-file", { + prefix: "heapSnapshotFile", + constructor: "HeapSnapshotFileActor", + type: { global: true } + }); }, /** * Install tab actors in documents loaded in content childs */ addChildActors: function () { // In case of apps being loaded in parent process, DebuggerServer is already // initialized and browser actors are already loaded,
--- a/devtools/server/tests/mochitest/memory-helpers.js +++ b/devtools/server/tests/mochitest/memory-helpers.js @@ -31,17 +31,17 @@ function startServerAndGetSelectedTabMem client.listTabs(response => { if (response.error) { reject(new Error(response.error + ": " + response.message)); return; } var form = response.tabs[response.selected]; - var memory = MemoryFront(client, form); + var memory = MemoryFront(client, form, response); resolve({ memory, client }); }); }); }); } function destroyServerAndFinish(client) {
--- a/devtools/server/tests/unit/head_dbg.js +++ b/devtools/server/tests/unit/head_dbg.js @@ -47,20 +47,31 @@ var loadSubScript = Cc[ * @returns `run_test` function */ function makeMemoryActorTest(testGeneratorFunction) { const TEST_GLOBAL_NAME = "test_MemoryActor"; return function run_test() { do_test_pending(); startTestDebuggerServer(TEST_GLOBAL_NAME).then(client => { - getTestTab(client, TEST_GLOBAL_NAME, function (tabForm) { + DebuggerServer.registerModule("devtools/server/actors/heap-snapshot-file", { + prefix: "heapSnapshotFile", + constructor: "HeapSnapshotFileActor", + type: { global: true } + }); + + getTestTab(client, TEST_GLOBAL_NAME, function (tabForm, rootForm) { + if (!tabForm || !rootForm) { + ok(false, "Could not attach to test tab: " + TEST_GLOBAL_NAME); + return; + } + Task.spawn(function* () { try { - const memoryFront = new MemoryFront(client, tabForm); + const memoryFront = new MemoryFront(client, tabForm, rootForm); yield memoryFront.attach(); yield* testGeneratorFunction(client, memoryFront); yield memoryFront.detach(); } catch(err) { DevToolsUtils.reportException("makeMemoryActorTest", err); ok(false, "Got an error: " + err); } @@ -289,17 +300,17 @@ function addTestGlobal(aName, aServer = } // List the DebuggerClient |aClient|'s tabs, look for one whose title is // |aTitle|, and apply |aCallback| to the packet's entry for that tab. function getTestTab(aClient, aTitle, aCallback) { aClient.listTabs(function (aResponse) { for (let tab of aResponse.tabs) { if (tab.title === aTitle) { - aCallback(tab); + aCallback(tab, aResponse); return; } } aCallback(null); }); } // Attach to |aClient|'s tab whose title is |aTitle|; pass |aCallback| the
--- a/devtools/shared/DevToolsUtils.js +++ b/devtools/shared/DevToolsUtils.js @@ -7,16 +7,17 @@ /* General utilities used throughout devtools. */ var { Ci, Cu, Cc, components } = require("chrome"); var Services = require("Services"); var promise = require("promise"); loader.lazyRequireGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm", true); +loader.lazyRequireGetter(this, "setTimeout", "Timer", true); /** * Turn the error |aError| into a string, without fail. */ exports.safeErrorString = function safeErrorString(aError) { try { let errorString = aError.toString(); if (typeof errorString == "string") { @@ -128,16 +129,28 @@ exports.zip = function zip(a, b) { * @param object obj * @returns array */ exports.entries = function entries(obj) { return Object.keys(obj).map(k => [k, obj[k]]); } /** + * Takes an array of 2-element arrays as key/values pairs and + * constructs an object using them. + */ +exports.toObject = function(arr) { + const obj = {}; + for(let pair of arr) { + obj[pair[0]] = pair[1]; + } + return obj; +} + +/** * Composes the given functions into a single function, which will * apply the results of each function right-to-left, starting with * applying the given arguments to the right-most function. * `compose(foo, bar, baz)` === `args => foo(bar(baz(args)` * * @param ...function funcs * @returns function */ @@ -181,17 +194,17 @@ exports.waitForTick = function waitForTi * * @param number aDelay * The amount of time to wait, in milliseconds. * @return Promise * A promise that is resolved after the specified amount of time passes. */ exports.waitForTime = function waitForTime(aDelay) { let deferred = promise.defer(); - require("Timer").setTimeout(deferred.resolve, aDelay); + setTimeout(deferred.resolve, aDelay); return deferred.promise; }; /** * Like Array.prototype.forEach, but doesn't cause jankiness when iterating over * very large arrays by yielding to the browser and continuing execution on the * next tick. *
--- a/devtools/shared/performance/recorder.js +++ b/devtools/shared/performance/recorder.js @@ -278,16 +278,35 @@ const PerformanceRecorder = exports.Perf let activeRecordings = this._recordings.filter(r => r.isRecording()); if (activeRecordings.length) { events.emit(this, "timeline-data", eventName, eventData, activeRecordings); } }, /** + * Checks whether or not recording is currently supported. At the moment, + * this is only influenced by private browsing mode and the profiler. + */ + canCurrentlyRecord: function() { + let success = true; + let reasons = []; + + if (!Profiler.canProfile()) { + success = false, + reasons.push("profiler-unavailable"); + } + + // Check other factors that will affect the possibility of successfully + // starting a recording here. + + return { success, reasons }; + }, + + /** * Begins a recording session * * @param boolean options.withMarkers * @param boolean options.withJITOptimizations * @param boolean options.withTicks * @param boolean options.withMemory * @param boolean options.withAllocations * @param boolean options.allocationsSampleProbability
--- a/devtools/shared/shared/profiler.js +++ b/devtools/shared/shared/profiler.js @@ -50,25 +50,34 @@ const ProfilerManager = (function () { // How many subscribers there _profilerStatusSubscribers: 0, /** * The nsIProfiler is target agnostic and interacts with the whole platform. * Therefore, special care needs to be given to make sure different profiler * consumers (i.e. "toolboxes") don't interfere with each other. Register - * the instance here. + * the profiler actor instances here. + * + * @param Profiler instance + * A profiler actor class. */ addInstance: function (instance) { consumers.add(instance); // Lazily register events this.registerEventListeners(); }, + /** + * Remove the profiler actor instances here. + * + * @param Profiler instance + * A profiler actor class. + */ removeInstance: function (instance) { consumers.delete(instance); if (this.length < 0) { let msg = "Somehow the number of started profilers is now negative."; DevToolsUtils.reportException("Profiler", msg); } @@ -96,35 +105,46 @@ const ProfilerManager = (function () { features: options.features || DEFAULT_PROFILER_OPTIONS.features, threadFilters: options.threadFilters || DEFAULT_PROFILER_OPTIONS.threadFilters, }; // The start time should be before any samples we might be // interested in. let currentTime = nsIProfilerModule.getElapsedTime(); - nsIProfilerModule.StartProfiler( - config.entries, - config.interval, - config.features, - config.features.length, - config.threadFilters, - config.threadFilters.length - ); - let { position, totalSize, generation } = this.getBufferInfo(); + try { + nsIProfilerModule.StartProfiler( + config.entries, + config.interval, + config.features, + config.features.length, + config.threadFilters, + config.threadFilters.length + ); + } catch (e) { + // For some reason, the profiler couldn't be started. This could happen, + // for example, when in private browsing mode. + Cu.reportError(`Could not start the profiler module: ${e.message}`); + return { started: false, reason: e, currentTime }; + } this._updateProfilerStatusPolling(); + + let { position, totalSize, generation } = this.getBufferInfo(); return { started: true, position, totalSize, generation, currentTime }; }, + /** + * Attempts to stop the nsIProfiler module. + */ stop: function () { // Actually stop the profiler only if the last client has stopped profiling. - // Since this is used as a root actor, and the profiler module interacts with the - // whole platform, we need to avoid a case in which the profiler is stopped - // when there might be other clients still profiling. + // Since this is used as a root actor, and the profiler module interacts + // with the whole platform, we need to avoid a case in which the profiler + // is stopped when there might be other clients still profiling. if (this.length <= 1) { nsIProfilerModule.StopProfiler(); } this._updateProfilerStatusPolling(); return { started: false }; }, /** @@ -301,17 +321,18 @@ const ProfilerManager = (function () { } }, /** * Unregisters handlers for all system events. */ unregisterEventListeners: function () { if (this._eventsRegistered) { - PROFILER_SYSTEM_EVENTS.forEach(eventName => Services.obs.removeObserver(this, eventName)); + PROFILER_SYSTEM_EVENTS.forEach(eventName => + Services.obs.removeObserver(this, eventName)); this._eventsRegistered = false; } }, /** * Takes an event name and additional data and emits them * through each profiler instance that is subscribed to the event. * @@ -478,16 +499,24 @@ var Profiler = exports.Profiler = Class( response.push(e); } }); return { registered: response }; }, }); /** + * Checks whether or not the profiler module can currently run. + * @return boolean + */ +Profiler.canProfile = function() { + return nsIProfilerModule.CanProfile(); +}; + +/** * JSON.stringify callback used in Profiler.prototype.observe. */ function cycleBreaker(key, value) { if (key == "wrappedJSObject") { return undefined; } return value; }
--- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -648,38 +648,65 @@ NotificationPermissionRequest::GetTypes( { nsTArray<nsString> emptyOptions; return nsContentPermissionUtils::CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"), NS_LITERAL_CSTRING("unused"), emptyOptions, aTypes); } -class NotificationObserver : public nsIObserver +// Observer that the alert service calls to do common tasks and/or dispatch to the +// specific observer for the context e.g. main thread, worker, or service worker. +class NotificationObserver final : public nsIObserver { public: - UniquePtr<NotificationRef> mNotificationRef; + nsCOMPtr<nsIObserver> mObserver; + nsCOMPtr<nsIPrincipal> mPrincipal; NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER - explicit NotificationObserver(UniquePtr<NotificationRef> aRef) - : mNotificationRef(Move(aRef)) + NotificationObserver(nsIObserver* aObserver, nsIPrincipal* aPrincipal) + : mObserver(aObserver), mPrincipal(aPrincipal) { AssertIsOnMainThread(); + MOZ_ASSERT(mObserver); + MOZ_ASSERT(mPrincipal); } protected: virtual ~NotificationObserver() { AssertIsOnMainThread(); } }; NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver) +class MainThreadNotificationObserver : public nsIObserver +{ +public: + UniquePtr<NotificationRef> mNotificationRef; + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit MainThreadNotificationObserver(UniquePtr<NotificationRef> aRef) + : mNotificationRef(Move(aRef)) + { + AssertIsOnMainThread(); + } + +protected: + virtual ~MainThreadNotificationObserver() + { + AssertIsOnMainThread(); + } +}; + +NS_IMPL_ISUPPORTS(MainThreadNotificationObserver, nsIObserver) + NS_IMETHODIMP NotificationTask::Run() { AssertIsOnMainThread(); // Get a pointer to notification before the notification takes ownership of // the ref (it owns itself temporarily, with ShowInternal() and // CloseInternal() passing on the ownership appropriately.) @@ -979,24 +1006,24 @@ Notification::GetPrincipal() return mWorkerPrivate->GetPrincipal(); } else { nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner()); NS_ENSURE_TRUE(sop, nullptr); return sop->GetPrincipal(); } } -class WorkerNotificationObserver final : public NotificationObserver +class WorkerNotificationObserver final : public MainThreadNotificationObserver { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIOBSERVER explicit WorkerNotificationObserver(UniquePtr<NotificationRef> aRef) - : NotificationObserver(Move(aRef)) + : MainThreadNotificationObserver(Move(aRef)) { AssertIsOnMainThread(); MOZ_ASSERT(mNotificationRef->GetNotification()->mWorkerPrivate); } void ForgetNotification() { @@ -1012,17 +1039,17 @@ protected: MOZ_ASSERT(mNotificationRef); Notification* notification = mNotificationRef->GetNotification(); if (notification) { notification->mObserver = nullptr; } } }; -NS_IMPL_ISUPPORTS_INHERITED0(WorkerNotificationObserver, NotificationObserver) +NS_IMPL_ISUPPORTS_INHERITED0(WorkerNotificationObserver, MainThreadNotificationObserver) class ServiceWorkerNotificationObserver final : public nsIObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER ServiceWorkerNotificationObserver(const nsAString& aScope, @@ -1120,16 +1147,35 @@ public: } }; NS_IMETHODIMP NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { AssertIsOnMainThread(); + + if (!strcmp("alertdisablecallback", aTopic)) { + nsCOMPtr<nsIPermissionManager> permissionManager = + mozilla::services::GetPermissionManager(); + if (!permissionManager) { + return NS_ERROR_FAILURE; + } + permissionManager->RemoveFromPrincipal(mPrincipal, "desktop-notification"); + return NS_OK; + } + + return mObserver->Observe(aSubject, aTopic, aData); +} + +NS_IMETHODIMP +MainThreadNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + AssertIsOnMainThread(); MOZ_ASSERT(mNotificationRef); Notification* notification = mNotificationRef->GetNotification(); MOZ_ASSERT(notification); if (!strcmp("alertclickcallback", aTopic)) { nsCOMPtr<nsPIDOMWindow> window = notification->GetOwner(); if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) { // Window has been closed, this observer is not valid anymore return NS_ERROR_FAILURE; @@ -1379,26 +1425,29 @@ Notification::ShowInternal() if (mWorkerPrivate) { // Scope better be set on ServiceWorker initiated requests. MOZ_ASSERT(!mWorkerPrivate->IsServiceWorker()); // Keep a pointer so that the feature can tell the observer not to release // the notification. mObserver = new WorkerNotificationObserver(Move(ownership)); observer = mObserver; } else { - observer = new NotificationObserver(Move(ownership)); + observer = new MainThreadNotificationObserver(Move(ownership)); } } else { // This observer does not care about the Notification. It will be released // at the end of this function. // - // The observer is wholly owned by the alerts service. + // The observer is wholly owned by the NotificationObserver passed to the alert service. observer = new ServiceWorkerNotificationObserver(mScope, GetPrincipal(), mID); } MOZ_ASSERT(observer); + nsCOMPtr<nsIObserver> alertObserver = new NotificationObserver(observer, + GetPrincipal()); + #ifdef MOZ_B2G nsCOMPtr<nsIAppNotificationService> appNotifier = do_GetService("@mozilla.org/system-alerts-service;1"); if (appNotifier) { uint32_t appId = nsIScriptSecurityManager::UNKNOWN_APP_ID; if (mWorkerPrivate) { appId = mWorkerPrivate->GetPrincipal()->GetAppId(); @@ -1427,17 +1476,17 @@ Notification::ShowInternal() ops.mMozbehavior.mSoundFile = soundUrl; if (!ToJSValue(cx, ops, &val)) { NS_WARNING("Converting dict to object failed!"); return; } appNotifier->ShowAppNotification(iconUrl, mTitle, mBody, - observer, val); + alertObserver, val); return; } } } #endif // In the case of IPC, the parent process uses the cookie to map to // nsIObserver. Thus the cookie must be unique to differentiate observers. @@ -1458,17 +1507,17 @@ Notification::ShowInternal() NS_QueryNotificationCallbacks(nullptr, loadGroup, NS_GET_IID(nsILoadContext), getter_AddRefs(loadContext)); inPrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing(); } nsAutoString alertName; GetAlertName(alertName); alertService->ShowAlertNotification(iconUrl, mTitle, mBody, true, - uniqueCookie, observer, alertName, + uniqueCookie, alertObserver, alertName, DirectionToString(mDir), mLang, mDataAsBase64, GetPrincipal(), inPrivateBrowsing); } /* static */ bool Notification::RequestPermissionEnabledForScope(JSContext* aCx, JSObject* /* unused */) {
--- a/dom/notification/Notification.h +++ b/dom/notification/Notification.h @@ -97,17 +97,17 @@ public: * dispatch a control runnable instead. * */ class Notification : public DOMEventTargetHelper { friend class CloseNotificationRunnable; friend class NotificationTask; friend class NotificationPermissionRequest; - friend class NotificationObserver; + friend class MainThreadNotificationObserver; friend class NotificationStorageCallback; friend class ServiceWorkerNotificationObserver; friend class WorkerGetRunnable; friend class WorkerNotificationObserver; public: IMPL_EVENT_HANDLER(click) IMPL_EVENT_HANDLER(show)
--- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -1,16 +1,17 @@ /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- * 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/. */ package org.mozilla.gecko; import org.mozilla.gecko.annotation.RobocopTarget; +import org.mozilla.gecko.AdjustConstants; import org.mozilla.gecko.AppConstants.Versions; import org.mozilla.gecko.DynamicToolbar.PinReason; import org.mozilla.gecko.DynamicToolbar.VisibilityTransition; import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException; import org.mozilla.gecko.Tabs.TabEvents; import org.mozilla.gecko.animation.PropertyAnimator; import org.mozilla.gecko.animation.TransitionsTracker; import org.mozilla.gecko.animation.ViewHelper; @@ -857,16 +858,23 @@ public class BrowserApp extends GeckoApp JavaAddonManager.getInstance().init(appContext); mSharedPreferencesHelper = new SharedPreferencesHelper(appContext); mOrderedBroadcastHelper = new OrderedBroadcastHelper(appContext); mBrowserHealthReporter = new BrowserHealthReporter(); mReadingListHelper = new ReadingListHelper(appContext, getProfile(), this); mAccountsHelper = new AccountsHelper(appContext, getProfile()); + if (AppConstants.MOZ_INSTALL_TRACKING) { + final SharedPreferences prefs = GeckoSharedPrefs.forApp(this); + if (prefs.getBoolean(GeckoPreferences.PREFS_HEALTHREPORT_UPLOAD_ENABLED, true)) { + AdjustConstants.getAdjustHelper().onCreate(this, AdjustConstants.MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN); + } + } + if (AppConstants.MOZ_ANDROID_BEAM) { NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this); if (nfc != null) { nfc.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() { @Override public NdefMessage createNdefMessage(NfcEvent event) { Tab tab = Tabs.getInstance().getSelectedTab(); if (tab == null || tab.isPrivate()) { @@ -1803,16 +1811,21 @@ public class BrowserApp extends GeckoApp Telemetry.addToHistogram("FENNEC_FAVICONS_COUNT", db.getCount(cr, "favicons")); Telemetry.addToHistogram("FENNEC_THUMBNAILS_COUNT", db.getCount(cr, "thumbnails")); Telemetry.addToHistogram("FENNEC_READING_LIST_COUNT", db.getReadingListAccessor().getCount(cr)); Telemetry.addToHistogram("BROWSER_IS_USER_DEFAULT", (isDefaultBrowser(Intent.ACTION_VIEW) ? 1 : 0)); Telemetry.addToHistogram("FENNEC_TABQUEUE_ENABLED", (TabQueueHelper.isTabQueueEnabled(BrowserApp.this) ? 1 : 0)); if (Versions.feature16Plus) { Telemetry.addToHistogram("BROWSER_IS_ASSIST_DEFAULT", (isDefaultBrowser(Intent.ACTION_ASSIST) ? 1 : 0)); } + + final SharedPreferences sharedPrefs = GeckoSharedPrefs.forApp(BrowserApp.this); + if (sharedPrefs.getBoolean(GeckoPreferences.PREFS_OPEN_URLS_IN_PRIVATE, false)) { + Telemetry.addToHistogram("FENNEC_OPEN_URLS_IN_PRIVATE", 1); + } } else if ("Updater:Launch".equals(event)) { handleUpdaterLaunch(); } else { super.handleMessage(event, message, callback); } } private void getFaviconFromCache(final EventCallback callback, final String url) {
--- a/mobile/android/base/GeckoApplication.java +++ b/mobile/android/base/GeckoApplication.java @@ -1,16 +1,14 @@ /* 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/. */ package org.mozilla.gecko; -import org.mozilla.gecko.AdjustConstants; -import org.mozilla.gecko.AppConstants; import org.mozilla.gecko.db.BrowserContract; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.db.LocalBrowserDB; import org.mozilla.gecko.home.HomePanelsManager; import org.mozilla.gecko.lwt.LightweightTheme; import org.mozilla.gecko.mdns.MulticastDNSManager; import org.mozilla.gecko.util.Clipboard; import org.mozilla.gecko.util.HardwareUtils; @@ -150,20 +148,16 @@ public class GeckoApplication extends Ap // Note that we don't use the profile directory -- we // send operations to the ContentProvider, which does // its own thing. return new LocalBrowserDB(profileName); } }); super.onCreate(); - - if (AppConstants.MOZ_INSTALL_TRACKING) { - AdjustConstants.getAdjustHelper().onCreate(this, AdjustConstants.MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN); - } } public boolean isApplicationInBackground() { return mInBackground; } public LightweightTheme getLightweightTheme() { return mLightweightTheme;
--- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -574,16 +574,18 @@ var BrowserApp = { InitLater(() => Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "")); InitLater(() => Messaging.sendRequest({ type: "Gecko:DelayedStartup" })); if (AppConstants.NIGHTLY_BUILD) { InitLater(() => ShumwayUtils.init(), window, "ShumwayUtils"); InitLater(() => Telemetry.addData("TRACKING_PROTECTION_ENABLED", Services.prefs.getBoolPref("privacy.trackingprotection.enabled"))); + InitLater(() => Telemetry.addData("TRACKING_PROTECTION_PBM_DISABLED", + !Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled"))); InitLater(() => WebcompatReporter.init()); } InitLater(() => LightWeightThemeWebInstaller.init()); InitLater(() => SpatialNavigation.init(BrowserApp.deck, null), window, "SpatialNavigation"); InitLater(() => CastingApps.init(), window, "CastingApps"); InitLater(() => Services.search.init(), Services, "search"); InitLater(() => DownloadNotifications.init(), window, "DownloadNotifications");
--- a/mobile/android/confvars.sh +++ b/mobile/android/confvars.sh @@ -114,10 +114,12 @@ MOZ_ADDON_SIGNING=1 # Enable the Switchboard A/B framework code. # Note: The framework is always included in the app. This flag controls # usage of the framework. if test "$NIGHTLY_BUILD"; then MOZ_SWITCHBOARD=1 fi -# Use native Firefox Accounts UI regardless of channel. +# Use native Firefox Accounts UI after Nightly. +if ! test "$NIGHTLY_BUILD"; then MOZ_ANDROID_NATIVE_ACCOUNT_UI=1 +fi
--- a/mobile/android/tests/browser/robocop/StringHelper.java +++ b/mobile/android/tests/browser/robocop/StringHelper.java @@ -356,17 +356,17 @@ public class StringHelper { CONTEXT_MENU_EDIT_SITE_SETTINGS, CONTEXT_MENU_ADD_TO_HOME_SCREEN }; TITLE_PLACE_HOLDER = res.getString(R.string.url_bar_default_text); // Import strings IMPORT = res.getString(R.string.bookmarkhistory_button_import); - BOOKMARKS = res.getString(R.string.bookmark); + BOOKMARKS = res.getString(R.string.bookmarks_title); // Settings menu strings // Section labels - ordered as found in the settings menu CUSTOMIZE_SECTION_LABEL = res.getString(R.string.pref_category_customize); DISPLAY_SECTION_LABEL = res.getString(R.string.pref_category_display); PRIVACY_SECTION_LABEL = res.getString(R.string.pref_category_privacy_short); MOZILLA_SECTION_LABEL = res.getString(R.string.pref_category_vendor); DEVELOPER_TOOLS_SECTION_LABEL = res.getString(R.string.pref_category_devtools);
--- a/mobile/android/tests/browser/robocop/testSettingsMenuItems.java +++ b/mobile/android/tests/browser/robocop/testSettingsMenuItems.java @@ -147,17 +147,16 @@ public class testSettingsMenuItems exten "The Settings menu did not load", mStringHelper.SETTINGS_LABEL); // Dismiss the Settings screen and verify that the view is returned to about:home page mSolo.goBack(); // Waiting for page title to appear to be sure that is fully loaded before opening the menu mAsserter.ok(mSolo.waitForText(mStringHelper.TITLE_PLACE_HOLDER), "about:home did not load", mStringHelper.TITLE_PLACE_HOLDER); - verifyUrl(mStringHelper.ABOUT_HOME_URL); selectMenuItem(mStringHelper.SETTINGS_LABEL); mAsserter.ok(mSolo.waitForText(mStringHelper.SETTINGS_LABEL), "The Settings menu did not load", mStringHelper.SETTINGS_LABEL); checkForSync(mDevice); checkMenuHierarchy(settingsMenuItems);
--- a/services/datareporting/datareporting-prefs.js +++ b/services/datareporting/datareporting-prefs.js @@ -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/. */ pref("datareporting.policy.dataSubmissionEnabled", true); +pref("datareporting.policy.dataSubmissionEnabled.v2", true); pref("datareporting.policy.firstRunTime", "0"); pref("datareporting.policy.dataSubmissionPolicyNotifiedTime", "0"); pref("datareporting.policy.dataSubmissionPolicyAcceptedVersion", 0); pref("datareporting.policy.dataSubmissionPolicyBypassNotification", false); pref("datareporting.policy.currentPolicyVersion", 2); pref("datareporting.policy.minimumPolicyVersion", 1); pref("datareporting.policy.minimumPolicyVersion.channel-beta", 2);
--- a/services/datareporting/policy.jsm +++ b/services/datareporting/policy.jsm @@ -373,16 +373,26 @@ this.DataReportingPolicy.prototype = Obj // Default is true because we are opt-out. return this._prefs.get("dataSubmissionEnabled", true); }, set dataSubmissionEnabled(value) { this._prefs.set("dataSubmissionEnabled", !!value); }, + /** + * Whether submission of data is allowed for v2. + * + * This is used to gently turn off data submission for FHR v2 in Firefox 42+. + */ + get dataSubmissionEnabledV2() { + // Default is true because we are opt-out. + return this._prefs.get("dataSubmissionEnabled.v2", true); + }, + get currentPolicyVersion() { return this._prefs.get("currentPolicyVersion", DATAREPORTING_POLICY_VERSION); }, /** * The minimum policy version which for dataSubmissionPolicyAccepted to * to be valid. */ @@ -643,17 +653,17 @@ this.DataReportingPolicy.prototype = Obj * * Typically this function is called automatically by the background polling. * But, it can safely be called manually as needed. */ checkStateAndTrigger: function checkStateAndTrigger() { // If the master data submission kill switch is toggled, we have nothing // to do. We don't notify about data policies because this would have // no effect. - if (!this.dataSubmissionEnabled) { + if (!this.dataSubmissionEnabled || !this.dataSubmissionEnabledV2) { this._log.debug("Data submission is disabled. Doing nothing."); return; } let now = this.now(); let nowT = now.getTime(); let nextSubmissionDate = this.nextDataSubmissionDate;
--- a/services/sync/tests/unit/test_fxa_node_reassignment.js +++ b/services/sync/tests/unit/test_fxa_node_reassignment.js @@ -1,321 +1,321 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -_("Test that node reassignment happens correctly using the FxA identity mgr."); -// The node-reassignment logic is quite different for FxA than for the legacy -// provider. In particular, there's no special request necessary for -// reassignment - it comes from the token server - so we need to ensure the -// Fxa cluster manager grabs a new token. - -Cu.import("resource://gre/modules/Log.jsm"); -Cu.import("resource://services-common/rest.js"); -Cu.import("resource://services-sync/constants.js"); -Cu.import("resource://services-sync/service.js"); -Cu.import("resource://services-sync/status.js"); -Cu.import("resource://services-sync/util.js"); -Cu.import("resource://testing-common/services/sync/rotaryengine.js"); -Cu.import("resource://services-sync/browserid_identity.js"); -Cu.import("resource://testing-common/services/sync/utils.js"); - -Service.engineManager.clear(); - -function run_test() { - Log.repository.getLogger("Sync.AsyncResource").level = Log.Level.Trace; - Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace; - Log.repository.getLogger("Sync.Resource").level = Log.Level.Trace; - Log.repository.getLogger("Sync.RESTRequest").level = Log.Level.Trace; - Log.repository.getLogger("Sync.Service").level = Log.Level.Trace; - Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace; - initTestLogging(); - - Service.engineManager.register(RotaryEngine); - - // Setup the FxA identity manager and cluster manager. - Status.__authManager = Service.identity = new BrowserIDManager(); - Service._clusterManager = Service.identity.createClusterManager(Service); - - // None of the failures in this file should result in a UI error. - function onUIError() { - do_throw("Errors should not be presented in the UI."); - } - Svc.Obs.add("weave:ui:login:error", onUIError); - Svc.Obs.add("weave:ui:sync:error", onUIError); - - run_next_test(); -} - - -// API-compatible with SyncServer handler. Bind `handler` to something to use -// as a ServerCollection handler. -function handleReassign(handler, req, resp) { - resp.setStatusLine(req.httpVersion, 401, "Node reassignment"); - resp.setHeader("Content-Type", "application/json"); - let reassignBody = JSON.stringify({error: "401inator in place"}); - resp.bodyOutputStream.write(reassignBody, reassignBody.length); -} - -var numTokenRequests = 0; - -function prepareServer(cbAfterTokenFetch) { - let config = makeIdentityConfig({username: "johndoe"}); - let server = new SyncServer(); - server.registerUser("johndoe"); - server.start(); - - // Set the token endpoint for the initial token request that's done implicitly - // via configureIdentity. - config.fxaccount.token.endpoint = server.baseURI + "1.1/johndoe"; - // And future token fetches will do magic around numReassigns. - let numReassigns = 0; - return configureIdentity(config).then(() => { - Service.identity._tokenServerClient = { - getTokenFromBrowserIDAssertion: function(uri, assertion, cb) { - // Build a new URL with trailing zeros for the SYNC_VERSION part - this - // will still be seen as equivalent by the test server, but different - // by sync itself. - numReassigns += 1; - let trailingZeros = new Array(numReassigns + 1).join('0'); - let token = config.fxaccount.token; - token.endpoint = server.baseURI + "1.1" + trailingZeros + "/johndoe"; - token.uid = config.username; - numTokenRequests += 1; - cb(null, token); - if (cbAfterTokenFetch) { - cbAfterTokenFetch(); - } - }, - }; - Service.clusterURL = config.fxaccount.token.endpoint; - return server; - }); -} - -function getReassigned() { - try { - return Services.prefs.getBoolPref("services.sync.lastSyncReassigned"); - } catch (ex if (ex.result == Cr.NS_ERROR_UNEXPECTED)) { - return false; - } catch (ex) { - do_throw("Got exception retrieving lastSyncReassigned: " + - Utils.exceptionStr(ex)); - } -} - -/** - * Make a test request to `url`, then watch the result of two syncs - * to ensure that a node request was made. - * Runs `between` between the two. This can be used to undo deliberate failure - * setup, detach observers, etc. - */ -function syncAndExpectNodeReassignment(server, firstNotification, between, - secondNotification, url) { - _("Starting syncAndExpectNodeReassignment\n"); - let deferred = Promise.defer(); - function onwards() { - let numTokenRequestsBefore; - function onFirstSync() { - _("First sync completed."); - Svc.Obs.remove(firstNotification, onFirstSync); - Svc.Obs.add(secondNotification, onSecondSync); - - do_check_eq(Service.clusterURL, ""); - - // Track whether we fetched a new token. - numTokenRequestsBefore = numTokenRequests; - - // Allow for tests to clean up error conditions. - between(); - } - function onSecondSync() { - _("Second sync completed."); - Svc.Obs.remove(secondNotification, onSecondSync); - Service.scheduler.clearSyncTriggers(); - - // Make absolutely sure that any event listeners are done with their work - // before we proceed. - waitForZeroTimer(function () { - _("Second sync nextTick."); - do_check_eq(numTokenRequests, numTokenRequestsBefore + 1, "fetched a new token"); - Service.startOver(); - server.stop(deferred.resolve); - }); - } - - Svc.Obs.add(firstNotification, onFirstSync); - Service.sync(); - } - - // Make sure that it works! - _("Making request to " + url + " which should 401"); - let request = new RESTRequest(url); - request.get(function () { - do_check_eq(request.response.status, 401); - Utils.nextTick(onwards); - }); - yield deferred.promise; -} - -add_task(function test_momentary_401_engine() { - _("Test a failure for engine URLs that's resolved by reassignment."); - let server = yield prepareServer(); - let john = server.user("johndoe"); - - _("Enabling the Rotary engine."); - let engine = Service.engineManager.get("rotary"); - engine.enabled = true; - - // We need the server to be correctly set up prior to experimenting. Do this - // through a sync. - let global = {syncID: Service.syncID, - storageVersion: STORAGE_VERSION, - rotary: {version: engine.version, - syncID: engine.syncID}} - john.createCollection("meta").insert("global", global); - - _("First sync to prepare server contents."); - Service.sync(); - - _("Setting up Rotary collection to 401."); - let rotary = john.createCollection("rotary"); - let oldHandler = rotary.collectionHandler; - rotary.collectionHandler = handleReassign.bind(this, undefined); - - // We want to verify that the clusterURL pref has been cleared after a 401 - // inside a sync. Flag the Rotary engine to need syncing. - john.collection("rotary").timestamp += 1000; - - function between() { - _("Undoing test changes."); - rotary.collectionHandler = oldHandler; - - function onLoginStart() { - // lastSyncReassigned shouldn't be cleared until a sync has succeeded. - _("Ensuring that lastSyncReassigned is still set at next sync start."); - Svc.Obs.remove("weave:service:login:start", onLoginStart); - do_check_true(getReassigned()); - } - - _("Adding observer that lastSyncReassigned is still set on login."); - Svc.Obs.add("weave:service:login:start", onLoginStart); - } - - yield syncAndExpectNodeReassignment(server, - "weave:service:sync:finish", - between, - "weave:service:sync:finish", - Service.storageURL + "rotary"); -}); - -// This test ends up being a failing info fetch *after we're already logged in*. -add_task(function test_momentary_401_info_collections_loggedin() { - _("Test a failure for info/collections after login that's resolved by reassignment."); - let server = yield prepareServer(); - - _("First sync to prepare server contents."); - Service.sync(); - - _("Arrange for info/collections to return a 401."); - let oldHandler = server.toplevelHandlers.info; - server.toplevelHandlers.info = handleReassign; - - function undo() { - _("Undoing test changes."); - server.toplevelHandlers.info = oldHandler; - } - - do_check_true(Service.isLoggedIn, "already logged in"); - - yield syncAndExpectNodeReassignment(server, - "weave:service:sync:error", - undo, - "weave:service:sync:finish", - Service.infoURL); -}); - -// This test ends up being a failing info fetch *before we're logged in*. -// In this case we expect to recover during the login phase - so the first -// sync succeeds. -add_task(function test_momentary_401_info_collections_loggedout() { - _("Test a failure for info/collections before login that's resolved by reassignment."); - - let oldHandler; - let sawTokenFetch = false; - - function afterTokenFetch() { - // After a single token fetch, we undo our evil handleReassign hack, so - // the next /info request returns the collection instead of a 401 - server.toplevelHandlers.info = oldHandler; - sawTokenFetch = true; - } - - let server = yield prepareServer(afterTokenFetch); - - // Return a 401 for the next /info request - it will be reset immediately - // after a new token is fetched. - oldHandler = server.toplevelHandlers.info - server.toplevelHandlers.info = handleReassign; - - do_check_false(Service.isLoggedIn, "not already logged in"); - - Service.sync(); - do_check_eq(Status.sync, SYNC_SUCCEEDED, "sync succeeded"); - // sync was successful - check we grabbed a new token. - do_check_true(sawTokenFetch, "a new token was fetched by this test.") - // and we are done. - Service.startOver(); - let deferred = Promise.defer(); - server.stop(deferred.resolve); - yield deferred.promise; -}); - -// This test ends up being a failing meta/global fetch *after we're already logged in*. -add_task(function test_momentary_401_storage_loggedin() { - _("Test a failure for any storage URL after login that's resolved by" + - "reassignment."); - let server = yield prepareServer(); - - _("First sync to prepare server contents."); - Service.sync(); - - _("Arrange for meta/global to return a 401."); - let oldHandler = server.toplevelHandlers.storage; - server.toplevelHandlers.storage = handleReassign; - - function undo() { - _("Undoing test changes."); - server.toplevelHandlers.storage = oldHandler; - } - - do_check_true(Service.isLoggedIn, "already logged in"); - - yield syncAndExpectNodeReassignment(server, - "weave:service:sync:error", - undo, - "weave:service:sync:finish", - Service.storageURL + "meta/global"); -}); - -// This test ends up being a failing meta/global fetch *before we've logged in*. -add_task(function test_momentary_401_storage_loggedout() { - _("Test a failure for any storage URL before login, not just engine parts. " + - "Resolved by reassignment."); - let server = yield prepareServer(); - - // Return a 401 for all storage requests. - let oldHandler = server.toplevelHandlers.storage; - server.toplevelHandlers.storage = handleReassign; - - function undo() { - _("Undoing test changes."); - server.toplevelHandlers.storage = oldHandler; - } - - do_check_false(Service.isLoggedIn, "already logged in"); - - yield syncAndExpectNodeReassignment(server, - "weave:service:login:error", - undo, - "weave:service:sync:finish", - Service.storageURL + "meta/global"); -}); - +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +_("Test that node reassignment happens correctly using the FxA identity mgr."); +// The node-reassignment logic is quite different for FxA than for the legacy +// provider. In particular, there's no special request necessary for +// reassignment - it comes from the token server - so we need to ensure the +// Fxa cluster manager grabs a new token. + +Cu.import("resource://gre/modules/Log.jsm"); +Cu.import("resource://services-common/rest.js"); +Cu.import("resource://services-sync/constants.js"); +Cu.import("resource://services-sync/service.js"); +Cu.import("resource://services-sync/status.js"); +Cu.import("resource://services-sync/util.js"); +Cu.import("resource://testing-common/services/sync/rotaryengine.js"); +Cu.import("resource://services-sync/browserid_identity.js"); +Cu.import("resource://testing-common/services/sync/utils.js"); + +Service.engineManager.clear(); + +function run_test() { + Log.repository.getLogger("Sync.AsyncResource").level = Log.Level.Trace; + Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace; + Log.repository.getLogger("Sync.Resource").level = Log.Level.Trace; + Log.repository.getLogger("Sync.RESTRequest").level = Log.Level.Trace; + Log.repository.getLogger("Sync.Service").level = Log.Level.Trace; + Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace; + initTestLogging(); + + Service.engineManager.register(RotaryEngine); + + // Setup the FxA identity manager and cluster manager. + Status.__authManager = Service.identity = new BrowserIDManager(); + Service._clusterManager = Service.identity.createClusterManager(Service); + + // None of the failures in this file should result in a UI error. + function onUIError() { + do_throw("Errors should not be presented in the UI."); + } + Svc.Obs.add("weave:ui:login:error", onUIError); + Svc.Obs.add("weave:ui:sync:error", onUIError); + + run_next_test(); +} + + +// API-compatible with SyncServer handler. Bind `handler` to something to use +// as a ServerCollection handler. +function handleReassign(handler, req, resp) { + resp.setStatusLine(req.httpVersion, 401, "Node reassignment"); + resp.setHeader("Content-Type", "application/json"); + let reassignBody = JSON.stringify({error: "401inator in place"}); + resp.bodyOutputStream.write(reassignBody, reassignBody.length); +} + +var numTokenRequests = 0; + +function prepareServer(cbAfterTokenFetch) { + let config = makeIdentityConfig({username: "johndoe"}); + let server = new SyncServer(); + server.registerUser("johndoe"); + server.start(); + + // Set the token endpoint for the initial token request that's done implicitly + // via configureIdentity. + config.fxaccount.token.endpoint = server.baseURI + "1.1/johndoe"; + // And future token fetches will do magic around numReassigns. + let numReassigns = 0; + return configureIdentity(config).then(() => { + Service.identity._tokenServerClient = { + getTokenFromBrowserIDAssertion: function(uri, assertion, cb) { + // Build a new URL with trailing zeros for the SYNC_VERSION part - this + // will still be seen as equivalent by the test server, but different + // by sync itself. + numReassigns += 1; + let trailingZeros = new Array(numReassigns + 1).join('0'); + let token = config.fxaccount.token; + token.endpoint = server.baseURI + "1.1" + trailingZeros + "/johndoe"; + token.uid = config.username; + numTokenRequests += 1; + cb(null, token); + if (cbAfterTokenFetch) { + cbAfterTokenFetch(); + } + }, + }; + Service.clusterURL = config.fxaccount.token.endpoint; + return server; + }); +} + +function getReassigned() { + try { + return Services.prefs.getBoolPref("services.sync.lastSyncReassigned"); + } catch (ex if (ex.result == Cr.NS_ERROR_UNEXPECTED)) { + return false; + } catch (ex) { + do_throw("Got exception retrieving lastSyncReassigned: " + + Utils.exceptionStr(ex)); + } +} + +/** + * Make a test request to `url`, then watch the result of two syncs + * to ensure that a node request was made. + * Runs `between` between the two. This can be used to undo deliberate failure + * setup, detach observers, etc. + */ +function syncAndExpectNodeReassignment(server, firstNotification, between, + secondNotification, url) { + _("Starting syncAndExpectNodeReassignment\n"); + let deferred = Promise.defer(); + function onwards() { + let numTokenRequestsBefore; + function onFirstSync() { + _("First sync completed."); + Svc.Obs.remove(firstNotification, onFirstSync); + Svc.Obs.add(secondNotification, onSecondSync); + + do_check_eq(Service.clusterURL, ""); + + // Track whether we fetched a new token. + numTokenRequestsBefore = numTokenRequests; + + // Allow for tests to clean up error conditions. + between(); + } + function onSecondSync() { + _("Second sync completed."); + Svc.Obs.remove(secondNotification, onSecondSync); + Service.scheduler.clearSyncTriggers(); + + // Make absolutely sure that any event listeners are done with their work + // before we proceed. + waitForZeroTimer(function () { + _("Second sync nextTick."); + do_check_eq(numTokenRequests, numTokenRequestsBefore + 1, "fetched a new token"); + Service.startOver(); + server.stop(deferred.resolve); + }); + } + + Svc.Obs.add(firstNotification, onFirstSync); + Service.sync(); + } + + // Make sure that it works! + _("Making request to " + url + " which should 401"); + let request = new RESTRequest(url); + request.get(function () { + do_check_eq(request.response.status, 401); + Utils.nextTick(onwards); + }); + yield deferred.promise; +} + +add_task(function test_momentary_401_engine() { + _("Test a failure for engine URLs that's resolved by reassignment."); + let server = yield prepareServer(); + let john = server.user("johndoe"); + + _("Enabling the Rotary engine."); + let engine = Service.engineManager.get("rotary"); + engine.enabled = true; + + // We need the server to be correctly set up prior to experimenting. Do this + // through a sync. + let global = {syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + rotary: {version: engine.version, + syncID: engine.syncID}} + john.createCollection("meta").insert("global", global); + + _("First sync to prepare server contents."); + Service.sync(); + + _("Setting up Rotary collection to 401."); + let rotary = john.createCollection("rotary"); + let oldHandler = rotary.collectionHandler; + rotary.collectionHandler = handleReassign.bind(this, undefined); + + // We want to verify that the clusterURL pref has been cleared after a 401 + // inside a sync. Flag the Rotary engine to need syncing. + john.collection("rotary").timestamp += 1000; + + function between() { + _("Undoing test changes."); + rotary.collectionHandler = oldHandler; + + function onLoginStart() { + // lastSyncReassigned shouldn't be cleared until a sync has succeeded. + _("Ensuring that lastSyncReassigned is still set at next sync start."); + Svc.Obs.remove("weave:service:login:start", onLoginStart); + do_check_true(getReassigned()); + } + + _("Adding observer that lastSyncReassigned is still set on login."); + Svc.Obs.add("weave:service:login:start", onLoginStart); + } + + yield syncAndExpectNodeReassignment(server, + "weave:service:sync:finish", + between, + "weave:service:sync:finish", + Service.storageURL + "rotary"); +}); + +// This test ends up being a failing info fetch *after we're already logged in*. +add_task(function test_momentary_401_info_collections_loggedin() { + _("Test a failure for info/collections after login that's resolved by reassignment."); + let server = yield prepareServer(); + + _("First sync to prepare server contents."); + Service.sync(); + + _("Arrange for info/collections to return a 401."); + let oldHandler = server.toplevelHandlers.info; + server.toplevelHandlers.info = handleReassign; + + function undo() { + _("Undoing test changes."); + server.toplevelHandlers.info = oldHandler; + } + + do_check_true(Service.isLoggedIn, "already logged in"); + + yield syncAndExpectNodeReassignment(server, + "weave:service:sync:error", + undo, + "weave:service:sync:finish", + Service.infoURL); +}); + +// This test ends up being a failing info fetch *before we're logged in*. +// In this case we expect to recover during the login phase - so the first +// sync succeeds. +add_task(function test_momentary_401_info_collections_loggedout() { + _("Test a failure for info/collections before login that's resolved by reassignment."); + + let oldHandler; + let sawTokenFetch = false; + + function afterTokenFetch() { + // After a single token fetch, we undo our evil handleReassign hack, so + // the next /info request returns the collection instead of a 401 + server.toplevelHandlers.info = oldHandler; + sawTokenFetch = true; + } + + let server = yield prepareServer(afterTokenFetch); + + // Return a 401 for the next /info request - it will be reset immediately + // after a new token is fetched. + oldHandler = server.toplevelHandlers.info + server.toplevelHandlers.info = handleReassign; + + do_check_false(Service.isLoggedIn, "not already logged in"); + + Service.sync(); + do_check_eq(Status.sync, SYNC_SUCCEEDED, "sync succeeded"); + // sync was successful - check we grabbed a new token. + do_check_true(sawTokenFetch, "a new token was fetched by this test.") + // and we are done. + Service.startOver(); + let deferred = Promise.defer(); + server.stop(deferred.resolve); + yield deferred.promise; +}); + +// This test ends up being a failing meta/global fetch *after we're already logged in*. +add_task(function test_momentary_401_storage_loggedin() { + _("Test a failure for any storage URL after login that's resolved by" + + "reassignment."); + let server = yield prepareServer(); + + _("First sync to prepare server contents."); + Service.sync(); + + _("Arrange for meta/global to return a 401."); + let oldHandler = server.toplevelHandlers.storage; + server.toplevelHandlers.storage = handleReassign; + + function undo() { + _("Undoing test changes."); + server.toplevelHandlers.storage = oldHandler; + } + + do_check_true(Service.isLoggedIn, "already logged in"); + + yield syncAndExpectNodeReassignment(server, + "weave:service:sync:error", + undo, + "weave:service:sync:finish", + Service.storageURL + "meta/global"); +}); + +// This test ends up being a failing meta/global fetch *before we've logged in*. +add_task(function test_momentary_401_storage_loggedout() { + _("Test a failure for any storage URL before login, not just engine parts. " + + "Resolved by reassignment."); + let server = yield prepareServer(); + + // Return a 401 for all storage requests. + let oldHandler = server.toplevelHandlers.storage; + server.toplevelHandlers.storage = handleReassign; + + function undo() { + _("Undoing test changes."); + server.toplevelHandlers.storage = oldHandler; + } + + do_check_false(Service.isLoggedIn, "already logged in"); + + yield syncAndExpectNodeReassignment(server, + "weave:service:login:error", + undo, + "weave:service:sync:finish", + Service.storageURL + "meta/global"); +}); +
--- a/services/sync/tests/unit/test_fxa_service_cluster.js +++ b/services/sync/tests/unit/test_fxa_service_cluster.js @@ -1,68 +1,68 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -Cu.import("resource://services-sync/service.js"); -Cu.import("resource://services-sync/util.js"); -Cu.import("resource://testing-common/services/sync/fxa_utils.js"); -Cu.import("resource://testing-common/services/sync/utils.js"); - -add_task(function test_findCluster() { - _("Test FxA _findCluster()"); - - _("_findCluster() throws on 500 errors."); - initializeIdentityWithTokenServerResponse({ - status: 500, - headers: [], - body: "", - }); - - yield Service.identity.initializeWithCurrentIdentity(); - yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise, - "should reject due to 500"); - - Assert.throws(function() { - Service._clusterManager._findCluster(); - }); - - _("_findCluster() returns null on authentication errors."); - initializeIdentityWithTokenServerResponse({ - status: 401, - headers: {"content-type": "application/json"}, - body: "{}", - }); - - yield Service.identity.initializeWithCurrentIdentity(); - yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise, - "should reject due to 401"); - - cluster = Service._clusterManager._findCluster(); - Assert.strictEqual(cluster, null); - - _("_findCluster() works with correct tokenserver response."); - let endpoint = "http://example.com/something"; - initializeIdentityWithTokenServerResponse({ - status: 200, - headers: {"content-type": "application/json"}, - body: - JSON.stringify({ - api_endpoint: endpoint, - duration: 300, - id: "id", - key: "key", - uid: "uid", - }) - }); - - yield Service.identity.initializeWithCurrentIdentity(); - yield Service.identity.whenReadyToAuthenticate.promise; - cluster = Service._clusterManager._findCluster(); - // The cluster manager ensures a trailing "/" - Assert.strictEqual(cluster, endpoint + "/"); - - Svc.Prefs.resetBranch(""); -}); - -function run_test() { - initTestLogging(); - run_next_test(); -} +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource://services-sync/service.js"); +Cu.import("resource://services-sync/util.js"); +Cu.import("resource://testing-common/services/sync/fxa_utils.js"); +Cu.import("resource://testing-common/services/sync/utils.js"); + +add_task(function test_findCluster() { + _("Test FxA _findCluster()"); + + _("_findCluster() throws on 500 errors."); + initializeIdentityWithTokenServerResponse({ + status: 500, + headers: [], + body: "", + }); + + yield Service.identity.initializeWithCurrentIdentity(); + yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise, + "should reject due to 500"); + + Assert.throws(function() { + Service._clusterManager._findCluster(); + }); + + _("_findCluster() returns null on authentication errors."); + initializeIdentityWithTokenServerResponse({ + status: 401, + headers: {"content-type": "application/json"}, + body: "{}", + }); + + yield Service.identity.initializeWithCurrentIdentity(); + yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise, + "should reject due to 401"); + + cluster = Service._clusterManager._findCluster(); + Assert.strictEqual(cluster, null); + + _("_findCluster() works with correct tokenserver response."); + let endpoint = "http://example.com/something"; + initializeIdentityWithTokenServerResponse({ + status: 200, + headers: {"content-type": "application/json"}, + body: + JSON.stringify({ + api_endpoint: endpoint, + duration: 300, + id: "id", + key: "key", + uid: "uid", + }) + }); + + yield Service.identity.initializeWithCurrentIdentity(); + yield Service.identity.whenReadyToAuthenticate.promise; + cluster = Service._clusterManager._findCluster(); + // The cluster manager ensures a trailing "/" + Assert.strictEqual(cluster, endpoint + "/"); + + Svc.Prefs.resetBranch(""); +}); + +function run_test() { + initTestLogging(); + run_next_test(); +}
--- a/services/sync/tests/unit/test_fxa_startOver.js +++ b/services/sync/tests/unit/test_fxa_startOver.js @@ -1,63 +1,63 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -Cu.import("resource://testing-common/services/sync/utils.js"); -Cu.import("resource://services-sync/identity.js"); -Cu.import("resource://services-sync/browserid_identity.js"); -Cu.import("resource://services-sync/service.js"); - -function run_test() { - initTestLogging("Trace"); - run_next_test(); -} - -add_task(function* test_startover() { - let oldValue = Services.prefs.getBoolPref("services.sync-testing.startOverKeepIdentity", true); - Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", false); - - ensureLegacyIdentityManager(); - yield configureIdentity({username: "johndoe"}); - - // The boolean flag on the xpcom service should reflect a legacy provider. - let xps = Cc["@mozilla.org/weave/service;1"] - .getService(Components.interfaces.nsISupports) - .wrappedJSObject; - do_check_false(xps.fxAccountsEnabled); - - // we expect the "legacy" provider (but can't instanceof that, as BrowserIDManager - // extends it) - do_check_false(Service.identity instanceof BrowserIDManager); - - Service.serverURL = "https://localhost/"; - Service.clusterURL = Service.serverURL; - - Service.login(); - // We should have a cluster URL - do_check_true(Service.clusterURL.length > 0); - - // remember some stuff so we can reset it after. - let oldIdentity = Service.identity; - let oldClusterManager = Service._clusterManager; - let deferred = Promise.defer(); - Services.obs.addObserver(function observeStartOverFinished() { - Services.obs.removeObserver(observeStartOverFinished, "weave:service:start-over:finish"); - deferred.resolve(); - }, "weave:service:start-over:finish", false); - - Service.startOver(); - yield deferred.promise; // wait for the observer to fire. - - // the xpcom service should indicate FxA is enabled. - do_check_true(xps.fxAccountsEnabled); - // should have swapped identities. - do_check_true(Service.identity instanceof BrowserIDManager); - // should have clobbered the cluster URL - do_check_eq(Service.clusterURL, ""); - - // we should have thrown away the old identity provider and cluster manager. - do_check_neq(oldIdentity, Service.identity); - do_check_neq(oldClusterManager, Service._clusterManager); - - // reset the world. - Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", oldValue); -}); +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource://testing-common/services/sync/utils.js"); +Cu.import("resource://services-sync/identity.js"); +Cu.import("resource://services-sync/browserid_identity.js"); +Cu.import("resource://services-sync/service.js"); + +function run_test() { + initTestLogging("Trace"); + run_next_test(); +} + +add_task(function* test_startover() { + let oldValue = Services.prefs.getBoolPref("services.sync-testing.startOverKeepIdentity", true); + Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", false); + + ensureLegacyIdentityManager(); + yield configureIdentity({username: "johndoe"}); + + // The boolean flag on the xpcom service should reflect a legacy provider. + let xps = Cc["@mozilla.org/weave/service;1"] + .getService(Components.interfaces.nsISupports) + .wrappedJSObject; + do_check_false(xps.fxAccountsEnabled); + + // we expect the "legacy" provider (but can't instanceof that, as BrowserIDManager + // extends it) + do_check_false(Service.identity instanceof BrowserIDManager); + + Service.serverURL = "https://localhost/"; + Service.clusterURL = Service.serverURL; + + Service.login(); + // We should have a cluster URL + do_check_true(Service.clusterURL.length > 0); + + // remember some stuff so we can reset it after. + let oldIdentity = Service.identity; + let oldClusterManager = Service._clusterManager; + let deferred = Promise.defer(); + Services.obs.addObserver(function observeStartOverFinished() { + Services.obs.removeObserver(observeStartOverFinished, "weave:service:start-over:finish"); + deferred.resolve(); + }, "weave:service:start-over:finish", false); + + Service.startOver(); + yield deferred.promise; // wait for the observer to fire. + + // the xpcom service should indicate FxA is enabled. + do_check_true(xps.fxAccountsEnabled); + // should have swapped identities. + do_check_true(Service.identity instanceof BrowserIDManager); + // should have clobbered the cluster URL + do_check_eq(Service.clusterURL, ""); + + // we should have thrown away the old identity provider and cluster manager. + do_check_neq(oldIdentity, Service.identity); + do_check_neq(oldClusterManager, Service._clusterManager); + + // reset the world. + Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", oldValue); +});
--- a/testing/mozharness/configs/developer_config.py +++ b/testing/mozharness/configs/developer_config.py @@ -32,16 +32,18 @@ config = { # Pip "find_links": ["http://pypi.pub.build.mozilla.org/pub"], "pip_index": False, # Talos related "python_webserver": True, "virtualenv_path": '%s/build/venv' % os.getcwd(), + "preflight_run_cmd_suites": [], + "postflight_run_cmd_suites": [], # Tooltool related "download_tooltool": True, "tooltool_cache": os.path.join(LOCAL_WORKDIR, "builds/tooltool_cache"), "tooltool_cache_path": os.path.join(LOCAL_WORKDIR, "builds/tooltool_cache"), # VCS tools "hgtool.py": 'http://hg.mozilla.org/build/puppet/raw-file/faaf5abd792e/modules/packages/files/hgtool.py',
--- a/toolkit/components/alerts/resources/content/alert.js +++ b/toolkit/components/alerts/resources/content/alert.js @@ -1,29 +1,23 @@ -// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - /* 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/. */ -Components.utils.import("resource://gre/modules/Services.jsm"); - -const Ci = Components.interfaces; -const Cc = Components.classes; - -var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"] - .getService(Ci.nsIWindowMediator); +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; // Copied from nsILookAndFeel.h, see comments on eMetric_AlertNotificationOrigin const NS_ALERT_HORIZONTAL = 1; const NS_ALERT_LEFT = 2; const NS_ALERT_TOP = 4; const WINDOW_MARGIN = 10; +Cu.import("resource://gre/modules/Services.jsm"); + var gOrigin = 0; // Default value: alert from bottom right. var gReplacedWindow = null; var gAlertListener = null; var gAlertTextClickable = false; var gAlertCookie = ""; var gIsReplaced = false; function prefillAlertInfo() { @@ -114,17 +108,17 @@ function onAlertLoad() { } } function moveWindowToReplace(aReplacedAlert) { let heightDelta = window.outerHeight - aReplacedAlert.outerHeight; // Move windows that come after the replaced alert if the height is different. if (heightDelta != 0) { - let windows = windowMediator.getEnumerator('alert:alert'); + let windows = Services.wm.getEnumerator('alert:alert'); while (windows.hasMoreElements()) { let alertWindow = windows.getNext(); // boolean to determine if the alert window is after the replaced alert. let alertIsAfter = gOrigin & NS_ALERT_TOP ? alertWindow.screenY > aReplacedAlert.screenY : aReplacedAlert.screenY > alertWindow.screenY; if (alertIsAfter) { // The new Y position of the window. @@ -144,17 +138,17 @@ function moveWindowToReplace(aReplacedAl function moveWindowToEnd() { // Determine position let x = gOrigin & NS_ALERT_LEFT ? screen.availLeft : screen.availLeft + screen.availWidth - window.outerWidth; let y = gOrigin & NS_ALERT_TOP ? screen.availTop : screen.availTop + screen.availHeight - window.outerHeight; // Position the window at the end of all alerts. - let windows = windowMediator.getEnumerator('alert:alert'); + let windows = Services.wm.getEnumerator('alert:alert'); while (windows.hasMoreElements()) { let alertWindow = windows.getNext(); if (alertWindow != window) { if (gOrigin & NS_ALERT_TOP) { y = Math.max(y, alertWindow.screenY + alertWindow.outerHeight); } else { y = Math.min(y, alertWindow.screenY - window.outerHeight); } @@ -167,17 +161,17 @@ function moveWindowToEnd() { window.moveTo(x, y); } function onAlertBeforeUnload() { if (!gIsReplaced) { // Move other alert windows to fill the gap left by closing alert. let heightDelta = window.outerHeight + WINDOW_MARGIN; - let windows = windowMediator.getEnumerator('alert:alert'); + let windows = Services.wm.getEnumerator('alert:alert'); while (windows.hasMoreElements()) { let alertWindow = windows.getNext(); if (alertWindow != window) { if (gOrigin & NS_ALERT_TOP) { if (alertWindow.screenY > window.screenY) { alertWindow.moveTo(alertWindow.screenX, alertWindow.screenY - heightDelta); } } else {
--- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -8166,16 +8166,21 @@ "n_values": 8, "description": "Baseline Requirements section 9.2.2: subject common name field (0: present, in subject alt. names; 1: not present; 2: not present in subject alt. names)" }, "TRACKING_PROTECTION_ENABLED": { "expires_in_version": "never", "kind": "boolean", "description": "Whether or not a session has tracking protection enabled" }, + "TRACKING_PROTECTION_PBM_DISABLED": { + "expires_in_version": "60", + "kind": "boolean", + "description": "Is the tracking protection in private browsing mode disabled?" + }, "TRACKING_PROTECTION_SHIELD": { "expires_in_version": "never", "kind": "enumerated", "n_values": 4, "description": "Tracking protection shield (0 = not shown, 1 = loaded, 2 = blocked)" }, "TRACKING_PROTECTION_EVENTS": { "expires_in_version": "never", @@ -8911,16 +8916,22 @@ "n_values": 3, "description": "The number of times the tab queue prompt was seen before the user selected NO." }, "FENNEC_TABQUEUE_ENABLED": { "expires_in_version": "never", "kind": "boolean", "description": "Has the tab queue functionality been enabled." }, + "FENNEC_OPEN_URLS_IN_PRIVATE": { + "alert_emails": ["margaret@mozilla.com"], + "expires_in_version": "44", + "kind": "flag", + "description": "Reports the state of the open external links in private tabs preference" + }, "VIDEO_EME_DISABLED": { "alert_emails": ["edwin@mozilla.com"], "expires_in_version": "45", "kind": "boolean", "description": "Set if media.eme.enabled is false, in a build that supports the Adobe Primetime Content Decryption Module." }, "GRAPHICS_DRIVER_STARTUP_TEST": { "alert_emails": ["danderson@mozilla.com"],
--- a/toolkit/components/telemetry/TelemetryController.jsm +++ b/toolkit/components/telemetry/TelemetryController.jsm @@ -18,16 +18,17 @@ Cu.import("resource://gre/modules/XPCOMU Cu.import("resource://gre/modules/osfile.jsm", this); Cu.import("resource://gre/modules/Promise.jsm", this); Cu.import("resource://gre/modules/PromiseUtils.jsm", this); Cu.import("resource://gre/modules/Task.jsm", this); Cu.import("resource://gre/modules/DeferredTask.jsm", this); Cu.import("resource://gre/modules/Preferences.jsm"); Cu.import("resource://gre/modules/Timer.jsm"); Cu.import("resource://gre/modules/TelemetryUtils.jsm", this); +Cu.import("resource://gre/modules/AppConstants.jsm"); const Utils = TelemetryUtils; const LOGGER_NAME = "Toolkit.Telemetry"; const LOGGER_PREFIX = "TelemetryController::"; const PREF_BRANCH = "toolkit.telemetry."; const PREF_BRANCH_LOG = PREF_BRANCH + "log."; @@ -669,28 +670,28 @@ var Impl = { // Configure base Telemetry recording. // Unified Telemetry makes it opt-out unless the unifedOptin pref is set. // Additionally, we make Telemetry opt-out for a 5% sample. // If extended Telemetry is enabled, base recording is always on as well. const enabled = Preferences.get(PREF_ENABLED, false); const isOptout = IS_UNIFIED_TELEMETRY && (!Policy.isUnifiedOptin() || this._isInOptoutSample()); Telemetry.canRecordBase = enabled || isOptout; -#ifdef MOZILLA_OFFICIAL - // Enable extended telemetry if: - // * the telemetry preference is set and - // * this is an official build or we are in test-mode - // We only do the latter check for official builds so that e.g. developer builds - // still enable Telemetry based on prefs. - Telemetry.canRecordExtended = enabled && (Telemetry.isOfficialTelemetry || this._testMode); -#else - // Turn off extended telemetry recording if disabled by preferences or if base/telemetry - // telemetry recording is off. - Telemetry.canRecordExtended = enabled; -#endif + if (AppConstants.MOZILLA_OFFICIAL) { + // Enable extended telemetry if: + // * the telemetry preference is set and + // * this is an official build or we are in test-mode + // We only do the latter check for official builds so that e.g. developer builds + // still enable Telemetry based on prefs. + Telemetry.canRecordExtended = enabled && (Telemetry.isOfficialTelemetry || this._testMode); + } else { + // Turn off extended telemetry recording if disabled by preferences or if base/telemetry + // telemetry recording is off. + Telemetry.canRecordExtended = enabled; + } this._log.config("enableTelemetryRecording - canRecordBase:" + Telemetry.canRecordBase + ", canRecordExtended: " + Telemetry.canRecordExtended); return Telemetry.canRecordBase; }, /**
--- a/toolkit/components/telemetry/TelemetryEnvironment.jsm +++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm @@ -15,26 +15,27 @@ Cu.import("resource://gre/modules/XPCOMU Cu.import("resource://gre/modules/Task.jsm"); Cu.import("resource://gre/modules/Log.jsm"); Cu.import("resource://gre/modules/Preferences.jsm"); Cu.import("resource://gre/modules/PromiseUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/TelemetryUtils.jsm", this); Cu.import("resource://gre/modules/ObjectUtils.jsm"); Cu.import("resource://gre/modules/TelemetryController.jsm", this); +Cu.import("resource://gre/modules/AppConstants.jsm"); const Utils = TelemetryUtils; XPCOMUtils.defineLazyModuleGetter(this, "ctypes", "resource://gre/modules/ctypes.jsm"); -#ifndef MOZ_WIDGET_GONK -Cu.import("resource://gre/modules/AddonManager.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager", - "resource://gre/modules/LightweightThemeManager.jsm"); -#endif +if (AppConstants.platform !== "gonk") { + Cu.import("resource://gre/modules/AddonManager.jsm"); + XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager", + "resource://gre/modules/LightweightThemeManager.jsm"); +} XPCOMUtils.defineLazyModuleGetter(this, "ProfileAge", "resource://gre/modules/ProfileAge.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils", "resource://gre/modules/UpdateUtils.jsm"); const CHANGE_THROTTLE_INTERVAL_MS = 5 * 60 * 1000; /** @@ -269,24 +270,29 @@ function getGfxAdapter(aSuffix = "") { subsysID: getGfxField("adapterSubsysID" + aSuffix, null), RAM: memoryMB, driver: getGfxField("adapterDriver" + aSuffix, null), driverVersion: getGfxField("adapterDriverVersion" + aSuffix, null), driverDate: getGfxField("adapterDriverDate" + aSuffix, null), }; } -#ifdef XP_WIN /** * Gets the service pack information on Windows platforms. This was copied from * nsUpdateService.js. * * @return An object containing the service pack major and minor versions. */ function getServicePack() { + const UNKNOWN_SERVICE_PACK = {major: null, minor: null}; + + if (AppConstants.platform !== "win") { + return UNKNOWN_SERVICE_PACK; + } + const BYTE = ctypes.uint8_t; const WORD = ctypes.uint16_t; const DWORD = ctypes.uint32_t; const WCHAR = ctypes.char16_t; const BOOL = ctypes.int; // This structure is described at: // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx @@ -319,25 +325,21 @@ function getServicePack() { throw("Failure in GetVersionEx (returned 0)"); } return { major: winVer.wServicePackMajor, minor: winVer.wServicePackMinor, }; } catch (e) { - return { - major: null, - minor: null, - }; + return UNKNOWN_SERVICE_PACK; } finally { kernel32.close(); } } -#endif /** * Encapsulates the asynchronous magic interfacing with the addon manager. The builder * is owned by a parent environment object and is an addon listener. */ function EnvironmentAddonBuilder(environment) { this._environment = environment; @@ -443,22 +445,22 @@ EnvironmentAddonBuilder.prototype = { * * @returns Promise<Object> This returns a Promise resolved with a status object with the following members: * changed - Whether the environment changed. * oldEnvironment - Only set if a change occured, contains the environment data before the change. */ _updateAddons: Task.async(function* () { this._environment._log.trace("_updateAddons"); let personaId = null; -#ifndef MOZ_WIDGET_GONK - let theme = LightweightThemeManager.currentTheme; - if (theme) { - personaId = theme.id; + if (AppConstants.platform !== "gonk") { + let theme = LightweightThemeManager.currentTheme; + if (theme) { + personaId = theme.id; + } } -#endif let addons = { activeAddons: yield this._getActiveAddons(), theme: yield this._getActiveTheme(), activePlugins: this._getActivePlugins(), activeGMPlugins: yield this._getActiveGMPlugins(), activeExperiment: this._getActiveExperiment(), persona: personaId, @@ -663,30 +665,30 @@ function EnvironmentCache() { this._updateSettings(); // Fill in the default search engine, if the search provider is already initialized. this._updateSearchEngine(); // Build the remaining asynchronous parts of the environment. Don't register change listeners // until the initial environment has been built. -#ifdef MOZ_WIDGET_GONK - this._addonBuilder = { - watchForChanges: function() {} + let p = []; + if (AppConstants.platform === "gonk") { + this._addonBuilder = { + watchForChanges: function() {} + }; + } else { + this._addonBuilder = new EnvironmentAddonBuilder(this); + p = [ this._addonBuilder.init() ]; } - let p = []; -#else - this._addonBuilder = new EnvironmentAddonBuilder(this); - let p = [ this._addonBuilder.init() ]; -#endif -#ifndef MOZ_WIDGET_ANDROID - this._currentEnvironment.profile = {}; - p.push(this._updateProfile()); -#endif + if (AppConstants.platform !== "android") { + this._currentEnvironment.profile = {}; + p.push(this._updateProfile()); + } let setup = () => { this._initTask = null; this._startWatchingPrefs(); this._addonBuilder.watchForChanges(); this._addObservers(); this._updateGraphicsFeatures(); return this.currentEnvironment; @@ -967,19 +969,19 @@ EnvironmentCache.prototype = { return buildData; }, /** * Determine if we're the default browser. * @returns null on error, true if we are the default browser, or false otherwise. */ _isDefaultBrowser: function () { -#ifdef MOZ_WIDGET_GONK - return true; -#else + if (AppConstants.platform === "gonk") { + return true; + } if (!("@mozilla.org/browser/shell-service;1" in Cc)) { this._log.info("_isDefaultBrowser - Could not obtain browser shell service"); return null; } let shellService; try { shellService = Cc["@mozilla.org/browser/shell-service;1"] @@ -993,48 +995,51 @@ EnvironmentCache.prototype = { // This uses the same set of flags used by the pref pane. return shellService.isDefaultBrowser(false, true) ? true : false; } catch (ex) { this._log.error("_isDefaultBrowser - Could not determine if default browser", ex); return null; } return null; -#endif }, /** * Update the cached settings data. */ _updateSettings: function () { let updateChannel = null; try { updateChannel = UpdateUtils.getUpdateChannel(false); } catch (e) {} this._currentEnvironment.settings = { -#ifndef MOZ_WIDGET_GONK - addonCompatibilityCheckEnabled: AddonManager.checkCompatibility, -#endif blocklistEnabled: Preferences.get(PREF_BLOCKLIST_ENABLED, true), -#ifndef MOZ_WIDGET_ANDROID - isDefaultBrowser: this._isDefaultBrowser(), -#endif e10sEnabled: Services.appinfo.browserTabsRemoteAutostart, telemetryEnabled: Preferences.get(PREF_TELEMETRY_ENABLED, false), isInOptoutSample: TelemetryController.isInOptoutSample, locale: getBrowserLocale(), update: { channel: updateChannel, enabled: Preferences.get(PREF_UPDATE_ENABLED, true), autoDownload: Preferences.get(PREF_UPDATE_AUTODOWNLOAD, true), }, userPrefs: this._getPrefData(), }; + if (AppConstants.platform !== "gonk") { + this._currentEnvironment.settings.addonCompatibilityCheckEnabled = + AddonManager.checkCompatibility; + } + + if (AppConstants.platform !== "android") { + this._currentEnvironment.settings.isDefaultBrowser = + this._isDefaultBrowser(); + } + this._updateSearchEngine(); }, /** * Update the cached profile data. * @returns Promise<> resolved when the I/O is complete. */ _updateProfile: Task.async(function* () { @@ -1101,53 +1106,55 @@ EnvironmentCache.prototype = { } } cpuData.extensions = availableExts; return cpuData; }, -#if defined(MOZ_WIDGET_GONK) || defined(MOZ_WIDGET_ANDROID) /** * Get the device information, if we are on a portable device. - * @return Object containing the device information data. + * @return Object containing the device information data, or null if + * not a portable device. */ _getDeviceData: function () { + if (["gonk", "android"].indexOf(AppConstants.platform) === -1) { + return null; + } + return { model: getSysinfoProperty("device", null), manufacturer: getSysinfoProperty("manufacturer", null), hardware: getSysinfoProperty("hardware", null), isTablet: getSysinfoProperty("tablet", null), }; }, -#endif /** * Get the OS information. * @return Object containing the OS data. */ _getOSData: function () { -#ifdef XP_WIN - // Try to get service pack information. - let servicePack = getServicePack(); -#endif - - return { + let data = { name: getSysinfoProperty("name", null), version: getSysinfoProperty("version", null), -#if defined(MOZ_WIDGET_GONK) || defined(MOZ_WIDGET_ANDROID) - kernelVersion: getSysinfoProperty("kernel_version", null), -#elif defined(XP_WIN) - servicePackMajor: servicePack.major, - servicePackMinor: servicePack.minor, - installYear: getSysinfoProperty("installYear", null), -#endif locale: getSystemLocale(), }; + + if (["gonk", "android"].indexOf(AppConstants.platform) !== -1) { + data.kernelVersion = getSysinfoProperty("kernel_version", null); + } else if (AppConstants.platform === "win") { + let servicePack = getServicePack(); + data.servicePackMajor = servicePack.major; + data.servicePackMinor = servicePack.minor; + data.installYear = getSysinfoProperty("installYear", null); + } + + return data; }, /** * Get the HDD information. * @return Object containing the HDD data. */ _getHDDData: function () { return { @@ -1177,24 +1184,24 @@ EnvironmentCache.prototype = { // The following line is disabled due to main thread jank and will be enabled // again as part of bug 1154500. //DWriteVersion: getGfxField("DWriteVersion", null), adapters: [], monitors: [], features: {}, }; -#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_GTK) - let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); - try { - gfxData.monitors = gfxInfo.getMonitors(); - } catch (e) { - this._log.error("nsIGfxInfo.getMonitors() caught error", e); + if (["gonk", "android", "linux"].indexOf(AppConstants.platform) === -1) { + let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); + try { + gfxData.monitors = gfxInfo.getMonitors(); + } catch (e) { + this._log.error("nsIGfxInfo.getMonitors() caught error", e); + } } -#endif try { let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); gfxData.features = gfxInfo.getFeatures(); } catch (e) { this._log.error("nsIGfxInfo.getFeatures() caught error", e); } @@ -1231,30 +1238,32 @@ EnvironmentCache.prototype = { let virtualMB = getSysinfoProperty("virtualmemsize", null); if (virtualMB) { // Send the total virtual memory size in megabytes. Rounding because // sysinfo doesn't always provide RAM in multiples of 1024. virtualMB = Math.round(virtualMB / 1024 / 1024); } - return { + let data = { memoryMB: memoryMB, virtualMaxMB: virtualMB, -#ifdef XP_WIN - isWow64: getSysinfoProperty("isWow64", null), -#endif cpu: this._getCpuData(), -#if defined(MOZ_WIDGET_GONK) || defined(MOZ_WIDGET_ANDROID) - device: this._getDeviceData(), -#endif os: this._getOSData(), hdd: this._getHDDData(), gfx: this._getGFXData(), }; + + if (AppConstants.platform === "win") { + data.isWow64 = getSysinfoProperty("isWow64", null); + } else if (["gonk", "android"].indexOf(AppConstants.platform) !== -1) { + data.device = this._getDeviceData(); + } + + return data; }, _onEnvironmentChange: function (what, oldEnvironment) { this._log.trace("_onEnvironmentChange for " + what); if (this._shutdown) { this._log.trace("_onEnvironmentChange - Already shut down."); return; }
--- a/toolkit/components/telemetry/TelemetrySession.jsm +++ b/toolkit/components/telemetry/TelemetrySession.jsm @@ -17,16 +17,17 @@ Cu.import("resource://gre/modules/Servic Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); Cu.import("resource://gre/modules/Promise.jsm", this); Cu.import("resource://gre/modules/DeferredTask.jsm", this); Cu.import("resource://gre/modules/Preferences.jsm"); Cu.import("resource://gre/modules/Task.jsm"); Cu.import("resource://gre/modules/Timer.jsm"); Cu.import("resource://gre/modules/TelemetrySend.jsm", this); Cu.import("resource://gre/modules/TelemetryUtils.jsm", this); +Cu.import("resource://gre/modules/AppConstants.jsm"); const Utils = TelemetryUtils; const myScope = this; // When modifying the payload in incompatible ways, please bump this version number const PAYLOAD_VERSION = 4; const PING_TYPE_MAIN = "main"; @@ -1300,22 +1301,23 @@ var Impl = { this._previousSubsessionId = this._subsessionId; this._subsessionId = Policy.generateSubsessionUUID(); this._subsessionCounter++; this._profileSubsessionCounter++; }, getSessionPayload: function getSessionPayload(reason, clearSubsession) { this._log.trace("getSessionPayload - reason: " + reason + ", clearSubsession: " + clearSubsession); -#if defined(MOZ_WIDGET_GONK) || defined(MOZ_WIDGET_ANDROID) - clearSubsession = false; - const isSubsession = false; -#else - const isSubsession = !this._isClassicReason(reason); -#endif + + const isMobile = ["gonk", "android"].indexOf(AppConstants.platform) !== -1; + const isSubsession = isMobile ? false : !this._isClassicReason(reason); + + if (isMobile) { + clearSubsession = false; + } let measurements = this.getSimpleMeasurements(reason == REASON_SAVED_SESSION, isSubsession, clearSubsession); let info = !Utils.isContentProcess ? this.getMetadata(reason) : null; let payload = this.assemblePayloadWithMeasurements(measurements, info, reason, clearSubsession); if (!Utils.isContentProcess && clearSubsession) { this.startNewSubsession(); @@ -1410,19 +1412,19 @@ var Impl = { Preferences.set(PREF_PREVIOUS_BUILDID, thisBuildID); } TelemetryController.shutdown.addBlocker("TelemetrySession: shutting down", () => this.shutdownChromeProcess(), () => this._getState()); Services.obs.addObserver(this, "sessionstore-windows-restored", false); -#ifdef MOZ_WIDGET_ANDROID - Services.obs.addObserver(this, "application-background", false); -#endif + if (AppConstants.platform === "android") { + Services.obs.addObserver(this, "application-background", false); + } Services.obs.addObserver(this, "xul-window-visible", false); this._hasWindowRestoredObserver = true; this._hasXulWindowVisibleObserver = true; ppml.addMessageListener(MESSAGE_TELEMETRY_PAYLOAD, this); // Delay full telemetry initialization to give the browser time to // run various late initializers. Otherwise our gathered memory @@ -1622,19 +1624,19 @@ var Impl = { if (this._hasWindowRestoredObserver) { Services.obs.removeObserver(this, "sessionstore-windows-restored"); this._hasWindowRestoredObserver = false; } if (this._hasXulWindowVisibleObserver) { Services.obs.removeObserver(this, "xul-window-visible"); this._hasXulWindowVisibleObserver = false; } -#ifdef MOZ_WIDGET_ANDROID - Services.obs.removeObserver(this, "application-background", false); -#endif + if (AppConstants.platform === "android") { + Services.obs.removeObserver(this, "application-background", false); + } }, getPayload: function getPayload(reason, clearSubsession) { this._log.trace("getPayload - clearSubsession: " + clearSubsession); reason = reason || REASON_GATHER_PAYLOAD; // This function returns the current Telemetry payload to the caller. // We only gather startup info once. if (Object.keys(this._slowSQLStartup).length == 0) { @@ -1725,44 +1727,45 @@ var Impl = { Services.tm.mainThread.dispatch((function() { // Notify that data should be gathered now. // TODO: We are keeping this behaviour for now but it will be removed as soon as // bug 1127907 lands. Services.obs.notifyObservers(null, "gather-telemetry", null); }).bind(this), Ci.nsIThread.DISPATCH_NORMAL); break; -#ifdef MOZ_WIDGET_ANDROID - // On Android, we can get killed without warning once we are in the background, - // but we may also submit data and/or come back into the foreground without getting - // killed. To deal with this, we save the current session data to file when we are - // put into the background. This handles the following post-backgrounding scenarios: - // 1) We are killed immediately. In this case the current session data (which we - // save to a file) will be loaded and submitted on a future run. - // 2) We submit the data while in the background, and then are killed. In this case - // the file that we saved will be deleted by the usual process in - // finishPingRequest after it is submitted. - // 3) We submit the data, and then come back into the foreground. Same as case (2). - // 4) We do not submit the data, but come back into the foreground. In this case - // we have the option of either deleting the file that we saved (since we will either - // send the live data while in the foreground, or create the file again on the next - // backgrounding), or not (in which case we will delete it on submit, or overwrite - // it on the next backgrounding). Not deleting it is faster, so that's what we do. case "application-background": + if (AppConstants.platform !== "android") { + break; + } + // On Android, we can get killed without warning once we are in the background, + // but we may also submit data and/or come back into the foreground without getting + // killed. To deal with this, we save the current session data to file when we are + // put into the background. This handles the following post-backgrounding scenarios: + // 1) We are killed immediately. In this case the current session data (which we + // save to a file) will be loaded and submitted on a future run. + // 2) We submit the data while in the background, and then are killed. In this case + // the file that we saved will be deleted by the usual process in + // finishPingRequest after it is submitted. + // 3) We submit the data, and then come back into the foreground. Same as case (2). + // 4) We do not submit the data, but come back into the foreground. In this case + // we have the option of either deleting the file that we saved (since we will either + // send the live data while in the foreground, or create the file again on the next + // backgrounding), or not (in which case we will delete it on submit, or overwrite + // it on the next backgrounding). Not deleting it is faster, so that's what we do. if (Telemetry.isOfficialTelemetry) { let payload = this.getSessionPayload(REASON_SAVED_SESSION, false); let options = { addClientId: true, addEnvironment: true, overwrite: true, }; TelemetryController.addPendingPing(getPingType(payload), payload, options); } break; -#endif } }, /** * This tells TelemetrySession to uninitialize and save any pending pings. * @param testing Optional. If true, always saves the ping whether Telemetry * can send pings or not, which is used for testing. */
new file mode 100644 --- /dev/null +++ b/toolkit/locales/en-US/chrome/alerts/alert.properties @@ -0,0 +1,11 @@ +# 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/. + +# LOCALIZATION NOTE(closeButton.title): Used as the close button text for web notifications on OS X. +# This should ideally match the string that OS X uses for the close button on alert-type +# notifications. OS X will truncate the value if it's too long. +closeButton.title = Close +# LOCALIZATION NOTE(actionButton.label): Used as the button label to provide more actions on OS X notifications. OS X will truncate this if it's too long. +actionButton.label = … +webActions.disable.label = Disable notifications from this site
deleted file mode 100644 --- a/toolkit/locales/en-US/chrome/alerts/notificationNames.properties +++ /dev/null @@ -1,5 +0,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/. - -general=General Notification
--- a/toolkit/locales/jar.mn +++ b/toolkit/locales/jar.mn @@ -118,17 +118,17 @@ locale/@AB_CD@/mozapps/update/updates.properties (%chrome/mozapps/update/updates.properties) locale/@AB_CD@/mozapps/update/history.dtd (%chrome/mozapps/update/history.dtd) locale/@AB_CD@/mozapps/xpinstall/xpinstallConfirm.dtd (%chrome/mozapps/extensions/xpinstallConfirm.dtd) locale/@AB_CD@/mozapps/xpinstall/xpinstallConfirm.properties (%chrome/mozapps/extensions/xpinstallConfirm.properties) % locale pluginproblem @AB_CD@ %locale/@AB_CD@/pluginproblem/ locale/@AB_CD@/pluginproblem/pluginproblem.dtd (%chrome/pluginproblem/pluginproblem.dtd) % locale alerts @AB_CD@ %locale/@AB_CD@/alerts/ locale/@AB_CD@/alerts/alert.dtd (%chrome/alerts/alert.dtd) - locale/@AB_CD@/alerts/notificationNames.properties (%chrome/alerts/notificationNames.properties) + locale/@AB_CD@/alerts/alert.properties (%chrome/alerts/alert.properties) % locale cookie @AB_CD@ %locale/@AB_CD@/cookie/ locale/@AB_CD@/cookie/cookieAcceptDialog.dtd (%chrome/cookie/cookieAcceptDialog.dtd) locale/@AB_CD@/cookie/cookieAcceptDialog.properties (%chrome/cookie/cookieAcceptDialog.properties) % locale formautofill @AB_CD@ %locale/@AB_CD@/formautofill/ locale/@AB_CD@/formautofill/requestAutocomplete.dtd (%chrome/formautofill/requestAutocomplete.dtd) % locale passwordmgr @AB_CD@ %locale/@AB_CD@/passwordmgr/ locale/@AB_CD@/passwordmgr/passwordmgr.properties (%chrome/passwordmgr/passwordmgr.properties) locale/@AB_CD@/passwordmgr/passwordManager.dtd (%chrome/passwordmgr/passwordManager.dtd)
--- a/toolkit/modules/NewTabUtils.jsm +++ b/toolkit/modules/NewTabUtils.jsm @@ -395,29 +395,27 @@ var PinnedLinks = { return this._links; }, /** * Pins a link at the given position. * @param aLink The link to pin. * @param aIndex The grid index to pin the cell at. + * @return true if link changes, false otherwise */ pin: function PinnedLinks_pin(aLink, aIndex) { // Clear the link's old position, if any. this.unpin(aLink); // change pinned link into a history link - // update all pages on link change - let updatePages = this._makeHistoryLink(aLink); + let changed = this._makeHistoryLink(aLink); this.links[aIndex] = aLink; this.save(); - if (updatePages) { - AllPages.update(); - } + return changed; }, /** * Unpins a given link. * @param aLink The link to unpin. */ unpin: function PinnedLinks_unpin(aLink) { let index = this._indexOfLink(aLink);
--- a/toolkit/mozapps/extensions/content/extensions.js +++ b/toolkit/mozapps/extensions/content/extensions.js @@ -1675,17 +1675,17 @@ function sortList(aList, aSortBy, aAscen aList.appendChild(element); } function getAddonsAndInstalls(aType, aCallback) { let addons = null, installs = null; let types = (aType != null) ? [aType] : null; AddonManager.getAddonsByTypes(types, function getAddonsAndInstalls_getAddonsByTypes(aAddonsList) { - addons = aAddonsList; + addons = aAddonsList.filter(a => !a.hidden); if (installs != null) aCallback(addons, installs); }); AddonManager.getInstallsByTypes(types, function getAddonsAndInstalls_getInstallsByTypes(aInstallsList) { // skip over upgrade installs and non-active installs installs = aInstallsList.filter(function installsFilter(aInstall) { return !(aInstall.existingAddon || @@ -2737,16 +2737,19 @@ var gListView = { sortList(this._listBox, aSortBy, aAscending); }, onExternalInstall: function gListView_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) { // The existing list item will take care of upgrade installs if (aExistingAddon) return; + if (aAddon.hidden) + return; + this.addItem(aAddon); }, onDownloadStarted: function gListView_onDownloadStarted(aInstall) { this.addItem(aInstall, true); }, onInstallStarted: function gListView_onInstallStarted(aInstall) { @@ -3529,17 +3532,17 @@ var gUpdatesView = { var self = this; AddonManager.getAllAddons(function showRecentUpdates_getAllAddons(aAddonsList) { if (gViewController && aRequest != gViewController.currentViewRequest) return; var elements = []; let threshold = Date.now() - UPDATES_RECENT_TIMESPAN; for (let addon of aAddonsList) { - if (!addon.updateDate || addon.updateDate.getTime() < threshold) + if (addon.hidden || !addon.updateDate || addon.updateDate.getTime() < threshold) continue; elements.push(createItem(addon)); } self.showEmptyNotice(elements.length == 0); if (elements.length > 0) { sortElements(elements, [self._sorters.sortBy], self._sorters.ascending);
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -130,16 +130,17 @@ const FILE_OLD_CACHE = const FILE_RDF_MANIFEST = "install.rdf"; const FILE_WEB_MANIFEST = "manifest.json"; const FILE_XPI_ADDONS_LIST = "extensions.ini"; const KEY_PROFILEDIR = "ProfD"; const KEY_APPDIR = "XCurProcD"; const KEY_TEMPDIR = "TmpD"; const KEY_APP_DISTRIBUTION = "XREAppDist"; +const KEY_APP_FEATURES = "XREAppFeat"; const KEY_APP_PROFILE = "app-profile"; const KEY_APP_SYSTEM_ADDONS = "app-system-addons"; const KEY_APP_SYSTEM_DEFAULTS = "app-system-defaults"; const KEY_APP_GLOBAL = "app-global"; const KEY_APP_SYSTEM_LOCAL = "app-system-local"; const KEY_APP_SYSTEM_SHARE = "app-system-share"; const KEY_APP_SYSTEM_USER = "app-system-user"; @@ -2440,19 +2441,18 @@ this.XPIProvider = { addDirectoryInstallLocation(KEY_APP_PROFILE, KEY_PROFILEDIR, [DIR_EXTENSIONS], AddonManager.SCOPE_PROFILE, false); addSystemAddonInstallLocation(KEY_APP_SYSTEM_ADDONS, KEY_PROFILEDIR, [DIR_SYSTEM_ADDONS], AddonManager.SCOPE_PROFILE); - addDirectoryInstallLocation(KEY_APP_SYSTEM_DEFAULTS, KEY_APP_DISTRIBUTION, - [DIR_SYSTEM_ADDONS], - AddonManager.SCOPE_PROFILE, true); + addDirectoryInstallLocation(KEY_APP_SYSTEM_DEFAULTS, KEY_APP_FEATURES, + [], AddonManager.SCOPE_PROFILE, true); if (enabledScopes & AddonManager.SCOPE_USER) { addDirectoryInstallLocation(KEY_APP_SYSTEM_USER, "XREUSysExt", [Services.appinfo.ID], AddonManager.SCOPE_USER, true); if (hasRegistry) { addRegistryInstallLocation("winreg-app-user", Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, @@ -6802,16 +6802,21 @@ function AddonWrapper(aAddon) { // Only set softDisabled if not already disabled if (!aAddon.userDisabled) aAddon.softDisabled = val; } return val; }); + this.__defineGetter__("hidden", function AddonWrapper_hidden() { + return (aAddon._installLocation.name == KEY_APP_SYSTEM_DEFAULTS || + aAddon._installLocation.name == KEY_APP_SYSTEM_ADDONS); + }); + this.isCompatibleWith = function AddonWrapper_isCompatiblewith(aAppVersion, aPlatformVersion) { return aAddon.isCompatibleWith(aAppVersion, aPlatformVersion); }; this.uninstall = function AddonWrapper_uninstall() { if (!(aAddon.inDatabase)) throw new Error("Cannot uninstall an add-on that isn't installed"); if (aAddon.pendingUninstall)
--- a/toolkit/mozapps/extensions/test/browser/browser_list.js +++ b/toolkit/mozapps/extensions/test/browser/browser_list.js @@ -113,16 +113,20 @@ add_task(function*() { isActive: false, appDisabled: true, foreignInstall: true, }, { id: "addon13@tests.mozilla.org", name: "Test add-on 13", signedState: AddonManager.SIGNEDSTATE_SIGNED, foreignInstall: true, + }, { + id: "addon15@tests.mozilla.org", + name: "Test add-on 15", + hidden: true, }]); gManagerWindow = yield open_manager(null); gCategoryUtilities = new CategoryUtilities(gManagerWindow); }); function get_test_items() { var tests = "@tests.mozilla.org"; @@ -681,16 +685,20 @@ add_task(function*() { add_task(function*() { gProvider.createAddons([{ id: "addon1@tests.mozilla.org", name: "Test add-on replacement", version: "2.0", description: "A test add-on with a new description", updateDate: gDate, operationsRequiringRestart: AddonManager.OP_NEEDS_RESTART_NONE + }, { + id: "addon14@tests.mozilla.org", + name: "Test add-on 14", + hidden: true, }]); let items = get_test_items(); is(Object.keys(items).length, EXPECTED_ADDONS, "Should be the right number of add-ons installed"); let addon = items["Test add-on replacement"]; addon.parentNode.ensureElementIsVisible(addon); let { name, version } = yield get_tooltip_info(addon);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_reset.js @@ -3,61 +3,55 @@ const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet"; // Enable signature checks for these tests Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true); const featureDir = FileUtils.getDir("ProfD", ["features"]); // Build the test sets -let dir = FileUtils.getDir("ProfD", ["sysfeatures", "app1", "features"], true); +let dir = FileUtils.getDir("ProfD", ["sysfeatures", "app1"], true); do_get_file("data/system_addons/system1_1.xpi").copyTo(dir, "system1@tests.mozilla.org.xpi"); do_get_file("data/system_addons/system2_1.xpi").copyTo(dir, "system2@tests.mozilla.org.xpi"); -dir = FileUtils.getDir("ProfD", ["sysfeatures", "app2", "features"], true); +dir = FileUtils.getDir("ProfD", ["sysfeatures", "app2"], true); do_get_file("data/system_addons/system1_2.xpi").copyTo(dir, "system1@tests.mozilla.org.xpi"); do_get_file("data/system_addons/system3_1.xpi").copyTo(dir, "system3@tests.mozilla.org.xpi"); -dir = FileUtils.getDir("ProfD", ["sysfeatures", "app3", "features"], true); +dir = FileUtils.getDir("ProfD", ["sysfeatures", "app3"], true); do_get_file("data/system_addons/system1_1_badcert.xpi").copyTo(dir, "system1@tests.mozilla.org.xpi"); do_get_file("data/system_addons/system3_1.xpi").copyTo(dir, "system3@tests.mozilla.org.xpi"); const distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "app0"], true); -registerDirectory("XREAppDist", distroDir); +registerDirectory("XREAppFeat", distroDir); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "0"); function makeUUID() { let uuidGen = AM_Cc["@mozilla.org/uuid-generator;1"]. getService(AM_Ci.nsIUUIDGenerator); return uuidGen.generateUUID().toString(); } function* check_installed(inProfile, ...versions) { - let expectedDir; - if (inProfile) { - expectedDir = featureDir; - } - else { - expectedDir = distroDir.clone(); - expectedDir.append("features"); - } + let expectedDir = inProfile ? featureDir : distroDir; for (let i = 0; i < versions.length; i++) { let id = "system" + (i + 1) + "@tests.mozilla.org"; let addon = yield promiseAddonByID(id); if (versions[i]) { // Add-on should be installed do_check_neq(addon, null); do_check_eq(addon.version, versions[i]); do_check_true(addon.isActive); do_check_false(addon.foreignInstall); do_check_false(hasFlag(addon.permissions, AddonManager.PERM_CAN_UPGRADE)); do_check_false(hasFlag(addon.permissions, AddonManager.PERM_CAN_UNINSTALL)); + do_check_true(addon.hidden); // Verify the add-ons file is in the right place let file = expectedDir.clone(); file.append(id + ".xpi"); do_check_true(file.exists()); do_check_true(file.isFile()); let uri = addon.getResourceURI(null);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_update.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update.js @@ -8,16 +8,23 @@ const PREF_APP_UPDATE_ENABLED = Components.utils.import("resource://testing-common/httpd.js"); const { computeHash } = Components.utils.import("resource://gre/modules/addons/ProductAddonChecker.jsm"); // Enable signature checks for these tests //Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true); const featureDir = FileUtils.getDir("ProfD", ["features"]); +function getCurrentFeatureDir() { + let dir = featureDir.clone(); + let set = JSON.parse(Services.prefs.getCharPref(PREF_SYSTEM_ADDON_SET)); + dir.append(set.directory); + return dir; +} + // Build the test sets let dir = FileUtils.getDir("ProfD", ["features", "prefilled"], true); do_get_file("data/system_addons/system2_2.xpi").copyTo(dir, "system2@tests.mozilla.org.xpi"); do_get_file("data/system_addons/system3_2.xpi").copyTo(dir, "system3@tests.mozilla.org.xpi"); // Mark these in the past so the startup file scan notices when files have changed properly FileUtils.getFile("ProfD", ["features", "prefilled", "system2@tests.mozilla.org.xpi"]).lastModifiedTime -= 10000; FileUtils.getFile("ProfD", ["features", "prefilled", "system3@tests.mozilla.org.xpi"]).lastModifiedTime -= 10000; @@ -30,26 +37,26 @@ const prefilledSet = { version: "2.0" }, "system3@tests.mozilla.org": { version: "2.0" }, } }; -dir = FileUtils.getDir("ProfD", ["sysfeatures", "hidden", "features"], true); +dir = FileUtils.getDir("ProfD", ["sysfeatures", "hidden"], true); do_get_file("data/system_addons/system1_1.xpi").copyTo(dir, "system1@tests.mozilla.org.xpi"); do_get_file("data/system_addons/system2_1.xpi").copyTo(dir, "system2@tests.mozilla.org.xpi"); -dir = FileUtils.getDir("ProfD", ["sysfeatures", "prefilled", "features"], true); +dir = FileUtils.getDir("ProfD", ["sysfeatures", "prefilled"], true); do_get_file("data/system_addons/system2_2.xpi").copyTo(dir, "system2@tests.mozilla.org.xpi"); do_get_file("data/system_addons/system3_2.xpi").copyTo(dir, "system3@tests.mozilla.org.xpi"); const distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "empty"], true); -registerDirectory("XREAppDist", distroDir); +registerDirectory("XREAppFeat", distroDir); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2"); let testserver = new HttpServer(); testserver.registerDirectory("/data/", do_get_file("data/system_addons")); testserver.start(); let root = testserver.identity.primaryScheme + "://" + testserver.identity.primaryHost + ":" + @@ -123,43 +130,39 @@ function* build_xml(addons) { xml += ` </addons>\n`; } xml += `</updates>\n`; return xml; } function* check_installed(inProfile, ...versions) { + let expectedDir = inProfile ? getCurrentFeatureDir() : distroDir; + for (let i = 0; i < versions.length; i++) { let id = "system" + (i + 1) + "@tests.mozilla.org"; let addon = yield promiseAddonByID(id); if (versions[i]) { // Add-on should be installed do_check_neq(addon, null); do_check_eq(addon.version, versions[i]); do_check_true(addon.isActive); do_check_false(addon.foreignInstall); + do_check_true(addon.hidden); // Verify the add-ons file is in the right place + let file = expectedDir.clone(); + file.append(id + ".xpi"); + do_check_true(file.exists()); + do_check_true(file.isFile()); + let uri = addon.getResourceURI(null); do_check_true(uri instanceof AM_Ci.nsIFileURL); - - let file = uri.file.parent; - if (inProfile) { - file = file.parent; - do_check_eq(file.leafName, "features"); - file = file.parent; - do_check_eq(file.path, gProfD.path); - } - else { - do_check_eq(file.leafName, "features"); - file = file.parent; - do_check_eq(file.path, distroDir.path); - } + do_check_eq(uri.file.path, file.path); //do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SYSTEM); // Verify the add-on actually started let installed = Services.prefs.getCharPref("bootstraptest." + id + ".active_version"); do_check_eq(installed, versions[i]); } else {
--- a/toolkit/themes/linux/global/alerts/alert.css +++ b/toolkit/themes/linux/global/alerts/alert.css @@ -1,65 +1,23 @@ /* 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/. */ /* ===== alert.css ===================================================== == Styles specific to the alerts dialog. ======================================================================= */ -@import url("chrome://global/skin/"); +@import url("chrome://global/skin/alerts/alert-common.css"); @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); .alertBox { border: 1px solid threedshadow; background-color: -moz-Dialog; } -.alertImageBox { - padding: 8px 0; - width: 64px; - background-image: linear-gradient(rgba(255,255,255,0.7), rgba(255,255,255,0.6)); - -moz-border-end: 1px solid rgba(0,0,0,.1); -} - -.alertTextBox { - padding: 8px; - -moz-padding-start: 16px; - width: 255px; -} - -.alertTextBox, -.alertCloseBox { - background-image: linear-gradient(rgba(255,255,255,0.2), rgba(255,255,255,0.1)); -} - -#alertNotification[clickable="true"]:hover .alertTextBox, -#alertNotification[clickable="true"]:hover .alertCloseBox { - background-image: linear-gradient(rgba(255,255,255,0.4), rgba(255,255,255,0.3)); -} - -.alertTitle { - font-weight: bold; - font-size: 110%; -} - -#alertImage { - max-width: 48px; - max-height: 48px; - list-style-image: url(chrome://global/skin/alerts/notification-48.png); -} - -#alertNotification[clickable="true"] { - cursor: pointer; -} - -label { - cursor: inherit; -} - .alertCloseButton { -moz-appearance: none; height: 16px; padding: 4px 2px; width: 16px; }
--- a/toolkit/themes/osx/global/alerts/alert.css +++ b/toolkit/themes/osx/global/alerts/alert.css @@ -1,79 +1,37 @@ /* 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/. */ /* ===== alert.css ===================================================== == Styles specific to the alerts dialog. ======================================================================= */ -@import url("chrome://global/skin/"); +@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-radius: 5px; overflow: hidden; background-color: rgba(240,240,240,0.93); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.3); } -.alertImageBox { - padding: 8px 0; - width: 64px; - background-image: linear-gradient(rgba(255,255,255,0.7), rgba(255,255,255,0.6)); - -moz-border-end: 1px solid rgba(0,0,0,.08); -} - .alertTitle, .alertTextBox { text-shadow: 0 1px white; } -.alertTextBox { - padding: 8px; - -moz-padding-start: 16px; - width: 255px; -} - -.alertTextBox, -.alertCloseBox { - background-image: linear-gradient(rgba(255,255,255,0.2), rgba(255,255,255,0.1)); -} - -#alertNotification[clickable="true"]:hover .alertTextBox, -#alertNotification[clickable="true"]:hover .alertCloseBox { - background-image: linear-gradient(rgba(255,255,255,0.4), rgba(255,255,255,0.3)); -} - -.alertTitle { - font-weight: bold; - font-size: 110%; -} - -#alertImage { - max-width: 48px; - max-height: 48px; - list-style-image: url(chrome://global/skin/alerts/notification-48.png); -} - -#alertNotification[clickable="true"] { - cursor: pointer; -} - -label { - cursor: inherit; -} - @keyframes alert-animation { from { opacity: 0; } 6.25% { opacity: 1; } 93.75% {
copy from toolkit/themes/linux/global/alerts/alert.css copy to toolkit/themes/shared/alert-common.css --- a/toolkit/themes/linux/global/alerts/alert.css +++ b/toolkit/themes/shared/alert-common.css @@ -1,25 +1,20 @@ /* 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/. */ /* ===== alert.css ===================================================== - == Styles specific to the alerts dialog. + == Shared styles specific to the alerts dialog. ======================================================================= */ @import url("chrome://global/skin/"); @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); -.alertBox { - border: 1px solid threedshadow; - background-color: -moz-Dialog; -} - .alertImageBox { padding: 8px 0; width: 64px; background-image: linear-gradient(rgba(255,255,255,0.7), rgba(255,255,255,0.6)); -moz-border-end: 1px solid rgba(0,0,0,.1); } .alertTextBox { @@ -51,15 +46,8 @@ #alertNotification[clickable="true"] { cursor: pointer; } label { cursor: inherit; } - -.alertCloseButton { - -moz-appearance: none; - height: 16px; - padding: 4px 2px; - width: 16px; -}
--- a/toolkit/themes/shared/jar.inc.mn +++ b/toolkit/themes/shared/jar.inc.mn @@ -13,16 +13,17 @@ skin/classic/global/aboutMemory.css (../../shared/aboutMemory.css) skin/classic/global/aboutReader.css (../../shared/aboutReader.css) skin/classic/global/aboutReaderContent.css (../../shared/aboutReaderContent.css) * skin/classic/global/aboutReaderControls.css (../../shared/aboutReaderControls.css) skin/classic/global/aboutSupport.css (../../shared/aboutSupport.css) skin/classic/global/appPicker.css (../../shared/appPicker.css) skin/classic/global/config.css (../../shared/config.css) skin/classic/global/icons/warning.svg (../../shared/incontent-icons/warning.svg) + skin/classic/global/alerts/alert-common.css (../../shared/alert-common.css) skin/classic/global/menu/shared-menu-check@2x.png (../../shared/menu-check@2x.png) skin/classic/global/menu/shared-menu-check.png (../../shared/menu-check.png) skin/classic/global/menu/shared-menu-check-active.svg (../../shared/menu-check-active.svg) skin/classic/global/menu/shared-menu-check-black.svg (../../shared/menu-check-black.svg) skin/classic/global/menu/shared-menu-check-hover.svg (../../shared/menu-check-hover.svg) skin/classic/global/in-content/check.svg (../../shared/in-content/check.svg) skin/classic/global/in-content/check-partial.svg (../../shared/in-content/check-partial.svg) skin/classic/global/in-content/dropdown.svg (../../shared/in-content/dropdown.svg)
--- a/toolkit/themes/windows/global/alerts/alert.css +++ b/toolkit/themes/windows/global/alerts/alert.css @@ -1,68 +1,26 @@ /* 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/. */ /* ===== alert.css ===================================================== == Styles specific to the alerts dialog. ======================================================================= */ -@import url("chrome://global/skin/"); +@import url("chrome://global/skin/alerts/alert-common.css"); @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); .alertBox { border: 1px solid threedshadow; border-radius: 3px; background-color: -moz-Dialog; } -.alertImageBox { - padding: 8px 0; - width: 64px; - background-image: linear-gradient(rgba(255,255,255,0.7), rgba(255,255,255,0.6)); - -moz-border-end: 1px solid rgba(0,0,0,.1); -} - -.alertTextBox { - padding: 8px; - -moz-padding-start: 16px; - width: 255px; -} - -.alertTextBox, -.alertCloseBox { - background-image: linear-gradient(rgba(255,255,255,0.2), rgba(255,255,255,0.1)); -} - -#alertNotification[clickable="true"]:hover .alertTextBox, -#alertNotification[clickable="true"]:hover .alertCloseBox { - background-image: linear-gradient(rgba(255,255,255,0.4), rgba(255,255,255,0.3)); -} - -.alertTitle { - font-weight: bold; - font-size: 110%; -} - -#alertImage { - max-width: 48px; - max-height: 48px; - list-style-image: url(chrome://global/skin/alerts/notification-48.png); -} - -#alertNotification[clickable="true"] { - cursor: pointer; -} - -label { - cursor: inherit; -} - @keyframes alert-animation { from { opacity: 0; } 6.25% { opacity: 1; } 93.75% {
--- a/toolkit/xre/nsXREDirProvider.cpp +++ b/toolkit/xre/nsXREDirProvider.cpp @@ -384,16 +384,21 @@ nsXREDirProvider::GetFile(const char* aP #endif } else if (!strcmp(aProperty, XRE_APP_DISTRIBUTION_DIR)) { bool persistent = false; rv = GetFile(NS_GRE_DIR, &persistent, getter_AddRefs(file)); if (NS_SUCCEEDED(rv)) rv = file->AppendNative(NS_LITERAL_CSTRING("distribution")); } + else if (!strcmp(aProperty, XRE_APP_FEATURES_DIR)) { + rv = GetAppDir()->Clone(getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) + rv = file->AppendNative(NS_LITERAL_CSTRING("features")); + } else if (NS_SUCCEEDED(GetProfileStartupDir(getter_AddRefs(file)))) { // We need to allow component, xpt, and chrome registration to // occur prior to the profile-after-change notification. if (!strcmp(aProperty, NS_APP_USER_CHROME_DIR)) { rv = file->AppendNative(NS_LITERAL_CSTRING("chrome")); } }
--- a/tools/profiler/gecko/nsIProfiler.idl +++ b/tools/profiler/gecko/nsIProfiler.idl @@ -1,25 +1,26 @@ /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "nsISupports.idl" %{C++ template<class T> class nsTArray; class nsCString; %} [ref] native StringArrayRef(const nsTArray<nsCString>); -[scriptable, uuid(921e1223-b1ea-4906-bb26-a846e6b6835b)] +[scriptable, uuid(ff398a14-df1c-4966-9ab2-772ea6a6da6c)] interface nsIProfiler : nsISupports { + boolean CanProfile(); void StartProfiler(in uint32_t aEntries, in double aInterval, [array, size_is(aFeatureCount)] in string aFeatures, in uint32_t aFeatureCount, [array, size_is(aFilterCount), optional] in string aThreadNameFilters, [optional] in uint32_t aFilterCount); void StopProfiler(); boolean IsPaused(); void PauseSampling();
--- a/tools/profiler/gecko/nsProfiler.cpp +++ b/tools/profiler/gecko/nsProfiler.cpp @@ -66,16 +66,23 @@ nsProfiler::Observe(nsISupports *aSubjec } else if (strcmp(aTopic, "last-pb-context-exited") == 0) { mLockedForPrivateBrowsing = false; profiler_unlock(); } return NS_OK; } NS_IMETHODIMP +nsProfiler::CanProfile(bool *aCanProfile) +{ + *aCanProfile = !mLockedForPrivateBrowsing; + return NS_OK; +} + +NS_IMETHODIMP nsProfiler::StartProfiler(uint32_t aEntries, double aInterval, const char** aFeatures, uint32_t aFeatureCount, const char** aThreadNameFilters, uint32_t aFilterCount) { if (mLockedForPrivateBrowsing) { return NS_ERROR_NOT_AVAILABLE; }
--- a/widget/cocoa/OSXNotificationCenter.h +++ b/widget/cocoa/OSXNotificationCenter.h @@ -1,25 +1,29 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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/. */ #ifndef OSXNotificationCenter_h #define OSXNotificationCenter_h #import <Foundation/Foundation.h> #include "nsIAlertsService.h" #include "imgINotificationObserver.h" #include "nsITimer.h" #include "nsTArray.h" #include "mozilla/RefPtr.h" @class mozNotificationCenterDelegate; +#if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8) +typedef NSInteger NSUserNotificationActivationType; +#endif + namespace mozilla { class OSXNotificationInfo; class OSXNotificationCenter : public nsIAlertsService, public imgINotificationObserver, public nsITimerCallback { @@ -28,17 +32,18 @@ public: NS_DECL_NSIALERTSSERVICE NS_DECL_IMGINOTIFICATIONOBSERVER NS_DECL_NSITIMERCALLBACK OSXNotificationCenter(); nsresult Init(); void CloseAlertCocoaString(NSString *aAlertName); - void OnClick(NSString *aAlertName); + void OnActivate(NSString *aAlertName, NSUserNotificationActivationType aActivationType, + unsigned long long aAdditionalActionIndex); void ShowPendingNotification(OSXNotificationInfo *osxni); protected: virtual ~OSXNotificationCenter(); private: mozNotificationCenterDelegate *mDelegate; nsTArray<nsRefPtr<OSXNotificationInfo> > mActiveAlerts;
--- a/widget/cocoa/OSXNotificationCenter.mm +++ b/widget/cocoa/OSXNotificationCenter.mm @@ -1,39 +1,52 @@ -/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "OSXNotificationCenter.h" #import <AppKit/AppKit.h> #include "imgIRequest.h" #include "imgIContainer.h" +#include "nsIStringBundle.h" #include "nsNetUtil.h" #include "imgLoader.h" #import "nsCocoaUtils.h" +#include "nsContentUtils.h" #include "nsObjCExceptions.h" #include "nsString.h" #include "nsCOMPtr.h" #include "nsIObserver.h" #include "nsIContentPolicy.h" #include "imgRequestProxy.h" using namespace mozilla; #if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8) @protocol NSUserNotificationCenterDelegate @end static NSString * const NSUserNotificationDefaultSoundName = @"DefaultSoundName"; enum { NSUserNotificationActivationTypeNone = 0, NSUserNotificationActivationTypeContentsClicked = 1, - NSUserNotificationActivationTypeActionButtonClicked = 2 + NSUserNotificationActivationTypeActionButtonClicked = 2, }; -typedef NSInteger NSUserNotificationActivationType; +#endif + +#if !defined(MAC_OS_X_VERSION_10_9) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9) +enum { + NSUserNotificationActivationTypeReplied = 3, +}; +#endif + +#if !defined(MAC_OS_X_VERSION_10_10) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10) +enum { + NSUserNotificationActivationTypeAdditionalActionClicked = 4 +}; #endif @protocol FakeNSUserNotification <NSObject> @property (copy) NSString* title; @property (copy) NSString* subtitle; @property (copy) NSString* informativeText; @property (copy) NSString* actionButtonTitle; @property (copy) NSDictionary* userInfo; @@ -85,17 +98,20 @@ typedef NSInteger NSUserNotificationActi didDeliverNotification:(id<FakeNSUserNotification>)notification { } - (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center didActivateNotification:(id<FakeNSUserNotification>)notification { - mOSXNC->OnClick([[notification userInfo] valueForKey:@"name"]); + NSNumber *alternateActionIndex = [(NSObject*)notification valueForKey:@"_alternateActionIndex"]; + mOSXNC->OnActivate([[notification userInfo] valueForKey:@"name"], + notification.activationType, + [alternateActionIndex unsignedLongLongValue]); } - (BOOL)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center shouldPresentNotification:(id<FakeNSUserNotification>)notification { return YES; } @@ -105,20 +121,32 @@ typedef NSInteger NSUserNotificationActi didRemoveDeliveredNotifications:(NSArray *)notifications { for (id<FakeNSUserNotification> notification in notifications) { NSString *name = [[notification userInfo] valueForKey:@"name"]; mOSXNC->CloseAlertCocoaString(name); } } +// This is an undocumented method that we need to be notified if a user clicks the close button. +- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center + didDismissAlert:(id<FakeNSUserNotification>)notification +{ + NSString *name = [[notification userInfo] valueForKey:@"name"]; + mOSXNC->CloseAlertCocoaString(name); +} + @end namespace mozilla { +enum { + OSXNotificationActionDisable = 0 +}; + class OSXNotificationInfo { private: ~OSXNotificationInfo(); public: NS_INLINE_DECL_REFCOUNTING(OSXNotificationInfo) OSXNotificationInfo(NSString *name, nsIObserver *observer, const nsAString & alertCookie); @@ -212,16 +240,43 @@ OSXNotificationCenter::ShowAlertNotifica Class unClass = NSClassFromString(@"NSUserNotification"); id<FakeNSUserNotification> notification = [[unClass alloc] init]; notification.title = [NSString stringWithCharacters:(const unichar *)aAlertTitle.BeginReading() length:aAlertTitle.Length()]; notification.informativeText = [NSString stringWithCharacters:(const unichar *)aAlertText.BeginReading() length:aAlertText.Length()]; notification.soundName = NSUserNotificationDefaultSoundName; notification.hasActionButton = NO; + + // If this is not an application/extension alert, show additional actions dealing with permissions. + if (aPrincipal && !nsContentUtils::IsSystemOrExpandedPrincipal(aPrincipal) + && !aPrincipal->GetIsNullPrincipal()) { + nsCOMPtr<nsIStringBundleService> sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID); + nsCOMPtr<nsIStringBundle> bundle; + nsresult rv = sbs->CreateBundle("chrome://alerts/locale/alert.properties", getter_AddRefs(bundle)); + if (NS_SUCCEEDED(rv)) { + nsXPIDLString closeButtonTitle, actionButtonTitle, disableButtonTitle; + bundle->GetStringFromName(NS_LITERAL_STRING("closeButton.title").get(), + getter_Copies(closeButtonTitle)); + bundle->GetStringFromName(NS_LITERAL_STRING("actionButton.label").get(), + getter_Copies(actionButtonTitle)); + bundle->GetStringFromName(NS_LITERAL_STRING("webActions.disable.label").get(), + getter_Copies(disableButtonTitle)); + + notification.hasActionButton = YES; + notification.otherButtonTitle = nsCocoaUtils::ToNSString(closeButtonTitle); + notification.actionButtonTitle = nsCocoaUtils::ToNSString(actionButtonTitle); + [(NSObject*)notification setValue:@(YES) forKey:@"_showsButtons"]; + [(NSObject*)notification setValue:@(YES) forKey:@"_alwaysShowAlternateActionMenu"]; + [(NSObject*)notification setValue:@[ + nsCocoaUtils::ToNSString(disableButtonTitle) + ] + forKey:@"_alternateActionButtonTitles"]; + } + } NSString *alertName = [NSString stringWithCharacters:(const unichar *)aAlertName.BeginReading() length:aAlertName.Length()]; if (!alertName) { return NS_ERROR_FAILURE; } notification.userInfo = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:alertName, nil] forKeys:[NSArray arrayWithObjects:@"name", nil]]; OSXNotificationInfo *osxni = new OSXNotificationInfo(alertName, aAlertListener, aAlertCookie); @@ -313,29 +368,46 @@ OSXNotificationCenter::CloseAlertCocoaSt break; } } NS_OBJC_END_TRY_ABORT_BLOCK; } void -OSXNotificationCenter::OnClick(NSString *aAlertName) +OSXNotificationCenter::OnActivate(NSString *aAlertName, + NSUserNotificationActivationType aActivationType, + unsigned long long aAdditionalActionIndex) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; if (!aAlertName) { return; // Can't do anything without a name } for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) { OSXNotificationInfo *osxni = mActiveAlerts[i]; if ([aAlertName isEqualToString:osxni->mName]) { if (osxni->mObserver) { - osxni->mObserver->Observe(nullptr, "alertclickcallback", osxni->mCookie.get()); + switch (aActivationType) { + case NSUserNotificationActivationTypeAdditionalActionClicked: + case NSUserNotificationActivationTypeActionButtonClicked: + switch (aAdditionalActionIndex) { + case OSXNotificationActionDisable: + osxni->mObserver->Observe(nullptr, "alertdisablecallback", osxni->mCookie.get()); + break; + default: + NS_WARNING("Unknown NSUserNotification additional action clicked"); + break; + } + break; + default: + osxni->mObserver->Observe(nullptr, "alertclickcallback", osxni->mCookie.get()); + break; + } } return; } } NS_OBJC_END_TRY_ABORT_BLOCK; }
--- a/xpcom/build/nsXULAppAPI.h +++ b/xpcom/build/nsXULAppAPI.h @@ -114,16 +114,21 @@ /** * A directory service key which specifies the distribution specific files for * the application. */ #define XRE_APP_DISTRIBUTION_DIR "XREAppDist" /** + * A directory service key which specifies the location for system add-ons. + */ +#define XRE_APP_FEATURES_DIR "XREAppFeat" + +/** * A directory service key which provides the update directory. * At present this is supported only on Windows. * Windows: Documents and Settings\<User>\Local Settings\Application Data\ * <Vendor>\<Application>\<relative path to app dir from Program Files> * If appDir is not under the Program Files, directory service will fail. * Callers should fallback to appDir. */ #define XRE_UPDATE_ROOT_DIR "UpdRootD"