Merge inbound to mozilla-central. a=merge

This commit is contained in:
Oana Pop Rus 2019-03-28 12:11:35 +02:00
Родитель ddd1679c05 ad469ee09b
Коммит 47e94b5d6e
109 изменённых файлов: 1169 добавлений и 4863 удалений

Просмотреть файл

@ -1,51 +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/. */
var EXPORTED_SYMBOLS = ["LightWeightThemeInstallChild"];
const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
class LightWeightThemeInstallChild extends ActorChild {
handleEvent(event) {
let {mm} = this;
switch (event.type) {
case "InstallBrowserTheme": {
mm.sendAsyncMessage("LightWeightThemeWebInstaller:Install", {
baseURI: event.target.baseURI,
principal: event.target.nodePrincipal,
themeData: event.target.getAttribute("data-browsertheme"),
});
break;
}
case "PreviewBrowserTheme": {
mm.sendAsyncMessage("LightWeightThemeWebInstaller:Preview", {
baseURI: event.target.baseURI,
principal: event.target.nodePrincipal,
themeData: event.target.getAttribute("data-browsertheme"),
});
this._previewWindow = event.target.ownerGlobal;
this._previewWindow.addEventListener("pagehide", this, true);
break;
}
case "pagehide": {
mm.sendAsyncMessage("LightWeightThemeWebInstaller:ResetPreview");
this._resetPreviewWindow();
break;
}
case "ResetBrowserThemePreview": {
if (this._previewWindow) {
mm.sendAsyncMessage("LightWeightThemeWebInstaller:ResetPreview",
{principal: event.target.nodePrincipal});
this._resetPreviewWindow();
}
break;
}
}
}
_resetPreviewWindow() {
this._previewWindow.removeEventListener("pagehide", this, true);
this._previewWindow = null;
}
}

Просмотреть файл

@ -10,9 +10,6 @@ with Files("**"):
with Files("LightweightThemeChild.jsm"): with Files("LightweightThemeChild.jsm"):
BUG_COMPONENT = ("WebExtensions", "Themes") BUG_COMPONENT = ("WebExtensions", "Themes")
with Files("LightWeightThemeInstallChild.jsm"):
BUG_COMPONENT = ("Firefox", "Theme")
with Files("PageInfoChild.jsm"): with Files("PageInfoChild.jsm"):
BUG_COMPONENT = ("Firefox", "Page Info Window") BUG_COMPONENT = ("Firefox", "Page Info Window")
@ -35,7 +32,6 @@ FINAL_TARGET_FILES.actors += [
'DOMFullscreenChild.jsm', 'DOMFullscreenChild.jsm',
'FormValidationChild.jsm', 'FormValidationChild.jsm',
'LightweightThemeChild.jsm', 'LightweightThemeChild.jsm',
'LightWeightThemeInstallChild.jsm',
'LinkHandlerChild.jsm', 'LinkHandlerChild.jsm',
'NetErrorChild.jsm', 'NetErrorChild.jsm',
'OfflineAppsChild.jsm', 'OfflineAppsChild.jsm',

Просмотреть файл

@ -184,7 +184,6 @@ pref("extensions.webextensions.themes.icons.buttons", "back,forward,reload,stop,
pref("lightweightThemes.update.enabled", true); pref("lightweightThemes.update.enabled", true);
pref("lightweightThemes.getMoreURL", "https://addons.mozilla.org/%LOCALE%/firefox/themes"); pref("lightweightThemes.getMoreURL", "https://addons.mozilla.org/%LOCALE%/firefox/themes");
pref("lightweightThemes.recommendedThemes", "[{\"id\":\"recommended-1\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/a-web-browser-renaissance/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.header.jpg\",\"textcolor\":\"#000000\",\"accentcolor\":\"#834d29\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.preview.jpg\",\"author\":\"Sean.Martell\",\"version\":\"0\"},{\"id\":\"recommended-2\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/space-fantasy/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.header.jpg\",\"textcolor\":\"#ffffff\",\"accentcolor\":\"#d9d9d9\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.preview.jpg\",\"author\":\"fx5800p\",\"version\":\"1.0\"},{\"id\":\"recommended-4\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/pastel-gradient/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.header.png\",\"textcolor\":\"#000000\",\"accentcolor\":\"#000000\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.icon.png\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.preview.png\",\"author\":\"darrinhenein\",\"version\":\"1.0\"}]");
#if defined(MOZ_WIDEVINE_EME) #if defined(MOZ_WIDEVINE_EME)
pref("browser.eme.ui.enabled", true); pref("browser.eme.ui.enabled", true);

Просмотреть файл

@ -671,156 +671,3 @@ var gExtensionsNotifications = {
} }
}, },
}; };
var LightWeightThemeWebInstaller = {
init() {
let mm = window.messageManager;
mm.addMessageListener("LightWeightThemeWebInstaller:Install", this);
mm.addMessageListener("LightWeightThemeWebInstaller:Preview", this);
mm.addMessageListener("LightWeightThemeWebInstaller:ResetPreview", this);
XPCOMUtils.defineLazyPreferenceGetter(this, "_apiTesting", "extensions.webapi.testing", false);
},
receiveMessage(message) {
// ignore requests from background tabs
if (message.target != gBrowser.selectedBrowser) {
return;
}
let data = message.data;
switch (message.name) {
case "LightWeightThemeWebInstaller:Install": {
this._installRequest(data.themeData, data.principal, data.baseURI);
break;
}
case "LightWeightThemeWebInstaller:Preview": {
this._preview(data.themeData, data.principal, data.baseURI);
break;
}
case "LightWeightThemeWebInstaller:ResetPreview": {
this._resetPreview(data && data.principal);
break;
}
}
},
handleEvent(event) {
switch (event.type) {
case "TabSelect": {
this._resetPreview();
break;
}
}
},
get _manager() {
let temp = {};
ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
delete this._manager;
return this._manager = temp.LightweightThemeManager;
},
_installRequest(dataString, principal, baseURI) {
// Don't allow installing off null principals.
if (!principal.URI) {
return;
}
let data = this._manager.parseTheme(dataString, baseURI);
if (!data) {
return;
}
// A notification bar with the option to undo is normally shown after a
// theme is installed. But the discovery pane served from the url(s)
// below has its own toggle switch for quick undos, so don't show the
// notification in that case.
let notify = this._shouldShowUndoPrompt(principal);
if (this._isAllowed(principal)) {
this._install(data, notify);
return;
}
let strings = {
header: gNavigatorBundle.getFormattedString("webextPerms.header", ["<>"]),
addonName: data.name,
text: gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message2",
[principal.URI.host]),
acceptText: gNavigatorBundle.getString("lwthemeInstallRequest.allowButton2"),
acceptKey: gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey2"),
cancelText: gNavigatorBundle.getString("webextPerms.cancel.label"),
cancelKey: gNavigatorBundle.getString("webextPerms.cancel.accessKey"),
msgs: [],
};
ExtensionsUI.showPermissionsPrompt(gBrowser.selectedBrowser, strings, null,
"installWeb").then(answer => {
if (answer) {
LightWeightThemeWebInstaller._install(data, notify);
}
});
},
_install(newLWTheme, notify) {
let listener = {
onEnabled(aAddon) {
if (notify) {
ExtensionsUI.showInstallNotification(gBrowser.selectedBrowser, newLWTheme);
}
},
};
AddonManager.addAddonListener(listener);
this._manager.currentTheme = newLWTheme;
AddonManager.removeAddonListener(listener);
},
_preview(dataString, principal, baseURI) {
if (!this._isAllowed(principal))
return;
let data = this._manager.parseTheme(dataString, baseURI);
if (!data)
return;
this._resetPreview();
gBrowser.tabContainer.addEventListener("TabSelect", this);
this._manager.previewTheme(data);
},
_resetPreview(principal) {
if (!this._isAllowed(principal))
return;
gBrowser.tabContainer.removeEventListener("TabSelect", this);
this._manager.resetPreview();
},
_isAllowed(principal) {
if (!principal || !principal.URI || !principal.URI.schemeIs("https")) {
return false;
}
let pm = Services.perms;
return pm.testPermission(principal.URI, "install") == pm.ALLOW_ACTION;
},
_shouldShowUndoPrompt(principal) {
if (!principal || !principal.URI) {
return true;
}
let prePath = principal.URI.prePath;
if (prePath == "https://discovery.addons.mozilla.org") {
return false;
}
if (this._apiTesting && (prePath == "https://discovery.addons.allizom.org" ||
prePath == "https://discovery.addons-dev.allizom.org")) {
return false;
}
return true;
},
};

Просмотреть файл

@ -2,6 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
ChromeUtils.defineModuleGetter(
this, "LightweightThemeManager",
"resource://gre/modules/LightweightThemeManager.jsm");
/** /**
* Enables compacttheme.css when needed. * Enables compacttheme.css when needed.
*/ */

Просмотреть файл

@ -6,13 +6,21 @@
/* eslint-env mozilla/browser-window */ /* eslint-env mozilla/browser-window */
/** /**
* Global browser interface with the WebRender backend. * Global browser interface with graphics utilities.
*/ */
var gWebRender = { var gGfxUtils = {
_isRecording: false,
/**
* Toggle composition recording for the current window.
*/
toggleWindowRecording() {
window.windowUtils.setCompositionRecording(!this._isRecording);
this._isRecording = !this._isRecording;
},
/** /**
* Trigger a WebRender capture of the current state into a local folder. * Trigger a WebRender capture of the current state into a local folder.
*/ */
capture() { webrenderCapture() {
window.windowUtils.wrCapture(); window.windowUtils.wrCapture();
}, },
}; };

Просмотреть файл

@ -96,7 +96,8 @@
<command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/> <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
#ifdef NIGHTLY_BUILD #ifdef NIGHTLY_BUILD
<command id="wrCaptureCmd" oncommand="gWebRender.capture();"/> <command id="wrCaptureCmd" oncommand="gGfxUtils.webrenderCapture();"/>
<command id="windowRecordingCmd" oncommand="gGfxUtils.toggleWindowRecording();"/>
#endif #endif
#ifdef XP_MACOSX #ifdef XP_MACOSX
<command id="minimizeWindow" <command id="minimizeWindow"
@ -317,6 +318,13 @@
key="#" modifiers="control" key="#" modifiers="control"
#endif #endif
command="wrCaptureCmd"/> command="wrCaptureCmd"/>
<key id="key_windowRecordingCmd"
#ifdef XP_MACOSX
key="4" modifiers="control,shift"
#else
key="$" modifiers="control"
#endif
command="windowRecordingCmd"/>
#endif #endif
#ifdef XP_MACOSX #ifdef XP_MACOSX
<key id="key_minimizeWindow" <key id="key_minimizeWindow"

Просмотреть файл

@ -31,7 +31,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
FormValidationHandler: "resource:///modules/FormValidationHandler.jsm", FormValidationHandler: "resource:///modules/FormValidationHandler.jsm",
HomePage: "resource:///modules/HomePage.jsm", HomePage: "resource:///modules/HomePage.jsm",
LightweightThemeConsumer: "resource://gre/modules/LightweightThemeConsumer.jsm", LightweightThemeConsumer: "resource://gre/modules/LightweightThemeConsumer.jsm",
LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
Log: "resource://gre/modules/Log.jsm", Log: "resource://gre/modules/Log.jsm",
LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm", LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm",
MigrationUtils: "resource:///modules/MigrationUtils.jsm", MigrationUtils: "resource:///modules/MigrationUtils.jsm",
@ -102,8 +101,7 @@ XPCOMUtils.defineLazyScriptGetter(this, "gViewSourceUtils",
"chrome://global/content/viewSourceUtils.js"); "chrome://global/content/viewSourceUtils.js");
XPCOMUtils.defineLazyScriptGetter(this, "gTabsPanel", XPCOMUtils.defineLazyScriptGetter(this, "gTabsPanel",
"chrome://browser/content/browser-allTabsMenu.js"); "chrome://browser/content/browser-allTabsMenu.js");
XPCOMUtils.defineLazyScriptGetter(this, ["LightWeightThemeWebInstaller", XPCOMUtils.defineLazyScriptGetter(this, ["gExtensionsNotifications",
"gExtensionsNotifications",
"gXPInstallObserver"], "gXPInstallObserver"],
"chrome://browser/content/browser-addons.js"); "chrome://browser/content/browser-addons.js");
XPCOMUtils.defineLazyScriptGetter(this, "ctrlTab", XPCOMUtils.defineLazyScriptGetter(this, "ctrlTab",
@ -141,8 +139,8 @@ XPCOMUtils.defineLazyScriptGetter(this, "gEditItemOverlay",
XPCOMUtils.defineLazyScriptGetter(this, "SearchOneOffs", XPCOMUtils.defineLazyScriptGetter(this, "SearchOneOffs",
"chrome://browser/content/search/search-one-offs.js"); "chrome://browser/content/search/search-one-offs.js");
if (AppConstants.NIGHTLY_BUILD) { if (AppConstants.NIGHTLY_BUILD) {
XPCOMUtils.defineLazyScriptGetter(this, "gWebRender", XPCOMUtils.defineLazyScriptGetter(this, "gGfxUtils",
"chrome://browser/content/browser-webrender.js"); "chrome://browser/content/browser-graphics-utils.js");
} }
XPCOMUtils.defineLazyScriptGetter(this, "pktUI", "chrome://pocket/content/main.js"); XPCOMUtils.defineLazyScriptGetter(this, "pktUI", "chrome://pocket/content/main.js");
@ -1654,8 +1652,6 @@ var gBrowserInit = {
placesContext.addEventListener("popuphiding", updateEditUIVisibility); placesContext.addEventListener("popuphiding", updateEditUIVisibility);
} }
LightWeightThemeWebInstaller.init();
FullScreen.init(); FullScreen.init();
PointerLock.init(); PointerLock.init();

Просмотреть файл

@ -5,15 +5,20 @@
* themes are applied. * themes are applied.
*/ */
const DEFAULT_THEME = "default-theme@mozilla.org";
const PREF_LWTHEME_USED_THEMES = "lightweightThemes.usedThemes"; const PREF_LWTHEME_USED_THEMES = "lightweightThemes.usedThemes";
const COMPACT_LIGHT_ID = "firefox-compact-light@mozilla.org"; const COMPACT_LIGHT_ID = "firefox-compact-light@mozilla.org";
const COMPACT_DARK_ID = "firefox-compact-dark@mozilla.org"; const COMPACT_DARK_ID = "firefox-compact-dark@mozilla.org";
const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm"); const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
async function selectTheme(id) {
let theme = await AddonManager.getAddonByID(id || DEFAULT_THEME);
await theme.enable();
}
registerCleanupFunction(() => { registerCleanupFunction(() => {
// Set preferences back to their original values // Set preferences back to their original values
LightweightThemeManager.currentTheme = null; return selectTheme(null);
Services.prefs.clearUserPref(PREF_LWTHEME_USED_THEMES);
}); });
function tick() { function tick() {
@ -22,70 +27,22 @@ function tick() {
add_task(async function startTests() { add_task(async function startTests() {
info("Setting the current theme to null"); info("Setting the current theme to null");
LightweightThemeManager.currentTheme = null; await selectTheme(null);
await tick(); await tick();
ok(!CompactTheme.isStyleSheetEnabled, "There is no compact style sheet when no lw theme is applied."); ok(!CompactTheme.isStyleSheetEnabled, "There is no compact style sheet when no lw theme is applied.");
info("Adding a lightweight theme.");
LightweightThemeManager.currentTheme = dummyLightweightTheme("preview0");
await tick();
ok(!CompactTheme.isStyleSheetEnabled, "The compact stylesheet has been removed when a lightweight theme is applied.");
info("Applying the dark compact theme."); info("Applying the dark compact theme.");
LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme(COMPACT_DARK_ID); await selectTheme(COMPACT_DARK_ID);
await tick(); await tick();
ok(CompactTheme.isStyleSheetEnabled, "The compact stylesheet has been added when the compact lightweight theme is applied"); ok(CompactTheme.isStyleSheetEnabled, "The compact stylesheet has been added when the compact lightweight theme is applied");
info("Applying the light compact theme."); info("Applying the light compact theme.");
LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme(COMPACT_LIGHT_ID); await selectTheme(COMPACT_LIGHT_ID);
await tick(); await tick();
ok(CompactTheme.isStyleSheetEnabled, "The compact stylesheet has been added when the compact lightweight theme is applied"); ok(CompactTheme.isStyleSheetEnabled, "The compact stylesheet has been added when the compact lightweight theme is applied");
info("Adding a different lightweight theme.");
LightweightThemeManager.currentTheme = dummyLightweightTheme("preview1");
await tick();
ok(!CompactTheme.isStyleSheetEnabled, "The compact stylesheet has been removed when a lightweight theme is applied.");
info("Unapplying all themes."); info("Unapplying all themes.");
LightweightThemeManager.currentTheme = null; await selectTheme(null);
await tick(); await tick();
ok(!CompactTheme.isStyleSheetEnabled, "There is no compact style sheet when no lw theme is applied."); ok(!CompactTheme.isStyleSheetEnabled, "There is no compact style sheet when no lw theme is applied.");
}); });
function dummyLightweightTheme(id) {
return {
id,
name: id,
iconURL: "resource:///chrome/browser/content/browser/defaultthemes/light.icon.svg",
textcolor: "red",
accentcolor: "blue",
};
}
add_task(async function testLightweightThemePreview() {
info("Setting compact to current and previewing others");
LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme(COMPACT_LIGHT_ID);
await tick();
ok(CompactTheme.isStyleSheetEnabled, "The compact stylesheet is enabled.");
LightweightThemeManager.previewTheme(dummyLightweightTheme("preview0"));
ok(!CompactTheme.isStyleSheetEnabled, "The compact stylesheet is not enabled after a lightweight theme preview.");
LightweightThemeManager.resetPreview();
LightweightThemeManager.previewTheme(dummyLightweightTheme("preview1"));
ok(!CompactTheme.isStyleSheetEnabled, "The compact stylesheet is not enabled after a second lightweight theme preview.");
LightweightThemeManager.resetPreview();
ok(CompactTheme.isStyleSheetEnabled, "The compact stylesheet is enabled again after resetting the preview.");
LightweightThemeManager.currentTheme = null;
await tick();
ok(!CompactTheme.isStyleSheetEnabled, "The compact stylesheet is gone after removing the current theme.");
info("Previewing the compact theme");
LightweightThemeManager.previewTheme(LightweightThemeManager.getUsedTheme(COMPACT_LIGHT_ID));
ok(CompactTheme.isStyleSheetEnabled, "The compact stylesheet is enabled.");
LightweightThemeManager.previewTheme(LightweightThemeManager.getUsedTheme(COMPACT_DARK_ID));
ok(CompactTheme.isStyleSheetEnabled, "The compact stylesheet is enabled.");
LightweightThemeManager.previewTheme(dummyLightweightTheme("preview2"));
ok(!CompactTheme.isStyleSheetEnabled, "The compact stylesheet is now disabled after previewing a new sheet.");
LightweightThemeManager.resetPreview();
ok(!CompactTheme.isStyleSheetEnabled, "The compact stylesheet is now disabled after resetting the preview.");
});

Просмотреть файл

@ -2,10 +2,17 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm"); const DEFAULT_THEME = "default-theme@mozilla.org";
const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
async function selectTheme(id) {
let theme = await AddonManager.getAddonByID(id || DEFAULT_THEME);
await theme.enable();
}
registerCleanupFunction(() => { registerCleanupFunction(() => {
LightweightThemeManager.currentTheme = null; return selectTheme(null);
Services.prefs.clearUserPref("lightweightThemes.usedThemes");
}); });
add_task(async function withoutLWT() { add_task(async function withoutLWT() {
@ -16,7 +23,7 @@ add_task(async function withoutLWT() {
}); });
add_task(async function withLWT() { add_task(async function withLWT() {
LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-compact-light@mozilla.org"); await selectTheme("firefox-compact-light@mozilla.org");
let win = await BrowserTestUtils.openNewBrowserWindow(); let win = await BrowserTestUtils.openNewBrowserWindow();
ok(!win.gBrowser.tabContainer.hasAttribute("overflow"), "tab container not overflowing"); ok(!win.gBrowser.tabContainer.hasAttribute("overflow"), "tab container not overflowing");
ok(win.gBrowser.tabContainer.arrowScrollbox.hasAttribute("notoverflowing"), "arrow scrollbox not overflowing"); ok(win.gBrowser.tabContainer.arrowScrollbox.hasAttribute("notoverflowing"), "arrow scrollbox not overflowing");

Просмотреть файл

@ -58,7 +58,7 @@ browser.jar:
content/browser/browser-tabsintitlebar.js (content/browser-tabsintitlebar.js) content/browser/browser-tabsintitlebar.js (content/browser-tabsintitlebar.js)
content/browser/browser-toolbarKeyNav.js (content/browser-toolbarKeyNav.js) content/browser/browser-toolbarKeyNav.js (content/browser-toolbarKeyNav.js)
content/browser/browser-thumbnails.js (content/browser-thumbnails.js) content/browser/browser-thumbnails.js (content/browser-thumbnails.js)
content/browser/browser-webrender.js (content/browser-webrender.js) content/browser/browser-graphics-utils.js (content/browser-graphics-utils.js)
content/browser/tab-content.js (content/tab-content.js) content/browser/tab-content.js (content/tab-content.js)
content/browser/content.js (content/content.js) content/browser/content.js (content/content.js)
content/browser/defaultthemes/1.header.jpg (content/defaultthemes/1.header.jpg) content/browser/defaultthemes/1.header.jpg (content/defaultthemes/1.header.jpg)

Просмотреть файл

@ -132,17 +132,6 @@ let ACTORS = {
}, },
}, },
LightWeightThemeInstall: {
child: {
module: "resource:///actors/LightWeightThemeInstallChild.jsm",
events: {
"InstallBrowserTheme": {wantUntrusted: true},
"PreviewBrowserTheme": {wantUntrusted: true},
"ResetBrowserThemePreview": {wantUntrusted: true},
},
},
},
LightweightTheme: { LightweightTheme: {
child: { child: {
module: "resource:///actors/LightweightThemeChild.jsm", module: "resource:///actors/LightweightThemeChild.jsm",

Просмотреть файл

@ -10,13 +10,14 @@ const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm")
const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetters(this, { XPCOMUtils.defineLazyModuleGetters(this, {
AddonManager: "resource://gre/modules/AddonManager.jsm",
AddonManagerPrivate: "resource://gre/modules/AddonManager.jsm",
SearchWidgetTracker: "resource:///modules/SearchWidgetTracker.jsm", SearchWidgetTracker: "resource:///modules/SearchWidgetTracker.jsm",
CustomizableWidgets: "resource:///modules/CustomizableWidgets.jsm", CustomizableWidgets: "resource:///modules/CustomizableWidgets.jsm",
DeferredTask: "resource://gre/modules/DeferredTask.jsm", DeferredTask: "resource://gre/modules/DeferredTask.jsm",
PanelMultiView: "resource:///modules/PanelMultiView.jsm", PanelMultiView: "resource:///modules/PanelMultiView.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
ShortcutUtils: "resource://gre/modules/ShortcutUtils.jsm", ShortcutUtils: "resource://gre/modules/ShortcutUtils.jsm",
LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
}); });
XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() { XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() {
@ -27,6 +28,8 @@ XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() {
XPCOMUtils.defineLazyServiceGetter(this, "gELS", XPCOMUtils.defineLazyServiceGetter(this, "gELS",
"@mozilla.org/eventlistenerservice;1", "nsIEventListenerService"); "@mozilla.org/eventlistenerservice;1", "nsIEventListenerService");
const kDefaultThemeID = "default-theme@mozilla.org";
const kSpecialWidgetPfx = "customizableui-special-"; const kSpecialWidgetPfx = "customizableui-special-";
const kPrefCustomizationState = "browser.uiCustomization.state"; const kPrefCustomizationState = "browser.uiCustomization.state";
@ -40,6 +43,9 @@ const kPrefAutoHideDownloadsButton = "browser.download.autohideButton";
const kExpectedWindowURL = AppConstants.BROWSER_CHROME_URL; const kExpectedWindowURL = AppConstants.BROWSER_CHROME_URL;
var gDefaultTheme;
var gSelectedTheme;
/** /**
* The keys are the handlers that are fired when the event type (the value) * The keys are the handlers that are fired when the event type (the value)
* is fired on the subview. A widget that provides a subview has the option * is fired on the subview. A widget that provides a subview has the option
@ -178,6 +184,11 @@ var CustomizableUIInternal = {
initialize() { initialize() {
log.debug("Initializing"); log.debug("Initializing");
Services.obs.addObserver(this, "xpi-database-loaded");
if (AddonManagerPrivate.isDBLoaded()) {
this.observe(null, "xpi-database-loaded");
}
this.addListener(this); this.addListener(this);
this._defineBuiltInWidgets(); this._defineBuiltInWidgets();
this.loadSavedState(); this.loadSavedState();
@ -245,6 +256,22 @@ var CustomizableUIInternal = {
SearchWidgetTracker.init(); SearchWidgetTracker.init();
}, },
async observe(subject, topic, data) {
if (topic == "xpi-database-loaded") {
AddonManager.addAddonListener(this);
let addons = await AddonManager.getAddonsByTypes(["theme"]);
gDefaultTheme = addons.find(addon => addon.id == kDefaultThemeID);
gSelectedTheme = addons.find(addon => addon.isActive) || gDefaultTheme;
}
},
onEnabled(addon) {
if (addon.type == "theme") {
gSelectedTheme = addon;
}
},
get _builtinAreas() { get _builtinAreas() {
return new Set([ return new Set([
...this._builtinToolbars, ...this._builtinToolbars,
@ -2652,7 +2679,7 @@ var CustomizableUIInternal = {
gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState); gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState);
gUIStateBeforeReset.uiDensity = Services.prefs.getIntPref(kPrefUIDensity); gUIStateBeforeReset.uiDensity = Services.prefs.getIntPref(kPrefUIDensity);
gUIStateBeforeReset.autoTouchMode = Services.prefs.getBoolPref(kPrefAutoTouchMode); gUIStateBeforeReset.autoTouchMode = Services.prefs.getBoolPref(kPrefAutoTouchMode);
gUIStateBeforeReset.currentTheme = LightweightThemeManager.currentTheme; gUIStateBeforeReset.currentTheme = gSelectedTheme;
gUIStateBeforeReset.autoHideDownloadsButton = Services.prefs.getBoolPref(kPrefAutoHideDownloadsButton); gUIStateBeforeReset.autoHideDownloadsButton = Services.prefs.getBoolPref(kPrefAutoHideDownloadsButton);
gUIStateBeforeReset.newElementCount = gNewElementCount; gUIStateBeforeReset.newElementCount = gNewElementCount;
} catch (e) { } } catch (e) { }
@ -2663,7 +2690,7 @@ var CustomizableUIInternal = {
Services.prefs.clearUserPref(kPrefUIDensity); Services.prefs.clearUserPref(kPrefUIDensity);
Services.prefs.clearUserPref(kPrefAutoTouchMode); Services.prefs.clearUserPref(kPrefAutoTouchMode);
Services.prefs.clearUserPref(kPrefAutoHideDownloadsButton); Services.prefs.clearUserPref(kPrefAutoHideDownloadsButton);
LightweightThemeManager.currentTheme = null; gDefaultTheme.enable();
gNewElementCount = 0; gNewElementCount = 0;
log.debug("State reset"); log.debug("State reset");
@ -2725,7 +2752,7 @@ var CustomizableUIInternal = {
Services.prefs.setIntPref(kPrefUIDensity, uiDensity); Services.prefs.setIntPref(kPrefUIDensity, uiDensity);
Services.prefs.setBoolPref(kPrefAutoTouchMode, autoTouchMode); Services.prefs.setBoolPref(kPrefAutoTouchMode, autoTouchMode);
Services.prefs.setBoolPref(kPrefAutoHideDownloadsButton, autoHideDownloadsButton); Services.prefs.setBoolPref(kPrefAutoHideDownloadsButton, autoHideDownloadsButton);
LightweightThemeManager.currentTheme = currentTheme; currentTheme.enable();
this.loadSavedState(); this.loadSavedState();
// If the user just customizes toolbar/titlebar visibility, gSavedState will be null // If the user just customizes toolbar/titlebar visibility, gSavedState will be null
// and we don't need to do anything else here: // and we don't need to do anything else here:
@ -2942,9 +2969,9 @@ var CustomizableUIInternal = {
return false; return false;
} }
if (LightweightThemeManager.currentTheme && // This should just be `gDefaultTheme.isActive`, but bugs...
LightweightThemeManager.currentTheme.id != "default-theme@mozilla.org") { if (gDefaultTheme && gDefaultTheme.id != gSelectedTheme.id) {
log.debug(LightweightThemeManager.currentTheme + " theme is non-default"); log.debug(gSelectedTheme.id + " theme is non-default");
return false; return false;
} }

Просмотреть файл

@ -28,14 +28,14 @@ const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.j
XPCOMUtils.defineLazyGlobalGetters(this, ["CSS"]); XPCOMUtils.defineLazyGlobalGetters(this, ["CSS"]);
ChromeUtils.defineModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
ChromeUtils.defineModuleGetter(this, "AMTelemetry", ChromeUtils.defineModuleGetter(this, "AMTelemetry",
"resource://gre/modules/AddonManager.jsm"); "resource://gre/modules/AddonManager.jsm");
ChromeUtils.defineModuleGetter(this, "DragPositionManager", ChromeUtils.defineModuleGetter(this, "DragPositionManager",
"resource:///modules/DragPositionManager.jsm"); "resource:///modules/DragPositionManager.jsm");
ChromeUtils.defineModuleGetter(this, "BrowserUtils", ChromeUtils.defineModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm"); "resource://gre/modules/BrowserUtils.jsm");
ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
"resource://gre/modules/LightweightThemeManager.jsm");
ChromeUtils.defineModuleGetter(this, "SessionStore", ChromeUtils.defineModuleGetter(this, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm"); "resource:///modules/sessionstore/SessionStore.jsm");
XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() { XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() {
@ -164,11 +164,11 @@ CustomizeMode.prototype = {
} }
}, },
_updateLWThemeButtonIcon() { async _updateThemeButtonIcon() {
let lwthemeButton = this.$("customization-lwtheme-button"); let lwthemeButton = this.$("customization-lwtheme-button");
let lwthemeIcon = this.document.getAnonymousElementByAttribute(lwthemeButton, let lwthemeIcon = this.document.getAnonymousElementByAttribute(lwthemeButton,
"class", "button-icon"); "class", "button-icon");
let theme = LightweightThemeManager.currentTheme; let theme = (await AddonManager.getAddonsByTypes(["theme"])).find(addon => addon.isActive);
lwthemeIcon.style.backgroundImage = theme ? "url(" + theme.iconURL + ")" : ""; lwthemeIcon.style.backgroundImage = theme ? "url(" + theme.iconURL + ")" : "";
}, },
@ -344,8 +344,8 @@ CustomizeMode.prototype = {
}, 0); }, 0);
this._updateEmptyPaletteNotice(); this._updateEmptyPaletteNotice();
this._updateLWThemeButtonIcon(); this._updateThemeButtonIcon();
Services.obs.addObserver(this, "lightweight-theme-changed"); AddonManager.addAddonListener(this);
this._setupDownloadAutoHideToggle(); this._setupDownloadAutoHideToggle();
@ -388,7 +388,7 @@ CustomizeMode.prototype = {
this._teardownDownloadAutoHideToggle(); this._teardownDownloadAutoHideToggle();
Services.obs.removeObserver(this, "lightweight-theme-changed"); AddonManager.removeAddonListener(this);
CustomizableUI.removeListener(this); CustomizableUI.removeListener(this);
this.document.removeEventListener("keypress", this); this.document.removeEventListener("keypress", this);
@ -1310,23 +1310,13 @@ CustomizeMode.prototype = {
this._onUIChange(); this._onUIChange();
}, },
onLWThemesMenuShowing(aEvent) { async onThemesMenuShowing(aEvent) {
const DEFAULT_THEME_ID = "default-theme@mozilla.org"; const DEFAULT_THEME_ID = "default-theme@mozilla.org";
const LIGHT_THEME_ID = "firefox-compact-light@mozilla.org"; const LIGHT_THEME_ID = "firefox-compact-light@mozilla.org";
const DARK_THEME_ID = "firefox-compact-dark@mozilla.org"; const DARK_THEME_ID = "firefox-compact-dark@mozilla.org";
const MAX_THEME_COUNT = 6; const MAX_THEME_COUNT = 6;
this._clearLWThemesMenu(aEvent.target); this._clearThemesMenu(aEvent.target);
function previewTheme(aPreviewThemeEvent) {
LightweightThemeManager.previewTheme(
aPreviewThemeEvent.target.theme.id != DEFAULT_THEME_ID ?
aPreviewThemeEvent.target.theme : null);
}
function resetPreview() {
LightweightThemeManager.resetPreview();
}
let onThemeSelected = panel => { let onThemeSelected = panel => {
// This causes us to call _onUIChange when the LWT actually changes, // This causes us to call _onUIChange when the LWT actually changes,
@ -1346,50 +1336,36 @@ CustomizeMode.prototype = {
tbb.setAttribute("tooltiptext", aTheme.description); tbb.setAttribute("tooltiptext", aTheme.description);
tbb.setAttribute("tabindex", "0"); tbb.setAttribute("tabindex", "0");
tbb.classList.add("customization-lwtheme-menu-theme"); tbb.classList.add("customization-lwtheme-menu-theme");
let isActive = activeThemeID == aTheme.id; let isActive = aTheme.isActive;
tbb.setAttribute("aria-checked", isActive); tbb.setAttribute("aria-checked", isActive);
tbb.setAttribute("role", "menuitemradio"); tbb.setAttribute("role", "menuitemradio");
if (isActive) { if (isActive) {
tbb.setAttribute("active", "true"); tbb.setAttribute("active", "true");
} }
tbb.addEventListener("focus", previewTheme);
tbb.addEventListener("mouseover", previewTheme);
tbb.addEventListener("blur", resetPreview);
return tbb; return tbb;
} }
let themes = []; let themes = await AddonManager.getAddonsByTypes(["theme"]);
let lwts = LightweightThemeManager.usedThemes; let currentTheme = themes.find(theme => theme.isActive);
let currentLwt = LightweightThemeManager.currentTheme;
let activeThemeID = currentLwt ? currentLwt.id : DEFAULT_THEME_ID;
// Move the current theme (if any) and the light/dark themes to the start: // Move the current theme (if any) and the light/dark themes to the start:
let importantThemes = [DEFAULT_THEME_ID, LIGHT_THEME_ID, DARK_THEME_ID]; let importantThemes = new Set([DEFAULT_THEME_ID, LIGHT_THEME_ID, DARK_THEME_ID]);
if (currentLwt && !importantThemes.includes(currentLwt.id)) { if (currentTheme) {
importantThemes.push(currentLwt.id); importantThemes.add(currentTheme.id);
} }
for (let importantTheme of importantThemes) {
let themeIndex = lwts.findIndex(theme => theme.id == importantTheme); themes.sort((a, b) => importantThemes.has(b) - importantThemes.has(a));
if (themeIndex > -1) {
themes.push(...lwts.splice(themeIndex, 1));
}
}
themes = themes.concat(lwts);
if (themes.length > MAX_THEME_COUNT) if (themes.length > MAX_THEME_COUNT)
themes.length = MAX_THEME_COUNT; themes.length = MAX_THEME_COUNT;
let footer = doc.getElementById("customization-lwtheme-menu-footer"); let footer = doc.getElementById("customization-lwtheme-menu-footer");
let panel = footer.parentNode; let panel = footer.parentNode;
let recommendedLabel = doc.getElementById("customization-lwtheme-menu-recommended");
for (let theme of themes) { for (let theme of themes) {
let button = buildToolbarButton(theme); let button = buildToolbarButton(theme);
button.addEventListener("command", () => { button.addEventListener("command", async () => {
if ("userDisabled" in button.theme) await button.theme.enable();
button.theme.userDisabled = false;
else
LightweightThemeManager.currentTheme = button.theme;
onThemeSelected(panel); onThemeSelected(panel);
AMTelemetry.recordActionEvent({ AMTelemetry.recordActionEvent({
object: "customize", object: "customize",
@ -1397,69 +1373,16 @@ CustomizeMode.prototype = {
extra: {type: "theme", addonId: theme.id}, extra: {type: "theme", addonId: theme.id},
}); });
}); });
panel.insertBefore(button, recommendedLabel);
}
function panelMouseOut(e) {
// mouseout events bubble, so we get mouseout events for the buttons
// in the panel. Here, we only care when the mouse actually leaves
// the panel. For some reason event.target might not be the panel
// even when the mouse *is* leaving the panel, so we check
// explicitOriginalTarget instead.
if (e.explicitOriginalTarget == panel) {
resetPreview();
}
}
panel.addEventListener("mouseout", panelMouseOut);
panel.addEventListener("popuphidden", () => {
panel.removeEventListener("mouseout", panelMouseOut);
resetPreview();
}, {once: true});
let lwthemePrefs = Services.prefs.getBranch("lightweightThemes.");
let recommendedThemes = lwthemePrefs.getStringPref("recommendedThemes");
recommendedThemes = JSON.parse(recommendedThemes);
let sb = Services.strings.createBundle("chrome://browser/locale/lightweightThemes.properties");
for (let theme of recommendedThemes) {
try {
theme.name = sb.GetStringFromName("lightweightThemes." + theme.id + ".name");
theme.description = sb.GetStringFromName("lightweightThemes." + theme.id + ".description");
} catch (ex) {
// If finding strings for this failed, just don't build it. This can
// happen for users with 'older' recommended themes lists, some of which
// have since been removed from Firefox.
continue;
}
let button = buildToolbarButton(theme);
button.addEventListener("command", () => {
LightweightThemeManager.setLocalTheme(button.theme);
recommendedThemes = recommendedThemes.filter((aTheme) => { return aTheme.id != button.theme.id; });
lwthemePrefs.setStringPref("recommendedThemes",
JSON.stringify(recommendedThemes));
onThemeSelected(panel);
let addonId = `${button.theme.id}@personas.mozilla.org`;
AMTelemetry.recordActionEvent({
object: "customize",
action: "enable",
value: "recommended",
extra: {type: "theme", addonId},
});
});
panel.insertBefore(button, footer); panel.insertBefore(button, footer);
} }
let hideRecommendedLabel = (footer.previousElementSibling == recommendedLabel);
recommendedLabel.hidden = hideRecommendedLabel;
}, },
_clearLWThemesMenu(panel) { _clearThemesMenu(panel) {
let footer = this.$("customization-lwtheme-menu-footer"); let footer = this.$("customization-lwtheme-menu-footer");
let recommendedLabel = this.$("customization-lwtheme-menu-recommended"); let element = footer;
for (let element of [footer, recommendedLabel]) { while (element.previousElementSibling &&
while (element.previousElementSibling && element.previousElementSibling.localName == "toolbarbutton") {
element.previousElementSibling.localName == "toolbarbutton") { element.previousElementSibling.remove();
element.previousElementSibling.remove();
}
} }
// Workaround for bug 1059934 // Workaround for bug 1059934
@ -1582,16 +1505,21 @@ CustomizeMode.prototype = {
this._updateDragSpaceCheckbox(); this._updateDragSpaceCheckbox();
} }
break; break;
case "lightweight-theme-changed":
this._updateLWThemeButtonIcon();
if (this._nextThemeChangeUserTriggered) {
this._onUIChange();
}
this._nextThemeChangeUserTriggered = false;
break;
} }
}, },
async onEnabled(addon) {
if (addon.type != "theme") {
return;
}
await this._updateThemeButtonIcon();
if (this._nextThemeChangeUserTriggered) {
this._onUIChange();
}
this._nextThemeChangeUserTriggered = false;
},
_canDrawInTitlebar() { _canDrawInTitlebar() {
return this.window.TabsInTitlebar.systemSupported; return this.window.TabsInTitlebar.systemSupported;
}, },

Просмотреть файл

@ -41,12 +41,11 @@
</button> </button>
<button id="customization-lwtheme-button" label="&customizeMode.lwthemes;" class="customizationmode-button" type="menu"> <button id="customization-lwtheme-button" label="&customizeMode.lwthemes;" class="customizationmode-button" type="menu">
<panel type="arrow" id="customization-lwtheme-menu" <panel type="arrow" id="customization-lwtheme-menu"
onpopupshowing="gCustomizeMode.onLWThemesMenuShowing(event);" onpopupshowing="gCustomizeMode.onThemesMenuShowing(event);"
position="topcenter bottomleft" position="topcenter bottomleft"
flip="none" flip="none"
role="menu"> role="menu">
<label id="customization-lwtheme-menu-header" value="&customizeMode.lwthemes.myThemes;"/> <label id="customization-lwtheme-menu-header" value="&customizeMode.lwthemes.myThemes;"/>
<label id="customization-lwtheme-menu-recommended" value="&customizeMode.lwthemes.recommended;"/>
<hbox id="customization-lwtheme-menu-footer"> <hbox id="customization-lwtheme-menu-footer">
<toolbarbutton class="customization-lwtheme-menu-footeritem" <toolbarbutton class="customization-lwtheme-menu-footeritem"
label="&customizeMode.lwthemes.menuManage;" label="&customizeMode.lwthemes.menuManage;"

Просмотреть файл

@ -7,11 +7,9 @@
const DEFAULT_THEME_ID = "default-theme@mozilla.org"; const DEFAULT_THEME_ID = "default-theme@mozilla.org";
const LIGHT_THEME_ID = "firefox-compact-light@mozilla.org"; const LIGHT_THEME_ID = "firefox-compact-light@mozilla.org";
const DARK_THEME_ID = "firefox-compact-dark@mozilla.org"; const DARK_THEME_ID = "firefox-compact-dark@mozilla.org";
const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
add_task(async function() { add_task(async function() {
Services.prefs.clearUserPref("lightweightThemes.usedThemes"); Services.prefs.clearUserPref("lightweightThemes.usedThemes");
Services.prefs.clearUserPref("lightweightThemes.recommendedThemes");
await startCustomizing(); await startCustomizing();
// Check restore defaults button is disabled. // Check restore defaults button is disabled.
@ -51,9 +49,9 @@ add_task(async function() {
await popupShownPromise; await popupShownPromise;
let header = document.getElementById("customization-lwtheme-menu-header"); let header = document.getElementById("customization-lwtheme-menu-header");
let recommendedHeader = document.getElementById("customization-lwtheme-menu-recommended"); let footer = document.getElementById("customization-lwtheme-menu-footer");
is(header.nextElementSibling.nextElementSibling.nextElementSibling.nextElementSibling, recommendedHeader, is(header.nextElementSibling.nextElementSibling.nextElementSibling.nextElementSibling, footer,
"There should only be three themes (default, light, dark) in the 'My Themes' section by default"); "There should only be three themes (default, light, dark) in the 'My Themes' section by default");
is(header.nextElementSibling.theme.id, DEFAULT_THEME_ID, is(header.nextElementSibling.theme.id, DEFAULT_THEME_ID,
"The first theme should be the default theme"); "The first theme should be the default theme");
@ -67,8 +65,11 @@ add_task(async function() {
info("Clicked on light theme"); info("Clicked on light theme");
await themeChangedPromise; await themeChangedPromise;
let button = document.getElementById("customization-reset-button");
await TestUtils.waitForCondition(() => !button.disabled);
// Check restore defaults button is enabled. // Check restore defaults button is enabled.
ok(!document.getElementById("customization-reset-button").disabled, ok(!button.disabled,
"Reset button should not be disabled anymore"); "Reset button should not be disabled anymore");
ok((/light/i).test(themesButtonIcon.style.backgroundImage), ok((/light/i).test(themesButtonIcon.style.backgroundImage),
`Button should show light theme thumbnail - was: "${themesButtonIcon.style.backgroundImage}"`); `Button should show light theme thumbnail - was: "${themesButtonIcon.style.backgroundImage}"`);
@ -84,13 +85,15 @@ add_task(async function() {
is(activeThemes[0].theme.id, LIGHT_THEME_ID, "Light theme should be selected"); is(activeThemes[0].theme.id, LIGHT_THEME_ID, "Light theme should be selected");
} }
let firstLWTheme = recommendedHeader.nextElementSibling; let firstLWTheme = footer.previousElementSibling;
let firstLWThemeId = firstLWTheme.theme.id; let firstLWThemeId = firstLWTheme.theme.id;
themeChangedPromise = promiseObserverNotified("lightweight-theme-changed"); themeChangedPromise = promiseObserverNotified("lightweight-theme-changed");
firstLWTheme.doCommand(); firstLWTheme.doCommand();
info("Clicked on first theme"); info("Clicked on first theme");
await themeChangedPromise; await themeChangedPromise;
await new Promise(executeSoon);
popupShownPromise = popupShown(popup); popupShownPromise = popupShown(popup);
EventUtils.synthesizeMouseAtCenter(themesButton, {}); EventUtils.synthesizeMouseAtCenter(themesButton, {});
info("Clicked on themes button"); info("Clicked on themes button");
@ -103,17 +106,13 @@ add_task(async function() {
} }
is(header.nextElementSibling.theme.id, DEFAULT_THEME_ID, "The first theme should be the Default theme"); is(header.nextElementSibling.theme.id, DEFAULT_THEME_ID, "The first theme should be the Default theme");
let installedThemeId = header.nextElementSibling.nextElementSibling.nextElementSibling.nextElementSibling.theme.id;
ok(installedThemeId.startsWith(firstLWThemeId),
"The second theme in the 'My Themes' section should be the newly installed theme: " +
"Installed theme id: " + installedThemeId + "; First theme ID: " + firstLWThemeId);
let themeCount = 0; let themeCount = 0;
let iterNode = header; let iterNode = header;
while (iterNode.nextElementSibling && iterNode.nextElementSibling.theme) { while (iterNode.nextElementSibling && iterNode.nextElementSibling.theme) {
themeCount++; themeCount++;
iterNode = iterNode.nextElementSibling; iterNode = iterNode.nextElementSibling;
} }
is(themeCount, 4, is(themeCount, 3,
"There should be four themes in the 'My Themes' section"); "There should be four themes in the 'My Themes' section");
let defaultTheme = header.nextElementSibling; let defaultTheme = header.nextElementSibling;
@ -128,20 +127,14 @@ add_task(async function() {
info("Clicked on themes button a fourth time"); info("Clicked on themes button a fourth time");
await popupShownPromise; await popupShownPromise;
firstLWTheme = recommendedHeader.nextElementSibling;
themeChangedPromise = promiseObserverNotified("lightweight-theme-changed");
firstLWTheme.doCommand();
info("Clicked on first theme again");
await themeChangedPromise;
// check that "Restore Defaults" button resets theme // check that "Restore Defaults" button resets theme
await gCustomizeMode.reset(); await gCustomizeMode.reset();
is(LightweightThemeManager.currentTheme.id, DEFAULT_THEME_ID, "Current theme reset to default");
defaultTheme = await AddonManager.getAddonByID(DEFAULT_THEME_ID);
is(defaultTheme.isActive, true, "Current theme reset to default");
await endCustomizing(); await endCustomizing();
Services.prefs.setCharPref("lightweightThemes.usedThemes", "[]"); Services.prefs.setCharPref("lightweightThemes.usedThemes", "[]");
Services.prefs.setCharPref("lightweightThemes.recommendedThemes", "[]");
info("Removed all recommended themes");
await startCustomizing(); await startCustomizing();
popupShownPromise = popupShown(popup); popupShownPromise = popupShown(popup);
EventUtils.synthesizeMouseAtCenter(themesButton, {}); EventUtils.synthesizeMouseAtCenter(themesButton, {});
@ -160,18 +153,10 @@ add_task(async function() {
themeNode = themeNode.nextElementSibling; themeNode = themeNode.nextElementSibling;
is(themeNode.theme.id, DARK_THEME_ID, "The third theme should be the Dark theme"); is(themeNode.theme.id, DARK_THEME_ID, "The third theme should be the Dark theme");
is(themeNode.hidden, false, "The dark theme should never be hidden"); is(themeNode.hidden, false, "The dark theme should never be hidden");
recommendedHeader = document.getElementById("customization-lwtheme-menu-recommended");
is(themeNode.nextElementSibling, recommendedHeader,
"There should only be three themes (default, light, dark) in the 'My Themes' section now");
let footer = document.getElementById("customization-lwtheme-menu-footer");
is(recommendedHeader.nextElementSibling.id, footer.id, "There should be no recommended themes in the menu");
is(recommendedHeader.hidden, true, "The recommendedHeader should be hidden since there are no recommended themes");
}); });
add_task(async function asyncCleanup() { add_task(async function asyncCleanup() {
await endCustomizing(); await endCustomizing();
Services.prefs.clearUserPref("lightweightThemes.usedThemes"); Services.prefs.clearUserPref("lightweightThemes.usedThemes");
Services.prefs.clearUserPref("lightweightThemes.recommendedThemes");
}); });

Просмотреть файл

@ -23,25 +23,28 @@ add_task(async function() {
info("Clicked on themes button"); info("Clicked on themes button");
await popupShownPromise; await popupShownPromise;
let recommendedHeader = document.getElementById("customization-lwtheme-menu-recommended"); let header = document.getElementById("customization-lwtheme-menu-header");
let firstLWTheme = recommendedHeader.nextElementSibling; let firstLWTheme = header.nextElementSibling.nextElementSibling;
let firstLWThemeId = firstLWTheme.theme.id; let firstLWThemeId = firstLWTheme.theme.id;
let themeChangedPromise = promiseObserverNotified("lightweight-theme-changed"); let themeChangedPromise = promiseObserverNotified("lightweight-theme-changed");
firstLWTheme.doCommand(); firstLWTheme.doCommand();
info("Clicked on first theme"); info("Clicked on first theme");
await themeChangedPromise; await themeChangedPromise;
is(LightweightThemeManager.currentTheme.id, firstLWThemeId, "Theme changed to first option"); let theme = await AddonManager.getAddonByID(firstLWThemeId);
is(theme.isActive, true, "Theme changed to first option");
await gCustomizeMode.reset(); await gCustomizeMode.reset();
ok(CustomizableUI.inDefaultState, "In default state after reset"); ok(CustomizableUI.inDefaultState, "In default state after reset");
is(undoResetButton.hidden, false, "The undo button is visible after reset"); is(undoResetButton.hidden, false, "The undo button is visible after reset");
is(LightweightThemeManager.currentTheme.id, "default-theme@mozilla.org", "Theme reset to default"); theme = await AddonManager.getAddonByID("default-theme@mozilla.org");
is(theme.isActive, true, "Theme reset to default");
await gCustomizeMode.undoReset(); await gCustomizeMode.undoReset();
is(LightweightThemeManager.currentTheme.id, firstLWThemeId, "Theme has been reset from default to original choice"); theme = await AddonManager.getAddonByID(firstLWThemeId);
is(theme.isActive, true, "Theme has been reset from default to original choice");
ok(!CustomizableUI.inDefaultState, "Not in default state after undo-reset"); ok(!CustomizableUI.inDefaultState, "Not in default state after undo-reset");
is(undoResetButton.hidden, true, "The undo button is hidden after clicking on the undo button"); is(undoResetButton.hidden, true, "The undo button is hidden after clicking on the undo button");
is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette"); is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette");

Просмотреть файл

@ -16,33 +16,6 @@ add_task(async function testCustomize() {
await startCustomizing(); await startCustomizing();
// Open the panel to populate the recommended themes.
let themePanel = document.getElementById("customization-lwtheme-menu");
themePanel.openPopup();
await BrowserTestUtils.waitForPopupEvent(themePanel, "shown");
// Install a recommended theme.
let recommendedLabel = document.getElementById("customization-lwtheme-menu-recommended");
let themeButton = recommendedLabel.nextElementSibling;
let themeId = `${themeButton.theme.id}@personas.mozilla.org`;
let themeChanged = TestUtils.topicObserved("lightweight-theme-changed");
themeButton.click();
// Wait for the theme to change and the popup to close.
await themeChanged;
await BrowserTestUtils.waitForPopupEvent(themePanel, "hidden");
// Switch back to the default theme.
let installedThemes = document.querySelectorAll(".customization-lwtheme-menu-theme");
let defaultId = "default-theme@mozilla.org";
let defaultThemeIndex = Array.from(installedThemes).findIndex(btn => btn.theme.id == defaultId);
let defaultThemeButton = installedThemes[defaultThemeIndex];
themeChanged = TestUtils.topicObserved("lightweight-theme-changed");
defaultThemeButton.click();
// Wait for the theme to change back to default.
await themeChanged;
// Find the footer buttons to test. // Find the footer buttons to test.
let footerRow = document.getElementById("customization-lwtheme-menu-footer"); let footerRow = document.getElementById("customization-lwtheme-menu-footer");
let [manageButton, getMoreButton] = footerRow.childNodes; let [manageButton, getMoreButton] = footerRow.childNodes;
@ -77,8 +50,6 @@ add_task(async function testCustomize() {
// Events are now [method, object, value, extra] as expected. // Events are now [method, object, value, extra] as expected.
Assert.deepEqual(relatedEvents, [ Assert.deepEqual(relatedEvents, [
["action", "customize", "recommended", {action: "enable", addonId: themeId, type: "theme"}],
["action", "customize", null, {action: "enable", addonId: defaultId, type: "theme"}],
["link", "customize", "manageThemes"], ["link", "customize", "manageThemes"],
["link", "customize", "getThemes"], ["link", "customize", "getThemes"],
], "The events are recorded correctly"); ], "The events are recorded correctly");

Просмотреть файл

@ -30,14 +30,6 @@ if (typeof Mozilla == "undefined") {
Mozilla.UITour = {}; Mozilla.UITour = {};
} }
var themeIntervalId = null;
function _stopCyclingThemes() {
if (themeIntervalId) {
clearInterval(themeIntervalId);
themeIntervalId = null;
}
}
function _sendEvent(action, data) { function _sendEvent(action, data) {
var event = new CustomEvent("mozUITour", { var event = new CustomEvent("mozUITour", {
bubbles: true, bubbles: true,
@ -306,88 +298,6 @@ if (typeof Mozilla == "undefined") {
_sendEvent("hideInfo"); _sendEvent("hideInfo");
}; };
/**
* Preview a lightweight-theme applied to the browser UI.
*
* @see Mozilla.UITour.cycleThemes
* @see Mozilla.UITour.resetTheme
*
* @param {Object} theme - Theme object format expected by `LightweightThemeManager.previewTheme`
* @example
* var theme = {
*
* "iconURL": "https://addons.mozilla.org/_files/…/preview_small.jpg",
* "headerURL": "https://addons.mozilla.org/_files/….jpg",
* "name": "My cool theme",
* "author": "Mozilla",
* "footer": "https://addons.mozilla.org/_files/….jpg",
* "previewURL": "https://addons.mozilla.org/_files/…/preview.jpg",
* "updateURL": "https://versioncheck.addons.mozilla.org/…",
* "accentcolor": "#000000",
* "header": "https://addons.mozilla.org/_files/….jpg",
* "version": "1.0",
* "detailURL": "https://addons.mozilla.org/firefox/addon/…",
* "textcolor": "#ffffff",
* "id": "18066",
* "description": "My awesome theme.",
*
* };
*
* Mozilla.UITour.previewTheme(theme);
*/
Mozilla.UITour.previewTheme = function(theme) {
_stopCyclingThemes();
_sendEvent("previewTheme", {
theme: JSON.stringify(theme),
});
};
/**
* Stop previewing and cycling themes, returning to the user's previous theme.
* @see Mozilla.UITour.cycleThemes
* @see Mozilla.UITour.previewTheme
*/
Mozilla.UITour.resetTheme = function() {
_stopCyclingThemes();
_sendEvent("resetTheme");
};
/**
* Cycle between an array of themes using the given delay.
*
* @see Mozilla.UITour.previewTheme
* @see Mozilla.UITour.resetTheme
*
* @param {Object[]} themes - Array of themes
* @param {Number} [delay=Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY]
* - Time in milliseconds between rotating themes
* @param {Function} callback - Function called at each rotation
*/
Mozilla.UITour.cycleThemes = function(themes, delay, callback) {
_stopCyclingThemes();
if (!delay) {
delay = Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY;
}
function nextTheme() {
var theme = themes.shift();
themes.push(theme);
_sendEvent("previewTheme", {
theme: JSON.stringify(theme),
state: true,
});
callback(theme);
}
themeIntervalId = setInterval(nextTheme, delay);
nextTheme();
};
/** /**
* @typedef {String} Mozilla.UITour.MenuName * @typedef {String} Mozilla.UITour.MenuName
* Valid values:<ul> * Valid values:<ul>

Просмотреть файл

@ -17,8 +17,6 @@ ChromeUtils.defineModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm"); "resource:///modules/CustomizableUI.jsm");
ChromeUtils.defineModuleGetter(this, "FxAccounts", ChromeUtils.defineModuleGetter(this, "FxAccounts",
"resource://gre/modules/FxAccounts.jsm"); "resource://gre/modules/FxAccounts.jsm");
ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
"resource://gre/modules/LightweightThemeManager.jsm");
ChromeUtils.defineModuleGetter(this, "PageActions", ChromeUtils.defineModuleGetter(this, "PageActions",
"resource:///modules/PageActions.jsm"); "resource:///modules/PageActions.jsm");
ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils", ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
@ -395,16 +393,6 @@ var UITour = {
break; break;
} }
case "previewTheme": {
this.previewTheme(data.theme);
break;
}
case "resetTheme": {
this.resetTheme();
break;
}
case "showMenu": { case "showMenu": {
this.noautohideMenus.add(data.name); this.noautohideMenus.add(data.name);
this.showMenu(window, data.name, () => { this.showMenu(window, data.name, () => {
@ -780,7 +768,6 @@ var UITour = {
} }
this.noautohideMenus.clear(); this.noautohideMenus.clear();
this.resetTheme();
// If there are no more tour tabs left in the window, teardown the tour for the whole window. // If there are no more tour tabs left in the window, teardown the tour for the whole window.
if (!openTourBrowsers || openTourBrowsers.size == 0) { if (!openTourBrowsers || openTourBrowsers.size == 0) {
@ -982,17 +969,6 @@ var UITour = {
return promise; return promise;
}, },
previewTheme(aTheme) {
let origin = Services.prefs.getCharPref("browser.uitour.themeOrigin");
let data = LightweightThemeManager.parseTheme(aTheme, origin);
if (data)
LightweightThemeManager.previewTheme(data);
},
resetTheme() {
LightweightThemeManager.resetPreview();
},
/** /**
* The node to which a highlight or notification(-popup) is anchored is sometimes * The node to which a highlight or notification(-popup) is anchored is sometimes
* obscured because it may be inside an overflow menu. This function should figure * obscured because it may be inside an overflow menu. This function should figure

Просмотреть файл

@ -973,7 +973,6 @@ you can use these alternative items. Otherwise, their values should be empty. -
<!ENTITY customizeMode.toolbars2 "Toolbars"> <!ENTITY customizeMode.toolbars2 "Toolbars">
<!ENTITY customizeMode.lwthemes "Themes"> <!ENTITY customizeMode.lwthemes "Themes">
<!ENTITY customizeMode.lwthemes.myThemes "My Themes"> <!ENTITY customizeMode.lwthemes.myThemes "My Themes">
<!ENTITY customizeMode.lwthemes.recommended "Recommended">
<!ENTITY customizeMode.lwthemes.menuManage "Manage"> <!ENTITY customizeMode.lwthemes.menuManage "Manage">
<!ENTITY customizeMode.lwthemes.menuManage.accessKey "M"> <!ENTITY customizeMode.lwthemes.menuManage.accessKey "M">
<!ENTITY customizeMode.lwthemes.menuGetMore "Get More Themes"> <!ENTITY customizeMode.lwthemes.menuGetMore "Get More Themes">

Просмотреть файл

@ -1,12 +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/.
lightweightThemes.recommended-1.name=A Web Browser Renaissance
lightweightThemes.recommended-1.description=A Web Browser Renaissance is (C) Sean.Martell. Available under CC-BY-SA. No warranty.
lightweightThemes.recommended-2.name=Space Fantasy
lightweightThemes.recommended-2.description=Space Fantasy is (C) fx5800p. Available under CC-BY-SA. No warranty.
lightweightThemes.recommended-4.name=Pastel Gradient
lightweightThemes.recommended-4.description=Pastel Gradient is (C) darrinhenein. Available under CC-BY. No warranty.

Просмотреть файл

@ -20,7 +20,6 @@
locale/browser/baseMenuOverlay.dtd (%chrome/browser/baseMenuOverlay.dtd) locale/browser/baseMenuOverlay.dtd (%chrome/browser/baseMenuOverlay.dtd)
locale/browser/browser.properties (%chrome/browser/browser.properties) locale/browser/browser.properties (%chrome/browser/browser.properties)
locale/browser/customizableui/customizableWidgets.properties (%chrome/browser/customizableui/customizableWidgets.properties) locale/browser/customizableui/customizableWidgets.properties (%chrome/browser/customizableui/customizableWidgets.properties)
locale/browser/lightweightThemes.properties (%chrome/browser/lightweightThemes.properties)
locale/browser/uiDensity.properties (%chrome/browser/uiDensity.properties) locale/browser/uiDensity.properties (%chrome/browser/uiDensity.properties)
locale/browser/pocket.properties (%chrome/browser/pocket.properties) locale/browser/pocket.properties (%chrome/browser/pocket.properties)
locale/browser/search.properties (%chrome/browser/search.properties) locale/browser/search.properties (%chrome/browser/search.properties)

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,175 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_dom_asmjscache_asmjscache_h
#define mozilla_dom_asmjscache_asmjscache_h
#include "ipc/IPCMessageUtils.h"
#include "js/TypeDecls.h"
#include "js/Vector.h"
#include "jsapi.h"
class nsIPrincipal;
namespace JS {
enum AsmJSCacheResult {
AsmJSCache_Success,
AsmJSCache_MIN = AsmJSCache_Success,
AsmJSCache_ModuleTooSmall,
AsmJSCache_SynchronousScript,
AsmJSCache_QuotaExceeded,
AsmJSCache_StorageInitFailure,
AsmJSCache_Disabled_Internal,
AsmJSCache_Disabled_ShellFlags,
AsmJSCache_Disabled_JitInspector,
AsmJSCache_InternalError,
AsmJSCache_Disabled_PrivateBrowsing,
AsmJSCache_LIMIT
};
} // namespace JS
namespace mozilla {
namespace ipc {
class PrincipalInfo;
} // namespace ipc
namespace dom {
namespace quota {
class Client;
} // namespace quota
namespace asmjscache {
class PAsmJSCacheEntryChild;
class PAsmJSCacheEntryParent;
enum OpenMode { eOpenForRead, eOpenForWrite, NUM_OPEN_MODES };
// Each origin stores a fixed size (kNumEntries) LRU cache of compiled asm.js
// modules. Each compiled asm.js module is stored in a separate file with one
// extra metadata file that stores the LRU cache and enough information for a
// client to pick which cached module's file to open.
struct Metadata {
static const unsigned kNumEntries = 16;
static const unsigned kLastEntry = kNumEntries - 1;
struct Entry {
uint32_t mFastHash;
uint32_t mNumChars;
uint32_t mFullHash;
unsigned mModuleIndex;
void clear() {
mFastHash = -1;
mNumChars = -1;
mFullHash = -1;
}
};
Entry mEntries[kNumEntries];
};
// Parameters specific to opening a cache entry for writing
struct WriteParams {
int64_t mSize;
int64_t mFastHash;
int64_t mNumChars;
int64_t mFullHash;
WriteParams() : mSize(0), mFastHash(0), mNumChars(0), mFullHash(0) {}
};
// Parameters specific to opening a cache entry for reading
struct ReadParams {
const char16_t* mBegin;
const char16_t* mLimit;
ReadParams() : mBegin(nullptr), mLimit(nullptr) {}
};
// Implementation of AsmJSCacheOps, installed for the main JSRuntime by
// nsJSEnvironment.cpp and DOM Worker JSRuntimes in RuntimeService.cpp.
//
// The Open* functions cannot be called directly from AsmJSCacheOps: they take
// an nsIPrincipal as the first argument instead of a Handle<JSObject*>. The
// caller must map the object to an nsIPrincipal.
//
// These methods may be called off the main thread and guarantee not to
// access the given aPrincipal except on the main thread. In exchange, the
// caller must ensure the given principal is alive from when OpenEntryForX is
// called to when CloseEntryForX returns.
bool OpenEntryForRead(nsIPrincipal* aPrincipal, const char16_t* aBegin,
const char16_t* aLimit, size_t* aSize,
const uint8_t** aMemory, intptr_t* aHandle);
void CloseEntryForRead(size_t aSize, const uint8_t* aMemory, intptr_t aHandle);
JS::AsmJSCacheResult OpenEntryForWrite(nsIPrincipal* aPrincipal,
const char16_t* aBegin,
const char16_t* aEnd, size_t aSize,
uint8_t** aMemory, intptr_t* aHandle);
void CloseEntryForWrite(size_t aSize, uint8_t* aMemory, intptr_t aHandle);
// Called from QuotaManager.cpp:
quota::Client* CreateClient();
// Called from ipc/ContentParent.cpp:
PAsmJSCacheEntryParent* AllocEntryParent(
OpenMode aOpenMode, WriteParams aWriteParams,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
void DeallocEntryParent(PAsmJSCacheEntryParent* aActor);
// Called from ipc/ContentChild.cpp:
void DeallocEntryChild(PAsmJSCacheEntryChild* aActor);
} // namespace asmjscache
} // namespace dom
} // namespace mozilla
namespace IPC {
template <>
struct ParamTraits<mozilla::dom::asmjscache::OpenMode>
: public ContiguousEnumSerializer<
mozilla::dom::asmjscache::OpenMode,
mozilla::dom::asmjscache::eOpenForRead,
mozilla::dom::asmjscache::NUM_OPEN_MODES> {};
template <>
struct ParamTraits<mozilla::dom::asmjscache::Metadata> {
typedef mozilla::dom::asmjscache::Metadata paramType;
static void Write(Message* aMsg, const paramType& aParam);
static bool Read(const Message* aMsg, PickleIterator* aIter,
paramType* aResult);
static void Log(const paramType& aParam, std::wstring* aLog);
};
template <>
struct ParamTraits<mozilla::dom::asmjscache::WriteParams> {
typedef mozilla::dom::asmjscache::WriteParams paramType;
static void Write(Message* aMsg, const paramType& aParam);
static bool Read(const Message* aMsg, PickleIterator* aIter,
paramType* aResult);
static void Log(const paramType& aParam, std::wstring* aLog);
};
template <>
struct ParamTraits<JS::AsmJSCacheResult>
: public ContiguousEnumSerializer<JS::AsmJSCacheResult, JS::AsmJSCache_MIN,
JS::AsmJSCache_LIMIT> {};
} // namespace IPC
#endif // mozilla_dom_asmjscache_asmjscache_h

Просмотреть файл

@ -1,53 +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/. */
include protocol PBackground;
using mozilla::dom::asmjscache::Metadata from "mozilla/dom/asmjscache/AsmJSCache.h";
using JS::AsmJSCacheResult from "mozilla/dom/asmjscache/AsmJSCache.h";
namespace mozilla {
namespace dom {
namespace asmjscache {
union OpenMetadataForReadResponse
{
AsmJSCacheResult;
uint32_t;
};
protocol PAsmJSCacheEntry
{
manager PBackground;
// When the cache is opened to read, the parent process sends over the
// origin's Metadata so the child process can select the cache entry to open
// (based on hash) and notify the parent (via SelectCacheFileToRead).
child:
async OnOpenMetadataForRead(Metadata metadata);
parent:
async SelectCacheFileToRead(OpenMetadataForReadResponse response);
child:
// Once the cache file has been opened, the child is notified and sent an
// open file descriptor.
async OnOpenCacheFile(int64_t fileSize, FileDescriptor fileDesc);
parent:
// When the child process is done with the cache entry, the parent process
// is notified (via Close).
async Close();
child:
// When there's an error during the opening phase, the child process is
// notified (via __delete__) and sent an error result.
// When the parent process receives the Close message, it closes the cache
// entry on the parent side and the child is notified (via __delete__).
// The protocol is destroyed in both cases.
async __delete__(AsmJSCacheResult result);
};
} // namespace asmjscache
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -1,26 +0,0 @@
# -*- Mode: python; 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/.
with Files("**"):
BUG_COMPONENT = ("Core", "JavaScript Engine")
EXPORTS.mozilla.dom.asmjscache += [
'AsmJSCache.h'
]
SOURCES += [
'AsmJSCache.cpp'
]
IPDL_SOURCES += [
'PAsmJSCacheEntry.ipdl'
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
MOCHITEST_MANIFESTS += ['test/mochitest.ini']

Просмотреть файл

@ -1,7 +0,0 @@
"use strict";
module.exports = {
"extends": [
"plugin:mozilla/mochitest-test",
],
};

Просмотреть файл

@ -1,78 +0,0 @@
/* globals jsFuns:false, complete:false */
function f1() { "use asm"; function g() {} return g; }
if (this.jsFuns) {
ok(jsFuns.isAsmJSModule(f1), "f1 is an asm.js module");
ok(jsFuns.isAsmJSFunction(f1()), "f1.g is an asm.js function");
}
function f2(stdlib, foreign, buffer) {
"use asm";
var i32 = new stdlib.Int32Array(buffer);
function main(n) {
n = n|0;
var i = 0, sum = 0;
for (; (i|0) < (n|0); i=(i + 1)|0)
sum = (sum + (i32[(i << 2) >> 2]|0))|0;
return sum|0;
}
return main;
}
if (this.jsFuns)
ok(jsFuns.isAsmJSModule(f2), "f2 is an asm.js module");
var i32 = new Int32Array(16384); // Smallest allowed buffer size is 64KBy
for (let i = 0; i < i32.length; i++)
i32[i] = i;
var f2Main = f2(this, null, i32.buffer);
if (this.jsFuns)
ok(jsFuns.isAsmJSFunction(f2Main), "f2.main is an asm.js function");
if (f2Main(4) !== 6)
throw new Error("f2Main(4)");
if (f2Main(100) !== 4950)
throw new Error("f2.main(100)");
var sum = (((i32.length - 1) * i32.length) / 2);
if (f2Main(i32.length) !== sum)
throw new Error(`f2.main(${i32.length})`);
if (f2Main(i32.length + 100) !== sum)
throw new Error(`f2.main(${i32.length})`);
/* eslint-disable no-unused-vars,no-shadow */
function f3(stdlib, foreign, buffer) {
"use asm";
var doneFunc = foreign.done;
var i32 = new stdlib.Int32Array(buffer);
function main() {
var i = 0;
var total = 0;
while (1) {
for (i = 0; (i|0) < 1000; i=(i + 1)|0)
total = (total + i)|0;
if (doneFunc(total|0)|0)
break;
}
return total|0;
}
return main;
}
/* eslint-enable no-unused-vars,no-shadow */
var begin;
var lastSum;
function done(sumInner) {
if (sumInner !== ((lastSum + 499500)|0))
throw new Error(`bad sum: ${sumInner}, ${lastSum}, ${((lastSum + 499500)|0)}`);
lastSum = sumInner;
return (Date.now() - begin) > 3000;
}
var f3Main = f3(this, {done}, i32.buffer);
if (this.jsFuns)
ok(jsFuns.isAsmJSFunction(f3Main), "f3.main is an asm.js function");
begin = Date.now();
lastSum = 0;
if (f3Main() !== lastSum)
throw new Error("f3.main()");
if (!this.jsFuns)
postMessage("ok");
else
complete();

Просмотреть файл

@ -1,14 +0,0 @@
[DEFAULT]
support-files =
file_slow.js
[test_cachingBasic.html]
disabled = bug 1520931
[test_workers.html]
disabled = bug 1520931
[test_cachingMulti.html]
disabled = bug 1520931
[test_slow.html]
# bug 929498
skip-if = os == 'android'
disabled = bug 1520931

Просмотреть файл

@ -1,70 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=929236
-->
<head>
<meta charset="utf-8">
<title>asm.js browser tests</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=929236">asm.js browser tests</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script>
var jsFuns = SpecialPowers.Cu.getJSTestingFunctions();
var code = "function f() { 'use asm';\n";
for (var i = 0; i < 5000; i++)
code += "function g" + i + "() { return " + i + "}\n";
code += "return g42 }\n";
code += "ok(jsFuns.isAsmJSModule(f), 'f is an asm.js module')\n";
code += "var g42 = f();\n";
code += "ok(jsFuns.isAsmJSFunction(g42), 'g42 is an asm.js function')\n";
code += "ok(g42() === 42, 'g42 returns the correct result')\n";
code += "finishedEvalAsync(f);";
ok(code.length > 100000, "code is long enough to definitely hit the cache");
function evalAsync(codeInner) {
var blob = new Blob([codeInner], {type: "application/javascript"});
var script = document.createElement("script");
script.src = URL.createObjectURL(blob);
document.body.appendChild(script);
}
var state = 0;
function finishedEvalAsync(module) {
switch (state) {
case 0:
state++;
evalAsync(code);
break;
case 1:
ok(jsFuns.isAsmJSModule(module), "module");
SimpleTest.finish();
break;
default:
throw new Error("huh?");
}
}
function runTest() {
// generate a big ol asm.js module and compile it async so that we can hit
// the asm.js cache.
SimpleTest.waitForExplicitFinish();
evalAsync(code);
}
if (!jsFuns.isAsmJSCompilationAvailable()) {
ok(true, "isAsmJSCompilationAvailable is false, skipping this test!");
} else {
runTest();
}
</script>
</body>
</html>

Просмотреть файл

@ -1,81 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=944821
-->
<head>
<meta charset="utf-8">
<title>asm.js browser tests</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=944821">asm.js browser tests</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script>
var jsFuns = SpecialPowers.Cu.getJSTestingFunctions();
// generate four slightly different big asm.js modules and compile them async
// so that we can hit the asm.js cache.
var code = "function f() { 'use asm';\n";
for (let i = 0; i < 5000; i++)
code += "function g" + i + "() { return " + i + "}\n";
ok(code.length > 100000, "code is long enough to definitely hit the cache");
const N = 4;
var codes = [];
for (let i = 0; i < N; i++) {
var code2 = code;
code2 += "return g" + i + ";\n";
code2 += "}\n";
code2 += "ok(jsFuns.isAsmJSModule(f), 'f is an asm.js module')\n";
code2 += "var gX = f();\n";
code2 += "ok(jsFuns.isAsmJSFunction(gX), 'gX is an asm.js function')\n";
code2 += "ok(gX() === " + i + ", 'gX returns the correct result')\n";
code2 += "finishedEvalAsync();\n";
codes.push(code2);
}
function evalAsync(codeInner) {
var blob = new Blob([codeInner], {type: "application/javascript"});
var script = document.createElement("script");
script.src = URL.createObjectURL(blob);
document.body.appendChild(script);
}
var finishedCount = 0;
function finishedEvalAsync() {
finishedCount++;
if (finishedCount < 1 || finishedCount > 2 * N) {
throw new Error("Huh?!");
} else if (finishedCount == N) {
for (let i = 0; i < N; i++)
evalAsync(codes[i]);
} else if (finishedCount == 2 * N) {
SimpleTest.finish();
}
}
function runTest() {
for (let i = 0; i < N; i++)
evalAsync(codes[i]);
SimpleTest.waitForExplicitFinish();
}
if (!jsFuns.isAsmJSCompilationAvailable()) {
ok(true, "isAsmJSCompilationAvailable is false, skipping this test!");
} else {
runTest();
}
</script>
</body>
</html>

Просмотреть файл

@ -1,48 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=854209
-->
<head>
<meta charset="utf-8">
<title>asm.js browser tests</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=854209">asm.js browser tests</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script>
var jsFuns = SpecialPowers.Cu.getJSTestingFunctions();
var completed = 0;
function complete() {
if (++completed == 2)
SimpleTest.finish();
}
if (!jsFuns.isAsmJSCompilationAvailable()) {
ok(true, "isAsmJSCompilationAvailable is false, skipping this test!");
} else {
var script = document.createElement("script");
script.src = "http://mochi.test:8888/tests/dom/asmjscache/test/file_slow.js";
document.body.appendChild(script);
var w = new Worker("http://mochi.test:8888/tests/dom/asmjscache/test/file_slow.js");
w.onmessage = function(e) {
ok(e.data === "ok", "Worker asm.js tests");
complete();
};
SimpleTest.waitForExplicitFinish();
}
</script>
<script>
</script>
</body>
</html>

Просмотреть файл

@ -1,74 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=941830
-->
<head>
<meta charset="utf-8">
<title>asm.js browser tests</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=941830">asm.js browser tests</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script>
var jsFuns = SpecialPowers.Cu.getJSTestingFunctions();
function runTest() {
var asmjsCode = "function f() { 'use asm';";
for (var i = 0; i < 5000; i++)
asmjsCode += "function g" + i + "() { return " + i + "}";
asmjsCode += "return g42 }";
ok(asmjsCode.length > 100000, "code is long enough to definitely hit the cache");
var workerCode = asmjsCode;
workerCode += "if (f()() !== 42) postMessage('fail'); else postMessage('ok');";
workerCode = 'var code = "' + workerCode + '"; eval(code); eval(code)';
var workerBlob = new Blob([workerCode], {type: "application/javascript"});
var mainCode = asmjsCode;
mainCode += "ok(jsFuns.isAsmJSModule(f), 'f is a module')\n";
mainCode += "var g42 = f();\n";
mainCode += "ok(jsFuns.isAsmJSFunction(g42), 'g42 is an asm.js function');\n";
mainCode += "ok(g42() === 42, 'g42 returns the correct result');\n";
mainCode += "SimpleTest.finish();\n";
var mainBlob = new Blob([mainCode], {type: "application/javascript"});
var w = new Worker(URL.createObjectURL(workerBlob));
var received = 0;
w.onmessage = function(e) {
switch (received) {
case 0:
ok(e.data === "ok", "Received first message");
received = 1;
break;
case 1:
ok(e.data === "ok", "Received second message");
received = 2;
var script = document.createElement("script");
script.src = URL.createObjectURL(mainBlob);
document.body.appendChild(script);
break;
default:
throw new Error("Huh?");
}
};
SimpleTest.waitForExplicitFinish();
}
if (!jsFuns.isAsmJSCompilationAvailable()) {
ok(true, "isAsmJSCompilationAvailable is false, skipping this test!");
} else {
runTest();
}
</script>
</body>
</html>

Просмотреть файл

@ -261,6 +261,17 @@ WebRenderBridgeChild* nsDOMWindowUtils::GetWebRenderBridge() {
return nullptr; return nullptr;
} }
CompositorBridgeChild* nsDOMWindowUtils::GetCompositorBridge() {
if (nsIWidget* widget = GetWidget()) {
if (LayerManager* lm = widget->GetLayerManager()) {
if (CompositorBridgeChild* cbc = lm->GetCompositorBridgeChild()) {
return cbc;
}
}
}
return nullptr;
}
NS_IMETHODIMP NS_IMETHODIMP
nsDOMWindowUtils::SyncFlushCompositor() { nsDOMWindowUtils::SyncFlushCompositor() {
if (nsIWidget* widget = GetWidget()) { if (nsIWidget* widget = GetWidget()) {
@ -4099,6 +4110,18 @@ nsDOMWindowUtils::WrCapture() {
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
nsDOMWindowUtils::SetCompositionRecording(bool aValue) {
if (CompositorBridgeChild* cbc = GetCompositorBridge()) {
if (aValue) {
cbc->SendBeginRecording(TimeStamp::Now());
} else {
cbc->SendEndRecording();
}
}
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
nsDOMWindowUtils::SetSystemFont(const nsACString& aFontName) { nsDOMWindowUtils::SetSystemFont(const nsACString& aFontName) {
nsIWidget* widget = GetWidget(); nsIWidget* widget = GetWidget();

Просмотреть файл

@ -82,6 +82,7 @@ class nsDOMWindowUtils final : public nsIDOMWindowUtils,
mozilla::dom::Document* GetDocument(); mozilla::dom::Document* GetDocument();
mozilla::layers::LayerTransactionChild* GetLayerTransaction(); mozilla::layers::LayerTransactionChild* GetLayerTransaction();
mozilla::layers::WebRenderBridgeChild* GetWebRenderBridge(); mozilla::layers::WebRenderBridgeChild* GetWebRenderBridge();
mozilla::layers::CompositorBridgeChild* GetCompositorBridge();
// Until callers are annotated. // Until callers are annotated.
MOZ_CAN_RUN_SCRIPT MOZ_CAN_RUN_SCRIPT

Просмотреть файл

@ -15694,8 +15694,18 @@ QuotaClient::~QuotaClient() {
nsresult QuotaClient::AsyncDeleteFile(FileManager* aFileManager, nsresult QuotaClient::AsyncDeleteFile(FileManager* aFileManager,
int64_t aFileId) { int64_t aFileId) {
AssertIsOnBackgroundThread(); AssertIsOnBackgroundThread();
MOZ_ASSERT(mDeleteTimer);
if (mShutdownRequested) {
// Whoops! We want to delete an IndexedDB disk-backed File but it's too late
// to actually delete the file! This means we're going to "leak" the file
// and leave it around when we shouldn't! (The file will stay around until
// next storage initialization is triggered when the app is started again).
// Fixing this is tracked by bug 1539377.
return NS_OK;
}
MOZ_ASSERT(mDeleteTimer);
MOZ_ALWAYS_SUCCEEDS(mDeleteTimer->Cancel()); MOZ_ALWAYS_SUCCEEDS(mDeleteTimer->Cancel());
nsresult rv = mDeleteTimer->InitWithNamedFuncCallback( nsresult rv = mDeleteTimer->InitWithNamedFuncCallback(

Просмотреть файл

@ -1932,6 +1932,11 @@ interface nsIDOMWindowUtils : nsISupports {
*/ */
void wrCapture(); void wrCapture();
/**
* Toggle recording of composition on and off.
*/
void setCompositionRecording(in boolean aValue);
/** /**
* Returns whether the document we're associated to has recorded a given CSS * Returns whether the document we're associated to has recorded a given CSS
* property via the use counter mechanism. * property via the use counter mechanism.

Просмотреть файл

@ -56,7 +56,6 @@ DIRS += [
'grid', 'grid',
'html', 'html',
'jsurl', 'jsurl',
'asmjscache',
'mathml', 'mathml',
'media', 'media',
'midi', 'midi',

Просмотреть файл

@ -33,7 +33,6 @@
#include "mozilla/CondVar.h" #include "mozilla/CondVar.h"
#include "mozilla/Telemetry.h" #include "mozilla/Telemetry.h"
#include "mozilla/dom/PContent.h" #include "mozilla/dom/PContent.h"
#include "mozilla/dom/asmjscache/AsmJSCache.h"
#include "mozilla/dom/cache/QuotaClient.h" #include "mozilla/dom/cache/QuotaClient.h"
#include "mozilla/dom/indexedDB/ActorsParent.h" #include "mozilla/dom/indexedDB/ActorsParent.h"
#include "mozilla/dom/localstorage/ActorsParent.h" #include "mozilla/dom/localstorage/ActorsParent.h"
@ -1375,7 +1374,7 @@ void ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr) {
namespace { namespace {
nsString gBaseDirPath; StaticAutoPtr<nsString> gBaseDirPath;
#ifdef DEBUG #ifdef DEBUG
bool gQuotaManagerInitialized = false; bool gQuotaManagerInitialized = false;
@ -2499,6 +2498,14 @@ QuotaManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
nsresult rv; nsresult rv;
if (!strcmp(aTopic, kProfileDoChangeTopic)) { if (!strcmp(aTopic, kProfileDoChangeTopic)) {
if (NS_WARN_IF(gBaseDirPath)) {
NS_WARNING("profile-before-change-qm must precede repeated "
"profile-do-change!");
return NS_OK;
}
gBaseDirPath = new nsString();
nsCOMPtr<nsIFile> baseDir; nsCOMPtr<nsIFile> baseDir;
rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR, rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR,
getter_AddRefs(baseDir)); getter_AddRefs(baseDir));
@ -2510,7 +2517,7 @@ QuotaManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
return rv; return rv;
} }
rv = baseDir->GetPath(gBaseDirPath); rv = baseDir->GetPath(*gBaseDirPath);
if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_WARN_IF(NS_FAILED(rv))) {
return rv; return rv;
} }
@ -2519,6 +2526,11 @@ QuotaManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
} }
if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) { if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) {
if (NS_WARN_IF(!gBaseDirPath)) {
NS_WARNING("profile-do-change must precede profile-before-change-qm!");
return NS_OK;
}
// mPendingProfileChange is our re-entrancy guard (the nested event loop // mPendingProfileChange is our re-entrancy guard (the nested event loop
// below may cause re-entrancy). // below may cause re-entrancy).
if (mPendingProfileChange) { if (mPendingProfileChange) {
@ -2542,7 +2554,7 @@ QuotaManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return mShutdownComplete; })); MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return mShutdownComplete; }));
gBaseDirPath.Truncate(); gBaseDirPath = nullptr;
return NS_OK; return NS_OK;
} }
@ -2905,16 +2917,19 @@ void QuotaManager::GetOrCreate(nsIRunnable* aCallback,
AssertIsOnBackgroundThread(); AssertIsOnBackgroundThread();
if (IsShuttingDown()) { if (IsShuttingDown()) {
MOZ_ASSERT(false, "Calling GetOrCreate() after shutdown!"); MOZ_ASSERT(false, "Calling QuotaManager::GetOrCreate() after shutdown!");
return; return;
} }
if (gInstance || gCreateFailed) { if (NS_WARN_IF(!gBaseDirPath)) {
NS_WARNING("profile-do-change must precede QuotaManager::GetOrCreate()");
MOZ_ASSERT(!gInstance);
} else if (gInstance || gCreateFailed) {
MOZ_ASSERT_IF(gCreateFailed, !gInstance); MOZ_ASSERT_IF(gCreateFailed, !gInstance);
} else { } else {
RefPtr<QuotaManager> manager = new QuotaManager(); RefPtr<QuotaManager> manager = new QuotaManager();
nsresult rv = manager->Init(gBaseDirPath); nsresult rv = manager->Init(*gBaseDirPath);
if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_WARN_IF(NS_FAILED(rv))) {
gCreateFailed = true; gCreateFailed = true;
} else { } else {
@ -3286,9 +3301,8 @@ nsresult QuotaManager::Init(const nsAString& aBasePath) {
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
static_assert(Client::IDB == 0 && Client::ASMJS == 1 && static_assert(Client::IDB == 0 && Client::DOMCACHE == 1 && Client::SDB == 2 &&
Client::DOMCACHE == 2 && Client::SDB == 3 && Client::LS == 3 && Client::TYPE_MAX == 4,
Client::LS == 4 && Client::TYPE_MAX == 5,
"Fix the registration!"); "Fix the registration!");
MOZ_ASSERT(mClients.Capacity() == Client::TYPE_MAX, MOZ_ASSERT(mClients.Capacity() == Client::TYPE_MAX,
@ -3296,7 +3310,6 @@ nsresult QuotaManager::Init(const nsAString& aBasePath) {
// Register clients. // Register clients.
mClients.AppendElement(indexedDB::CreateQuotaClient()); mClients.AppendElement(indexedDB::CreateQuotaClient());
mClients.AppendElement(asmjscache::CreateClient());
mClients.AppendElement(cache::CreateQuotaClient()); mClients.AppendElement(cache::CreateQuotaClient());
mClients.AppendElement(simpledb::CreateQuotaClient()); mClients.AppendElement(simpledb::CreateQuotaClient());
if (NextGenLocalStorageEnabled()) { if (NextGenLocalStorageEnabled()) {

Просмотреть файл

@ -43,7 +43,6 @@ class Client {
enum Type { enum Type {
IDB = 0, IDB = 0,
// APPCACHE, // APPCACHE,
ASMJS,
DOMCACHE, DOMCACHE,
SDB, SDB,
LS, LS,

Просмотреть файл

@ -0,0 +1,64 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "CompositionRecorder.h"
#include "gfxUtils.h"
#include "mozilla/gfx/2D.h"
#include "gfxPrefs.h"
#include <ctime>
#include <iomanip>
#include "stdio.h"
#ifdef XP_WIN
# include "direct.h"
#else
# include <sys/types.h>
# include "sys/stat.h"
#endif
using namespace mozilla::gfx;
namespace mozilla {
namespace layers {
CompositionRecorder::CompositionRecorder(TimeStamp aRecordingStart)
: mRecordingStart(aRecordingStart) {}
CompositionRecorder::~CompositionRecorder() {}
void CompositionRecorder::RecordFrame(RecordedFrame* aFrame) {
mCollectedFrames.AppendElement(aFrame);
}
void CompositionRecorder::WriteCollectedFrames() {
std::time_t t = std::time(nullptr);
std::tm tm = *std::localtime(&t);
std::stringstream str;
str << gfxPrefs::LayersWindowRecordingPath() << "windowrecording-"
<< std::put_time(&tm, "%Y%m%d%H%M%S");
#ifdef XP_WIN
_mkdir(str.str().c_str());
#else
mkdir(str.str().c_str(), 0777);
#endif
uint32_t i = 1;
for (RefPtr<RecordedFrame>& frame : mCollectedFrames) {
RefPtr<DataSourceSurface> surf = frame->GetSourceSurface();
std::stringstream filename;
filename << str.str() << "/frame-" << i << "-"
<< uint32_t(
(frame->GetTimeStamp() - mRecordingStart).ToMilliseconds())
<< ".png";
gfxUtils::WriteAsPNG(surf, filename.str().c_str());
i++;
}
mCollectedFrames.Clear();
}
} // namespace layers
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,60 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_layers_CompositionRecorder_h
#define mozilla_layers_CompositionRecorder_h
#include "mozilla/RefPtr.h"
#include "mozilla/TimeStamp.h"
#include "nsISupportsImpl.h"
#include "nsTArray.h"
namespace mozilla {
namespace gfx {
class DataSourceSurface;
}
namespace layers {
class RecordedFrame {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RecordedFrame)
// The resulting DataSourceSurface must not be kept alive beyond the lifetime
// of the RecordedFrame object, since it may refer to data owned by the frame.
virtual already_AddRefed<gfx::DataSourceSurface> GetSourceSurface() = 0;
TimeStamp GetTimeStamp() { return mTimeStamp; }
protected:
virtual ~RecordedFrame() {}
RecordedFrame(const TimeStamp& aTimeStamp) : mTimeStamp(aTimeStamp) {}
private:
TimeStamp mTimeStamp;
};
/**
*
*/
class CompositionRecorder final {
public:
explicit CompositionRecorder(TimeStamp aRecordingStart);
~CompositionRecorder();
void RecordFrame(RecordedFrame* aFrame);
void WriteCollectedFrames();
private:
nsTArray<RefPtr<RecordedFrame>> mCollectedFrames;
TimeStamp mRecordingStart;
};
} // namespace layers
} // namespace mozilla
#endif // mozilla_layers_ProfilerScreenshots_h

Просмотреть файл

@ -415,7 +415,8 @@ MOZ_DEFINE_ENUM_CLASS_WITH_BASE(ScrollDirection, uint32_t, (
MOZ_DEFINE_ENUM_CLASS_WITH_BASE(CompositionPayloadType, uint8_t, ( MOZ_DEFINE_ENUM_CLASS_WITH_BASE(CompositionPayloadType, uint8_t, (
eKeyPress, eKeyPress,
eAPZScroll, eAPZScroll,
eAPZPinchZoom eAPZPinchZoom,
eContentPaint
)); ));
// clang-format on // clang-format on

Просмотреть файл

@ -9,6 +9,7 @@
#include <stdint.h> // for uint64_t, uint32_t #include <stdint.h> // for uint64_t, uint32_t
#include "mozilla/layers/APZTypes.h"
#include "mozilla/layers/LayersTypes.h" // for TouchBehaviorFlags #include "mozilla/layers/LayersTypes.h" // for TouchBehaviorFlags
#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid, etc #include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid, etc
#include "mozilla/layers/ZoomConstraints.h" // for ZoomConstraints #include "mozilla/layers/ZoomConstraints.h" // for ZoomConstraints

Просмотреть файл

@ -371,6 +371,9 @@ void ClientLayerManager::EndTransaction(DrawPaintedLayerCallback aCallback,
mWidget->PrepareWindowEffects(); mWidget->PrepareWindowEffects();
} }
EndTransactionInternal(aCallback, aCallbackData, aFlags); EndTransactionInternal(aCallback, aCallbackData, aFlags);
if (XRE_IsContentProcess()) {
RegisterPayload({CompositionPayloadType::eContentPaint, TimeStamp::Now()});
}
ForwardTransaction(!(aFlags & END_NO_REMOTE_COMPOSITE)); ForwardTransaction(!(aFlags & END_NO_REMOTE_COMPOSITE));
if (mRepeatTransaction) { if (mRepeatTransaction) {

Просмотреть файл

@ -53,6 +53,7 @@ namespace layers {
class CanvasLayerComposite; class CanvasLayerComposite;
class ColorLayerComposite; class ColorLayerComposite;
class Compositor; class Compositor;
class CompositionRecorder;
class ContainerLayerComposite; class ContainerLayerComposite;
class Diagnostics; class Diagnostics;
struct EffectChain; struct EffectChain;
@ -200,6 +201,10 @@ class HostLayerManager : public LayerManager {
mCompositorBridgeID = aID; mCompositorBridgeID = aID;
} }
void SetCompositionRecorder(CompositionRecorder* aRecorder) {
mCompositionRecorder = aRecorder;
}
protected: protected:
bool mDebugOverlayWantsNextFrame; bool mDebugOverlayWantsNextFrame;
nsTArray<ImageCompositeNotificationInfo> mImageCompositeNotifications; nsTArray<ImageCompositeNotificationInfo> mImageCompositeNotifications;
@ -213,6 +218,7 @@ class HostLayerManager : public LayerManager {
bool mWindowOverlayChanged; bool mWindowOverlayChanged;
TimeDuration mLastPaintTime; TimeDuration mLastPaintTime;
TimeStamp mRenderStartTime; TimeStamp mRenderStartTime;
CompositionRecorder* mCompositionRecorder = nullptr;
// Render time for the current composition. // Render time for the current composition.
TimeStamp mCompositionTime; TimeStamp mCompositionTime;

Просмотреть файл

@ -45,6 +45,7 @@
#include "mozilla/layers/APZUpdater.h" // for APZUpdater #include "mozilla/layers/APZUpdater.h" // for APZUpdater
#include "mozilla/layers/AsyncCompositionManager.h" #include "mozilla/layers/AsyncCompositionManager.h"
#include "mozilla/layers/BasicCompositor.h" // for BasicCompositor #include "mozilla/layers/BasicCompositor.h" // for BasicCompositor
#include "mozilla/layers/CompositionRecorder.h" // for CompositionRecorder
#include "mozilla/layers/Compositor.h" // for Compositor #include "mozilla/layers/Compositor.h" // for Compositor
#include "mozilla/layers/CompositorManagerParent.h" // for CompositorManagerParent #include "mozilla/layers/CompositorManagerParent.h" // for CompositorManagerParent
#include "mozilla/layers/CompositorOGL.h" // for CompositorOGL #include "mozilla/layers/CompositorOGL.h" // for CompositorOGL
@ -2599,5 +2600,19 @@ int32_t RecordContentFrameTime(
return 0; return 0;
} }
mozilla::ipc::IPCResult CompositorBridgeParent::RecvBeginRecording(
const TimeStamp& aRecordingStart) {
mCompositionRecorder.reset(new CompositionRecorder(aRecordingStart));
mLayerManager->SetCompositionRecorder(mCompositionRecorder.get());
return IPC_OK();
}
mozilla::ipc::IPCResult CompositorBridgeParent::RecvEndRecording() {
mLayerManager->SetCompositionRecorder(nullptr);
mCompositionRecorder->WriteCollectedFrames();
mCompositionRecorder.reset(nullptr);
return IPC_OK();
}
} // namespace layers } // namespace layers
} // namespace mozilla } // namespace mozilla

Просмотреть файл

@ -72,6 +72,7 @@ class APZSampler;
class APZUpdater; class APZUpdater;
class AsyncCompositionManager; class AsyncCompositionManager;
class AsyncImagePipelineManager; class AsyncImagePipelineManager;
class CompositionRecorder;
class Compositor; class Compositor;
class CompositorAnimationStorage; class CompositorAnimationStorage;
class CompositorBridgeParent; class CompositorBridgeParent;
@ -238,6 +239,9 @@ class CompositorBridgeParentBase : public PCompositorBridgeParent,
const nsIntRegion& region) = 0; const nsIntRegion& region) = 0;
virtual mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint() = 0; virtual mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint() = 0;
virtual mozilla::ipc::IPCResult RecvAllPluginsCaptured() = 0; virtual mozilla::ipc::IPCResult RecvAllPluginsCaptured() = 0;
virtual mozilla::ipc::IPCResult RecvBeginRecording(
const TimeStamp& aRecordingStart) = 0;
virtual mozilla::ipc::IPCResult RecvEndRecording() = 0;
virtual mozilla::ipc::IPCResult RecvInitialize( virtual mozilla::ipc::IPCResult RecvInitialize(
const LayersId& rootLayerTreeId) = 0; const LayersId& rootLayerTreeId) = 0;
virtual mozilla::ipc::IPCResult RecvGetFrameUniformity( virtual mozilla::ipc::IPCResult RecvGetFrameUniformity(
@ -343,6 +347,9 @@ class CompositorBridgeParent final : public CompositorBridgeParentBase,
}; };
mozilla::ipc::IPCResult RecvAllPluginsCaptured() override; mozilla::ipc::IPCResult RecvAllPluginsCaptured() override;
mozilla::ipc::IPCResult RecvBeginRecording(
const TimeStamp& aRecordingStart) override;
mozilla::ipc::IPCResult RecvEndRecording() override;
virtual void NotifyMemoryPressure() override; virtual void NotifyMemoryPressure() override;
virtual void AccumulateMemoryReport(wr::MemoryReport*) override; virtual void AccumulateMemoryReport(wr::MemoryReport*) override;
@ -762,6 +769,7 @@ class CompositorBridgeParent final : public CompositorBridgeParentBase,
// mSelfRef is cleared in DeferredDestroy which is scheduled by ActorDestroy. // mSelfRef is cleared in DeferredDestroy which is scheduled by ActorDestroy.
RefPtr<CompositorBridgeParent> mSelfRef; RefPtr<CompositorBridgeParent> mSelfRef;
RefPtr<CompositorAnimationStorage> mAnimationStorage; RefPtr<CompositorAnimationStorage> mAnimationStorage;
UniquePtr<CompositionRecorder> mCompositionRecorder;
TimeDuration mPaintTime; TimeDuration mPaintTime;

Просмотреть файл

@ -84,6 +84,12 @@ class ContentCompositorBridgeParent final : public CompositorBridgeParentBase {
mozilla::ipc::IPCResult RecvAllPluginsCaptured() override { return IPC_OK(); } mozilla::ipc::IPCResult RecvAllPluginsCaptured() override { return IPC_OK(); }
mozilla::ipc::IPCResult RecvBeginRecording(
const TimeStamp& aRecordingStart) override {
return IPC_OK();
}
mozilla::ipc::IPCResult RecvEndRecording() override { return IPC_OK(); }
mozilla::ipc::IPCResult RecvGetFrameUniformity( mozilla::ipc::IPCResult RecvGetFrameUniformity(
FrameUniformityData* aOutData) override { FrameUniformityData* aOutData) override {
// Don't support calculating frame uniformity on the child process and // Don't support calculating frame uniformity on the child process and

Просмотреть файл

@ -264,6 +264,9 @@ parent:
sync CheckContentOnlyTDR(uint32_t sequenceNum) sync CheckContentOnlyTDR(uint32_t sequenceNum)
returns (bool isContentOnlyTDR); returns (bool isContentOnlyTDR);
async BeginRecording(TimeStamp aRecordingStart);
async EndRecording();
child: child:
// Send back Compositor Frame Metrics from APZCs so tiled layers can // Send back Compositor Frame Metrics from APZCs so tiled layers can
// update progressively. // update progressively.

Просмотреть файл

@ -22,6 +22,7 @@
#include "FrameBuilder.h" #include "FrameBuilder.h"
#include "LayersLogging.h" #include "LayersLogging.h"
#include "UtilityMLGPU.h" #include "UtilityMLGPU.h"
#include "CompositionRecorder.h"
#include "mozilla/layers/Diagnostics.h" #include "mozilla/layers/Diagnostics.h"
#include "mozilla/layers/TextRenderer.h" #include "mozilla/layers/TextRenderer.h"
@ -42,6 +43,49 @@ static const int kDebugOverlayY = 5;
static const int kDebugOverlayMaxWidth = 600; static const int kDebugOverlayMaxWidth = 600;
static const int kDebugOverlayMaxHeight = 96; static const int kDebugOverlayMaxHeight = 96;
class RecordedFrameMLGPU : public RecordedFrame {
public:
RecordedFrameMLGPU(MLGDevice* aDevice, MLGTexture* aTexture,
const TimeStamp& aTimestamp)
: RecordedFrame(aTimestamp), mDevice(aDevice) {
mSoftTexture =
aDevice->CreateTexture(aTexture->GetSize(), SurfaceFormat::B8G8R8A8,
MLGUsage::Staging, MLGTextureFlags::None);
aDevice->CopyTexture(mSoftTexture, IntPoint(), aTexture,
IntRect(IntPoint(), aTexture->GetSize()));
}
~RecordedFrameMLGPU() {
if (mIsMapped) {
mDevice->Unmap(mSoftTexture);
}
}
virtual already_AddRefed<gfx::DataSourceSurface> GetSourceSurface() override {
if (mDataSurf) {
return RefPtr<DataSourceSurface>(mDataSurf).forget();
}
MLGMappedResource map;
if (!mDevice->Map(mSoftTexture, MLGMapType::READ, &map)) {
return nullptr;
}
mIsMapped = true;
mDataSurf = Factory::CreateWrappingDataSourceSurface(
map.mData, map.mStride, mSoftTexture->GetSize(),
SurfaceFormat::B8G8R8A8);
return RefPtr<DataSourceSurface>(mDataSurf).forget();
}
private:
RefPtr<MLGDevice> mDevice;
// Software texture in VRAM.
RefPtr<MLGTexture> mSoftTexture;
RefPtr<DataSourceSurface> mDataSurf;
bool mIsMapped = false;
};
LayerManagerMLGPU::LayerManagerMLGPU(widget::CompositorWidget* aWidget) LayerManagerMLGPU::LayerManagerMLGPU(widget::CompositorWidget* aWidget)
: mWidget(aWidget), : mWidget(aWidget),
mDrawDiagnostics(false), mDrawDiagnostics(false),
@ -356,6 +400,22 @@ void LayerManagerMLGPU::RenderLayers() {
mProfilerScreenshotGrabber.MaybeGrabScreenshot( mProfilerScreenshotGrabber.MaybeGrabScreenshot(
mDevice, builder.GetWidgetRT()->GetTexture()); mDevice, builder.GetWidgetRT()->GetTexture());
if (mCompositionRecorder) {
bool hasContentPaint = false;
for (CompositionPayload& payload : mPayload) {
if (payload.mType == CompositionPayloadType::eContentPaint) {
hasContentPaint = true;
break;
}
}
if (hasContentPaint) {
RefPtr<RecordedFrame> frame = new RecordedFrameMLGPU(
mDevice, builder.GetWidgetRT()->GetTexture(), TimeStamp::Now());
mCompositionRecorder->RecordFrame(frame);
}
}
mCurrentFrame = nullptr; mCurrentFrame = nullptr;
if (mDrawDiagnostics) { if (mDrawDiagnostics) {

Просмотреть файл

@ -161,6 +161,7 @@ EXPORTS.mozilla.layers += [
'composite/TextRenderer.h', 'composite/TextRenderer.h',
'composite/TextureHost.h', 'composite/TextureHost.h',
'composite/TiledContentHost.h', 'composite/TiledContentHost.h',
'CompositionRecorder.h',
'Compositor.h', 'Compositor.h',
'CompositorTypes.h', 'CompositorTypes.h',
'CopyableCanvasRenderer.h', 'CopyableCanvasRenderer.h',
@ -409,6 +410,7 @@ UNIFIED_SOURCES += [
'composite/TextRenderer.cpp', 'composite/TextRenderer.cpp',
'composite/TextureHost.cpp', 'composite/TextureHost.cpp',
'composite/TiledContentHost.cpp', 'composite/TiledContentHost.cpp',
'CompositionRecorder.cpp',
'Compositor.cpp', 'Compositor.cpp',
'CopyableCanvasRenderer.cpp', 'CopyableCanvasRenderer.cpp',
'Effects.cpp', 'Effects.cpp',

Просмотреть файл

@ -7,7 +7,9 @@
#ifndef MOZILLA_GFX_GLMANAGER_H #ifndef MOZILLA_GFX_GLMANAGER_H
#define MOZILLA_GFX_GLMANAGER_H #define MOZILLA_GFX_GLMANAGER_H
#include "GLDefs.h"
#include "mozilla/gfx/Types.h" // for SurfaceFormat #include "mozilla/gfx/Types.h" // for SurfaceFormat
#include "mozilla/gfx/2D.h"
namespace mozilla { namespace mozilla {
namespace gl { namespace gl {

Просмотреть файл

@ -669,6 +669,7 @@ class gfxPrefs final {
DECL_GFX_PREF(Live, "layers.shared-buffer-provider.enabled", PersistentBufferProviderSharedEnabled, bool, false); DECL_GFX_PREF(Live, "layers.shared-buffer-provider.enabled", PersistentBufferProviderSharedEnabled, bool, false);
DECL_GFX_PREF(Live, "layers.single-tile.enabled", LayersSingleTileEnabled, bool, true); DECL_GFX_PREF(Live, "layers.single-tile.enabled", LayersSingleTileEnabled, bool, true);
DECL_GFX_PREF(Live, "layers.force-synchronous-resize", LayersForceSynchronousResize, bool, true); DECL_GFX_PREF(Live, "layers.force-synchronous-resize", LayersForceSynchronousResize, bool, true);
DECL_GFX_PREF(Once, "layers.windowrecording.path", LayersWindowRecordingPath, std::string, std::string());
// We allow for configurable and rectangular tile size to avoid wasting memory on devices whose // We allow for configurable and rectangular tile size to avoid wasting memory on devices whose
// screen size does not align nicely to the default tile size. Although layers can be any size, // screen size does not align nicely to the default tile size. Although layers can be any size,

Просмотреть файл

@ -24,7 +24,6 @@
#include "mozilla/dom/PFileSystemRequestChild.h" #include "mozilla/dom/PFileSystemRequestChild.h"
#include "mozilla/dom/EndpointForReportChild.h" #include "mozilla/dom/EndpointForReportChild.h"
#include "mozilla/dom/FileSystemTaskBase.h" #include "mozilla/dom/FileSystemTaskBase.h"
#include "mozilla/dom/asmjscache/AsmJSCache.h"
#include "mozilla/dom/cache/ActorUtils.h" #include "mozilla/dom/cache/ActorUtils.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.h" #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.h"
#include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsChild.h" #include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsChild.h"
@ -89,7 +88,6 @@ using mozilla::dom::PServiceWorkerChild;
using mozilla::dom::PServiceWorkerContainerChild; using mozilla::dom::PServiceWorkerContainerChild;
using mozilla::dom::PServiceWorkerRegistrationChild; using mozilla::dom::PServiceWorkerRegistrationChild;
using mozilla::dom::StorageDBChild; using mozilla::dom::StorageDBChild;
using mozilla::dom::asmjscache::PAsmJSCacheEntryChild;
using mozilla::dom::cache::PCacheChild; using mozilla::dom::cache::PCacheChild;
using mozilla::dom::cache::PCacheStorageChild; using mozilla::dom::cache::PCacheStorageChild;
using mozilla::dom::cache::PCacheStreamControlChild; using mozilla::dom::cache::PCacheStreamControlChild;
@ -572,21 +570,6 @@ bool BackgroundChildImpl::DeallocPParentToChildStreamChild(
return true; return true;
} }
PAsmJSCacheEntryChild* BackgroundChildImpl::AllocPAsmJSCacheEntryChild(
const dom::asmjscache::OpenMode& aOpenMode,
const dom::asmjscache::WriteParams& aWriteParams,
const PrincipalInfo& aPrincipalInfo) {
MOZ_CRASH("PAsmJSCacheEntryChild actors should be manually constructed!");
}
bool BackgroundChildImpl::DeallocPAsmJSCacheEntryChild(
PAsmJSCacheEntryChild* aActor) {
MOZ_ASSERT(aActor);
dom::asmjscache::DeallocEntryChild(aActor);
return true;
}
BackgroundChildImpl::PQuotaChild* BackgroundChildImpl::AllocPQuotaChild() { BackgroundChildImpl::PQuotaChild* BackgroundChildImpl::AllocPQuotaChild() {
MOZ_CRASH("PQuotaChild actor should be manually constructed!"); MOZ_CRASH("PQuotaChild actor should be manually constructed!");
} }

Просмотреть файл

@ -213,14 +213,6 @@ class BackgroundChildImpl : public PBackgroundChild {
virtual bool DeallocPParentToChildStreamChild( virtual bool DeallocPParentToChildStreamChild(
PParentToChildStreamChild* aActor) override; PParentToChildStreamChild* aActor) override;
virtual PAsmJSCacheEntryChild* AllocPAsmJSCacheEntryChild(
const dom::asmjscache::OpenMode& aOpenMode,
const dom::asmjscache::WriteParams& aWriteParams,
const PrincipalInfo& aPrincipalInfo) override;
virtual bool DeallocPAsmJSCacheEntryChild(
PAsmJSCacheEntryChild* aActor) override;
virtual PQuotaChild* AllocPQuotaChild() override; virtual PQuotaChild* AllocPQuotaChild() override;
virtual bool DeallocPQuotaChild(PQuotaChild* aActor) override; virtual bool DeallocPQuotaChild(PQuotaChild* aActor) override;

Просмотреть файл

@ -28,7 +28,6 @@
#include "mozilla/dom/ServiceWorkerManagerParent.h" #include "mozilla/dom/ServiceWorkerManagerParent.h"
#include "mozilla/dom/ServiceWorkerRegistrar.h" #include "mozilla/dom/ServiceWorkerRegistrar.h"
#include "mozilla/dom/StorageActivityService.h" #include "mozilla/dom/StorageActivityService.h"
#include "mozilla/dom/asmjscache/AsmJSCache.h"
#include "mozilla/dom/cache/ActorUtils.h" #include "mozilla/dom/cache/ActorUtils.h"
#include "mozilla/dom/indexedDB/ActorsParent.h" #include "mozilla/dom/indexedDB/ActorsParent.h"
#include "mozilla/dom/ipc/IPCBlobInputStreamParent.h" #include "mozilla/dom/ipc/IPCBlobInputStreamParent.h"
@ -88,7 +87,6 @@ using mozilla::dom::PServiceWorkerParent;
using mozilla::dom::PServiceWorkerRegistrationParent; using mozilla::dom::PServiceWorkerRegistrationParent;
using mozilla::dom::UDPSocketParent; using mozilla::dom::UDPSocketParent;
using mozilla::dom::WebAuthnTransactionParent; using mozilla::dom::WebAuthnTransactionParent;
using mozilla::dom::asmjscache::PAsmJSCacheEntryParent;
using mozilla::dom::cache::PCacheParent; using mozilla::dom::cache::PCacheParent;
using mozilla::dom::cache::PCacheStorageParent; using mozilla::dom::cache::PCacheStorageParent;
using mozilla::dom::cache::PCacheStreamControlParent; using mozilla::dom::cache::PCacheStreamControlParent;
@ -949,26 +947,6 @@ mozilla::ipc::IPCResult BackgroundParentImpl::RecvMessagePortForceClose(
return IPC_OK(); return IPC_OK();
} }
PAsmJSCacheEntryParent* BackgroundParentImpl::AllocPAsmJSCacheEntryParent(
const dom::asmjscache::OpenMode& aOpenMode,
const dom::asmjscache::WriteParams& aWriteParams,
const PrincipalInfo& aPrincipalInfo) {
AssertIsInMainOrSocketProcess();
AssertIsOnBackgroundThread();
return dom::asmjscache::AllocEntryParent(aOpenMode, aWriteParams,
aPrincipalInfo);
}
bool BackgroundParentImpl::DeallocPAsmJSCacheEntryParent(
PAsmJSCacheEntryParent* aActor) {
AssertIsInMainOrSocketProcess();
AssertIsOnBackgroundThread();
dom::asmjscache::DeallocEntryParent(aActor);
return true;
}
BackgroundParentImpl::PQuotaParent* BackgroundParentImpl::AllocPQuotaParent() { BackgroundParentImpl::PQuotaParent* BackgroundParentImpl::AllocPQuotaParent() {
AssertIsInMainOrSocketProcess(); AssertIsInMainOrSocketProcess();
AssertIsOnBackgroundThread(); AssertIsOnBackgroundThread();

Просмотреть файл

@ -263,14 +263,6 @@ class BackgroundParentImpl : public PBackgroundParent {
const nsID& aUUID, const nsID& aDestinationUUID, const nsID& aUUID, const nsID& aDestinationUUID,
const uint32_t& aSequenceID) override; const uint32_t& aSequenceID) override;
virtual PAsmJSCacheEntryParent* AllocPAsmJSCacheEntryParent(
const dom::asmjscache::OpenMode& aOpenMode,
const dom::asmjscache::WriteParams& aWriteParams,
const PrincipalInfo& aPrincipalInfo) override;
virtual bool DeallocPAsmJSCacheEntryParent(
PAsmJSCacheEntryParent* aActor) override;
virtual PQuotaParent* AllocPQuotaParent() override; virtual PQuotaParent* AllocPQuotaParent() override;
virtual bool DeallocPQuotaParent(PQuotaParent* aActor) override; virtual bool DeallocPQuotaParent(PQuotaParent* aActor) override;

Просмотреть файл

@ -2,7 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PAsmJSCacheEntry;
include protocol PBackgroundIDBFactory; include protocol PBackgroundIDBFactory;
include protocol PBackgroundIndexedDBUtils; include protocol PBackgroundIndexedDBUtils;
include protocol PBackgroundSDBConnection; include protocol PBackgroundSDBConnection;
@ -63,18 +62,11 @@ include "mozilla/layers/LayersMessageUtils.h";
using mozilla::dom::cache::Namespace using mozilla::dom::cache::Namespace
from "mozilla/dom/cache/Types.h"; from "mozilla/dom/cache/Types.h";
using mozilla::dom::asmjscache::OpenMode
from "mozilla/dom/asmjscache/AsmJSCache.h";
using mozilla::dom::asmjscache::WriteParams
from "mozilla/dom/asmjscache/AsmJSCache.h";
namespace mozilla { namespace mozilla {
namespace ipc { namespace ipc {
sync protocol PBackground sync protocol PBackground
{ {
manages PAsmJSCacheEntry;
manages PBackgroundIDBFactory; manages PBackgroundIDBFactory;
manages PBackgroundIndexedDBUtils; manages PBackgroundIndexedDBUtils;
manages PBackgroundSDBConnection; manages PBackgroundSDBConnection;
@ -185,10 +177,6 @@ parent:
async MessagePortForceClose(nsID uuid, nsID destinationUuid, uint32_t sequenceId); async MessagePortForceClose(nsID uuid, nsID destinationUuid, uint32_t sequenceId);
async PAsmJSCacheEntry(OpenMode openMode,
WriteParams write,
PrincipalInfo principalInfo);
async PQuota(); async PQuota();
async ShutdownQuotaManager(); async ShutdownQuotaManager();

Просмотреть файл

@ -29,7 +29,7 @@
* - writes to fields in private data * - writes to fields in private data
* - writes to non-markable fields like JSObject::private that point to * - writes to non-markable fields like JSObject::private that point to
* markable data * markable data
* The last category is the trickiest. Even though the private pointers does not * The last category is the trickiest. Even though the private pointer does not
* point to a GC thing, changing the private pointer may change the set of * point to a GC thing, changing the private pointer may change the set of
* objects that are traced by the GC. Therefore it needs a write barrier. * objects that are traced by the GC. Therefore it needs a write barrier.
* *
@ -119,9 +119,9 @@
* reference them. The solution is to maintain information about these pointers, * reference them. The solution is to maintain information about these pointers,
* and mark their targets when we start a minor collection. * and mark their targets when we start a minor collection.
* *
* The pointers can be thought of as edges in object graph, and the set of edges * The pointers can be thought of as edges in an object graph, and the set of
* from the tenured generation into the nursery is know as the remembered set. * edges from the tenured generation into the nursery is known as the remembered
* Post barriers are used to track this remembered set. * set. Post barriers are used to track this remembered set.
* *
* Whenever a slot which could contain such a pointer is written, we check * Whenever a slot which could contain such a pointer is written, we check
* whether the pointed-to thing is in the nursery (if storeBuffer() returns a * whether the pointed-to thing is in the nursery (if storeBuffer() returns a

Просмотреть файл

@ -63,6 +63,8 @@ LOCAL_INCLUDES += [
'celt', 'celt',
'include', 'include',
'silk', 'silk',
'silk/fixed',
'silk/float',
'src', 'src',
] ]
@ -76,15 +78,9 @@ UNIFIED_SOURCES += opus_sources
SOURCES += opus_nonunified_sources SOURCES += opus_nonunified_sources
if CONFIG['MOZ_SAMPLE_TYPE_FLOAT32']: if CONFIG['MOZ_SAMPLE_TYPE_FLOAT32']:
LOCAL_INCLUDES += [
'silk/float',
]
UNIFIED_SOURCES += silk_sources_float UNIFIED_SOURCES += silk_sources_float
UNIFIED_SOURCES += opus_sources_float UNIFIED_SOURCES += opus_sources_float
else: else:
LOCAL_INCLUDES += [
'silk/fixed',
]
UNIFIED_SOURCES += silk_sources_fixed UNIFIED_SOURCES += silk_sources_fixed
if CONFIG['CPU_ARCH'] in ('x86', 'x86_64'): if CONFIG['CPU_ARCH'] in ('x86', 'x86_64'):

Просмотреть файл

@ -532,7 +532,7 @@ var BrowserApp = {
Telemetry.addData("FENNEC_TRACKING_PROTECTION_STATE", parseInt(BrowserApp.getTrackingProtectionState())); Telemetry.addData("FENNEC_TRACKING_PROTECTION_STATE", parseInt(BrowserApp.getTrackingProtectionState()));
}); });
InitLater(() => LightWeightThemeWebInstaller.init()); InitLater(() => LightWeightThemeStuff.init());
InitLater(() => CastingApps.init(), window, "CastingApps"); InitLater(() => CastingApps.init(), window, "CastingApps");
// Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008. // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
@ -3274,14 +3274,11 @@ ChromeUtils.defineModuleGetter(this, "PageActions",
}); });
}); });
var LightWeightThemeWebInstaller = { var LightWeightThemeStuff = {
init: function sh_init() { init: function sh_init() {
let temp = {}; let {LightweightThemeConsumer} =
ChromeUtils.import("resource://gre/modules/LightweightThemeConsumer.jsm", temp); ChromeUtils.import("resource://gre/modules/LightweightThemeConsumer.jsm");
let theme = new temp.LightweightThemeConsumer(document); new LightweightThemeConsumer(document);
BrowserApp.deck.addEventListener("InstallBrowserTheme", this, false, true);
BrowserApp.deck.addEventListener("PreviewBrowserTheme", this, false, true);
BrowserApp.deck.addEventListener("ResetBrowserThemePreview", this, false, true);
if (ParentalControls.parentalControlsEnabled && if (ParentalControls.parentalControlsEnabled &&
!this._manager.currentTheme && !this._manager.currentTheme &&
@ -3291,38 +3288,11 @@ var LightWeightThemeWebInstaller = {
} }
}, },
handleEvent: function (event) {
switch (event.type) {
case "InstallBrowserTheme":
case "PreviewBrowserTheme":
case "ResetBrowserThemePreview":
// ignore requests from background tabs
if (event.target.ownerGlobal.top != content)
return;
}
switch (event.type) {
case "InstallBrowserTheme":
this._installRequest(event);
break;
case "PreviewBrowserTheme":
this._preview(event);
break;
case "ResetBrowserThemePreview":
this._resetPreview(event);
break;
case "pagehide":
case "TabSelect":
this._resetPreview();
break;
}
},
get _manager () { get _manager () {
let temp = {}; let {LightweightThemeManager} =
ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", temp); ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
delete this._manager; delete this._manager;
return this._manager = temp.LightweightThemeManager; return this._manager = LightweightThemeManager;
}, },
_installParentalControlsTheme: function() { _installParentalControlsTheme: function() {
@ -3336,81 +3306,6 @@ var LightWeightThemeWebInstaller = {
mgr.addBuiltInTheme(parentalControlsTheme); mgr.addBuiltInTheme(parentalControlsTheme);
mgr.themeChanged(parentalControlsTheme); mgr.themeChanged(parentalControlsTheme);
}, },
_installRequest: function (event) {
let node = event.target;
let data = this._getThemeFromNode(node);
if (!data)
return;
if (this._isAllowed(node)) {
this._install(data);
return;
}
let allowButtonText = Strings.browser.GetStringFromName("lwthemeInstallRequest.allowButton");
let IDNService = Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
let hostname = IDNService.convertToDisplayIDN(node.ownerDocument.location.hostname, {});
let message = Strings.browser.formatStringFromName("lwthemeInstallRequest.message", [hostname], 1);
let buttons = [{
label: allowButtonText,
callback: function () {
LightWeightThemeWebInstaller._install(data);
},
positive: true
}];
NativeWindow.doorhanger.show(message, "Personas", buttons, BrowserApp.selectedTab.id);
},
_install: function (newLWTheme) {
this._manager.currentTheme = newLWTheme;
},
_previewWindow: null,
_preview: function (event) {
if (!this._isAllowed(event.target))
return;
let data = this._getThemeFromNode(event.target);
if (!data)
return;
this._resetPreview();
this._previewWindow = event.target.ownerGlobal;
this._previewWindow.addEventListener("pagehide", this, true);
BrowserApp.deck.addEventListener("TabSelect", this);
this._manager.previewTheme(data);
},
_resetPreview: function (event) {
if (!this._previewWindow ||
event && !this._isAllowed(event.target))
return;
this._previewWindow.removeEventListener("pagehide", this, true);
this._previewWindow = null;
BrowserApp.deck.removeEventListener("TabSelect", this);
this._manager.resetPreview();
},
_isAllowed: function (node) {
// Make sure the whitelist has been imported to permissions
PermissionsUtils.importFromPrefs("xpinstall.", "install");
let pm = Services.perms;
let uri = node.ownerDocument.documentURIObject;
if (!uri.schemeIs("https")) {
return false;
}
return pm.testPermission(uri, "install") == pm.ALLOW_ACTION;
},
_getThemeFromNode: function (node) {
return this._manager.parseTheme(node.getAttribute("data-browsertheme"), node.baseURI);
}
}; };
var DesktopUserAgent = { var DesktopUserAgent = {

Просмотреть файл

@ -15,9 +15,6 @@ const {Svc, Utils} = ChromeUtils.import("resource://services-sync/util.js");
const {SCORE_INCREMENT_XLARGE} = ChromeUtils.import("resource://services-sync/constants.js"); const {SCORE_INCREMENT_XLARGE} = ChromeUtils.import("resource://services-sync/constants.js");
const {CommonUtils} = ChromeUtils.import("resource://services-common/utils.js"); const {CommonUtils} = ChromeUtils.import("resource://services-common/utils.js");
ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
"resource://gre/modules/LightweightThemeManager.jsm");
XPCOMUtils.defineLazyGetter(this, "PREFS_GUID", XPCOMUtils.defineLazyGetter(this, "PREFS_GUID",
() => CommonUtils.encodeBase64URL(Services.appinfo.ID)); () => CommonUtils.encodeBase64URL(Services.appinfo.ID));
@ -127,19 +124,7 @@ PrefStore.prototype = {
return values; return values;
}, },
_updateLightWeightTheme(themeID) {
let themeObject = null;
if (themeID) {
themeObject = LightweightThemeManager.getUsedTheme(themeID);
}
LightweightThemeManager.currentTheme = themeObject;
},
_setAllPrefs(values) { _setAllPrefs(values) {
let selectedThemeIDPref = "lightweightThemes.selectedThemeID";
let selectedThemeIDBefore = this._prefs.get(selectedThemeIDPref, null);
let selectedThemeIDAfter = selectedThemeIDBefore;
// Update 'services.sync.prefs.sync.foo.pref' before 'foo.pref', otherwise // Update 'services.sync.prefs.sync.foo.pref' before 'foo.pref', otherwise
// _isSynced returns false when 'foo.pref' doesn't exist (e.g., on a new device). // _isSynced returns false when 'foo.pref' doesn't exist (e.g., on a new device).
let prefs = Object.keys(values).sort(a => -a.indexOf(PREF_SYNC_PREFS_PREFIX)); let prefs = Object.keys(values).sort(a => -a.indexOf(PREF_SYNC_PREFS_PREFIX));
@ -154,31 +139,17 @@ PrefStore.prototype = {
continue; continue;
} }
switch (pref) { if (value == null) {
// Some special prefs we don't want to set directly. // Pref has gone missing. The best we can do is reset it.
case selectedThemeIDPref: this._prefs.reset(pref);
selectedThemeIDAfter = value; } else {
break; try {
this._prefs.set(pref, value);
// default is to just set the pref } catch (ex) {
default: this._log.trace(`Failed to set pref: ${pref}`, ex);
if (value == null) { }
// Pref has gone missing. The best we can do is reset it.
this._prefs.reset(pref);
} else {
try {
this._prefs.set(pref, value);
} catch (ex) {
this._log.trace(`Failed to set pref: ${pref}`, ex);
}
}
} }
} }
// Notify the lightweight theme manager if the selected theme has changed.
if (selectedThemeIDBefore != selectedThemeIDAfter) {
this._updateLightWeightTheme(selectedThemeIDAfter);
}
}, },
async getAllIDs() { async getAllIDs() {

Просмотреть файл

@ -1,7 +1,6 @@
/* Any copyright is dedicated to the Public Domain. /* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */ http://creativecommons.org/publicdomain/zero/1.0/ */
const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
const {Preferences} = ChromeUtils.import("resource://gre/modules/Preferences.jsm"); const {Preferences} = ChromeUtils.import("resource://gre/modules/Preferences.jsm");
const {PrefRec} = ChromeUtils.import("resource://services-sync/engines/prefs.js"); const {PrefRec} = ChromeUtils.import("resource://services-sync/engines/prefs.js");
const {Service} = ChromeUtils.import("resource://services-sync/service.js"); const {Service} = ChromeUtils.import("resource://services-sync/service.js");
@ -101,32 +100,6 @@ add_task(async function run_test() {
Assert.equal(prefs.get("testing.unsynced.url"), "https://www.example.com/2"); Assert.equal(prefs.get("testing.unsynced.url"), "https://www.example.com/2");
Assert.equal(Svc.Prefs.get("prefs.sync.testing.somepref"), true); Assert.equal(Svc.Prefs.get("prefs.sync.testing.somepref"), true);
_("Enable persona");
// Ensure we don't go to the network to fetch personas and end up leaking
// stuff.
Services.io.offline = true;
Assert.equal(LightweightThemeManager.currentTheme.id, "default-theme@mozilla.org");
let persona1 = makePersona();
let persona2 = makePersona();
let usedThemes = JSON.stringify([persona1, persona2]);
record.value = {
"lightweightThemes.selectedThemeID": persona1.id,
"lightweightThemes.usedThemes": usedThemes,
};
await store.update(record);
Assert.equal(prefs.get("lightweightThemes.selectedThemeID"), persona1.id);
Assert.ok(Utils.deepEquals(LightweightThemeManager.currentTheme,
persona1));
_("Disable persona");
record.value = {
"lightweightThemes.selectedThemeID": null,
"lightweightThemes.usedThemes": usedThemes,
};
await store.update(record);
Assert.equal(LightweightThemeManager.currentTheme.id, "default-theme@mozilla.org");
_("Only the current app's preferences are applied."); _("Only the current app's preferences are applied.");
record = new PrefRec("prefs", "some-fake-app"); record = new PrefRec("prefs", "some-fake-app");
record.value = { record.value = {
@ -134,38 +107,6 @@ add_task(async function run_test() {
}; };
await store.update(record); await store.update(record);
Assert.equal(prefs.get("testing.int"), 42); Assert.equal(prefs.get("testing.int"), 42);
_("The light-weight theme preference is handled correctly.");
let lastThemeID = undefined;
let orig_updateLightWeightTheme = store._updateLightWeightTheme;
store._updateLightWeightTheme = function(themeID) {
lastThemeID = themeID;
};
try {
record = new PrefRec("prefs", PREFS_GUID);
record.value = {
"testing.int": 42,
};
await store.update(record);
Assert.ok(lastThemeID === undefined,
"should not have tried to change the theme with an unrelated pref.");
Services.prefs.setCharPref("lightweightThemes.selectedThemeID", "foo");
record.value = {
"lightweightThemes.selectedThemeID": "foo",
};
await store.update(record);
Assert.ok(lastThemeID === undefined,
"should not have tried to change the theme when the incoming pref matches current value.");
record.value = {
"lightweightThemes.selectedThemeID": "bar",
};
await store.update(record);
Assert.equal(lastThemeID, "bar",
"should have tried to change the theme when the incoming pref was different.");
} finally {
store._updateLightWeightTheme = orig_updateLightWeightTheme;
}
} finally { } finally {
prefs.resetBranch(""); prefs.resetBranch("");
} }

Просмотреть файл

@ -2,8 +2,6 @@
/* vim: set sts=2 sw=2 et tw=80: */ /* vim: set sts=2 sw=2 et tw=80: */
"use strict"; "use strict";
const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
const {PromiseTestUtils} = ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm"); const {PromiseTestUtils} = ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm");
PromiseTestUtils.whitelistRejectionsGlobally(/Message manager disconnected/); PromiseTestUtils.whitelistRejectionsGlobally(/Message manager disconnected/);
@ -56,7 +54,7 @@ add_task(async function test_management_themes() {
let addons = await browser.management.getAll(); let addons = await browser.management.getAll();
let themes = addons.filter(addon => addon.type === "theme"); let themes = addons.filter(addon => addon.type === "theme");
// We get the 3 built-in themes plus the lwt and our addon. // We get the 3 built-in themes plus the lwt and our addon.
browser.test.assertEq(5, themes.length, "got expected addons"); browser.test.assertEq(4, themes.length, "got expected addons");
// We should also get our test extension. // We should also get our test extension.
let testExtension = addons.find(addon => { return addon.id === TEST_ID; }); let testExtension = addons.find(addon => { return addon.id === TEST_ID; });
browser.test.assertTrue(!!testExtension, browser.test.assertTrue(!!testExtension,
@ -109,27 +107,8 @@ add_task(async function test_management_themes() {
}); });
await extension.startup(); await extension.startup();
// Test LWT
LightweightThemeManager.currentTheme = {
id: "lwt@personas.mozilla.org",
version: "1",
name: "Bling",
description: "SO MUCH BLING!",
author: "Pixel Pusher",
homepageURL: "http://mochi.test:8888/data/index.html",
headerURL: "http://mochi.test:8888/data/header.png",
previewURL: "http://mochi.test:8888/data/preview.png",
iconURL: "http://mochi.test:8888/data/icon.png",
textcolor: Math.random().toString(),
accentcolor: Math.random().toString(),
};
is(await extension.awaitMessage("onInstalled"), "Bling", "LWT installed");
is(await extension.awaitMessage("onEnabled"), "Bling", "LWT enabled");
await theme.startup(); await theme.startup();
is(await extension.awaitMessage("onInstalled"), "Simple theme test", "webextension theme installed"); is(await extension.awaitMessage("onInstalled"), "Simple theme test", "webextension theme installed");
is(await extension.awaitMessage("onDisabled"), "Bling", "LWT disabled");
// no enabled event when installed.
extension.sendMessage("test"); extension.sendMessage("test");
is(await extension.awaitMessage("onEnabled"), "Default", "default enabled"); is(await extension.awaitMessage("onEnabled"), "Default", "default enabled");

Просмотреть файл

@ -71,12 +71,9 @@ add_task(async function test_autocomplete_footer_onclick() {
}, "Waiting for footer to become visible"); }, "Waiting for footer to become visible");
EventUtils.synthesizeMouseAtCenter(footer, {}); EventUtils.synthesizeMouseAtCenter(footer, {});
await TestUtils.waitForCondition(() => { let window = await waitForPasswordManagerDialog();
return Services.wm.getMostRecentWindow("Toolkit:PasswordManager") !== null;
}, "Waiting for the password manager dialog to open");
info("Login dialog was opened"); info("Login dialog was opened");
let window = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
await TestUtils.waitForCondition(() => { await TestUtils.waitForCondition(() => {
return window.document.getElementById("filter").value == "example.com"; return window.document.getElementById("filter").value == "example.com";
}, "Waiting for the search string to filter logins"); }, "Waiting for the search string to filter logins");
@ -109,12 +106,9 @@ add_task(async function test_autocomplete_footer_keydown() {
await EventUtils.synthesizeKey("KEY_ArrowDown"); await EventUtils.synthesizeKey("KEY_ArrowDown");
await EventUtils.synthesizeKey("KEY_Enter"); await EventUtils.synthesizeKey("KEY_Enter");
await TestUtils.waitForCondition(() => { let window = await waitForPasswordManagerDialog();
return Services.wm.getMostRecentWindow("Toolkit:PasswordManager") !== null;
}, "Waiting for the password manager dialog to open");
info("Login dialog was opened"); info("Login dialog was opened");
let window = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
await TestUtils.waitForCondition(() => { await TestUtils.waitForCondition(() => {
return window.document.getElementById("filter").value == "example.com"; return window.document.getElementById("filter").value == "example.com";
}, "Waiting for the search string to filter logins"); }, "Waiting for the search string to filter logins");

Просмотреть файл

@ -8,10 +8,7 @@ registerCleanupFunction(resetPrefs);
add_task(async function test_noFilter() { add_task(async function test_noFilter() {
LoginHelper.openPasswordManager(window); LoginHelper.openPasswordManager(window);
await TestUtils.waitForCondition(() => { let win = await waitForPasswordManagerDialog();
return Services.wm.getMostRecentWindow("Toolkit:PasswordManager") !== null;
}, "Waiting for the password manager dialog to open");
let win = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
ok(win, "Login dialog was opened"); ok(win, "Login dialog was opened");
await BrowserTestUtils.closeWindow(win); await BrowserTestUtils.closeWindow(win);
await TestUtils.waitForCondition(() => { await TestUtils.waitForCondition(() => {
@ -23,10 +20,7 @@ add_task(async function test_filter() {
// Greek IDN for example.test // Greek IDN for example.test
let domain = "παράδειγμα.δοκιμή"; let domain = "παράδειγμα.δοκιμή";
LoginHelper.openPasswordManager(window, domain); LoginHelper.openPasswordManager(window, domain);
await TestUtils.waitForCondition(() => { let win = await waitForPasswordManagerDialog();
return Services.wm.getMostRecentWindow("Toolkit:PasswordManager") !== null;
}, "Waiting for the password manager dialog to open");
let win = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
await TestUtils.waitForCondition(() => { await TestUtils.waitForCondition(() => {
return win.document.getElementById("filter").value == domain; return win.document.getElementById("filter").value == domain;
}, "Waiting for the search string to filter logins"); }, "Waiting for the search string to filter logins");

Просмотреть файл

@ -154,3 +154,13 @@ async function checkDoorhangerUsernamePassword(username, password) {
} }
// End popup notification (doorhanger) functions // // End popup notification (doorhanger) functions //
async function waitForPasswordManagerDialog() {
let win;
await TestUtils.waitForCondition(() => {
win = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
return win && win.document.getElementById("filter");
}, "Waiting for the password manager dialog to open");
return win;
}

Просмотреть файл

@ -24,8 +24,6 @@ ChromeUtils.defineModuleGetter(this, "AttributionCode",
"resource:///modules/AttributionCode.jsm"); "resource:///modules/AttributionCode.jsm");
ChromeUtils.defineModuleGetter(this, "ctypes", ChromeUtils.defineModuleGetter(this, "ctypes",
"resource://gre/modules/ctypes.jsm"); "resource://gre/modules/ctypes.jsm");
ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
"resource://gre/modules/LightweightThemeManager.jsm");
ChromeUtils.defineModuleGetter(this, "ProfileAge", ChromeUtils.defineModuleGetter(this, "ProfileAge",
"resource://gre/modules/ProfileAge.jsm"); "resource://gre/modules/ProfileAge.jsm");
ChromeUtils.defineModuleGetter(this, "WindowsRegistry", ChromeUtils.defineModuleGetter(this, "WindowsRegistry",
@ -699,18 +697,12 @@ EnvironmentAddonBuilder.prototype = {
*/ */
async _updateAddons(atStartup) { async _updateAddons(atStartup) {
this._environment._log.trace("_updateAddons"); this._environment._log.trace("_updateAddons");
let personaId = null;
let theme = LightweightThemeManager.currentTheme;
if (theme) {
personaId = theme.id;
}
let addons = { let addons = {
activeAddons: await this._getActiveAddons(), activeAddons: await this._getActiveAddons(),
theme: await this._getActiveTheme(), theme: await this._getActiveTheme(),
activePlugins: this._getActivePlugins(atStartup), activePlugins: this._getActivePlugins(atStartup),
activeGMPlugins: await this._getActiveGMPlugins(atStartup), activeGMPlugins: await this._getActiveGMPlugins(atStartup),
persona: personaId,
}; };
let result = { let result = {

Просмотреть файл

@ -275,7 +275,6 @@ Structure:
}, },
... ...
}, },
persona: <string>, // id of the current persona
}, },
experiments: { experiments: {
"<experiment id>": { branch: "<branch>" }, "<experiment id>": { branch: "<branch>" },
@ -465,6 +464,10 @@ For each experiment we collect the ``id`` and the ``branch`` the client is enrol
Version History Version History
--------------- ---------------
- Firefox 67:
- Removed ``persona``. The ``addons.activeAddons`` list should be used instead. (`bug 1525511 https://bugzilla.mozilla.org/show_bug.cgi?id=1525511>`_)
- Firefox 61: - Firefox 61:
- Removed empty ``addons.activeExperiment`` (`bug 1452935 <https://bugzilla.mozilla.org/show_bug.cgi?id=1452935>`_). - Removed empty ``addons.activeExperiment`` (`bug 1452935 <https://bugzilla.mozilla.org/show_bug.cgi?id=1452935>`_).

Просмотреть файл

@ -17,10 +17,6 @@ const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
ChromeUtils.defineModuleGetter(this, "AttributionCode", ChromeUtils.defineModuleGetter(this, "AttributionCode",
"resource:///modules/AttributionCode.jsm"); "resource:///modules/AttributionCode.jsm");
// Lazy load |LightweightThemeManager|.
ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
"resource://gre/modules/LightweightThemeManager.jsm");
ChromeUtils.defineModuleGetter(this, "ExtensionTestUtils", ChromeUtils.defineModuleGetter(this, "ExtensionTestUtils",
"resource://testing-common/ExtensionXPCShellUtils.jsm"); "resource://testing-common/ExtensionXPCShellUtils.jsm");
@ -72,12 +68,6 @@ const PLUGIN2_NAME = "Quicktime";
const PLUGIN2_DESC = "A mock Quicktime plugin"; const PLUGIN2_DESC = "A mock Quicktime plugin";
const PLUGIN2_VERSION = "2.3"; const PLUGIN2_VERSION = "2.3";
const PERSONA_ID = "3785";
// Defined by LightweightThemeManager, it is appended to the PERSONA_ID.
const PERSONA_ID_SUFFIX = "@personas.mozilla.org";
const PERSONA_NAME = "Test Theme";
const PERSONA_DESCRIPTION = "A nice theme/persona description.";
const PLUGIN_UPDATED_TOPIC = "plugins-list-updated"; const PLUGIN_UPDATED_TOPIC = "plugins-list-updated";
// system add-ons are enabled at startup, so record date when the test starts // system add-ons are enabled at startup, so record date when the test starts
@ -274,20 +264,6 @@ function createMockAddonProvider(aName) {
return mockProvider; return mockProvider;
} }
/**
* Used to spoof the Persona Id.
*/
function spoofTheme(aId, aName, aDesc) {
return {
id: aId,
name: aName,
description: aDesc,
headerURL: "http://lwttest.invalid/a.png",
textcolor: Math.random().toString(),
accentcolor: Math.random().toString(),
};
}
function spoofGfxAdapter() { function spoofGfxAdapter() {
try { try {
let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfoDebug); let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfoDebug);
@ -816,7 +792,6 @@ function checkActiveGMPlugin(data) {
function checkAddonsSection(data, expectBrokenAddons, partialAddonsRecords) { function checkAddonsSection(data, expectBrokenAddons, partialAddonsRecords) {
const EXPECTED_FIELDS = [ const EXPECTED_FIELDS = [
"activeAddons", "theme", "activePlugins", "activeGMPlugins", "activeAddons", "theme", "activePlugins", "activeGMPlugins",
"persona",
]; ];
Assert.ok("addons" in data, "There must be an addons section in Environment."); Assert.ok("addons" in data, "There must be an addons section in Environment.");
@ -848,9 +823,6 @@ function checkAddonsSection(data, expectBrokenAddons, partialAddonsRecords) {
for (let gmPlugin in activeGMPlugins) { for (let gmPlugin in activeGMPlugins) {
checkActiveGMPlugin(activeGMPlugins[gmPlugin]); checkActiveGMPlugin(activeGMPlugins[gmPlugin]);
} }
// Check persona
Assert.ok(checkNullOrString(data.addons.persona));
} }
function checkExperimentsSection(data) { function checkExperimentsSection(data) {
@ -902,10 +874,6 @@ add_task(async function setup() {
system_addon.lastModifiedTime = SYSTEM_ADDON_INSTALL_DATE; system_addon.lastModifiedTime = SYSTEM_ADDON_INSTALL_DATE;
loadAddonManager(APP_ID, APP_NAME, APP_VERSION, PLATFORM_VERSION); loadAddonManager(APP_ID, APP_NAME, APP_VERSION, PLATFORM_VERSION);
// Spoof the persona ID.
LightweightThemeManager.currentTheme =
spoofTheme(PERSONA_ID, PERSONA_NAME, PERSONA_DESCRIPTION);
// The test runs in a fresh profile so starting the AddonManager causes // The test runs in a fresh profile so starting the AddonManager causes
// the addons database to be created (as does setting new theme). // the addons database to be created (as does setting new theme).
// For test_addonsStartup below, we want to test a "warm" startup where // For test_addonsStartup below, we want to test a "warm" startup where
@ -1376,12 +1344,6 @@ add_task(async function test_addonsAndPlugins() {
await webextension.unload(); await webextension.unload();
// Check theme data.
let theme = data.addons.theme;
Assert.equal(theme.id, (PERSONA_ID + PERSONA_ID_SUFFIX));
Assert.equal(theme.name, PERSONA_NAME);
Assert.equal(theme.description, PERSONA_DESCRIPTION);
// Check plugin data. // Check plugin data.
Assert.equal(data.addons.activePlugins.length, 1, "We must have only one active plugin."); Assert.equal(data.addons.activePlugins.length, 1, "We must have only one active plugin.");
let targetPlugin = data.addons.activePlugins[0]; let targetPlugin = data.addons.activePlugins[0];
@ -1394,8 +1356,6 @@ add_task(async function test_addonsAndPlugins() {
Assert.ok(targetPlugin.mimeTypes.find(m => m == PLUGIN_MIME_TYPE2)); Assert.ok(targetPlugin.mimeTypes.find(m => m == PLUGIN_MIME_TYPE2));
Assert.ok(!targetPlugin.mimeTypes.find(m => m == "Not There.")); Assert.ok(!targetPlugin.mimeTypes.find(m => m == "Not There."));
Assert.equal(data.addons.persona, PERSONA_ID, "The correct Persona Id must be reported.");
// Uninstall the addon. // Uninstall the addon.
await addon.startupPromise; await addon.startupPromise;
await addon.uninstall(); await addon.uninstall();

Просмотреть файл

@ -11,7 +11,6 @@
const {CommonUtils} = ChromeUtils.import("resource://services-common/utils.js"); const {CommonUtils} = ChromeUtils.import("resource://services-common/utils.js");
const {ClientID} = ChromeUtils.import("resource://gre/modules/ClientID.jsm"); const {ClientID} = ChromeUtils.import("resource://gre/modules/ClientID.jsm");
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", this);
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this); ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", this); ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", this);
ChromeUtils.import("resource://gre/modules/TelemetrySession.jsm", this); ChromeUtils.import("resource://gre/modules/TelemetrySession.jsm", this);

Просмотреть файл

@ -5,9 +5,9 @@
// Simulates quickly switching between different list views to verify that only // Simulates quickly switching between different list views to verify that only
// the last selected is displayed // the last selected is displayed
var tempScope = {}; const {PromiseTestUtils} = ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm");
ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", tempScope);
var LightweightThemeManager = tempScope.LightweightThemeManager; PromiseTestUtils.whitelistRejectionsGlobally(/this\._errorLink/);
var gManagerWindow; var gManagerWindow;
var gCategoryUtilities; var gCategoryUtilities;
@ -15,13 +15,6 @@ var gCategoryUtilities;
async function test() { async function test() {
waitForExplicitFinish(); waitForExplicitFinish();
// Add a lightweight theme so at least one theme exists
LightweightThemeManager.currentTheme = {
id: "test",
name: "Test lightweight theme",
headerURL: "http://example.com/header.png",
};
let aWindow = await open_manager(null); let aWindow = await open_manager(null);
gManagerWindow = aWindow; gManagerWindow = aWindow;
gCategoryUtilities = new CategoryUtilities(gManagerWindow); gCategoryUtilities = new CategoryUtilities(gManagerWindow);
@ -30,7 +23,6 @@ async function test() {
async function end_test() { async function end_test() {
await close_manager(gManagerWindow); await close_manager(gManagerWindow);
LightweightThemeManager.forgetUsedTheme("test");
finish(); finish();
} }

Просмотреть файл

@ -5,25 +5,9 @@
// Bug 591465 - Context menu of add-ons miss context related state change entries // Bug 591465 - Context menu of add-ons miss context related state change entries
var tempScope = {};
ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", tempScope);
var LightweightThemeManager = tempScope.LightweightThemeManager;
var gManagerWindow; var gManagerWindow;
var gProvider; var gProvider;
var gContextMenu; var gContextMenu;
var gLWTheme = {
id: "4",
version: "1",
name: "Bling",
description: "SO MUCH BLING!",
author: "Pixel Pusher",
homepageURL: "http://mochi.test:8888/data/index.html",
headerURL: "http://mochi.test:8888/data/header.png",
previewURL: "http://mochi.test:8888/data/preview.png",
iconURL: "http://mochi.test:8888/data/icon.png",
};
add_task(async function setup() { add_task(async function setup() {
@ -222,85 +206,6 @@ add_test(function() {
}); });
add_test(function() {
LightweightThemeManager.currentTheme = gLWTheme;
var el = get_addon_element(gManagerWindow, "4@personas.mozilla.org");
gContextMenu.addEventListener("popupshown", function() {
check_contextmenu(true, true, false, false, false);
gContextMenu.hidePopup();
run_next_test();
}, {once: true});
info("Opening context menu on enabled LW theme item");
el.parentNode.ensureElementIsVisible(el);
EventUtils.synthesizeMouse(el, 4, 4, { }, gManagerWindow);
EventUtils.synthesizeMouse(el, 4, 4, { type: "contextmenu", button: 2 }, gManagerWindow);
});
add_test(function() {
LightweightThemeManager.currentTheme = null;
var el = get_addon_element(gManagerWindow, "4@personas.mozilla.org");
gContextMenu.addEventListener("popupshown", function() {
check_contextmenu(true, false, false, false, false);
gContextMenu.hidePopup();
run_next_test();
}, {once: true});
info("Opening context menu on disabled LW theme item");
el.parentNode.ensureElementIsVisible(el);
EventUtils.synthesizeMouse(el, 4, 4, { }, gManagerWindow);
EventUtils.synthesizeMouse(el, 4, 4, { type: "contextmenu", button: 2 }, gManagerWindow);
});
add_test(async function() {
LightweightThemeManager.currentTheme = gLWTheme;
gManagerWindow.loadView("addons://detail/4@personas.mozilla.org");
await wait_for_view_load(gManagerWindow);
gContextMenu.addEventListener("popupshown", function() {
check_contextmenu(true, true, false, true, false);
gContextMenu.hidePopup();
run_next_test();
}, {once: true});
info("Opening context menu on enabled LW theme, in detail view");
var el = gManagerWindow.document.querySelector("#detail-view .detail-view-container");
EventUtils.synthesizeMouse(el, 4, 4, { }, gManagerWindow);
EventUtils.synthesizeMouse(el, 4, 4, { type: "contextmenu", button: 2 }, gManagerWindow);
});
add_test(async function() {
LightweightThemeManager.currentTheme = null;
gManagerWindow.loadView("addons://detail/4@personas.mozilla.org");
await wait_for_view_load(gManagerWindow);
gContextMenu.addEventListener("popupshown", async function() {
check_contextmenu(true, false, false, true, false);
gContextMenu.hidePopup();
let aAddon = await AddonManager.getAddonByID("4@personas.mozilla.org");
aAddon.uninstall();
run_next_test();
}, {once: true});
info("Opening context menu on disabled LW theme, in detail view");
var el = gManagerWindow.document.querySelector("#detail-view .detail-view-container");
EventUtils.synthesizeMouse(el, 4, 4, { }, gManagerWindow);
EventUtils.synthesizeMouse(el, 4, 4, { type: "contextmenu", button: 2 }, gManagerWindow);
});
add_test(async function() { add_test(async function() {
gManagerWindow.loadView("addons://detail/addon1@tests.mozilla.org"); gManagerWindow.loadView("addons://detail/addon1@tests.mozilla.org");
await wait_for_view_load(gManagerWindow); await wait_for_view_load(gManagerWindow);

Просмотреть файл

@ -4,10 +4,11 @@
// Tests the list view // Tests the list view
var tempScope = {}; const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", tempScope);
var LightweightThemeManager = tempScope.LightweightThemeManager; const {PromiseTestUtils} = ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm");
const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
PromiseTestUtils.whitelistRejectionsGlobally(/this\._errorLink/);
var gProvider; var gProvider;
var gManagerWindow; var gManagerWindow;
@ -20,18 +21,6 @@ var infoURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsi
const EXPECTED_ADDONS = 11; const EXPECTED_ADDONS = 11;
var gLWTheme = {
id: "4",
version: "1",
name: "Bling",
description: "SO MUCH BLING!",
author: "Pixel Pusher",
homepageURL: "http://mochi.test:8888/data/index.html",
headerURL: "http://mochi.test:8888/data/header.png",
previewURL: "http://mochi.test:8888/data/preview.png",
iconURL: "http://mochi.test:8888/data/icon.png",
};
add_task(async function() { add_task(async function() {
gProvider = new MockProvider(); gProvider = new MockProvider();
@ -452,38 +441,6 @@ add_task(async function() {
}); });
function tick() {
return new Promise(SimpleTest.executeSoon);
}
add_task(async function() {
info("Enabling lightweight theme");
LightweightThemeManager.currentTheme = gLWTheme;
await tick();
gManagerWindow.loadView("addons://list/theme");
await new Promise(resolve => wait_for_view_load(gManagerWindow, resolve));
var addon = get_addon_element(gManagerWindow, "4@personas.mozilla.org");
is_element_hidden(get_node(addon, "preferences-btn"), "Preferences button should be hidden");
is_element_hidden(get_node(addon, "enable-btn"), "Enable button should be hidden");
is_element_visible(get_node(addon, "disable-btn"), "Disable button should be visible");
is_element_visible(get_node(addon, "remove-btn"), "Remove button should be visible");
info("Disabling lightweight theme");
LightweightThemeManager.currentTheme = null;
await tick();
is_element_hidden(get_node(addon, "preferences-btn"), "Preferences button should be hidden");
is_element_visible(get_node(addon, "enable-btn"), "Enable button should be hidden");
is_element_hidden(get_node(addon, "disable-btn"), "Disable button should be visible");
is_element_visible(get_node(addon, "remove-btn"), "Remove button should be visible");
let [aAddon] = await promiseAddonsByIDs(["4@personas.mozilla.org"]);
aAddon.uninstall();
});
// Check that onPropertyChanges for appDisabled updates the UI // Check that onPropertyChanges for appDisabled updates the UI
add_task(async function() { add_task(async function() {
info("Checking that onPropertyChanges for appDisabled updates the UI"); info("Checking that onPropertyChanges for appDisabled updates the UI");

Просмотреть файл

@ -1,615 +0,0 @@
const MANDATORY = ["id", "name"];
const OPTIONAL = ["headerURL", "textcolor", "accentcolor",
"iconURL", "previewURL", "author", "description",
"homepageURL", "updateURL", "version"];
const DEFAULT_THEME_ID = "default-theme@mozilla.org";
var LightweightThemeManager;
function dummy(id) {
return {
id: id || Math.random().toString(),
name: Math.random().toString(),
headerURL: "http://lwttest.invalid/a.png",
textcolor: Math.random().toString(),
accentcolor: Math.random().toString(),
};
}
function tick() {
return new Promise(executeSoon);
}
function setTheme(theme) {
LightweightThemeManager.currentTheme = theme;
return tick();
}
function hasPermission(aAddon, aPerm) {
var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
return !!(aAddon.permissions & perm);
}
add_task(async function run_test() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
await promiseStartupManager();
Services.prefs.setIntPref("lightweightThemes.maxUsedThemes", 8);
({LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm"));
let ltm = LightweightThemeManager;
Assert.equal(typeof ltm, "object");
Assert.equal(typeof ltm.usedThemes, "object");
Assert.equal(ltm.usedThemes.length, 1);
Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
ltm.previewTheme(dummy("preview0"));
Assert.equal(ltm.usedThemes.length, 1);
Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
ltm.previewTheme(dummy("preview1"));
Assert.equal(ltm.usedThemes.length, 1);
Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
ltm.resetPreview();
await setTheme(dummy("x0"));
Assert.equal(ltm.usedThemes.length, 2);
Assert.equal(ltm.currentTheme.id, "x0");
Assert.equal(ltm.usedThemes[0].id, "x0");
Assert.equal(ltm.getUsedTheme("x0").id, "x0");
ltm.previewTheme(dummy("preview0"));
Assert.equal(ltm.usedThemes.length, 2);
Assert.equal(ltm.currentTheme.id, "x0");
ltm.resetPreview();
Assert.equal(ltm.usedThemes.length, 2);
Assert.equal(ltm.currentTheme.id, "x0");
await setTheme(dummy("x1"));
Assert.equal(ltm.usedThemes.length, 3);
Assert.equal(ltm.currentTheme.id, "x1");
Assert.equal(ltm.usedThemes[1].id, "x0");
await setTheme(dummy("x2"));
Assert.equal(ltm.usedThemes.length, 4);
Assert.equal(ltm.currentTheme.id, "x2");
Assert.equal(ltm.usedThemes[1].id, "x1");
Assert.equal(ltm.usedThemes[2].id, "x0");
ltm.currentTheme = dummy("x3");
ltm.currentTheme = dummy("x4");
ltm.currentTheme = dummy("x5");
ltm.currentTheme = dummy("x6");
ltm.currentTheme = dummy("x7");
await tick();
Assert.equal(ltm.usedThemes.length, 9);
Assert.equal(ltm.currentTheme.id, "x7");
Assert.equal(ltm.usedThemes[1].id, "x6");
Assert.equal(ltm.usedThemes[7].id, "x0");
await setTheme(dummy("x8"));
Assert.equal(ltm.usedThemes.length, 9);
Assert.equal(ltm.currentTheme.id, "x8");
Assert.equal(ltm.usedThemes[1].id, "x7");
Assert.equal(ltm.usedThemes[7].id, "x1");
Assert.equal(ltm.getUsedTheme("x0"), null);
ltm.forgetUsedTheme("nonexistent");
Assert.equal(ltm.usedThemes.length, 9);
Assert.notEqual(ltm.currentTheme.id, DEFAULT_THEME_ID);
ltm.forgetUsedTheme("x8");
await tick();
Assert.equal(ltm.usedThemes.length, 8);
Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
Assert.equal(ltm.usedThemes[0].id, "x7");
Assert.equal(ltm.usedThemes[6].id, "x1");
ltm.forgetUsedTheme("x7");
ltm.forgetUsedTheme("x6");
ltm.forgetUsedTheme("x5");
ltm.forgetUsedTheme("x4");
ltm.forgetUsedTheme("x3");
Assert.equal(ltm.usedThemes.length, 3);
Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
Assert.equal(ltm.usedThemes[0].id, "x2");
Assert.equal(ltm.usedThemes[1].id, "x1");
await setTheme(dummy("x1"));
Assert.equal(ltm.usedThemes.length, 3);
Assert.equal(ltm.currentTheme.id, "x1");
Assert.equal(ltm.usedThemes[0].id, "x1");
Assert.equal(ltm.usedThemes[1].id, "x2");
await setTheme(dummy("x2"));
Assert.equal(ltm.usedThemes.length, 3);
Assert.equal(ltm.currentTheme.id, "x2");
Assert.equal(ltm.usedThemes[0].id, "x2");
Assert.equal(ltm.usedThemes[1].id, "x1");
await setTheme(ltm.getUsedTheme("x1"));
Assert.equal(ltm.usedThemes.length, 3);
Assert.equal(ltm.currentTheme.id, "x1");
Assert.equal(ltm.usedThemes[0].id, "x1");
Assert.equal(ltm.usedThemes[1].id, "x2");
ltm.forgetUsedTheme("x1");
ltm.forgetUsedTheme("x2");
await tick();
Assert.equal(ltm.usedThemes.length, 1);
Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
// Use chinese name to test utf-8, for bug #541943
var chineseTheme = dummy("chinese0");
chineseTheme.name = "笢恅0";
chineseTheme.description = "笢恅1";
await setTheme(chineseTheme);
Assert.equal(ltm.usedThemes.length, 2);
Assert.equal(ltm.currentTheme.name, "笢恅0");
Assert.equal(ltm.currentTheme.description, "笢恅1");
Assert.equal(ltm.usedThemes[0].name, "笢恅0");
Assert.equal(ltm.usedThemes[0].description, "笢恅1");
Assert.equal(ltm.getUsedTheme("chinese0").name, "笢恅0");
Assert.equal(ltm.getUsedTheme("chinese0").description, "笢恅1");
// This name used to break the usedTheme JSON causing all LWTs to be lost
var chineseTheme1 = dummy("chinese1");
chineseTheme1.name = "眵昜湮桵蔗坌~郔乾";
chineseTheme1.description = "眵昜湮桵蔗坌~郔乾";
await setTheme(chineseTheme1);
Assert.notEqual(ltm.currentTheme.id, DEFAULT_THEME_ID);
Assert.equal(ltm.usedThemes.length, 3);
Assert.equal(ltm.currentTheme.name, "眵昜湮桵蔗坌~郔乾");
Assert.equal(ltm.currentTheme.description, "眵昜湮桵蔗坌~郔乾");
Assert.equal(ltm.usedThemes[1].name, "笢恅0");
Assert.equal(ltm.usedThemes[1].description, "笢恅1");
Assert.equal(ltm.usedThemes[0].name, "眵昜湮桵蔗坌~郔乾");
Assert.equal(ltm.usedThemes[0].description, "眵昜湮桵蔗坌~郔乾");
ltm.forgetUsedTheme("chinese0");
await tick();
Assert.equal(ltm.usedThemes.length, 2);
Assert.notEqual(ltm.currentTheme.id, DEFAULT_THEME_ID);
ltm.forgetUsedTheme("chinese1");
await tick();
Assert.equal(ltm.usedThemes.length, 1);
Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
Assert.equal(ltm.parseTheme("invalid json"), null);
Assert.equal(ltm.parseTheme('"json string"'), null);
function roundtrip(data, secure) {
return ltm.parseTheme(JSON.stringify(data),
"http" + (secure ? "s" : "") + "://lwttest.invalid/");
}
var data = dummy();
Assert.notEqual(roundtrip(data), null);
data.id = null;
Assert.equal(roundtrip(data), null);
data.id = 1;
Assert.equal(roundtrip(data), null);
data.id = 1.5;
Assert.equal(roundtrip(data), null);
data.id = true;
Assert.equal(roundtrip(data), null);
data.id = {};
Assert.equal(roundtrip(data), null);
data.id = [];
Assert.equal(roundtrip(data), null);
// Check whether parseTheme handles international characters right
var chineseTheme2 = dummy();
chineseTheme2.name = "眵昜湮桵蔗坌~郔乾";
chineseTheme2.description = "眵昜湮桵蔗坌~郔乾";
Assert.notEqual(roundtrip(chineseTheme2), null);
Assert.equal(roundtrip(chineseTheme2).name, "眵昜湮桵蔗坌~郔乾");
Assert.equal(roundtrip(chineseTheme2).description, "眵昜湮桵蔗坌~郔乾");
data = dummy();
data.unknownProperty = "Foo";
Assert.equal(typeof roundtrip(data).unknownProperty, "undefined");
data = dummy();
data.unknownURL = "http://lwttest.invalid/";
Assert.equal(typeof roundtrip(data).unknownURL, "undefined");
function roundtripSet(props, modify, test, secure) {
props.forEach(function(prop) {
var theme = dummy();
modify(theme, prop);
test(roundtrip(theme, secure), prop, theme);
});
}
roundtripSet(MANDATORY, function(theme, prop) {
delete theme[prop];
}, function(after) {
Assert.equal(after, null);
});
roundtripSet(OPTIONAL, function(theme, prop) {
delete theme[prop];
}, function(after) {
Assert.notEqual(after, null);
});
roundtripSet(MANDATORY, function(theme, prop) {
theme[prop] = "";
}, function(after) {
Assert.equal(after, null);
});
roundtripSet(OPTIONAL, function(theme, prop) {
theme[prop] = "";
}, function(after, prop) {
Assert.equal(typeof after[prop], "undefined");
});
roundtripSet(MANDATORY, function(theme, prop) {
theme[prop] = " ";
}, function(after) {
Assert.equal(after, null);
});
roundtripSet(OPTIONAL, function(theme, prop) {
theme[prop] = " ";
}, function(after, prop) {
Assert.notEqual(after, null);
Assert.equal(typeof after[prop], "undefined");
});
function non_urls(props) {
return props.filter(prop => !/URL$/.test(prop));
}
function urls(props) {
return props.filter(prop => /URL$/.test(prop));
}
roundtripSet(non_urls(MANDATORY.concat(OPTIONAL)), function(theme, prop) {
theme[prop] = prop;
}, function(after, prop, before) {
Assert.equal(after[prop], before[prop]);
});
roundtripSet(non_urls(MANDATORY.concat(OPTIONAL)), function(theme, prop) {
theme[prop] = " " + prop + " ";
}, function(after, prop, before) {
Assert.equal(after[prop], before[prop].trim());
});
roundtripSet(urls(MANDATORY.concat(OPTIONAL)), function(theme, prop) {
theme[prop] = Math.random().toString();
}, function(after, prop, before) {
if (prop == "updateURL")
Assert.equal(typeof after[prop], "undefined");
else
Assert.equal(after[prop], "http://lwttest.invalid/" + before[prop]);
});
roundtripSet(urls(MANDATORY.concat(OPTIONAL)), function(theme, prop) {
theme[prop] = Math.random().toString();
}, function(after, prop, before) {
Assert.equal(after[prop], "https://lwttest.invalid/" + before[prop]);
}, true);
roundtripSet(urls(MANDATORY.concat(OPTIONAL)), function(theme, prop) {
theme[prop] = "https://sub.lwttest.invalid/" + Math.random().toString();
}, function(after, prop, before) {
Assert.equal(after[prop], before[prop]);
});
roundtripSet(urls(MANDATORY), function(theme, prop) {
theme[prop] = "ftp://lwttest.invalid/" + Math.random().toString();
}, function(after) {
Assert.equal(after, null);
});
roundtripSet(urls(OPTIONAL), function(theme, prop) {
theme[prop] = "ftp://lwttest.invalid/" + Math.random().toString();
}, function(after, prop) {
Assert.equal(typeof after[prop], "undefined");
});
Assert.equal(ltm.usedThemes.length, 1);
Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
data = dummy();
delete data.name;
try {
ltm.currentTheme = data;
do_throw("Should have rejected a theme with no name");
} catch (e) {
// Expected exception
}
// Sanitize themes with a bad headerURL
data = dummy();
data.headerURL = "foo";
await setTheme(data);
Assert.equal(ltm.usedThemes.length, 2);
Assert.equal(ltm.currentTheme.headerURL, undefined);
ltm.forgetUsedTheme(ltm.currentTheme.id);
await tick();
Assert.equal(ltm.usedThemes.length, 1);
// Sanitize themes with a non-http(s) headerURL
data = dummy();
data.headerURL = "ftp://lwtest.invalid/test.png";
await setTheme(data);
Assert.equal(ltm.usedThemes.length, 2);
Assert.equal(ltm.currentTheme.headerURL, undefined);
ltm.forgetUsedTheme(ltm.currentTheme.id);
await tick();
Assert.equal(ltm.usedThemes.length, 1);
// Sanitize themes with a non-http(s) headerURL
data = dummy();
data.headerURL = "file:///test.png";
await setTheme(data);
Assert.equal(ltm.usedThemes.length, 2);
Assert.equal(ltm.currentTheme.headerURL, undefined);
ltm.forgetUsedTheme(ltm.currentTheme.id);
await tick();
Assert.equal(ltm.usedThemes.length, 1);
data = dummy();
data.updateURL = "file:///test.json";
ltm.setLocalTheme(data);
await tick();
Assert.equal(ltm.usedThemes.length, 2);
Assert.equal(ltm.currentTheme.updateURL, undefined);
ltm.forgetUsedTheme(ltm.currentTheme.id);
await tick();
Assert.equal(ltm.usedThemes.length, 1);
data = dummy();
data.headerURL = "file:///test.png";
ltm.setLocalTheme(data);
await tick();
Assert.equal(ltm.usedThemes.length, 2);
Assert.equal(ltm.currentTheme.headerURL, "file:///test.png");
ltm.forgetUsedTheme(ltm.currentTheme.id);
await tick();
Assert.equal(ltm.usedThemes.length, 1);
data = dummy();
data.headerURL = "ftp://lwtest.invalid/test.png";
ltm.setLocalTheme(data);
await tick();
Assert.equal(ltm.usedThemes.length, 2);
Assert.equal(ltm.currentTheme.updateURL, undefined);
ltm.forgetUsedTheme(ltm.currentTheme.id);
await tick();
Assert.equal(ltm.usedThemes.length, 1);
data = dummy();
delete data.id;
try {
ltm.currentTheme = data;
do_throw("Should have rejected a theme with no ID");
} catch (e) {
// Expected exception
}
Assert.equal(ltm.usedThemes.length, 1);
Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
// Force the theme into the prefs anyway
let themes = [data];
Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
Assert.equal(ltm.usedThemes.length, 2);
// This should silently drop the bad theme.
await setTheme(dummy());
Assert.equal(ltm.usedThemes.length, 2);
ltm.forgetUsedTheme(ltm.currentTheme.id);
await tick();
Assert.equal(ltm.usedThemes.length, 1);
Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
// Add one broken and some working.
themes = [data, dummy("x1"), dummy("x2")];
Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
Assert.equal(ltm.usedThemes.length, 4);
// Switching to an existing theme should drop the bad theme.
await setTheme(ltm.getUsedTheme("x1"));
Assert.equal(ltm.usedThemes.length, 3);
ltm.forgetUsedTheme("x1");
ltm.forgetUsedTheme("x2");
await tick();
Assert.equal(ltm.usedThemes.length, 1);
Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
Assert.equal(ltm.usedThemes.length, 4);
// Forgetting an existing theme should drop the bad theme.
ltm.forgetUsedTheme("x1");
await tick();
Assert.equal(ltm.usedThemes.length, 2);
ltm.forgetUsedTheme("x2");
await tick();
Assert.equal(ltm.usedThemes.length, 1);
Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
// Test whether a JSON set with setCharPref can be retrieved with usedThemes
ltm.currentTheme = dummy("x0");
ltm.currentTheme = dummy("x1");
await tick();
Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(ltm.usedThemes));
Assert.equal(ltm.usedThemes.length, 4);
Assert.equal(ltm.currentTheme.id, "x1");
Assert.equal(ltm.usedThemes[1].id, "x0");
Assert.equal(ltm.usedThemes[0].id, "x1");
ltm.forgetUsedTheme("x0");
await tick();
Assert.equal(ltm.usedThemes.length, 2);
Assert.notEqual(ltm.currentTheme.id, DEFAULT_THEME_ID);
ltm.forgetUsedTheme("x1");
await tick();
Assert.equal(ltm.usedThemes.length, 1);
Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
Services.prefs.clearUserPref("lightweightThemes.maxUsedThemes");
ltm.currentTheme = dummy("x1");
ltm.currentTheme = dummy("x2");
ltm.currentTheme = dummy("x3");
ltm.currentTheme = dummy("x4");
ltm.currentTheme = dummy("x5");
ltm.currentTheme = dummy("x6");
ltm.currentTheme = dummy("x7");
ltm.currentTheme = dummy("x8");
ltm.currentTheme = dummy("x9");
ltm.currentTheme = dummy("x10");
ltm.currentTheme = dummy("x11");
ltm.currentTheme = dummy("x12");
ltm.currentTheme = dummy("x13");
ltm.currentTheme = dummy("x14");
ltm.currentTheme = dummy("x15");
ltm.currentTheme = dummy("x16");
ltm.currentTheme = dummy("x17");
ltm.currentTheme = dummy("x18");
ltm.currentTheme = dummy("x19");
ltm.currentTheme = dummy("x20");
ltm.currentTheme = dummy("x21");
ltm.currentTheme = dummy("x22");
ltm.currentTheme = dummy("x23");
ltm.currentTheme = dummy("x24");
ltm.currentTheme = dummy("x25");
ltm.currentTheme = dummy("x26");
ltm.currentTheme = dummy("x27");
ltm.currentTheme = dummy("x28");
ltm.currentTheme = dummy("x29");
ltm.currentTheme = dummy("x30");
await tick();
Assert.equal(ltm.usedThemes.length, 31);
ltm.currentTheme = dummy("x31");
await tick();
Assert.equal(ltm.usedThemes.length, 31);
Assert.equal(ltm.getUsedTheme("x1"), null);
Services.prefs.setIntPref("lightweightThemes.maxUsedThemes", 15);
Assert.equal(ltm.usedThemes.length, 16);
Services.prefs.setIntPref("lightweightThemes.maxUsedThemes", 32);
ltm.currentTheme = dummy("x1");
ltm.currentTheme = dummy("x2");
ltm.currentTheme = dummy("x3");
ltm.currentTheme = dummy("x4");
ltm.currentTheme = dummy("x5");
ltm.currentTheme = dummy("x6");
ltm.currentTheme = dummy("x7");
ltm.currentTheme = dummy("x8");
ltm.currentTheme = dummy("x9");
ltm.currentTheme = dummy("x10");
ltm.currentTheme = dummy("x11");
ltm.currentTheme = dummy("x12");
ltm.currentTheme = dummy("x13");
ltm.currentTheme = dummy("x14");
ltm.currentTheme = dummy("x15");
ltm.currentTheme = dummy("x16");
ltm.currentTheme = dummy("x32");
await tick();
Assert.equal(ltm.usedThemes.length, 33);
ltm.currentTheme = dummy("x33");
await tick();
Assert.equal(ltm.usedThemes.length, 33);
Services.prefs.clearUserPref("lightweightThemes.maxUsedThemes");
Assert.equal(ltm.usedThemes.length, 31);
let usedThemes = ltm.usedThemes;
for (let theme of usedThemes) {
ltm.forgetUsedTheme(theme.id);
}
// Check builtInTheme functionality for Bug 1094821
Assert.equal(ltm._builtInThemes.toString(), "[object Map]");
Assert.equal([...ltm._builtInThemes.entries()].length, 1);
Assert.equal(ltm.usedThemes.length, 1);
ltm.addBuiltInTheme(dummy("builtInTheme0"));
Assert.equal([...ltm._builtInThemes].length, 2);
Assert.equal(ltm.usedThemes.length, 2);
Assert.equal(ltm.usedThemes[1].id, "builtInTheme0");
ltm.addBuiltInTheme(dummy("builtInTheme1"));
Assert.equal([...ltm._builtInThemes].length, 3);
Assert.equal(ltm.usedThemes.length, 3);
Assert.equal(ltm.usedThemes[2].id, "builtInTheme1");
// Clear all and then re-add
ltm.clearBuiltInThemes();
Assert.equal([...ltm._builtInThemes].length, 0);
Assert.equal(ltm.usedThemes.length, 0);
ltm.addBuiltInTheme(dummy("builtInTheme0"));
ltm.addBuiltInTheme(dummy("builtInTheme1"));
Assert.equal([...ltm._builtInThemes].length, 2);
Assert.equal(ltm.usedThemes.length, 2);
let builtInThemeAddon = await AddonManager.getAddonByID("builtInTheme0@personas.mozilla.org");
Assert.equal(hasPermission(builtInThemeAddon, "uninstall"), false);
Assert.equal(hasPermission(builtInThemeAddon, "disable"), false);
Assert.equal(hasPermission(builtInThemeAddon, "enable"), true);
await setTheme(dummy("x0"));
Assert.equal([...ltm._builtInThemes].length, 2);
Assert.equal(ltm.usedThemes.length, 3);
Assert.equal(ltm.usedThemes[0].id, "x0");
Assert.equal(ltm.currentTheme.id, "x0");
Assert.equal(ltm.usedThemes[1].id, "builtInTheme0");
Assert.equal(ltm.usedThemes[2].id, "builtInTheme1");
Assert.throws(() => { ltm.addBuiltInTheme(dummy("builtInTheme0")); },
/Error: Trying to add invalid builtIn theme/,
"Exception is thrown adding a duplicate theme");
Assert.throws(() => { ltm.addBuiltInTheme("not a theme object"); },
/Error: Trying to add invalid builtIn theme/,
"Exception is thrown adding an invalid theme");
let x0Addon = await AddonManager.getAddonByID("x0@personas.mozilla.org");
Assert.equal(hasPermission(x0Addon, "uninstall"), true);
Assert.equal(hasPermission(x0Addon, "disable"), true);
Assert.equal(hasPermission(x0Addon, "enable"), false);
ltm.forgetUsedTheme("x0");
await tick();
Assert.equal(ltm.currentTheme, null);
// Removing the currently applied app specific theme should unapply it
await setTheme(ltm.getUsedTheme("builtInTheme0"));
Assert.equal(ltm.currentTheme.id, "builtInTheme0");
Assert.ok(ltm.forgetBuiltInTheme("builtInTheme0"));
Assert.equal(ltm.currentTheme, null);
Assert.equal([...ltm._builtInThemes].length, 1);
Assert.equal(ltm.usedThemes.length, 1);
Assert.ok(ltm.forgetBuiltInTheme("builtInTheme1"));
Assert.ok(!ltm.forgetBuiltInTheme("not-an-existing-theme-id"));
Assert.equal([...ltm._builtInThemes].length, 0);
Assert.equal(ltm.usedThemes.length, 0);
Assert.equal(ltm.currentTheme, null);
});

Просмотреть файл

@ -1,182 +0,0 @@
"use strict";
const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
add_task(async function test_theme_updates() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
const server = createHttpServer({hosts: ["example.com"]});
let updates = {};
server.registerPrefixHandler("/", (request, response) => {
let update = updates[request.path];
if (update) {
response.setHeader("content-type", "application/json");
response.write(JSON.stringify(update));
} else {
response.setStatusLine("1.1", 404, "Not Found");
}
});
// Themes 1 and 2 will be updated to xpi themes, theme 3 will
// (attempt to) be updated to a regular webextension.
let theme1XPI = createTempWebExtensionFile({
manifest: {
name: "Theme 1",
version: "2.0",
theme: {
images: { headerURL: "webextension.png" },
},
},
});
server.registerFile("/theme1.xpi", theme1XPI);
let theme2XPI = createTempWebExtensionFile({
manifest: {
name: "Theme 2",
version: "2.0",
theme: {
images: { headerURL: "webextension.png" },
},
},
});
server.registerFile("/theme2.xpi", theme2XPI);
let theme3XPI = createTempWebExtensionFile({
manifest: {
name: "Theme 3",
version: "2.0",
},
});
server.registerFile("/theme3.xpi", theme3XPI);
await promiseStartupManager();
function makeTheme(n, {image = "orig.png", version = "1.0"} = {}) {
return {
id: `theme${n}`,
name: `Theme ${n}`,
version,
updateURL: `http://example.com/${n}`,
headerURL: `http://example.com/${image}`,
textcolor: "#000000",
accentcolor: "#ffffff",
};
}
// "Install" some LWTs. Leave theme 2 as current
LightweightThemeManager.currentTheme = makeTheme(1);
LightweightThemeManager.currentTheme = makeTheme(3);
LightweightThemeManager.currentTheme = makeTheme(2);
function filterThemes(themes) {
return themes.filter(t => t.id != "default-theme@mozilla.org");
}
equal(filterThemes(LightweightThemeManager.usedThemes).length, 3,
"Have 3 lightweight themes");
// Update URLs are all 404, nothing should change
await AddonManagerPrivate.backgroundUpdateCheck();
equal(filterThemes(LightweightThemeManager.usedThemes).length, 3,
"Have 3 lightweight themes");
// Make updates to all LWTs available, only the active on should be updated
updates["/1"] = makeTheme(1, {image: "new.png", version: "1.1"});
updates["/2"] = makeTheme(2, {image: "new.png", version: "1.1"});
updates["/3"] = makeTheme(3, {image: "new.png", version: "1.1"});
await AddonManagerPrivate.backgroundUpdateCheck();
equal(LightweightThemeManager.currentTheme.id, "theme2",
"Theme 2 is currently selected");
let lwThemes = filterThemes(LightweightThemeManager.usedThemes);
let theme1 = lwThemes.find(t => t.id == "theme1");
notEqual(theme1, undefined, "Found theme1");
ok(theme1.headerURL.endsWith("orig.png"), "LWT 1 was not updated");
let theme2 = lwThemes.find(t => t.id == "theme2");
notEqual(theme2, undefined, "Found theme2");
dump(`theme2 ${JSON.stringify(theme2)}\n`);
ok(theme2.headerURL.endsWith("new.png"), "LWT 2 was updated");
let theme3 = lwThemes.find(t => t.id == "theme3");
notEqual(theme3, undefined, "Found theme3");
ok(theme3.headerURL.endsWith("orig.png"), "LWT 3 was not updated");
// Update an inactive LWT to an XPI packaged theme
updates["/1"] = {
converted_theme: {
url: "http://example.com/theme1.xpi",
hash: do_get_file_hash(theme1XPI),
},
};
await AddonManagerPrivate.backgroundUpdateCheck();
let themes = filterThemes(await AddonManager.getAddonsByTypes(["theme"]));
equal(themes.length, 3, "Still have a total of 3 themes");
equal(filterThemes(LightweightThemeManager.usedThemes).length, 2,
"Have 2 lightweight themes");
theme1 = themes.find(t => t.name == "Theme 1");
notEqual(theme1, undefined, "Found theme1");
ok(!theme1.id.endsWith("@personas.mozilla.org"),
"Theme 1 has been updated to an XPI packaged theme");
equal(theme1.userDisabled, true, "Theme 1 is not active");
Assert.deepEqual(theme1.installTelemetryInfo, {
source: "lwt-converted-theme",
}, "Got the expected source on the converted webextension theme");
// Update the current LWT to an XPI
updates["/2"] = {
converted_theme: {
url: "http://example.com/theme2.xpi",
hash: do_get_file_hash(theme2XPI),
},
};
await AddonManagerPrivate.backgroundUpdateCheck();
themes = filterThemes(await AddonManager.getAddonsByTypes(["theme"]));
equal(themes.length, 3, "Still have a total of 3 themes");
equal(filterThemes(LightweightThemeManager.usedThemes).length, 1,
"Have 1 lightweight theme");
theme2 = themes.find(t => t.name == "Theme 2");
notEqual(theme2, undefined, "Found theme2");
ok(!theme2.id.endsWith("@personas.mozilla.org"),
"Theme 2 has been updated to an XPI packaged theme");
equal(theme2.userDisabled, false, "Theme 2 is active");
Assert.deepEqual(theme2.installTelemetryInfo, {
source: "lwt-converted-theme",
}, "Got the expected source on the converted webextension theme");
// Serve an update with a bad hash, the LWT should remain in place.
updates["/3"] = {
converted_theme: {
url: "http://example.com/theme3.xpi",
hash: "sha256:abcd",
},
};
await AddonManagerPrivate.backgroundUpdateCheck();
equal(filterThemes(LightweightThemeManager.usedThemes).length, 1,
"Still have 1 lightweight theme");
// Try updating to a regular (non-theme) webextension, the
// LWT should remain in place.
updates["/3"] = {
converted_theme: {
url: "http://example.com/theme3.xpi",
hash: do_get_file_hash(theme3XPI),
},
};
await AddonManagerPrivate.backgroundUpdateCheck();
equal(filterThemes(LightweightThemeManager.usedThemes).length, 1,
"Still have 1 lightweight theme");
await promiseShutdownManager();
});

Просмотреть файл

@ -11,10 +11,6 @@ Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
// doesn't support lightweight themes. // doesn't support lightweight themes.
Services.prefs.setBoolPref("lightweightThemes.update.enabled", true); Services.prefs.setBoolPref("lightweightThemes.update.enabled", true);
const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
var gInstallDate;
const updateFile = "test_update.json"; const updateFile = "test_update.json";
const profileDir = gProfD.clone(); const profileDir = gProfD.clone();
@ -771,153 +767,6 @@ add_task(async function test_no_auto_update() {
await a8.uninstall(); await a8.uninstall();
}); });
// Test that background update checks work for lightweight themes
add_task(async function test_lwt_update() {
LightweightThemeManager.currentTheme = {
id: "1",
version: "1",
name: "Test LW Theme",
description: "A test theme",
author: "Mozilla",
homepageURL: "http://example.com/data/index.html",
headerURL: "http://example.com/data/header.png",
previewURL: "http://example.com/data/preview.png",
iconURL: "http://example.com/data/icon.png",
updateURL: "http://example.com/data/lwtheme.js",
};
// XXX The lightweight theme manager strips non-https updateURLs so hack it
// back in.
let themes = JSON.parse(Services.prefs.getCharPref("lightweightThemes.usedThemes"));
equal(themes.length, 1);
themes[0].updateURL = "http://example.com/data/lwtheme.js";
Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
testserver.registerPathHandler("/data/lwtheme.js", function(request, response) {
// Server will specify an expiry in one year.
let expiry = new Date();
expiry.setFullYear(expiry.getFullYear() + 1);
response.setHeader("Expires", expiry.toUTCString(), false);
response.write(JSON.stringify({
id: "1",
version: "2",
name: "Updated Theme",
description: "A test theme",
author: "Mozilla",
homepageURL: "http://example.com/data/index2.html",
headerURL: "http://example.com/data/header.png",
previewURL: "http://example.com/data/preview.png",
iconURL: "http://example.com/data/icon2.png",
updateURL: "http://example.com/data/lwtheme.js",
}));
});
let p1 = await AddonManager.getAddonByID("1@personas.mozilla.org");
notEqual(p1, null);
equal(p1.version, "1");
equal(p1.name, "Test LW Theme");
ok(p1.isActive);
equal(p1.installDate.getTime(), p1.updateDate.getTime());
// 5 seconds leeway seems like a lot, but tests can run slow and really if
// this is within 5 seconds it is fine. If it is going to be wrong then it
// is likely to be hours out at least
ok((Date.now() - p1.installDate.getTime()) < 5000);
gInstallDate = p1.installDate.getTime();
await new Promise(resolve => {
prepare_test({
"1@personas.mozilla.org": [
["onInstalling", false],
"onInstalled",
],
}, [
"onExternalInstall",
], resolve);
AddonManagerInternal.backgroundUpdateCheck();
});
p1 = await AddonManager.getAddonByID("1@personas.mozilla.org");
notEqual(p1, null);
equal(p1.version, "2");
equal(p1.name, "Updated Theme");
equal(p1.installDate.getTime(), gInstallDate);
ok(p1.installDate.getTime() < p1.updateDate.getTime());
// 5 seconds leeway seems like a lot, but tests can run slow and really if
// this is within 5 seconds it is fine. If it is going to be wrong then it
// is likely to be hours out at least
ok((Date.now() - p1.updateDate.getTime()) < 5000);
gInstallDate = p1.installDate.getTime();
});
// Test that background update checks for lightweight themes do not use the cache
// The update body from test 7 shouldn't be used since the cache should be bypassed.
add_task(async function() {
// XXX The lightweight theme manager strips non-https updateURLs so hack it
// back in.
let themes = JSON.parse(Services.prefs.getCharPref("lightweightThemes.usedThemes"));
equal(themes.length, 1);
themes[0].updateURL = "http://example.com/data/lwtheme.js";
Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
testserver.registerPathHandler("/data/lwtheme.js", function(request, response) {
response.write(JSON.stringify({
id: "1",
version: "3",
name: "Updated Theme v.3",
description: "A test theme v.3",
author: "John Smith",
homepageURL: "http://example.com/data/index3.html?v=3",
headerURL: "http://example.com/data/header.png?v=3",
previewURL: "http://example.com/data/preview.png?v=3",
iconURL: "http://example.com/data/icon2.png?v=3",
updateURL: "https://example.com/data/lwtheme.js?v=3",
}));
});
let p1 = await AddonManager.getAddonByID("1@personas.mozilla.org");
notEqual(p1, null);
equal(p1.version, "2");
equal(p1.name, "Updated Theme");
ok(p1.isActive);
equal(p1.installDate.getTime(), gInstallDate);
ok(p1.installDate.getTime() < p1.updateDate.getTime());
await new Promise(resolve => {
prepare_test({
"1@personas.mozilla.org": [
["onInstalling", false],
"onInstalled",
],
}, [
"onExternalInstall",
], resolve);
AddonManagerInternal.backgroundUpdateCheck();
});
p1 = await AddonManager.getAddonByID("1@personas.mozilla.org");
let currentTheme = LightweightThemeManager.currentTheme;
notEqual(p1, null);
equal(p1.version, "3");
equal(p1.name, "Updated Theme v.3");
equal(p1.description, "A test theme v.3");
info(JSON.stringify(p1));
equal(p1.creator.name, "John Smith");
equal(p1.homepageURL, "http://example.com/data/index3.html?v=3");
equal(p1.screenshots[0].url, "http://example.com/data/preview.png?v=3");
equal(p1.iconURL, "http://example.com/data/icon2.png?v=3");
equal(currentTheme.headerURL, "http://example.com/data/header.png?v=3");
equal(currentTheme.updateURL, "https://example.com/data/lwtheme.js?v=3");
equal(p1.installDate.getTime(), gInstallDate);
ok(p1.installDate.getTime() < p1.updateDate.getTime());
});
// Test that the update check returns nothing for addons in locked install // Test that the update check returns nothing for addons in locked install
// locations. // locations.
add_task(async function run_test_locked_install() { add_task(async function run_test_locked_install() {

Просмотреть файл

@ -8,13 +8,12 @@
* Coverage may overlap with other tests in this folder. * Coverage may overlap with other tests in this folder.
*/ */
ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
"resource://gre/modules/LightweightThemeManager.jsm");
const THEME_IDS = [ const THEME_IDS = [
"theme3@tests.mozilla.org", "theme3@tests.mozilla.org",
"theme2@personas.mozilla.org", "theme2@personas.mozilla.org", // Unused. Legacy. Evil.
"default-theme@mozilla.org", "default-theme@mozilla.org",
]; ];
const REAL_THEME_IDS = [THEME_IDS[0], THEME_IDS[2]];
const DEFAULT_THEME = THEME_IDS[2]; const DEFAULT_THEME = THEME_IDS[2];
const profileDir = gProfD.clone(); const profileDir = gProfD.clone();
@ -41,38 +40,20 @@ add_task(async function setup_to_default_browserish_state() {
await promiseStartupManager(); await promiseStartupManager();
// We can add an LWT only after the Addon Manager was started.
LightweightThemeManager.currentTheme = {
id: THEME_IDS[1].substr(0, THEME_IDS[1].indexOf("@")),
version: "1",
name: "Bling",
description: "SO MUCH BLING!",
author: "Pixel Pusher",
homepageURL: "http://localhost:8888/data/index.html",
headerURL: "http://localhost:8888/data/header.png",
previewURL: "http://localhost:8888/data/preview.png",
iconURL: "http://localhost:8888/data/icon.png",
textcolor: Math.random().toString(),
accentcolor: Math.random().toString(),
};
let [ t1, t2, d ] = await promiseAddonsByIDs(THEME_IDS); let [ t1, t2, d ] = await promiseAddonsByIDs(THEME_IDS);
Assert.ok(t1, "Theme addon should exist"); Assert.ok(t1, "Theme addon should exist");
Assert.ok(t2, "Theme addon should exist"); Assert.equal(t2, null, "Theme addon is not a thing anymore");
Assert.ok(d, "Theme addon should exist"); Assert.ok(d, "Theme addon should exist");
await t1.disable(); await t1.disable();
await t2.disable();
await new Promise(executeSoon); await new Promise(executeSoon);
Assert.ok(!t1.isActive, "Theme should be disabled"); Assert.ok(!t1.isActive, "Theme should be disabled");
Assert.ok(!t2.isActive, "Theme should be disabled");
Assert.ok(d.isActive, "Default theme should be active"); Assert.ok(d.isActive, "Default theme should be active");
await promiseRestartManager(); await promiseRestartManager();
[ t1, t2, d ] = await promiseAddonsByIDs(THEME_IDS); [ t1, t2, d ] = await promiseAddonsByIDs(THEME_IDS);
Assert.ok(!t1.isActive, "Theme should still be disabled"); Assert.ok(!t1.isActive, "Theme should still be disabled");
Assert.ok(!t2.isActive, "Theme should still be disabled");
Assert.ok(d.isActive, "Default theme should still be active"); Assert.ok(d.isActive, "Default theme should still be active");
gActiveTheme = d.id; gActiveTheme = d.id;
@ -117,7 +98,7 @@ async function setDisabledStateAndCheck(which, disabled = false) {
} }
let isDisabled; let isDisabled;
for (theme of await promiseAddonsByIDs(THEME_IDS)) { for (theme of await promiseAddonsByIDs(REAL_THEME_IDS)) {
isDisabled = (theme.id in expectedStates) ? expectedStates[theme.id] : true; isDisabled = (theme.id in expectedStates) ? expectedStates[theme.id] : true;
Assert.equal(theme.userDisabled, isDisabled, Assert.equal(theme.userDisabled, isDisabled,
`Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`); `Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`);
@ -130,7 +111,7 @@ async function setDisabledStateAndCheck(which, disabled = false) {
await promiseRestartManager(); await promiseRestartManager();
// All should still be good after a restart of the Addon Manager. // All should still be good after a restart of the Addon Manager.
for (theme of await promiseAddonsByIDs(THEME_IDS)) { for (theme of await promiseAddonsByIDs(REAL_THEME_IDS)) {
isDisabled = (theme.id in expectedStates) ? expectedStates[theme.id] : true; isDisabled = (theme.id in expectedStates) ? expectedStates[theme.id] : true;
Assert.equal(theme.userDisabled, isDisabled, Assert.equal(theme.userDisabled, isDisabled,
`Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`); `Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`);
@ -154,29 +135,6 @@ add_task(async function test_WebExtension_themes() {
// Enable it again. // Enable it again.
await setDisabledStateAndCheck(THEME_IDS[0]); await setDisabledStateAndCheck(THEME_IDS[0]);
// Enabling an LWT should disable the active theme.
await setDisabledStateAndCheck(THEME_IDS[1]);
// Switching back should disable the LWT.
await setDisabledStateAndCheck(THEME_IDS[0]);
});
add_task(async function test_LWTs() {
// Start with enabling an LWT.
await setDisabledStateAndCheck(THEME_IDS[1]);
// Disabling LWT should revert to the default theme.
await setDisabledStateAndCheck(THEME_IDS[1], true);
// Enable it again.
await setDisabledStateAndCheck(THEME_IDS[1]);
// Enabling a WebExtension theme should disable the active theme.
await setDisabledStateAndCheck(THEME_IDS[0]);
// Switching back should disable the LWT.
await setDisabledStateAndCheck(THEME_IDS[1]);
}); });
add_task(async function test_default_theme() { add_task(async function test_default_theme() {

Просмотреть файл

@ -14,7 +14,6 @@ support-files =
skip-if = os == "android" skip-if = os == "android"
[test_AddonRepository_langpacks.js] [test_AddonRepository_langpacks.js]
[test_AddonRepository_paging.js] [test_AddonRepository_paging.js]
[test_LightweightThemeManager.js]
[test_ProductAddonChecker.js] [test_ProductAddonChecker.js]
[test_XPIStates.js] [test_XPIStates.js]
[test_XPIcancel.js] [test_XPIcancel.js]
@ -229,7 +228,6 @@ head = head_addons.js head_system_addons.js
[test_temporary.js] [test_temporary.js]
skip-if = os == "win" # Bug 1469904 skip-if = os == "win" # Bug 1469904
tags = webextensions tags = webextensions
[test_theme_update.js]
[test_trash_directory.js] [test_trash_directory.js]
skip-if = os != "win" skip-if = os != "win"
[test_types.js] [test_types.js]

Просмотреть файл

@ -46,7 +46,7 @@ interface nsIProfileLock : nsISupports
* @note THIS INTERFACE SHOULD BE IMPLEMENTED BY THE TOOLKIT CODE ONLY! DON'T * @note THIS INTERFACE SHOULD BE IMPLEMENTED BY THE TOOLKIT CODE ONLY! DON'T
* EVEN THINK ABOUT IMPLEMENTING THIS IN JAVASCRIPT! * EVEN THINK ABOUT IMPLEMENTING THIS IN JAVASCRIPT!
*/ */
[scriptable, uuid(7422b090-4a86-4407-972e-75468a625388)] [scriptable, builtinclass, uuid(7422b090-4a86-4407-972e-75468a625388)]
interface nsIToolkitProfile : nsISupports interface nsIToolkitProfile : nsISupports
{ {
/** /**

Просмотреть файл

@ -52,20 +52,67 @@ using namespace mozilla;
#define DEV_EDITION_NAME "dev-edition-default" #define DEV_EDITION_NAME "dev-edition-default"
#define DEFAULT_NAME "default" #define DEFAULT_NAME "default"
#define COMPAT_FILE NS_LITERAL_STRING("compatibility.ini") #define COMPAT_FILE NS_LITERAL_STRING("compatibility.ini")
#define PROFILE_DB_VERSION "2"
#define INSTALL_PREFIX "Install"
#define INSTALL_PREFIX_LENGTH 7
struct KeyValue {
KeyValue(const char* aKey, const char* aValue) : key(aKey), value(aValue) {}
nsCString key;
nsCString value;
};
static bool GetStrings(const char* aString, const char* aValue,
void* aClosure) {
nsTArray<UniquePtr<KeyValue>>* array =
static_cast<nsTArray<UniquePtr<KeyValue>>*>(aClosure);
array->AppendElement(MakeUnique<KeyValue>(aString, aValue));
return true;
}
/**
* Returns an array of the strings inside a section of an ini file.
*/
nsTArray<UniquePtr<KeyValue>> GetSectionStrings(nsINIParser* aParser,
const char* aSection) {
nsTArray<UniquePtr<KeyValue>> result;
aParser->GetStrings(aSection, &GetStrings, &result);
return result;
}
nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir, nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
nsIFile* aLocalDir, nsToolkitProfile* aPrev) nsIFile* aLocalDir, bool aFromDB)
: mPrev(aPrev), : mName(aName),
mName(aName),
mRootDir(aRootDir), mRootDir(aRootDir),
mLocalDir(aLocalDir), mLocalDir(aLocalDir),
mLock(nullptr) { mLock(nullptr),
mIndex(0),
mSection("Profile") {
NS_ASSERTION(aRootDir, "No file!"); NS_ASSERTION(aRootDir, "No file!");
if (aPrev) { RefPtr<nsToolkitProfile> prev =
aPrev->mNext = this; nsToolkitProfileService::gService->mProfiles.getLast();
} else { if (prev) {
nsToolkitProfileService::gService->mFirst = this; mIndex = prev->mIndex + 1;
}
mSection.AppendInt(mIndex);
nsToolkitProfileService::gService->mProfiles.insertBack(this);
// If this profile isn't in the database already add it.
if (!aFromDB) {
nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
db->SetString(mSection.get(), "Name", mName.get());
bool isRelative = false;
nsCString descriptor;
nsToolkitProfileService::gService->GetProfileDescriptor(this, descriptor,
&isRelative);
db->SetString(mSection.get(), "IsRelative", isRelative ? "1" : "0");
db->SetString(mSection.get(), "Path", descriptor.get());
} }
} }
@ -106,6 +153,10 @@ nsToolkitProfile::SetName(const nsACString& aName) {
mName = aName; mName = aName;
nsresult rv = nsToolkitProfileService::gService->mProfileDB.SetString(
mSection.get(), "Name", mName.get());
NS_ENSURE_SUCCESS(rv, rv);
// Setting the name to the dev-edition default profile name will cause this // Setting the name to the dev-edition default profile name will cause this
// profile to become the dev-edition default. // profile to become the dev-edition default.
if (aName.EqualsLiteral(DEV_EDITION_NAME) && if (aName.EqualsLiteral(DEV_EDITION_NAME) &&
@ -122,8 +173,9 @@ nsresult nsToolkitProfile::RemoveInternal(bool aRemoveFiles,
if (mLock) return NS_ERROR_FILE_IS_LOCKED; if (mLock) return NS_ERROR_FILE_IS_LOCKED;
if (!mPrev && !mNext && nsToolkitProfileService::gService->mFirst != this) if (!isInList()) {
return NS_ERROR_NOT_INITIALIZED; return NS_ERROR_NOT_INITIALIZED;
}
if (aRemoveFiles) { if (aRemoveFiles) {
// Check if another instance is using this profile. // Check if another instance is using this profile.
@ -160,15 +212,29 @@ nsresult nsToolkitProfile::RemoveInternal(bool aRemoveFiles,
} }
} }
if (mPrev) nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
mPrev->mNext = mNext; db->DeleteSection(mSection.get());
else
nsToolkitProfileService::gService->mFirst = mNext;
if (mNext) mNext->mPrev = mPrev; // We make some assumptions that the profile's index in the database is based
// on its position in the linked list. Removing a profile means we have to fix
// the index of later profiles in the list. The easiest way to do that is just
// to move the last profile into the profile's position and just update its
// index.
RefPtr<nsToolkitProfile> last =
nsToolkitProfileService::gService->mProfiles.getLast();
if (last != this) {
// Update the section in the db.
last->mIndex = mIndex;
db->RenameSection(last->mSection.get(), mSection.get());
last->mSection = mSection;
mPrev = nullptr; if (last != getNext()) {
mNext = nullptr; last->remove();
setNext(last);
}
}
remove();
if (nsToolkitProfileService::gService->mNormalDefault == this) { if (nsToolkitProfileService::gService->mNormalDefault == this) {
nsToolkitProfileService::gService->mNormalDefault = nullptr; nsToolkitProfileService::gService->mNormalDefault = nullptr;
@ -313,7 +379,10 @@ nsToolkitProfileService::nsToolkitProfileService()
gService = this; gService = this;
} }
nsToolkitProfileService::~nsToolkitProfileService() { gService = nullptr; } nsToolkitProfileService::~nsToolkitProfileService() {
gService = nullptr;
mProfiles.clear();
}
void nsToolkitProfileService::CompleteStartup() { void nsToolkitProfileService::CompleteStartup() {
if (!mStartupProfileSelected) { if (!mStartupProfileSelected) {
@ -335,7 +404,7 @@ void nsToolkitProfileService::CompleteStartup() {
NS_ENSURE_SUCCESS_VOID(rv); NS_ENSURE_SUCCESS_VOID(rv);
if (isDefaultApp) { if (isDefaultApp) {
mInstallData.SetString(mInstallHash.get(), "Locked", "1"); mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
Flush(); Flush();
} }
} }
@ -440,7 +509,7 @@ bool nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
const nsCString& install = installs[i]; const nsCString& install = installs[i];
nsCString path; nsCString path;
rv = mInstallData.GetString(install.get(), "Default", path); rv = mProfileDB.GetString(install.get(), "Default", path);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
continue; continue;
} }
@ -452,7 +521,7 @@ bool nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
// Is this profile locked to this other install? // Is this profile locked to this other install?
nsCString isLocked; nsCString isLocked;
rv = mInstallData.GetString(install.get(), "Locked", isLocked); rv = mProfileDB.GetString(install.get(), "Locked", isLocked);
if (NS_SUCCEEDED(rv) && isLocked.Equals("1")) { if (NS_SUCCEEDED(rv) && isLocked.Equals("1")) {
return false; return false;
} }
@ -465,7 +534,7 @@ bool nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
for (uint32_t i = 0; i < inUseInstalls.Length(); i++) { for (uint32_t i = 0; i < inUseInstalls.Length(); i++) {
// Removing the default setting entirely will make the install go through // Removing the default setting entirely will make the install go through
// the first run process again at startup and create itself a new profile. // the first run process again at startup and create itself a new profile.
mInstallData.DeleteString(inUseInstalls[i].get(), "Default"); mProfileDB.DeleteString(inUseInstalls[i].get(), "Default");
} }
// Set this as the default profile for this install. // Set this as the default profile for this install.
@ -474,7 +543,7 @@ bool nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
// SetDefaultProfile will have locked this profile to this install so no // SetDefaultProfile will have locked this profile to this install so no
// other installs will steal it, but this was auto-selected so we want to // other installs will steal it, but this was auto-selected so we want to
// unlock it so that other installs can potentially take it. // unlock it so that other installs can potentially take it.
mInstallData.DeleteString(mInstallHash.get(), "Locked"); mProfileDB.DeleteString(mInstallSection.get(), "Locked");
// Persist the changes. // Persist the changes.
Flush(); Flush();
@ -486,6 +555,33 @@ bool nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
return true; return true;
} }
struct ImportInstallsClosure {
nsINIParser* backupData;
nsINIParser* profileDB;
};
static bool ImportInstalls(const char* aSection, void* aClosure) {
ImportInstallsClosure* closure =
static_cast<ImportInstallsClosure*>(aClosure);
nsTArray<UniquePtr<KeyValue>> strings =
GetSectionStrings(closure->backupData, aSection);
if (strings.IsEmpty()) {
return true;
}
nsCString newSection(INSTALL_PREFIX);
newSection.Append(aSection);
nsCString buffer;
for (uint32_t i = 0; i < strings.Length(); i++) {
closure->profileDB->SetString(newSection.get(), strings[i]->key.get(),
strings[i]->value.get());
}
return true;
}
nsresult nsToolkitProfileService::Init() { nsresult nsToolkitProfileService::Init() {
NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!"); NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!");
nsresult rv; nsresult rv;
@ -496,54 +592,76 @@ nsresult nsToolkitProfileService::Init() {
rv = nsXREDirProvider::GetUserLocalDataDirectory(getter_AddRefs(mTempData)); rv = nsXREDirProvider::GetUserLocalDataDirectory(getter_AddRefs(mTempData));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
nsCString installProfilePath; rv = mAppData->Clone(getter_AddRefs(mProfileDBFile));
if (mUseDedicatedProfile) {
// Load the dedicated profiles database.
rv = mAppData->Clone(getter_AddRefs(mInstallFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = mInstallFile->AppendNative(NS_LITERAL_CSTRING("installs.ini"));
NS_ENSURE_SUCCESS(rv, rv);
nsString installHash;
rv = gDirServiceProvider->GetInstallHash(installHash);
NS_ENSURE_SUCCESS(rv, rv);
CopyUTF16toUTF8(installHash, mInstallHash);
rv = mInstallData.Init(mInstallFile);
if (NS_SUCCEEDED(rv)) {
// Try to find the descriptor for the default profile for this install.
rv = mInstallData.GetString(mInstallHash.get(), "Default",
installProfilePath);
// Not having a value means this install doesn't appear in installs.ini so
// this is the first run for this install.
mIsFirstRun = NS_FAILED(rv);
}
}
rv = mAppData->Clone(getter_AddRefs(mListFile));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
rv = mListFile->AppendNative(NS_LITERAL_CSTRING("profiles.ini")); rv = mProfileDBFile->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
nsINIParser parser; rv = mAppData->Clone(getter_AddRefs(mInstallDBFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = mInstallDBFile->AppendNative(NS_LITERAL_CSTRING("installs.ini"));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString buffer;
bool exists; bool exists;
rv = mListFile->IsFile(&exists); rv = mProfileDBFile->IsFile(&exists);
if (NS_SUCCEEDED(rv) && exists) { if (NS_SUCCEEDED(rv) && exists) {
rv = parser.Init(mListFile); rv = mProfileDB.Init(mProfileDBFile);
// Init does not fail on parsing errors, only on OOM/really unexpected // Init does not fail on parsing errors, only on OOM/really unexpected
// conditions. // conditions.
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return rv; return rv;
} }
rv = mProfileDB.GetString("General", "StartWithLastProfile", buffer);
if (NS_SUCCEEDED(rv)) {
mStartWithLast = !buffer.EqualsLiteral("0");
}
rv = mProfileDB.GetString("General", "Version", buffer);
if (NS_FAILED(rv)) {
// This is a profiles.ini written by an older version. We must restore
// any install data from the backup.
nsINIParser installDB;
rv = mInstallDBFile->IsFile(&exists);
if (NS_SUCCEEDED(rv) && exists &&
NS_SUCCEEDED(installDB.Init(mInstallDBFile))) {
// There is install data to import.
ImportInstallsClosure closure = {&installDB, &mProfileDB};
installDB.GetSections(&ImportInstalls, &closure);
}
rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
NS_ENSURE_SUCCESS(rv, rv);
}
} else {
rv = mProfileDB.SetString("General", "StartWithLastProfile",
mStartWithLast ? "1" : "0");
NS_ENSURE_SUCCESS(rv, rv);
rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
NS_ENSURE_SUCCESS(rv, rv);
} }
nsAutoCString buffer; nsCString installProfilePath;
rv = parser.GetString("General", "StartWithLastProfile", buffer);
if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("0")) mStartWithLast = false; if (mUseDedicatedProfile) {
nsString installHash;
rv = gDirServiceProvider->GetInstallHash(installHash);
NS_ENSURE_SUCCESS(rv, rv);
CopyUTF16toUTF8(installHash, mInstallSection);
mInstallSection.Insert(INSTALL_PREFIX, 0);
// Try to find the descriptor for the default profile for this install.
rv = mProfileDB.GetString(mInstallSection.get(), "Default",
installProfilePath);
// Not having a value means this install doesn't appear in installs.ini so
// this is the first run for this install.
mIsFirstRun = NS_FAILED(rv);
}
nsToolkitProfile* currentProfile = nullptr; nsToolkitProfile* currentProfile = nullptr;
@ -575,14 +693,14 @@ nsresult nsToolkitProfileService::Init() {
nsAutoCString profileID("Profile"); nsAutoCString profileID("Profile");
profileID.AppendInt(c); profileID.AppendInt(c);
rv = parser.GetString(profileID.get(), "IsRelative", buffer); rv = mProfileDB.GetString(profileID.get(), "IsRelative", buffer);
if (NS_FAILED(rv)) break; if (NS_FAILED(rv)) break;
bool isRelative = buffer.EqualsLiteral("1"); bool isRelative = buffer.EqualsLiteral("1");
nsAutoCString filePath; nsAutoCString filePath;
rv = parser.GetString(profileID.get(), "Path", filePath); rv = mProfileDB.GetString(profileID.get(), "Path", filePath);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
NS_ERROR("Malformed profiles.ini: Path= not found"); NS_ERROR("Malformed profiles.ini: Path= not found");
continue; continue;
@ -590,7 +708,7 @@ nsresult nsToolkitProfileService::Init() {
nsAutoCString name; nsAutoCString name;
rv = parser.GetString(profileID.get(), "Name", name); rv = mProfileDB.GetString(profileID.get(), "Name", name);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
NS_ERROR("Malformed profiles.ini: Name= not found"); NS_ERROR("Malformed profiles.ini: Name= not found");
continue; continue;
@ -618,11 +736,10 @@ nsresult nsToolkitProfileService::Init() {
localDir = rootDir; localDir = rootDir;
} }
currentProfile = currentProfile = new nsToolkitProfile(name, rootDir, localDir, true);
new nsToolkitProfile(name, rootDir, localDir, currentProfile);
NS_ENSURE_TRUE(currentProfile, NS_ERROR_OUT_OF_MEMORY); NS_ENSURE_TRUE(currentProfile, NS_ERROR_OUT_OF_MEMORY);
rv = parser.GetString(profileID.get(), "Default", buffer); rv = mProfileDB.GetString(profileID.get(), "Default", buffer);
if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1")) { if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1")) {
mNormalDefault = currentProfile; mNormalDefault = currentProfile;
} }
@ -644,7 +761,7 @@ nsresult nsToolkitProfileService::Init() {
// If there is only one non-dev-edition profile then mark it as the default. // If there is only one non-dev-edition profile then mark it as the default.
if (!mNormalDefault && nonDevEditionProfiles == 1) { if (!mNormalDefault && nonDevEditionProfiles == 1) {
mNormalDefault = autoSelectProfile; SetNormalDefault(autoSelectProfile);
} }
if (!mUseDedicatedProfile) { if (!mUseDedicatedProfile) {
@ -664,6 +781,9 @@ nsresult nsToolkitProfileService::Init() {
NS_IMETHODIMP NS_IMETHODIMP
nsToolkitProfileService::SetStartWithLastProfile(bool aValue) { nsToolkitProfileService::SetStartWithLastProfile(bool aValue) {
if (mStartWithLast != aValue) { if (mStartWithLast != aValue) {
nsresult rv = mProfileDB.SetString("General", "StartWithLastProfile",
mStartWithLast ? "1" : "0");
NS_ENSURE_SUCCESS(rv, rv);
mStartWithLast = aValue; mStartWithLast = aValue;
} }
return NS_OK; return NS_OK;
@ -677,7 +797,7 @@ nsToolkitProfileService::GetStartWithLastProfile(bool* aResult) {
NS_IMETHODIMP NS_IMETHODIMP
nsToolkitProfileService::GetProfiles(nsISimpleEnumerator** aResult) { nsToolkitProfileService::GetProfiles(nsISimpleEnumerator** aResult) {
*aResult = new ProfileEnumerator(this->mFirst); *aResult = new ProfileEnumerator(mProfiles.getFirst());
if (!*aResult) return NS_ERROR_OUT_OF_MEMORY; if (!*aResult) return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aResult); NS_ADDREF(*aResult);
@ -696,7 +816,7 @@ nsToolkitProfileService::ProfileEnumerator::GetNext(nsISupports** aResult) {
NS_ADDREF(*aResult = mCurrent); NS_ADDREF(*aResult = mCurrent);
mCurrent = mCurrent->mNext; mCurrent = mCurrent->getNext();
return NS_OK; return NS_OK;
} }
@ -722,6 +842,26 @@ nsToolkitProfileService::GetDefaultProfile(nsIToolkitProfile** aResult) {
return NS_OK; return NS_OK;
} }
void nsToolkitProfileService::SetNormalDefault(nsIToolkitProfile* aProfile) {
if (mNormalDefault == aProfile) {
return;
}
if (mNormalDefault) {
nsToolkitProfile* profile =
static_cast<nsToolkitProfile*>(mNormalDefault.get());
mProfileDB.DeleteString(profile->mSection.get(), "Default");
}
mNormalDefault = aProfile;
if (mNormalDefault) {
nsToolkitProfile* profile =
static_cast<nsToolkitProfile*>(mNormalDefault.get());
mProfileDB.SetString(profile->mSection.get(), "Default", "1");
}
}
NS_IMETHODIMP NS_IMETHODIMP
nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) { nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) {
if (mUseDedicatedProfile) { if (mUseDedicatedProfile) {
@ -730,20 +870,20 @@ nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) {
// Setting this to the empty string means no profile will be found on // Setting this to the empty string means no profile will be found on
// startup but we'll recognise that this install has been used // startup but we'll recognise that this install has been used
// previously. // previously.
mInstallData.SetString(mInstallHash.get(), "Default", ""); mProfileDB.SetString(mInstallSection.get(), "Default", "");
} else { } else {
nsCString profilePath; nsCString profilePath;
nsresult rv = GetProfileDescriptor(aProfile, profilePath, nullptr); nsresult rv = GetProfileDescriptor(aProfile, profilePath, nullptr);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
mInstallData.SetString(mInstallHash.get(), "Default", mProfileDB.SetString(mInstallSection.get(), "Default",
profilePath.get()); profilePath.get());
} }
mDedicatedProfile = aProfile; mDedicatedProfile = aProfile;
// Some kind of choice has happened here, lock this profile to this // Some kind of choice has happened here, lock this profile to this
// install. // install.
mInstallData.SetString(mInstallHash.get(), "Locked", "1"); mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
} }
return NS_OK; return NS_OK;
} }
@ -753,7 +893,8 @@ nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) {
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
mNormalDefault = aProfile; SetNormalDefault(aProfile);
return NS_OK; return NS_OK;
} }
@ -813,7 +954,7 @@ nsresult nsToolkitProfileService::CreateDefaultProfile(
} else if (mUseDevEditionProfile) { } else if (mUseDevEditionProfile) {
mDevEditionDefault = mCurrent; mDevEditionDefault = mCurrent;
} else { } else {
mNormalDefault = mCurrent; SetNormalDefault(mCurrent);
} }
return NS_OK; return NS_OK;
@ -842,9 +983,9 @@ nsToolkitProfileService::SelectStartupProfile(
argv[argc] = nullptr; argv[argc] = nullptr;
bool wasDefault; bool wasDefault;
nsresult rv = SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir, nsresult rv =
aLocalDir, aProfile, aDidCreate, SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir, aLocalDir,
&wasDefault); aProfile, aDidCreate, &wasDefault);
// Since we were called outside of the normal startup path complete any // Since we were called outside of the normal startup path complete any
// startup tasks. // startup tasks.
@ -1163,10 +1304,12 @@ nsresult nsToolkitProfileService::SelectStartupProfile(
// older versions of Firefox use then we must create a default profile // older versions of Firefox use then we must create a default profile
// for older versions of Firefox to avoid the existing profile being // for older versions of Firefox to avoid the existing profile being
// auto-selected. // auto-selected.
if ((mUseDedicatedProfile || mUseDevEditionProfile) && mFirst && if ((mUseDedicatedProfile || mUseDevEditionProfile) &&
!mFirst->mNext) { mProfiles.getFirst() == mProfiles.getLast()) {
nsCOMPtr<nsIToolkitProfile> newProfile;
CreateProfile(nullptr, NS_LITERAL_CSTRING(DEFAULT_NAME), CreateProfile(nullptr, NS_LITERAL_CSTRING(DEFAULT_NAME),
getter_AddRefs(mNormalDefault)); getter_AddRefs(newProfile));
SetNormalDefault(newProfile);
} }
Flush(); Flush();
@ -1249,14 +1392,14 @@ nsresult nsToolkitProfileService::ApplyResetProfile(
// If the old profile would have been the default for old installs then mark // If the old profile would have been the default for old installs then mark
// the new profile as such. // the new profile as such.
if (mNormalDefault == aOldProfile) { if (mNormalDefault == aOldProfile) {
mNormalDefault = mCurrent; SetNormalDefault(mCurrent);
} }
if (mUseDedicatedProfile && mDedicatedProfile == aOldProfile) { if (mUseDedicatedProfile && mDedicatedProfile == aOldProfile) {
bool wasLocked = false; bool wasLocked = false;
nsCString val; nsCString val;
if (NS_SUCCEEDED( if (NS_SUCCEEDED(
mInstallData.GetString(mInstallHash.get(), "Locked", val))) { mProfileDB.GetString(mInstallSection.get(), "Locked", val))) {
wasLocked = val.Equals("1"); wasLocked = val.Equals("1");
} }
@ -1264,7 +1407,7 @@ nsresult nsToolkitProfileService::ApplyResetProfile(
// Make the locked state match if necessary. // Make the locked state match if necessary.
if (!wasLocked) { if (!wasLocked) {
mInstallData.DeleteString(mInstallHash.get(), "Locked"); mProfileDB.DeleteString(mInstallSection.get(), "Locked");
} }
} }
@ -1286,13 +1429,11 @@ nsresult nsToolkitProfileService::ApplyResetProfile(
NS_IMETHODIMP NS_IMETHODIMP
nsToolkitProfileService::GetProfileByName(const nsACString& aName, nsToolkitProfileService::GetProfileByName(const nsACString& aName,
nsIToolkitProfile** aResult) { nsIToolkitProfile** aResult) {
nsToolkitProfile* curP = mFirst; for (RefPtr<nsToolkitProfile> profile : mProfiles) {
while (curP) { if (profile->mName.Equals(aName)) {
if (curP->mName.Equals(aName)) { NS_ADDREF(*aResult = profile);
NS_ADDREF(*aResult = curP);
return NS_OK; return NS_OK;
} }
curP = curP->mNext;
} }
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
@ -1305,18 +1446,16 @@ nsToolkitProfileService::GetProfileByName(const nsACString& aName,
void nsToolkitProfileService::GetProfileByDir(nsIFile* aRootDir, void nsToolkitProfileService::GetProfileByDir(nsIFile* aRootDir,
nsIFile* aLocalDir, nsIFile* aLocalDir,
nsIToolkitProfile** aResult) { nsIToolkitProfile** aResult) {
nsToolkitProfile* curP = mFirst; for (RefPtr<nsToolkitProfile> profile : mProfiles) {
while (curP) {
bool equal; bool equal;
nsresult rv = curP->mRootDir->Equals(aRootDir, &equal); nsresult rv = profile->mRootDir->Equals(aRootDir, &equal);
if (NS_SUCCEEDED(rv) && equal) { if (NS_SUCCEEDED(rv) && equal) {
rv = curP->mLocalDir->Equals(aLocalDir, &equal); rv = profile->mLocalDir->Equals(aLocalDir, &equal);
if (NS_SUCCEEDED(rv) && equal) { if (NS_SUCCEEDED(rv) && equal) {
NS_ADDREF(*aResult = curP); NS_ADDREF(*aResult = profile);
return; return;
} }
} }
curP = curP->mNext;
} }
} }
@ -1449,15 +1588,8 @@ nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
rv = CreateTimesInternal(rootDir); rv = CreateTimesInternal(rootDir);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
nsToolkitProfile* last = mFirst.get();
if (last) {
while (last->mNext) {
last = last->mNext;
}
}
nsCOMPtr<nsIToolkitProfile> profile = nsCOMPtr<nsIToolkitProfile> profile =
new nsToolkitProfile(aName, rootDir, localDir, last); new nsToolkitProfile(aName, rootDir, localDir, false);
if (!profile) return NS_ERROR_OUT_OF_MEMORY; if (!profile) return NS_ERROR_OUT_OF_MEMORY;
if (aName.Equals(DEV_EDITION_NAME)) { if (aName.Equals(DEV_EDITION_NAME)) {
@ -1496,6 +1628,11 @@ struct FindInstallsClosure {
static bool FindInstalls(const char* aSection, void* aClosure) { static bool FindInstalls(const char* aSection, void* aClosure) {
FindInstallsClosure* closure = static_cast<FindInstallsClosure*>(aClosure); FindInstallsClosure* closure = static_cast<FindInstallsClosure*>(aClosure);
// Check if the section starts with "Install"
if (strncmp(aSection, INSTALL_PREFIX, INSTALL_PREFIX_LENGTH) != 0) {
return true;
}
nsCString install(aSection); nsCString install(aSection);
closure->installs->AppendElement(install); closure->installs->AppendElement(install);
@ -1504,9 +1641,9 @@ static bool FindInstalls(const char* aSection, void* aClosure) {
nsTArray<nsCString> nsToolkitProfileService::GetKnownInstalls() { nsTArray<nsCString> nsToolkitProfileService::GetKnownInstalls() {
nsTArray<nsCString> result; nsTArray<nsCString> result;
FindInstallsClosure closure = {&mInstallData, &result}; FindInstallsClosure closure = {&mProfileDB, &result};
mInstallData.GetSections(&FindInstalls, &closure); mProfileDB.GetSections(&FindInstalls, &closure);
return result; return result;
} }
@ -1545,10 +1682,9 @@ nsresult nsToolkitProfileService::CreateTimesInternal(nsIFile* aProfileDir) {
NS_IMETHODIMP NS_IMETHODIMP
nsToolkitProfileService::GetProfileCount(uint32_t* aResult) { nsToolkitProfileService::GetProfileCount(uint32_t* aResult) {
*aResult = 0; *aResult = 0;
nsToolkitProfile* profile = mFirst; for (nsToolkitProfile* profile : mProfiles) {
while (profile) { Unused << profile;
(*aResult)++; (*aResult)++;
profile = profile->mNext;
} }
return NS_OK; return NS_OK;
@ -1558,71 +1694,59 @@ NS_IMETHODIMP
nsToolkitProfileService::Flush() { nsToolkitProfileService::Flush() {
nsresult rv; nsresult rv;
// If we aren't using dedicated profiles then nothing about the list of
// installs can have changed, so no need to update the backup.
if (mUseDedicatedProfile) { if (mUseDedicatedProfile) {
rv = mInstallData.WriteToFile(mInstallFile); // Export the installs to the backup.
NS_ENSURE_SUCCESS(rv, rv); nsTArray<nsCString> installs = GetKnownInstalls();
}
// Errors during writing might cause unhappy semi-written files. if (!installs.IsEmpty()) {
// To avoid this, write the entire thing to a buffer, then write nsCString data;
// that buffer to disk. nsCString buffer;
uint32_t pCount = 0; for (uint32_t i = 0; i < installs.Length(); i++) {
nsToolkitProfile* cur; nsTArray<UniquePtr<KeyValue>> strings =
GetSectionStrings(&mProfileDB, installs[i].get());
if (strings.IsEmpty()) {
continue;
}
for (cur = mFirst; cur != nullptr; cur = cur->mNext) ++pCount; // Strip "Install" from the start.
const nsDependentCSubstring& install =
Substring(installs[i], INSTALL_PREFIX_LENGTH);
data.AppendPrintf("[%s]\n", PromiseFlatCString(install).get());
uint32_t length; for (uint32_t j = 0; j < strings.Length(); j++) {
const int bufsize = 100 + MAXPATHLEN * pCount; data.AppendPrintf("%s=%s\n", strings[j]->key.get(),
auto buffer = MakeUnique<char[]>(bufsize); strings[j]->value.get());
}
char* pos = buffer.get(); data.Append("\n");
char* end = pos + bufsize; }
pos += snprintf(pos, end - pos, FILE* writeFile;
"[General]\n" rv = mInstallDBFile->OpenANSIFileDesc("w", &writeFile);
"StartWithLastProfile=%s\n\n", NS_ENSURE_SUCCESS(rv, rv);
mStartWithLast ? "1" : "0");
nsAutoCString path; uint32_t length = data.Length();
cur = mFirst; if (fwrite(data.get(), sizeof(char), length, writeFile) != length) {
pCount = 0; fclose(writeFile);
return NS_ERROR_UNEXPECTED;
}
while (cur) { fclose(writeFile);
bool isRelative; } else {
nsresult rv = GetProfileDescriptor(cur, path, &isRelative); rv = mInstallDBFile->Remove(false);
NS_ENSURE_SUCCESS(rv, rv); if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
rv != NS_ERROR_FILE_NOT_FOUND) {
pos += return rv;
snprintf(pos, end - pos, }
"[Profile%u]\n"
"Name=%s\n"
"IsRelative=%s\n"
"Path=%s\n",
pCount, cur->mName.get(), isRelative ? "1" : "0", path.get());
if (cur == mNormalDefault) {
pos += snprintf(pos, end - pos, "Default=1\n");
} }
pos += snprintf(pos, end - pos, "\n");
cur = cur->mNext;
++pCount;
} }
FILE* writeFile; rv = mProfileDB.WriteToFile(mProfileDBFile);
rv = mListFile->OpenANSIFileDesc("w", &writeFile);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
length = pos - buffer.get();
if (fwrite(buffer.get(), sizeof(char), length, writeFile) != length) {
fclose(writeFile);
return NS_ERROR_UNEXPECTED;
}
fclose(writeFile);
return NS_OK; return NS_OK;
} }

Просмотреть файл

@ -15,20 +15,20 @@
#include "nsProfileLock.h" #include "nsProfileLock.h"
#include "nsINIParser.h" #include "nsINIParser.h"
class nsToolkitProfile final : public nsIToolkitProfile { class nsToolkitProfile final
: public nsIToolkitProfile,
public mozilla::LinkedListElement<RefPtr<nsToolkitProfile>> {
public: public:
NS_DECL_ISUPPORTS NS_DECL_ISUPPORTS
NS_DECL_NSITOOLKITPROFILE NS_DECL_NSITOOLKITPROFILE
friend class nsToolkitProfileService; friend class nsToolkitProfileService;
RefPtr<nsToolkitProfile> mNext;
nsToolkitProfile* mPrev;
private: private:
~nsToolkitProfile() = default; ~nsToolkitProfile() = default;
nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir, nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
nsIFile* aLocalDir, nsToolkitProfile* aPrev); nsIFile* aLocalDir, bool aFromDB);
nsresult RemoveInternal(bool aRemoveFiles, bool aInBackground); nsresult RemoveInternal(bool aRemoveFiles, bool aInBackground);
@ -38,6 +38,8 @@ class nsToolkitProfile final : public nsIToolkitProfile {
nsCOMPtr<nsIFile> mRootDir; nsCOMPtr<nsIFile> mRootDir;
nsCOMPtr<nsIFile> mLocalDir; nsCOMPtr<nsIFile> mLocalDir;
nsIProfileLock* mLock; nsIProfileLock* mLock;
uint32_t mIndex;
nsCString mSection;
}; };
class nsToolkitProfileLock final : public nsIProfileLock { class nsToolkitProfileLock final : public nsIProfileLock {
@ -103,6 +105,7 @@ class nsToolkitProfileService final : public nsIToolkitProfileService {
bool MaybeMakeDefaultDedicatedProfile(nsIToolkitProfile* aProfile); bool MaybeMakeDefaultDedicatedProfile(nsIToolkitProfile* aProfile);
bool IsSnapEnvironment(); bool IsSnapEnvironment();
nsresult CreateDefaultProfile(nsIToolkitProfile** aResult); nsresult CreateDefaultProfile(nsIToolkitProfile** aResult);
void SetNormalDefault(nsIToolkitProfile* aProfile);
// Returns the known install hashes from the installs database. Modifying the // Returns the known install hashes from the installs database. Modifying the
// installs database is safe while iterating the returned array. // installs database is safe while iterating the returned array.
@ -110,8 +113,8 @@ class nsToolkitProfileService final : public nsIToolkitProfileService {
// Tracks whether SelectStartupProfile has been called. // Tracks whether SelectStartupProfile has been called.
bool mStartupProfileSelected; bool mStartupProfileSelected;
// The first profile in a linked list of profiles loaded from profiles.ini. // The profiles loaded from profiles.ini.
RefPtr<nsToolkitProfile> mFirst; mozilla::LinkedList<RefPtr<nsToolkitProfile>> mProfiles;
// The profile selected for use at startup, if it exists in profiles.ini. // The profile selected for use at startup, if it exists in profiles.ini.
nsCOMPtr<nsIToolkitProfile> mCurrent; nsCOMPtr<nsIToolkitProfile> mCurrent;
// The profile selected for this install in installs.ini. // The profile selected for this install in installs.ini.
@ -126,13 +129,13 @@ class nsToolkitProfileService final : public nsIToolkitProfileService {
// The directory that holds the cache files for profiles. // The directory that holds the cache files for profiles.
nsCOMPtr<nsIFile> mTempData; nsCOMPtr<nsIFile> mTempData;
// The location of profiles.ini. // The location of profiles.ini.
nsCOMPtr<nsIFile> mListFile; nsCOMPtr<nsIFile> mProfileDBFile;
// The location of installs.ini. // The location of installs.ini.
nsCOMPtr<nsIFile> mInstallFile; nsCOMPtr<nsIFile> mInstallDBFile;
// The data loaded from installs.ini. // The data loaded from profiles.ini.
nsINIParser mInstallData; nsINIParser mProfileDB;
// The install hash for the currently running install. // The section in the profiles db for the current install.
nsCString mInstallHash; nsCString mInstallSection;
// Whether to start with the selected profile by default. // Whether to start with the selected profile by default.
bool mStartWithLast; bool mStartWithLast;
// True if during startup it appeared that this is the first run. // True if during startup it appeared that this is the first run.

Просмотреть файл

@ -169,7 +169,7 @@ function writeProfilesIni(profileData) {
getService(Ci.nsIINIParserFactory); getService(Ci.nsIINIParserFactory);
let ini = factory.createINIParser().QueryInterface(Ci.nsIINIParserWriter); let ini = factory.createINIParser().QueryInterface(Ci.nsIINIParserWriter);
const { options = {}, profiles = [] } = profileData; const { options = {}, profiles = [], installs = null } = profileData;
let { startWithLastProfile = true } = options; let { startWithLastProfile = true } = options;
ini.setString("General", "StartWithLastProfile", startWithLastProfile ? "1" : "0"); ini.setString("General", "StartWithLastProfile", startWithLastProfile ? "1" : "0");
@ -187,6 +187,21 @@ function writeProfilesIni(profileData) {
} }
} }
if (installs) {
ini.setString("General", "Version", "2");
for (let hash of Object.keys(installs)) {
ini.setString(`Install${hash}`, "Default", installs[hash].default);
if ("locked" in installs[hash]) {
ini.setString(`Install${hash}`, "Locked", installs[hash].locked ? "1" : "0");
}
}
writeInstallsIni({ installs });
} else {
writeInstallsIni(null);
}
ini.writeFile(target); ini.writeFile(target);
} }
@ -205,6 +220,7 @@ function readProfilesIni() {
startWithLastProfile: true, startWithLastProfile: true,
}, },
profiles: [], profiles: [],
installs: null,
}; };
if (!target.exists()) { if (!target.exists()) {
@ -216,29 +232,52 @@ function readProfilesIni() {
let ini = factory.createINIParser(target); let ini = factory.createINIParser(target);
profileData.options.startWithLastProfile = safeGet(ini, "General", "StartWithLastProfile") == "1"; profileData.options.startWithLastProfile = safeGet(ini, "General", "StartWithLastProfile") == "1";
if (safeGet(ini, "General", "Version") == "2") {
profileData.installs = {};
}
for (let i = 0; true; i++) { let sections = ini.getSections();
let section = `Profile${i}`; while (sections.hasMore()) {
let section = sections.getNext();
let isRelative = safeGet(ini, section, "IsRelative"); if (section == "General") {
if (isRelative === null) { continue;
break;
}
Assert.equal(isRelative, "1", "Paths should always be relative in these tests.");
let profile = {
name: safeGet(ini, section, "Name"),
path: safeGet(ini, section, "Path"),
};
try {
profile.default = ini.getString(section, "Default") == "1";
Assert.ok(profile.default, "The Default value is only written when true.");
} catch (e) {
profile.default = false;
} }
profileData.profiles.push(profile); if (section.startsWith("Profile")) {
let isRelative = safeGet(ini, section, "IsRelative");
if (isRelative === null) {
break;
}
Assert.equal(isRelative, "1", "Paths should always be relative in these tests.");
let profile = {
name: safeGet(ini, section, "Name"),
path: safeGet(ini, section, "Path"),
};
try {
profile.default = ini.getString(section, "Default") == "1";
Assert.ok(profile.default, "The Default value is only written when true.");
} catch (e) {
profile.default = false;
}
profileData.profiles.push(profile);
}
if (section.startsWith("Install")) {
Assert.ok(profileData.installs, "Should only see an install section if the ini version was correct.");
profileData.installs[section.substring(7)] = {
default: safeGet(ini, section, "Default"),
};
let locked = safeGet(ini, section, "Locked");
if (locked !== null) {
profileData.installs[section.substring(7)].locked = locked;
}
}
} }
profileData.profiles.sort((a, b) => a.name.localeCompare(b.name)); profileData.profiles.sort((a, b) => a.name.localeCompare(b.name));
@ -255,6 +294,14 @@ function writeInstallsIni(installData) {
let target = gDataHome.clone(); let target = gDataHome.clone();
target.append("installs.ini"); target.append("installs.ini");
if (!installData) {
try {
target.remove(false);
} catch (e) {
}
return;
}
const { installs = {} } = installData; const { installs = {} } = installData;
let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]. let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
@ -283,6 +330,7 @@ function readInstallsIni() {
}; };
if (!target.exists()) { if (!target.exists()) {
dump("Missing installs.ini\n");
return installData; return installData;
} }
@ -296,19 +344,37 @@ function readInstallsIni() {
if (hash != "General") { if (hash != "General") {
installData.installs[hash] = { installData.installs[hash] = {
default: safeGet(ini, hash, "Default"), default: safeGet(ini, hash, "Default"),
locked: safeGet(ini, hash, "Locked") == 1,
}; };
let locked = safeGet(ini, hash, "Locked");
if (locked !== null) {
installData.installs[hash].locked = locked;
}
} }
} }
return installData; return installData;
} }
/**
* Check that the backup data in installs.ini matches the install data in
* profiles.ini.
*/
function checkBackup(profileData = readProfilesIni(), installData = readInstallsIni()) {
if (!profileData.installs) {
// If the profiles db isn't of the right version we wouldn't expect the
// backup to be accurate.
return;
}
Assert.deepEqual(profileData.installs, installData.installs, "Backup installs.ini should match installs in profiles.ini");
}
/** /**
* Checks that the profile service seems to have the right data in it compared * Checks that the profile service seems to have the right data in it compared
* to profile and install data structured as in the above functions. * to profile and install data structured as in the above functions.
*/ */
function checkProfileService(profileData = readProfilesIni(), installData = readInstallsIni()) { function checkProfileService(profileData = readProfilesIni(), verifyBackup = true) {
let service = getProfileService(); let service = getProfileService();
let serviceProfiles = Array.from(service.profiles); let serviceProfiles = Array.from(service.profiles);
@ -320,7 +386,8 @@ function checkProfileService(profileData = readProfilesIni(), installData = read
profileData.profiles.sort((a, b) => a.name.localeCompare(b.name)); profileData.profiles.sort((a, b) => a.name.localeCompare(b.name));
let hash = xreDirProvider.getInstallHash(); let hash = xreDirProvider.getInstallHash();
let defaultPath = hash in installData.installs ? installData.installs[hash].default : null; let defaultPath = (profileData.installs && hash in profileData.installs) ?
profileData.installs[hash].default : null;
let dedicatedProfile = null; let dedicatedProfile = null;
let snapProfile = null; let snapProfile = null;
@ -352,6 +419,10 @@ function checkProfileService(profileData = readProfilesIni(), installData = read
} else { } else {
Assert.equal(service.defaultProfile, dedicatedProfile, "Should have seen the right profile selected."); Assert.equal(service.defaultProfile, dedicatedProfile, "Should have seen the right profile selected.");
} }
if (verifyBackup) {
checkBackup(profileData);
}
} }
/** /**

Просмотреть файл

@ -0,0 +1,41 @@
/*
* Tests that when the profiles DB is missing the install data we reload it.
*/
add_task(async () => {
let hash = xreDirProvider.getInstallHash();
let profileData = {
options: {
startWithLastProfile: true,
},
profiles: [{
name: "Profile1",
path: "Path1",
}, {
name: "Profile2",
path: "Path2",
}],
};
let installs = {
[hash]: {
default: "Path2",
},
};
writeProfilesIni(profileData);
writeInstallsIni({ installs });
let { profile, didCreate } = selectStartupProfile();
checkStartupReason("default");
let service = getProfileService();
// Should have added the backup data to the service, check that is true.
profileData.installs = installs;
checkProfileService(profileData);
Assert.ok(!didCreate, "Should not have created a new profile.");
Assert.equal(profile.name, "Profile2", "Should have selected the right profile");
Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");
});

Просмотреть файл

@ -14,10 +14,6 @@ add_task(async () => {
path: defaultProfile.leafName, path: defaultProfile.leafName,
default: true, default: true,
}], }],
});
let hash = xreDirProvider.getInstallHash();
writeProfilesIni({
installs: { installs: {
other: { other: {
default: defaultProfile.leafName, default: defaultProfile.leafName,
@ -29,16 +25,16 @@ add_task(async () => {
let { profile: selectedProfile, didCreate } = selectStartupProfile(); let { profile: selectedProfile, didCreate } = selectStartupProfile();
let profileData = readProfilesIni(); let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile."); Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 2, "Should have the right number of profiles."); Assert.equal(profileData.profiles.length, 2, "Should have the right number of profiles.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be two known installs."); let hash = xreDirProvider.getInstallHash();
Assert.notEqual(installData.installs[hash].default, defaultProfile.leafName, "Should not have marked the original default profile as the default for this install."); Assert.equal(Object.keys(profileData.installs).length, 2, "Should be two known installs.");
Assert.ok(installData.installs[hash].locked, "Should have locked as we created this profile for this install."); Assert.notEqual(profileData.installs[hash].default, defaultProfile.leafName, "Should not have marked the original default profile as the default for this install.");
Assert.ok(profileData.installs[hash].locked, "Should have locked as we created this profile for this install.");
checkProfileService(profileData, installData); checkProfileService(profileData);
Assert.ok(didCreate, "Should have created a new profile."); Assert.ok(didCreate, "Should have created a new profile.");
Assert.ok(!selectedProfile.rootDir.equals(defaultProfile), "Should be using a different directory."); Assert.ok(!selectedProfile.rootDir.equals(defaultProfile), "Should be using a different directory.");

Просмотреть файл

@ -18,7 +18,6 @@ add_task(async () => {
service.flush(); service.flush();
let profileData = readProfilesIni(); let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile."); Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles."); Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -28,15 +27,14 @@ add_task(async () => {
Assert.ok(!profile.default, "Should not be marked as the old-style default."); Assert.ok(!profile.default, "Should not be marked as the old-style default.");
// The new profile hasn't been marked as the default yet! // The new profile hasn't been marked as the default yet!
Assert.equal(Object.keys(installData.installs).length, 0, "Should be no defaults for installs yet."); Assert.equal(Object.keys(profileData.installs).length, 0, "Should be no defaults for installs yet.");
checkProfileService(profileData, installData); checkProfileService(profileData);
service.defaultProfile = newProfile; service.defaultProfile = newProfile;
service.flush(); service.flush();
profileData = readProfilesIni(); profileData = readProfilesIni();
installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile."); Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles."); Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -46,10 +44,10 @@ add_task(async () => {
Assert.ok(!profile.default, "Should not be marked as the old-style default."); Assert.ok(!profile.default, "Should not be marked as the old-style default.");
let hash = xreDirProvider.getInstallHash(); let hash = xreDirProvider.getInstallHash();
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install."); Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
Assert.equal(installData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install."); Assert.equal(profileData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
checkProfileService(profileData, installData); checkProfileService(profileData);
let otherProfile = service.createProfile(null, "another"); let otherProfile = service.createProfile(null, "another");
service.defaultProfile = otherProfile; service.defaultProfile = otherProfile;
@ -57,7 +55,6 @@ add_task(async () => {
service.flush(); service.flush();
profileData = readProfilesIni(); profileData = readProfilesIni();
installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile."); Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 2, "Should have the right number of profiles."); Assert.equal(profileData.profiles.length, 2, "Should have the right number of profiles.");
@ -70,16 +67,15 @@ add_task(async () => {
Assert.equal(profile.name, "dedicated", "Should have the right name."); Assert.equal(profile.name, "dedicated", "Should have the right name.");
Assert.ok(!profile.default, "Should not be marked as the old-style default."); Assert.ok(!profile.default, "Should not be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install."); Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
Assert.equal(installData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install."); Assert.equal(profileData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
checkProfileService(profileData, installData); checkProfileService(profileData);
newProfile.remove(true); newProfile.remove(true);
service.flush(); service.flush();
profileData = readProfilesIni(); profileData = readProfilesIni();
installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile."); Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles."); Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -88,23 +84,22 @@ add_task(async () => {
Assert.equal(profile.name, "another", "Should have the right name."); Assert.equal(profile.name, "another", "Should have the right name.");
Assert.ok(!profile.default, "Should not be marked as the old-style default."); Assert.ok(!profile.default, "Should not be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install."); Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
Assert.equal(installData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install."); Assert.equal(profileData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
checkProfileService(profileData, installData); checkProfileService(profileData);
otherProfile.remove(true); otherProfile.remove(true);
service.flush(); service.flush();
profileData = readProfilesIni(); profileData = readProfilesIni();
installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile."); Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 0, "Should have the right number of profiles."); Assert.equal(profileData.profiles.length, 0, "Should have the right number of profiles.");
// We leave a reference to the missing profile to stop us trying to steal the // We leave a reference to the missing profile to stop us trying to steal the
// old-style default profile on next startup. // old-style default profile on next startup.
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install."); Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
checkProfileService(profileData, installData); checkProfileService(profileData);
}); });

Просмотреть файл

@ -9,8 +9,7 @@ add_task(async () => {
checkStartupReason("firstrun-created-default"); checkStartupReason("firstrun-created-default");
let profileData = readProfilesIni(); let profileData = readProfilesIni();
let installData = readInstallsIni(); checkProfileService(profileData);
checkProfileService(profileData, installData);
Assert.ok(didCreate, "Should have created a new profile."); Assert.ok(didCreate, "Should have created a new profile.");
Assert.equal(profile, service.defaultProfile, "Should now be the default profile."); Assert.equal(profile, service.defaultProfile, "Should now be the default profile.");
@ -29,5 +28,5 @@ add_task(async () => {
Assert.ok(!profile.default, "Should not be marked as the old-style default."); Assert.ok(!profile.default, "Should not be marked as the old-style default.");
let hash = xreDirProvider.getInstallHash(); let hash = xreDirProvider.getInstallHash();
Assert.ok(installData.installs[hash].locked, "Should have locked the profile"); Assert.ok(profileData.installs[hash].locked, "Should have locked the profile");
}); });

Просмотреть файл

@ -21,7 +21,6 @@ add_task(async () => {
let hash = xreDirProvider.getInstallHash(); let hash = xreDirProvider.getInstallHash();
let profileData = readProfilesIni(); let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile."); Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles."); Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -31,11 +30,11 @@ add_task(async () => {
Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile."); Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile.");
Assert.ok(profile.default, "Should be marked as the old-style default."); Assert.ok(profile.default, "Should be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install."); Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
Assert.equal(installData.installs[hash].default, defaultProfile.leafName, "Should have marked the original default profile as the default for this install."); Assert.equal(profileData.installs[hash].default, defaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(installData.installs[hash].locked, "Should have locked as we're the default app."); Assert.ok(profileData.installs[hash].locked, "Should have locked as we're the default app.");
checkProfileService(profileData, installData); checkProfileService(profileData);
Assert.ok(!didCreate, "Should not have created a new profile."); Assert.ok(!didCreate, "Should not have created a new profile.");
Assert.ok(selectedProfile.rootDir.equals(defaultProfile), "Should be using the right directory."); Assert.ok(selectedProfile.rootDir.equals(defaultProfile), "Should be using the right directory.");

Просмотреть файл

@ -0,0 +1,37 @@
/**
* When profiles.ini is missing there isn't any point in restoring from any
* installs.ini, the profiles it refers to are gone anyway.
*/
add_task(async () => {
let hash = xreDirProvider.getInstallHash();
let installs = {
[hash]: {
default: "Path2",
},
otherhash: {
default: "foo",
},
anotherhash: {
default: "bar",
},
};
writeInstallsIni({ installs });
let { profile, didCreate } = selectStartupProfile();
checkStartupReason("firstrun-created-default");
let service = getProfileService();
Assert.ok(didCreate, "Should have created a new profile.");
Assert.equal(profile.name, DEDICATED_NAME, "Should have created the right profile");
Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");
let profilesData = readProfilesIni();
Assert.equal(Object.keys(profilesData.installs).length, 1, "Should be only one known install");
Assert.ok(hash in profilesData.installs, "Should be the expected install.");
Assert.notEqual(profilesData.installs[hash].default, "Path2", "Didn't import the previous data.");
checkProfileService(profilesData);
});

Просмотреть файл

@ -31,7 +31,6 @@ add_task(async () => {
let hash = xreDirProvider.getInstallHash(); let hash = xreDirProvider.getInstallHash();
let profileData = readProfilesIni(); let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile."); Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 3, "Should have the right number of profiles."); Assert.equal(profileData.profiles.length, 3, "Should have the right number of profiles.");
@ -51,16 +50,16 @@ add_task(async () => {
Assert.equal(profile.path, mydefaultProfile.leafName, "Should be the original default profile."); Assert.equal(profile.path, mydefaultProfile.leafName, "Should be the original default profile.");
Assert.ok(profile.default, "Should be marked as the old-style default."); Assert.ok(profile.default, "Should be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install."); Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
if (AppConstants.MOZ_DEV_EDITION) { if (AppConstants.MOZ_DEV_EDITION) {
Assert.equal(installData.installs[hash].default, devDefaultProfile.leafName, "Should have marked the original dev default profile as the default for this install."); Assert.equal(profileData.installs[hash].default, devDefaultProfile.leafName, "Should have marked the original dev default profile as the default for this install.");
} else { } else {
Assert.equal(installData.installs[hash].default, mydefaultProfile.leafName, "Should have marked the original default profile as the default for this install."); Assert.equal(profileData.installs[hash].default, mydefaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
} }
Assert.ok(!installData.installs[hash].locked, "Should not be locked as we're not the default app."); Assert.ok(!profileData.installs[hash].locked, "Should not be locked as we're not the default app.");
checkProfileService(profileData, installData); checkProfileService(profileData);
Assert.ok(!didCreate, "Should not have created a new profile."); Assert.ok(!didCreate, "Should not have created a new profile.");
if (AppConstants.MOZ_DEV_EDITION) { if (AppConstants.MOZ_DEV_EDITION) {

Просмотреть файл

@ -16,9 +16,6 @@ add_task(async () => {
path: defaultProfile.leafName, path: defaultProfile.leafName,
default: true, default: true,
}], }],
});
writeInstallsIni({
installs: { installs: {
[hash]: { [hash]: {
default: "foobar", default: "foobar",
@ -30,7 +27,6 @@ add_task(async () => {
testStartsProfileManager(); testStartsProfileManager();
let profileData = readProfilesIni(); let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile."); Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles."); Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -42,7 +38,7 @@ add_task(async () => {
Assert.ok(profile.default, "Should be marked as the old-style default."); Assert.ok(profile.default, "Should be marked as the old-style default.");
// We keep the data here so we don't steal on the next reboot... // We keep the data here so we don't steal on the next reboot...
Assert.equal(Object.keys(installData.installs).length, 1, "Still list the broken reference."); Assert.equal(Object.keys(profileData.installs).length, 1, "Still list the broken reference.");
checkProfileService(profileData, installData); checkProfileService(profileData);
}); });

Просмотреть файл

@ -0,0 +1,90 @@
/*
* Tests adding and removing functions correctly.
*/
function compareLists(service, knownProfiles) {
Assert.equal(service.profileCount, knownProfiles.length, "profileCount should be correct.");
let serviceProfiles = Array.from(service.profiles);
Assert.equal(serviceProfiles.length, knownProfiles.length, "Enumerator length should be correct.");
for (let i = 0; i < knownProfiles.length; i++) {
// Cannot use strictEqual here, it attempts to print out a string
// representation of the profile objects and on some platforms that recurses
// infinitely.
Assert.ok(serviceProfiles[i] === knownProfiles[i], `Should have the right profile in position ${i}.`);
}
}
function removeProfile(profiles, position) {
dump(`Removing profile in position ${position}.`);
Assert.greaterOrEqual(position, 0, "Should be removing a valid position.");
Assert.less(position, profiles.length, "Should be removing a valid position.");
let last = profiles.pop();
if (profiles.length == position) {
// We were asked to remove the last profile.
last.remove(false);
return;
}
profiles[position].remove(false);
profiles[position] = last;
}
add_task(async () => {
let service = getProfileService();
let profiles = [];
compareLists(service, profiles);
profiles.push(service.createProfile(null, "profile1"));
profiles.push(service.createProfile(null, "profile2"));
profiles.push(service.createProfile(null, "profile3"));
profiles.push(service.createProfile(null, "profile4"));
profiles.push(service.createProfile(null, "profile5"));
profiles.push(service.createProfile(null, "profile6"));
profiles.push(service.createProfile(null, "profile7"));
profiles.push(service.createProfile(null, "profile8"));
profiles.push(service.createProfile(null, "profile9"));
compareLists(service, profiles);
// Test removing the first profile.
removeProfile(profiles, 0);
compareLists(service, profiles);
// And the last profile.
removeProfile(profiles, profiles.length - 1);
compareLists(service, profiles);
// Last but one...
removeProfile(profiles, profiles.length - 2);
compareLists(service, profiles);
// Second one...
removeProfile(profiles, 1);
compareLists(service, profiles);
// Something in the middle.
removeProfile(profiles, 2);
compareLists(service, profiles);
let expectedNames = [
"profile9",
"profile7",
"profile5",
"profile4",
];
let serviceProfiles = Array.from(service.profiles);
for (let i = 0; i < expectedNames.length; i++) {
Assert.equal(serviceProfiles[i].name, expectedNames[i]);
}
removeProfile(profiles, 0);
removeProfile(profiles, 0);
removeProfile(profiles, 0);
removeProfile(profiles, 0);
Assert.equal(Array.from(service.profiles).length, 0, "All profiles gone.");
Assert.equal(service.profileCount, 0, "All profiles gone.");
});

Просмотреть файл

@ -13,27 +13,23 @@ add_task(async () => {
path: defaultProfile.leafName, path: defaultProfile.leafName,
default: true, default: true,
}], }],
};
writeProfilesIni(profilesIni);
let installsIni = {
installs: { installs: {
[hash]: { [hash]: {
default: defaultProfile.leafName, default: defaultProfile.leafName,
}, },
}, },
}; };
writeInstallsIni(installsIni); writeProfilesIni(profilesIni);
let service = getProfileService(); let service = getProfileService();
checkProfileService(profilesIni, installsIni); checkProfileService(profilesIni);
let { profile, didCreate } = selectStartupProfile(); let { profile, didCreate } = selectStartupProfile();
Assert.ok(!didCreate, "Should have not created a new profile."); Assert.ok(!didCreate, "Should have not created a new profile.");
Assert.equal(profile.name, "default", "Should have selected the default profile."); Assert.equal(profile.name, "default", "Should have selected the default profile.");
Assert.equal(profile, service.defaultProfile, "Should have selected the default profile."); Assert.equal(profile, service.defaultProfile, "Should have selected the default profile.");
checkProfileService(profilesIni, installsIni); checkProfileService(profilesIni);
// In an actual run of Firefox we wouldn't be able to delete the profile in // In an actual run of Firefox we wouldn't be able to delete the profile in
// use because it would be locked. But we don't actually lock the profile in // use because it would be locked. But we don't actually lock the profile in
@ -45,9 +41,10 @@ add_task(async () => {
// These are the modifications that should have been made. // These are the modifications that should have been made.
profilesIni.profiles.pop(); profilesIni.profiles.pop();
installsIni.installs[hash].default = ""; profilesIni.installs[hash].default = "";
checkProfileService(profilesIni, installsIni); // The data isn't flushed to disk so don't check the backup here.
checkProfileService(profilesIni, false);
service.flush(); service.flush();
@ -56,6 +53,6 @@ add_task(async () => {
// checkProfileService doesn't differentiate between a blank default profile // checkProfileService doesn't differentiate between a blank default profile
// for the install and a missing install. // for the install and a missing install.
let installs = readInstallsIni(); profilesIni = readProfilesIni();
Assert.equal(installs.installs[hash].default, "", "Should be a blank default profile."); Assert.equal(profilesIni.installs[hash].default, "", "Should be a blank default profile.");
}); });

Просмотреть файл

@ -16,8 +16,6 @@ add_task(async () => {
name: "Profile3", name: "Profile3",
path: "Path3", path: "Path3",
}], }],
};
let installData = {
installs: { installs: {
[hash]: { [hash]: {
default: "Path2", default: "Path2",
@ -43,7 +41,6 @@ add_task(async () => {
} }
writeProfilesIni(profileData); writeProfilesIni(profileData);
writeInstallsIni(installData);
let { profile, didCreate } = selectStartupProfile(); let { profile, didCreate } = selectStartupProfile();
checkStartupReason("default"); checkStartupReason("default");

Просмотреть файл

@ -22,7 +22,6 @@ add_task(async () => {
let hash = xreDirProvider.getInstallHash(); let hash = xreDirProvider.getInstallHash();
let profileData = readProfilesIni(); let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile."); Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles."); Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -32,11 +31,11 @@ add_task(async () => {
Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile."); Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile.");
Assert.ok(profile.default, "Should be marked as the old-style default."); Assert.ok(profile.default, "Should be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install."); Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
Assert.equal(installData.installs[hash].default, defaultProfile.leafName, "Should have marked the original default profile as the default for this install."); Assert.equal(profileData.installs[hash].default, defaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(!installData.installs[hash].locked, "Should not have locked as we're not the default app."); Assert.ok(!profileData.installs[hash].locked, "Should not have locked as we're not the default app.");
checkProfileService(profileData, installData); checkProfileService(profileData);
Assert.ok(!didCreate, "Should not have created a new profile."); Assert.ok(!didCreate, "Should not have created a new profile.");
let service = getProfileService(); let service = getProfileService();

Просмотреть файл

@ -23,7 +23,6 @@ add_task(async () => {
let service = getProfileService(); let service = getProfileService();
let profileData = readProfilesIni(); let profileData = readProfilesIni();
let installData = readInstallsIni();
Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile."); Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles."); Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
@ -33,9 +32,9 @@ add_task(async () => {
Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile."); Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile.");
Assert.ok(!profile.default, "Should not be marked as the old-style default."); Assert.ok(!profile.default, "Should not be marked as the old-style default.");
Assert.equal(Object.keys(installData.installs).length, 0, "Should be no defaults for installs yet."); Assert.ok(!profileData.installs, "Should be no defaults for installs yet.");
checkProfileService(profileData, installData); checkProfileService(profileData);
let { profile: selectedProfile, didCreate } = selectStartupProfile(); let { profile: selectedProfile, didCreate } = selectStartupProfile();
checkStartupReason("firstrun-skipped-default"); checkStartupReason("firstrun-skipped-default");

Просмотреть файл

@ -26,10 +26,7 @@ add_task(async () => {
name: "Profile3", name: "Profile3",
path: "Path3", path: "Path3",
}], }],
}; // Another install is using the profile and it is locked.
// Another install is using the profile and it is locked.
let installData = {
installs: { installs: {
otherinstall: { otherinstall: {
default: root.leafName, default: root.leafName,
@ -39,8 +36,7 @@ add_task(async () => {
}; };
writeProfilesIni(profileData); writeProfilesIni(profileData);
writeInstallsIni(installData); checkProfileService(profileData);
checkProfileService(profileData, installData);
let env = Cc["@mozilla.org/process/environment;1"]. let env = Cc["@mozilla.org/process/environment;1"].
getService(Ci.nsIEnvironment); getService(Ci.nsIEnvironment);
@ -73,10 +69,9 @@ add_task(async () => {
Assert.ok(!profileData.profiles[1].default, "Should not be the old default profile."); Assert.ok(!profileData.profiles[1].default, "Should not be the old default profile.");
let hash = xreDirProvider.getInstallHash(); let hash = xreDirProvider.getInstallHash();
installData = readInstallsIni(); Assert.equal(Object.keys(profileData.installs).length, 2, "Should be one known install.");
Assert.equal(Object.keys(installData.installs).length, 2, "Should be one known install."); Assert.notEqual(profileData.installs[hash].default, root.leafName, "Should have marked the original default profile as the default for this install.");
Assert.notEqual(installData.installs[hash].default, root.leafName, "Should have marked the original default profile as the default for this install."); Assert.ok(profileData.installs[hash].locked, "Should have locked as we created the profile for this install.");
Assert.ok(installData.installs[hash].locked, "Should have locked as we created the profile for this install."); Assert.equal(profileData.installs.otherinstall.default, root.leafName, "Should have left the other profile as the default for the other install.");
Assert.equal(installData.installs.otherinstall.default, root.leafName, "Should have left the other profile as the default for the other install."); Assert.ok(profileData.installs[hash].locked, "Should still be locked to the other install.");
Assert.ok(installData.installs[hash].locked, "Should still be locked to the other install.");
}); });

Просмотреть файл

@ -26,10 +26,7 @@ add_task(async () => {
name: "Profile3", name: "Profile3",
path: "Path3", path: "Path3",
}], }],
}; // Another install is using the profile but it isn't locked.
// Another install is using the profile but it isn't locked.
let installData = {
installs: { installs: {
otherinstall: { otherinstall: {
default: root.leafName, default: root.leafName,
@ -38,8 +35,7 @@ add_task(async () => {
}; };
writeProfilesIni(profileData); writeProfilesIni(profileData);
writeInstallsIni(installData); checkProfileService(profileData);
checkProfileService(profileData, installData);
let env = Cc["@mozilla.org/process/environment;1"]. let env = Cc["@mozilla.org/process/environment;1"].
getService(Ci.nsIEnvironment); getService(Ci.nsIEnvironment);
@ -64,9 +60,8 @@ add_task(async () => {
Assert.ok(profileData.profiles[0].default, "Should still be the old default profile."); Assert.ok(profileData.profiles[0].default, "Should still be the old default profile.");
let hash = xreDirProvider.getInstallHash(); let hash = xreDirProvider.getInstallHash();
installData = readInstallsIni();
// The info about the other install will have been removed so it goes through first run on next startup. // The info about the other install will have been removed so it goes through first run on next startup.
Assert.equal(Object.keys(installData.installs).length, 1, "Should be one known install."); Assert.equal(Object.keys(profileData.installs).length, 1, "Should be one known install.");
Assert.equal(installData.installs[hash].default, root.leafName, "Should have marked the original default profile as the default for this install."); Assert.equal(profileData.installs[hash].default, root.leafName, "Should have marked the original default profile as the default for this install.");
Assert.ok(!installData.installs[hash].locked, "Should not have locked as we're not the default app."); Assert.ok(!profileData.installs[hash].locked, "Should not have locked as we're not the default app.");
}); });

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше