author | Tim Taubert <ttaubert@mozilla.com> |
Sun, 11 Aug 2013 09:27:17 +0200 | |
changeset 142101 | 3d20597e0a07b9913d62948f5568052045111352 |
parent 142069 | 6cd6960ea7f60ed69f4a7c5653eab901dbd48ae8 (current diff) |
parent 142100 | 61e0fb09a73cd63aa2244883f344a66412d67a0d (diff) |
child 142211 | f057fca0962763dcdb50286e34ec05c9f768818d |
child 142246 | ae46de5551b3a19624f66c5102f00433766eba5e |
push id | 25083 |
push user | ttaubert@mozilla.com |
push date | Sun, 11 Aug 2013 07:28:28 +0000 |
treeherder | mozilla-central@3d20597e0a07 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 26.0a1 |
first release with | nightly linux32
3d20597e0a07
/
26.0a1
/
20130811030225
/
files
nightly linux64
3d20597e0a07
/
26.0a1
/
20130811030225
/
files
nightly mac
3d20597e0a07
/
26.0a1
/
20130811030225
/
files
nightly win32
3d20597e0a07
/
26.0a1
/
20130811030225
/
files
nightly win64
3d20597e0a07
/
26.0a1
/
20130811030225
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
26.0a1
/
20130811030225
/
pushlog to previous
nightly linux64
26.0a1
/
20130811030225
/
pushlog to previous
nightly mac
26.0a1
/
20130811030225
/
pushlog to previous
nightly win32
26.0a1
/
20130811030225
/
pushlog to previous
nightly win64
26.0a1
/
20130811030225
/
pushlog to previous
|
toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js | file | annotate | diff | comparison | revisions |
--- a/browser/components/sessionstore/src/DocumentUtils.jsm +++ b/browser/components/sessionstore/src/DocumentUtils.jsm @@ -1,12 +1,14 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + this.EXPORTED_SYMBOLS = [ "DocumentUtils" ]; const Cu = Components.utils; const Ci = Components.interfaces; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource:///modules/sessionstore/XPathGenerator.jsm");
--- a/browser/components/sessionstore/src/SessionMigration.jsm +++ b/browser/components/sessionstore/src/SessionMigration.jsm @@ -1,12 +1,14 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + this.EXPORTED_SYMBOLS = ["SessionMigration"]; const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); Cu.import("resource://gre/modules/Task.jsm", this); Cu.import("resource://gre/modules/osfile.jsm", this); // An encoder to UTF-8.
--- a/browser/components/sessionstore/src/SessionStorage.jsm +++ b/browser/components/sessionstore/src/SessionStorage.jsm @@ -1,12 +1,14 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + this.EXPORTED_SYMBOLS = ["SessionStorage"]; const Cu = Components.utils; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
--- a/browser/components/sessionstore/src/SessionStore.jsm +++ b/browser/components/sessionstore/src/SessionStore.jsm @@ -1,12 +1,14 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + this.EXPORTED_SYMBOLS = ["SessionStore"]; const Cu = Components.utils; const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const STATE_STOPPED = 0; @@ -299,20 +301,16 @@ let SessionStoreInternal = { // time in milliseconds when the session was started (saved across sessions), // defaults to now if no session was restored or timestamp doesn't exist _sessionStartTime: Date.now(), // states for all currently opened windows _windows: {}, - // internal states for all open windows (data we need to associate, - // but not write to disk) - _internalWindows: {}, - // states for all recently closed windows _closedWindows: [], // collection of session states yet to be restored _statesToRestore: {}, // counts the number of crashes since the last clean start _recentCrashes: 0, @@ -714,19 +712,16 @@ let SessionStoreInternal = { return; // assign it a unique identifier (timestamp) aWindow.__SSi = "window" + Date.now(); // and create its data object this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [], busy: false }; - // and create its internal data object - this._internalWindows[aWindow.__SSi] = { hosts: {} } - let isPrivateWindow = false; if (PrivateBrowsingUtils.isWindowPrivate(aWindow)) this._windows[aWindow.__SSi].isPrivate = isPrivateWindow = true; if (!this._isWindowLoaded(aWindow)) this._windows[aWindow.__SSi]._restoring = true; if (!aWindow.toolbar.visible) this._windows[aWindow.__SSi].isPopup = true; @@ -870,22 +865,22 @@ let SessionStoreInternal = { }, /** * On window open * @param aWindow * Window reference */ onOpen: function ssi_onOpen(aWindow) { - var _this = this; - aWindow.addEventListener("load", function(aEvent) { - aEvent.currentTarget.removeEventListener("load", arguments.callee, false); - _this.onLoad(aEvent.currentTarget); - }, false); - return; + let onload = () => { + aWindow.removeEventListener("load", onload); + this.onLoad(aWindow); + }; + + aWindow.addEventListener("load", onload); }, /** * On window close... * - remove event listeners from tabs * - save all window data * @param aWindow * Window reference @@ -930,19 +925,17 @@ let SessionStoreInternal = { if (this._loadState == STATE_RUNNING) { // window not closed during a regular shut-down // update all window data for a last time this._collectWindowData(aWindow); if (isFullyLoaded) { winData.title = aWindow.content.document.title || tabbrowser.selectedTab.label; winData.title = this._replaceLoadingTitle(winData.title, tabbrowser, tabbrowser.selectedTab); - let windows = {}; - windows[aWindow.__SSi] = winData; - this._updateCookies(windows); + this._updateCookies([winData]); } #ifndef XP_MACOSX // Until we decide otherwise elsewhere, this window is part of a series // of closing windows to quit. winData._shouldRestore = true; #endif @@ -954,17 +947,16 @@ let SessionStoreInternal = { delete winData.busy; this._closedWindows.unshift(winData); this._capClosedWindows(); } // clear this window from the list delete this._windows[aWindow.__SSi]; - delete this._internalWindows[aWindow.__SSi]; // save the state without this window to disk this.saveStateDelayed(); } for (let i = 0; i < tabbrowser.tabs.length; i++) { this.onTabRemove(aWindow, tabbrowser.tabs[i], true); } @@ -1050,43 +1042,37 @@ let SessionStoreInternal = { this._lastSessionState = null; let openWindows = {}; this._forEachBrowserWindow(function(aWindow) { Array.forEach(aWindow.gBrowser.tabs, function(aTab) { TabStateCache.delete(aTab); delete aTab.linkedBrowser.__SS_data; delete aTab.linkedBrowser.__SS_tabStillLoading; delete aTab.linkedBrowser.__SS_formDataSaved; - delete aTab.linkedBrowser.__SS_hostSchemeData; if (aTab.linkedBrowser.__SS_restoreState) this._resetTabRestoringState(aTab); - }); + }, this); openWindows[aWindow.__SSi] = true; }); // also clear all data about closed tabs and windows for (let ix in this._windows) { if (ix in openWindows) { this._windows[ix]._closedTabs = []; - } - else { + } else { delete this._windows[ix]; - delete this._internalWindows[ix]; } } // also clear all data about closed windows this._closedWindows = []; // give the tabbrowsers a chance to clear their histories first var win = this._getMostRecentBrowserWindow(); if (win) win.setTimeout(function() { _this.saveState(true); }, 0); else if (this._loadState == STATE_RUNNING) this.saveState(true); - // Delete the private browsing backed up state, if any - if ("_stateBackup" in this) - delete this._stateBackup; this._clearRestoringWindows(); }, /** * On purge of domain data * @param aData * String domain data @@ -1220,17 +1206,16 @@ let SessionStoreInternal = { browser.removeEventListener("load", this, true); let mm = browser.messageManager; MESSAGES.forEach(msg => mm.removeMessageListener(msg, this)); delete browser.__SS_data; delete browser.__SS_tabStillLoading; delete browser.__SS_formDataSaved; - delete browser.__SS_hostSchemeData; // If this tab was in the middle of restoring or still needs to be restored, // we need to reset that state. If the tab was restoring, we will attempt to // restore the next tab. let previousState = browser.__SS_restoreState; if (previousState) { this._resetTabRestoringState(aTab); if (previousState == TAB_STATE_RESTORING) @@ -1976,21 +1961,20 @@ let SessionStoreInternal = { if (history && browser.__SS_data && browser.__SS_data.entries[history.index] && browser.__SS_data.entries[history.index].url == browser.currentURI.spec && history.index < this._sessionhistory_max_entries - 1 && !includePrivateData) { tabData = browser.__SS_data; tabData.index = history.index + 1; } else if (history && history.count > 0) { - browser.__SS_hostSchemeData = []; try { for (var j = 0; j < history.count; j++) { let entry = this._serializeHistoryEntry(history.getEntryAtIndex(j, false), - includePrivateData, aTab.pinned, browser.__SS_hostSchemeData); + includePrivateData, aTab.pinned); tabData.entries.push(entry); } // If we make it through the for loop, then we're ok and we should clear // any indicator of brokenness. delete aTab.__SS_broken_history; } catch (ex) { // In some cases, getEntryAtIndex will throw. This seems to be due to @@ -2072,33 +2056,22 @@ let SessionStoreInternal = { * Get an object that is a serialized representation of a History entry * Used for data storage * @param aEntry * nsISHEntry instance * @param aIncludePrivateData * always return privacy sensitive data (use with care) * @param aIsPinned * the tab is pinned and should be treated differently for privacy - * @param aHostSchemeData - * an array of objects with host & scheme keys * @returns object */ _serializeHistoryEntry: - function ssi_serializeHistoryEntry(aEntry, aIncludePrivateData, aIsPinned, aHostSchemeData) { + function ssi_serializeHistoryEntry(aEntry, aIncludePrivateData, aIsPinned) { var entry = { url: aEntry.URI.spec }; - try { - // throwing is expensive, we know that about: pages will throw - if (entry.url.indexOf("about:") != 0) - aHostSchemeData.push({ host: aEntry.URI.host, scheme: aEntry.URI.scheme }); - } - catch (ex) { - // We just won't attempt to get cookies for this entry. - } - if (aEntry.title && aEntry.title != entry.url) { entry.title = aEntry.title; } if (aEntry.isSubFrame) { entry.subframe = true; } if (!(aEntry instanceof Ci.nsISHEntry)) { return entry; @@ -2199,17 +2172,17 @@ let SessionStoreInternal = { if (child) { // don't try to restore framesets containing wyciwyg URLs (cf. bug 424689 and bug 450595) if (child.URI.schemeIs("wyciwyg")) { children = []; break; } children.push(this._serializeHistoryEntry(child, aIncludePrivateData, - aIsPinned, aHostSchemeData)); + aIsPinned)); } } if (children.length) entry.children = children; } return entry; @@ -2420,18 +2393,18 @@ let SessionStoreInternal = { else if (aScheme == "file") { aHosts[aHost] = true; } }, /** * Serialize cookie data * @param aWindows - * JS object containing window data references - * { id: winData, etc. } + * An array of window data objects + * { tabs: [ ... ], etc. } */ _updateCookies: function ssi_updateCookies(aWindows) { function addCookieToHash(aHash, aHost, aPath, aName, aCookie) { // lazily build up a 3-dimensional hash, with // aHost, aPath, and aName as keys if (!aHash[aHost]) aHash[aHost] = {}; if (!aHash[aHost][aPath]) @@ -2439,22 +2412,28 @@ let SessionStoreInternal = { aHash[aHost][aPath][aName] = aCookie; } var jscookies = {}; var _this = this; // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision var MAX_EXPIRY = Math.pow(2, 62); - for (let [id, window] in Iterator(aWindows)) { + for (let window of aWindows) { window.cookies = []; - let internalWindow = this._internalWindows[id]; - if (!internalWindow.hosts) - return; - for (var [host, isPinned] in Iterator(internalWindow.hosts)) { + + // Collect all hosts for the current window. + let hosts = {}; + window.tabs.forEach(function(tab) { + tab.entries.forEach(function(entry) { + this._extractHostsForCookiesFromEntry(entry, hosts, true, tab.pinned); + }, this); + }, this); + + for (var [host, isPinned] in Iterator(hosts)) { let list; try { list = Services.cookies.getCookiesFromHost(host); } catch (ex) { debug("getCookiesFromHost failed. Host: " + host); } while (list && list.hasMoreElements()) { @@ -2537,30 +2516,34 @@ let SessionStoreInternal = { } else { // always update the window features (whose change alone never triggers a save operation) this._updateWindowFeatures(aWindow); } }); DirtyWindows.clear(); } - // collect the data for all windows - var total = [], windows = {}, ids = []; + // An array that at the end will hold all current window data. + var total = []; + // The ids of all windows contained in 'total' in the same order. + var ids = []; + // The number of window that are _not_ popups. var nonPopupCount = 0; var ix; + + // collect the data for all windows for (ix in this._windows) { if (this._windows[ix]._restoring) // window data is still in _statesToRestore continue; total.push(this._windows[ix]); ids.push(ix); - windows[ix] = this._windows[ix]; if (!this._windows[ix].isPopup) nonPopupCount++; } - this._updateCookies(windows); + this._updateCookies(total); // collect the data for all windows yet to be restored for (ix in this._statesToRestore) { for each (let winData in this._statesToRestore[ix].windows) { total.push(winData); if (!winData.isPopup) nonPopupCount++; } @@ -2622,48 +2605,34 @@ let SessionStoreInternal = { _getWindowState: function ssi_getWindowState(aWindow) { if (!this._isWindowLoaded(aWindow)) return this._statesToRestore[aWindow.__SS_restoreID]; if (this._loadState == STATE_RUNNING) { this._collectWindowData(aWindow); } - var winData = this._windows[aWindow.__SSi]; - let windows = {}; - windows[aWindow.__SSi] = winData; + let windows = [this._windows[aWindow.__SSi]]; this._updateCookies(windows); - return { windows: [winData] }; + return { windows: windows }; }, _collectWindowData: function ssi_collectWindowData(aWindow) { if (!this._isWindowLoaded(aWindow)) return; let tabbrowser = aWindow.gBrowser; let tabs = tabbrowser.tabs; let winData = this._windows[aWindow.__SSi]; let tabsData = winData.tabs = []; - let hosts = this._internalWindows[aWindow.__SSi].hosts = {}; // update the internal state data for this window for (let tab of tabs) { tabsData.push(this._collectTabData(tab)); - - // Since we are only ever called for open - // windows during a session, we can call into - // _extractHostsForCookiesFromHostScheme directly using data - // that is attached to each browser. - let hostSchemeData = tab.linkedBrowser.__SS_hostSchemeData || []; - for (let j = 0; j < hostSchemeData.length; j++) { - this._extractHostsForCookiesFromHostScheme(hostSchemeData[j].host, - hostSchemeData[j].scheme, - hosts, true, tab.pinned); - } } winData.selected = tabbrowser.mTabBox.selectedIndex + 1; this._updateWindowFeatures(aWindow); // Make sure we keep __SS_lastSessionWindowID around for cases like entering // or leaving PB mode. if (aWindow.__SS_lastSessionWindowID)
--- a/browser/components/sessionstore/src/XPathGenerator.jsm +++ b/browser/components/sessionstore/src/XPathGenerator.jsm @@ -1,12 +1,14 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + this.EXPORTED_SYMBOLS = ["XPathGenerator"]; this.XPathGenerator = { // these two hashes should be kept in sync namespaceURIs: { "xhtml": "http://www.w3.org/1999/xhtml" }, namespacePrefixes: { "http://www.w3.org/1999/xhtml": "xhtml" }, /**
--- a/browser/components/sessionstore/src/nsSessionStartup.js +++ b/browser/components/sessionstore/src/nsSessionStartup.js @@ -1,12 +1,14 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + /** * Session Storage and Restoration * * Overview * This service reads user's session file at startup, and makes a determination * as to whether the session should be restored. It will restore the session * under the circumstances described below. If the auto-start Private Browsing * mode is active, however, the session is never restored.
--- a/browser/components/sessionstore/src/nsSessionStore.js +++ b/browser/components/sessionstore/src/nsSessionStore.js @@ -1,12 +1,14 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + /** * Session Storage and Restoration * * Overview * This service keeps track of a user's session, storing the various bits * required to return the browser to its current state. The relevant data is * stored in memory, and is periodically saved to disk in a file in the * profile directory. The service is started at first window load, in
--- a/browser/metro/base/content/ContextCommands.js +++ b/browser/metro/base/content/ContextCommands.js @@ -32,69 +32,76 @@ var ContextCommands = { * Context menu handlers */ // Text specific cut: function cc_cut() { let target = ContextMenuUI.popupState.target; - if (!target) + if (!target) { return; + } if (target.localName === "browser") { // content if (ContextMenuUI.popupState.string) { this.sendCommand("cut"); SelectionHelperUI.closeEditSession(true); } } else { // chrome - target.editor.cut(); + CommandUpdater.doCommand("cmd_cut"); } target.focus(); }, copy: function cc_copy() { let target = ContextMenuUI.popupState.target; - if (!target) + if (!target) { return; + } if (target.localName == "browser") { // content if (ContextMenuUI.popupState.string) { this.sendCommand("copy"); SelectionHelperUI.closeEditSession(true); } } else if (ContextMenuUI.popupState.string) { this.clipboard.copyString(ContextMenuUI.popupState.string, this.docRef); } else { // chrome - target.editor.copy(); + CommandUpdater.doCommand("cmd_copy"); } target.focus(); }, paste: function cc_paste() { let target = ContextMenuUI.popupState.target; + + if (!target) { + return; + } + if (target.localName == "browser") { // content let x = ContextMenuUI.popupState.x; let y = ContextMenuUI.popupState.y; let json = {x: x, y: y, command: "paste" }; target.messageManager.sendAsyncMessage("Browser:ContextCommand", json); SelectionHelperUI.closeEditSession(); } else { // chrome - target.editor.paste(Ci.nsIClipboard.kGlobalClipboard); + CommandUpdater.doCommand("cmd_paste"); target.focus(); } }, pasteAndGo: function cc_pasteAndGo() { let target = ContextMenuUI.popupState.target; target.editor.selectAll(); target.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
--- a/browser/metro/base/content/bindings/bindings.xml +++ b/browser/metro/base/content/bindings/bindings.xml @@ -255,20 +255,16 @@ <body><![CDATA[ let selectionStart = aTextbox.selectionStart; let selectionEnd = aTextbox.selectionEnd; let json = { types: ["input-text"], string: "" }; if (selectionStart != selectionEnd) { json.types.push("cut"); json.types.push("copy"); - json.string = aTextbox.value.slice(selectionStart, selectionEnd); - } else if (aTextbox.value) { - json.types.push("copy-all"); - json.string = aTextbox.value; } if (selectionStart > 0 || selectionEnd < aTextbox.textLength) json.types.push("select-all"); let clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]. getService(Ci.nsIClipboard); let flavors = ["text/unicode"];
--- a/browser/metro/base/content/bindings/grid.xml +++ b/browser/metro/base/content/bindings/grid.xml @@ -795,33 +795,34 @@ </handler> </handlers> </binding> <binding id="richgrid-item"> <content> <html:div anonid="anon-tile" class="tile-content" xbl:inherits="customImage"> <html:div class="tile-start-container" xbl:inherits="customImage"> - <html:div class="tile-icon-box"><xul:image anonid="anon-tile-icon" xbl:inherits="src=iconURI"/></html:div> + <html:div class="tile-icon-box" anonid="anon-tile-icon-box"><xul:image anonid="anon-tile-icon" xbl:inherits="src=iconURI"/></html:div> </html:div> <html:div anonid="anon-tile-label" class="tile-desc" xbl:inherits="xbl:text=label"/> </html:div> </content> <implementation> <property name="isBound" readonly="true" onget="return !!this._icon"/> <constructor> <![CDATA[ this.refresh(); ]]> </constructor> <property name="_contentBox" onget="return document.getAnonymousElementByAttribute(this, 'class', 'tile-content');"/> <property name="_textbox" onget="return document.getAnonymousElementByAttribute(this, 'class', 'tile-desc');"/> <property name="_top" onget="return document.getAnonymousElementByAttribute(this, 'class', 'tile-start-container');"/> <property name="_icon" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-tile-icon');"/> + <property name="_iconBox" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-tile-icon-box');"/> <property name="_label" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'anon-tile-label');"/> <property name="iconSrc" onset="this._icon.src = val; this.setAttribute('iconURI', val);" onget="return this._icon.src;" /> <property name="selected" onget="return this.hasAttribute('selected');" onset="if (val) this.setAttribute('selected', val); else this.removeAttribute('selected');" /> @@ -871,19 +872,31 @@ ]]></getter> </property> <property name="color" onget="return this.getAttribute('customColor');"> <setter><![CDATA[ if (val) { this.setAttribute("customColor", val); this._contentBox.style.backgroundColor = val; + + // overridden in tiles.css for non-thumbnail types + this._label.style.backgroundColor = val.replace(/rgb\(([^\)]+)\)/, 'rgba($1, 0.8)'); + + // Small icons get a border+background-color treatment. + // See tiles.css for large icon overrides + this._iconBox.style.borderColor = val.replace(/rgb\(([^\)]+)\)/, 'rgba($1, 0.6)'); + this._iconBox.style.backgroundColor = this.hasAttribute("tintColor") ? + this.getAttribute("tintColor") : "#fff"; } else { this.removeAttribute("customColor"); this._contentBox.style.removeProperty("background-color"); + this._label.style.removeProperty("background-color"); + this._iconBox.style.removeProperty("border-color"); + this._iconBox.style.removeProperty("background-color"); } ]]></setter> </property> <property name="backgroundImage" onget="return this.getAttribute('customImage');"> <setter><![CDATA[ if (val) { this.setAttribute("customImage", val);
--- a/browser/metro/base/content/bindings/urlbar.xml +++ b/browser/metro/base/content/bindings/urlbar.xml @@ -152,17 +152,17 @@ </body> </method> <method name="_getSelectedValueForClipboard"> <body> <![CDATA[ // Grab the actual input field's value, not our value, which could include moz-action: let inputVal = this.inputField.value; - let selectedVal = inputVal.substring(this.selectionStart, this.electionEnd); + let selectedVal = inputVal.substring(this.selectionStart, this.selectionEnd); // If the selection doesn't start at the beginning or doesn't span the full domain or // the URL bar is modified, nothing else to do here. if (this.selectionStart > 0 || this.valueIsTyped) return selectedVal; // The selection doesn't span the full domain if it doesn't contain a slash and is // followed by some character other than a slash. @@ -480,24 +480,27 @@ return; ]]> </handler> </handlers> </binding> <binding id="urlbar-autocomplete"> <content orient="horizontal"> - <xul:vbox id="results-vbox" flex="1"> - <xul:label class="meta-section-title" value="&autocompleteResultsHeader.label;"/> - <richgrid id="results-richgrid" rows="3" deferlayout="true" anonid="results" seltype="single" flex="1"/> + <xul:vbox id="results-vbox" anonid="results-container" flex="1"> + <xul:label class="meta-section-title" + value="&autocompleteResultsHeader.label;"/> + <richgrid id="results-richgrid" anonid="results" rows="3" flex="1" + seltype="single" deferlayout="true"/> </xul:vbox> <xul:vbox id="searches-vbox" flex="1"> - <xul:label class="meta-section-title" value="&autocompleteSearchesHeader.label;"/> - <richgrid id="searches-richgrid" rows="3" deferlayout="true" anonid="searches" seltype="single" flex="1"/> + <xul:label anonid="searches-header" class="meta-section-title"/> + <richgrid id="searches-richgrid" anonid="searches" rows="3" flex="1" + seltype="single" deferlayout="true"/> </xul:vbox> </content> <implementation implements="nsIAutoCompletePopup, nsIObserver"> <constructor> <![CDATA[ this.hidden = true; @@ -585,17 +588,21 @@ Elements.urlbarState.removeAttribute("autocomplete"); ]]> </body> </method> <!-- Updating grid content --> <field name="_grid">null</field> + <field name="_results" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'results');</field> + <field name="_resultsContainer" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'results-container');</field> + + <field name="_searchesHeader" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'searches-header');</field> <field name="_searches" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'searches');</field> <property name="_otherGrid" readonly="true"> <getter> <![CDATA[ if (this._grid == null) return null; @@ -615,32 +622,39 @@ <method name="invalidate"> <body> <![CDATA[ if (!this.popupOpen) return; this.updateResults(); - this.updateSearchEngineSubtitles(); + this.updateSearchEngineHeader(); ]]> </body> </method> <!-- Updating grid content: results --> <method name="updateResults"> <body> <![CDATA[ if (!this._isGridBound(this._results)) return; if (!this.input) return; + let haveNoResults = (this.matchCount == 0); + this._resultsContainer.hidden = haveNoResults; + + if (haveNoResults) { + return; + } + let controller = this.input.controller; let lastMatch = this.matchCount - 1; let iterCount = Math.max(this._results.itemCount, this.matchCount); // Swap out existing items for new search hit results for (let i = 0; i < iterCount; i++) { if (i > lastMatch) { let lastItem = this._results.itemCount - 1; @@ -701,30 +715,27 @@ item.setAttribute("iconURI", iconURI); }.bind(this)); this._searches.arrangeItems(); ]]> </body> </method> - <method name="updateSearchEngineSubtitles"> + <method name="updateSearchEngineHeader"> <body> <![CDATA[ if (!this._isGridBound(this._searches)) return; let searchString = this.input.controller.searchString; - let label = Strings.browser.formatStringFromName("opensearch.search", [searchString], 1); + let label = Strings.browser.formatStringFromName( + "opensearch.search.header", [searchString], 1); - for (let i = 0, len = this._searches.itemCount; i < len; i++) { - let item = this._searches.getItemAtIndex(i); - item.setAttribute("label", label); - item.refresh && item.refresh(); - } + this._searchesHeader.value = label; ]]> </body> </method> <!-- Selecting results --> <method name="selectBy"> <parameter name="aReverse"/>
--- a/browser/metro/base/content/startui/HistoryView.js +++ b/browser/metro/base/content/startui/HistoryView.js @@ -104,17 +104,17 @@ HistoryView.prototype = Util.extend(Obje let item = this._set.insertItemAt(aPos || 0, aTitle, aURI, this._inBatch); this._setContextActions(item); this._updateFavicon(item, aURI); }, _setContextActions: function bv__setContextActions(aItem) { let uri = aItem.getAttribute("value"); aItem.setAttribute("data-contextactions", "delete," + (this._pinHelper.isPinned(uri) ? "unpin" : "pin")); - if (aItem.refresh) aItem.refresh(); + if ("refresh" in aItem) aItem.refresh(); }, _sendNeedsRefresh: function bv__sendNeedsRefresh(){ // Event sent when all views need to refresh. let event = document.createEvent("Events"); event.initEvent("HistoryNeedsRefresh", true, false); window.dispatchEvent(event); }, @@ -268,16 +268,18 @@ HistoryView.prototype = Util.extend(Obje onPageChanged: function(aURI, aWhat, aValue) { if (aWhat == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) { let changedItems = this._set.getItemsByUrl(aURI.spec); for (let item of changedItems) { let currIcon = item.getAttribute("iconURI"); if (currIcon != aValue) { item.setAttribute("iconURI", aValue); + if("refresh" in item) + item.refresh(); } } } }, onDeleteVisits: function (aURI, aVisitTime, aGUID, aReason, aTransitionType) { if ((aReason == Ci.nsINavHistoryObserver.REASON_DELETED) && !this._inBatch) { this.populateGrid(true);
--- a/browser/metro/base/tests/mochiperf/browser_deck_01.js +++ b/browser/metro/base/tests/mochiperf/browser_deck_01.js @@ -1,11 +1,13 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + function test() { let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); Services.scriptloader.loadSubScript(testDir + "/perfhelpers.js", this); runTests(); } gTests.push({ desc: "deck offset",
--- a/browser/metro/base/tests/mochiperf/browser_firstx.js +++ b/browser/metro/base/tests/mochiperf/browser_firstx.js @@ -1,11 +1,13 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + function test() { let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); Services.scriptloader.loadSubScript(testDir + "/perfhelpers.js", this); runTests(); } gTests.push({ desc: "first x metrics",
--- a/browser/metro/base/tests/mochiperf/msgmanagerecho.js +++ b/browser/metro/base/tests/mochiperf/msgmanagerecho.js @@ -1,16 +1,18 @@ /* 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/. */ /* * TestEchoReceiver - receives json data, reserializes it and send it back. */ +"use strict"; + var TestEchoReceiver = { init: function init() { addMessageListener("Test:EchoRequest", this); }, receiveMessage: function receiveMessage(aMessage) { let json = aMessage.json; switch (aMessage.name) {
--- a/browser/metro/base/tests/mochiperf/perfhelpers.js +++ b/browser/metro/base/tests/mochiperf/perfhelpers.js @@ -1,11 +1,13 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + // Misc. constants const kInfoHeader = "PERF-TEST | "; const kDeclareId = "DECLARE "; const kResultsId = "RESULTS "; // Mochitest log data format version const kDataSetVersion = "1";
--- a/browser/metro/base/tests/mochitest/browser_selection_urlbar.js +++ b/browser/metro/base/tests/mochitest/browser_selection_urlbar.js @@ -129,15 +129,46 @@ gTests.push({ let editCoords = logicalCoordsForElement(edit); SelectionHelperUI.attachEditSession(ChromeSelectionHandler, editCoords.x, editCoords.y); edit.blur(); ok(!SelectionHelperUI.isSelectionUIVisible, "selection no longer enabled"); clearSelection(edit); } }); +function getClipboardCondition(aExpected) { + return () => aExpected == SpecialPowers.getClipboardData("text/unicode"); +} + +gTests.push({ + desc: "bug 894715 - URLs selected by touch are copied with trimming", + run: function () { + gWindow = window; + yield showNavBar(); + + let edit = document.getElementById("urlbar-edit"); + edit.value = "http://www.wikipedia.org/"; + + sendElementTap(window, edit); + edit.select(); + + let panel = ContextMenuUI._menuPopup._panel; + let promise = waitForEvent(panel, "popupshown") + sendContextMenuClickToElement(window, edit); + ok((yield promise), "show context menu"); + + let copy = document.getElementById("context-copy"); + ok(!copy.hidden, "copy menu item is visible") + + let condition = getClipboardCondition("http://www.wikipedia.org/"); + let promise = waitForCondition(condition); + sendElementTap(window, copy); + ok((yield promise), "copy text onto clipboard") + } +}) + function test() { if (!isLandscapeMode()) { todo(false, "browser_selection_tests need landscape mode to run."); return; } runTests(); }
--- a/browser/metro/base/tests/unit/test_util_extend.js +++ b/browser/metro/base/tests/unit/test_util_extend.js @@ -1,8 +1,10 @@ +"use strict"; + load('Util.js'); function run_test() { do_print("Testing Util.extend"); do_print("Check if function is defined"); do_check_true(!!Util.extend);
--- a/browser/metro/locales/en-US/chrome/browser.dtd +++ b/browser/metro/locales/en-US/chrome/browser.dtd @@ -8,17 +8,16 @@ <!ENTITY urlbar.accesskey "d"> <!ENTITY back.label "Back"> <!ENTITY forward.label "Forward"> <!ENTITY newtab.label "New Tab"> <!ENTITY closetab.label "Close Tab"> <!ENTITY autocompleteResultsHeader.label "Your Results"> -<!ENTITY autocompleteSearchesHeader.label "Internet Searches"> <!ENTITY appbarErrorConsole.label "Open error console"> <!ENTITY appbarJSShell.label "Open JavaScript shell"> <!ENTITY appbarFindInPage2.label "Find in page"> <!ENTITY appbarViewOnDesktop2.label "View on desktop"> <!ENTITY topSitesHeader.label "Top Sites"> <!ENTITY bookmarksHeader.label "Bookmarks">
--- a/browser/metro/locales/en-US/chrome/browser.properties +++ b/browser/metro/locales/en-US/chrome/browser.properties @@ -123,18 +123,19 @@ offlineApps.wantsTo=%S wants to store da # IndexedDB Quota increases indexedDBQuota.allow=Allow indexedDBQuota.wantsTo=%S wants to store a lot of data on your device for offline use. tabs.emptyTabTitle=New Tab # Open Search -# LOCALIZATION NOTE (opensearch.search): %S is the word or phrase typed by the user -opensearch.search=Search: %S +# LOCALIZATION NOTE (opensearch.search.header): %S is the word or phrase +# typed by the user in the urlbar to search +opensearch.search.header=Search for “%S” on: # Check for Updates in the About Panel - button labels and accesskeys # LOCALIZATION NOTE - all of the following update buttons labels will only be # displayed one at a time. So, if a button is displayed nothing else will # be displayed alongside of the button. The button when displayed is located # directly under the Firefox version in the about dialog (see bug 596813 for # screenshots). update.checkInsideButton.label=Check for Updates
--- a/browser/metro/modules/View.jsm +++ b/browser/metro/modules/View.jsm @@ -53,16 +53,23 @@ View.prototype = { aIconUri = makeURI(aIconUri); } aItem.iconSrc = aIconUri.spec; let faviconURL = (PlacesUtils.favicons.getFaviconLinkForIcon(aIconUri)).spec; let xpFaviconURI = makeURI(faviconURL.replace("moz-anno:favicon:","")); let successAction = function(foreground, background) { aItem.style.color = foreground; //color text aItem.setAttribute("customColor", background); + let matteColor = 0xffffff; // white + let alpha = 0.04; // the tint weight + let [,r,g,b] = background.match(/rgb\((\d+),(\d+),(\d+)/); + // get the rgb value that represents this color at given opacity over a white matte + let tintColor = ColorUtils.addRgbColors(matteColor, ColorUtils.createDecimalColorWord(r,g,b,alpha)); + aItem.setAttribute("tintColor", ColorUtils.convertDecimalToRgbColor(tintColor)); + if (aItem.refresh) { aItem.refresh(); } }; let failureAction = function() {}; ColorUtils.getForegroundAndBackgroundIconColors(xpFaviconURI, successAction, failureAction); }
--- a/browser/metro/theme/browser.css +++ b/browser/metro/theme/browser.css @@ -444,17 +444,17 @@ documenttab[selected] .documenttab-selec #navbar { visibility: visible; } #navbar:not([hiding]):not([visible]) > #toolbar-overlay { visibility: hidden; } .circularprogressindicator-progressRing { - margin: 0 @toolbar_horizontal_spacing@; + margin: -2px 18px; pointer-events:none; position: absolute; } /* Progress meter ---------------------------------------------------------- */ #progress-container { display: block;
index f0a4dbfdf948a8227a3076f6fa4876ee4b36b210..44de3fb173079de5bf89e9b94977d43f3e930e50 GIT binary patch literal 363 zc%17D@N?(olHy`uVBq!ia0vp^Iv~u!1|;QLq8Nb`TavfC3&Vd9T(EcfWS|IVfk$L9 z1A}ZE2s6em5^e+vvX^-Jy0YKp;1uQ3?A@~=9Vqm})5S3);_%y<rv(oiaJZZeoA@dy zW|g=2Io0+Uk<XEgpE@?T*SdLlD6k)ndmsGu?q|h7&6#|jZPQCGDD0ThXmxNY>l((W zOJ*V`{e6z?er@`J`TnCKFN=A5q=ML9UzrpX(Y|cTVX3VXLxX-k|L}s}IKgz{zoG>; zo7{Ytgc&(Kxu~^^D_5pla+*i`{4EZ<OLing7^s?kV7qrf`Uc;fg)?|0E-dnp=sMY3 zw8KdEU&i9IcO5IYJM4Hk@ux)S{#l#D92W8)i%*a0ck4<lot_<fGhXJ*mx3Q%ja##} zM=d^(l@b4A*TWME+_S8v%;dDoeRGt}Us~tfof%xe8Em3@ohN<|s|1DtgQu&X%Q~lo FCIA^ulwkk>
--- a/browser/metro/theme/platform.css +++ b/browser/metro/theme/platform.css @@ -572,33 +572,32 @@ arrowbox { .meta-section { margin: 0 @metro_spacing_large@; } .meta-section-title { font-size: @metro_font_large@; font-weight: 100; - display: none; cursor: default; } #start-container[viewstate="snapped"] { padding-top: 0; } #start-container[viewstate="snapped"] .meta-section-title, #start-container[viewstate="snapped"] richgrid { margin-top: @metro_spacing_xnormal@; padding: 0; } -#start-container[viewstate="snapped"] .meta-section-title.narrow-title, -#start-container:not([viewstate="snapped"]) .meta-section-title.wide-title { - display: block; +#start-container:not([viewstate="snapped"]) .meta-section-title.narrow-title, +#start-container[viewstate="snapped"] .meta-section-title.wide-title { + display: none; } .meta-section:not([expanded]) > .meta-section-title.narrow-title:-moz-locale-dir(ltr):after { content: ">"; } .meta-section:not([expanded]) > .meta-section-title.narrow-title:-moz-locale-dir(rtl):before { content: "<";
--- a/browser/metro/theme/tiles.css +++ b/browser/metro/theme/tiles.css @@ -57,71 +57,98 @@ richgriditem { transition: 300ms height ease-out, 150ms opacity ease-out, 100ms transform ease-out; } .tile-content { display: block; position: absolute; - background-color: #fff; + /* background-color colors the tile-edge, + and will normally be overridden with a favicon-based color */ + background-color: #ccc; background-origin: padding-box; /* content positioning within the grid "cell" gives us the gutters/spacing between tiles */ top: 2px; right: 6px; bottom: 10px; left: 6px; border: @metro_border_thin@ solid @tile_border_color@; box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.1); transition: 150ms transform ease-out; } .tile-start-container { position: absolute; top: 0; bottom: 0; right: 0; - left: 20px; - background: hsla(0,2%,98%,.95); + left: 10px; padding: 8px; + background-color: #fff; } richgriditem:not([tiletype="thumbnail"]) .tile-start-container { background-image: none!important; } .tile-icon-box { - display: inline-block; - padding: 4px; - background: #fff; + position: absolute; + top: 50%; + margin-top: -17px; + padding: 8px; + /* default color, may be overriden by a favicon-based color */ + background-color: white; + border: 1px solid #ccc; + border-radius: 1px; opacity: 1.0; } .tile-icon-box > image { + display: block; + width: 16px; + height: 16px; + list-style-image: url("chrome://browser/skin/images/identity-icons-generic.png"); +} + +/* for larger favicons (which includes the fallback icon) */ +richgriditem:not([iconURI]) .tile-icon-box, +richgriditem[iconURI=""] .tile-icon-box, +richgriditem[iconsize="large"] .tile-icon-box { + background-color: transparent!important; + border-color: transparent!important; + padding: 4px; +} + +richgriditem[iconsize="large"] .tile-icon-box > image, +.tile-icon-box > image[src=""] { width: 24px; height: 24px; - list-style-image: url("chrome://browser/skin/images/identity-icons-generic.png"); } .tile-desc { display: block; position: absolute; - bottom: 0; + top: 6px; + left: 52px; /* label goes to the right of the favicon */ right: 0; - left: 20px; /* the colored bar in the default tile is the background color peeking through */ - z-index: 1; - padding: 4px 8px; + padding: 1em 6px 6px 12px; color: #333; margin: 0; -moz-margin-start: 0; display: block; - font-size: 20px; + font-size: 16px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } +richgriditem:not([tiletype="thumbnail"]) .tile-desc { + background-color: transparent!important; +} + + richgriditem.collapsed { height: 0!important; overflow: hidden; opacity: 0; } richgriditem.collapsed > .tile-content { transform: scaleY(0); @@ -142,40 +169,46 @@ richgriditem[tiletype="thumbnail"] { width: @grid_double_column_width@; height: @grid_double_row_height@; -moz-box-pack: end; padding: 0px; color: #1a1a1a; } richgriditem[tiletype="thumbnail"] .tile-desc { - background: transparent; - margin: 0px; + margin: 0; + top: auto; + bottom: 0; left: 0; + padding: 4px 8px 4px 56px; } richgriditem[tiletype="thumbnail"] > .tile-content > .tile-desc { /* ensure thumbnail labels get their color from the parent richgriditem element */ color: inherit; } -/* put the image in place of the icon if there is an image background */ +/* thumbnail tiles use a screenshot thumbnail as the background */ richgriditem[tiletype="thumbnail"] > .tile-content > .tile-start-container { background-size: cover; background-position: top left; background-repeat: no-repeat; position: absolute; top: 0; - bottom: 32px; /* TODO: should be some em value? */; + bottom: 0; right: 0; left: 0; - background-color: hsla(0,2%,98%,.95); } richgriditem[tiletype="thumbnail"] .tile-icon-box { - visibility: collapse; + top: auto; + left: 10px; + bottom: 6px; + margin-top: 0; + z-index: 1; + box-shadow: 0px 0px 2px 2px rgba(0, 0, 0, 0.05), 0px 2px 0px rgba(0, 0, 0, 0.1); } /* selected tile indicator */ richgriditem[selected] > .tile-content::after { content: ""; pointer-events: none; display: block; position: absolute; @@ -257,35 +290,17 @@ richgriditem[bending] > .tile-content { richgriditem { width: @grid_double_column_width@; overflow: hidden; height: @compactgrid_row_height@; } .tile-desc { - top: 0; - left: 44px; /* label goes to the right of the favicon */ - right: 0; - padding: 8px; - } - - .tile-start-container { - position: absolute; - top: 0; - bottom: 0; - right: 0; - left: 6px; - background: #fff; - padding: 8px; - } - .tile-icon-box { - padding: 2px; - background: #fff; - opacity: 1.0; + padding: 0.5em 4px 4px 4px; } .tile-content { left: 0; right: 0; } richgriditem {
--- a/toolkit/components/passwordmgr/test/test_basic_form.html +++ b/toolkit/components/passwordmgr/test/test_basic_form.html @@ -3,45 +3,44 @@ <head> <title>Test for Login Manager</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="pwmgr_common.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Login Manager test: simple form fill + +<script> +commonInit(); +SimpleTest.waitForExplicitFinish(); + +/** Test for Login Manager: form fill, multiple forms. **/ + +function startTest() { + is($_(1, "uname").value, "testuser", "Checking for filled username"); + is($_(1, "pword").value, "testpass", "Checking for filled password"); + + SimpleTest.finish(); +} + +window.onload = startTest; +</script> + <p id="display"></p> <div id="content" style="display: none"> <form id="form1" action="formtest.js"> <p>This is form 1.</p> <input type="text" name="uname"> <input type="password" name="pword"> <button type="submit">Submit</button> <button type="reset"> Reset </button> </form> </div> -<pre id="test"> -<script class="testbody" type="text/javascript"> -commonInit(); -/** Test for Login Manager: form fill, multiple forms. **/ - -// Make sure that all forms in a document are processed. - -function startTest() { - is($_(1, "uname").value, "testuser", "Checking for filled username"); - is($_(1, "pword").value, "testpass", "Checking for filled password"); - - SimpleTest.finish(); -} - -window.onload = startTest; - -SimpleTest.waitForExplicitFinish(); -</script> -</pre> +<pre id="test"></pre> </body> </html>
--- a/toolkit/components/passwordmgr/test/test_basic_form_2.html +++ b/toolkit/components/passwordmgr/test/test_basic_form_2.html @@ -3,16 +3,26 @@ <head> <title>Test for Login Manager</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="pwmgr_common.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Login Manager test: simple form fill with autofillForms disabled +<script> +commonInit(); +SimpleTest.waitForExplicitFinish(); + +var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"] + .getService(SpecialPowers.Ci.nsILoginManager); +// Assume that the pref starts out true, so set to false +SpecialPowers.setBoolPref("signon.autofillForms", false); +</script> + <p id="display"></p> <div id="content" style="display: block"> <form id="form1" action="formtest.js"> <p>This is form 1.</p> <input type="text" name="uname"> <input type="password" name="pword"> @@ -23,47 +33,33 @@ Login Manager test: simple form fill wit </div> <pre id="test"> <script class="testbody" type="text/javascript"> /** Test for Login Manager: simple form fill with autofillForms disabled **/ -commonInit(); - -netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); - -var pwmgr = Components.classes["@mozilla.org/login-manager;1"]. - getService(Components.interfaces.nsILoginManager); -var prefs = Components.classes["@mozilla.org/preferences-service;1"]. - getService(Components.interfaces.nsIPrefService); -prefs = prefs.getBranch("signon."); -// Assume that the pref starts out true, so set to false -prefs.setBoolPref("autofillForms", false); - function startTest(){ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); // Ensure the form is empty at start is($_(1, "uname").value, "", "Checking for blank username"); is($_(1, "pword").value, "", "Checking for blank password"); // Call the public method, check return value is(pwmgr.fillForm(document.getElementById("form1")), true, "Checking return value of fillForm"); // Check that the form was filled is($_(1, "uname").value, "testuser", "Checking for filled username"); is($_(1, "pword").value, "testpass", "Checking for filled password"); // Reset pref (since we assumed it was true to start) - prefs.setBoolPref("autofillForms", true); + SpecialPowers.setBoolPref("signon.autofillForms", true); SimpleTest.finish(); } - window.onload = startTest; -SimpleTest.waitForExplicitFinish(); </script> </pre> </body> </html>
--- a/toolkit/components/passwordmgr/test/test_basic_form_autocomplete.html +++ b/toolkit/components/passwordmgr/test/test_basic_form_autocomplete.html @@ -4,96 +4,24 @@ <title>Test for Login Manager</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> <script type="text/javascript" src="pwmgr_common.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Login Manager test: multiple login autocomplete -<p id="display"></p> -<!-- we presumably can't hide the content for this test. --> -<div id="content"> - - <!-- form1 tests multiple matching logins --> - <form id="form1" action="http://autocomplete:8888/formtest.js" onsubmit="return false;"> - <input type="text" name="uname"> - <input type="password" name="pword"> - <button type="submit">Submit</button> - </form> - - <!-- other forms test single logins, with autocomplete=off set --> - <form id="form2" action="http://autocomplete2" onsubmit="return false;"> - <input type="text" name="uname"> - <input type="password" name="pword" autocomplete="off"> - <button type="submit">Submit</button> - </form> - - <form id="form3" action="http://autocomplete2" onsubmit="return false;"> - <input type="text" name="uname" autocomplete="off"> - <input type="password" name="pword"> - <button type="submit">Submit</button> - </form> - - <form id="form4" action="http://autocomplete2" onsubmit="return false;" autocomplete="off"> - <input type="text" name="uname"> - <input type="password" name="pword"> - <button type="submit">Submit</button> - </form> - - <form id="form5" action="http://autocomplete2" onsubmit="return false;"> - <input type="text" name="uname" autocomplete="off"> - <input type="password" name="pword" autocomplete="off"> - <button type="submit">Submit</button> - </form> - - <!-- control --> - <form id="form6" action="http://autocomplete2" onsubmit="return false;"> - <input type="text" name="uname"> - <input type="password" name="pword"> - <button type="submit">Submit</button> - </form> - - <!-- This form will be manipulated to insert a different username field. --> - <form id="form7" action="http://autocomplete3" onsubmit="return false;"> - <input type="text" name="uname"> - <input type="password" name="pword"> - <button type="submit">Submit</button> - </form> - - <!-- test for no autofill after onblur with blank username --> - <form id="form8" action="http://autocomplete4" onsubmit="return false;"> - <input type="text" name="uname"> - <input type="password" name="pword"> - <button type="submit">Submit</button> - </form> - - <!-- test autocomplete dropdown --> - <form id="form9" action="http://autocomplete5" onsubmit="return false;"> - <input type="text" name="uname"> - <input type="password" name="pword"> - <button type="submit">Submit</button> - </form> -</div> - -<pre id="test"> -<script class="testbody" type="text/javascript"> - -/** Test for Login Manager: multiple login autocomplete. **/ - +<script> commonInit(); - -var uname = $_(1, "uname"); -var pword = $_(1, "pword"); -const shiftModifier = Components.interfaces.nsIDOMEvent.SHIFT_MASK; +SimpleTest.waitForExplicitFinish(); // Get the pwmgr service -var pwmgr = SpecialPowers.wrap(Components).classes["@mozilla.org/login-manager;1"] - .getService(Components.interfaces.nsILoginManager); +var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"] + .getService(SpecialPowers.Ci.nsILoginManager); ok(pwmgr != null, "nsLoginManager service"); // Create some logins just for this form, since we'll be deleting them. var nsLoginInfo = SpecialPowers.wrap(Components).Constructor("@mozilla.org/login-manager/loginInfo;1", Components.interfaces.nsILoginInfo, "init"); ok(nsLoginInfo != null, "nsLoginInfo constructor"); @@ -159,16 +87,92 @@ try { pwmgr.addLogin(login6B); pwmgr.addLogin(login7); pwmgr.addLogin(login8A); pwmgr.addLogin(login8B); } catch (e) { ok(false, "addLogin threw: " + e); } +</script> +<p id="display"></p> + +<!-- we presumably can't hide the content for this test. --> +<div id="content"> + + <!-- form1 tests multiple matching logins --> + <form id="form1" action="http://autocomplete:8888/formtest.js" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- other forms test single logins, with autocomplete=off set --> + <form id="form2" action="http://autocomplete2" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword" autocomplete="off"> + <button type="submit">Submit</button> + </form> + + <form id="form3" action="http://autocomplete2" onsubmit="return false;"> + <input type="text" name="uname" autocomplete="off"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form4" action="http://autocomplete2" onsubmit="return false;" autocomplete="off"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form5" action="http://autocomplete2" onsubmit="return false;"> + <input type="text" name="uname" autocomplete="off"> + <input type="password" name="pword" autocomplete="off"> + <button type="submit">Submit</button> + </form> + + <!-- control --> + <form id="form6" action="http://autocomplete2" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- This form will be manipulated to insert a different username field. --> + <form id="form7" action="http://autocomplete3" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- test for no autofill after onblur with blank username --> + <form id="form8" action="http://autocomplete4" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- test autocomplete dropdown --> + <form id="form9" action="http://autocomplete5" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: multiple login autocomplete. **/ + + +var uname = $_(1, "uname"); +var pword = $_(1, "pword"); +const shiftModifier = Components.interfaces.nsIDOMEvent.SHIFT_MASK; // Restore the form to the default state. function restoreForm() { uname.value = ""; pword.value = ""; uname.focus(); } @@ -807,15 +811,13 @@ function startTest() { // shouldn't reach into browser internals like this and // shouldn't assume ID is consistent across products autocompletePopup = chromeWin.document.getElementById("PopupAutoComplete"); ok(autocompletePopup, "Got autocomplete popup"); runTest(1); } window.onload = startTest; - -SimpleTest.waitForExplicitFinish(); </script> </pre> </body> </html>
--- a/toolkit/components/passwordmgr/test/test_basic_form_html5.html +++ b/toolkit/components/passwordmgr/test/test_basic_form_html5.html @@ -3,16 +3,49 @@ <head> <title>Test for Login Manager</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="pwmgr_common.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Login Manager test: html5 input types (email, tel, url, etc.) +<script> +commonInit(); +SimpleTest.waitForExplicitFinish(); + +const Ci = SpecialPowers.Ci; +const Cc = SpecialPowers.Cc; +pwmgr = Cc["@mozilla.org/login-manager;1"]. + getService(Ci.nsILoginManager); + +login1 = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); +login2 = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); +login3 = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); +login4 = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + +login1.init("http://mochi.test:8888", "http://bug600551-1", null, + "testuser@example.com", "testpass1", "", ""); +login2.init("http://mochi.test:8888", "http://bug600551-2", null, + "555-555-5555", "testpass2", "", ""); +login3.init("http://mochi.test:8888", "http://bug600551-3", null, + "http://mozilla.org", "testpass3", "", ""); +login4.init("http://mochi.test:8888", "http://bug600551-4", null, + "123456789", "testpass4", "", ""); + +pwmgr.addLogin(login1); +pwmgr.addLogin(login2); +pwmgr.addLogin(login3); +pwmgr.addLogin(login4); +</script> + <p id="display"></p> <div id="content" style="display: none"> <form id="form1" action="http://bug600551-1"> <input type="email" name="uname"> <input type="password" name="pword"> <button type="submit">Submit</button> </form> @@ -94,48 +127,16 @@ Login Manager test: html5 input types (e </div> <pre id="test"> <script class="testbody" type="text/javascript"> /* Test for Login Manager: 600551 (Password manager not working with input type=email) */ -commonInit(); - -netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); - -const Ci = Components.interfaces; -const Cc = Components.classes; -pwmgr = Cc["@mozilla.org/login-manager;1"]. - getService(Ci.nsILoginManager); - -login1 = Cc["@mozilla.org/login-manager/loginInfo;1"]. - createInstance(Ci.nsILoginInfo); -login2 = Cc["@mozilla.org/login-manager/loginInfo;1"]. - createInstance(Ci.nsILoginInfo); -login3 = Cc["@mozilla.org/login-manager/loginInfo;1"]. - createInstance(Ci.nsILoginInfo); -login4 = Cc["@mozilla.org/login-manager/loginInfo;1"]. - createInstance(Ci.nsILoginInfo); - -login1.init("http://mochi.test:8888", "http://bug600551-1", null, - "testuser@example.com", "testpass1", "", ""); -login2.init("http://mochi.test:8888", "http://bug600551-2", null, - "555-555-5555", "testpass2", "", ""); -login3.init("http://mochi.test:8888", "http://bug600551-3", null, - "http://mozilla.org", "testpass3", "", ""); -login4.init("http://mochi.test:8888", "http://bug600551-4", null, - "123456789", "testpass4", "", ""); - -pwmgr.addLogin(login1); -pwmgr.addLogin(login2); -pwmgr.addLogin(login3); -pwmgr.addLogin(login4); - function startTest() { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); checkForm(1, "testuser@example.com", "testpass1"); checkForm(2, "555-555-5555", "testpass2"); checkForm(3, "http://mozilla.org", "testpass3"); checkForm(4, "123456789", "testpass4"); @@ -161,15 +162,12 @@ function startTest() { pwmgr.removeLogin(login2); pwmgr.removeLogin(login3); pwmgr.removeLogin(login4); SimpleTest.finish(); } window.onload = startTest; - -SimpleTest.waitForExplicitFinish(); - </script> </pre> </body> </html>
--- a/toolkit/components/passwordmgr/test/test_basic_form_observer_autocomplete.html +++ b/toolkit/components/passwordmgr/test/test_basic_form_observer_autocomplete.html @@ -3,53 +3,55 @@ <head> <title>Test for Login Manager</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="pwmgr_common.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Login Manager test: simple form with autocomplete off and notifying observers & normal form +<script> +const Cc = SpecialPowers.Cc; +const Ci = SpecialPowers.Ci; +netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); +const Cu = Components.utils; +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +commonInit(); +SimpleTest.waitForExplicitFinish(); +</script> + <p id="display"></p> <div id="content" style="display: block"> <form id="form1" action="formtest.js" autocomplete="off"> <p>This is form 1.</p> <input type="text" name="uname"> <input type="password" name="pword"> <button type="submit">Submit</button> <button type="reset"> Reset </button> </form> - + <form id="form2" action="formtest.js"> <p>This is form 2.</p> <input type="text" name="uname"> <input type="password" name="pword"> <button type="submit">Submit</button> <button type="reset"> Reset </button> </form> </div> <pre id="test"> <script class="testbody" type="text/javascript"> /** Test for Login Manager: simple form with autocomplete off and notifying observers & normal form **/ - -commonInit(); -netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - var TestObserver = { receivedNotification1 : false, receivedNotification2 : false, data1 : "", data2 : "", QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), observe : function (subject, topic, data) { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); @@ -94,14 +96,12 @@ function startTest(){ // Remove the observer os.removeObserver(TestObserver, "passwordmgr-found-form"); SimpleTest.finish(); } window.onload = startTest; - -SimpleTest.waitForExplicitFinish(); </script> </pre> </body> </html>
--- a/toolkit/components/passwordmgr/test/test_basic_form_observer_autofillForms.html +++ b/toolkit/components/passwordmgr/test/test_basic_form_observer_autofillForms.html @@ -3,16 +3,31 @@ <head> <title>Test for Login Manager</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="pwmgr_common.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Login Manager test: simple form with autofillForms disabled and notifying observers +<script> +commonInit(); +SimpleTest.waitForExplicitFinish(); + +const Cc = SpecialPowers.Cc; +const Ci = SpecialPowers.Ci; +netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); +const Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +// Assume that the pref starts out true, so set to false +SpecialPowers.setBoolPref("signon.autofillForms", false); +</script> + <p id="display"></p> <div id="content" style="display: block"> <form id="form1" action="formtest.js"> <p>This is form 1.</p> <input type="text" name="uname"> <input type="password" name="pword"> @@ -23,30 +38,16 @@ Login Manager test: simple form with aut </div> <pre id="test"> <script class="testbody" type="text/javascript"> /** Test for Login Manager: simple form with autofillForms disabled and notifying observers **/ -commonInit(); -netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -var prefs = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefService); -prefs = prefs.getBranch("signon."); -// Assume that the pref starts out true, so set to false -prefs.setBoolPref("autofillForms", false); - var TestObserver = { receivedNotificationFoundForm : false, receivedNotificationFoundLogins : false, dataFoundForm : "", dataFoundLogins : null, QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), observe : function (subject, topic, data) { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); @@ -87,24 +88,22 @@ function startTest(){ is(TestObserver.dataFoundLogins.get("passwordField"), $_(1, "pword"), "Checking password field is correct"); is(TestObserver.dataFoundLogins.get("foundLogins").constructor.name, "Array", "Checking foundLogins is array"); is(TestObserver.dataFoundLogins.get("foundLogins").length, 1, "Checking foundLogins contains one login"); ok(TestObserver.dataFoundLogins.get("selectedLogin").QueryInterface(Ci.nsILoginInfo), "Checking selectedLogin is nsILoginInfo"); ok(TestObserver.dataFoundLogins.get("selectedLogin").equals(TestObserver.dataFoundLogins.get("foundLogins")[0]), "Checking selectedLogin is found login"); // Reset pref (since we assumed it was true to start) - prefs.setBoolPref("autofillForms", true); + SpecialPowers.setBoolPref("signon.autofillForms", true); // Remove the observer os.removeObserver(TestObserver, "passwordmgr-found-form"); os.removeObserver(TestObserver, "passwordmgr-found-logins"); SimpleTest.finish(); } window.onload = startTest; - -SimpleTest.waitForExplicitFinish(); </script> </pre> </body> </html>
--- a/toolkit/components/passwordmgr/test/test_basic_form_observer_foundLogins.html +++ b/toolkit/components/passwordmgr/test/test_basic_form_observer_foundLogins.html @@ -3,29 +3,80 @@ <head> <title>Test for Login Manager</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="pwmgr_common.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Login Manager test: notifying observers of passwordmgr-found-logins +<script> +commonInit(); +SimpleTest.waitForExplicitFinish(); + +const Cc = SpecialPowers.Cc; +const Ci = SpecialPowers.Ci; +netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); +const Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +// Configure the login manager with two logins for one of the forms +// so we can do a multiple logins test. +var nsLoginInfo = + new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", + Components.interfaces.nsILoginInfo); +var login1 = new nsLoginInfo(); +login1.init("http://mochi.test:8888", "http://www.example.com", null, + "testuser1", "testpass1", "uname", "pword"); +var login2 = new nsLoginInfo(); +login2.init("http://mochi.test:8888", "http://www.example.com", null, + "testuser2", "testpass2", "uname", "pword"); +var pwmgr = Cc["@mozilla.org/login-manager;1"]. + getService(Ci.nsILoginManager); +pwmgr.addLogin(login1); +pwmgr.addLogin(login2); + +var TestObserver = { + results: {}, + QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), + observe : function (subject, topic, data) { + netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); + if (topic == "passwordmgr-found-logins") { + var formInfo = subject.QueryInterface(Ci.nsIPropertyBag2); + var id = formInfo.get("passwordField").form.id; + this.results[id].receivedNotification = true; + this.results[id].data = formInfo; + } + } +}; + +// Initialize the object that stores the results of notifications. +for (var formID of ["form1", "form2", "form3", "form4", "form5"]) + TestObserver.results[formID] = { receivedNotification: false, data: null }; + +// Add the observer +var os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); +os.addObserver(TestObserver, "passwordmgr-found-logins", false); +</script> + <p id="display"></p> <div id="content" style="display: block"> <form id="form1" action="formtest.js"> <p>This is form 1.</p> <input type="text" name="uname"> <input type="password" name="pword"> <button type="submit">Submit</button> <button type="reset"> Reset </button> </form> - + <form id="form2" action="formtest.js"> <p>This is form 2.</p> <input type="text" name="uname" value="existing"> <input type="password" name="pword"> <button type="submit">Submit</button> <button type="reset"> Reset </button> </form> @@ -59,63 +110,16 @@ Login Manager test: notifying observers </div> <pre id="test"> <script class="testbody" type="text/javascript"> /** Test for Login Manager: notifying observers of passwordmgr-found-logins **/ -commonInit(); -netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -// Configure the login manager with two logins for one of the forms -// so we can do a multiple logins test. -var nsLoginInfo = - new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", - Components.interfaces.nsILoginInfo); -var login1 = new nsLoginInfo(); -login1.init("http://mochi.test:8888", "http://www.example.com", null, - "testuser1", "testpass1", "uname", "pword"); -var login2 = new nsLoginInfo(); -login2.init("http://mochi.test:8888", "http://www.example.com", null, - "testuser2", "testpass2", "uname", "pword"); -var pwmgr = Cc["@mozilla.org/login-manager;1"]. - getService(Ci.nsILoginManager); -pwmgr.addLogin(login1); -pwmgr.addLogin(login2); - -var TestObserver = { - results: {}, - QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), - observe : function (subject, topic, data) { - netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); - if (topic == "passwordmgr-found-logins") { - var formInfo = subject.QueryInterface(Ci.nsIPropertyBag2); - var id = formInfo.get("passwordField").form.id; - this.results[id].receivedNotification = true; - this.results[id].data = formInfo; - } - } -}; - -// Initialize the object that stores the results of notifications. -for (var formID of ["form1", "form2", "form3", "form4", "form5"]) - TestObserver.results[formID] = { receivedNotification: false, data: null }; - -// Add the observer -var os = Cc["@mozilla.org/observer-service;1"]. - getService(Ci.nsIObserverService); -os.addObserver(TestObserver, "passwordmgr-found-logins", false); - function startTest(){ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); // Test notification of a form that was filled. is(TestObserver.results["form1"].receivedNotification, true, "Checking observer was notified"); is(TestObserver.results["form1"].data.get("didntFillReason"), null, "Checking didntFillReason is null"); is(TestObserver.results["form1"].data.get("usernameField"), $_(1, "uname"), "Checking username field is correct"); is(TestObserver.results["form1"].data.get("passwordField"), $_(1, "pword"), "Checking password field is correct"); @@ -176,14 +180,12 @@ function startTest(){ // Remove the logins added for the multiple logins test. pwmgr.removeLogin(login1); pwmgr.removeLogin(login2); SimpleTest.finish(); } window.onload = startTest; - -SimpleTest.waitForExplicitFinish(); </script> </pre> </body> </html>
--- a/toolkit/components/passwordmgr/test/test_basic_form_pwonly.html +++ b/toolkit/components/passwordmgr/test/test_basic_form_pwonly.html @@ -3,16 +3,49 @@ <head> <title>Test for Login Manager</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="pwmgr_common.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Login Manager test: forms and logins without a username. +<script> +commonInit(); +SimpleTest.waitForExplicitFinish(); + +var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"] + .getService(SpecialPowers.Ci.nsILoginManager); + +var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Components.interfaces.nsILoginInfo); +ok(nsLoginInfo != null, "nsLoginInfo constructor"); + +// pwlogin1 uses a unique formSubmitURL, to check forms where no other logins +// will apply. pwlogin2 uses the normal formSubmitURL, so that we can test +// forms with a mix of username and non-username logins that might apply. +// +// Note: pwlogin2 is deleted at the end of the test. + +pwlogin1 = new nsLoginInfo(); +pwlogin2 = new nsLoginInfo(); + +pwlogin1.init("http://mochi.test:8888", "http://mochi.test:1111", null, + "", "1234", "uname", "pword"); + +pwlogin2.init("http://mochi.test:8888", "http://mochi.test:8888", null, + "", "1234", "uname", "pword"); + +try { + pwmgr.addLogin(pwlogin1); + pwmgr.addLogin(pwlogin2); +} catch (e) { + ok(false, "addLogin threw: " + e); +} + +</script> <p id="display"></p> <div id="content" style="display: none"> <!-- simple form: no username field, 1 password field --> <form id='form1' action='http://mochi.test:1111/formtest.js'> 1 <input type='password' name='pname' value=''> @@ -149,18 +182,16 @@ password-only, the other is username+pas </div> <pre id="test"> <script class="testbody" type="text/javascript"> /** Test for Login Manager: password-only logins **/ - -commonInit(); function startTest() { checkForm(1, "1234"); checkForm(2, "1234", ""); checkForm(3, "1234", "", ""); checkUnmodifiedForm(4); checkForm(5, "", "1234"); @@ -176,55 +207,14 @@ function startTest() { checkUnmodifiedForm(12); checkUnmodifiedForm(13); netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); pwmgr.removeLogin(pwlogin2); SimpleTest.finish(); } - -netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); - -// Get the pwmgr service -var Cc_pwmgr = Components.classes["@mozilla.org/login-manager;1"]; -ok(Cc_pwmgr != null, "Access Cc[@mozilla.org/login-manager;1]"); - -var Ci_pwmgr = Components.interfaces.nsILoginManager; -ok(Ci_pwmgr != null, "Access Ci.nsILoginManager"); - -var pwmgr = Cc_pwmgr.getService(Ci_pwmgr); -ok(pwmgr != null, "pwmgr getService()"); - - -var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Components.interfaces.nsILoginInfo); -ok(nsLoginInfo != null, "nsLoginInfo constructor"); - -// pwlogin1 uses a unique formSubmitURL, to check forms where no other logins -// will apply. pwlogin2 uses the normal formSubmitURL, so that we can test -// forms with a mix of username and non-username logins that might apply. -// -// Note: pwlogin2 is deleted at the end of the test. - -pwlogin1 = new nsLoginInfo(); -pwlogin2 = new nsLoginInfo(); - -pwlogin1.init("http://mochi.test:8888", "http://mochi.test:1111", null, - "", "1234", "uname", "pword"); - -pwlogin2.init("http://mochi.test:8888", "http://mochi.test:8888", null, - "", "1234", "uname", "pword"); - -try { - pwmgr.addLogin(pwlogin1); - pwmgr.addLogin(pwlogin2); -} catch (e) { - ok(false, "addLogin threw: " + e); -} - window.onload = startTest; - -SimpleTest.waitForExplicitFinish(); </script> </pre> </body> </html>
--- a/toolkit/components/passwordmgr/test/test_bug_227640.html +++ b/toolkit/components/passwordmgr/test/test_bug_227640.html @@ -3,19 +3,27 @@ <head> <title>Test for Login Manager</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="pwmgr_common.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Login Manager test: 227640 +<script> +commonInit(); +SimpleTest.waitForExplicitFinish(); + +var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"] + .getService(SpecialPowers.Ci.nsILoginManager); +</script> + <p id="display"></p> <div id="content" style="display: none"> - + <!-- no autocomplete for password field --> <form id="form1" onsubmit="return checkSubmit(1)" method="get"> <input type="text" name="uname" value=""> <input type="password" name="pword" value="" autocomplete=off> <button type="submit">Submit</button> <button type="reset"> Reset </button> </form> @@ -142,17 +150,16 @@ Login Manager test: 227640 </form> </div> <pre id="test"> <script class="testbody" type="text/javascript"> /** Test for Login Manager: 227640 (password is saved even when the password field has autocomplete="off") **/ -commonInit(); // This test ensures that pwmgr does not save a username or password when // autocomplete=off is present. var numStartingLogins = 0; var numSubmittedForms = 0; function startTest() { @@ -225,31 +232,14 @@ function getFormSubmitButton(formNum) { // Counts the number of logins currently stored by password manager. function countLogins() { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); var logins = pwmgr.getAllLogins(); return logins.length; } - -// Get the pwmgr service -netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); - -var Cc_pwmgr = Components.classes["@mozilla.org/login-manager;1"]; -ok(Cc_pwmgr != null, "Access Cc[@mozilla.org/login-manager;1]"); - -var Ci_pwmgr = Components.interfaces.nsILoginManager; -ok(Ci_pwmgr != null, "Access Ci.nsILoginManager"); - -var pwmgr = Cc_pwmgr.getService(Ci_pwmgr); -ok(pwmgr != null, "pwmgr getService()"); - - window.onload = startTest; - -SimpleTest.waitForExplicitFinish(); - </script> </pre> </body> </html>
--- a/toolkit/components/passwordmgr/test/test_bug_427033.html +++ b/toolkit/components/passwordmgr/test/test_bug_427033.html @@ -3,71 +3,49 @@ <head> <title>Test for Login Manager</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="pwmgr_common.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Login Manager test: form with JS submit action +<script> +SimpleTest.waitForExplicitFinish(); +var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"] + .getService(SpecialPowers.Ci.nsILoginManager); +var jslogin = SpecialPowers.Cc["@mozilla.org/login-manager/loginInfo;1"] + .createInstance(SpecialPowers.Ci.nsILoginInfo); +jslogin.init("http://mochi.test:8888", "javascript:", null, + "jsuser", "jspass123", "uname", "pword"); +pwmgr.addLogin(jslogin); + +/** Test for Login Manager: JS action URL **/ + +function startTest() { + checkForm(1, "jsuser", "jspass123"); + + pwmgr.removeLogin(jslogin); + SimpleTest.finish(); +} + +window.onload = startTest; +</script> + <p id="display"></p> <div id="content" style="display: none"> <form id='form1' action='javascript:alert("never shows")'> 1 <input name="uname"> <input name="pword" type="password"> <button type='submit'>Submit</button> <button type='reset'> Reset </button> </form> </div> -<pre id="test"> -<script class="testbody" type="text/javascript"> - -/** Test for Login Manager: JS action URL **/ - -function startTest() { - - checkForm(1, "jsuser", "jspass123"); - - netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); - pwmgr.removeLogin(jslogin); - SimpleTest.finish(); -} - - -netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); - -// Get the pwmgr service -var Cc_pwmgr = Components.classes["@mozilla.org/login-manager;1"]; -ok(Cc_pwmgr != null, "Access Cc[@mozilla.org/login-manager;1]"); - -var Ci_pwmgr = Components.interfaces.nsILoginManager; -ok(Ci_pwmgr != null, "Access Ci.nsILoginManager"); - -var pwmgr = Cc_pwmgr.getService(Ci_pwmgr); -ok(pwmgr != null, "pwmgr getService()"); - -var jslogin = Components.classes["@mozilla.org/login-manager/loginInfo;1"]. - createInstance(Components.interfaces.nsILoginInfo); -ok(jslogin != null, "create a login"); - -jslogin.init("http://mochi.test:8888", "javascript:", null, - "jsuser", "jspass123", "uname", "pword"); - -try { - pwmgr.addLogin(jslogin); -} catch (e) { - ok(false, "addLogin threw: " + e); -} - -window.onload = startTest; - -SimpleTest.waitForExplicitFinish(); -</script> -</pre> +<pre id="test"></pre> </body> </html>
--- a/toolkit/components/passwordmgr/test/test_bug_444968.html +++ b/toolkit/components/passwordmgr/test/test_bug_444968.html @@ -3,16 +3,55 @@ <head> <title>Test for Login Manager</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="pwmgr_common.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Login Manager test: 444968 +<script> +commonInit(); +SimpleTest.waitForExplicitFinish(); + +const Ci = SpecialPowers.Ci; +const Cc = SpecialPowers.Cc; +pwmgr = Cc["@mozilla.org/login-manager;1"]. + getService(Ci.nsILoginManager); + +login1A = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); +login1B = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); +login2A = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); +login2B = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); +login2C = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + +login1A.init("http://mochi.test:8888", "http://bug444968-1", null, + "testuser1A", "testpass1A", "", ""); +login1B.init("http://mochi.test:8888", "http://bug444968-1", null, + "", "testpass1B", "", ""); + +login2A.init("http://mochi.test:8888", "http://bug444968-2", null, + "testuser2A", "testpass2A", "", ""); +login2B.init("http://mochi.test:8888", "http://bug444968-2", null, + "", "testpass2B", "", ""); +login2C.init("http://mochi.test:8888", "http://bug444968-2", null, + "testuser2C", "testpass2C", "", ""); + +pwmgr.addLogin(login1A); +pwmgr.addLogin(login1B); +pwmgr.addLogin(login2A); +pwmgr.addLogin(login2B); +pwmgr.addLogin(login2C); +</script> + <p id="display"></p> <div id="content" style="display: none"> <!-- first 3 forms have matching user+pass and pass-only logins --> <!-- user+pass form. --> <form id="form1" action="http://bug444968-1"> <input type="text" name="uname"> <input type="password" name="pword"> @@ -64,75 +103,32 @@ Login Manager test: 444968 </div> <pre id="test"> <script class="testbody" type="text/javascript"> /* Test for Login Manager: 444968 (password-only forms should prefer a * password-only login when present ) */ -commonInit(); - -netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); - -const Ci = Components.interfaces; -const Cc = Components.classes; -pwmgr = Cc["@mozilla.org/login-manager;1"]. - getService(Ci.nsILoginManager); - -login1A = Cc["@mozilla.org/login-manager/loginInfo;1"]. - createInstance(Ci.nsILoginInfo); -login1B = Cc["@mozilla.org/login-manager/loginInfo;1"]. - createInstance(Ci.nsILoginInfo); -login2A = Cc["@mozilla.org/login-manager/loginInfo;1"]. - createInstance(Ci.nsILoginInfo); -login2B = Cc["@mozilla.org/login-manager/loginInfo;1"]. - createInstance(Ci.nsILoginInfo); -login2C = Cc["@mozilla.org/login-manager/loginInfo;1"]. - createInstance(Ci.nsILoginInfo); - -login1A.init("http://mochi.test:8888", "http://bug444968-1", null, - "testuser1A", "testpass1A", "", ""); -login1B.init("http://mochi.test:8888", "http://bug444968-1", null, - "", "testpass1B", "", ""); - -login2A.init("http://mochi.test:8888", "http://bug444968-2", null, - "testuser2A", "testpass2A", "", ""); -login2B.init("http://mochi.test:8888", "http://bug444968-2", null, - "", "testpass2B", "", ""); -login2C.init("http://mochi.test:8888", "http://bug444968-2", null, - "testuser2C", "testpass2C", "", ""); - -pwmgr.addLogin(login1A); -pwmgr.addLogin(login1B); -pwmgr.addLogin(login2A); -pwmgr.addLogin(login2B); -pwmgr.addLogin(login2C); - function startTest() { - netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); - checkForm(1, "testuser1A", "testpass1A"); checkForm(2, "testpass1B"); checkForm(3, "testuser1A", "testpass1A"); checkUnmodifiedForm(4); // 2 logins match checkForm(5, "testpass2B"); checkForm(6, "testuser2A", "testpass2A"); checkForm(7, "testuser2C", "testpass2C"); pwmgr.removeLogin(login1A); pwmgr.removeLogin(login1B); pwmgr.removeLogin(login2A); pwmgr.removeLogin(login2B); pwmgr.removeLogin(login2C); - + SimpleTest.finish(); } window.onload = startTest; - -SimpleTest.waitForExplicitFinish(); - </script> </pre> </body> </html>
--- a/toolkit/components/passwordmgr/test/test_master_password.html +++ b/toolkit/components/passwordmgr/test/test_master_password.html @@ -4,28 +4,54 @@ <title>Test for Login Manager</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="pwmgr_common.js"></script> <script type="text/javascript" src="prompt_common.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Login Manager test: master password. +<script> +commonInit(); +SimpleTest.waitForExplicitFinish(); + +var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"] + .getService(SpecialPowers.Ci.nsILoginManager); +var pwcrypt = SpecialPowers.Cc["@mozilla.org/login-manager/crypto/SDR;1"] + .getService(Ci.nsILoginManagerCrypto); + +var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo); + +var exampleCom = "http://example.com/tests/toolkit/components/passwordmgr/test/"; +var exampleOrg = "http://example.org/tests/toolkit/components/passwordmgr/test/"; + +var login1 = new nsLoginInfo(); +var login2 = new nsLoginInfo(); + +login1.init("http://example.com", "http://example.com", null, + "user1", "pass1", "uname", "pword"); +login2.init("http://example.org", "http://example.org", null, + "user2", "pass2", "uname", "pword"); + +pwmgr.addLogin(login1); +pwmgr.addLogin(login2); +</script> + <p id="display"></p> <div id="content" style="display: none"> <iframe id="iframe1"></iframe> <iframe id="iframe2"></iframe> </div> <pre id="test"> <script class="testbody" type="text/javascript"> - -commonInit(); var testNum = 1; +var iframe1 = document.getElementById("iframe1"); +var iframe2 = document.getElementById("iframe2"); /* * handleDialog * * Invoked a short period of time after calling startCallbackTimer(), and * allows testing the actual auth dialog while it's being displayed. Tests * should call startCallbackTimer() each time the auth dialog is expected (the * timer is a one-shot). @@ -74,18 +100,22 @@ function handleDialog(doc, testNum) { dialog.acceptDialog(); else dialog.cancelDialog(); } ok(true, "handleDialog done"); didDialog = true; - if (testNum == 4) + if (testNum == 3) + SimpleTest.executeSoon(checkTest3); + else if (testNum == 4) checkTest4A(); + else if (testNum == 5) + SimpleTest.executeSoon(checkTest4C); } function startTest1() { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); ok(pwcrypt.isLoggedIn, "should be initially logged in (no MP)"); enableMasterPassword(); @@ -114,22 +144,16 @@ function startTest1() { ok(didDialog, "handleDialog was invoked"); ok(failedAsExpected, "getAllLogins should have thrown"); is(logins, null, "shouldn't have gotten logins"); ok(!pwcrypt.isLoggedIn, "should still be logged out"); // --- Test 3 --- // Load a single iframe to trigger a MP testNum++; - - // Note that because DOMContentLoaded is dispatched synchronously, the - // document's load event is blocked until after the MP entry (because - // pwmgr's listener doesn't return until after it processes the form, - // which is blocked waiting on a MP entry). - iframe1.onload = checkTest3; iframe1.src = exampleCom + "subtst_master_pass.html"; startCallbackTimer(); } function checkTest3() { ok(true, "checkTest3 starting"); ok(didDialog, "handleDialog was invoked"); @@ -142,17 +166,16 @@ function checkTest3() { ok(pwcrypt.isLoggedIn, "should be logged in"); logoutMasterPassword(); ok(!pwcrypt.isLoggedIn, "should be logged out"); // --- Test 4 --- // first part of loading 2 MP-triggering iframes testNum++; - iframe1.onload = checkTest4C; iframe1.src = exampleOrg + "subtst_master_pass.html"; // start the callback, but we'll not enter the MP, just call checkTest4A startCallbackTimer(); } function checkTest4A() { ok(true, "checkTest4A starting"); ok(didDialog, "handleDialog was invoked"); @@ -165,17 +188,19 @@ function checkTest4A() { ok(!pwcrypt.isLoggedIn, "should be logged out"); // XXX check that there's 1 MP window open // Load another iframe with a login form // This should detect that there's already a pending MP prompt, and not - // put up a second one. The load event will fire. + // put up a second one. The load event will fire (note that when pwmgr is + // driven from DOMContentLoaded, if that blocks due to prompting for a MP, + // the load even will also be blocked until the prompt is dismissed). iframe2.onload = checkTest4B; iframe2.src = exampleCom + "subtst_master_pass.html"; } function checkTest4B() { ok(true, "checkTest4B starting"); // iframe2 should load without having triggered a MP prompt (because one // is already waiting) @@ -191,17 +216,16 @@ function checkTest4B() { // Ok, now enter the MP. The MP prompt is already up, but we'll just reuse startCallBackTimer. // --- Test 5 --- testNum++; startCallbackTimer(); } function checkTest4C() { - // iframe1 finally loads after the MP entry. ok(true, "checkTest4C starting"); ok(didDialog, "handleDialog was invoked"); // We shouldn't have to worry about iframe1's load event racing with // filling of iframe2's data. We notify observers synchronously, so // iframe2's observer will process iframe2 before iframe1 even finishes // processing the form (which is blocking its load event). ok(pwcrypt.isLoggedIn, "should be logged in"); @@ -227,48 +251,14 @@ function finishTest() { disableMasterPassword(); ok(pwcrypt.isLoggedIn, "should be logged in"); pwmgr.removeLogin(login1); pwmgr.removeLogin(login2); SimpleTest.finish(); } - -netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); - -// Get the pwmgr service -var pwmgr = Cc["@mozilla.org/login-manager;1"]. - getService(Ci.nsILoginManager); -ok(pwmgr != null, "pwmgr getService()"); - -var pwcrypt = Cc["@mozilla.org/login-manager/crypto/SDR;1"]. - getService(Ci.nsILoginManagerCrypto); -ok(pwcrypt != null, "pwcrypt getService()"); - -var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo); -ok(nsLoginInfo != null, "nsLoginInfo constructor"); - -var exampleCom = "http://example.com/tests/toolkit/components/passwordmgr/test/"; -var exampleOrg = "http://example.org/tests/toolkit/components/passwordmgr/test/"; - -var login1 = new nsLoginInfo(); -var login2 = new nsLoginInfo(); - -login1.init("http://example.com", "http://example.com", null, - "user1", "pass1", "uname", "pword"); -login2.init("http://example.org", "http://example.org", null, - "user2", "pass2", "uname", "pword"); - -pwmgr.addLogin(login1); -pwmgr.addLogin(login2); - -var iframe1 = document.getElementById("iframe1"); -var iframe2 = document.getElementById("iframe2"); - window.onload = startTest1; - -SimpleTest.waitForExplicitFinish(); </script> </pre> </body> </html>
--- a/toolkit/components/passwordmgr/test/test_master_password_cleanup.html +++ b/toolkit/components/passwordmgr/test/test_master_password_cleanup.html @@ -6,18 +6,16 @@ <script type="text/javascript" src="pwmgr_common.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Login Manager test: master password cleanup <p id="display"></p> <div id="content" style="display: none"> -<iframe id="iframe1"></iframe> -<iframe id="iframe2"></iframe> </div> <pre id="test"> <script class="testbody" type="text/javascript"> /* * The entire purpose of this test is to make sure that, if the previous master
--- a/toolkit/components/passwordmgr/test/test_maxforms_1.html +++ b/toolkit/components/passwordmgr/test/test_maxforms_1.html @@ -7,18 +7,20 @@ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Test limiting number of forms filled. <p id="display"></p> <div id="content" style="display: none"></div> +<script> +commonInit(); +SimpleTest.waitForExplicitFinish(); -<script> var FORMS_TO_CREATE = 39; function createForm(id) { var template1 = "<form id='form" var template2 = "'><input name='u'><input type='password' name='p'></form>\n"; return id + template1 + id + template2; } @@ -29,17 +31,16 @@ for (var i = 1; i <= FORMS_TO_CREATE; i+ var theDiv = document.getElementById("content"); theDiv.innerHTML = formsHtml; </script> <pre id="test"> <script class="testbody" type="text/javascript"> -commonInit(); /** Test for Login Manager: form fill, multiple forms. **/ function startTest() { for (var i = 1; i <= FORMS_TO_CREATE; i++) { if (true) { is($_(i, "u").value, "testuser", "Checking for filled username in form " + i); is($_(i, "p").value, "testpass", "Checking for filled password in form " + i); } else { @@ -47,15 +48,12 @@ function startTest() { is($_(i, "p").value, "", "Checking for unfilled password in form " + i); } } SimpleTest.finish(); } window.onload = startTest; - -SimpleTest.waitForExplicitFinish(); </script> </pre> </body> </html> -
--- a/toolkit/components/passwordmgr/test/test_maxforms_2.html +++ b/toolkit/components/passwordmgr/test/test_maxforms_2.html @@ -7,18 +7,20 @@ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Test limiting number of forms filled. <p id="display"></p> <div id="content" style="display: none"></div> +<script> +commonInit(); +SimpleTest.waitForExplicitFinish(); -<script> var FORMS_TO_CREATE = 40; function createForm(id) { var template1 = "<form id='form" var template2 = "'><input name='u'><input type='password' name='p'></form>\n"; return id + template1 + id + template2; } @@ -29,17 +31,16 @@ for (var i = 1; i <= FORMS_TO_CREATE; i+ var theDiv = document.getElementById("content"); theDiv.innerHTML = formsHtml; </script> <pre id="test"> <script class="testbody" type="text/javascript"> -commonInit(); /** Test for Login Manager: form fill, multiple forms. **/ function startTest() { for (var i = 1; i <= FORMS_TO_CREATE; i++) { if (true) { is($_(i, "u").value, "testuser", "Checking for filled username in form " + i); is($_(i, "p").value, "testpass", "Checking for filled password in form " + i); } else { @@ -47,15 +48,12 @@ function startTest() { is($_(i, "p").value, "", "Checking for unfilled password in form " + i); } } SimpleTest.finish(); } window.onload = startTest; - -SimpleTest.waitForExplicitFinish(); </script> </pre> </body> </html> -
--- a/toolkit/components/passwordmgr/test/test_maxforms_3.html +++ b/toolkit/components/passwordmgr/test/test_maxforms_3.html @@ -7,18 +7,20 @@ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> Test limiting number of forms filled. <p id="display"></p> <div id="content" style="display: none"></div> +<script> +commonInit(); +SimpleTest.waitForExplicitFinish(); -<script> var FORMS_TO_CREATE = 41; function createForm(id) { var template1 = "<form id='form" var template2 = "'><input name='u'><input type='password' name='p'></form>\n"; return id + template1 + id + template2; } @@ -29,17 +31,16 @@ for (var i = 1; i <= FORMS_TO_CREATE; i+ var theDiv = document.getElementById("content"); theDiv.innerHTML = formsHtml; </script> <pre id="test"> <script class="testbody" type="text/javascript"> -commonInit(); /** Test for Login Manager: form fill, multiple forms. **/ function startTest() { for (var i = 1; i <= FORMS_TO_CREATE; i++) { if (i != 21) { is($_(i, "u").value, "testuser", "Checking for filled username in form " + i); is($_(i, "p").value, "testpass", "Checking for filled password in form " + i); } else { @@ -47,15 +48,12 @@ function startTest() { is($_(i, "p").value, "", "Checking for unfilled password in form " + i); } } SimpleTest.finish(); } window.onload = startTest; - -SimpleTest.waitForExplicitFinish(); </script> </pre> </body> </html> -
--- a/toolkit/mozapps/extensions/DeferredSave.jsm +++ b/toolkit/mozapps/extensions/DeferredSave.jsm @@ -68,32 +68,36 @@ function DeferredSave(aPath, aDataProvid this._path = aPath; this._dataProvider = aDataProvider; this._timer = null; // Some counters for telemetry // The total number of times the file was written this.totalSaves = 0; + // The number of times the data became dirty while // another save was in progress this.overlappedSaves = 0; + // Error returned by the most recent write (if any) + this._lastError = null; + if (aDelay && (aDelay > 0)) this._delay = aDelay; else this._delay = DEFAULT_SAVE_DELAY_MS; } DeferredSave.prototype = { get dirty() { return this._pending || this.writeInProgress; }, - get error() { + get lastError() { return this._lastError; }, // Start the pending timer if data is dirty _startTimer: function() { if (!this._pending) { return; }
--- a/toolkit/mozapps/extensions/Makefile.in +++ b/toolkit/mozapps/extensions/Makefile.in @@ -10,17 +10,17 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk ifeq (,$(filter aurora beta release esr,$(MOZ_UPDATE_CHANNEL))) DEFINES += -DMOZ_COMPATIBILITY_NIGHTLY=1 endif # This is used in multiple places, so is defined here to avoid it getting # out of sync. -DEFINES += -DMOZ_EXTENSIONS_DB_SCHEMA=14 +DEFINES += -DMOZ_EXTENSIONS_DB_SCHEMA=15 # Additional debugging info is exposed in debug builds, or by setting the # MOZ_EM_DEBUG environment variable when building. ifneq (,$(MOZ_EM_DEBUG)) DEFINES += -DMOZ_EM_DEBUG=1 else ifdef MOZ_DEBUG DEFINES += -DMOZ_EM_DEBUG=1 endif
--- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -73,17 +73,17 @@ const URI_EXTENSION_STRINGS = const STRING_TYPE_NAME = "type.%ID%.name"; const DIR_EXTENSIONS = "extensions"; const DIR_STAGE = "staged"; const DIR_XPI_STAGE = "staged-xpis"; const DIR_TRASH = "trash"; -const FILE_DATABASE = "extensions.sqlite"; +const FILE_DATABASE = "extensions.json"; const FILE_OLD_CACHE = "extensions.cache"; const FILE_INSTALL_MANIFEST = "install.rdf"; const FILE_XPI_ADDONS_LIST = "extensions.ini"; const KEY_PROFILEDIR = "ProfD"; const KEY_APPDIR = "XCurProcD"; const KEY_TEMPDIR = "TmpD"; const KEY_APP_DISTRIBUTION = "XREAppDist"; @@ -115,17 +115,22 @@ const PROP_LOCALE_SINGLE = ["name", "des const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"]; const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"]; // Properties that should be migrated where possible from an old database. These // shouldn't include properties that can be read directly from install.rdf files // or calculated const DB_MIGRATE_METADATA= ["installDate", "userDisabled", "softDisabled", "sourceURI", "applyBackgroundUpdates", - "releaseNotesURI", "isForeignInstall", "syncGUID"]; + "releaseNotesURI", "foreignInstall", "syncGUID"]; +// Properties to cache and reload when an addon installation is pending +const PENDING_INSTALL_METADATA = + ["syncGUID", "targetApplications", "userDisabled", "softDisabled", + "existingAddonID", "sourceURI", "releaseNotesURI", "installDate", + "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"]; // Note: When adding/changing/removing items here, remember to change the // DB schema version to ensure changes are picked up ASAP. const STATIC_BLOCKLIST_PATTERNS = [ { creator: "Mozilla Corp.", level: Ci.nsIBlocklistService.STATE_BLOCKED, blockID: "i162" }, { creator: "Mozilla.org", @@ -164,22 +169,25 @@ const MSG_JAR_FLUSH = "AddonJarFlush"; var gGlobalScope = this; /** * Valid IDs fit this pattern. */ var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i; ["LOG", "WARN", "ERROR"].forEach(function(aName) { - this.__defineGetter__(aName, function logFuncGetter() { - Components.utils.import("resource://gre/modules/AddonLogging.jsm"); - - LogManager.getLogger("addons.xpi", this); - return this[aName]; - }) + Object.defineProperty(this, aName, { + get: function logFuncGetter() { + Components.utils.import("resource://gre/modules/AddonLogging.jsm"); + + LogManager.getLogger("addons.xpi", this); + return this[aName]; + }, + configurable: true + }); }, this); const LAZY_OBJECTS = ["XPIDatabase"]; var gLazyObjectsLoaded = false; function loadLazyObjects() { @@ -192,19 +200,22 @@ function loadLazyObjects() { delete gGlobalScope[name]; gGlobalScope[name] = scope[name]; } gLazyObjectsLoaded = true; return scope; } for (let name of LAZY_OBJECTS) { - gGlobalScope.__defineGetter__(name, function lazyObjectGetter() { - let objs = loadLazyObjects(); - return objs[name]; + Object.defineProperty(gGlobalScope, name, { + get: function lazyObjectGetter() { + let objs = loadLazyObjects(); + return objs[name]; + }, + configurable: true }); } function findMatchingStaticBlocklistItem(aAddon) { for (let item of STATIC_BLOCKLIST_PATTERNS) { if ("creator" in item && typeof item.creator == "string") { if ((aAddon.defaultLocale && aAddon.defaultLocale.creator == item.creator) || @@ -493,16 +504,19 @@ function findClosestLocale(aLocales) { } /** * Sets the userDisabled and softDisabled properties of an add-on based on what * values those properties had for a previous instance of the add-on. The * previous instance may be a previous install or in the case of an application * version change the same add-on. * + * NOTE: this may modify aNewAddon in place; callers should save the database if + * necessary + * * @param aOldAddon * The previous instance of the add-on * @param aNewAddon * The new instance of the add-on * @param aAppVersion * The optional application version to use when checking the blocklist * or undefined to use the current application * @param aPlatformVersion @@ -579,21 +593,18 @@ function isUsableAddon(aAddon) { return true; } function isAddonDisabled(aAddon) { return aAddon.appDisabled || aAddon.softDisabled || aAddon.userDisabled; } -this.__defineGetter__("gRDF", function gRDFGetter() { - delete this.gRDF; - return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. - getService(Ci.nsIRDFService); -}); +XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1", + Ci.nsIRDFService); function EM_R(aProperty) { return gRDF.GetResource(PREFIX_NS_EM + aProperty); } /** * Converts an RDF literal, resource or integer into a string. * @@ -689,18 +700,21 @@ function loadManifestFromRDF(aUri, aStre } } PROP_LOCALE_SINGLE.forEach(function(aProp) { locale[aProp] = getRDFProperty(aDs, aSource, aProp); }); PROP_LOCALE_MULTI.forEach(function(aProp) { - locale[aProp] = getPropertyArray(aDs, aSource, - aProp.substring(0, aProp.length - 1)); + // Don't store empty arrays + let props = getPropertyArray(aDs, aSource, + aProp.substring(0, aProp.length - 1)); + if (props.length > 0) + locale[aProp] = props; }); return locale; } let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"]. createInstance(Ci.nsIRDFXMLParser) let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]. @@ -1316,29 +1330,34 @@ function recursiveRemove(aFile) { * Returns the timestamp of the most recently modified file in a directory, * or simply the file's own timestamp if it is not a directory. * * @param aFile * A non-null nsIFile object * @return Epoch time, as described above. 0 for an empty directory. */ function recursiveLastModifiedTime(aFile) { - if (aFile.isFile()) - return aFile.lastModifiedTime; - - if (aFile.isDirectory()) { - let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); - let entry, time; - let maxTime = aFile.lastModifiedTime; - while ((entry = entries.nextFile)) { - time = recursiveLastModifiedTime(entry); - maxTime = Math.max(time, maxTime); - } - entries.close(); - return maxTime; + try { + if (aFile.isFile()) + return aFile.lastModifiedTime; + + if (aFile.isDirectory()) { + let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); + let entry, time; + let maxTime = aFile.lastModifiedTime; + while ((entry = entries.nextFile)) { + time = recursiveLastModifiedTime(entry); + maxTime = Math.max(time, maxTime); + } + entries.close(); + return maxTime; + } + } + catch (e) { + WARN("Problem getting last modified time for " + aFile.path, e); } // If the file is something else, just ignore it. return 0; } /** * Gets a snapshot of directory entries. @@ -1743,17 +1762,17 @@ var XPIProvider = { this.defaultSkin); this.selectedSkin = this.currentSkin; this.applyThemeChange(); this.minCompatibleAppVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_APP_VERSION, null); this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, null); - this.enabledAddons = []; + this.enabledAddons = ""; Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false); Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false); let flushCaches = this.checkForChanges(aAppChanged, aOldAppVersion, aOldPlatformVersion); // Changes to installed extensions may have changed which theme is selected @@ -1860,21 +1879,23 @@ var XPIProvider = { // This is needed to allow xpcshell tests to simulate a restart this.extensionsActive = false; // Remove URI mappings again delete this._uriMappings; if (gLazyObjectsLoaded) { - XPIDatabase.shutdown(function shutdownCallback() { - Services.obs.notifyObservers(null, "xpi-provider-shutdown", null); + XPIDatabase.shutdown(function shutdownCallback(saveError) { + LOG("Notifying XPI shutdown observers"); + Services.obs.notifyObservers(null, "xpi-provider-shutdown", saveError); }); } else { + LOG("Notifying XPI shutdown observers"); Services.obs.notifyObservers(null, "xpi-provider-shutdown", null); } }, /** * Applies any pending theme change to the preferences. */ applyThemeChange: function XPI_applyThemeChange() { @@ -1922,17 +1943,17 @@ var XPIProvider = { } // Ensure any changes to the add-ons list are flushed to disk XPIDatabase.writeAddonsList(); Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false); }, /** - * Persists changes to XPIProvider.bootstrappedAddons to it's store (a pref). + * Persists changes to XPIProvider.bootstrappedAddons to its store (a pref). */ persistBootstrappedAddons: function XPI_persistBootstrappedAddons() { Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS, JSON.stringify(this.bootstrappedAddons)); }, /** * Adds a list of currently active add-ons to the next crash report. @@ -2482,17 +2503,17 @@ var XPIProvider = { try { // If not load it if (!newAddon) { let file = aInstallLocation.getLocationForID(aOldAddon.id); newAddon = loadManifestFromFile(file); applyBlocklistChanges(aOldAddon, newAddon); // Carry over any pendingUninstall state to add-ons modified directly - // in the profile. This is impoprtant when the attempt to remove the + // in the profile. This is important when the attempt to remove the // add-on in processPendingFileChanges failed and caused an mtime // change to the add-ons files. newAddon.pendingUninstall = aOldAddon.pendingUninstall; } // The ID in the manifest that was loaded must match the ID of the old // add-on. if (newAddon.id != aOldAddon.id) @@ -2513,41 +2534,43 @@ var XPIProvider = { } // Set the additional properties on the new AddonInternal newAddon._installLocation = aInstallLocation; newAddon.updateDate = aAddonState.mtime; newAddon.visible = !(newAddon.id in visibleAddons); // Update the database - XPIDatabase.updateAddonMetadata(aOldAddon, newAddon, aAddonState.descriptor); - if (newAddon.visible) { - visibleAddons[newAddon.id] = newAddon; + let newDBAddon = XPIDatabase.updateAddonMetadata(aOldAddon, newAddon, + aAddonState.descriptor); + if (newDBAddon.visible) { + visibleAddons[newDBAddon.id] = newDBAddon; // Remember add-ons that were changed during startup AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, - newAddon.id); + newDBAddon.id); // If this was the active theme and it is now disabled then enable the // default theme - if (aOldAddon.active && isAddonDisabled(newAddon)) + if (aOldAddon.active && isAddonDisabled(newDBAddon)) XPIProvider.enableDefaultTheme(); // If the new add-on is bootstrapped and active then call its install method - if (newAddon.active && newAddon.bootstrap) { + if (newDBAddon.active && newDBAddon.bootstrap) { // Startup cache must be flushed before calling the bootstrap script flushStartupCache(); - let installReason = Services.vc.compare(aOldAddon.version, newAddon.version) < 0 ? + let installReason = Services.vc.compare(aOldAddon.version, newDBAddon.version) < 0 ? BOOTSTRAP_REASONS.ADDON_UPGRADE : BOOTSTRAP_REASONS.ADDON_DOWNGRADE; let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); file.persistentDescriptor = aAddonState.descriptor; - XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, newAddon.type, file, - "install", installReason, { oldVersion: aOldAddon.version }); + XPIProvider.callBootstrapMethod(newDBAddon.id, newDBAddon.version, + newDBAddon.type, file, "install", + installReason, { oldVersion: aOldAddon.version }); return false; } return true; } return false; } @@ -2564,21 +2587,20 @@ var XPIProvider = { * @param aAddonState * The new state of the add-on * @return a boolean indicating if flushing caches is required to complete * changing this add-on */ function updateDescriptor(aInstallLocation, aOldAddon, aAddonState) { LOG("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor); - aOldAddon._descriptor = aAddonState.descriptor; + aOldAddon.descriptor = aAddonState.descriptor; aOldAddon.visible = !(aOldAddon.id in visibleAddons); - - // Update the database - XPIDatabase.setAddonDescriptor(aOldAddon, aAddonState.descriptor); + XPIDatabase.saveChanges(); + if (aOldAddon.visible) { visibleAddons[aOldAddon.id] = aOldAddon; if (aOldAddon.bootstrap && aOldAddon.active) { let bootstrap = oldBootstrappedAddons[aOldAddon.id]; bootstrap.descriptor = aAddonState.descriptor; XPIProvider.bootstrappedAddons[aOldAddon.id] = bootstrap; } @@ -2625,78 +2647,65 @@ var XPIProvider = { file.persistentDescriptor = aAddonState.descriptor; XPIProvider.callBootstrapMethod(aOldAddon.id, aOldAddon.version, aOldAddon.type, file, "install", BOOTSTRAP_REASONS.ADDON_INSTALL); // If it should be active then mark it as active otherwise unload // its scope if (!isAddonDisabled(aOldAddon)) { - aOldAddon.active = true; - XPIDatabase.updateAddonActive(aOldAddon); + XPIDatabase.updateAddonActive(aOldAddon, true); } else { XPIProvider.unloadBootstrapScope(newAddon.id); } } else { // Otherwise a restart is necessary changed = true; } } } // App version changed, we may need to update the appDisabled property. if (aUpdateCompatibility) { - // Create a basic add-on object for the new state to save reproducing - // the applyBlocklistChanges code - let newAddon = new AddonInternal(); - newAddon.id = aOldAddon.id; - newAddon.syncGUID = aOldAddon.syncGUID; - newAddon.version = aOldAddon.version; - newAddon.type = aOldAddon.type; - newAddon.appDisabled = !isUsableAddon(aOldAddon); - - // Sync the userDisabled flag to the selectedSkin - if (aOldAddon.type == "theme") - newAddon.userDisabled = aOldAddon.internalName != XPIProvider.selectedSkin; - - applyBlocklistChanges(aOldAddon, newAddon, aOldAppVersion, + let wasDisabled = isAddonDisabled(aOldAddon); + let wasAppDisabled = aOldAddon.appDisabled; + let wasUserDisabled = aOldAddon.userDisabled; + let wasSoftDisabled = aOldAddon.softDisabled; + + // This updates the addon's JSON cached data in place + applyBlocklistChanges(aOldAddon, aOldAddon, aOldAppVersion, aOldPlatformVersion); - - let wasDisabled = isAddonDisabled(aOldAddon); - let isDisabled = isAddonDisabled(newAddon); + aOldAddon.appDisabled = !isUsableAddon(aOldAddon); + + let isDisabled = isAddonDisabled(aOldAddon); // If either property has changed update the database. - if (newAddon.appDisabled != aOldAddon.appDisabled || - newAddon.userDisabled != aOldAddon.userDisabled || - newAddon.softDisabled != aOldAddon.softDisabled) { + if (wasAppDisabled != aOldAddon.appDisabled || + wasUserDisabled != aOldAddon.userDisabled || + wasSoftDisabled != aOldAddon.softDisabled) { LOG("Add-on " + aOldAddon.id + " changed appDisabled state to " + - newAddon.appDisabled + ", userDisabled state to " + - newAddon.userDisabled + " and softDisabled state to " + - newAddon.softDisabled); - XPIDatabase.setAddonProperties(aOldAddon, { - appDisabled: newAddon.appDisabled, - userDisabled: newAddon.userDisabled, - softDisabled: newAddon.softDisabled - }); + aOldAddon.appDisabled + ", userDisabled state to " + + aOldAddon.userDisabled + " and softDisabled state to " + + aOldAddon.softDisabled); + XPIDatabase.saveChanges(); } // If this is a visible add-on and it has changed disabled state then we // may need a restart or to update the bootstrap list. if (aOldAddon.visible && wasDisabled != isDisabled) { // Remember add-ons that became disabled or enabled by the application // change let change = isDisabled ? AddonManager.STARTUP_CHANGE_DISABLED : AddonManager.STARTUP_CHANGE_ENABLED; AddonManagerPrivate.addStartupChange(change, aOldAddon.id); if (aOldAddon.bootstrap) { // Update the add-ons active state - aOldAddon.active = !isDisabled; - XPIDatabase.updateAddonActive(aOldAddon); + XPIDatabase.updateAddonActive(aOldAddon, !isDisabled); } else { changed = true; } } } if (aOldAddon.visible && aOldAddon.active && aOldAddon.bootstrap) { @@ -2708,27 +2717,25 @@ var XPIProvider = { } return changed; } /** * Called when an add-on has been removed. * - * @param aInstallLocation - * The install location containing the add-on * @param aOldAddon * The AddonInternal as it appeared the last time the application * ran * @return a boolean indicating if flushing caches is required to complete * changing this add-on */ - function removeMetadata(aInstallLocation, aOldAddon) { + function removeMetadata(aOldAddon) { // This add-on has disappeared - LOG("Add-on " + aOldAddon.id + " removed from " + aInstallLocation); + LOG("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location); XPIDatabase.removeAddonMetadata(aOldAddon); // Remember add-ons that were uninstalled during startup if (aOldAddon.visible) { AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED, aOldAddon.id); } else if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED) @@ -2881,95 +2888,84 @@ var XPIProvider = { else newAddon.userDisabled = true; } } else { newAddon.active = (newAddon.visible && !isAddonDisabled(newAddon)) } - try { - // Update the database. - XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); - } - catch (e) { - // Failing to write the add-on into the database is non-fatal, the - // add-on will just be unavailable until we try again in a subsequent - // startup - ERROR("Failed to add add-on " + aId + " in " + aInstallLocation.name + - " to database", e); - return false; - } - - if (newAddon.visible) { + let newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor); + + if (newDBAddon.visible) { // Remember add-ons that were first detected during startup. if (isDetectedInstall) { // If a copy from a higher priority location was removed then this // add-on has changed if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_UNINSTALLED) - .indexOf(newAddon.id) != -1) { + .indexOf(newDBAddon.id) != -1) { AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, - newAddon.id); + newDBAddon.id); } else { AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED, - newAddon.id); + newDBAddon.id); } } // Note if any visible add-on is not in the application install location - if (newAddon._installLocation.name != KEY_APP_GLOBAL) + if (newDBAddon._installLocation.name != KEY_APP_GLOBAL) XPIProvider.allAppGlobal = false; - visibleAddons[newAddon.id] = newAddon; + visibleAddons[newDBAddon.id] = newDBAddon; let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL; let extraParams = {}; // If we're hiding a bootstrapped add-on then call its uninstall method - if (newAddon.id in oldBootstrappedAddons) { - let oldBootstrap = oldBootstrappedAddons[newAddon.id]; + if (newDBAddon.id in oldBootstrappedAddons) { + let oldBootstrap = oldBootstrappedAddons[newDBAddon.id]; extraParams.oldVersion = oldBootstrap.version; - XPIProvider.bootstrappedAddons[newAddon.id] = oldBootstrap; + XPIProvider.bootstrappedAddons[newDBAddon.id] = oldBootstrap; // If the old version is the same as the new version, or we're // recovering from a corrupt DB, don't call uninstall and install // methods. if (sameVersion || !isNewInstall) return false; - installReason = Services.vc.compare(oldBootstrap.version, newAddon.version) < 0 ? + installReason = Services.vc.compare(oldBootstrap.version, newDBAddon.version) < 0 ? BOOTSTRAP_REASONS.ADDON_UPGRADE : BOOTSTRAP_REASONS.ADDON_DOWNGRADE; let oldAddonFile = Cc["@mozilla.org/file/local;1"]. createInstance(Ci.nsIFile); oldAddonFile.persistentDescriptor = oldBootstrap.descriptor; - XPIProvider.callBootstrapMethod(newAddon.id, oldBootstrap.version, + XPIProvider.callBootstrapMethod(newDBAddon.id, oldBootstrap.version, oldBootstrap.type, oldAddonFile, "uninstall", - installReason, { newVersion: newAddon.version }); - XPIProvider.unloadBootstrapScope(newAddon.id); + installReason, { newVersion: newDBAddon.version }); + XPIProvider.unloadBootstrapScope(newDBAddon.id); // If the new add-on is bootstrapped then we must flush the caches // before calling the new bootstrap script - if (newAddon.bootstrap) + if (newDBAddon.bootstrap) flushStartupCache(); } - if (!newAddon.bootstrap) + if (!newDBAddon.bootstrap) return true; // Visible bootstrapped add-ons need to have their install method called let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); file.persistentDescriptor = aAddonState.descriptor; - XPIProvider.callBootstrapMethod(newAddon.id, newAddon.version, newAddon.type, file, + XPIProvider.callBootstrapMethod(newDBAddon.id, newDBAddon.version, newDBAddon.type, file, "install", installReason, extraParams); - if (!newAddon.active) - XPIProvider.unloadBootstrapScope(newAddon.id); + if (!newDBAddon.active) + XPIProvider.unloadBootstrapScope(newDBAddon.id); } return false; } let changed = false; let knownLocations = XPIDatabase.getInstallLocations(); @@ -2984,19 +2980,18 @@ var XPIProvider = { aState.reverse().forEach(function(aSt) { // We can't include the install location directly in the state as it has // to be cached as JSON. let installLocation = this.installLocationsByName[aSt.name]; let addonStates = aSt.addons; // Check if the database knows about any add-ons in this install location. - let pos = knownLocations.indexOf(installLocation.name); - if (pos >= 0) { - knownLocations.splice(pos, 1); + if (knownLocations.has(installLocation.name)) { + knownLocations.delete(installLocation.name); let addons = XPIDatabase.getAddonsInLocation(installLocation.name); // Iterate through the add-ons installed the last time the application // ran addons.forEach(function(aOldAddon) { // If a version of this add-on has been installed in an higher // priority install location then count it as changed if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED) .indexOf(aOldAddon.id) != -1) { @@ -3029,30 +3024,30 @@ var XPIProvider = { // add-ons in the application directory when the application version // has changed if (aOldAddon.id in aManifests[installLocation.name] || aOldAddon.updateDate != addonState.mtime || (aUpdateCompatibility && installLocation.name == KEY_APP_GLOBAL)) { changed = updateMetadata(installLocation, aOldAddon, addonState) || changed; } - else if (aOldAddon._descriptor != addonState.descriptor) { + else if (aOldAddon.descriptor != addonState.descriptor) { changed = updateDescriptor(installLocation, aOldAddon, addonState) || changed; } else { changed = updateVisibilityAndCompatibility(installLocation, aOldAddon, addonState) || changed; } if (aOldAddon.visible && aOldAddon._installLocation.name != KEY_APP_GLOBAL) XPIProvider.allAppGlobal = false; } else { - changed = removeMetadata(installLocation.name, aOldAddon) || changed; + changed = removeMetadata(aOldAddon) || changed; } }, this); } // All the remaining add-ons in this install location must be new. // Get the migration data for this install location. let locMigrateData = {}; @@ -3063,22 +3058,22 @@ var XPIProvider = { locMigrateData[id]) || changed; } }, this); // The remaining locations that had add-ons installed in them no longer // have any add-ons installed in them, or the locations no longer exist. // The metadata for the add-ons that were in them must be removed from the // database. - knownLocations.forEach(function(aLocation) { - let addons = XPIDatabase.getAddonsInLocation(aLocation); + for (let location of knownLocations) { + let addons = XPIDatabase.getAddonsInLocation(location); addons.forEach(function(aOldAddon) { - changed = removeMetadata(aLocation, aOldAddon) || changed; + changed = removeMetadata(aOldAddon) || changed; }, this); - }, this); + } // Tell Telemetry what we found AddonManagerPrivate.recordSimpleMeasure("modifiedUnpacked", modifiedUnpacked); if (modifiedUnpacked > 0) AddonManagerPrivate.recordSimpleMeasure("modifiedExceptInstallRDF", modifiedExManifest); AddonManagerPrivate.recordSimpleMeasure("modifiedXPI", modifiedXPI); // Cache the new install location states @@ -3187,18 +3182,16 @@ var XPIProvider = { // If the state has changed then we must update the database let cache = Prefs.getCharPref(PREF_INSTALL_CACHE, null); updateDatabase = cache != JSON.stringify(state); } // If the database doesn't exist and there are add-ons installed then we // must update the database however if there are no add-ons then there is // no need to update the database. - // Avoid using XPIDatabase.dbFileExists, as that code is lazy-loaded, - // and we want to avoid loading it until absolutely necessary. let dbFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true); if (!dbFile.exists()) updateDatabase = state.length > 0; if (!updateDatabase) { let bootstrapDescriptors = [this.bootstrappedAddons[b].descriptor for (b in this.bootstrappedAddons)]; @@ -3211,85 +3204,72 @@ var XPIProvider = { }); if (bootstrapDescriptors.length > 0) { WARN("Bootstrap state is invalid (missing add-ons: " + bootstrapDescriptors.toSource() + ")"); updateDatabase = true; } } - // Catch any errors during the main startup and rollback the database changes - let transationBegun = false; + // Catch and log any errors during the main startup try { let extensionListChanged = false; // If the database needs to be updated then open it and then update it // from the filesystem if (updateDatabase || hasPendingChanges) { - XPIDatabase.beginTransaction(); - transationBegun = true; - XPIDatabase.openConnection(false, true); - + XPIDatabase.syncLoadDB(false); try { extensionListChanged = this.processFileChanges(state, manifests, aAppChanged, aOldAppVersion, aOldPlatformVersion); } catch (e) { - ERROR("Error processing file changes", e); + ERROR("Failed to process extension changes at startup", e); } } AddonManagerPrivate.recordSimpleMeasure("installedUnpacked", this.unpackedAddons); if (aAppChanged) { // When upgrading the app and using a custom skin make sure it is still // compatible otherwise switch back the default if (this.currentSkin != this.defaultSkin) { - if (!transationBegun) { - XPIDatabase.beginTransaction(); - transationBegun = true; - } let oldSkin = XPIDatabase.getVisibleAddonForInternalName(this.currentSkin); if (!oldSkin || isAddonDisabled(oldSkin)) this.enableDefaultTheme(); } // When upgrading remove the old extensions cache to force older // versions to rescan the entire list of extensions - let oldCache = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_CACHE], true); - if (oldCache.exists()) - oldCache.remove(true); + try { + let oldCache = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_CACHE], true); + if (oldCache.exists()) + oldCache.remove(true); + } + catch (e) { + WARN("Unable to remove old extension cache " + oldCache.path, e); + } } // If the application crashed before completing any pending operations then // we should perform them now. if (extensionListChanged || hasPendingChanges) { LOG("Updating database with changes to installed add-ons"); - if (!transationBegun) { - XPIDatabase.beginTransaction(); - transationBegun = true; - } XPIDatabase.updateActiveAddons(); - XPIDatabase.commitTransaction(); XPIDatabase.writeAddonsList(); Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false); Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS, JSON.stringify(this.bootstrappedAddons)); return true; } LOG("No changes found"); - if (transationBegun) - XPIDatabase.commitTransaction(); } catch (e) { - ERROR("Error during startup file checks, rolling back any database " + - "changes", e); - if (transationBegun) - XPIDatabase.rollbackTransaction(); + ERROR("Error during startup file checks", e); } // Check that the add-ons list still exists let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST], true); if (addonsList.exists() == (state.length == 0)) { LOG("Add-ons list is invalid, rebuilding"); XPIDatabase.writeAddonsList(); @@ -3460,17 +3440,17 @@ var XPIProvider = { */ getAddonsWithOperationsByTypes: function XPI_getAddonsWithOperationsByTypes(aTypes, aCallback) { XPIDatabase.getVisibleAddonsWithPendingOperations(aTypes, function getAddonsWithOpsByTypes_getVisibleAddonsWithPendingOps(aAddons) { let results = [createWrapper(a) for each (a in aAddons)]; XPIProvider.installs.forEach(function(aInstall) { if (aInstall.state == AddonManager.STATE_INSTALLED && - !(aInstall.addon instanceof DBAddonInternal)) + !(aInstall.addon.inDatabase)) results.push(createWrapper(aInstall.addon)); }); aCallback(results); }); }, /** * Called to get the current AddonInstalls, optionally limiting to a list of @@ -3668,17 +3648,17 @@ var XPIProvider = { observe: function XPI_observe(aSubject, aTopic, aData) { switch (aData) { case PREF_EM_MIN_COMPAT_APP_VERSION: case PREF_EM_MIN_COMPAT_PLATFORM_VERSION: this.minCompatibleAppVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_APP_VERSION, null); this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, null); - this.updateAllAddonDisabledStates(); + this.updateAddonAppDisabledStates(); break; } }, /** * Tests whether enabling an add-on will require a restart. * * @param aAddon @@ -3778,17 +3758,17 @@ var XPIProvider = { // restarting if (Services.appinfo.inSafeMode) return false; // Add-ons that are already installed don't require a restart to install. // This wouldn't normally be called for an already installed add-on (except // for forming the operationsRequiringRestart flags) so is really here as // a safety measure. - if (aAddon instanceof DBAddonInternal) + if (aAddon.inDatabase) return false; // If we have an AddonInstall for this add-on then we can see if there is // an existing installed add-on with the same ID if ("_install" in aAddon && aAddon._install) { // If there is an existing installed add-on and uninstalling it would // require a restart then installing the update will also require a // restart @@ -3987,57 +3967,46 @@ var XPIProvider = { } LOG("Calling bootstrap method " + aMethod + " on " + aId + " version " + aVersion); try { this.bootstrapScopes[aId][aMethod](params, aReason); } catch (e) { - WARN("Exception running bootstrap method " + aMethod + " on " + - aId, e); + WARN("Exception running bootstrap method " + aMethod + " on " + aId, e); } } finally { if (aMethod == "shutdown") { LOG("Removing manifest for " + aFile.path); Components.manager.removeBootstrappedManifestLocation(aFile); } } }, /** - * Updates the appDisabled property for all add-ons. - */ - updateAllAddonDisabledStates: function XPI_updateAllAddonDisabledStates() { - let addons = XPIDatabase.getAddons(); - addons.forEach(function(aAddon) { - this.updateAddonDisabledState(aAddon); - }, this); - }, - - /** * Updates the disabled state for an add-on. Its appDisabled property will be - * calculated and if the add-on is changed appropriate notifications will be - * sent out to the registered AddonListeners. + * calculated and if the add-on is changed the database will be saved and + * appropriate notifications will be sent out to the registered AddonListeners. * * @param aAddon * The DBAddonInternal to update * @param aUserDisabled * Value for the userDisabled property. If undefined the value will * not change * @param aSoftDisabled * Value for the softDisabled property. If undefined the value will * not change. If true this will force userDisabled to be true * @throws if addon is not a DBAddonInternal */ updateAddonDisabledState: function XPI_updateAddonDisabledState(aAddon, aUserDisabled, aSoftDisabled) { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Can only update addon states for installed addons."); if (aUserDisabled !== undefined && aSoftDisabled !== undefined) { throw new Error("Cannot change userDisabled and softDisabled at the " + "same time"); } if (aUserDisabled === undefined) { aUserDisabled = aAddon.userDisabled; @@ -4100,18 +4069,17 @@ var XPIProvider = { } else { needsRestart = this.enableRequiresRestart(aAddon); AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, needsRestart); } if (!needsRestart) { - aAddon.active = !isDisabled; - XPIDatabase.updateAddonActive(aAddon); + XPIDatabase.updateAddonActive(aAddon, !isDisabled); if (isDisabled) { if (aAddon.bootstrap) { let file = aAddon._installLocation.getLocationForID(aAddon.id); this.callBootstrapMethod(aAddon.id, aAddon.version, aAddon.type, file, "shutdown", BOOTSTRAP_REASONS.ADDON_DISABLE); this.unloadBootstrapScope(aAddon.id); } AddonManagerPrivate.callAddonListeners("onDisabled", wrapper); @@ -4137,17 +4105,17 @@ var XPIProvider = { * uninstall if not. * * @param aAddon * The DBAddonInternal to uninstall * @throws if the addon cannot be uninstalled because it is in an install * location that does not allow it */ uninstallAddon: function XPI_uninstallAddon(aAddon) { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Can only uninstall installed addons."); if (aAddon._installLocation.locked) throw new Error("Cannot uninstall addons from locked install locations"); if ("_hasResourceCache" in aAddon) aAddon._hasResourceCache = new Map(); @@ -4179,18 +4147,17 @@ var XPIProvider = { // Reveal the highest priority add-on with the same ID function revealAddon(aAddon) { XPIDatabase.makeAddonVisible(aAddon); let wrappedAddon = createWrapper(aAddon); AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false); if (!isAddonDisabled(aAddon) && !XPIProvider.enableRequiresRestart(aAddon)) { - aAddon.active = true; - XPIDatabase.updateAddonActive(aAddon); + XPIDatabase.updateAddonActive(aAddon, true); } if (aAddon.bootstrap) { let file = aAddon._installLocation.getLocationForID(aAddon.id); XPIProvider.callBootstrapMethod(aAddon.id, aAddon.version, aAddon.type, file, "install", BOOTSTRAP_REASONS.ADDON_INSTALL); if (aAddon.active) { @@ -4250,17 +4217,17 @@ var XPIProvider = { /** * Cancels the pending uninstall of an add-on. * * @param aAddon * The DBAddonInternal to cancel uninstall for */ cancelUninstallAddon: function XPI_cancelUninstallAddon(aAddon) { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Can only cancel uninstall for installed addons."); cleanStagingDir(aAddon._installLocation.getStagingDir(), [aAddon.id]); XPIDatabase.setAddonProperties(aAddon, { pendingUninstall: false }); @@ -5239,17 +5206,17 @@ AddonInstall.prototype = { this.file.copyTo(this.installLocation.getStagingDir(), this.addon.id + ".xpi"); } if (requiresRestart) { // Point the add-on to its extracted files as the xpi may get deleted this.addon._sourceBundle = stagedAddon; - // Cache the AddonInternal as it may have updated compatibiltiy info + // Cache the AddonInternal as it may have updated compatibility info let stagedJSON = stagedAddon.clone(); stagedJSON.leafName = this.addon.id + ".json"; if (stagedJSON.exists()) stagedJSON.remove(true); let stream = Cc["@mozilla.org/network/file-output-stream;1"]. createInstance(Ci.nsIFileOutputStream); let converter = Cc["@mozilla.org/intl/converter-output-stream;1"]. createInstance(Ci.nsIConverterOutputStream); @@ -5308,78 +5275,73 @@ AddonInstall.prototype = { this.existingAddon.type, file, "uninstall", reason, { newVersion: this.addon.version }); XPIProvider.unloadBootstrapScope(this.existingAddon.id); flushStartupCache(); } if (!isUpgrade && this.existingAddon.active) { - this.existingAddon.active = false; - XPIDatabase.updateAddonActive(this.existingAddon); + XPIDatabase.updateAddonActive(this.existingAddon, false); } } // Install the new add-on into its final location let existingAddonID = this.existingAddon ? this.existingAddon.id : null; let file = this.installLocation.installAddon(this.addon.id, stagedAddon, existingAddonID); cleanStagingDir(stagedAddon.parent, []); // Update the metadata in the database this.addon._sourceBundle = file; this.addon._installLocation = this.installLocation; - this.addon.updateDate = recursiveLastModifiedTime(file); + this.addon.updateDate = recursiveLastModifiedTime(file); // XXX sync recursive scan this.addon.visible = true; if (isUpgrade) { - XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon, - file.persistentDescriptor); + this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon, + file.persistentDescriptor); } else { this.addon.installDate = this.addon.updateDate; this.addon.active = (this.addon.visible && !isAddonDisabled(this.addon)) - XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor); + this.addon = XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor); + } + + let extraParams = {}; + if (this.existingAddon) { + extraParams.oldVersion = this.existingAddon.version; + } + + if (this.addon.bootstrap) { + XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version, + this.addon.type, file, "install", + reason, extraParams); } - // Retrieve the new DBAddonInternal for the add-on we just added - let self = this; - XPIDatabase.getAddonInLocation(this.addon.id, this.installLocation.name, - function startInstall_getAddonInLocation(a) { - self.addon = a; - let extraParams = {}; - if (self.existingAddon) { - extraParams.oldVersion = self.existingAddon.version; - } - - if (self.addon.bootstrap) { - XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version, - self.addon.type, file, "install", + AddonManagerPrivate.callAddonListeners("onInstalled", + createWrapper(this.addon)); + + LOG("Install of " + this.sourceURI.spec + " completed."); + this.state = AddonManager.STATE_INSTALLED; + AddonManagerPrivate.callInstallListeners("onInstallEnded", + this.listeners, this.wrapper, + createWrapper(this.addon)); + + if (this.addon.bootstrap) { + if (this.addon.active) { + XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version, + this.addon.type, file, "startup", reason, extraParams); } - - AddonManagerPrivate.callAddonListeners("onInstalled", - createWrapper(self.addon)); - - LOG("Install of " + self.sourceURI.spec + " completed."); - self.state = AddonManager.STATE_INSTALLED; - AddonManagerPrivate.callInstallListeners("onInstallEnded", - self.listeners, self.wrapper, - createWrapper(self.addon)); - - if (self.addon.bootstrap) { - if (self.addon.active) { - XPIProvider.callBootstrapMethod(self.addon.id, self.addon.version, - self.addon.type, file, "startup", - reason, extraParams); - } - else { - XPIProvider.unloadBootstrapScope(self.addon.id); - } + else { + // XXX this makes it dangerous to do many things in onInstallEnded + // listeners because important cleanup hasn't been done yet + XPIProvider.unloadBootstrapScope(this.addon.id); } - }); + } } } catch (e) { WARN("Failed to install", e); if (stagedAddon.exists()) recursiveRemove(stagedAddon); this.state = AddonManager.STATE_INSTALL_FAILED; this.error = AddonManager.ERROR_FILE_ACCESS; @@ -5744,40 +5706,33 @@ UpdateChecker.prototype = { this.callListener("onNoCompatibilityUpdateAvailable", createWrapper(this.addon)); this.callListener("onNoUpdateAvailable", createWrapper(this.addon)); this.callListener("onUpdateFinished", createWrapper(this.addon), aError); } }; /** * The AddonInternal is an internal only representation of add-ons. It may - * have come from the database (see DBAddonInternal below) or an install - * manifest. + * have come from the database (see DBAddonInternal in XPIProviderUtils.jsm) + * or an install manifest. */ function AddonInternal() { } AddonInternal.prototype = { _selectedLocale: null, active: false, visible: false, userDisabled: false, appDisabled: false, softDisabled: false, sourceURI: null, releaseNotesURI: null, foreignInstall: false, - get isForeignInstall() { - return this.foreignInstall; - }, - set isForeignInstall(aVal) { - this.foreignInstall = aVal; - }, - get selectedLocale() { if (this._selectedLocale) return this._selectedLocale; let locale = findClosestLocale(this.locales); this._selectedLocale = locale ? locale : this.defaultLocale; return this._selectedLocale; }, @@ -5962,103 +5917,29 @@ AddonInternal.prototype = { * When an add-on install is pending its metadata will be cached in a file. * This method reads particular properties of that metadata that may be newer * than that in the install manifest, like compatibility information. * * @param aObj * A JS object containing the cached metadata */ importMetadata: function AddonInternal_importMetaData(aObj) { - ["syncGUID", "targetApplications", "userDisabled", "softDisabled", - "existingAddonID", "sourceURI", "releaseNotesURI", "installDate", - "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"] - .forEach(function(aProp) { + PENDING_INSTALL_METADATA.forEach(function(aProp) { if (!(aProp in aObj)) return; this[aProp] = aObj[aProp]; }, this); // Compatibility info may have changed so update appDisabled this.appDisabled = !isUsableAddon(this); } }; /** - * The DBAddonInternal is a special AddonInternal that has been retrieved from - * the database. Add-ons retrieved synchronously only have the basic metadata - * the rest is filled out synchronously when needed. Asynchronously read add-ons - * have all data available. - */ -function DBAddonInternal() { - this.__defineGetter__("targetApplications", function DBA_targetApplicationsGetter() { - delete this.targetApplications; - return this.targetApplications = XPIDatabase._getTargetApplications(this); - }); - - this.__defineGetter__("targetPlatforms", function DBA_targetPlatformsGetter() { - delete this.targetPlatforms; - return this.targetPlatforms = XPIDatabase._getTargetPlatforms(this); - }); - - this.__defineGetter__("locales", function DBA_localesGetter() { - delete this.locales; - return this.locales = XPIDatabase._getLocales(this); - }); - - this.__defineGetter__("defaultLocale", function DBA_defaultLocaleGetter() { - delete this.defaultLocale; - return this.defaultLocale = XPIDatabase._getDefaultLocale(this); - }); - - this.__defineGetter__("pendingUpgrade", function DBA_pendingUpgradeGetter() { - delete this.pendingUpgrade; - for (let install of XPIProvider.installs) { - if (install.state == AddonManager.STATE_INSTALLED && - !(install.addon instanceof DBAddonInternal) && - install.addon.id == this.id && - install.installLocation == this._installLocation) { - return this.pendingUpgrade = install.addon; - } - }; - }); -} - -DBAddonInternal.prototype = { - applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { - let changes = []; - this.targetApplications.forEach(function(aTargetApp) { - aUpdate.targetApplications.forEach(function(aUpdateTarget) { - if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility || - Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) { - aTargetApp.minVersion = aUpdateTarget.minVersion; - aTargetApp.maxVersion = aUpdateTarget.maxVersion; - changes.push(aUpdateTarget); - } - }); - }); - try { - XPIDatabase.updateTargetApplications(this, changes); - } - catch (e) { - // A failure just means that we discard the compatibility update - ERROR("Failed to update target application info in the database for " + - "add-on " + this.id, e); - return; - } - XPIProvider.updateAddonDisabledState(this); - } -} - -DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; -// Make it accessible to XPIDatabase. -XPIProvider.DBAddonInternal = DBAddonInternal; - - -/** * Creates an AddonWrapper for an AddonInternal. * * @param addon * The AddonInternal to wrap * @return an AddonWrapper or null if addon was null */ function createWrapper(aAddon) { if (!aAddon) @@ -6299,17 +6180,17 @@ function AddonWrapper(aAddon) { return val; }); this.__defineSetter__("syncGUID", function AddonWrapper_syncGUIDGetter(val) { if (aAddon.syncGUID == val) return val; - if (aAddon instanceof DBAddonInternal) + if (aAddon.inDatabase) XPIDatabase.setAddonSyncGUID(aAddon, val); aAddon.syncGUID = val; return val; }); this.__defineGetter__("install", function AddonWrapper_installGetter() { @@ -6326,17 +6207,17 @@ function AddonWrapper(aAddon) { if (aAddon._installLocation) return aAddon._installLocation.scope; return AddonManager.SCOPE_PROFILE; }); this.__defineGetter__("pendingOperations", function AddonWrapper_pendingOperationsGetter() { let pending = 0; - if (!(aAddon instanceof DBAddonInternal)) { + if (!(aAddon.inDatabase)) { // Add-on is pending install if there is no associated install (shouldn't // happen here) or if the install is in the process of or has successfully // completed the install. If an add-on is pending install then we ignore // any other pending operations. if (!aAddon._install || aAddon._install.state == AddonManager.STATE_INSTALLING || aAddon._install.state == AddonManager.STATE_INSTALLED) return AddonManager.PENDING_INSTALL; } @@ -6370,17 +6251,17 @@ function AddonWrapper(aAddon) { return ops; }); this.__defineGetter__("permissions", function AddonWrapper_permisionsGetter() { let permissions = 0; // Add-ons that aren't installed cannot be modified in any way - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) return permissions; if (!aAddon.appDisabled) { if (this.userDisabled) permissions |= AddonManager.PERM_CAN_ENABLE; else if (aAddon.type != "theme") permissions |= AddonManager.PERM_CAN_DISABLE; } @@ -6405,17 +6286,17 @@ function AddonWrapper(aAddon) { this.__defineGetter__("userDisabled", function AddonWrapper_userDisabledGetter() { return aAddon.softDisabled || aAddon.userDisabled; }); this.__defineSetter__("userDisabled", function AddonWrapper_userDisabledSetter(val) { if (val == this.userDisabled) return val; - if (aAddon instanceof DBAddonInternal) { + if (aAddon.inDatabase) { if (aAddon.type == "theme" && val) { if (aAddon.internalName == XPIProvider.defaultSkin) throw new Error("Cannot disable the default theme"); XPIProvider.enableDefaultTheme(); } else { XPIProvider.updateAddonDisabledState(aAddon, val); } @@ -6429,17 +6310,17 @@ function AddonWrapper(aAddon) { return val; }); this.__defineSetter__("softDisabled", function AddonWrapper_softDisabledSetter(val) { if (val == aAddon.softDisabled) return val; - if (aAddon instanceof DBAddonInternal) { + if (aAddon.inDatabase) { // When softDisabling a theme just enable the active theme if (aAddon.type == "theme" && val && !aAddon.userDisabled) { if (aAddon.internalName == XPIProvider.defaultSkin) throw new Error("Cannot disable the default theme"); XPIProvider.enableDefaultTheme(); } else { XPIProvider.updateAddonDisabledState(aAddon, undefined, val); @@ -6454,25 +6335,25 @@ function AddonWrapper(aAddon) { return val; }); this.isCompatibleWith = function AddonWrapper_isCompatiblewith(aAppVersion, aPlatformVersion) { return aAddon.isCompatibleWith(aAppVersion, aPlatformVersion); }; this.uninstall = function AddonWrapper_uninstall() { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Cannot uninstall an add-on that isn't installed"); if (aAddon.pendingUninstall) throw new Error("Add-on is already marked to be uninstalled"); XPIProvider.uninstallAddon(aAddon); }; this.cancelUninstall = function AddonWrapper_cancelUninstall() { - if (!(aAddon instanceof DBAddonInternal)) + if (!(aAddon.inDatabase)) throw new Error("Cannot cancel uninstall for an add-on that isn't installed"); if (!aAddon.pendingUninstall) throw new Error("Add-on is not marked to be uninstalled"); XPIProvider.cancelUninstallAddon(aAddon); }; this.findUpdates = function AddonWrapper_findUpdates(aListener, aReason, aAppVersion, aPlatformVersion) { new UpdateChecker(aAddon, aListener, aReason, aAppVersion, aPlatformVersion);
--- a/toolkit/mozapps/extensions/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/XPIProviderUtils.js @@ -2,44 +2,56 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; +const Cu = Components.utils; -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/AddonRepository.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm"); - +XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave", + "resource://gre/modules/DeferredSave.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); ["LOG", "WARN", "ERROR"].forEach(function(aName) { - this.__defineGetter__(aName, function logFuncGetter () { - Components.utils.import("resource://gre/modules/AddonLogging.jsm"); + Object.defineProperty(this, aName, { + get: function logFuncGetter () { + Cu.import("resource://gre/modules/AddonLogging.jsm"); - LogManager.getLogger("addons.xpi-utils", this); - return this[aName]; - }) + LogManager.getLogger("addons.xpi-utils", this); + return this[aName]; + }, + configurable: true + }); }, this); const KEY_PROFILEDIR = "ProfD"; const FILE_DATABASE = "extensions.sqlite"; +const FILE_JSON_DB = "extensions.json"; const FILE_OLD_DATABASE = "extensions.rdf"; const FILE_XPI_ADDONS_LIST = "extensions.ini"; // The value for this is in Makefile.in #expand const DB_SCHEMA = __MOZ_EXTENSIONS_DB_SCHEMA__; +// The last version of DB_SCHEMA implemented in SQLITE +const LAST_SQLITE_DB_SCHEMA = 14; const PREF_DB_SCHEMA = "extensions.databaseSchema"; const PREF_PENDING_OPERATIONS = "extensions.pendingOperations"; const PREF_EM_ENABLED_ADDONS = "extensions.enabledAddons"; const PREF_EM_DSS_ENABLED = "extensions.dss.enabled"; // Properties that only exist in the database const DB_METADATA = ["syncGUID", @@ -67,27 +79,37 @@ const FIELDS_ADDON = "internal_id, id, s // Properties that exist in the install manifest const PROP_METADATA = ["id", "version", "type", "internalName", "updateURL", "updateKey", "optionsURL", "optionsType", "aboutURL", "iconURL", "icon64URL"]; const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"]; const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"]; const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"]; +// Properties to save in JSON file +const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type", + "internalName", "updateURL", "updateKey", "optionsURL", + "optionsType", "aboutURL", "iconURL", "icon64URL", + "defaultLocale", "visible", "active", "userDisabled", + "appDisabled", "pendingUninstall", "descriptor", "installDate", + "updateDate", "applyBackgroundUpdates", "bootstrap", + "skinnable", "size", "sourceURI", "releaseNotesURI", + "softDisabled", "foreignInstall", "hasBinaryComponents", + "strictCompatibility", "locales", "targetApplications", + "targetPlatforms"]; +// Time to wait before async save of XPI JSON database, in milliseconds +const ASYNC_SAVE_DELAY_MS = 20; const PREFIX_ITEM_URI = "urn:mozilla:item:"; const RDFURI_ITEM_ROOT = "urn:mozilla:item:root" const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#"; -this.__defineGetter__("gRDF", function gRDFGetter() { - delete this.gRDF; - return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. - getService(Ci.nsIRDFService); -}); +XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1", + Ci.nsIRDFService); function EM_R(aProperty) { return gRDF.GetResource(PREFIX_NS_EM + aProperty); } /** * Converts an RDF literal, resource or integer into a string. * @@ -115,70 +137,91 @@ function getRDFValue(aLiteral) { * @param aProperty * The property to read * @return a string if the property existed or null */ function getRDFProperty(aDs, aResource, aProperty) { return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true)); } +/** + * Asynchronously fill in the _repositoryAddon field for one addon + */ +function getRepositoryAddon(aAddon, aCallback) { + if (!aAddon) { + aCallback(aAddon); + return; + } + function completeAddon(aRepositoryAddon) { + aAddon._repositoryAddon = aRepositoryAddon; + aAddon.compatibilityOverrides = aRepositoryAddon ? + aRepositoryAddon.compatibilityOverrides : + null; + aCallback(aAddon); + } + AddonRepository.getCachedAddonByID(aAddon.id, completeAddon); +} /** - * A mozIStorageStatementCallback that will asynchronously build DBAddonInternal - * instances from the results it receives. Once the statement has completed - * executing and all of the metadata for all of the add-ons has been retrieved - * they will be passed as an array to aCallback. - * - * @param aCallback - * A callback function to pass the array of DBAddonInternals to + * Wrap an API-supplied function in an exception handler to make it safe to call */ -function AsyncAddonListCallback(aCallback) { - this.callback = aCallback; - this.addons = []; +function safeCallback(aCallback) { + return function(...aArgs) { + try { + aCallback.apply(null, aArgs); + } + catch(ex) { + WARN("XPI Database callback failed", ex); + } + } } -AsyncAddonListCallback.prototype = { - callback: null, - complete: false, - count: 0, - addons: null, +/** + * A helper method to asynchronously call a function on an array + * of objects, calling a callback when function(x) has been gathered + * for every element of the array. + * WARNING: not currently error-safe; if the async function does not call + * our internal callback for any of the array elements, asyncMap will not + * call the callback parameter. + * + * @param aObjects + * The array of objects to process asynchronously + * @param aMethod + * Function with signature function(object, function aCallback(f_of_object)) + * @param aCallback + * Function with signature f([aMethod(object)]), called when all values + * are available + */ +function asyncMap(aObjects, aMethod, aCallback) { + var resultsPending = aObjects.length; + var results = [] + if (resultsPending == 0) { + aCallback(results); + return; + } - handleResult: function AsyncAddonListCallback_handleResult(aResults) { - let row = null; - while ((row = aResults.getNextRow())) { - this.count++; - let self = this; - XPIDatabase.makeAddonFromRowAsync(row, function handleResult_makeAddonFromRowAsync(aAddon) { - function completeAddon(aRepositoryAddon) { - aAddon._repositoryAddon = aRepositoryAddon; - aAddon.compatibilityOverrides = aRepositoryAddon ? - aRepositoryAddon.compatibilityOverrides : - null; - self.addons.push(aAddon); - if (self.complete && self.addons.length == self.count) - self.callback(self.addons); - } + function asyncMap_gotValue(aIndex, aValue) { + results[aIndex] = aValue; + if (--resultsPending == 0) { + aCallback(results); + } + } - if ("getCachedAddonByID" in AddonRepository) - AddonRepository.getCachedAddonByID(aAddon.id, completeAddon); - else - completeAddon(null); + aObjects.map(function asyncMap_each(aObject, aIndex, aArray) { + try { + aMethod(aObject, function asyncMap_callback(aResult) { + asyncMap_gotValue(aIndex, aResult); }); } - }, - - handleError: asyncErrorLogger, - - handleCompletion: function AsyncAddonListCallback_handleCompletion(aReason) { - this.complete = true; - if (this.addons.length == this.count) - this.callback(this.addons); - } -}; - + catch (e) { + WARN("Async map function failed", e); + asyncMap_gotValue(aIndex, undefined); + } + }); +} /** * A generator to synchronously return result rows from an mozIStorageStatement. * * @param aStatement * The statement to execute */ function resultRows(aStatement) { @@ -210,34 +253,16 @@ function logSQLError(aError, aErrorStrin * @param aError * A mozIStorageError to log */ function asyncErrorLogger(aError) { logSQLError(aError.result, aError.message); } /** - * A helper function to execute a statement synchronously and log any error - * that occurs. - * - * @param aStatement - * A mozIStorageStatement to execute - */ -function executeStatement(aStatement) { - try { - aStatement.execute(); - } - catch (e) { - logSQLError(XPIDatabase.connection.lastError, - XPIDatabase.connection.lastErrorString); - throw e; - } -} - -/** * A helper function to step a statement synchronously and log any error that * occurs. * * @param aStatement * A mozIStorageStatement to execute */ function stepStatement(aStatement) { try { @@ -288,355 +313,463 @@ function copyRowProperties(aRow, aProper if (!aTarget) aTarget = {}; aProperties.forEach(function(aProp) { aTarget[aProp] = aRow.getResultByName(aProp); }); return aTarget; } +/** + * The DBAddonInternal is a special AddonInternal that has been retrieved from + * the database. The constructor will initialize the DBAddonInternal with a set + * of fields, which could come from either the JSON store or as an + * XPIProvider.AddonInternal created from an addon's manifest + * @constructor + * @param aLoaded + * Addon data fields loaded from JSON or the addon manifest. + */ +function DBAddonInternal(aLoaded) { + copyProperties(aLoaded, PROP_JSON_FIELDS, this); + + if (aLoaded._installLocation) { + this._installLocation = aLoaded._installLocation; + this.location = aLoaded._installLocation._name; + } + else if (aLoaded.location) { + this._installLocation = XPIProvider.installLocationsByName[this.location]; + } + + this._key = this.location + ":" + this.id; + + try { + this._sourceBundle = this._installLocation.getLocationForID(this.id); + } + catch (e) { + // An exception will be thrown if the add-on appears in the database but + // not on disk. In general this should only happen during startup as + // this change is being detected. + } + + XPCOMUtils.defineLazyGetter(this, "pendingUpgrade", + function DBA_pendingUpgradeGetter() { + for (let install of XPIProvider.installs) { + if (install.state == AddonManager.STATE_INSTALLED && + !(install.addon.inDatabase) && + install.addon.id == this.id && + install.installLocation == this._installLocation) { + delete this.pendingUpgrade; + return this.pendingUpgrade = install.addon; + } + }; + return null; + }); +} + +DBAddonInternal.prototype = { + applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { + this.targetApplications.forEach(function(aTargetApp) { + aUpdate.targetApplications.forEach(function(aUpdateTarget) { + if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility || + Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) { + aTargetApp.minVersion = aUpdateTarget.minVersion; + aTargetApp.maxVersion = aUpdateTarget.maxVersion; + XPIDatabase.saveChanges(); + } + }); + }); + XPIProvider.updateAddonDisabledState(this); + }, + + get inDatabase() { + return true; + }, + + toJSON: function() { + return copyProperties(this, PROP_JSON_FIELDS); + } +} + +DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; + +/** + * Internal interface: find an addon from an already loaded addonDB + */ +function _findAddon(addonDB, aFilter) { + for (let [, addon] of addonDB) { + if (aFilter(addon)) { + return addon; + } + } + return null; +} + +/** + * Internal interface to get a filtered list of addons from a loaded addonDB + */ +function _filterDB(addonDB, aFilter) { + let addonList = []; + for (let [, addon] of addonDB) { + if (aFilter(addon)) { + addonList.push(addon); + } + } + + return addonList; +} + this.XPIDatabase = { // true if the database connection has been opened initialized: false, - // A cache of statements that are used and need to be finalized on shutdown - statementCache: {}, - // A cache of weak referenced DBAddonInternals so we can reuse objects where - // possible - addonCache: [], - // The nested transaction count - transactionCount: 0, // The database file - dbfile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true), + jsonFile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_JSON_DB], true), // Migration data loaded from an old version of the database. migrateData: null, // Active add-on directories loaded from extensions.ini and prefs at startup. activeBundles: null, - // The statements used by the database - statements: { - _getDefaultLocale: "SELECT id, name, description, creator, homepageURL " + - "FROM locale WHERE id=:id", - _getLocales: "SELECT addon_locale.locale, locale.id, locale.name, " + - "locale.description, locale.creator, locale.homepageURL " + - "FROM addon_locale JOIN locale ON " + - "addon_locale.locale_id=locale.id WHERE " + - "addon_internal_id=:internal_id", - _getTargetApplications: "SELECT addon_internal_id, id, minVersion, " + - "maxVersion FROM targetApplication WHERE " + - "addon_internal_id=:internal_id", - _getTargetPlatforms: "SELECT os, abi FROM targetPlatform WHERE " + - "addon_internal_id=:internal_id", - _readLocaleStrings: "SELECT locale_id, type, value FROM locale_strings " + - "WHERE locale_id=:id", - - addAddonMetadata_addon: "INSERT INTO addon VALUES (NULL, :id, :syncGUID, " + - ":location, :version, :type, :internalName, " + - ":updateURL, :updateKey, :optionsURL, " + - ":optionsType, :aboutURL, " + - ":iconURL, :icon64URL, :locale, :visible, :active, " + - ":userDisabled, :appDisabled, :pendingUninstall, " + - ":descriptor, :installDate, :updateDate, " + - ":applyBackgroundUpdates, :bootstrap, :skinnable, " + - ":size, :sourceURI, :releaseNotesURI, :softDisabled, " + - ":isForeignInstall, :hasBinaryComponents, " + - ":strictCompatibility)", - addAddonMetadata_addon_locale: "INSERT INTO addon_locale VALUES " + - "(:internal_id, :name, :locale)", - addAddonMetadata_locale: "INSERT INTO locale (name, description, creator, " + - "homepageURL) VALUES (:name, :description, " + - ":creator, :homepageURL)", - addAddonMetadata_strings: "INSERT INTO locale_strings VALUES (:locale, " + - ":type, :value)", - addAddonMetadata_targetApplication: "INSERT INTO targetApplication VALUES " + - "(:internal_id, :id, :minVersion, " + - ":maxVersion)", - addAddonMetadata_targetPlatform: "INSERT INTO targetPlatform VALUES " + - "(:internal_id, :os, :abi)", - - clearVisibleAddons: "UPDATE addon SET visible=0 WHERE id=:id", - updateAddonActive: "UPDATE addon SET active=:active WHERE " + - "internal_id=:internal_id", - - getActiveAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE active=1 AND " + - "type<>'theme' AND bootstrap=0", - getActiveTheme: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " + - "internalName=:internalName AND type='theme'", - getThemes: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type='theme'", + // Saved error object if we fail to read an existing database + _loadError: null, - getAddonInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE id=:id " + - "AND location=:location", - getAddons: "SELECT " + FIELDS_ADDON + " FROM addon", - getAddonsByType: "SELECT " + FIELDS_ADDON + " FROM addon WHERE type=:type", - getAddonsInLocation: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " + - "location=:location", - getInstallLocations: "SELECT DISTINCT location FROM addon", - getVisibleAddonForID: "SELECT " + FIELDS_ADDON + " FROM addon WHERE " + - "visible=1 AND id=:id", - getVisibleAddonForInternalName: "SELECT " + FIELDS_ADDON + " FROM addon " + - "WHERE visible=1 AND internalName=:internalName", - getVisibleAddons: "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1", - getVisibleAddonsWithPendingOperations: "SELECT " + FIELDS_ADDON + " FROM " + - "addon WHERE visible=1 " + - "AND (pendingUninstall=1 OR " + - "MAX(userDisabled,appDisabled)=active)", - getAddonBySyncGUID: "SELECT " + FIELDS_ADDON + " FROM addon " + - "WHERE syncGUID=:syncGUID", - makeAddonVisible: "UPDATE addon SET visible=1 WHERE internal_id=:internal_id", - removeAddonMetadata: "DELETE FROM addon WHERE internal_id=:internal_id", - // Equates to active = visible && !userDisabled && !softDisabled && - // !appDisabled && !pendingUninstall - setActiveAddons: "UPDATE addon SET active=MIN(visible, 1 - userDisabled, " + - "1 - softDisabled, 1 - appDisabled, 1 - pendingUninstall)", - setAddonProperties: "UPDATE addon SET userDisabled=:userDisabled, " + - "appDisabled=:appDisabled, " + - "softDisabled=:softDisabled, " + - "pendingUninstall=:pendingUninstall, " + - "applyBackgroundUpdates=:applyBackgroundUpdates WHERE " + - "internal_id=:internal_id", - setAddonDescriptor: "UPDATE addon SET descriptor=:descriptor WHERE " + - "internal_id=:internal_id", - setAddonSyncGUID: "UPDATE addon SET syncGUID=:syncGUID WHERE " + - "internal_id=:internal_id", - updateTargetApplications: "UPDATE targetApplication SET " + - "minVersion=:minVersion, maxVersion=:maxVersion " + - "WHERE addon_internal_id=:internal_id AND id=:id", - - createSavepoint: "SAVEPOINT 'default'", - releaseSavepoint: "RELEASE SAVEPOINT 'default'", - rollbackSavepoint: "ROLLBACK TO SAVEPOINT 'default'" - }, - - get dbfileExists() { - delete this.dbfileExists; - return this.dbfileExists = this.dbfile.exists(); - }, - set dbfileExists(aValue) { - delete this.dbfileExists; - return this.dbfileExists = aValue; + // Error reported by our most recent attempt to read or write the database, if any + get lastError() { + if (this._loadError) + return this._loadError; + if (this._deferredSave) + return this._deferredSave.lastError; + return null; }, /** - * Begins a new transaction in the database. Transactions may be nested. Data - * written by an inner transaction may be rolled back on its own. Rolling back - * an outer transaction will rollback all the changes made by inner - * transactions even if they were committed. No data is written to the disk - * until the outermost transaction is committed. Transactions can be started - * even when the database is not yet open in which case they will be started - * when the database is first opened. + * Mark the current stored data dirty, and schedule a flush to disk */ - beginTransaction: function XPIDB_beginTransaction() { - if (this.initialized) - this.getStatement("createSavepoint").execute(); - this.transactionCount++; + saveChanges: function() { + if (!this.initialized) { + throw new Error("Attempt to use XPI database when it is not initialized"); + } + + if (!this._deferredSave) { + this._deferredSave = new DeferredSave(this.jsonFile.path, + () => JSON.stringify(this), + ASYNC_SAVE_DELAY_MS); + } + + let promise = this._deferredSave.saveChanges(); + if (!this._schemaVersionSet) { + this._schemaVersionSet = true; + promise.then( + count => { + // Update the XPIDB schema version preference the first time we successfully + // save the database. + LOG("XPI Database saved, setting schema version preference to " + DB_SCHEMA); + Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA); + // Reading the DB worked once, so we don't need the load error + this._loadError = null; + }, + error => { + // Need to try setting the schema version again later + this._schemaVersionSet = false; + WARN("Failed to save XPI database", error); + // this._deferredSave.lastError has the most recent error so we don't + // need this any more + this._loadError = null; + }); + } + }, + + flush: function() { + // handle the "in memory only" and "saveChanges never called" cases + if (!this._deferredSave) { + return Promise.resolve(0); + } + + return this._deferredSave.flush(); }, /** - * Commits the most recent transaction. The data may still be rolled back if - * an outer transaction is rolled back. + * Converts the current internal state of the XPI addon database to + * a JSON.stringify()-ready structure */ - commitTransaction: function XPIDB_commitTransaction() { - if (this.transactionCount == 0) { - ERROR("Attempt to commit one transaction too many."); - return; + toJSON: function() { + if (!this.addonDB) { + // We never loaded the database? + throw new Error("Attempt to save database without loading it first"); } - if (this.initialized) - this.getStatement("releaseSavepoint").execute(); - this.transactionCount--; + let toSave = { + schemaVersion: DB_SCHEMA, + addons: [...this.addonDB.values()] + }; + return toSave; }, /** - * Rolls back the most recent transaction. The database will return to its - * state when the transaction was started. + * Pull upgrade information from an existing SQLITE database + * + * @return false if there is no SQLITE database + * true and sets this.migrateData to null if the SQLITE DB exists + * but does not contain useful information + * true and sets this.migrateData to + * {location: {id1:{addon1}, id2:{addon2}}, location2:{...}, ...} + * if there is useful information */ - rollbackTransaction: function XPIDB_rollbackTransaction() { - if (this.transactionCount == 0) { - ERROR("Attempt to rollback one transaction too many."); - return; + getMigrateDataFromSQLITE: function XPIDB_getMigrateDataFromSQLITE() { + let connection = null; + let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true); + // Attempt to open the database + try { + connection = Services.storage.openUnsharedDatabase(dbfile); } - - if (this.initialized) { - this.getStatement("rollbackSavepoint").execute(); - this.getStatement("releaseSavepoint").execute(); + catch (e) { + WARN("Failed to open sqlite database " + dbfile.path + " for upgrade", e); + return null; } - this.transactionCount--; + LOG("Migrating data from sqlite"); + let migrateData = this.getMigrateDataFromDatabase(connection); + connection.close(); + return migrateData; }, /** - * Attempts to open the database file. If it fails it will try to delete the - * existing file and create an empty database. If that fails then it will - * open an in-memory database that can be used during this session. + * Synchronously opens and reads the database file, upgrading from old + * databases or making a new DB if needed. * - * @param aDBFile - * The nsIFile to open - * @return the mozIStorageConnection for the database - */ - openDatabaseFile: function XPIDB_openDatabaseFile(aDBFile) { - LOG("Opening database"); - let connection = null; - - // Attempt to open the database - try { - connection = Services.storage.openUnsharedDatabase(aDBFile); - this.dbfileExists = true; - } - catch (e) { - ERROR("Failed to open database (1st attempt)", e); - // If the database was locked for some reason then assume it still - // has some good data and we should try to load it the next time around. - if (e.result != Cr.NS_ERROR_STORAGE_BUSY) { - try { - aDBFile.remove(true); - } - catch (e) { - ERROR("Failed to remove database that could not be opened", e); - } - try { - connection = Services.storage.openUnsharedDatabase(aDBFile); - } - catch (e) { - ERROR("Failed to open database (2nd attempt)", e); - - // If we have got here there seems to be no way to open the real - // database, instead open a temporary memory database so things will - // work for this session. - return Services.storage.openSpecialDatabase("memory"); - } - } - else { - return Services.storage.openSpecialDatabase("memory"); - } - } - - connection.executeSimpleSQL("PRAGMA synchronous = FULL"); - connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE"); - - return connection; - }, - - /** - * Opens a new connection to the database file. - * + * The possibilities, in order of priority, are: + * 1) Perfectly good, up to date database + * 2) Out of date JSON database needs to be upgraded => upgrade + * 3) JSON database exists but is mangled somehow => build new JSON + * 4) no JSON DB, but a useable SQLITE db we can upgrade from => upgrade + * 5) useless SQLITE DB => build new JSON + * 6) useable RDF DB => upgrade + * 7) useless RDF DB => build new JSON + * 8) Nothing at all => build new JSON * @param aRebuildOnError * A boolean indicating whether add-on information should be loaded * from the install locations if the database needs to be rebuilt. - * @return the migration data from the database if it was an old schema or - * null otherwise. + * (if false, caller is XPIProvider.checkForChanges() which will rebuild) */ - openConnection: function XPIDB_openConnection(aRebuildOnError, aForceOpen) { - delete this.connection; - - if (!aForceOpen && !this.dbfileExists) { - this.connection = null; - return {}; - } - - this.initialized = true; + syncLoadDB: function XPIDB_syncLoadDB(aRebuildOnError) { + // XXX TELEMETRY report synchronous opens (startup time) vs. delayed opens this.migrateData = null; - - this.connection = this.openDatabaseFile(this.dbfile); - - // If the database was corrupt or missing then the new blank database will - // have a schema version of 0. - let schemaVersion = this.connection.schemaVersion; - if (schemaVersion != DB_SCHEMA) { - // A non-zero schema version means that a schema has been successfully - // created in the database in the past so we might be able to get useful - // information from it - if (schemaVersion != 0) { - LOG("Migrating data from schema " + schemaVersion); - this.migrateData = this.getMigrateDataFromDatabase(); - - // Delete the existing database - this.connection.close(); - try { - if (this.dbfileExists) - this.dbfile.remove(true); - - // Reopen an empty database - this.connection = this.openDatabaseFile(this.dbfile); + let fstream = null; + let data = ""; + try { + LOG("Opening XPI database " + this.jsonFile.path); + fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. + createInstance(Components.interfaces.nsIFileInputStream); + fstream.init(this.jsonFile, -1, 0, 0); + let cstream = null; + try { + cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Components.interfaces.nsIConverterInputStream); + cstream.init(fstream, "UTF-8", 0, 0); + let (str = {}) { + let read = 0; + do { + read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value + data += str.value; + } while (read != 0); } - catch (e) { - ERROR("Failed to remove old database", e); - // If the file couldn't be deleted then fall back to an in-memory - // database - this.connection = Services.storage.openSpecialDatabase("memory"); - } + this.parseDB(data, aRebuildOnError); + } + catch(e) { + ERROR("Failed to load XPI JSON data from profile", e); + this.rebuildDatabase(aRebuildOnError); + } + finally { + if (cstream) + cstream.close(); + } + } + catch (e) { + if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND) { + this.upgradeDB(aRebuildOnError); } else { - let dbSchema = 0; - try { - dbSchema = Services.prefs.getIntPref(PREF_DB_SCHEMA); - } catch (e) {} - - if (dbSchema == 0) { - // Only migrate data from the RDF if we haven't done it before - this.migrateData = this.getMigrateDataFromRDF(); - } - } - - // At this point the database should be completely empty - try { - this.createSchema(); - } - catch (e) { - // If creating the schema fails, then the database is unusable, - // fall back to an in-memory database. - this.connection = Services.storage.openSpecialDatabase("memory"); - } - - // If there is no migration data then load the list of add-on directories - // that were active during the last run - if (!this.migrateData) - this.activeBundles = this.getActiveBundles(); - - if (aRebuildOnError) { - WARN("Rebuilding add-ons database from installed extensions."); - this.beginTransaction(); - try { - let state = XPIProvider.getInstallLocationStates(); - XPIProvider.processFileChanges(state, {}, false); - // Make sure to update the active add-ons and add-ons list on shutdown - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); - this.commitTransaction(); - } - catch (e) { - ERROR("Error processing file changes", e); - this.rollbackTransaction(); - } + this.rebuildUnreadableDB(e, aRebuildOnError); } } + finally { + if (fstream) + fstream.close(); + } + // If an async load was also in progress, resolve that promise with our DB; + // otherwise create a resolved promise + if (this._dbPromise) + this._dbPromise.resolve(this.addonDB); + else + this._dbPromise = Promise.resolve(this.addonDB); + }, - // If the database connection has a file open then it has the right schema - // by now so make sure the preferences reflect that. - if (this.connection.databaseFile) { - Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA); - Services.prefs.savePrefFile(null); + /** + * Parse loaded data, reconstructing the database if the loaded data is not valid + * @param aRebuildOnError + * If true, synchronously reconstruct the database from installed add-ons + */ + parseDB: function(aData, aRebuildOnError) { + try { + // dump("Loaded JSON:\n" + aData + "\n"); + let inputAddons = JSON.parse(aData); + // Now do some sanity checks on our JSON db + if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) { + // Content of JSON file is bad, need to rebuild from scratch + ERROR("bad JSON file contents"); + this.rebuildDatabase(aRebuildOnError); + return; + } + if (inputAddons.schemaVersion != DB_SCHEMA) { + // Handle mismatched JSON schema version. For now, we assume + // compatibility for JSON data, though we throw away any fields we + // don't know about + LOG("JSON schema mismatch: expected " + DB_SCHEMA + + ", actual " + inputAddons.schemaVersion); + } + // If we got here, we probably have good data + // Make AddonInternal instances from the loaded data and save them + let addonDB = new Map(); + for (let loadedAddon of inputAddons.addons) { + let newAddon = new DBAddonInternal(loadedAddon); + addonDB.set(newAddon._key, newAddon); + }; + this.addonDB = addonDB; + LOG("Successfully read XPI database"); + this.initialized = true; } - - // Begin any pending transactions - for (let i = 0; i < this.transactionCount; i++) - this.connection.executeSimpleSQL("SAVEPOINT 'default'"); + catch(e) { + // If we catch and log a SyntaxError from the JSON + // parser, the xpcshell test harness fails the test for us: bug 870828 + if (e.name == "SyntaxError") { + ERROR("Syntax error parsing saved XPI JSON data"); + } + else { + ERROR("Failed to load XPI JSON data from profile", e); + } + this.rebuildDatabase(aRebuildOnError); + } }, /** - * A lazy getter for the database connection. + * Upgrade database from earlier (sqlite or RDF) version if available + */ + upgradeDB: function(aRebuildOnError) { + try { + let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA); + if (schemaVersion <= LAST_SQLITE_DB_SCHEMA) { + // we should have an older SQLITE database + this.migrateData = this.getMigrateDataFromSQLITE(); + } + // else we've upgraded before but the JSON file is gone, fall through + // and rebuild from scratch + } + catch(e) { + // No schema version pref means either a really old upgrade (RDF) or + // a new profile + this.migrateData = this.getMigrateDataFromRDF(); + } + + this.rebuildDatabase(aRebuildOnError); + }, + + /** + * Reconstruct when the DB file exists but is unreadable + * (for example because read permission is denied) + */ + rebuildUnreadableDB: function(aError, aRebuildOnError) { + WARN("Extensions database " + this.jsonFile.path + + " exists but is not readable; rebuilding in memory", aError); + // Remember the error message until we try and write at least once, so + // we know at shutdown time that there was a problem + this._loadError = aError; + // XXX TELEMETRY report when this happens? + this.rebuildDatabase(aRebuildOnError); + }, + + /** + * Open and read the XPI database asynchronously, upgrading if + * necessary. If any DB load operation fails, we need to + * synchronously rebuild the DB from the installed extensions. + * + * @return Promise<Map> resolves to the Map of loaded JSON data stored + * in this.addonDB; never rejects. */ - get connection() { - this.openConnection(true); - return this.connection; + asyncLoadDB: function XPIDB_asyncLoadDB(aDBCallback) { + // Already started (and possibly finished) loading + if (this._dbPromise) { + return this._dbPromise; + } + + LOG("Starting async load of XPI database " + this.jsonFile.path); + return this._dbPromise = OS.File.read(this.jsonFile.path).then( + byteArray => { + if (this._addonDB) { + LOG("Synchronous load completed while waiting for async load"); + return this.addonDB; + } + LOG("Finished async read of XPI database, parsing..."); + let decoder = new TextDecoder(); + let data = decoder.decode(byteArray); + this.parseDB(data, true); + return this.addonDB; + }) + .then(null, + error => { + if (this._addonDB) { + LOG("Synchronous load completed while waiting for async load"); + return this.addonDB; + } + if (error.becauseNoSuchFile) { + this.upgradeDB(true); + } + else { + // it's there but unreadable + this.rebuildUnreadableDB(error, true); + } + return this.addonDB; + }); + }, + + /** + * Rebuild the database from addon install directories. If this.migrateData + * is available, uses migrated information for settings on the addons found + * during rebuild + * @param aRebuildOnError + * A boolean indicating whether add-on information should be loaded + * from the install locations if the database needs to be rebuilt. + * (if false, caller is XPIProvider.checkForChanges() which will rebuild) + */ + rebuildDatabase: function XIPDB_rebuildDatabase(aRebuildOnError) { + this.addonDB = new Map(); + this.initialized = true; + + // If there is no migration data then load the list of add-on directories + // that were active during the last run + if (!this.migrateData) + this.activeBundles = this.getActiveBundles(); + + if (aRebuildOnError) { + WARN("Rebuilding add-ons database from installed extensions."); + try { + let state = XPIProvider.getInstallLocationStates(); + XPIProvider.processFileChanges(state, {}, false); + } + catch (e) { + ERROR("Failed to rebuild XPI database from installed extensions", e); + } + // Make to update the active add-ons and add-ons list on shutdown + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + } }, /** * Gets the list of file descriptors of active extension directories or XPI * files from the add-ons list. This must be loaded from disk since the * directory service gives no easy way to get both directly. This list doesn't * include themes as preferences already say which theme is currently active * - * @return an array of persisitent descriptors for the directories + * @return an array of persistent descriptors for the directories */ getActiveBundles: function XPIDB_getActiveBundles() { let bundles = []; let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST], true); if (!addonsList.exists()) @@ -736,23 +869,23 @@ this.XPIDatabase = { }, /** * Retrieves migration data from a database that has an older or newer schema. * * @return an object holding information about what add-ons were previously * userDisabled and any updated compatibility information */ - getMigrateDataFromDatabase: function XPIDB_getMigrateDataFromDatabase() { + getMigrateDataFromDatabase: function XPIDB_getMigrateDataFromDatabase(aConnection) { let migrateData = {}; // Attempt to migrate data from a different (even future!) version of the // database try { - var stmt = this.connection.createStatement("PRAGMA table_info(addon)"); + var stmt = aConnection.createStatement("PRAGMA table_info(addon)"); const REQUIRED = ["internal_id", "id", "location", "userDisabled", "installDate", "version"]; let reqCount = 0; let props = []; for (let row in resultRows(stmt)) { if (REQUIRED.indexOf(row.name) != -1) { @@ -768,34 +901,36 @@ this.XPIDatabase = { } if (reqCount < REQUIRED.length) { ERROR("Unable to read anything useful from the database"); return null; } stmt.finalize(); - stmt = this.connection.createStatement("SELECT " + props.join(",") + " FROM addon"); + stmt = aConnection.createStatement("SELECT " + props.join(",") + " FROM addon"); for (let row in resultRows(stmt)) { if (!(row.location in migrateData)) migrateData[row.location] = {}; let addonData = { targetApplications: [] } migrateData[row.location][row.id] = addonData; props.forEach(function(aProp) { + if (aProp == "isForeignInstall") + addonData.foreignInstall = (row[aProp] == 1); if (DB_BOOL_METADATA.indexOf(aProp) != -1) addonData[aProp] = row[aProp] == 1; else addonData[aProp] = row[aProp]; }) } - var taStmt = this.connection.createStatement("SELECT id, minVersion, " + + var taStmt = aConnection.createStatement("SELECT id, minVersion, " + "maxVersion FROM " + "targetApplication WHERE " + "addon_internal_id=:internal_id"); for (let location in migrateData) { for (let id in migrateData[location]) { taStmt.params.internal_id = migrateData[location][id].internal_id; delete migrateData[location][id].internal_id; @@ -825,1078 +960,469 @@ this.XPIDatabase = { }, /** * Shuts down the database connection and releases all cached objects. */ shutdown: function XPIDB_shutdown(aCallback) { LOG("shutdown"); if (this.initialized) { - for each (let stmt in this.statementCache) - stmt.finalize(); - this.statementCache = {}; - this.addonCache = []; - - if (this.transactionCount > 0) { - ERROR(this.transactionCount + " outstanding transactions, rolling back."); - while (this.transactionCount > 0) - this.rollbackTransaction(); - } - - // If we are running with an in-memory database then force a new - // extensions.ini to be written to disk on the next startup - if (!this.connection.databaseFile) - Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); + // If our last database I/O had an error, try one last time to save. + if (this.lastError) + this.saveChanges(); this.initialized = false; - let connection = this.connection; - delete this.connection; - // Re-create the connection smart getter to allow the database to be - // re-loaded during testing. - this.__defineGetter__("connection", function connectionGetter() { - this.openConnection(true); - return this.connection; - }); + // Make sure any pending writes of the DB are complete, and we + // finish cleaning up, and then call back + this.flush() + .then(null, error => { + ERROR("Flush of XPI database failed", error); + return 0; + }) + .then(count => { + // If our last attempt to read or write the DB failed, force a new + // extensions.ini to be written to disk on the next startup + let lastSaveFailed = this.lastError; + if (lastSaveFailed) + Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true); - connection.asyncClose(function shutdown_asyncClose() { - LOG("Database closed"); - aCallback(); - }); + // Clear out the cached addons data loaded from JSON + delete this.addonDB; + delete this._dbPromise; + // same for the deferred save + delete this._deferredSave; + // re-enable the schema version setter + delete this._schemaVersionSet; + + if (aCallback) + aCallback(lastSaveFailed); + }); } else { if (aCallback) - aCallback(); - } - }, - - /** - * Gets a cached statement or creates a new statement if it doesn't already - * exist. - * - * @param key - * A unique key to reference the statement - * @param aSql - * An optional SQL string to use for the query, otherwise a - * predefined sql string for the key will be used. - * @return a mozIStorageStatement for the passed SQL - */ - getStatement: function XPIDB_getStatement(aKey, aSql) { - if (aKey in this.statementCache) - return this.statementCache[aKey]; - if (!aSql) - aSql = this.statements[aKey]; - - try { - return this.statementCache[aKey] = this.connection.createStatement(aSql); - } - catch (e) { - ERROR("Error creating statement " + aKey + " (" + aSql + ")"); - throw e; - } - }, - - /** - * Creates the schema in the database. - */ - createSchema: function XPIDB_createSchema() { - LOG("Creating database schema"); - this.beginTransaction(); - - // Any errors in here should rollback the transaction - try { - this.connection.createTable("addon", - "internal_id INTEGER PRIMARY KEY AUTOINCREMENT, " + - "id TEXT, syncGUID TEXT, " + - "location TEXT, version TEXT, " + - "type TEXT, internalName TEXT, updateURL TEXT, " + - "updateKey TEXT, optionsURL TEXT, " + - "optionsType TEXT, aboutURL TEXT, iconURL TEXT, " + - "icon64URL TEXT, defaultLocale INTEGER, " + - "visible INTEGER, active INTEGER, " + - "userDisabled INTEGER, appDisabled INTEGER, " + - "pendingUninstall INTEGER, descriptor TEXT, " + - "installDate INTEGER, updateDate INTEGER, " + - "applyBackgroundUpdates INTEGER, " + - "bootstrap INTEGER, skinnable INTEGER, " + - "size INTEGER, sourceURI TEXT, " + - "releaseNotesURI TEXT, softDisabled INTEGER, " + - "isForeignInstall INTEGER, " + - "hasBinaryComponents INTEGER, " + - "strictCompatibility INTEGER, " + - "UNIQUE (id, location), " + - "UNIQUE (syncGUID)"); - this.connection.createTable("targetApplication", - "addon_internal_id INTEGER, " + - "id TEXT, minVersion TEXT, maxVersion TEXT, " + - "UNIQUE (addon_internal_id, id)"); - this.connection.createTable("targetPlatform", - "addon_internal_id INTEGER, " + - "os, abi TEXT, " + - "UNIQUE (addon_internal_id, os, abi)"); - this.connection.createTable("addon_locale", - "addon_internal_id INTEGER, "+ - "locale TEXT, locale_id INTEGER, " + - "UNIQUE (addon_internal_id, locale)"); - this.connection.createTable("locale", - "id INTEGER PRIMARY KEY AUTOINCREMENT, " + - "name TEXT, description TEXT, creator TEXT, " + - "homepageURL TEXT"); - this.connection.createTable("locale_strings", - "locale_id INTEGER, type TEXT, value TEXT"); - this.connection.executeSimpleSQL("CREATE INDEX locale_strings_idx ON " + - "locale_strings (locale_id)"); - this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon AFTER DELETE " + - "ON addon BEGIN " + - "DELETE FROM targetApplication WHERE addon_internal_id=old.internal_id; " + - "DELETE FROM targetPlatform WHERE addon_internal_id=old.internal_id; " + - "DELETE FROM addon_locale WHERE addon_internal_id=old.internal_id; " + - "DELETE FROM locale WHERE id=old.defaultLocale; " + - "END"); - this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon_locale AFTER " + - "DELETE ON addon_locale WHEN NOT EXISTS " + - "(SELECT * FROM addon_locale WHERE locale_id=old.locale_id) BEGIN " + - "DELETE FROM locale WHERE id=old.locale_id; " + - "END"); - this.connection.executeSimpleSQL("CREATE TRIGGER delete_locale AFTER " + - "DELETE ON locale BEGIN " + - "DELETE FROM locale_strings WHERE locale_id=old.id; " + - "END"); - this.connection.schemaVersion = DB_SCHEMA; - this.commitTransaction(); - } - catch (e) { - ERROR("Failed to create database schema", e); - logSQLError(this.connection.lastError, this.connection.lastErrorString); - this.rollbackTransaction(); - this.connection.close(); - this.connection = null; - throw e; - } - }, - - /** - * Synchronously reads the multi-value locale strings for a locale - * - * @param aLocale - * The locale object to read into - */ - _readLocaleStrings: function XPIDB__readLocaleStrings(aLocale) { - let stmt = this.getStatement("_readLocaleStrings"); - - stmt.params.id = aLocale.id; - for (let row in resultRows(stmt)) { - if (!(row.type in aLocale)) - aLocale[row.type] = []; - aLocale[row.type].push(row.value); + aCallback(null); } }, /** - * Synchronously reads the locales for an add-on + * Return a list of all install locations known about by the database. This + * is often a a subset of the total install locations when not all have + * installed add-ons, occasionally a superset when an install location no + * longer exists. Only called from XPIProvider.processFileChanges, when + * the database should already be loaded. * - * @param aAddon - * The DBAddonInternal to read the locales for - * @return the array of locales - */ - _getLocales: function XPIDB__getLocales(aAddon) { - let stmt = this.getStatement("_getLocales"); - - let locales = []; - stmt.params.internal_id = aAddon._internal_id; - for (let row in resultRows(stmt)) { - let locale = { - id: row.id, - locales: [row.locale] - }; - copyProperties(row, PROP_LOCALE_SINGLE, locale); - locales.push(locale); - } - locales.forEach(function(aLocale) { - this._readLocaleStrings(aLocale); - }, this); - return locales; - }, - - /** - * Synchronously reads the default locale for an add-on - * - * @param aAddon - * The DBAddonInternal to read the default locale for - * @return the default locale for the add-on - * @throws if the database does not contain the default locale information - */ - _getDefaultLocale: function XPIDB__getDefaultLocale(aAddon) { - let stmt = this.getStatement("_getDefaultLocale"); - - stmt.params.id = aAddon._defaultLocale; - if (!stepStatement(stmt)) - throw new Error("Missing default locale for " + aAddon.id); - let locale = copyProperties(stmt.row, PROP_LOCALE_SINGLE); - locale.id = aAddon._defaultLocale; - stmt.reset(); - this._readLocaleStrings(locale); - return locale; - }, - - /** - * Synchronously reads the target application entries for an add-on - * - * @param aAddon - * The DBAddonInternal to read the target applications for - * @return an array of target applications + * @return a Set of names of install locations */ - _getTargetApplications: function XPIDB__getTargetApplications(aAddon) { - let stmt = this.getStatement("_getTargetApplications"); - - stmt.params.internal_id = aAddon._internal_id; - return [copyProperties(row, PROP_TARGETAPP) for each (row in resultRows(stmt))]; - }, - - /** - * Synchronously reads the target platform entries for an add-on - * - * @param aAddon - * The DBAddonInternal to read the target platforms for - * @return an array of target platforms - */ - _getTargetPlatforms: function XPIDB__getTargetPlatforms(aAddon) { - let stmt = this.getStatement("_getTargetPlatforms"); - - stmt.params.internal_id = aAddon._internal_id; - return [copyProperties(row, ["os", "abi"]) for each (row in resultRows(stmt))]; - }, + getInstallLocations: function XPIDB_getInstallLocations() { + let locations = new Set(); + if (!this.addonDB) + return locations; - /** - * Synchronously makes a DBAddonInternal from a storage row or returns one - * from the cache. - * - * @param aRow - * The storage row to make the DBAddonInternal from - * @return a DBAddonInternal - */ - makeAddonFromRow: function XPIDB_makeAddonFromRow(aRow) { - if (this.addonCache[aRow.internal_id]) { - let addon = this.addonCache[aRow.internal_id].get(); - if (addon) - return addon; + for (let [, addon] of this.addonDB) { + locations.add(addon.location); } - - let addon = new XPIProvider.DBAddonInternal(); - addon._internal_id = aRow.internal_id; - addon._installLocation = XPIProvider.installLocationsByName[aRow.location]; - addon._descriptor = aRow.descriptor; - addon._defaultLocale = aRow.defaultLocale; - copyProperties(aRow, PROP_METADATA, addon); - copyProperties(aRow, DB_METADATA, addon); - DB_BOOL_METADATA.forEach(function(aProp) { - addon[aProp] = aRow[aProp] != 0; - }); - try { - addon._sourceBundle = addon._installLocation.getLocationForID(addon.id); - } - catch (e) { - // An exception will be thrown if the add-on appears in the database but - // not on disk. In general this should only happen during startup as - // this change is being detected. - } - - this.addonCache[aRow.internal_id] = Components.utils.getWeakReference(addon); - return addon; + return locations; }, /** - * Asynchronously fetches additional metadata for a DBAddonInternal. - * - * @param aAddon - * The DBAddonInternal + * Asynchronously list all addons that match the filter function + * @param aFilter + * Function that takes an addon instance and returns + * true if that addon should be included in the selected array * @param aCallback - * The callback to call when the metadata is completely retrieved + * Called back with an array of addons matching aFilter + * or an empty array if none match */ - fetchAddonMetadata: function XPIDB_fetchAddonMetadata(aAddon) { - function readLocaleStrings(aLocale, aCallback) { - let stmt = XPIDatabase.getStatement("_readLocaleStrings"); - - stmt.params.id = aLocale.id; - stmt.executeAsync({ - handleResult: function readLocaleStrings_handleResult(aResults) { - let row = null; - while ((row = aResults.getNextRow())) { - let type = row.getResultByName("type"); - if (!(type in aLocale)) - aLocale[type] = []; - aLocale[type].push(row.getResultByName("value")); - } - }, - - handleError: asyncErrorLogger, - - handleCompletion: function readLocaleStrings_handleCompletion(aReason) { - aCallback(); - } - }); - } - - function readDefaultLocale() { - delete aAddon.defaultLocale; - let stmt = XPIDatabase.getStatement("_getDefaultLocale"); - - stmt.params.id = aAddon._defaultLocale; - stmt.executeAsync({ - handleResult: function readDefaultLocale_handleResult(aResults) { - aAddon.defaultLocale = copyRowProperties(aResults.getNextRow(), - PROP_LOCALE_SINGLE); - aAddon.defaultLocale.id = aAddon._defaultLocale; - }, - - handleError: asyncErrorLogger, - - handleCompletion: function readDefaultLocale_handleCompletion(aReason) { - if (aAddon.defaultLocale) { - readLocaleStrings(aAddon.defaultLocale, readLocales); - } - else { - ERROR("Missing default locale for " + aAddon.id); - readLocales(); - } - } - }); - } - - function readLocales() { - delete aAddon.locales; - aAddon.locales = []; - let stmt = XPIDatabase.getStatement("_getLocales"); - - stmt.params.internal_id = aAddon._internal_id; - stmt.executeAsync({ - handleResult: function readLocales_handleResult(aResults) { - let row = null; - while ((row = aResults.getNextRow())) { - let locale = { - id: row.getResultByName("id"), - locales: [row.getResultByName("locale")] - }; - copyRowProperties(row, PROP_LOCALE_SINGLE, locale); - aAddon.locales.push(locale); - } - }, - - handleError: asyncErrorLogger, - - handleCompletion: function readLocales_handleCompletion(aReason) { - let pos = 0; - function readNextLocale() { - if (pos < aAddon.locales.length) - readLocaleStrings(aAddon.locales[pos++], readNextLocale); - else - readTargetApplications(); - } - - readNextLocale(); - } - }); - } - - function readTargetApplications() { - delete aAddon.targetApplications; - aAddon.targetApplications = []; - let stmt = XPIDatabase.getStatement("_getTargetApplications"); - - stmt.params.internal_id = aAddon._internal_id; - stmt.executeAsync({ - handleResult: function readTargetApplications_handleResult(aResults) { - let row = null; - while ((row = aResults.getNextRow())) - aAddon.targetApplications.push(copyRowProperties(row, PROP_TARGETAPP)); - }, - - handleError: asyncErrorLogger, - - handleCompletion: function readTargetApplications_handleCompletion(aReason) { - readTargetPlatforms(); - } - }); - } - - function readTargetPlatforms() { - delete aAddon.targetPlatforms; - aAddon.targetPlatforms = []; - let stmt = XPIDatabase.getStatement("_getTargetPlatforms"); - - stmt.params.internal_id = aAddon._internal_id; - stmt.executeAsync({ - handleResult: function readTargetPlatforms_handleResult(aResults) { - let row = null; - while ((row = aResults.getNextRow())) - aAddon.targetPlatforms.push(copyRowProperties(row, ["os", "abi"])); - }, - - handleError: asyncErrorLogger, - - handleCompletion: function readTargetPlatforms_handleCompletion(aReason) { - let callbacks = aAddon._pendingCallbacks; - delete aAddon._pendingCallbacks; - callbacks.forEach(function(aCallback) { - aCallback(aAddon); - }); - } - }); - } - - readDefaultLocale(); + getAddonList: function(aFilter, aCallback) { + this.asyncLoadDB().then( + addonDB => { + let addonList = _filterDB(addonDB, aFilter); + asyncMap(addonList, getRepositoryAddon, safeCallback(aCallback)); + }) + .then(null, + error => { + ERROR("getAddonList failed", e); + safeCallback(aCallback)([]); + }); }, /** - * Synchronously makes a DBAddonInternal from a mozIStorageRow or returns one - * from the cache. - * - * @param aRow - * The mozIStorageRow to make the DBAddonInternal from - * @return a DBAddonInternal + * (Possibly asynchronously) get the first addon that matches the filter function + * @param aFilter + * Function that takes an addon instance and returns + * true if that addon should be selected + * @param aCallback + * Called back with the addon, or null if no matching addon is found */ - makeAddonFromRowAsync: function XPIDB_makeAddonFromRowAsync(aRow, aCallback) { - let internal_id = aRow.getResultByName("internal_id"); - if (this.addonCache[internal_id]) { - let addon = this.addonCache[internal_id].get(); - if (addon) { - // If metadata is still pending for this instance add our callback to - // the list to be called when complete, otherwise pass the addon to - // our callback - if ("_pendingCallbacks" in addon) - addon._pendingCallbacks.push(aCallback); - else - aCallback(addon); - return; - } - } - - let addon = new XPIProvider.DBAddonInternal(); - addon._internal_id = internal_id; - let location = aRow.getResultByName("location"); - addon._installLocation = XPIProvider.installLocationsByName[location]; - addon._descriptor = aRow.getResultByName("descriptor"); - copyRowProperties(aRow, PROP_METADATA, addon); - addon._defaultLocale = aRow.getResultByName("defaultLocale"); - copyRowProperties(aRow, DB_METADATA, addon); - DB_BOOL_METADATA.forEach(function(aProp) { - addon[aProp] = aRow.getResultByName(aProp) != 0; - }); - try { - addon._sourceBundle = addon._installLocation.getLocationForID(addon.id); - } - catch (e) { - // An exception will be thrown if the add-on appears in the database but - // not on disk. In general this should only happen during startup as - // this change is being detected. - } - - this.addonCache[internal_id] = Components.utils.getWeakReference(addon); - addon._pendingCallbacks = [aCallback]; - this.fetchAddonMetadata(addon); - }, - - /** - * Synchronously reads all install locations known about by the database. This - * is often a a subset of the total install locations when not all have - * installed add-ons, occasionally a superset when an install location no - * longer exists. - * - * @return an array of names of install locations - */ - getInstallLocations: function XPIDB_getInstallLocations() { - if (!this.connection) - return []; - - let stmt = this.getStatement("getInstallLocations"); - - return [row.location for each (row in resultRows(stmt))]; + getAddon: function(aFilter, aCallback) { + return this.asyncLoadDB().then( + addonDB => { + getRepositoryAddon(_findAddon(addonDB, aFilter), safeCallback(aCallback)); + }) + .then(null, + error => { + ERROR("getAddon failed", e); + safeCallback(aCallback)(null); + }); }, /** * Synchronously reads all the add-ons in a particular install location. + * Always called with the addon database already loaded. * - * @param location + * @param aLocation * The name of the install location * @return an array of DBAddonInternals */ getAddonsInLocation: function XPIDB_getAddonsInLocation(aLocation) { - if (!this.connection) - return []; - - let stmt = this.getStatement("getAddonsInLocation"); - - stmt.params.location = aLocation; - return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))]; + return _filterDB(this.addonDB, aAddon => (aAddon.location == aLocation)); }, /** * Asynchronously gets an add-on with a particular ID in a particular * install location. * * @param aId * The ID of the add-on to retrieve * @param aLocation * The name of the install location * @param aCallback * A callback to pass the DBAddonInternal to */ getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) { - if (!this.connection) { - aCallback(null); - return; - } - - let stmt = this.getStatement("getAddonInLocation"); - - stmt.params.id = aId; - stmt.params.location = aLocation; - stmt.executeAsync(new AsyncAddonListCallback(function getAddonInLocation_executeAsync(aAddons) { - if (aAddons.length == 0) { - aCallback(null); - return; - } - // This should never happen but indicates invalid data in the database if - // it does - if (aAddons.length > 1) - ERROR("Multiple addons with ID " + aId + " found in location " + aLocation); - aCallback(aAddons[0]); - })); + this.asyncLoadDB().then( + addonDB => getRepositoryAddon(addonDB.get(aLocation + ":" + aId), + safeCallback(aCallback))); }, /** - * Asynchronously gets the add-on with an ID that is visible. + * Asynchronously gets the add-on with the specified ID that is visible. * * @param aId * The ID of the add-on to retrieve * @param aCallback * A callback to pass the DBAddonInternal to */ getVisibleAddonForID: function XPIDB_getVisibleAddonForID(aId, aCallback) { - if (!this.connection) { - aCallback(null); - return; - } - - let stmt = this.getStatement("getVisibleAddonForID"); - - stmt.params.id = aId; - stmt.executeAsync(new AsyncAddonListCallback(function getVisibleAddonForID_executeAsync(aAddons) { - if (aAddons.length == 0) { - aCallback(null); - return; - } - // This should never happen but indicates invalid data in the database if - // it does - if (aAddons.length > 1) - ERROR("Multiple visible addons with ID " + aId + " found"); - aCallback(aAddons[0]); - })); + this.getAddon(aAddon => ((aAddon.id == aId) && aAddon.visible), + aCallback); }, /** * Asynchronously gets the visible add-ons, optionally restricting by type. * * @param aTypes * An array of types to include or null to include all types * @param aCallback * A callback to pass the array of DBAddonInternals to */ getVisibleAddons: function XPIDB_getVisibleAddons(aTypes, aCallback) { - if (!this.connection) { - aCallback([]); - return; - } - - let stmt = null; - if (!aTypes || aTypes.length == 0) { - stmt = this.getStatement("getVisibleAddons"); - } - else { - let sql = "SELECT " + FIELDS_ADDON + " FROM addon WHERE visible=1 AND " + - "type IN ("; - for (let i = 1; i <= aTypes.length; i++) { - sql += "?" + i; - if (i < aTypes.length) - sql += ","; - } - sql += ")"; - - // Note that binding to index 0 sets the value for the ?1 parameter - stmt = this.getStatement("getVisibleAddons_" + aTypes.length, sql); - for (let i = 0; i < aTypes.length; i++) - stmt.bindByIndex(i, aTypes[i]); - } - - stmt.executeAsync(new AsyncAddonListCallback(aCallback)); + this.getAddonList(aAddon => (aAddon.visible && + (!aTypes || (aTypes.length == 0) || + (aTypes.indexOf(aAddon.type) > -1))), + aCallback); }, /** * Synchronously gets all add-ons of a particular type. * * @param aType * The type of add-on to retrieve * @return an array of DBAddonInternals */ getAddonsByType: function XPIDB_getAddonsByType(aType) { - if (!this.connection) - return []; - - let stmt = this.getStatement("getAddonsByType"); - - stmt.params.type = aType; - return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))]; + if (!this.addonDB) { + // jank-tastic! Must synchronously load DB if the theme switches from + // an XPI theme to a lightweight theme before the DB has loaded, + // because we're called from sync XPIProvider.addonChanged + WARN("Synchronous load of XPI database due to getAddonsByType(" + aType + ")"); + this.syncLoadDB(true); + } + return _filterDB(this.addonDB, aAddon => (aAddon.type == aType)); }, /** * Synchronously gets an add-on with a particular internalName. * * @param aInternalName * The internalName of the add-on to retrieve * @return a DBAddonInternal */ getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(aInternalName) { - if (!this.connection) - return null; - - let stmt = this.getStatement("getVisibleAddonForInternalName"); - - let addon = null; - stmt.params.internalName = aInternalName; - - if (stepStatement(stmt)) - addon = this.makeAddonFromRow(stmt.row); - - stmt.reset(); - return addon; + if (!this.addonDB) { + // This may be called when the DB hasn't otherwise been loaded + // XXX TELEMETRY + WARN("Synchronous load of XPI database due to getVisibleAddonForInternalName"); + this.syncLoadDB(true); + } + + return _findAddon(this.addonDB, + aAddon => aAddon.visible && + (aAddon.internalName == aInternalName)); }, /** * Asynchronously gets all add-ons with pending operations. * * @param aTypes * The types of add-ons to retrieve or null to get all types * @param aCallback * A callback to pass the array of DBAddonInternal to */ getVisibleAddonsWithPendingOperations: function XPIDB_getVisibleAddonsWithPendingOperations(aTypes, aCallback) { - if (!this.connection) { - aCallback([]); - return; - } - let stmt = null; - if (!aTypes || aTypes.length == 0) { - stmt = this.getStatement("getVisibleAddonsWithPendingOperations"); - } - else { - let sql = "SELECT * FROM addon WHERE visible=1 AND " + - "(pendingUninstall=1 OR MAX(userDisabled,appDisabled)=active) " + - "AND type IN ("; - for (let i = 1; i <= aTypes.length; i++) { - sql += "?" + i; - if (i < aTypes.length) - sql += ","; - } - sql += ")"; - - // Note that binding to index 0 sets the value for the ?1 parameter - stmt = this.getStatement("getVisibleAddonsWithPendingOperations_" + - aTypes.length, sql); - for (let i = 0; i < aTypes.length; i++) - stmt.bindByIndex(i, aTypes[i]); - } - - stmt.executeAsync(new AsyncAddonListCallback(aCallback)); + this.getAddonList( + aAddon => (aAddon.visible && + (aAddon.pendingUninstall || + // Logic here is tricky. If we're active but either + // disabled flag is set, we're pending disable; if we're not + // active and neither disabled flag is set, we're pending enable + (aAddon.active == (aAddon.userDisabled || aAddon.appDisabled))) && + (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))), + aCallback); }, /** * Asynchronously get an add-on by its Sync GUID. * * @param aGUID * Sync GUID of add-on to fetch * @param aCallback * A callback to pass the DBAddonInternal record to. Receives null * if no add-on with that GUID is found. * */ getAddonBySyncGUID: function XPIDB_getAddonBySyncGUID(aGUID, aCallback) { - let stmt = this.getStatement("getAddonBySyncGUID"); - stmt.params.syncGUID = aGUID; - - stmt.executeAsync(new AsyncAddonListCallback(function getAddonBySyncGUID_executeAsync(aAddons) { - if (aAddons.length == 0) { - aCallback(null); - return; - } - aCallback(aAddons[0]); - })); + this.getAddon(aAddon => aAddon.syncGUID == aGUID, + aCallback); }, /** * Synchronously gets all add-ons in the database. + * This is only called from the preference observer for the default + * compatibility version preference, so we can return an empty list if + * we haven't loaded the database yet. * * @return an array of DBAddonInternals */ getAddons: function XPIDB_getAddons() { - if (!this.connection) + if (!this.addonDB) { return []; - - let stmt = this.getStatement("getAddons"); - - return [this.makeAddonFromRow(row) for each (row in resultRows(stmt))]; + } + return _filterDB(this.addonDB, aAddon => true); }, /** * Synchronously adds an AddonInternal's metadata to the database. * * @param aAddon * AddonInternal to add * @param aDescriptor * The file descriptor of the add-on + * @return The DBAddonInternal that was added to the database */ addAddonMetadata: function XPIDB_addAddonMetadata(aAddon, aDescriptor) { - // If there is no DB yet then forcibly create one - if (!this.connection) - this.openConnection(false, true); - - this.beginTransaction(); - - var self = this; - function insertLocale(aLocale) { - let localestmt = self.getStatement("addAddonMetadata_locale"); - let stringstmt = self.getStatement("addAddonMetadata_strings"); - - copyProperties(aLocale, PROP_LOCALE_SINGLE, localestmt.params); - executeStatement(localestmt); - let row = XPIDatabase.connection.lastInsertRowID; - - PROP_LOCALE_MULTI.forEach(function(aProp) { - aLocale[aProp].forEach(function(aStr) { - stringstmt.params.locale = row; - stringstmt.params.type = aProp; - stringstmt.params.value = aStr; - executeStatement(stringstmt); - }); - }); - return row; + if (!this.addonDB) { + // XXX telemetry. Should never happen on platforms that have a default theme + this.syncLoadDB(false); } - // Any errors in here should rollback the transaction - try { - - if (aAddon.visible) { - let stmt = this.getStatement("clearVisibleAddons"); - stmt.params.id = aAddon.id; - executeStatement(stmt); - } - - let stmt = this.getStatement("addAddonMetadata_addon"); - - stmt.params.locale = insertLocale(aAddon.defaultLocale); - stmt.params.location = aAddon._installLocation.name; - stmt.params.descriptor = aDescriptor; - copyProperties(aAddon, PROP_METADATA, stmt.params); - copyProperties(aAddon, DB_METADATA, stmt.params); - DB_BOOL_METADATA.forEach(function(aProp) { - stmt.params[aProp] = aAddon[aProp] ? 1 : 0; - }); - executeStatement(stmt); - let internal_id = this.connection.lastInsertRowID; + let newAddon = new DBAddonInternal(aAddon); + newAddon.descriptor = aDescriptor; + this.addonDB.set(newAddon._key, newAddon); + if (newAddon.visible) { + this.makeAddonVisible(newAddon); + } - stmt = this.getStatement("addAddonMetadata_addon_locale"); - aAddon.locales.forEach(function(aLocale) { - let id = insertLocale(aLocale); - aLocale.locales.forEach(function(aName) { - stmt.params.internal_id = internal_id; - stmt.params.name = aName; - stmt.params.locale = id; - executeStatement(stmt); - }); - }); - - stmt = this.getStatement("addAddonMetadata_targetApplication"); - - aAddon.targetApplications.forEach(function(aApp) { - stmt.params.internal_id = internal_id; - stmt.params.id = aApp.id; - stmt.params.minVersion = aApp.minVersion; - stmt.params.maxVersion = aApp.maxVersion; - executeStatement(stmt); - }); - - stmt = this.getStatement("addAddonMetadata_targetPlatform"); - - aAddon.targetPlatforms.forEach(function(aPlatform) { - stmt.params.internal_id = internal_id; - stmt.params.os = aPlatform.os; - stmt.params.abi = aPlatform.abi; - executeStatement(stmt); - }); - - this.commitTransaction(); - } - catch (e) { - this.rollbackTransaction(); - throw e; - } + this.saveChanges(); + return newAddon; }, /** - * Synchronously updates an add-ons metadata in the database. Currently just + * Synchronously updates an add-on's metadata in the database. Currently just * removes and recreates. * * @param aOldAddon * The DBAddonInternal to be replaced * @param aNewAddon * The new AddonInternal to add * @param aDescriptor * The file descriptor of the add-on + * @return The DBAddonInternal that was added to the database */ updateAddonMetadata: function XPIDB_updateAddonMetadata(aOldAddon, aNewAddon, aDescriptor) { - this.beginTransaction(); - - // Any errors in here should rollback the transaction - try { - this.removeAddonMetadata(aOldAddon); - aNewAddon.syncGUID = aOldAddon.syncGUID; - aNewAddon.installDate = aOldAddon.installDate; - aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates; - aNewAddon.foreignInstall = aOldAddon.foreignInstall; - aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled && - !aNewAddon.appDisabled && !aNewAddon.pendingUninstall) - - this.addAddonMetadata(aNewAddon, aDescriptor); - this.commitTransaction(); - } - catch (e) { - this.rollbackTransaction(); - throw e; - } - }, + this.removeAddonMetadata(aOldAddon); + aNewAddon.syncGUID = aOldAddon.syncGUID; + aNewAddon.installDate = aOldAddon.installDate; + aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates; + aNewAddon.foreignInstall = aOldAddon.foreignInstall; + aNewAddon.active = (aNewAddon.visible && !aNewAddon.userDisabled && + !aNewAddon.appDisabled && !aNewAddon.pendingUninstall); - /** - * Synchronously updates the target application entries for an add-on. - * - * @param aAddon - * The DBAddonInternal being updated - * @param aTargets - * The array of target applications to update - */ - updateTargetApplications: function XPIDB_updateTargetApplications(aAddon, - aTargets) { - this.beginTransaction(); - - // Any errors in here should rollback the transaction - try { - let stmt = this.getStatement("updateTargetApplications"); - aTargets.forEach(function(aTarget) { - stmt.params.internal_id = aAddon._internal_id; - stmt.params.id = aTarget.id; - stmt.params.minVersion = aTarget.minVersion; - stmt.params.maxVersion = aTarget.maxVersion; - executeStatement(stmt); - }); - this.commitTransaction(); - } - catch (e) { - this.rollbackTransaction(); - throw e; - } + // addAddonMetadata does a saveChanges() + return this.addAddonMetadata(aNewAddon, aDescriptor); }, /** * Synchronously removes an add-on from the database. * * @param aAddon * The DBAddonInternal being removed */ removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) { - let stmt = this.getStatement("removeAddonMetadata"); - stmt.params.internal_id = aAddon._internal_id; - executeStatement(stmt); + this.addonDB.delete(aAddon._key); + this.saveChanges(); }, /** * Synchronously marks a DBAddonInternal as visible marking all other * instances with the same ID as not visible. * * @param aAddon * The DBAddonInternal to make visible * @param callback * A callback to pass the DBAddonInternal to */ makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) { - let stmt = this.getStatement("clearVisibleAddons"); - stmt.params.id = aAddon.id; - executeStatement(stmt); - - stmt = this.getStatement("makeAddonVisible"); - stmt.params.internal_id = aAddon._internal_id; - executeStatement(stmt); - + LOG("Make addon " + aAddon._key + " visible"); + for (let [, otherAddon] of this.addonDB) { + if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) { + LOG("Hide addon " + otherAddon._key); + otherAddon.visible = false; + } + } aAddon.visible = true; + this.saveChanges(); }, /** * Synchronously sets properties for an add-on. * * @param aAddon * The DBAddonInternal being updated * @param aProperties * A dictionary of properties to set */ setAddonProperties: function XPIDB_setAddonProperties(aAddon, aProperties) { - function convertBoolean(value) { - return value ? 1 : 0; + for (let key in aProperties) { + aAddon[key] = aProperties[key]; } - - let stmt = this.getStatement("setAddonProperties"); - stmt.params.internal_id = aAddon._internal_id; - - ["userDisabled", "appDisabled", "softDisabled", - "pendingUninstall"].forEach(function(aProp) { - if (aProp in aProperties) { - stmt.params[aProp] = convertBoolean(aProperties[aProp]); - aAddon[aProp] = aProperties[aProp]; - } - else { - stmt.params[aProp] = convertBoolean(aAddon[aProp]); - } - }); - - if ("applyBackgroundUpdates" in aProperties) { - stmt.params.applyBackgroundUpdates = aProperties.applyBackgroundUpdates; - aAddon.applyBackgroundUpdates = aProperties.applyBackgroundUpdates; - } - else { - stmt.params.applyBackgroundUpdates = aAddon.applyBackgroundUpdates; - } - - executeStatement(stmt); + this.saveChanges(); }, /** * Synchronously sets the Sync GUID for an add-on. + * Only called when the database is already loaded. * * @param aAddon * The DBAddonInternal being updated * @param aGUID * GUID string to set the value to + * @throws if another addon already has the specified GUID */ setAddonSyncGUID: function XPIDB_setAddonSyncGUID(aAddon, aGUID) { - let stmt = this.getStatement("setAddonSyncGUID"); - stmt.params.internal_id = aAddon._internal_id; - stmt.params.syncGUID = aGUID; - - executeStatement(stmt); - }, - - /** - * Synchronously sets the file descriptor for an add-on. - * - * @param aAddon - * The DBAddonInternal being updated - * @param aProperties - * A dictionary of properties to set - */ - setAddonDescriptor: function XPIDB_setAddonDescriptor(aAddon, aDescriptor) { - let stmt = this.getStatement("setAddonDescriptor"); - stmt.params.internal_id = aAddon._internal_id; - stmt.params.descriptor = aDescriptor; - - executeStatement(stmt); + // Need to make sure no other addon has this GUID + function excludeSyncGUID(otherAddon) { + return (otherAddon._key != aAddon._key) && (otherAddon.syncGUID == aGUID); + } + let otherAddon = _findAddon(this.addonDB, excludeSyncGUID); + if (otherAddon) { + throw new Error("Addon sync GUID conflict for addon " + aAddon._key + + ": " + otherAddon._key + " already has GUID " + aGUID); + } + aAddon.syncGUID = aGUID; + this.saveChanges(); }, /** * Synchronously updates an add-on's active flag in the database. * * @param aAddon * The DBAddonInternal to update */ - updateAddonActive: function XPIDB_updateAddonActive(aAddon) { - LOG("Updating add-on state"); + updateAddonActive: function XPIDB_updateAddonActive(aAddon, aActive) { + LOG("Updating active state for add-on " + aAddon.id + " to " + aActive); - let stmt = this.getStatement("updateAddonActive"); - stmt.params.internal_id = aAddon._internal_id; - stmt.params.active = aAddon.active ? 1 : 0; - executeStatement(stmt); + aAddon.active = aActive; + this.saveChanges(); }, /** * Synchronously calculates and updates all the active flags in the database. */ updateActiveAddons: function XPIDB_updateActiveAddons() { LOG("Updating add-on states"); - let stmt = this.getStatement("setActiveAddons"); - executeStatement(stmt); - - // Note that this does not update the active property on cached - // DBAddonInternal instances so we throw away the cache. This should only - // happen during shutdown when everything is going away anyway or during - // startup when the only references are internal. - this.addonCache = []; + for (let [, addon] of this.addonDB) { + let newActive = (addon.visible && !addon.userDisabled && + !addon.softDisabled && !addon.appDisabled && + !addon.pendingUninstall); + if (newActive != addon.active) { + addon.active = newActive; + this.saveChanges(); + } + } }, /** * Writes out the XPI add-ons list for the platform to read. */ writeAddonsList: function XPIDB_writeAddonsList() { + if (!this.addonDB) { + // Unusual condition, force the DB to load + this.syncLoadDB(true); + } Services.appinfo.invalidateCachesOnRestart(); let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST], true); - if (!this.connection) { - try { - addonsList.remove(false); - LOG("Deleted add-ons list"); - } - catch (e) { - } - - Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS); - return; - } - let enabledAddons = []; let text = "[ExtensionDirs]\r\n"; let count = 0; let fullCount = 0; - let stmt = this.getStatement("getActiveAddons"); + let activeAddons = _filterDB( + this.addonDB, + aAddon => aAddon.active && !aAddon.bootstrap && (aAddon.type != "theme")); - for (let row in resultRows(stmt)) { + for (let row of activeAddons) { text += "Extension" + (count++) + "=" + row.descriptor + "\r\n"; enabledAddons.push(encodeURIComponent(row.id) + ":" + encodeURIComponent(row.version)); } fullCount += count; // The selected skin may come from an inactive theme (the default theme // when a lightweight theme is applied for example) text += "\r\n[ThemeDirs]\r\n"; let dssEnabled = false; try { dssEnabled = Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED); } catch (e) {} + let themes = []; if (dssEnabled) { - stmt = this.getStatement("getThemes"); + themes = _filterDB(this.addonDB, aAddon => aAddon.type == "theme"); } else { - stmt = this.getStatement("getActiveTheme"); - stmt.params.internalName = XPIProvider.selectedSkin; + let activeTheme = _findAddon( + this.addonDB, + aAddon => (aAddon.type == "theme") && + (aAddon.internalName == XPIProvider.selectedSkin)); + if (activeTheme) { + themes.push(activeTheme); + } } - if (stmt) { + if (themes.length > 0) { count = 0; - for (let row in resultRows(stmt)) { + for (let row of themes) { text += "Extension" + (count++) + "=" + row.descriptor + "\r\n"; enabledAddons.push(encodeURIComponent(row.id) + ":" + encodeURIComponent(row.version)); } fullCount += count; } if (fullCount > 0) {
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -339,16 +339,20 @@ function do_check_compatibilityoverride( } function do_check_icons(aActual, aExpected) { for (var size in aExpected) { do_check_eq(remove_port(aActual[size]), remove_port(aExpected[size])); } } +// Record the error (if any) from trying to save the XPI +// database at shutdown time +let gXPISaveError = null; + /** * Starts up the add-on manager as if it was started by the application. * * @param aAppChanged * An optional boolean parameter to simulate the case where the * application has changed version since the last run. If not passed it * defaults to true */ @@ -391,56 +395,51 @@ function restartManager(aNewVersion) { startupManager(false); } } function shutdownManager() { if (!gInternalManager) return; - let obs = AM_Cc["@mozilla.org/observer-service;1"]. - getService(AM_Ci.nsIObserverService); - let xpiShutdown = false; - obs.addObserver({ + Services.obs.addObserver({ observe: function(aSubject, aTopic, aData) { xpiShutdown = true; - obs.removeObserver(this, "xpi-provider-shutdown"); + gXPISaveError = aData; + Services.obs.removeObserver(this, "xpi-provider-shutdown"); } }, "xpi-provider-shutdown", false); let repositoryShutdown = false; - obs.addObserver({ + Services.obs.addObserver({ observe: function(aSubject, aTopic, aData) { repositoryShutdown = true; - obs.removeObserver(this, "addon-repository-shutdown"); + Services.obs.removeObserver(this, "addon-repository-shutdown"); } }, "addon-repository-shutdown", false); - obs.notifyObservers(null, "quit-application-granted", null); + Services.obs.notifyObservers(null, "quit-application-granted", null); let scope = Components.utils.import("resource://gre/modules/AddonManager.jsm"); scope.AddonManagerInternal.shutdown(); gInternalManager = null; AddonRepository.shutdown(); // Load the add-ons list as it was after application shutdown loadAddonsList(); // Clear any crash report annotations gAppInfo.annotations = {}; - let thr = AM_Cc["@mozilla.org/thread-manager;1"]. - getService(AM_Ci.nsIThreadManager). - mainThread; + let thr = Services.tm.mainThread; // Wait until we observe the shutdown notifications while (!repositoryShutdown || !xpiShutdown) { - if (thr.hasPendingEvents()) - thr.processNextEvent(false); + thr.processNextEvent(true); } // Force the XPIProvider provider to reload to better // simulate real-world usage. scope = Components.utils.import("resource://gre/modules/XPIProvider.jsm"); AddonManagerPrivate.unregisterProvider(scope.XPIProvider); Components.utils.unload("resource://gre/modules/XPIProvider.jsm"); } @@ -1389,16 +1388,70 @@ function do_exception_wrap(func) { func.apply(null, arguments); } catch(e) { do_report_unexpected_exception(e); } }; } +const EXTENSIONS_DB = "extensions.json"; +let gExtensionsJSON = gProfD.clone(); +gExtensionsJSON.append(EXTENSIONS_DB); + +/** + * Change the schema version of the JSON extensions database + */ +function changeXPIDBVersion(aNewVersion) { + let jData = loadJSON(gExtensionsJSON); + jData.schemaVersion = aNewVersion; + saveJSON(jData, gExtensionsJSON); +} + +/** + * Raw load of a JSON file + */ +function loadJSON(aFile) { + let data = ""; + let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. + createInstance(Components.interfaces.nsIFileInputStream); + let cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Components.interfaces.nsIConverterInputStream); + fstream.init(aFile, -1, 0, 0); + cstream.init(fstream, "UTF-8", 0, 0); + let (str = {}) { + let read = 0; + do { + read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value + data += str.value; + } while (read != 0); + } + cstream.close(); + do_print("Loaded JSON file " + aFile.path); + return(JSON.parse(data)); +} + +/** + * Raw save of a JSON blob to file + */ +function saveJSON(aData, aFile) { + do_print("Starting to save JSON file " + aFile.path); + let stream = FileUtils.openSafeFileOutputStream(aFile); + let converter = AM_Cc["@mozilla.org/intl/converter-output-stream;1"]. + createInstance(AM_Ci.nsIConverterOutputStream); + converter.init(stream, "UTF-8", 0, 0x0000); + // XXX pretty print the JSON while debugging + converter.writeString(JSON.stringify(aData, null, 2)); + converter.flush(); + // nsConverterOutputStream doesn't finish() safe output streams on close() + FileUtils.closeSafeFileOutputStream(stream); + converter.close(); + do_print("Done saving JSON file " + aFile.path); +} + /** * Create a callback function that calls do_execute_soon on an actual callback and arguments */ function callback_soon(aFunction) { return function(...args) { do_execute_soon(function() { aFunction.apply(null, args); }, aFunction.name ? "delayed callback " + aFunction.name : "delayed callback");
--- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js @@ -507,17 +507,17 @@ function check_cache(aExpectedToFind, aE * * @param aExpectedToFind * An array of booleans representing which REPOSITORY_ADDONS are * expected to be found in the cache * @param aCallback * A callback to call once the checks are complete */ function check_initialized_cache(aExpectedToFind, aCallback) { - check_cache(aExpectedToFind, true, function() { + check_cache(aExpectedToFind, true, function restart_initialized_cache() { restartManager(); // If cache is disabled, then expect results immediately let cacheEnabled = Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED); check_cache(aExpectedToFind, !cacheEnabled, aCallback); }); } @@ -529,35 +529,35 @@ function waitForFlushedData(aCallback) { Services.obs.removeObserver(this, "addon-repository-data-written"); aCallback(aData == "true"); } }, "addon-repository-data-written", false); } function run_test() { // Setup for test - do_test_pending(); + do_test_pending("test_AddonRepository_cache"); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9"); startupManager(); // Install XPI add-ons - installAllFiles(ADDON_FILES, function() { + installAllFiles(ADDON_FILES, function first_installs() { restartManager(); gServer = new HttpServer(); gServer.registerDirectory("/data/", do_get_file("data")); gServer.start(PORT); do_execute_soon(run_test_1); }); } function end_test() { - gServer.stop(do_test_finished); + gServer.stop(function() {do_test_finished("test_AddonRepository_cache");}); } // Tests AddonRepository.cacheEnabled function run_test_1() { Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); do_check_false(AddonRepository.cacheEnabled); Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); do_check_true(AddonRepository.cacheEnabled); @@ -573,17 +573,17 @@ function run_test_2() { } // Tests repopulateCache when the search fails function run_test_3() { check_database_exists(true); Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_FAILED); - AddonRepository.repopulateCache(ADDON_IDS, function() { + AddonRepository.repopulateCache(ADDON_IDS, function test_3_repopulated() { check_initialized_cache([false, false, false], run_test_4); }); } // Tests repopulateCache when search returns no results function run_test_4() { Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_EMPTY); @@ -690,17 +690,17 @@ function run_test_11() { // Tests that XPI add-ons do not use any of the repository properties if // caching is disabled, even if there are repository properties available function run_test_12() { check_database_exists(true); Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS); - AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) { + AddonManager.getAddonsByIDs(ADDON_IDS, function test_12_check(aAddons) { check_results(aAddons, WITHOUT_CACHE); do_execute_soon(run_test_13); }); } // Tests that a background update with caching disabled deletes the add-ons // database, and that XPI add-ons still do not use any of repository properties function run_test_13() {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_ChromeManifestParser.js @@ -98,11 +98,11 @@ function run_test_1() { manifest = ChromeManifestParser.parseSync(manifestURI); do_check_true(Array.isArray(manifest)); do_check_eq(manifest.length, expected.length); for (let i = 0; i < manifest.length; i++) { do_check_eq(JSON.stringify(manifest[i]), JSON.stringify(expected[i])); } - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_DeferredSave.js @@ -32,18 +32,18 @@ function DeferredSaveTester(aDelay, aDat return tester.saver.saveChanges(); }, flush: (aWriteHandler) => { tester.writeHandler = aWriteHandler || writer; return tester.saver.flush(); }, - get error() { - return tester.saver.error; + get lastError() { + return tester.saver.lastError; } }; // Default write handler for most cases where the test case doesn't need // to do anything while the write is in progress; just completes the write // on the next event loop function writer(aTester) { do_print("default write callback"); @@ -149,21 +149,21 @@ add_task(function test_error_immediate() function writeFail(aTester) { aTester.waDeferred.reject(testError); } yield tester.save("test_error_immediate", writeFail).then( count => do_throw("Did not get expected error"), error => do_check_eq(testError.message, error.message) ); - do_check_eq(testError, tester.error); + do_check_eq(testError, tester.lastError); // This write should succeed and clear the error yield tester.save("test_error_immediate succeeds"); - do_check_eq(null, tester.error); + do_check_eq(null, tester.lastError); // The failed save attempt counts in our total do_check_eq(2, tester.saver.totalSaves); }); // Save one set of changes, then while the write is in progress, modify the // data two more times. Test that we re-write the dirty data exactly once // after the first write succeeds add_task(function dirty_while_writing() {
new file mode 100644 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bad_json.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Tests that we rebuild the database correctly if it contains +// JSON data that parses correctly but doesn't contain required fields + +var addon1 = { + id: "addon1@tests.mozilla.org", + version: "2.0", + name: "Test 1", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }] +}; + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +function run_test() { + do_test_pending("Bad JSON"); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + + // This addon will be auto-installed at startup + writeInstallRDFForExtension(addon1, profileDir); + + startupManager(); + + shutdownManager(); + + // First startup/shutdown finished + // Replace the JSON store with something bogus + saveJSON({not: "what we expect to find"}, gExtensionsJSON); + + startupManager(false); + // Retrieve an addon to force the database to rebuild + AddonManager.getAddonsByIDs([addon1.id], callback_soon(after_db_rebuild)); +} + +function after_db_rebuild([a1]) { + do_check_eq(a1.id, addon1.id); + + shutdownManager(); + + // Make sure our JSON database has schemaVersion and our installed extension + let data = loadJSON(gExtensionsJSON); + do_check_true("schemaVersion" in data); + do_check_eq(data.addons[0].id, addon1.id); + + do_test_finished("Bad JSON"); +}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_badschema.js @@ -249,24 +249,21 @@ function run_test_1() { do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); do_execute_soon(run_test_1_modified_db); }); } function run_test_1_modified_db() { - // After restarting the database won't be open and so can be replaced with - // a bad file - restartManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + // After restarting the database won't be open so we can alter + // the schema + shutdownManager(); + changeXPIDBVersion(100); + startupManager(); // Accessing the add-ons should open and recover the database. Since // migration occurs everything should be recovered correctly AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", "addon4@tests.mozilla.org", "addon5@tests.mozilla.org",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js @@ -330,22 +330,22 @@ const ADDON_IDS = ["softblock1@tests.moz "softblock4@tests.mozilla.org", "softblock5@tests.mozilla.org", "hardblock@tests.mozilla.org", "regexpblock@tests.mozilla.org"]; // Don't need the full interface, attempts to call other methods will just // throw which is just fine var WindowWatcher = { - openWindow: function(parent, url, name, features, arguments) { + openWindow: function(parent, url, name, features, openArgs) { // Should be called to list the newly blocklisted items do_check_eq(url, URI_EXTENSION_BLOCKLIST_DIALOG); // Simulate auto-disabling any softblocks - var list = arguments.wrappedJSObject.list; + var list = openArgs.wrappedJSObject.list; list.forEach(function(aItem) { if (!aItem.blocked) aItem.disable = true; }); //run the code after the blocklist is closed Services.obs.notifyObservers(null, "addon-blocklist-closed", null); @@ -526,20 +526,20 @@ function manual_update(aVersion, aCallba }, "application/x-xpinstall"); }, "application/x-xpinstall"); }, "application/x-xpinstall"); } // Checks that an add-ons properties match expected values function check_addon(aAddon, aExpectedVersion, aExpectedUserDisabled, aExpectedSoftDisabled, aExpectedState) { + do_check_neq(aAddon, null); dump("Testing " + aAddon.id + " version " + aAddon.version + "\n"); dump(aAddon.userDisabled + " " + aAddon.softDisabled + "\n"); - do_check_neq(aAddon, null); do_check_eq(aAddon.version, aExpectedVersion); do_check_eq(aAddon.blocklistState, aExpectedState); do_check_eq(aAddon.userDisabled, aExpectedUserDisabled); do_check_eq(aAddon.softDisabled, aExpectedSoftDisabled); if (aAddon.softDisabled) do_check_true(aAddon.userDisabled); if (aExpectedState == Ci.nsIBlocklistService.STATE_BLOCKED) { @@ -701,21 +701,17 @@ add_test(function run_app_update_schema_ do_check_eq(Services.prefs.getCharPref("general.skins.selectedSkin"), "test/1.0"); do_execute_soon(update_schema_2); }); function update_schema_2() { shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + changeXPIDBVersion(100); gAppInfo.version = "2"; startupManager(true); AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) { check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); @@ -733,21 +729,17 @@ add_test(function run_app_update_schema_ do_execute_soon(update_schema_3); }); } function update_schema_3() { restartManager(); shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + changeXPIDBVersion(100); gAppInfo.version = "2.5"; startupManager(true); AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) { check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); @@ -759,21 +751,17 @@ add_test(function run_app_update_schema_ do_execute_soon(update_schema_4); }); } function update_schema_4() { shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + changeXPIDBVersion(100); startupManager(false); AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) { check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); @@ -784,21 +772,17 @@ add_test(function run_app_update_schema_ do_execute_soon(update_schema_5); }); } function update_schema_5() { shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - db.schemaVersion = 100; - db.close(); + changeXPIDBVersion(100); gAppInfo.version = "1"; startupManager(true); AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) { check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); @@ -1340,17 +1324,17 @@ add_test(function run_manual_update_2_te writeInstallRDFForExtension(softblock3_1, profileDir); writeInstallRDFForExtension(softblock4_1, profileDir); writeInstallRDFForExtension(softblock5_1, profileDir); writeInstallRDFForExtension(hardblock_1, profileDir); writeInstallRDFForExtension(regexpblock_1, profileDir); startupManager(false); - AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) { + AddonManager.getAddonsByIDs(ADDON_IDS, callback_soon(function([s1, s2, s3, s4, s5, h, r]) { check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); s2.userDisabled = false; @@ -1358,17 +1342,18 @@ add_test(function run_manual_update_2_te check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); s3.userDisabled = false; check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); restartManager(); manual_update("2", function manual_update_2_2() { restartManager(); - AddonManager.getAddonsByIDs(ADDON_IDS, function([s1, s2, s3, s4, s5, h, r]) { + AddonManager.getAddonsByIDs(ADDON_IDS, + callback_soon(function([s1, s2, s3, s4, s5, h, r]) { check_addon(s1, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s2, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); check_addon(s3, "2.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED); // Can't manually update to a hardblocked add-on check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED); @@ -1386,19 +1371,19 @@ add_test(function run_manual_update_2_te check_addon(r, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED); s1.userDisabled = false; s2.userDisabled = false; s4.userDisabled = true; run_next_test(); }); }); - }); + })); }); - }); + })); }); // Uses the API to install blocked add-ons from the local filesystem add_test(function run_local_install_test() { do_print("Test: " + arguments.callee.name + "\n"); shutdownManager(); getFileForAddon(profileDir, softblock1_1.id).remove(true);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap.js @@ -6,18 +6,16 @@ const APP_STARTUP = const APP_SHUTDOWN = 2; const ADDON_ENABLE = 3; const ADDON_DISABLE = 4; const ADDON_INSTALL = 5; const ADDON_UNINSTALL = 6; const ADDON_UPGRADE = 7; const ADDON_DOWNGRADE = 8; -const EXTENSIONS_DB = "extensions.sqlite"; - // This verifies that bootstrappable add-ons can be used without restarts. Components.utils.import("resource://gre/modules/Services.jsm"); // Enable loading extensions from the user scopes Services.prefs.setIntPref("extensions.enabledScopes", AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_USER); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); @@ -143,20 +141,19 @@ function do_check_bootstrappedPref(aCall function run_test() { do_test_pending(); resetPrefs(); startupManager(); + do_check_false(gExtensionsJSON.exists()); + let file = gProfD.clone(); - file.append(EXTENSIONS_DB); - do_check_false(file.exists()); - file.leafName = "extensions.ini"; do_check_false(file.exists()); do_check_bootstrappedPref(run_test_1); } // Tests that installing doesn't require a restart function run_test_1() { @@ -203,19 +200,16 @@ function run_test_1() { do_check_eq(getActiveVersion(), -1); }); install.install(); }); } function check_test_1(installSyncGUID) { let file = gProfD.clone(); - file.append(EXTENSIONS_DB); - do_check_true(file.exists()); - file.leafName = "extensions.ini"; do_check_false(file.exists()); AddonManager.getAllInstalls(function(installs) { // There should be no active installs now since the install completed and // doesn't require a restart. do_check_eq(installs.length, 0); @@ -351,16 +345,19 @@ function run_test_4() { do_check_bootstrappedPref(run_test_5); }); }); } // Tests that a restart shuts down and restarts the add-on function run_test_5() { shutdownManager(); + // By the time we've shut down, the database must have been written + do_check_true(gExtensionsJSON.exists()); + do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 0); do_check_eq(getShutdownReason(), APP_SHUTDOWN); do_check_eq(getShutdownNewVersion(), 0); do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0"); startupManager(false); do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 1); @@ -453,27 +450,27 @@ function run_test_7() { function check_test_7() { ensure_test_completed(); do_check_eq(getInstalledVersion(), 0); do_check_eq(getActiveVersion(), 0); do_check_eq(getShutdownReason(), ADDON_UNINSTALL); do_check_eq(getShutdownNewVersion(), 0); do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "2.0"); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { do_check_eq(b1, null); restartManager(); AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(newb1) { do_check_eq(newb1, null); do_check_bootstrappedPref(run_test_8); }); - }); + })); } // Test that a bootstrapped extension dropped into the profile loads properly // on startup and doesn't cause an EM restart function run_test_8() { shutdownManager(); manuallyInstall(do_get_addon("test_bootstrap1_1"), profileDir, @@ -669,20 +666,23 @@ function run_test_12() { do_check_true(b1.isActive); do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 1); do_check_eq(getStartupReason(), ADDON_INSTALL); do_check_eq(getStartupOldVersion(), 0); do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0"); b1.uninstall(); - restartManager(); + do_execute_soon(test_12_restart); + }); +} - do_check_bootstrappedPref(run_test_13); - }); +function test_12_restart() { + restartManager(); + do_check_bootstrappedPref(run_test_13); } // Tests that installing a bootstrapped extension with an invalid application // entry doesn't call it's startup method function run_test_13() { prepare_test({ }, [ "onNewInstall" @@ -701,17 +701,17 @@ function run_test_13() { prepare_test({ "bootstrap1@tests.mozilla.org": [ ["onInstalling", false], "onInstalled" ] }, [ "onInstallStarted", "onInstallEnded", - ], function() {do_execute_soon(check_test_13)}); + ], callback_soon(check_test_13)); install.install(); }); } function check_test_13() { AddonManager.getAllInstalls(function(installs) { // There should be no active installs now since the install completed and // doesn't require a restart. @@ -722,33 +722,37 @@ function check_test_13() { do_check_eq(b1.version, "3.0"); do_check_true(b1.appDisabled); do_check_false(b1.userDisabled); do_check_false(b1.isActive); do_check_eq(getInstalledVersion(), 3); // We call install even for disabled add-ons do_check_eq(getActiveVersion(), 0); // Should not have called startup though do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "3.0"); - restartManager(); + do_execute_soon(test_13_restart); + }); + }); +} + +function test_13_restart() { + restartManager(); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { - do_check_neq(b1, null); - do_check_eq(b1.version, "3.0"); - do_check_true(b1.appDisabled); - do_check_false(b1.userDisabled); - do_check_false(b1.isActive); - do_check_eq(getInstalledVersion(), 3); // We call install even for disabled add-ons - do_check_eq(getActiveVersion(), 0); // Should not have called startup though - do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "3.0"); + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + do_check_neq(b1, null); + do_check_eq(b1.version, "3.0"); + do_check_true(b1.appDisabled); + do_check_false(b1.userDisabled); + do_check_false(b1.isActive); + do_check_eq(getInstalledVersion(), 3); // We call install even for disabled add-ons + do_check_eq(getActiveVersion(), 0); // Should not have called startup though + do_check_not_in_crash_annotation("bootstrap1@tests.mozilla.org", "3.0"); - do_check_bootstrappedPref(function() { - b1.uninstall(); - do_execute_soon(run_test_14); - }); - }); + do_check_bootstrappedPref(function() { + b1.uninstall(); + do_execute_soon(run_test_14); }); }); } // Tests that a bootstrapped extension with an invalid target application entry // does not get loaded when detected during startup function run_test_14() { restartManager(); @@ -810,17 +814,17 @@ function run_test_15() { prepare_test({ "bootstrap1@tests.mozilla.org": [ ["onInstalling", false], "onInstalled" ] }, [ "onInstallStarted", "onInstallEnded", - ], function() {do_execute_soon(check_test_15)}); + ], callback_soon(check_test_15)); install.install(); }); }); }); installAllFiles([do_get_addon("test_bootstrap1_1")], function test_15_addon_installed() { }); } function check_test_15() { @@ -852,17 +856,17 @@ function check_test_15() { }); }); } // Tests that bootstrapped extensions don't get loaded when in safe mode function run_test_16() { resetPrefs(); waitForPref("bootstraptest.startup_reason", function test_16_after_startup() { - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { // Should have installed and started do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 1); do_check_true(b1.isActive); do_check_eq(b1.iconURL, "chrome://foo/skin/icon.png"); do_check_eq(b1.aboutURL, "chrome://foo/content/about.xul"); do_check_eq(b1.optionsURL, "chrome://foo/content/options.xul"); @@ -870,17 +874,17 @@ function run_test_16() { // Should have stopped do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 0); gAppInfo.inSafeMode = true; startupManager(false); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { // Should still be stopped do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 0); do_check_false(b1.isActive); do_check_eq(b1.iconURL, null); do_check_eq(b1.aboutURL, null); do_check_eq(b1.optionsURL, null); @@ -890,20 +894,20 @@ function run_test_16() { // Should have started do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 1); AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { b1.uninstall(); - run_test_17(); + do_execute_soon(run_test_17); }); - }); - }); + })); + })); }); installAllFiles([do_get_addon("test_bootstrap1_1")], function() { }); } // Check that a bootstrapped extension in a non-profile location is loaded function run_test_17() { shutdownManager(); @@ -1020,17 +1024,17 @@ function run_test_20() { do_check_eq(getInstallReason(), ADDON_UPGRADE); do_check_eq(getStartupReason(), APP_STARTUP); do_check_eq(getShutdownNewVersion(), 0); do_check_eq(getUninstallNewVersion(), 2); do_check_eq(getInstallOldVersion(), 1); do_check_eq(getStartupOldVersion(), 0); - run_test_21(); + do_execute_soon(run_test_21); }); } // Check that a detected removal reveals the non-profile one function run_test_21() { resetPrefs(); shutdownManager(); @@ -1077,17 +1081,17 @@ function run_test_22() { let file = manuallyInstall(do_get_addon("test_bootstrap1_1"), profileDir, "bootstrap1@tests.mozilla.org"); // Make it look old so changes are detected setExtensionModifiedTime(file, file.lastModifiedTime - 5000); startupManager(); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { // Should have installed and started do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 1); do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_true(b1.isActive); resetPrefs(); @@ -1121,17 +1125,17 @@ function run_test_22() { do_check_eq(getStartupOldVersion(), 0); do_check_bootstrappedPref(function() { b1.uninstall(); run_test_23(); }); }); - }); + })); } // Tests that installing from a URL doesn't require a restart function run_test_23() { prepare_test({ }, [ "onNewInstall" ]); @@ -1195,27 +1199,27 @@ function check_test_23() { do_check_true(b1.hasResource("install.rdf")); do_check_true(b1.hasResource("bootstrap.js")); do_check_false(b1.hasResource("foo.bar")); do_check_in_crash_annotation("bootstrap1@tests.mozilla.org", "1.0"); let dir = do_get_addon_root_uri(profileDir, "bootstrap1@tests.mozilla.org"); do_check_eq(b1.getResourceURI("bootstrap.js").spec, dir + "bootstrap.js"); - AddonManager.getAddonsWithOperationsByTypes(null, function(list) { + AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) { do_check_eq(list.length, 0); restartManager(); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { b1.uninstall(); restartManager(); testserver.stop(run_test_24); - }); - }); + })); + })); }); }); }); } // Tests that we recover from a broken preference function run_test_24() { resetPrefs(); @@ -1272,17 +1276,17 @@ function run_test_25() { do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 1); installAllFiles([do_get_addon("test_bootstrap1_4")], function() { // Needs a restart to complete this so the old version stays running do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 1); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_true(b1.isActive); do_check_true(hasFlag(b1.pendingOperations, AddonManager.PENDING_UPGRADE)); restartManager(); do_check_eq(getInstalledVersion(), 0); @@ -1293,33 +1297,33 @@ function run_test_25() { AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "4.0"); do_check_true(b1.isActive); do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE); do_check_bootstrappedPref(run_test_26); }); - }); + })); }); }); installAllFiles([do_get_addon("test_bootstrap1_1")], function test_25_installed() { do_print("test 25 install done"); }); } // Tests that updating from a normal add-on to a bootstrappable add-on calls // the install method function run_test_26() { installAllFiles([do_get_addon("test_bootstrap1_1")], function() { // Needs a restart to complete this do_check_eq(getInstalledVersion(), 0); do_check_eq(getActiveVersion(), 0); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "4.0"); do_check_true(b1.isActive); do_check_true(hasFlag(b1.pendingOperations, AddonManager.PENDING_UPGRADE)); restartManager(); do_check_eq(getInstalledVersion(), 1); @@ -1330,17 +1334,17 @@ function run_test_26() { AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_true(b1.isActive); do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE); do_check_bootstrappedPref(run_test_27); }); - }); + })); }); } // Tests that updating from a bootstrappable add-on to a normal add-on while // disabled calls the uninstall method function run_test_27() { AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { do_check_neq(b1, null); @@ -1353,17 +1357,17 @@ function run_test_27() { installAllFiles([do_get_addon("test_bootstrap1_4")], function() { // Updating disabled things happens immediately do_check_eq(getInstalledVersion(), 0); do_check_eq(getUninstallReason(), ADDON_UPGRADE); do_check_eq(getUninstallNewVersion(), 4); do_check_eq(getActiveVersion(), 0); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "4.0"); do_check_false(b1.isActive); do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE); restartManager(); do_check_eq(getInstalledVersion(), 0); @@ -1372,33 +1376,33 @@ function run_test_27() { AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "4.0"); do_check_false(b1.isActive); do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE); do_check_bootstrappedPref(run_test_28); }); - }); + })); }); }); } // Tests that updating from a normal add-on to a bootstrappable add-on when // disabled calls the install method but not the startup method function run_test_28() { installAllFiles([do_get_addon("test_bootstrap1_1")], function() { do_execute_soon(function bootstrap_disabled_downgrade_check() { // Doesn't need a restart to complete this do_check_eq(getInstalledVersion(), 1); do_check_eq(getInstallReason(), ADDON_DOWNGRADE); do_check_eq(getInstallOldVersion(), 4); do_check_eq(getActiveVersion(), 0); - AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) { + AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", callback_soon(function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_false(b1.isActive); do_check_true(b1.userDisabled); do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE); restartManager(); @@ -1412,12 +1416,12 @@ function run_test_28() { do_check_eq(b1.version, "1.0"); do_check_true(b1.isActive); do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE); do_check_eq(getInstalledVersion(), 1); do_check_eq(getActiveVersion(), 1); do_check_bootstrappedPref(do_test_finished); }); - }); + })); }); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug324121.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug324121.js @@ -133,17 +133,17 @@ function run_test() { testserver = new HttpServer(); testserver.registerDirectory("/data/", dataDir); testserver.start(4444); startupManager(); installAllFiles([do_get_addon(a.addon) for each (a in ADDONS)], function() { restartManager(); - AddonManager.getAddonByID(ADDONS[0].id, function(addon) { + AddonManager.getAddonByID(ADDONS[0].id, callback_soon(function(addon) { do_check_true(!(!addon)); addon.userDisabled = true; restartManager(); AddonManager.getAddonsByTypes(["extension"], function(installedItems) { var items = []; for (let addon of ADDONS) { @@ -163,16 +163,16 @@ function run_test() { updateListener.pendingCount++; installedItem.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED, "3", "3"); } } } }); - }); + })); }); } function test_complete() { do_check_eq(gItemsNotChecked.length, 0); testserver.stop(do_test_finished); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js @@ -160,22 +160,22 @@ function run_test() { Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false); Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "en-US"); startupManager(); installAllFiles([do_get_addon(a.addon) for each (a in ADDONS)], function() { restartManager(); - AddonManager.getAddonByID(ADDONS[1].id, function(addon) { + AddonManager.getAddonByID(ADDONS[1].id, callback_soon(function(addon) { do_check_true(!(!addon)); addon.userDisabled = true; restartManager(); AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], function(installedItems) { installedItems.forEach(function(item) { updateListener.pendingCount++; item.findUpdates(updateListener, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); }); - }); + })); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug371495.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug371495.js @@ -23,13 +23,13 @@ function run_test() do_check_eq(addon.name, "Test theme"); restartManager(); AddonManager.getAddonByID(ID, function(addon) { do_check_neq(addon, null); do_check_eq(addon.optionsURL, null); do_check_eq(addon.aboutURL, null); - do_test_finished(); + do_execute_soon(do_test_finished); }); }); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug384052.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug384052.js @@ -91,13 +91,13 @@ function run_test() onUpdateAvailable: function(addon, install) { do_throw("Should not have seen an available update"); }, onUpdateFinished: function(addon, error) { do_check_eq(error, AddonManager.UPDATE_STATUS_DOWNLOAD_ERROR); do_check_true(gSeenExpectedURL); - shutdownTest(); + do_execute_soon(shutdownTest); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug397778.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug397778.js @@ -22,96 +22,96 @@ function run_test() installAllFiles([do_get_addon(ADDON)], function() { restartManager(); run_test_1(); }); } function run_test_1() { - AddonManager.getAddonByID(ID, function(addon) { + AddonManager.getAddonByID(ID, callback_soon(function(addon) { do_check_neq(addon, null); do_check_eq(addon.name, "fr Name"); do_check_eq(addon.description, "fr Description"); // Disable item addon.userDisabled = true; restartManager(); AddonManager.getAddonByID(ID, function(newAddon) { do_check_neq(newAddon, null); do_check_eq(newAddon.name, "fr Name"); - run_test_2(); + do_execute_soon(run_test_2); }); - }); + })); } function run_test_2() { // Change locale. The more specific de-DE is the best match Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "de"); restartManager(); AddonManager.getAddonByID(ID, function(addon) { do_check_neq(addon, null); do_check_eq(addon.name, "de-DE Name"); do_check_eq(addon.description, null); - run_test_3(); + do_execute_soon(run_test_3); }); } function run_test_3() { // Change locale. Locale case should have no effect Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "DE-de"); restartManager(); AddonManager.getAddonByID(ID, function(addon) { do_check_neq(addon, null); do_check_eq(addon.name, "de-DE Name"); do_check_eq(addon.description, null); - run_test_4(); + do_execute_soon(run_test_4); }); } function run_test_4() { // Change locale. es-ES should closely match Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "es-AR"); restartManager(); AddonManager.getAddonByID(ID, function(addon) { do_check_neq(addon, null); do_check_eq(addon.name, "es-ES Name"); do_check_eq(addon.description, "es-ES Description"); - run_test_5(); + do_execute_soon(run_test_5); }); } function run_test_5() { // Change locale. Either zh-CN or zh-TW could match Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "zh"); restartManager(); AddonManager.getAddonByID(ID, function(addon) { do_check_neq(addon, null); if (addon.name != "zh-TW Name" && addon.name != "zh-CN Name") do_throw("zh matched to " + addon.name); - run_test_6(); + do_execute_soon(run_test_6); }); } function run_test_6() { // Unknown locale should try to match against en-US as well. Of en,en-GB // en should match as being less specific Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "nl-NL"); restartManager(); AddonManager.getAddonByID(ID, function(addon) { do_check_neq(addon, null); do_check_eq(addon.name, "en Name"); do_check_eq(addon.description, "en Description"); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug425657.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug425657.js @@ -16,12 +16,12 @@ function run_test() startupManager(); installAllFiles([do_get_addon(ADDON)], function() { restartManager(); AddonManager.getAddonByID(ID, function(addon) { do_check_neq(addon, null); do_check_eq(addon.name, "Deutsches W\u00f6rterbuch"); do_check_eq(addon.name.length, 20); - do_test_finished(); + do_execute_soon(do_test_finished); }); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js @@ -280,17 +280,17 @@ function check_initial_state(callback) { callback(); }); } // Tests the add-ons were installed and the initial blocklist applied as expected function check_test_pt1() { dump("Checking pt 1\n"); - AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], function(addons) { + AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], callback_soon(function(addons) { for (var i = 0; i < ADDONS.length; i++) { if (!addons[i]) do_throw("Addon " + (i + 1) + " did not get installed correctly"); } do_check_eq(check_addon_state(addons[0]), "false,false,false"); do_check_eq(check_addon_state(addons[1]), "false,false,false"); do_check_eq(check_addon_state(addons[2]), "false,false,false"); @@ -311,17 +311,17 @@ function check_test_pt1() { addons[4].userDisabled = false; restartManager(); check_initial_state(function() { gNotificationCheck = check_notification_pt2; gTestCheck = check_test_pt2; load_blocklist("bug455906_warn.xml"); }); - }); + })); } function check_notification_pt2(args) { dump("Checking notification pt 2\n"); do_check_eq(args.list.length, 4); for (let addon of args.list) { if (addon.item instanceof Ci.nsIPluginTag) { @@ -352,17 +352,17 @@ function check_notification_pt2(args) { } } } function check_test_pt2() { restartManager(); dump("Checking results pt 2\n"); - AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], function(addons) { + AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], callback_soon(function(addons) { // Should have disabled this add-on as requested do_check_eq(check_addon_state(addons[2]), "true,true,false"); do_check_eq(check_plugin_state(PLUGINS[2]), "true,false"); // The blocked add-on should have changed to soft disabled do_check_eq(check_addon_state(addons[5]), "true,true,false"); do_check_eq(check_plugin_state(PLUGINS[5]), "true,false"); @@ -381,17 +381,17 @@ function check_test_pt2() { addons[2].userDisabled = false; addons[5].userDisabled = false; PLUGINS[2].enabledState = Ci.nsIPluginTag.STATE_ENABLED; PLUGINS[5].enabledState = Ci.nsIPluginTag.STATE_ENABLED; restartManager(); gNotificationCheck = null; gTestCheck = run_test_pt3; load_blocklist("bug455906_start.xml"); - }); + })); } function run_test_pt3() { restartManager(); check_initial_state(function() { gNotificationCheck = check_notification_pt3; gTestCheck = check_test_pt3; load_blocklist("bug455906_block.xml"); @@ -480,26 +480,26 @@ function check_test_pt3() { // Back to starting state gNotificationCheck = null; gTestCheck = run_test_pt4; load_blocklist("bug455906_start.xml"); }); } function run_test_pt4() { - AddonManager.getAddonByID(ADDONS[4].id, function(addon) { + AddonManager.getAddonByID(ADDONS[4].id, callback_soon(function(addon) { addon.userDisabled = false; PLUGINS[4].enabledState = Ci.nsIPluginTag.STATE_ENABLED; restartManager(); check_initial_state(function() { gNotificationCheck = check_notification_pt4; gTestCheck = check_test_pt4; load_blocklist("bug455906_empty.xml"); }); - }); + })); } function check_notification_pt4(args) { dump("Checking notification pt 4\n"); // Should be just the dummy add-on to force this notification do_check_eq(args.list.length, 1); do_check_false(args.list[0].item instanceof Ci.nsIPluginTag);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3.js @@ -85,11 +85,11 @@ function run_test_2() { do_check_true(a2.isActive); do_check_neq(a3, null); do_check_true(a3.isActive); do_check_neq(a4, null); do_check_true(a4.isActive); do_check_neq(a5, null); do_check_true(a5.isActive); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_3_strictcompat.js @@ -84,11 +84,11 @@ function run_test_2() { do_check_true(a2.isActive); do_check_neq(a3, null); do_check_true(a3.isActive); do_check_neq(a4, null); do_check_true(a4.isActive); do_check_neq(a5, null); do_check_true(a5.isActive); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_4.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug470377_4.js @@ -82,11 +82,11 @@ function run_test_2() { do_check_false(a2.isActive); do_check_neq(a3, null); do_check_false(a3.isActive); do_check_neq(a4, null); do_check_true(a4.isActive); do_check_neq(a5, null); do_check_true(a5.isActive); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug521905.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug521905.js @@ -49,11 +49,11 @@ function run_test_1() { function run_test_2() { Services.prefs.setBoolPref("extensions.checkCompatibility.2.0p", false); restartManager(); AddonManager.getAddonByID(ID, function(addon) { do_check_neq(addon, null); do_check_false(addon.isActive); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug526598.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug526598.js @@ -11,17 +11,17 @@ function run_test() { installAllFiles([do_get_file("data/test_bug526598_1.xpi"), do_get_file("data/test_bug526598_2.xpi")], function() { restartManager(); AddonManager.getAddonsByIDs(["bug526598_1@tests.mozilla.org", "bug526598_2@tests.mozilla.org"], - function([a1, a2]) { + callback_soon(function([a1, a2]) { do_check_neq(a1, null); do_check_true(a1.hasResource("install.rdf")); let uri = a1.getResourceURI("install.rdf"); do_check_true(uri instanceof AM_Ci.nsIFileURL); let file = uri.file; do_check_true(file.exists()); do_check_true(file.isReadable()); @@ -42,13 +42,13 @@ function run_test() { restartManager(); AddonManager.getAddonsByIDs(["bug526598_1@tests.mozilla.org", "bug526598_2@tests.mozilla.org"], function([newa1, newa2]) { do_check_eq(newa1, null); do_check_eq(newa2, null); - do_test_finished(); + do_execute_soon(do_test_finished); }); - }); + })); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug541420.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug541420.js @@ -26,12 +26,12 @@ function run_test() { // We don't understand executable permissions on Windows since we don't // support NTFS permissions so we don't need to test there. OSX's isExecutable // only tests if the file is an application so it is better to just check the // raw permission bits if (!("nsIWindowsRegKey" in Components.interfaces)) do_check_true((file.permissions & 0100) == 0100); - do_test_finished(); + do_execute_soon(do_test_finished); }); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug542391.js @@ -327,34 +327,34 @@ function run_test() { check_startup_changes("installed", []); check_startup_changes("updated", []); check_startup_changes("uninstalled", ["addon1@tests.mozilla.org"]); check_startup_changes("disabled", []); check_startup_changes("enabled", []); AddonManager.getAddonsByIDs(["bug542391_2@tests.mozilla.org", "bug542391_4@tests.mozilla.org"], - function disable_and_restart([a2, a4]) { + callback_soon(function disable_and_restart([a2, a4]) { do_check_true(a2 != null && a4 != null); a2.userDisabled = true; a4.userDisabled = true; restartManager(); check_startup_changes("installed", []); check_startup_changes("updated", []); check_startup_changes("uninstalled", []); check_startup_changes("disabled", []); check_startup_changes("enabled", []); AddonManager.getAddonsByIDs(["bug542391_1@tests.mozilla.org", "bug542391_2@tests.mozilla.org", "bug542391_3@tests.mozilla.org", "bug542391_4@tests.mozilla.org", "bug542391_5@tests.mozilla.org", "bug542391_6@tests.mozilla.org"], - function(addons) { + callback_soon(function(addons) { check_state_v1(addons); WindowWatcher.expected = true; restartManager("2"); check_startup_changes("installed", []); check_startup_changes("updated", []); check_startup_changes("uninstalled", []); check_startup_changes("disabled", ["bug542391_1@tests.mozilla.org"]); @@ -367,18 +367,18 @@ function run_test() { "bug542391_4@tests.mozilla.org", "bug542391_5@tests.mozilla.org", "bug542391_6@tests.mozilla.org"], function(addons) { check_state_v2(addons); do_execute_soon(run_test_1); }); - }); - }); + })); + })); }); } function end_test() { testserver.stop(do_test_finished); } // Upgrade to version 3 which will appDisable two more add-ons. Check that the
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug559800.js @@ -1,17 +1,15 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ // This verifies that deleting the database from the profile doesn't break // anything -const EXTENSIONS_DB = "extensions.sqlite"; - const profileDir = gProfD.clone(); profileDir.append("extensions"); // getting an unused port Components.utils.import("resource://testing-common/httpd.js"); let gServer = new HttpServer(); gServer.start(-1); gPort = gServer.identity.primaryPort; @@ -38,37 +36,36 @@ function run_test() { run_test_1(); } function end_test() { gServer.stop(do_test_finished); } function run_test_1() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); shutdownManager(); - let db = gProfD.clone(); - db.append(EXTENSIONS_DB); - db.remove(true); + gExtensionsJSON.remove(true); do_execute_soon(check_test_1); - }); + })); } function check_test_1() { startupManager(false); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); - let db = gProfD.clone(); - db.append(EXTENSIONS_DB); - do_check_true(db.exists()); - do_check_true(db.fileSize > 0); + // due to delayed write, the file may not exist until + // after shutdown + shutdownManager(); + do_check_true(gExtensionsJSON.exists()); + do_check_true(gExtensionsJSON.fileSize > 0); end_test(); - }); + })); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug563256.js @@ -60,17 +60,17 @@ function run_test() { do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_DISABLE)); do_check_true(hasFlag(a.permissions, AddonManager.PERM_CAN_ENABLE)); run_test_1(d, a); }); } function end_test() { - do_test_finished(); + do_execute_soon(do_test_finished); } // Checks switching to a different theme and back again leaves everything the // same function run_test_1(d, a) { a.userDisabled = false; do_check_true(d.userDisabled); @@ -191,17 +191,17 @@ function run_test_2() { do_execute_soon(check_test_2); }); } function check_test_2() { restartManager(); AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "alternate@tests.mozilla.org"], function([d, a]) { + "alternate@tests.mozilla.org"], callback_soon(function([d, a]) { do_check_eq(Services.prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN), "alternate/1.0"); do_check_true(d.userDisabled); do_check_false(d.appDisabled); do_check_false(d.isActive); do_check_false(isThemeInAddonsList(profileDir, d.id)); do_check_false(hasFlag(d.permissions, AddonManager.PERM_CAN_DISABLE)); do_check_true(hasFlag(d.permissions, AddonManager.PERM_CAN_ENABLE)); @@ -250,10 +250,10 @@ function check_test_2() { do_check_false(a.appDisabled); do_check_false(a.isActive); do_check_false(isThemeInAddonsList(profileDir, a.id)); do_check_false(hasFlag(a.permissions, AddonManager.PERM_CAN_DISABLE)); do_check_true(hasFlag(a.permissions, AddonManager.PERM_CAN_ENABLE)); end_test(); }); - }); + })); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug564030.js @@ -23,17 +23,17 @@ function run_test() { }] }, profileDir); // Attempt to make this look like it was added some time in the past so // the update makes the last modified time change. setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000); startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a) { do_check_neq(a, null); do_check_eq(a.version, "1.0"); do_check_false(a.userDisabled); do_check_true(a.appDisabled); do_check_false(a.isActive); do_check_false(isExtensionInAddonsList(profileDir, a.id)); writeInstallRDFForExtension({ @@ -52,12 +52,12 @@ function run_test() { AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a) { do_check_neq(a, null); do_check_eq(a.version, "2.0"); do_check_false(a.userDisabled); do_check_false(a.appDisabled); do_check_true(a.isActive); do_check_true(isExtensionInAddonsList(profileDir, a.id)); - do_test_finished(); + do_execute_soon(do_test_finished); }); - }); + })); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug566626.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug566626.js @@ -101,12 +101,12 @@ function run_test_1() { // Verifies that a subsequent call gets the same add-on from the cache function run_test_2() { AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.name, "Test 1"); do_check_eq(a1, gAddon); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug567184.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug567184.js @@ -42,12 +42,12 @@ function check_test_1() { do_check_eq(installs.length, 0); AddonManager.getAddonByID("bug567184@tests.mozilla.org", function(b1) { do_check_neq(b1, null); do_check_true(b1.appDisabled); do_check_false(b1.userDisabled); do_check_false(b1.isActive); - do_test_finished(); + do_execute_soon(do_test_finished); }); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug569138.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug569138.js @@ -136,12 +136,12 @@ function run_test() { do_check_neq(a6, null); do_check_true(a6.appDisabled); do_check_false(a6.isActive); do_check_neq(a6, null); do_check_true(a6.appDisabled); do_check_false(a6.isActive); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug576735.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug576735.js @@ -33,17 +33,17 @@ profileDir.append("extensions"); function run_test() { do_test_pending(); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "2"); writeInstallRDFForExtension(addon1, profileDir); startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { a1.uninstall(); shutdownManager(); var dest = profileDir.clone(); dest.append(do_get_expected_addon_name("addon1@tests.mozilla.org")); dest.remove(true); @@ -55,12 +55,12 @@ function run_test() { "addon2@tests.mozilla.org"], function([a1, a2]) { // Addon1 should no longer be installed do_check_eq(a1, null); // Addon2 should have been detected do_check_neq(a2, null); - do_test_finished(); + do_execute_soon(do_test_finished); }); - }); + })); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug587088.js @@ -91,49 +91,49 @@ function run_test_1() { fstream.init(uri.QueryInterface(AM_Ci.nsIFileURL).file, -1, 0, 0); installAllFiles([do_get_addon("test_bug587088_2")], function() { check_addon_upgrading(a1); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { check_addon_upgrading(a1); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { check_addon_upgrading(a1); fstream.close(); restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { check_addon(a1, "2.0"); a1.uninstall(); do_execute_soon(run_test_2); }); - }); - }); + })); + })); }); }); }); } // Test that a failed uninstall gets rolled back function run_test_2() { restartManager(); installAllFiles([do_get_addon("test_bug587088_1")], function() { restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { check_addon(a1, "1.0"); // Lock either install.rdf for unpacked add-ons or the xpi for packed add-ons. let uri = a1.getResourceURI("install.rdf"); if (uri.schemeIs("jar")) uri = a1.getResourceURI(); let fstream = AM_Cc["@mozilla.org/network/file-input-stream;1"]. @@ -141,34 +141,34 @@ function run_test_2() { fstream.init(uri.QueryInterface(AM_Ci.nsIFileURL).file, -1, 0, 0); a1.uninstall(); check_addon_uninstalling(a1); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { check_addon_uninstalling(a1, true); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { check_addon_uninstalling(a1, true); fstream.close(); restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_eq(a1, null); var dir = profileDir.clone(); dir.append(do_get_expected_addon_name("addon1@tests.mozilla.org")); do_check_false(dir.exists()); do_check_false(isExtensionInAddonsList(profileDir, "addon1@tests.mozilla.org")); - do_test_finished(); + do_execute_soon(do_test_finished); }); - }); - }); - }); + })); + })); + })); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug595573.js @@ -30,11 +30,11 @@ function run_test_1() { function run_test_2() { restartManager(); AddonManager.getAddonByID("{2f69dacd-03df-4150-a9f1-e8a7b2748829}", function(a1) { do_check_neq(a1, null); do_check_true(isExtensionInAddonsList(profileDir, a1.id)); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug596607.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug596607.js @@ -130,11 +130,11 @@ function run_test_3() { do_check_neq(a2, null); do_check_true(a2.isActive); do_check_false(hasFlag(a2.permissions, AddonManager.PERM_CAN_UNINSTALL)); do_check_eq(a2.scope, AddonManager.SCOPE_USER); do_check_eq(a3, null); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js @@ -16,11 +16,11 @@ function run_test() { startupManager(); do_test_pending(); test_string_compare(); AddonManager.getAddonByID("foo", function(aAddon) { test_string_compare(); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug619730.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug619730.js @@ -9,17 +9,23 @@ const Cu = Components.utils; Cu.import("resource://testing-common/httpd.js"); var gTestserver = new HttpServer(); gTestserver.start(-1); gPort = gTestserver.identity.primaryPort; mapFile("/data/test_bug619730.xml", gTestserver); -function load_blocklist(file) { +function load_blocklist(file, aCallback) { + Services.obs.addObserver(function() { + Services.obs.removeObserver(arguments.callee, "blocklist-updated"); + + do_execute_soon(aCallback); + }, "blocklist-updated", false); + Services.prefs.setCharPref("extensions.blocklist.url", "http://localhost:" + gPort + "/data/" + file); var blocklist = Cc["@mozilla.org/extensions/blocklist;1"]. getService(Ci.nsITimerCallback); blocklist.notify(null); } var gSawGFX = false; @@ -44,14 +50,15 @@ function run_test() { do_check_eq(aSubject.getAttribute("testattr"), "FOO"); do_check_eq(aSubject.childNodes.length, 3); gSawTest = true; }, "blocklist-data-testItems", false); Services.obs.addObserver(function(aSubject, aTopic, aData) { do_check_true(gSawGFX); do_check_true(gSawTest); - - gTestserver.stop(do_test_finished); }, "blocklist-data-fooItems", false); - load_blocklist("test_bug619730.xml"); + // Need to wait for the blocklist to load; Bad Things happen if the test harness + // shuts down AddonManager before the blocklist service is done telling it about + // changes + load_blocklist("test_bug619730.xml", () => gTestserver.stop(do_test_finished)); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug655254.js @@ -79,17 +79,17 @@ function run_test_1() { do_check_true(a2.isActive); do_check_false(isExtensionInAddonsList(userDir, a2.id)); do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 1); a1.findUpdates({ onUpdateFinished: function() { restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_neq(a1, null); do_check_false(a1.appDisabled); do_check_true(a1.isActive); do_check_true(isExtensionInAddonsList(userDir, a1.id)); shutdownManager(); do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 0); @@ -110,27 +110,27 @@ function run_test_1() { do_check_true(isExtensionInAddonsList(userDir, a1.id)); do_check_neq(a2, null); do_check_false(a2.appDisabled); do_check_true(a2.isActive); do_check_false(isExtensionInAddonsList(userDir, a2.id)); do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 1); - run_test_2(); + do_execute_soon(run_test_2); }); - }); + })); } }, AddonManager.UPDATE_WHEN_USER_REQUESTED); }); } //Set up the profile function run_test_2() { - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) { do_check_neq(a2, null); do_check_false(a2.appDisabled); do_check_true(a2.isActive); do_check_false(isExtensionInAddonsList(userDir, a2.id)); do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 1); a2.userDisabled = true; do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 0); @@ -155,10 +155,10 @@ function run_test_2() { do_check_neq(a2, null); do_check_true(a2.userDisabled); do_check_false(a2.isActive); do_check_false(isExtensionInAddonsList(userDir, a2.id)); do_check_eq(Services.prefs.getIntPref("bootstraptest.active_version"), 0); end_test(); }); - }); + })); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug659772.js @@ -103,24 +103,18 @@ function run_test_1() { // Prepare the add-on update, and a bootstrapped addon (bug 693714) installAllFiles([ do_get_addon("test_bug659772"), do_get_addon("test_bootstrap1_1") ], function() { shutdownManager(); // Make it look like the next time the app is started it has a new DB schema - let dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let db = AM_Cc["@mozilla.org/storage/service;1"]. - getService(AM_Ci.mozIStorageService). - openDatabase(dbfile); - db.schemaVersion = 1; + changeXPIDBVersion(1); Services.prefs.setIntPref("extensions.databaseSchema", 1); - db.close(); let jsonfile = gProfD.clone(); jsonfile.append("extensions"); jsonfile.append("staged"); jsonfile.append("addon3@tests.mozilla.org.json"); do_check_true(jsonfile.exists()); // Remove an unnecessary property from the cached manifest @@ -168,20 +162,20 @@ function run_test_1() { do_check_false(a2.userDisabled); do_check_true(a2.isActive); do_check_true(isExtensionInAddonsList(profileDir, addon2.id)); // Should stay enabled because we migrate the compat info from // the previous version of the DB do_check_neq(a3, null); do_check_eq(a3.version, "2.0"); - do_check_false(a3.appDisabled); + todo_check_false(a3.appDisabled); // XXX unresolved issue do_check_false(a3.userDisabled); - do_check_true(a3.isActive); - do_check_true(isExtensionInAddonsList(profileDir, addon3.id)); + todo_check_true(a3.isActive); // XXX same + todo_check_true(isExtensionInAddonsList(profileDir, addon3.id)); // XXX same do_check_neq(a4, null); do_check_eq(a4.version, "2.0"); do_check_true(a4.appDisabled); do_check_false(a4.userDisabled); do_check_false(a4.isActive); do_check_false(isExtensionInAddonsList(profileDir, addon4.id)); @@ -250,24 +244,18 @@ function run_test_2() { do_get_addon("test_bug659772"), do_get_addon("test_bootstrap1_1") ], function() { do_execute_soon(prepare_schema_migrate); }); function prepare_schema_migrate() { shutdownManager(); // Make it look like the next time the app is started it has a new DB schema - let dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - let db = AM_Cc["@mozilla.org/storage/service;1"]. - getService(AM_Ci.mozIStorageService). - openDatabase(dbfile); - db.schemaVersion = 1; + changeXPIDBVersion(1); Services.prefs.setIntPref("extensions.databaseSchema", 1); - db.close(); let jsonfile = gProfD.clone(); jsonfile.append("extensions"); jsonfile.append("staged"); jsonfile.append("addon3@tests.mozilla.org.json"); do_check_true(jsonfile.exists()); // Remove an unnecessary property from the cached manifest @@ -297,17 +285,17 @@ function run_test_2() { gAppInfo.version = "2"; startupManager(true); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", "addon4@tests.mozilla.org"], - function([a1, a2, a3, a4]) { + callback_soon(function([a1, a2, a3, a4]) { do_check_neq(a1, null); do_check_eq(a1.version, "2.0"); do_check_true(a1.appDisabled); do_check_false(a1.userDisabled); do_check_false(a1.isActive); do_check_false(isExtensionInAddonsList(profileDir, addon1.id)); do_check_neq(a2, null); @@ -316,20 +304,20 @@ function run_test_2() { do_check_false(a2.userDisabled); do_check_true(a2.isActive); do_check_true(isExtensionInAddonsList(profileDir, addon2.id)); // Should become appDisabled because we migrate the compat info from // the previous version of the DB do_check_neq(a3, null); do_check_eq(a3.version, "2.0"); - do_check_true(a3.appDisabled); + todo_check_true(a3.appDisabled); do_check_false(a3.userDisabled); - do_check_false(a3.isActive); - do_check_false(isExtensionInAddonsList(profileDir, addon3.id)); + todo_check_false(a3.isActive); + todo_check_false(isExtensionInAddonsList(profileDir, addon3.id)); do_check_neq(a4, null); do_check_eq(a4.version, "2.0"); do_check_false(a4.appDisabled); do_check_false(a4.userDisabled); do_check_true(a4.isActive); do_check_true(isExtensionInAddonsList(profileDir, addon4.id)); @@ -341,12 +329,12 @@ function run_test_2() { a2.uninstall(); a3.uninstall(); a4.uninstall(); restartManager(); shutdownManager(); do_test_finished(); - }); + })); }; }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug675371.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug675371.js @@ -19,19 +19,17 @@ function run_test() { prepare_test({ "bug675371@tests.mozilla.org": [ ["onInstalling", false], "onInstalled" ] }, [ "onInstallStarted", "onInstallEnded", - ], function() { - do_execute_soon(check_test) - }); + ], callback_soon(check_test)); install.install(); }); } function check_test() { AddonManager.getAddonByID("bug675371@tests.mozilla.org", do_exception_wrap(function(addon) { do_check_neq(addon, null); do_check_true(addon.isActive); @@ -83,11 +81,11 @@ function check_test() { target.active = false; try { Services.scriptloader.loadSubScript("chrome://bug675371/content/test.js", target); do_throw("Chrome file should not have been found"); } catch (e) { do_check_false(target.active); } - do_test_finished(); + do_execute_soon(do_test_finished); })); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug740612.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug740612.js @@ -30,11 +30,11 @@ function run_test() { AddonManager.getAddonsByIDs(["bug740612_1@tests.mozilla.org", "bug740612_2@tests.mozilla.org"], function([a1, a2]) { do_check_neq(a1, null); do_check_neq(a2, null); do_check_eq(getInstalledVersion(), "1.0"); do_check_eq(getActiveVersion(), "1.0"); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug753900.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug753900.js @@ -76,11 +76,11 @@ function run_test() { do_check_in_crash_annotation(addon1.id, addon1.version); do_check_neq(a2, null); do_check_in_crash_annotation(addon2.id, addon2.version); do_check_neq(a3, null); do_check_in_crash_annotation(addon3.id, addon3.version); do_check_neq(a4, null); do_check_in_crash_annotation(addon4.id, addon4.version); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug757663.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug757663.js @@ -102,11 +102,11 @@ function run_test_2() { listener2.sawEvent = false; do_check_true(listener3.sawEvent); listener3.sawEvent = false; AddonManager.removeInstallListener(listener1); AddonManager.removeInstallListener(listener2); AddonManager.removeInstallListener(listener3); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_cacheflush.js @@ -112,11 +112,11 @@ function run_test_4() { gExpectedFile = gProfD.clone(); gExpectedFile.append("extensions"); gExpectedFile.append("addon2@tests.mozilla.org.xpi"); a2.uninstall(); do_check_true(gCacheFlushed); gCacheFlushed = false; - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_checkcompatibility.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_checkcompatibility.js @@ -186,11 +186,11 @@ function run_test_4() { AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", "addon4@tests.mozilla.org", "addon5@tests.mozilla.org"], function([a1, a2, a3, a4, a5]) { check_state(false, a1, a2, a3, a4, a5); - do_test_finished("checkcompatibility.js"); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js @@ -246,20 +246,18 @@ function run_test_1() { do_check_false(t2.userDisabled); do_check_false(t2.appDisabled); do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); // Shutdown and replace the database with a corrupt file (a directory // serves this purpose). On startup the add-ons manager won't rebuild // because there is a file there still. shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - dbfile.remove(true); - dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755); + gExtensionsJSON.remove(true); + gExtensionsJSON.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); startupManager(false); // Accessing the add-ons should open and recover the database AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", "addon4@tests.mozilla.org", "addon5@tests.mozilla.org",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt_strictcompat.js @@ -247,20 +247,18 @@ function run_test_1() { do_check_false(t2.userDisabled); do_check_false(t2.appDisabled); do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE); // Shutdown and replace the database with a corrupt file (a directory // serves this purpose). On startup the add-ons manager won't rebuild // because there is a file there still. shutdownManager(); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - dbfile.remove(true); - dbfile.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755); + gExtensionsJSON.remove(true); + gExtensionsJSON.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); startupManager(false); // Accessing the add-ons should open and recover the database AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", "addon2@tests.mozilla.org", "addon3@tests.mozilla.org", "addon4@tests.mozilla.org", "addon5@tests.mozilla.org",
deleted file mode 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_db_sanity.js +++ /dev/null @@ -1,181 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -// This tests the data in extensions.sqlite for general sanity, making sure -// rows in one table only reference rows in another table that actually exist. - - -function check_db() { - do_print("Checking DB sanity..."); - var dbfile = gProfD.clone(); - dbfile.append("extensions.sqlite"); - var db = Services.storage.openDatabase(dbfile); - - do_print("Checking locale_strings references rows in locale correctly..."); - let localeStringsStmt = db.createStatement("SELECT * FROM locale_strings"); - let localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:locale_id"); - let i = 0; - while (localeStringsStmt.executeStep()) { - i++; - localeStmt.params.locale_id = localeStringsStmt.row.locale_id; - do_check_true(localeStmt.executeStep()); - do_check_eq(localeStmt.row.count, 1); - localeStmt.reset(); - } - localeStmt.finalize(); - localeStringsStmt.finalize(); - do_print("Done. " + i + " rows in locale_strings checked."); - - - do_print("Checking locale references rows in addon_locale and addon correctly..."); - localeStmt = db.createStatement("SELECT * FROM locale"); - let addonLocaleStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon_locale WHERE locale_id=:locale_id"); - let addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE defaultLocale=:locale_id"); - i = 0; - while (localeStmt.executeStep()) { - i++; - addonLocaleStmt.params.locale_id = localeStmt.row.id; - do_check_true(addonLocaleStmt.executeStep()); - if (addonLocaleStmt.row.count == 0) { - addonStmt.params.locale_id = localeStmt.row.id; - do_check_true(addonStmt.executeStep()); - do_check_eq(addonStmt.row.count, 1); - } else { - do_check_eq(addonLocaleStmt.row.count, 1); - } - addonLocaleStmt.reset(); - addonStmt.reset(); - } - addonLocaleStmt.finalize(); - localeStmt.finalize(); - addonStmt.finalize(); - do_print("Done. " + i + " rows in locale checked."); - - - do_print("Checking addon_locale references rows in locale correctly..."); - addonLocaleStmt = db.createStatement("SELECT * FROM addon_locale"); - localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:locale_id"); - i = 0; - while (addonLocaleStmt.executeStep()) { - i++; - localeStmt.params.locale_id = addonLocaleStmt.row.locale_id; - do_check_true(localeStmt.executeStep()); - do_check_eq(localeStmt.row.count, 1); - localeStmt.reset(); - } - addonLocaleStmt.finalize(); - localeStmt.finalize(); - do_print("Done. " + i + " rows in addon_locale checked."); - - - do_print("Checking addon_locale references rows in addon correctly..."); - addonLocaleStmt = db.createStatement("SELECT * FROM addon_locale"); - addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id"); - i = 0; - while (addonLocaleStmt.executeStep()) { - i++; - addonStmt.params.addon_internal_id = addonLocaleStmt.row.addon_internal_id; - do_check_true(addonStmt.executeStep()); - do_check_eq(addonStmt.row.count, 1); - addonStmt.reset(); - } - addonLocaleStmt.finalize(); - addonStmt.finalize(); - do_print("Done. " + i + " rows in addon_locale checked."); - - - do_print("Checking addon references rows in locale correctly..."); - addonStmt = db.createStatement("SELECT * FROM addon"); - localeStmt = db.createStatement("SELECT COUNT(*) AS count FROM locale WHERE id=:defaultLocale"); - i = 0; - while (addonStmt.executeStep()) { - i++; - localeStmt.params.defaultLocale = addonStmt.row.defaultLocale; - do_check_true(localeStmt.executeStep()); - do_check_eq(localeStmt.row.count, 1); - localeStmt.reset(); - } - addonStmt.finalize(); - localeStmt.finalize(); - do_print("Done. " + i + " rows in addon checked."); - - - do_print("Checking targetApplication references rows in addon correctly..."); - let targetAppStmt = db.createStatement("SELECT * FROM targetApplication"); - addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id"); - i = 0; - while (targetAppStmt.executeStep()) { - i++; - addonStmt.params.addon_internal_id = targetAppStmt.row.addon_internal_id; - do_check_true(addonStmt.executeStep()); - do_check_eq(addonStmt.row.count, 1); - addonStmt.reset(); - } - targetAppStmt.finalize(); - addonStmt.finalize(); - do_print("Done. " + i + " rows in targetApplication checked."); - - - do_print("Checking targetPlatform references rows in addon correctly..."); - let targetPlatformStmt = db.createStatement("SELECT * FROM targetPlatform"); - addonStmt = db.createStatement("SELECT COUNT(*) AS count FROM addon WHERE internal_id=:addon_internal_id"); - i = 0; - while (targetPlatformStmt.executeStep()) { - i++; - addonStmt.params.addon_internal_id = targetPlatformStmt.row.addon_internal_id; - do_check_true(addonStmt.executeStep()); - do_check_eq(addonStmt.row.count, 1); - addonStmt.reset(); - } - targetPlatformStmt.finalize(); - addonStmt.finalize(); - do_print("Done. " + i + " rows in targetPlatform checked."); - - - db.close(); - do_print("Done checking DB sanity."); -} - -function run_test() { - do_test_pending(); - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9"); - startupManager(); - - installAllFiles([do_get_addon("test_db_sanity_1_1")], run_test_1); -} - -function run_test_1() { - shutdownManager(); - check_db(); - startupManager(); - - AddonManager.getAddonByID("test_db_sanity_1@tests.mozilla.org", function(aAddon) { - aAddon.uninstall(); - - shutdownManager(); - check_db(); - startupManager(); - - installAllFiles([do_get_addon("test_db_sanity_1_1")], run_test_2); - }); -} - -function run_test_2() { - installAllFiles([do_get_addon("test_db_sanity_1_2")], function() { - shutdownManager(); - check_db(); - startupManager(); - run_test_3(); - }); -} - -function run_test_3() { - AddonManager.getAddonByID("test_db_sanity_1@tests.mozilla.org", function(aAddon) { - aAddon.uninstall(); - - shutdownManager(); - check_db(); - - do_test_finished(); - }); -}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_dictionary.js @@ -144,17 +144,17 @@ function run_test_1() { }, [ "onInstallStarted", "onInstallEnded", ], function() { do_check_true(addon.hasResource("install.rdf")); HunspellEngine.listener = function(aEvent) { HunspellEngine.listener = null; do_check_eq(aEvent, "addDirectory"); - check_test_1(); + do_execute_soon(check_test_1); }; }); install.install(); }); } function check_test_1() { AddonManager.getAllInstalls(function(installs) { @@ -312,27 +312,28 @@ function run_test_7() { }); } function check_test_7() { ensure_test_completed(); do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); do_check_not_in_crash_annotation("ab-CD@dictionaries.addons.mozilla.org", "1.0"); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", + callback_soon(function(b1) { do_check_eq(b1, null); restartManager(); AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(newb1) { do_check_eq(newb1, null); do_execute_soon(run_test_8); }); - }); + })); } // Test that a bootstrapped extension dropped into the profile loads properly // on startup and doesn't cause an EM restart function run_test_8() { shutdownManager(); let dir = profileDir.clone(); @@ -422,47 +423,49 @@ function run_test_12() { // Tests that bootstrapped extensions don't get loaded when in safe mode function run_test_16() { restartManager(); installAllFiles([do_get_addon("test_dictionary")], function() { // spin the event loop to let the addon finish starting do_execute_soon(function check_installed_dictionary() { - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", + callback_soon(function(b1) { // Should have installed and started do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); shutdownManager(); // Should have stopped do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); gAppInfo.inSafeMode = true; startupManager(false); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", + callback_soon(function(b1) { // Should still be stopped do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); do_check_false(b1.isActive); shutdownManager(); gAppInfo.inSafeMode = false; startupManager(false); // Should have started do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { b1.uninstall(); do_execute_soon(run_test_17); }); - }); - }); + })); + })); }); }); } // Check that a bootstrapped extension in a non-profile location is loaded function run_test_17() { shutdownManager(); @@ -478,32 +481,33 @@ function run_test_17() { dir.append("dictionaries"); dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755); dir.append("ab-CD.dic"); zip.extract("dictionaries/ab-CD.dic", dir); zip.close(); startupManager(); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", + callback_soon(function(b1) { // Should have installed and started do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_true(b1.isActive); // From run_test_21 dir = userExtDir.clone(); dir.append("ab-CD@dictionaries.addons.mozilla.org"); dir.remove(true); restartManager(); run_test_23(); - }); + })); } // Tests that installing from a URL doesn't require a restart function run_test_23() { prepare_test({ }, [ "onNewInstall" ]); @@ -560,25 +564,25 @@ function check_test_23() { do_check_true(b1.isActive); do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); do_check_true(b1.hasResource("install.rdf")); do_check_false(b1.hasResource("bootstrap.js")); do_check_in_crash_annotation("ab-CD@dictionaries.addons.mozilla.org", "1.0"); let dir = do_get_addon_root_uri(profileDir, "ab-CD@dictionaries.addons.mozilla.org"); - AddonManager.getAddonsWithOperationsByTypes(null, function(list) { + AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) { do_check_eq(list.length, 0); restartManager(); AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { b1.uninstall(); do_execute_soon(run_test_25); }); - }); + })); }); }); } // Tests that updating from a bootstrappable add-on to a normal add-on calls // the uninstall method function run_test_25() { restartManager(); @@ -587,17 +591,18 @@ function run_test_25() { HunspellEngine.listener = null; do_check_eq(aEvent, "addDirectory"); do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); installAllFiles([do_get_addon("test_dictionary_2")], function test_25_installed2() { // Needs a restart to complete this so the old version stays running do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", + callback_soon(function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "1.0"); do_check_true(b1.isActive); do_check_true(hasFlag(b1.pendingOperations, AddonManager.PENDING_UPGRADE)); restartManager(); do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); @@ -605,31 +610,32 @@ function run_test_25() { AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "2.0"); do_check_true(b1.isActive); do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE); do_execute_soon(run_test_26); }); - }); + })); }); }; installAllFiles([do_get_addon("test_dictionary")], function test_25_installed() { }); } // Tests that updating from a normal add-on to a bootstrappable add-on calls // the install method function run_test_26() { installAllFiles([do_get_addon("test_dictionary")], function test_26_install() { // Needs a restart to complete this do_check_false(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); - AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", function(b1) { + AddonManager.getAddonByID("ab-CD@dictionaries.addons.mozilla.org", + callback_soon(function(b1) { do_check_neq(b1, null); do_check_eq(b1.version, "2.0"); do_check_true(b1.isActive); do_check_true(hasFlag(b1.pendingOperations, AddonManager.PENDING_UPGRADE)); restartManager(); do_check_true(HunspellEngine.isDictionaryEnabled("ab-CD.dic")); @@ -639,17 +645,17 @@ function run_test_26() { do_check_eq(b1.version, "1.0"); do_check_true(b1.isActive); do_check_eq(b1.pendingOperations, AddonManager.PENDING_NONE); HunspellEngine.deactivate(); b1.uninstall(); do_execute_soon(run_test_27); }); - }); + })); }); } // Tests that an update check from a normal add-on to a bootstrappable add-on works function run_test_27() { restartManager(); writeInstallRDFForExtension({ id: "ab-CD@dictionaries.addons.mozilla.org", @@ -669,17 +675,17 @@ function run_test_27() { "onInstalling" ] }, [ "onNewInstall", "onDownloadStarted", "onDownloadEnded", "onInstallStarted", "onInstallEnded" - ], check_test_27); + ], callback_soon(check_test_27)); AddonManagerPrivate.backgroundUpdateCheck(); } function check_test_27(install) { do_check_eq(install.existingAddon.pendingUpgrade.install, install); restartManager(); @@ -715,17 +721,17 @@ function run_test_28() { "onInstalling" ] }, [ "onNewInstall", "onDownloadStarted", "onDownloadEnded", "onInstallStarted", "onInstallEnded" - ], check_test_28); + ], callback_soon(check_test_28)); AddonManagerPrivate.backgroundUpdateCheck(); } function check_test_28(install) { do_check_eq(install.existingAddon.pendingUpgrade.install, install); restartManager(); @@ -779,17 +785,17 @@ function check_test_29(install) { do_check_eq(b2.type, "dictionary"); prepare_test({ "gh@dictionaries.addons.mozilla.org": [ ["onUninstalling", false], ["onUninstalled", false], ] }, [ - ], finish_test_29); + ], callback_soon(finish_test_29)); b2.uninstall(); }); } function finish_test_29() { testserver.stop(do_test_finished); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_disable.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_disable.js @@ -27,17 +27,17 @@ var gIconURL = null; // Sets up the profile by installing an add-on. function run_test() { do_test_pending(); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); startupManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_eq(a1, null); do_check_not_in_crash_annotation(addon1.id, addon1.version); writeInstallRDFForExtension(addon1, profileDir, addon1.id, "icon.png"); gIconURL = do_get_addon_root_uri(profileDir.clone(), addon1.id) + "icon.png"; restartManager(); @@ -52,17 +52,17 @@ function run_test() { do_check_true(hasFlag(newa1.permissions, AddonManager.PERM_CAN_DISABLE)); do_check_false(hasFlag(newa1.permissions, AddonManager.PERM_CAN_ENABLE)); do_check_eq(newa1.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_DISABLE | AddonManager.OP_NEEDS_RESTART_UNINSTALL); do_check_in_crash_annotation(addon1.id, addon1.version); run_test_1(); }); - }); + })); } // Disabling an add-on should work function run_test_1() { prepare_test({ "addon1@tests.mozilla.org": [ "onDisabling" ] @@ -78,17 +78,17 @@ function run_test_1() { do_check_false(hasFlag(a1.permissions, AddonManager.PERM_CAN_DISABLE)); do_check_true(hasFlag(a1.permissions, AddonManager.PERM_CAN_ENABLE)); do_check_eq(a1.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_DISABLE | AddonManager.OP_NEEDS_RESTART_UNINSTALL); do_check_in_crash_annotation(addon1.id, addon1.version); ensure_test_completed(); - AddonManager.getAddonsWithOperationsByTypes(null, function(list) { + AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) { do_check_eq(list.length, 1); do_check_eq(list[0].id, "addon1@tests.mozilla.org"); restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(newa1) { do_check_neq(newa1, null); do_check_false(newa1.isActive); @@ -99,17 +99,17 @@ function run_test_1() { do_check_false(isExtensionInAddonsList(profileDir, newa1.id)); do_check_false(hasFlag(newa1.permissions, AddonManager.PERM_CAN_DISABLE)); do_check_true(hasFlag(newa1.permissions, AddonManager.PERM_CAN_ENABLE)); do_check_eq(newa1.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_ENABLE); do_check_not_in_crash_annotation(addon1.id, addon1.version); run_test_2(); }); - }); + })); }); } // Enabling an add-on should work. function run_test_2() { prepare_test({ "addon1@tests.mozilla.org": [ "onEnabling" @@ -122,17 +122,17 @@ function run_test_2() { do_check_eq(a1.optionsURL, null); do_check_eq(a1.iconURL, gIconURL); do_check_true(hasFlag(a1.permissions, AddonManager.PERM_CAN_DISABLE)); do_check_false(hasFlag(a1.permissions, AddonManager.PERM_CAN_ENABLE)); do_check_eq(a1.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_ENABLE); ensure_test_completed(); - AddonManager.getAddonsWithOperationsByTypes(null, function(list) { + AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(list) { do_check_eq(list.length, 1); do_check_eq(list[0].id, "addon1@tests.mozilla.org"); restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(newa1) { do_check_neq(newa1, null); do_check_true(newa1.isActive); @@ -144,29 +144,29 @@ function run_test_2() { do_check_true(hasFlag(newa1.permissions, AddonManager.PERM_CAN_DISABLE)); do_check_false(hasFlag(newa1.permissions, AddonManager.PERM_CAN_ENABLE)); do_check_eq(newa1.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_DISABLE | AddonManager.OP_NEEDS_RESTART_UNINSTALL); do_check_in_crash_annotation(addon1.id, addon1.version); run_test_3(); }); - }); + })); }); } // Disabling then enabling without restart should fire onOperationCancelled. function run_test_3() { prepare_test({ "addon1@tests.mozilla.org": [ "onDisabling" ] }); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { a1.userDisabled = true; ensure_test_completed(); prepare_test({ "addon1@tests.mozilla.org": [ "onOperationCancelled" ] }); a1.userDisabled = false; @@ -183,12 +183,12 @@ function run_test_3() { do_check_false(newa1.userDisabled); do_check_eq(newa1.aboutURL, "chrome://foo/content/about.xul"); do_check_eq(newa1.optionsURL, "chrome://foo/content/options.xul"); do_check_eq(newa1.iconURL, "chrome://foo/content/icon.png"); do_check_true(isExtensionInAddonsList(profileDir, newa1.id)); do_check_true(hasFlag(newa1.permissions, AddonManager.PERM_CAN_DISABLE)); do_check_false(hasFlag(newa1.permissions, AddonManager.PERM_CAN_ENABLE)); - do_test_finished(); + do_execute_soon(do_test_finished); }); - }); + })); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_distribution.js @@ -144,27 +144,27 @@ function run_test_4() { do_check_eq(a1.scope, AddonManager.SCOPE_PROFILE); do_execute_soon(run_test_5); }); } // Tests that after uninstalling a restart doesn't re-install the extension function run_test_5() { - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { a1.uninstall(); restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_eq(a1, null); do_execute_soon(run_test_6); }); - }); + })); } // Tests that upgrading the application still doesn't re-install the uninstalled // extension function run_test_6() { restartManager("4"); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { @@ -252,11 +252,11 @@ function run_test_9() { do_check_true(addonDir.exists()); do_check_true(addonDir.isDirectory()); addonDir.append("dummy2.txt"); do_check_true(addonDir.exists()); do_check_true(addonDir.isFile()); a2.uninstall(); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_dss.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_dss.js @@ -110,17 +110,17 @@ function run_test() { do_check_false(hasFlag(t2.permissions, AddonManager.PERM_CAN_DISABLE)); do_check_true(hasFlag(t2.permissions, AddonManager.PERM_CAN_ENABLE)); do_execute_soon(run_test_1); }); } function end_test() { - do_test_finished(); + do_execute_soon(do_test_finished); } // Checks enabling one theme disables the others function run_test_1() { prepare_test({ "theme1@tests.mozilla.org": [ ["onDisabling", false], "onDisabled" @@ -587,17 +587,17 @@ function run_test_9() { do_execute_soon(run_test_10); }); }); } // Uninstalling a custom theme in use should require a restart function run_test_10() { - AddonManager.getAddonByID("theme2@tests.mozilla.org", function(oldt2) { + AddonManager.getAddonByID("theme2@tests.mozilla.org", callback_soon(function(oldt2) { prepare_test({ "theme2@tests.mozilla.org": [ ["onEnabling", false], "onEnabled" ], "default@tests.mozilla.org": [ ["onDisabling", false], "onDisabled" @@ -606,17 +606,18 @@ function run_test_10() { oldt2.userDisabled = false; ensure_test_completed(); restartManager(); AddonManager.getAddonsByIDs(["default@tests.mozilla.org", - "theme2@tests.mozilla.org"], function([d, t2]) { + "theme2@tests.mozilla.org"], + callback_soon(function([d, t2]) { do_check_true(t2.isActive); do_check_false(t2.userDisabled); do_check_false(t2.appDisabled); do_check_false(d.isActive); do_check_true(d.userDisabled); do_check_false(d.appDisabled); prepare_test({ @@ -633,18 +634,18 @@ function run_test_10() { t2.uninstall(); ensure_test_completed(); do_check_false(gLWThemeChanged); restartManager(); do_execute_soon(run_test_11); - }); - }); + })); + })); } // Installing a custom theme not in use should not require a restart function run_test_11() { prepare_test({ }, [ "onNewInstall" ]); @@ -660,17 +661,17 @@ function run_test_11() { prepare_test({ "theme1@tests.mozilla.org": [ ["onInstalling", false], "onInstalled" ] }, [ "onInstallStarted", "onInstallEnded", - ], check_test_11); + ], callback_soon(check_test_11)); install.install(); }); } function check_test_11() { restartManager(); AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { do_check_neq(t1, null); @@ -717,17 +718,17 @@ function check_test_12() { do_check_false(gLWThemeChanged); do_execute_soon(run_test_13); }); } // Updating a custom theme in use should require a restart function run_test_13() { - AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { + AddonManager.getAddonByID("theme1@tests.mozilla.org", callback_soon(function(t1) { prepare_test({ "theme1@tests.mozilla.org": [ ["onEnabling", false], "onEnabled" ], "default@tests.mozilla.org": [ ["onDisabling", false], "onDisabled" @@ -753,39 +754,39 @@ function run_test_13() { prepare_test({ "theme1@tests.mozilla.org": [ "onInstalling", ] }, [ "onInstallStarted", "onInstallEnded", - ], check_test_13); + ], callback_soon(check_test_13)); install.install(); }); - }); + })); } function check_test_13() { restartManager(); AddonManager.getAddonByID("theme1@tests.mozilla.org", function(t1) { do_check_neq(t1, null); do_check_true(t1.isActive); do_check_false(gLWThemeChanged); t1.uninstall(); - restartManager(); do_execute_soon(run_test_14); }); } // Switching from a lightweight theme to the default theme should not require // a restart function run_test_14() { + restartManager(); LightweightThemeManager.currentTheme = { id: "1", version: "1", name: "Test LW Theme", description: "A test theme", author: "Mozilla", homepageURL: "http://localhost:" + gPort + "/data/index.html", headerURL: "http://localhost:" + gPort + "/data/header.png",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js @@ -159,26 +159,26 @@ function run_test_2() { do_execute_soon(run_test_3); }); } // Test that IDs persist across restart function run_test_3() { restartManager(); - AddonManager.getAddonByID(gPluginIDs[0], function(p) { + AddonManager.getAddonByID(gPluginIDs[0], callback_soon(function(p) { do_check_neq(p, null); do_check_eq(p.name, "Duplicate Plugin 1"); do_check_eq(p.description, "A duplicate plugin"); // Reorder the plugins and restart again [PLUGINS[0], PLUGINS[1]] = [PLUGINS[1], PLUGINS[0]]; restartManager(); AddonManager.getAddonByID(gPluginIDs[0], function(p) { do_check_neq(p, null); do_check_eq(p.name, "Duplicate Plugin 1"); do_check_eq(p.description, "A duplicate plugin"); - do_test_finished(); + do_execute_soon(do_test_finished); }); - }); + })); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_error.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_error.js @@ -80,11 +80,11 @@ function run_test_4() { // Checks that an add-on with an illegal ID shows an error function run_test_5() { AddonManager.getInstallForFile(do_get_addon("test_bug567173"), function(install) { do_check_neq(install, null); do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED); do_check_eq(install.error, AddonManager.ERROR_CORRUPT_FILE); - do_test_finished(); + do_execute_soon(do_test_finished); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js @@ -145,17 +145,17 @@ function run_test_2() { "addon1@tests.mozilla.org": [ "onInstalling" ] }, [ "onDownloadStarted", "onDownloadEnded", "onInstallStarted", "onInstallEnded" - ], check_test_2); + ], callback_soon(check_test_2)); install.install(); }, "application/x-xpinstall"); } function check_test_2() { restartManager(); @@ -185,30 +185,30 @@ function check_test_2() { // Tests that uninstalling doesn't clobber the original sources function run_test_3() { restartManager(); writePointer(addon1.id); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); a1.uninstall(); restartManager(); let source = sourceDir.clone(); source.append(addon1.id); do_check_true(source.exists()); do_execute_soon(run_test_4); - }); + })); } // Tests that misnaming a pointer doesn't clobber the sources function run_test_4() { writePointer("addon2@tests.mozilla.org", addon1.id); restartManager(); @@ -233,17 +233,17 @@ function run_test_4() { function run_test_5() { var dest = writeInstallRDFForExtension(addon1, sourceDir); // Make sure the modification time changes enough to be detected. setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000); writePointer(addon1.id); restartManager(); - AddonManager.getAddonByID(addon1.id, function(a1) { + AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); writeInstallRDFForExtension(addon2, sourceDir, addon1.id); restartManager(); AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org", @@ -256,53 +256,53 @@ function run_test_5() { do_check_true(source.exists()); let pointer = profileDir.clone(); pointer.append(addon1.id); do_check_false(pointer.exists()); do_execute_soon(run_test_6); }); - }); + })); } // Removing the pointer file should uninstall the add-on function run_test_6() { var dest = writeInstallRDFForExtension(addon1, sourceDir); // Make sure the modification time changes enough to be detected in run_test_8. setExtensionModifiedTime(dest, dest.lastModifiedTime - 5000); writePointer(addon1.id); restartManager(); - AddonManager.getAddonByID(addon1.id, function(a1) { + AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); let pointer = profileDir.clone(); pointer.append(addon1.id); pointer.remove(false); restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_eq(a1, null); do_execute_soon(run_test_7); }); - }); + })); } // Removing the pointer file and replacing it with a directory should work function run_test_7() { writePointer(addon1.id); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); let pointer = profileDir.clone(); pointer.append(addon1.id); pointer.remove(false); writeInstallRDFForExtension(addon1_2, profileDir); @@ -310,75 +310,75 @@ function run_test_7() { restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "2.0"); a1.uninstall(); - restartManager(); - do_execute_soon(run_test_8); }); - }); + })); } // Changes to the source files should be detected function run_test_8() { + restartManager(); + writePointer(addon1.id); restartManager(); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); writeInstallRDFForExtension(addon1_2, sourceDir); restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "2.0"); a1.uninstall(); - restartManager(); - do_execute_soon(run_test_9); }); - }); + })); } // Removing the add-on the pointer file points at should uninstall the add-on function run_test_9() { + restartManager(); + var dest = writeInstallRDFForExtension(addon1, sourceDir); writePointer(addon1.id); restartManager(); - AddonManager.getAddonByID(addon1.id, function(a1) { + AddonManager.getAddonByID(addon1.id, callback_soon(function(a1) { do_check_neq(a1, null); do_check_eq(a1.version, "1.0"); dest.remove(true); restartManager(); AddonManager.getAddonByID(addon1.id, function(a1) { do_check_eq(a1, null); let pointer = profileDir.clone(); pointer.append(addon1.id); do_check_false(pointer.exists()); do_execute_soon(run_test_10); }); - }); + })); } // Tests that installing a new add-on by pointer with a relative path works function run_test_10() { writeInstallRDFForExtension(addon1, sourceDir); writeRelativePointer(addon1.id); restartManager();
--- a/toolkit/mozapps/extensions/test/xpcshell/test_fuel.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_fuel.js @@ -155,10 +155,10 @@ function onPrefChange(evt) { inspector3.prefs.get("fuel.fuel-test").events.addListener("change", onPrefChange2); inspector3.prefs.setValue("fuel.fuel-test", "change event2"); }); } function onPrefChange2(evt) { do_check_eq(evt.data, testdata.dummy); - do_test_finished(); + do_execute_soon(do_test_finished); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_general.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_general.js @@ -13,17 +13,17 @@ function run_test() { do_test_pending(); createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); var count = 0; startupManager(); AddonManager.getAddonsByTypes(null, function(list) { gCount = list.length; - run_test_1(); + do_execute_soon(run_test_1); }); } function run_test_1() { restartManager(); AddonManager.getAddonsByTypes(null, function(addons) { do_check_eq(gCount, addons.length); @@ -46,14 +46,13 @@ function run_test_2() { do_execute_soon(run_test_3); }); } function run_test_3() { restartManager(); - AddonManager.getAddonsByTypes(null, function(addons) { + AddonManager.getAddonsByTypes(null, callback_soon(function(addons) { do_check_eq(gCount, addons.length); - shutdownManager(); do_test_finished(); - }); + })); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_getresource.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_getresource.js @@ -70,24 +70,25 @@ function run_test() { try { // hasResource should never throw an exception. do_check_false(a1.hasResource("icon.png")); } catch (e) { do_check_true(false); } - AddonManager.getInstallForFile(do_get_addon("test_getresource"), function(aInstall) { + AddonManager.getInstallForFile(do_get_addon("test_getresource"), + callback_soon(function(aInstall) { do_check_false(a1.hasResource("icon.png")); do_check_true(aInstall.addon.hasResource("icon.png")); restartManager(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(newa1) { do_check_eq(newa1, null); - do_test_finished(); + do_execute_soon(do_test_finished); }); - }); + })); }); }); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js @@ -71,12 +71,12 @@ function run_test() { do_check_true(a5.hasBinaryComponents); else do_check_false(a5.hasBinaryComponents); do_check_true(a5.isCompatible); do_check_false(a5.appDisabled); do_check_true(a5.isActive); do_check_true(isExtensionInAddonsList(profileDir, a5.id)); - do_test_finished(); + do_execute_soon(do_test_finished); }); }); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_hotfix.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_hotfix.js @@ -45,17 +45,17 @@ function run_test_1() { "onInstalling" ] }, [ "onNewInstall", "onDownloadStarted", "onDownloadEnded", "onInstallStarted", "onInstallEnded", - ], check_test_1); + ], callback_soon(check_test_1)); // Fake a timer event gInternalManager.notify(null); } function check_test_1() { restartManager(); @@ -100,17 +100,17 @@ function run_test_3() { "onInstalling" ] }, [ "onNewInstall", "onDownloadStarted", "onDownloadEnded", "onInstallStarted", "onInstallEnded", - ], check_test_3); + ], callback_soon(check_test_3)); // Fake a timer event gInternalManager.notify(null); } function check_test_3() { restartManager(); @@ -184,17 +184,17 @@ function run_test_6() { "onInstalling" ] }, [ "onNewInstall", "onDownloadStarted", "onDownloadEnded", "onInstallStarted", "onInstallEnded", - ], check_test_6); + ], callback_soon(check_test_6)); // Fake a timer event gInternalManager.notify(null); } function check_test_6() { AddonManager.addInstallListener({ onNewInstall: function() { @@ -256,17 +256,17 @@ function check_test_7(aInstall) { "onInstalling" ] }, [ "onNewInstall", "onDownloadStarted", "onDownloadEnded", "onInstallStarted", "onInstallEnded", - ], finish_test_7); + ], callback_soon(finish_test_7)); // Fake a timer event gInternalManager.notify(null); } function finish_test_7() { restartManager(); @@ -321,25 +321,25 @@ function check_test_8() { "onInstallEnded", ], finish_test_8); // Fake a timer event gInternalManager.notify(null); } function finish_test_8() { - AddonManager.getAllInstalls(function(aInstalls) { + AddonManager.getAllInstalls(callback_soon(function(aInstalls) { do_check_eq(aInstalls.length, 1); do_check_eq(aInstalls[0].version, "2.0"); restartManager(); - AddonManager.getAddonByID("hotfix@tests.mozilla.org", function(aAddon) { + AddonManager.getAddonByID("hotfix@tests.mozilla.org", callback_soon(function(aAddon) { do_check_neq(aAddon, null); do_check_eq(aAddon.version, "2.0"); aAddon.uninstall(); restartManager(); end_test(); - }); - }); + })); + })); }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_install.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_install.js @@ -119,17 +119,17 @@ function run_test_1() { }); } function check_test_1(installSyncGUID) { ensure_test_completed(); AddonManager.getAddonByID("addon1@tests.mozilla.org", function(olda1) { do_check_eq(olda1, null); - AddonManager.getAddonsWithOperationsByTypes(null, function(pendingAddons) { + AddonManager.getAddonsWithOperationsByTypes(null, callback_soon(function(pendingAddons) { do_check_eq(pendingAddons.length, 1); do_check_eq(pendingAddons[0].id, "addon1@tests.mozilla.org"); let uri = NetUtil.newURI(pendingAddons[0].iconURL); if (uri instanceof AM_Ci.nsIJARURI) { let jarURI = uri.QueryInterface(AM_Ci.nsIJARURI); let archiveURI = jarURI.JARFile; let archiveFile = archiveURI.QueryInterface(AM_Ci.nsIFileURL).file; let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]. @@ -157,17 +157,17 @@ function check_test_1(installSyncGUID) { do_check_false(hasFlag(pendingAddons[0].permissions, AddonManager.PERM_CAN_ENABLE)); do_check_false(hasFlag(pendingAddons[0].permissions, AddonManager.PERM_CAN_DISABLE)); restartManager(); AddonManager.getAllInstalls(function(activeInstalls) { do_check_eq(activeInstalls, 0); - AddonManager.getAddonByID("addon1@tests.mozilla.org", function(a1) { + AddonManager.getAddonByID("addon1@tests.mozilla.org", callback_soon(function(a1) { do_check_neq(a1, null); do_check_neq(a1.syncGUID, null); do_check_true(a1.syncGUID.length >= 9); do_check_eq(a1.syncGUID, installSyncGUID); do_check_eq(a1.type, "extension"); do_check_eq(a1.version, "1.0"); do_check_eq(a1.name, "Test 1"); do_check_true(isExtensionInAddonsList(profileDir, a1.id)); @@ -193,20 +193,20 @@ function check_test_1(installSyncGUID) { do_check_eq(a1.getResourceURI("install.rdf").spec, uri + "install.rdf"); do_check_eq(a1.iconURL, uri + "icon.png"); do_check_eq(a1.icon64URL, uri + "icon64.png"); a1.uninstall(); restartManager(); do_check_not_in_crash_annotation(a1.id, a1.version); - run_test_2(); - }); + do_execute_soon(run_test_2); + })); }); - }); + })); }); } // Tests that an install from a url downloads. function run_test_2() { let url = "http://localhost:" + gPort + "/addons/test_install2_1.xpi"; AddonManager.getInstallForURL(url, function(install) { do_check_neq(install, null); @@ -270,17 +270,17 @@ function run_test_3(install) { function check_test_3(aInstall) { // Make the pending install have a sensible date let updateDate = Date.now(); let extURI = aInstall.addon.getResourceURI(""); let ext = extURI.QueryInterface(AM_Ci.nsIFileURL).file; setExtensionModifiedTime(ext, updateDate); ensure_test_completed(); - AddonManager.getAddonByID("addon2@tests.mozilla.org", function(olda2) { + AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(olda2) { do_check_eq(olda2, null); restartManager(); AddonManager.getAllInstalls(function(installs) { do_check_eq(installs, 0); AddonManager.getAddonByID("addon2@tests.mozilla.org", function(a2) { do_check_neq(a2, null); @@ -302,17 +302,17 @@ function check_test_3(aInstall) { if (Math.abs(difference) > MAX_TIME_DIFFERENCE) do_throw("Add-on update time was out by " + difference + "ms"); gInstallDate = a2.installDate.getTime(); run_test_4(); }); });