mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central and fx-team
This commit is contained in:
commit
f26e4e2c73
@ -639,7 +639,7 @@
|
||||
|
||||
<hbox id="nav-bar-customization-target" flex="1">
|
||||
<toolbaritem id="urlbar-container" flex="400" persist="width"
|
||||
forwarddisabled="true" title="&locationItem.title;" removable="false"
|
||||
title="&locationItem.title;" removable="false"
|
||||
cui-areatype="toolbar"
|
||||
class="chromeclass-location" overflows="false">
|
||||
<toolbarbutton id="back-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
||||
|
@ -9,6 +9,7 @@
|
||||
const TEST_CONTENT_HELPER = "chrome://mochitests/content/browser/browser/base/content/test/social/social_crash_content_helper.js";
|
||||
|
||||
let {getFrameWorkerHandle} = Cu.import("resource://gre/modules/FrameWorker.jsm", {});
|
||||
let {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
@ -16,6 +17,9 @@ function test() {
|
||||
// We need to ensure all our workers are in the same content process.
|
||||
Services.prefs.setIntPref("dom.ipc.processCount", 1);
|
||||
|
||||
// This test generates many uncaught promises that should not cause failures.
|
||||
Promise.Debugging.clearUncaughtErrorObservers();
|
||||
|
||||
runSocialTestWithProvider(gProviders, function (finishcb) {
|
||||
runSocialTests(tests, undefined, undefined, function() {
|
||||
Services.prefs.clearUserPref("dom.ipc.processCount");
|
||||
|
@ -126,15 +126,16 @@ SessionStartup.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
this._initialState = this._parseStateString(stateString);
|
||||
this._initialState = this._parseStateString(stateString);
|
||||
|
||||
let shouldResumeSessionOnce = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
|
||||
let shouldResumeSession = shouldResumeSessionOnce ||
|
||||
Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
|
||||
|
||||
// If this is a normal restore then throw away any previous session
|
||||
if (!shouldResumeSessionOnce)
|
||||
if (!shouldResumeSessionOnce && this._initialState) {
|
||||
delete this._initialState.lastSessionState;
|
||||
}
|
||||
|
||||
let resumeFromCrash = Services.prefs.getBoolPref("browser.sessionstore.resume_from_crash");
|
||||
|
||||
|
@ -246,7 +246,7 @@ function test() {
|
||||
test_existingSHEnd_noClear, test_existingSHMiddle_noClear,
|
||||
test_getBrowserState_lotsOfTabsOpening,
|
||||
test_getBrowserState_userTypedValue, test_userTypedClearLoadURI];
|
||||
let originalState = ss.getBrowserState();
|
||||
let originalState = JSON.parse(ss.getBrowserState());
|
||||
let state = {
|
||||
windows: [{
|
||||
tabs: [{ entries: [{ url: "about:blank" }] }]
|
||||
@ -256,8 +256,7 @@ function test() {
|
||||
if (tests.length) {
|
||||
waitForBrowserState(state, tests.shift());
|
||||
} else {
|
||||
ss.setBrowserState(originalState);
|
||||
executeSoon(finish);
|
||||
waitForBrowserState(originalState, finish);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 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/. */
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
let stateBackup = JSON.parse(ss.getBrowserState());
|
||||
|
||||
function test() {
|
||||
/** Test for Bug 600545 **/
|
||||
@ -72,8 +72,7 @@ function done() {
|
||||
currentWindow.close();
|
||||
}
|
||||
|
||||
ss.setBrowserState(stateBackup);
|
||||
executeSoon(finish);
|
||||
waitForBrowserState(stateBackup, finish);
|
||||
}
|
||||
|
||||
// Count up the number of tabs in the state data
|
||||
|
@ -46,7 +46,7 @@ function testBug601955_3() {
|
||||
}
|
||||
|
||||
function done() {
|
||||
gBrowser.removeTab(window.gBrowser.tabs[0]);
|
||||
gBrowser.removeTab(window.gBrowser.tabs[1]);
|
||||
|
||||
Services.prefs.clearUserPref("browser.sessionstore.interval");
|
||||
|
||||
|
@ -165,6 +165,9 @@ function waitForBrowserState(aState, aSetStateCallback) {
|
||||
listening = true;
|
||||
gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, true);
|
||||
|
||||
// Ensure setBrowserState() doesn't remove the initial tab.
|
||||
gBrowser.selectedTab = gBrowser.tabs[0];
|
||||
|
||||
// Finally, call setBrowserState
|
||||
ss.setBrowserState(JSON.stringify(aState));
|
||||
}
|
||||
@ -520,6 +523,7 @@ let TestRunner = {
|
||||
*/
|
||||
finish: function () {
|
||||
closeAllButPrimaryWindow();
|
||||
gBrowser.selectedTab = gBrowser.tabs[0];
|
||||
waitForBrowserState(this.backupState, finish);
|
||||
}
|
||||
};
|
||||
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"windows": // invalid json
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function run_test() {
|
||||
let profd = do_get_profile();
|
||||
|
||||
let sourceSession = do_get_file("data/sessionstore_invalid.js");
|
||||
sourceSession.copyTo(profd, "sessionstore.js");
|
||||
|
||||
let sourceCheckpoints = do_get_file("data/sessionCheckpoints_all.json");
|
||||
sourceCheckpoints.copyTo(profd, "sessionCheckpoints.json");
|
||||
|
||||
do_test_pending();
|
||||
let startup = Cc["@mozilla.org/browser/sessionstartup;1"].
|
||||
getService(Ci.nsISessionStartup);
|
||||
|
||||
afterSessionStartupInitialization(function cb() {
|
||||
do_check_eq(startup.sessionType, Ci.nsISessionStartup.NO_SESSION);
|
||||
do_test_finished();
|
||||
});
|
||||
}
|
@ -4,9 +4,11 @@ tail =
|
||||
firefox-appdir = browser
|
||||
support-files =
|
||||
data/sessionCheckpoints_all.json
|
||||
data/sessionstore_invalid.js
|
||||
data/sessionstore_valid.js
|
||||
|
||||
[test_backup.js]
|
||||
[test_backup_once.js]
|
||||
[test_startup_nosession_async.js]
|
||||
[test_startup_session_async.js]
|
||||
[test_startup_invalid_session.js]
|
||||
|
@ -37,6 +37,7 @@ function test() {
|
||||
|
||||
Services.prefs.clearUserPref("browser.sessionstore.restore_hidden_tabs");
|
||||
|
||||
gBrowser.selectedTab = gBrowser.tabs[0];
|
||||
ss.setBrowserState(stateBackup);
|
||||
});
|
||||
|
||||
|
@ -9,6 +9,10 @@ function test() {
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gBrowser.unpinTab(pinnedTab);
|
||||
|
||||
// Don't remove the initial tab.
|
||||
gBrowser.moveTabTo(gBrowser.tabs[1], 0);
|
||||
|
||||
while (gBrowser.tabs[1])
|
||||
gBrowser.removeTab(gBrowser.tabs[1]);
|
||||
hideTabView();
|
||||
|
@ -3,34 +3,26 @@
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
|
||||
TabView.toggle();
|
||||
newWindowWithTabView(onTabViewWindowLoaded);
|
||||
}
|
||||
|
||||
function onTabViewWindowLoaded() {
|
||||
window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
|
||||
|
||||
let contentWindow = document.getElementById("tab-view").contentWindow;
|
||||
function onTabViewWindowLoaded(win) {
|
||||
let contentWindow = win.TabView.getContentWindow();
|
||||
|
||||
is(contentWindow.GroupItems.groupItems.length, 1,
|
||||
"There is one group item on startup");
|
||||
is(gBrowser.tabs.length, 1, "There is one tab on startup");
|
||||
is(win.gBrowser.tabs.length, 1, "There is one tab on startup");
|
||||
let groupItem = contentWindow.GroupItems.groupItems[0];
|
||||
|
||||
hideGroupItem(groupItem, function () {
|
||||
let onTabViewHidden = function() {
|
||||
window.removeEventListener("tabviewhidden", onTabViewHidden, false);
|
||||
hideTabView(() => {
|
||||
is(contentWindow.GroupItems.groupItems.length, 1,
|
||||
"There is still one group item");
|
||||
isnot(groupItem, contentWindow.GroupItems.groupItems[0],
|
||||
"The initial group item is not the same as the final group item");
|
||||
is(gBrowser.tabs.length, 1, "There is only one tab");
|
||||
ok(!TabView.isVisible(), "Tab View is hidden");
|
||||
finish();
|
||||
};
|
||||
window.addEventListener("tabviewhidden", onTabViewHidden, false);
|
||||
|
||||
TabView.hide();
|
||||
is(win.gBrowser.tabs.length, 1, "There is only one tab");
|
||||
ok(!win.TabView.isVisible(), "Tab View is hidden");
|
||||
promiseWindowClosed(win).then(finish);
|
||||
}, win);
|
||||
});
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ function test() {
|
||||
is(targetGroup.getChildren().length, 2, 'target group has now two children');
|
||||
|
||||
// cleanup and finish
|
||||
tabItem.close();
|
||||
targetGroup.getChild(0).close();
|
||||
hideTabView(finishTest);
|
||||
});
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
function test() {
|
||||
let cw;
|
||||
let win;
|
||||
let currentTest;
|
||||
|
||||
let getGroupItem = function (index) {
|
||||
@ -15,7 +16,7 @@ function test() {
|
||||
cw.UI.setActive(groupItem);
|
||||
|
||||
for (let i=0; i<numTabs || 0; i++)
|
||||
gBrowser.loadOneTab('about:blank', {inBackground: true});
|
||||
win.gBrowser.loadOneTab('about:blank', {inBackground: true});
|
||||
|
||||
return groupItem;
|
||||
}
|
||||
@ -35,13 +36,13 @@ function test() {
|
||||
}
|
||||
|
||||
currentTest = test.name;
|
||||
showTabView(test.func);
|
||||
showTabView(test.func, win);
|
||||
} else
|
||||
hideTabView(finish);
|
||||
promiseWindowClosed(win).then(finish);
|
||||
}
|
||||
|
||||
let assertTabViewIsHidden = function () {
|
||||
ok(!TabView.isVisible(), currentTest + ': tabview is hidden');
|
||||
ok(!win.TabView.isVisible(), currentTest + ': tabview is hidden');
|
||||
}
|
||||
|
||||
let assertNumberOfGroupItems = function (num) {
|
||||
@ -49,7 +50,7 @@ function test() {
|
||||
}
|
||||
|
||||
let assertNumberOfTabs = function (num) {
|
||||
is(gBrowser.tabs.length, num, currentTest + ': number of tabs is equal to ' + num);
|
||||
is(win.gBrowser.tabs.length, num, currentTest + ': number of tabs is equal to ' + num);
|
||||
}
|
||||
|
||||
let assertGroupItemRemoved = function (groupItem) {
|
||||
@ -68,7 +69,7 @@ function test() {
|
||||
closeGroupItem(groupItem, function () {
|
||||
assertNumberOfGroupItems(1);
|
||||
assertGroupItemRemoved(groupItem);
|
||||
whenTabViewIsHidden(next);
|
||||
whenTabViewIsHidden(next, win);
|
||||
});
|
||||
}
|
||||
|
||||
@ -82,7 +83,7 @@ function test() {
|
||||
assertNumberOfGroupItems(1);
|
||||
assertGroupItemRemoved(groupItem);
|
||||
next();
|
||||
});
|
||||
}, win);
|
||||
});
|
||||
}
|
||||
|
||||
@ -97,7 +98,7 @@ function test() {
|
||||
closeGroupItem(groupItem, function () {
|
||||
assertNumberOfGroupItems(1);
|
||||
assertGroupItemExists(newGroupItem);
|
||||
hideTabView(next);
|
||||
hideTabView(next, win);
|
||||
});
|
||||
}
|
||||
|
||||
@ -115,7 +116,7 @@ function test() {
|
||||
assertGroupItemRemoved(groupItem);
|
||||
assertGroupItemExists(newGroupItem);
|
||||
next();
|
||||
});
|
||||
}, win);
|
||||
});
|
||||
}
|
||||
|
||||
@ -123,37 +124,37 @@ function test() {
|
||||
// action: exit panorama
|
||||
// expected: nothing should happen
|
||||
let testPinnedTab1 = function () {
|
||||
gBrowser.pinTab(gBrowser.selectedTab);
|
||||
win.gBrowser.pinTab(win.gBrowser.selectedTab);
|
||||
|
||||
let groupItem = getGroupItem(0);
|
||||
hideTabView(function () {
|
||||
assertNumberOfGroupItems(1);
|
||||
assertGroupItemExists(groupItem);
|
||||
gBrowser.unpinTab(gBrowser.selectedTab);
|
||||
win.gBrowser.unpinTab(win.gBrowser.selectedTab);
|
||||
next();
|
||||
});
|
||||
}, win);
|
||||
}
|
||||
|
||||
// setup: 1 pinned tab
|
||||
// action: exit panorama
|
||||
// expected: new blank group is created
|
||||
let testPinnedTab2 = function () {
|
||||
gBrowser.pinTab(gBrowser.selectedTab);
|
||||
win.gBrowser.pinTab(win.gBrowser.selectedTab);
|
||||
getGroupItem(0).close();
|
||||
|
||||
hideTabView(function () {
|
||||
assertNumberOfTabs(1);
|
||||
assertNumberOfGroupItems(1);
|
||||
gBrowser.unpinTab(gBrowser.selectedTab);
|
||||
win.gBrowser.unpinTab(win.gBrowser.selectedTab);
|
||||
next();
|
||||
});
|
||||
}, win);
|
||||
}
|
||||
|
||||
// setup: 1 pinned tab, 1 empty group, 1 non-empty group
|
||||
// action: close non-empty group
|
||||
// expected: nothing should happen
|
||||
let testPinnedTab3 = function () {
|
||||
gBrowser.pinTab(gBrowser.selectedTab);
|
||||
win.gBrowser.pinTab(win.gBrowser.selectedTab);
|
||||
|
||||
let groupItem = getGroupItem(0);
|
||||
let newGroupItem = createGroupItem(1);
|
||||
@ -163,8 +164,8 @@ function test() {
|
||||
assertNumberOfGroupItems(1);
|
||||
assertGroupItemExists(groupItem);
|
||||
|
||||
gBrowser.unpinTab(gBrowser.selectedTab);
|
||||
hideTabView(next);
|
||||
win.gBrowser.unpinTab(win.gBrowser.selectedTab);
|
||||
hideTabView(next, win);
|
||||
});
|
||||
}
|
||||
|
||||
@ -172,7 +173,7 @@ function test() {
|
||||
// action: hide non-empty group, exit panorama
|
||||
// expected: nothing should happen
|
||||
let testPinnedTab4 = function () {
|
||||
gBrowser.pinTab(gBrowser.selectedTab);
|
||||
win.gBrowser.pinTab(win.gBrowser.selectedTab);
|
||||
|
||||
let groupItem = getGroupItem(0);
|
||||
let newGroupItem = createGroupItem(1);
|
||||
@ -183,9 +184,9 @@ function test() {
|
||||
assertNumberOfGroupItems(1);
|
||||
assertGroupItemExists(groupItem);
|
||||
assertGroupItemRemoved(newGroupItem);
|
||||
gBrowser.unpinTab(gBrowser.selectedTab);
|
||||
win.gBrowser.unpinTab(win.gBrowser.selectedTab);
|
||||
next();
|
||||
});
|
||||
}, win);
|
||||
});
|
||||
}
|
||||
|
||||
@ -200,7 +201,7 @@ function test() {
|
||||
closeGroupItem(groupItem, function () {
|
||||
assertNumberOfGroupItems(1);
|
||||
assertGroupItemExists(newGroupItem);
|
||||
whenTabViewIsHidden(next);
|
||||
whenTabViewIsHidden(next, win);
|
||||
});
|
||||
}
|
||||
|
||||
@ -218,7 +219,7 @@ function test() {
|
||||
assertGroupItemRemoved(groupItem);
|
||||
assertGroupItemExists(newGroupItem);
|
||||
next();
|
||||
});
|
||||
}, win);
|
||||
});
|
||||
}
|
||||
|
||||
@ -235,7 +236,7 @@ function test() {
|
||||
assertNumberOfGroupItems(1);
|
||||
assertGroupItemRemoved(groupItem);
|
||||
assertGroupItemExists(hiddenGroupItem);
|
||||
hideTabView(next);
|
||||
hideTabView(next, win);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -255,7 +256,7 @@ function test() {
|
||||
assertGroupItemRemoved(groupItem);
|
||||
assertGroupItemRemoved(hiddenGroupItem);
|
||||
next();
|
||||
});
|
||||
}, win);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -279,8 +280,9 @@ function test() {
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
showTabView(function () {
|
||||
cw = TabView.getContentWindow();
|
||||
newWindowWithTabView(window => {
|
||||
win = window;
|
||||
cw = win.TabView.getContentWindow();
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
@ -3,11 +3,12 @@
|
||||
|
||||
function test() {
|
||||
let cw;
|
||||
let win;
|
||||
let groupItemId;
|
||||
let prefix = 'start';
|
||||
|
||||
let assertTabViewIsHidden = function () {
|
||||
ok(!TabView.isVisible(), prefix + ': tabview is hidden');
|
||||
ok(!win.TabView.isVisible(), prefix + ': tabview is hidden');
|
||||
}
|
||||
|
||||
let assertNumberOfGroups = function (num) {
|
||||
@ -15,11 +16,11 @@ function test() {
|
||||
}
|
||||
|
||||
let assertNumberOfTabs = function (num) {
|
||||
is(gBrowser.visibleTabs.length, num, prefix + ': there should be ' + num + ' tabs');
|
||||
is(win.gBrowser.visibleTabs.length, num, prefix + ': there should be ' + num + ' tabs');
|
||||
}
|
||||
|
||||
let assertNumberOfPinnedTabs = function (num) {
|
||||
is(gBrowser._numPinnedTabs, num, prefix + ': there should be ' + num + ' pinned tabs');
|
||||
is(win.gBrowser._numPinnedTabs, num, prefix + ': there should be ' + num + ' pinned tabs');
|
||||
}
|
||||
|
||||
let assertGroupItemPreserved = function () {
|
||||
@ -34,7 +35,7 @@ function test() {
|
||||
}
|
||||
|
||||
let createTab = function (url) {
|
||||
return gBrowser.loadOneTab(url || 'http://mochi.test:8888/', {inBackground: true});
|
||||
return win.gBrowser.loadOneTab(url || 'http://mochi.test:8888/', {inBackground: true});
|
||||
}
|
||||
|
||||
let createBlankTab = function () {
|
||||
@ -44,7 +45,7 @@ function test() {
|
||||
let finishTest = function () {
|
||||
prefix = 'finish';
|
||||
assertValidPrerequisites();
|
||||
finish();
|
||||
promiseWindowClosed(win).then(finish);
|
||||
}
|
||||
|
||||
let testUndoCloseWithSelectedBlankTab = function () {
|
||||
@ -53,7 +54,7 @@ function test() {
|
||||
assertNumberOfTabs(2);
|
||||
|
||||
afterAllTabsLoaded(function () {
|
||||
gBrowser.removeTab(tab);
|
||||
win.gBrowser.removeTab(tab);
|
||||
assertNumberOfTabs(1);
|
||||
assertNumberOfPinnedTabs(0);
|
||||
|
||||
@ -63,9 +64,9 @@ function test() {
|
||||
assertGroupItemPreserved();
|
||||
|
||||
createBlankTab();
|
||||
afterAllTabsLoaded(testUndoCloseWithSelectedBlankPinnedTab);
|
||||
});
|
||||
});
|
||||
afterAllTabsLoaded(testUndoCloseWithSelectedBlankPinnedTab, win);
|
||||
}, 0, win);
|
||||
}, win);
|
||||
}
|
||||
|
||||
let testUndoCloseWithSelectedBlankPinnedTab = function () {
|
||||
@ -73,14 +74,8 @@ function test() {
|
||||
assertNumberOfTabs(2);
|
||||
|
||||
afterAllTabsLoaded(function () {
|
||||
gBrowser.removeTab(gBrowser.tabs[0]);
|
||||
gBrowser.pinTab(gBrowser.tabs[0]);
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
let tab = gBrowser.tabs[0];
|
||||
if (tab.pinned)
|
||||
gBrowser.unpinTab(tab);
|
||||
});
|
||||
win.gBrowser.removeTab(win.gBrowser.tabs[0]);
|
||||
win.gBrowser.pinTab(win.gBrowser.tabs[0]);
|
||||
|
||||
assertNumberOfTabs(1);
|
||||
assertNumberOfPinnedTabs(1);
|
||||
@ -91,23 +86,24 @@ function test() {
|
||||
assertGroupItemPreserved();
|
||||
|
||||
createBlankTab();
|
||||
gBrowser.removeTab(gBrowser.tabs[0]);
|
||||
win.gBrowser.removeTab(win.gBrowser.tabs[0]);
|
||||
|
||||
afterAllTabsLoaded(finishTest);
|
||||
});
|
||||
});
|
||||
afterAllTabsLoaded(finishTest, win);
|
||||
}, 0, win);
|
||||
}, win);
|
||||
}
|
||||
|
||||
waitForExplicitFinish();
|
||||
registerCleanupFunction(function () TabView.hide());
|
||||
|
||||
showTabView(function () {
|
||||
newWindowWithTabView(window => {
|
||||
win = window;
|
||||
|
||||
hideTabView(function () {
|
||||
cw = TabView.getContentWindow();
|
||||
cw = win.TabView.getContentWindow();
|
||||
groupItemId = cw.GroupItems.groupItems[0].id;
|
||||
|
||||
assertValidPrerequisites();
|
||||
testUndoCloseWithSelectedBlankTab();
|
||||
});
|
||||
}, win);
|
||||
});
|
||||
}
|
||||
|
@ -18,18 +18,18 @@ let activeGroup;
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
showTabView(function () {
|
||||
contentWindow = TabView.getContentWindow();
|
||||
newWindowWithTabView(win => {
|
||||
contentWindow = win.TabView.getContentWindow();
|
||||
activeGroup = contentWindow.GroupItems.getActiveGroupItem();
|
||||
|
||||
gBrowser.browsers[0].loadURI("data:text/html,<p>test for bug 626455, tab1");
|
||||
win.gBrowser.browsers[0].loadURI("data:text/html,<p>test for bug 626455, tab1");
|
||||
|
||||
let tab = gBrowser.addTab(TEST_URL);
|
||||
afterAllTabsLoaded(() => testStayOnPage(tab));
|
||||
let tab = win.gBrowser.addTab(TEST_URL);
|
||||
afterAllTabsLoaded(() => testStayOnPage(win, tab));
|
||||
});
|
||||
}
|
||||
|
||||
function testStayOnPage(blockingTab) {
|
||||
function testStayOnPage(win, blockingTab) {
|
||||
let browser = blockingTab.linkedBrowser;
|
||||
waitForOnBeforeUnloadDialog(browser, function (btnLeave, btnStay) {
|
||||
// stay on page
|
||||
@ -37,12 +37,12 @@ function testStayOnPage(blockingTab) {
|
||||
|
||||
executeSoon(function () {
|
||||
showTabView(function () {
|
||||
is(gBrowser.tabs.length, 1,
|
||||
is(win.gBrowser.tabs.length, 1,
|
||||
"The total number of tab is 1 when staying on the page");
|
||||
|
||||
// The other initial tab has been closed when trying to close the tab
|
||||
// group. The only tab left is the one with the onbeforeunload dialog.
|
||||
let url = gBrowser.browsers[0].currentURI.spec;
|
||||
let url = win.gBrowser.browsers[0].currentURI.spec;
|
||||
ok(url.contains("onbeforeunload"), "The open tab is the expected one");
|
||||
|
||||
is(contentWindow.GroupItems.getActiveGroupItem(), activeGroup,
|
||||
@ -52,32 +52,32 @@ function testStayOnPage(blockingTab) {
|
||||
"Only one group is open");
|
||||
|
||||
// start the next test
|
||||
testLeavePage(gBrowser.tabs[0]);
|
||||
});
|
||||
testLeavePage(win, win.gBrowser.tabs[0]);
|
||||
}, win);
|
||||
});
|
||||
});
|
||||
|
||||
closeGroupItem(activeGroup);
|
||||
}
|
||||
|
||||
function testLeavePage(blockingTab) {
|
||||
function testLeavePage(win, blockingTab) {
|
||||
let browser = blockingTab.linkedBrowser;
|
||||
waitForOnBeforeUnloadDialog(browser, function (btnLeave, btnStay) {
|
||||
// Leave page
|
||||
btnLeave.click();
|
||||
});
|
||||
|
||||
whenGroupClosed(activeGroup, finishTest);
|
||||
whenGroupClosed(activeGroup, () => finishTest(win));
|
||||
closeGroupItem(activeGroup);
|
||||
}
|
||||
|
||||
function finishTest() {
|
||||
is(gBrowser.tabs.length, 1,
|
||||
function finishTest(win) {
|
||||
is(win.gBrowser.tabs.length, 1,
|
||||
"The total number of tab is 1 after leaving the page");
|
||||
is(contentWindow.TabItems.getItems().length, 1,
|
||||
"The total number of tab items is 1 after leaving the page");
|
||||
|
||||
let location = gBrowser.browsers[0].currentURI.spec;
|
||||
let location = win.gBrowser.browsers[0].currentURI.spec;
|
||||
is(location, BROWSER_NEW_TAB_URL, "The open tab is the expected one");
|
||||
|
||||
isnot(contentWindow.GroupItems.getActiveGroupItem(), activeGroup,
|
||||
@ -88,7 +88,7 @@ function finishTest() {
|
||||
|
||||
contentWindow = null;
|
||||
activeGroup = null;
|
||||
finish();
|
||||
promiseWindowClosed(win).then(finish);
|
||||
}
|
||||
|
||||
// ----------
|
||||
|
@ -4,10 +4,10 @@
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
showTabView(function () {
|
||||
is(gBrowser.tabs.length, 1, "There is only one tab");
|
||||
newWindowWithTabView(win => {
|
||||
is(win.gBrowser.tabs.length, 1, "There is only one tab");
|
||||
|
||||
let tab = gBrowser.tabs[0];
|
||||
let tab = win.gBrowser.tabs[0];
|
||||
let tabItem = tab._tabViewTabItem;
|
||||
ok(tabItem.parent, "The tab item belongs to a group");
|
||||
let groupId = tabItem.parent.id;
|
||||
@ -16,16 +16,16 @@ function test() {
|
||||
|
||||
whenTabViewIsHidden(function() {
|
||||
// a new tab with group should be opened
|
||||
is(gBrowser.tabs.length, 1, "There is still one tab");
|
||||
isnot(gBrowser.selectedTab, tab, "The tab is different");
|
||||
is(win.gBrowser.tabs.length, 1, "There is still one tab");
|
||||
isnot(win.gBrowser.selectedTab, tab, "The tab is different");
|
||||
|
||||
tab = gBrowser.tabs[0];
|
||||
tab = win.gBrowser.tabs[0];
|
||||
tabItem = tab._tabViewTabItem;
|
||||
ok(tabItem.parent, "This new tab item belongs to a group");
|
||||
|
||||
is(tabItem.parent.id, groupId, "The group is different");
|
||||
|
||||
finish();
|
||||
});
|
||||
promiseWindowClosed(win).then(finish);
|
||||
}, win);
|
||||
});
|
||||
}
|
||||
|
@ -4,44 +4,38 @@
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.addTab("http://example.com/");
|
||||
gBrowser.addTab("http://example.com/");
|
||||
newWindowWithTabView(win => {
|
||||
win.gBrowser.addTab("http://example.com/");
|
||||
win.gBrowser.addTab("http://example.com/");
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
while (gBrowser.tabs.length > 1)
|
||||
gBrowser.removeTab(gBrowser.tabs[1]);
|
||||
hideTabView();
|
||||
})
|
||||
|
||||
afterAllTabsLoaded(function() {
|
||||
showTabView(function() {
|
||||
let cw = TabView.getContentWindow();
|
||||
afterAllTabsLoaded(function() {
|
||||
let cw = win.TabView.getContentWindow();
|
||||
|
||||
let groupItemOne = cw.GroupItems.groupItems[0];
|
||||
is(groupItemOne.getChildren().length, 3, "The number of tabs in group one is 3");
|
||||
|
||||
|
||||
// create a group with a blank tab
|
||||
let groupItemTwo = createGroupItemWithBlankTabs(window, 400, 400, 40, 1);
|
||||
let groupItemTwo = createGroupItemWithBlankTabs(win, 400, 400, 40, 1);
|
||||
is(groupItemTwo.getChildren().length, 1, "The number of tabs in group two is 1");
|
||||
|
||||
cw.UI.setActive(groupItemOne);
|
||||
|
||||
moveTabToAnotherGroup(groupItemOne.getChild(2).tab, groupItemOne, groupItemTwo, function() {
|
||||
moveTabToAnotherGroup(groupItemOne.getChild(1).tab, groupItemOne, groupItemTwo, function() {
|
||||
moveTabToAnotherGroup(win, groupItemOne.getChild(2).tab, groupItemOne, groupItemTwo, function() {
|
||||
moveTabToAnotherGroup(win, groupItemOne.getChild(1).tab, groupItemOne, groupItemTwo, function() {
|
||||
groupItemOne.close();
|
||||
hideTabView(finish);
|
||||
promiseWindowClosed(win).then(finish);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function moveTabToAnotherGroup(targetTab, groupItemOne, groupItemTwo, callback) {
|
||||
function moveTabToAnotherGroup(win, targetTab, groupItemOne, groupItemTwo, callback) {
|
||||
hideTabView(function() {
|
||||
let tabCountInGroupItemOne = groupItemOne.getChildren().length;
|
||||
let tabCountInGroupItemTwo = groupItemTwo.getChildren().length;
|
||||
|
||||
TabView.moveTabTo(targetTab, groupItemTwo.id);
|
||||
win.TabView.moveTabTo(targetTab, groupItemTwo.id);
|
||||
|
||||
showTabView(function() {
|
||||
is(groupItemOne.getChildren().length, --tabCountInGroupItemOne, "The number of tab items in group one is decreased");
|
||||
@ -49,6 +43,6 @@ function moveTabToAnotherGroup(targetTab, groupItemOne, groupItemTwo, callback)
|
||||
is(groupItemTwo.getChild(tabCountInGroupItemTwo-1).tab, targetTab, "The last tab is the moved tab");
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}, win);
|
||||
}, win);
|
||||
}
|
||||
|
@ -509,12 +509,13 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, {
|
||||
if (stackframeItem) {
|
||||
// The container is not empty and an actual item was selected.
|
||||
let depth = stackframeItem.attachment.depth;
|
||||
DebuggerController.StackFrames.selectFrame(depth);
|
||||
|
||||
// Mirror the selected item in the classic list.
|
||||
this.suppressSelectionEvents = true;
|
||||
this._mirror.selectedItem = e => e.attachment.depth == depth;
|
||||
this.suppressSelectionEvents = false;
|
||||
|
||||
DebuggerController.StackFrames.selectFrame(depth);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -749,10 +749,11 @@ function openVarPopup(aPanel, aCoords, aWaitForFetchedProperties) {
|
||||
let fetchedProperties = aWaitForFetchedProperties
|
||||
? waitForDebuggerEvents(aPanel, events.FETCHED_BUBBLE_PROPERTIES)
|
||||
: promise.resolve(null);
|
||||
let updatedFrame = waitForDebuggerEvents(aPanel, events.FETCHED_SCOPES);
|
||||
|
||||
let { left, top } = editor.getCoordsFromPosition(aCoords);
|
||||
bubble._findIdentifier(left, top);
|
||||
return promise.all([popupShown, fetchedProperties]).then(waitForTick);
|
||||
return promise.all([popupShown, fetchedProperties, updatedFrame]).then(waitForTick);
|
||||
}
|
||||
|
||||
// Simulates the mouse hovering a variable in the debugger
|
||||
|
@ -1702,13 +1702,17 @@ Experiments.ExperimentEntry.prototype = {
|
||||
}
|
||||
|
||||
this._enabled = false;
|
||||
|
||||
let changes = yield this.reconcileAddonState();
|
||||
let now = this._policy.now();
|
||||
this._lastChangedDate = now;
|
||||
this._endDate = now;
|
||||
|
||||
let changes = yield this.reconcileAddonState();
|
||||
this._logTermination(terminationKind, terminationReason);
|
||||
|
||||
if (terminationKind == TELEMETRY_LOG.TERMINATION.ADDON_UNINSTALLED) {
|
||||
changes |= this.ADDON_CHANGE_UNINSTALL;
|
||||
}
|
||||
|
||||
return changes;
|
||||
}),
|
||||
|
||||
|
@ -11,6 +11,7 @@ Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://services-sync/healthreport.jsm", this);
|
||||
Cu.import("resource://testing-common/services/healthreport/utils.jsm", this);
|
||||
Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
|
||||
Cu.import("resource://testing-common/AddonManagerTesting.jsm");
|
||||
|
||||
const PREF_EXPERIMENTS_ENABLED = "experiments.enabled";
|
||||
const PREF_LOGGING_LEVEL = "experiments.logging.level";
|
||||
|
@ -25,6 +25,24 @@ let gManifestObject = null;
|
||||
let gManifestHandlerURI = null;
|
||||
let gTimerScheduleOffset = -1;
|
||||
|
||||
function uninstallExperimentAddons() {
|
||||
return Task.spawn(function* () {
|
||||
let addons = yield getExperimentAddons();
|
||||
for (let a of addons) {
|
||||
yield AddonTestUtils.uninstallAddonByID(a.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testCleanup(experimentsInstance) {
|
||||
return Task.spawn(function* () {
|
||||
yield experimentsInstance.uninit();
|
||||
yield removeCacheFile();
|
||||
yield uninstallExperimentAddons();
|
||||
restartManager();
|
||||
});
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
@ -256,8 +274,7 @@ add_task(function* test_getExperiments() {
|
||||
// Cleanup.
|
||||
|
||||
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// Test that we handle the experiments addon already being
|
||||
@ -337,8 +354,7 @@ add_task(function* test_addonAlreadyInstalled() {
|
||||
// Cleanup.
|
||||
|
||||
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
add_task(function* test_lastActiveToday() {
|
||||
@ -364,8 +380,7 @@ add_task(function* test_lastActiveToday() {
|
||||
Assert.ok(lastActive, "Have a last active experiment");
|
||||
Assert.equal(lastActive, e[0], "Last active object is expected.");
|
||||
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// Test explicitly disabling experiments.
|
||||
@ -450,10 +465,7 @@ add_task(function* test_disableExperiment() {
|
||||
"Property " + k + " should match reference data.");
|
||||
}
|
||||
|
||||
// Cleanup.
|
||||
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
add_task(function* test_disableExperimentsFeature() {
|
||||
@ -541,10 +553,7 @@ add_task(function* test_disableExperimentsFeature() {
|
||||
"Property " + k + " should match reference data.");
|
||||
}
|
||||
|
||||
// Cleanup.
|
||||
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// Test that after a failed experiment install:
|
||||
@ -676,11 +685,7 @@ add_task(function* test_installFailure() {
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup.
|
||||
|
||||
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// Test that after an experiment was disabled by user action,
|
||||
@ -779,8 +784,7 @@ add_task(function* test_userDisabledAndUpdated() {
|
||||
// Cleanup.
|
||||
|
||||
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// Test that changing the hash for an active experiments triggers an
|
||||
@ -871,8 +875,7 @@ add_task(function* test_updateActiveExperiment() {
|
||||
// Cleanup.
|
||||
|
||||
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// Tests that setting the disable flag for an active experiment
|
||||
@ -963,8 +966,7 @@ add_task(function* test_disableActiveExperiment() {
|
||||
// Cleanup.
|
||||
|
||||
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// Test that:
|
||||
@ -1044,8 +1046,7 @@ add_task(function* test_freezePendingExperiment() {
|
||||
// Cleanup.
|
||||
|
||||
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// Test that setting the frozen flag for an active experiment doesn't
|
||||
@ -1125,8 +1126,7 @@ add_task(function* test_freezeActiveExperiment() {
|
||||
// Cleanup.
|
||||
|
||||
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// Test that removing an active experiment from the manifest doesn't
|
||||
@ -1218,8 +1218,7 @@ add_task(function* test_removeActiveExperiment() {
|
||||
// Cleanup.
|
||||
|
||||
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// Test that we correctly handle experiment start & install failures.
|
||||
@ -1274,8 +1273,7 @@ add_task(function* test_invalidUrl() {
|
||||
// Cleanup.
|
||||
|
||||
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// Test that we handle it properly when active experiment addons are being
|
||||
@ -1353,8 +1351,7 @@ add_task(function* test_unexpectedUninstall() {
|
||||
// Cleanup.
|
||||
|
||||
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// If the Addon Manager knows of an experiment that we don't, it should get
|
||||
@ -1387,8 +1384,7 @@ add_task(function* testUnknownExperimentsUninstalled() {
|
||||
addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "Experiment 1 was uninstalled.");
|
||||
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// If someone else installs an experiment add-on, we detect and stop that.
|
||||
@ -1415,8 +1411,7 @@ add_task(function* testForeignExperimentInstall() {
|
||||
addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "Add-on install should have been cancelled.");
|
||||
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// Experiment add-ons will be disabled after Addon Manager restarts. Ensure
|
||||
@ -1441,7 +1436,7 @@ add_task(function* testEnabledAfterRestart() {
|
||||
};
|
||||
|
||||
let addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "Precondition: No experimenta add-ons installed.");
|
||||
Assert.equal(addons.length, 0, "Precondition: No experiment add-ons installed.");
|
||||
|
||||
yield experiments.updateManifest();
|
||||
let fromManifest = yield experiments.getExperiments();
|
||||
@ -1464,6 +1459,65 @@ add_task(function* testEnabledAfterRestart() {
|
||||
yield experiments.updateManifest();
|
||||
Assert.ok(addons[0].isActive, "It activates when the manifest is evaluated.");
|
||||
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
||||
// Test coverage for an add-on uninstall disabling the experiment and that it stays
|
||||
// disabled over restarts.
|
||||
add_task(function* test_foreignUninstallAndRestart() {
|
||||
let experiments = new Experiments.Experiments(gPolicy);
|
||||
|
||||
gManifestObject = {
|
||||
"version": 1,
|
||||
experiments: [
|
||||
{
|
||||
id: EXPERIMENT1_ID,
|
||||
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
|
||||
xpiHash: EXPERIMENT1_XPI_SHA1,
|
||||
startTime: gPolicy.now().getTime() / 1000 - 60,
|
||||
endTime: gPolicy.now().getTime() / 1000 + 60,
|
||||
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
appName: ["XPCShell"],
|
||||
channel: ["nightly"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "Precondition: No experiment add-ons installed.");
|
||||
|
||||
yield experiments.updateManifest();
|
||||
let experimentList = yield experiments.getExperiments();
|
||||
Assert.equal(experimentList.length, 1, "A single experiment is known.");
|
||||
|
||||
addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 1, "A single experiment add-on is installed.");
|
||||
Assert.ok(addons[0].isActive, "That experiment is active.");
|
||||
|
||||
yield AddonTestUtils.uninstallAddonByID(EXPERIMENT1_ID);
|
||||
yield experiments._mainTask;
|
||||
|
||||
let addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "Experiment add-on should have been removed.");
|
||||
|
||||
experimentList = yield experiments.getExperiments();
|
||||
Assert.equal(experimentList.length, 1, "A single experiment is known.");
|
||||
Assert.equal(experimentList[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
|
||||
Assert.ok(!experimentList[0].active, "Experiment 1 should not be active anymore.");
|
||||
|
||||
// Fake restart behaviour.
|
||||
experiments.uninit();
|
||||
restartManager();
|
||||
experiments = new Experiments.Experiments(gPolicy);
|
||||
yield experiments.updateManifest();
|
||||
|
||||
let addons = yield getExperimentAddons();
|
||||
Assert.equal(addons.length, 0, "No experiment add-ons installed.");
|
||||
|
||||
experimentList = yield experiments.getExperiments();
|
||||
Assert.equal(experimentList.length, 1, "A single experiment is known.");
|
||||
Assert.equal(experimentList[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
|
||||
Assert.ok(!experimentList[0].active, "Experiment 1 should not be active.");
|
||||
|
||||
yield testCleanup(experiments);
|
||||
});
|
||||
|
@ -1265,7 +1265,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
background-clip: padding-box;
|
||||
padding-left: 4px;
|
||||
padding-left: 3px;
|
||||
border-radius: 2.5px 0 0 2.5px;
|
||||
border-width: 0 8px 0 0;
|
||||
border-style: solid;
|
||||
@ -1275,6 +1275,10 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box {
|
||||
padding-left: 7px;
|
||||
}
|
||||
|
||||
#notification-popup-box:not([hidden]) + #identity-box {
|
||||
-moz-padding-start: 10px;
|
||||
border-radius: 0;
|
||||
|
@ -3384,7 +3384,7 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
||||
}
|
||||
}
|
||||
|
||||
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar > #notification-popup-box {
|
||||
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box {
|
||||
padding-left: 7px;
|
||||
}
|
||||
|
||||
|
@ -2274,8 +2274,8 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
|
||||
-moz-margin-end: -8px;
|
||||
}
|
||||
|
||||
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box {
|
||||
padding-left: 5px;
|
||||
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box {
|
||||
padding-left: 7px;
|
||||
}
|
||||
|
||||
#notification-popup-box:-moz-locale-dir(rtl),
|
||||
|
@ -418,6 +418,31 @@ abstract public class BrowserApp extends GeckoApp
|
||||
});
|
||||
}
|
||||
|
||||
private void handleReaderFaviconRequest(final String url) {
|
||||
(new UiAsyncTask<Void, Void, String>(ThreadUtils.getBackgroundHandler()) {
|
||||
@Override
|
||||
public String doInBackground(Void... params) {
|
||||
return Favicons.getFaviconURLForPageURL(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(String faviconUrl) {
|
||||
JSONObject args = new JSONObject();
|
||||
|
||||
if (faviconUrl != null) {
|
||||
try {
|
||||
args.put("url", url);
|
||||
args.put("faviconUrl", faviconUrl);
|
||||
} catch (JSONException e) {
|
||||
Log.w(LOGTAG, "Error building JSON favicon arguments.", e);
|
||||
}
|
||||
}
|
||||
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:FaviconReturn", args.toString()));
|
||||
}
|
||||
}).execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
mAboutHomeStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_ABOUTHOME");
|
||||
@ -497,6 +522,11 @@ abstract public class BrowserApp extends GeckoApp
|
||||
registerEventListener("Accounts:Create");
|
||||
registerEventListener("Accounts:Exist");
|
||||
registerEventListener("Prompt:ShowTop");
|
||||
registerEventListener("Reader:ListStatusRequest");
|
||||
registerEventListener("Reader:Added");
|
||||
registerEventListener("Reader:Removed");
|
||||
registerEventListener("Reader:Share");
|
||||
registerEventListener("Reader:FaviconRequest");
|
||||
|
||||
Distribution.init(this);
|
||||
JavaAddonManager.getInstance().init(getApplicationContext());
|
||||
@ -843,6 +873,11 @@ abstract public class BrowserApp extends GeckoApp
|
||||
unregisterEventListener("Menu:Update");
|
||||
unregisterEventListener("Accounts:Create");
|
||||
unregisterEventListener("Accounts:Exist");
|
||||
unregisterEventListener("Reader:ListStatusRequest");
|
||||
unregisterEventListener("Reader:Added");
|
||||
unregisterEventListener("Reader:Removed");
|
||||
unregisterEventListener("Reader:Share");
|
||||
unregisterEventListener("Reader:FaviconRequest");
|
||||
|
||||
if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
|
||||
NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
|
||||
@ -1206,6 +1241,9 @@ abstract public class BrowserApp extends GeckoApp
|
||||
final String url = message.getString("url");
|
||||
GeckoAppShell.openUriExternal(url, "text/plain", "", "",
|
||||
Intent.ACTION_SEND, title);
|
||||
} else if (event.equals("Reader:FaviconRequest")) {
|
||||
final String url = message.getString("url");
|
||||
handleReaderFaviconRequest(url);
|
||||
} else if (event.equals("Settings:Show")) {
|
||||
// null strings return "null" (http://code.google.com/p/android/issues/detail?id=13830)
|
||||
String resource = null;
|
||||
|
@ -496,31 +496,6 @@ public abstract class GeckoApp
|
||||
outState.putString(SAVED_STATE_PRIVATE_SESSION, mPrivateBrowsingSession);
|
||||
}
|
||||
|
||||
void handleFaviconRequest(final String url) {
|
||||
(new UiAsyncTask<Void, Void, String>(ThreadUtils.getBackgroundHandler()) {
|
||||
@Override
|
||||
public String doInBackground(Void... params) {
|
||||
return Favicons.getFaviconURLForPageURL(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(String faviconUrl) {
|
||||
JSONObject args = new JSONObject();
|
||||
|
||||
if (faviconUrl != null) {
|
||||
try {
|
||||
args.put("url", url);
|
||||
args.put("faviconUrl", faviconUrl);
|
||||
} catch (JSONException e) {
|
||||
Log.w(LOGTAG, "Error building JSON favicon arguments.", e);
|
||||
}
|
||||
}
|
||||
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:FaviconReturn", args.toString()));
|
||||
}
|
||||
}).execute();
|
||||
}
|
||||
|
||||
void handleClearHistory() {
|
||||
BrowserDB.clearHistory(getContentResolver());
|
||||
}
|
||||
@ -565,9 +540,6 @@ public abstract class GeckoApp
|
||||
// generic log listener
|
||||
final String msg = message.getString("msg");
|
||||
Log.d(LOGTAG, "Log: " + msg);
|
||||
} else if (event.equals("Reader:FaviconRequest")) {
|
||||
final String url = message.getString("url");
|
||||
handleFaviconRequest(url);
|
||||
} else if (event.equals("Gecko:DelayedStartup")) {
|
||||
ThreadUtils.postToBackgroundThread(new UninstallListener.DelayedStartupTask(this));
|
||||
} else if (event.equals("Gecko:Ready")) {
|
||||
@ -1573,11 +1545,6 @@ public abstract class GeckoApp
|
||||
|
||||
//register for events
|
||||
registerEventListener("log");
|
||||
registerEventListener("Reader:ListStatusRequest");
|
||||
registerEventListener("Reader:Added");
|
||||
registerEventListener("Reader:Removed");
|
||||
registerEventListener("Reader:Share");
|
||||
registerEventListener("Reader:FaviconRequest");
|
||||
registerEventListener("onCameraCapture");
|
||||
registerEventListener("Gecko:Ready");
|
||||
registerEventListener("Gecko:DelayedStartup");
|
||||
@ -2114,11 +2081,6 @@ public abstract class GeckoApp
|
||||
public void onDestroy()
|
||||
{
|
||||
unregisterEventListener("log");
|
||||
unregisterEventListener("Reader:ListStatusRequest");
|
||||
unregisterEventListener("Reader:Added");
|
||||
unregisterEventListener("Reader:Removed");
|
||||
unregisterEventListener("Reader:Share");
|
||||
unregisterEventListener("Reader:FaviconRequest");
|
||||
unregisterEventListener("onCameraCapture");
|
||||
unregisterEventListener("Gecko:Ready");
|
||||
unregisterEventListener("Gecko:DelayedStartup");
|
||||
|
@ -29,13 +29,19 @@ public interface TelemetryContract {
|
||||
|
||||
// Set default panel.
|
||||
public static final String PANEL_SET_DEFAULT = "setdefault.1";
|
||||
|
||||
// Sanitizing private data.
|
||||
public static final String SANITIZE = "sanitize.1";
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds event methods. Intended for use in
|
||||
* Telemetry.sendUIEvent() as the "method" parameter.
|
||||
*/
|
||||
public interface Method {}
|
||||
public interface Method {
|
||||
// Action triggered from a dialog.
|
||||
public static final String DIALOG = "dialog";
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds session names. Intended for use with
|
||||
|
@ -7,6 +7,8 @@ package org.mozilla.gecko.preferences;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@ -19,11 +21,8 @@ class PrivateDataPreference extends MultiChoicePreference {
|
||||
private static final String LOGTAG = "GeckoPrivateDataPreference";
|
||||
private static final String PREF_KEY_PREFIX = "private.data.";
|
||||
|
||||
private Context mContext;
|
||||
|
||||
public PrivateDataPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -33,6 +32,8 @@ class PrivateDataPreference extends MultiChoicePreference {
|
||||
if (!positiveResult)
|
||||
return;
|
||||
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SANITIZE, TelemetryContract.Method.DIALOG);
|
||||
|
||||
CharSequence keys[] = getEntryKeys();
|
||||
boolean values[] = getValues();
|
||||
JSONObject json = new JSONObject();
|
||||
|
@ -118,6 +118,8 @@ skip-if = android_version == "10"
|
||||
[testSharedPreferences]
|
||||
[testSimpleDiscovery]
|
||||
[testUITelemetry]
|
||||
# disabled on 2.2, see bug 993813
|
||||
skip-if = android_version == "8"
|
||||
[testVideoDiscovery]
|
||||
|
||||
# Used for Talos, please don't use in mochitest
|
||||
|
@ -74,6 +74,10 @@ public final class HardwareUtils {
|
||||
}
|
||||
|
||||
public static boolean isTelevision() {
|
||||
if (Build.VERSION.SDK_INT < 16) {
|
||||
// System feature not supported before Jelly Bean.
|
||||
return false;
|
||||
}
|
||||
if (sIsTelevision == null) {
|
||||
sIsTelevision = sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION);
|
||||
}
|
||||
|
@ -29,6 +29,12 @@ var MemoryObserver = {
|
||||
}
|
||||
}
|
||||
Telemetry.addData("FENNEC_LOWMEM_TAB_COUNT", tabs.length);
|
||||
|
||||
// Change some preferences temporarily for only this session
|
||||
let defaults = Services.prefs.getDefaultBranch(null);
|
||||
|
||||
// Reduce the amount of decoded image data we keep around
|
||||
defaults.setIntPref("image.mem.max_decoded_image_kb", 0);
|
||||
},
|
||||
|
||||
zombify: function(tab) {
|
||||
|
@ -69,52 +69,9 @@ this.CommonUtils = {
|
||||
return true;
|
||||
},
|
||||
|
||||
exceptionStr: function exceptionStr(e) {
|
||||
if (!e) {
|
||||
return "" + e;
|
||||
}
|
||||
let message = e.message ? e.message : e;
|
||||
return message + " " + CommonUtils.stackTrace(e);
|
||||
},
|
||||
|
||||
stackTrace: function stackTrace(e) {
|
||||
// Wrapped nsIException
|
||||
if (e.location) {
|
||||
let frame = e.location;
|
||||
let output = [];
|
||||
while (frame) {
|
||||
// Works on frames or exceptions, munges file:// URIs to shorten the paths
|
||||
// FIXME: filename munging is sort of hackish, might be confusing if
|
||||
// there are multiple extensions with similar filenames
|
||||
let str = "<file:unknown>";
|
||||
|
||||
let file = frame.filename || frame.fileName;
|
||||
if (file){
|
||||
str = file.replace(/^(?:chrome|file):.*?([^\/\.]+\.\w+)$/, "$1");
|
||||
}
|
||||
|
||||
if (frame.lineNumber){
|
||||
str += ":" + frame.lineNumber;
|
||||
}
|
||||
if (frame.name){
|
||||
str = frame.name + "()@" + str;
|
||||
}
|
||||
|
||||
if (str){
|
||||
output.push(str);
|
||||
}
|
||||
frame = frame.caller;
|
||||
}
|
||||
return "Stack trace: " + output.join(" < ");
|
||||
}
|
||||
// Standard JS exception
|
||||
if (e.stack){
|
||||
return "JS Stack trace: " + e.stack.trim().replace(/\n/g, " < ").
|
||||
replace(/@[^@]*?([^\/\.]+\.\w+:)/g, "@$1");
|
||||
}
|
||||
|
||||
return "No traceback available";
|
||||
},
|
||||
// Import these from Log.jsm for backward compatibility
|
||||
exceptionStr: Log.exceptionStr,
|
||||
stackTrace: Log.stackTrace,
|
||||
|
||||
/**
|
||||
* Encode byte string as base64URL (RFC 4648).
|
||||
|
@ -296,8 +296,9 @@ add_task(function test_category_manager_registration_error() {
|
||||
do_check_eq(errorCount, 1);
|
||||
|
||||
let msg = yield deferred.promise;
|
||||
do_check_true(msg.contains("Provider error: DummyThrowOnInitProvider: " +
|
||||
"Error registering provider from category manager: Dummy Error"));
|
||||
do_check_true(msg.contains("Provider error: DummyThrowOnInitProvider: "
|
||||
+ "Error registering provider from category manager: "
|
||||
+ "Error: Dummy Error"));
|
||||
|
||||
yield storage.close();
|
||||
});
|
||||
@ -322,7 +323,7 @@ add_task(function test_pull_only_registration_error() {
|
||||
|
||||
let msg = yield deferred.promise;
|
||||
do_check_true(msg.contains("Provider error: DummyPullOnlyThrowsOnInitProvider: " +
|
||||
"Error registering pull-only provider: Dummy Error"));
|
||||
"Error registering pull-only provider: Error: Dummy Error"));
|
||||
|
||||
yield storage.close();
|
||||
});
|
||||
@ -350,7 +351,7 @@ add_task(function test_error_during_shutdown() {
|
||||
do_check_eq(errorCount, 1);
|
||||
let msg = yield deferred.promise;
|
||||
do_check_true(msg.contains("Provider error: DummyThrowOnShutdownProvider: " +
|
||||
"Error when shutting down provider: Dummy shutdown error"));
|
||||
"Error when shutting down provider: Error: Dummy shutdown error"));
|
||||
|
||||
yield storage.close();
|
||||
});
|
||||
|
@ -102,8 +102,13 @@ checkAndLogStatementPerformance(sqlite3_stmt *aStatement)
|
||||
else
|
||||
message.Append(" sort operations have ");
|
||||
message.Append("occurred for the SQL statement '");
|
||||
#ifdef MOZ_STORAGE_SORTWARNING_SQL_DUMP
|
||||
message.Append("SQL command: ");
|
||||
message.Append(sql);
|
||||
#else
|
||||
nsPrintfCString address("0x%p", aStatement);
|
||||
message.Append(address);
|
||||
#endif
|
||||
message.Append("'. See https://developer.mozilla.org/En/Storage/Warnings "
|
||||
"details.");
|
||||
NS_WARNING(message.get());
|
||||
|
@ -6,6 +6,18 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const Debugger = require("Debugger");
|
||||
const Services = require("Services");
|
||||
const { Cc, Ci, Cu, components } = require("chrome");
|
||||
const { ActorPool } = require("devtools/server/actors/common");
|
||||
const { DebuggerServer } = require("devtools/server/main");
|
||||
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
const { dbg_assert, dumpn } = DevToolsUtils;
|
||||
const { SourceMapConsumer, SourceMapGenerator } = require("source-map");
|
||||
const { all, defer, resolve } = promise;
|
||||
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
let B2G_ID = "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}";
|
||||
|
||||
let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
|
||||
@ -38,7 +50,7 @@ function mapURIToAddonID(uri, id) {
|
||||
return addonManager.mapURIToAddonID(uri, id);
|
||||
}
|
||||
catch (e) {
|
||||
DevtoolsUtils.reportException("mapURIToAddonID", e);
|
||||
DevToolsUtils.reportException("mapURIToAddonID", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -313,6 +325,8 @@ BreakpointStore.prototype = {
|
||||
},
|
||||
};
|
||||
|
||||
exports.BreakpointStore = BreakpointStore;
|
||||
|
||||
/**
|
||||
* Manages pushing event loops and automatically pops and exits them in the
|
||||
* correct order as they are resolved.
|
||||
@ -2398,6 +2412,7 @@ ThreadActor.prototype.requestTypes = {
|
||||
"prototypesAndProperties": ThreadActor.prototype.onPrototypesAndProperties
|
||||
};
|
||||
|
||||
exports.ThreadActor = ThreadActor;
|
||||
|
||||
/**
|
||||
* Creates a PauseActor.
|
||||
@ -3461,6 +3476,7 @@ ObjectActor.prototype.requestTypes = {
|
||||
"scope": ObjectActor.prototype.onScope,
|
||||
};
|
||||
|
||||
exports.ObjectActor = ObjectActor;
|
||||
|
||||
/**
|
||||
* Functions for adding information to ObjectActor grips for the purpose of
|
||||
@ -4236,6 +4252,7 @@ LongStringActor.prototype.requestTypes = {
|
||||
"release": LongStringActor.prototype.onRelease
|
||||
};
|
||||
|
||||
exports.LongStringActor = LongStringActor;
|
||||
|
||||
/**
|
||||
* Creates an actor for the specified stack frame.
|
||||
@ -4647,6 +4664,8 @@ EnvironmentActor.prototype.requestTypes = {
|
||||
"bindings": EnvironmentActor.prototype.onBindings
|
||||
};
|
||||
|
||||
exports.EnvironmentActor = EnvironmentActor;
|
||||
|
||||
/**
|
||||
* Override the toString method in order to get more meaningful script output
|
||||
* for debugging the debugger.
|
||||
@ -4748,6 +4767,8 @@ update(ChromeDebuggerActor.prototype, {
|
||||
}
|
||||
});
|
||||
|
||||
exports.ChromeDebuggerActor = ChromeDebuggerActor;
|
||||
|
||||
/**
|
||||
* Creates an actor for handling add-on debugging. AddonThreadActor is
|
||||
* a thin wrapper over ThreadActor.
|
||||
@ -4923,6 +4944,8 @@ update(AddonThreadActor.prototype.requestTypes, {
|
||||
"attach": AddonThreadActor.prototype.onAttach
|
||||
});
|
||||
|
||||
exports.AddonThreadActor = AddonThreadActor;
|
||||
|
||||
/**
|
||||
* Manages the sources for a thread. Handles source maps, locations in the
|
||||
* sources, etc for ThreadActors.
|
||||
@ -5285,6 +5308,8 @@ ThreadSources.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
exports.ThreadSources = ThreadSources;
|
||||
|
||||
// Utility functions.
|
||||
|
||||
// TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when it is
|
||||
@ -5393,7 +5418,7 @@ function fetch(aURL, aOptions={ loadFromCache: true }) {
|
||||
case "resource":
|
||||
try {
|
||||
NetUtil.asyncFetch(url, function onFetch(aStream, aStatus, aRequest) {
|
||||
if (!Components.isSuccessCode(aStatus)) {
|
||||
if (!components.isSuccessCode(aStatus)) {
|
||||
deferred.reject(new Error("Request failed with status code = "
|
||||
+ aStatus
|
||||
+ " after NetUtil.asyncFetch for url = "
|
||||
@ -5424,7 +5449,7 @@ function fetch(aURL, aOptions={ loadFromCache: true }) {
|
||||
let chunks = [];
|
||||
let streamListener = {
|
||||
onStartRequest: function(aRequest, aContext, aStatusCode) {
|
||||
if (!Components.isSuccessCode(aStatusCode)) {
|
||||
if (!components.isSuccessCode(aStatusCode)) {
|
||||
deferred.reject(new Error("Request failed with status code = "
|
||||
+ aStatusCode
|
||||
+ " in onStartRequest handler for url = "
|
||||
@ -5435,7 +5460,7 @@ function fetch(aURL, aOptions={ loadFromCache: true }) {
|
||||
chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
|
||||
},
|
||||
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
||||
if (!Components.isSuccessCode(aStatusCode)) {
|
||||
if (!components.isSuccessCode(aStatusCode)) {
|
||||
deferred.reject(new Error("Request failed with status code = "
|
||||
+ aStatusCode
|
||||
+ " in onStopRequest handler for url = "
|
||||
@ -5600,3 +5625,15 @@ function getInnerId(window) {
|
||||
return window.QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
|
||||
};
|
||||
|
||||
exports.register = function(handle) {
|
||||
ThreadActor.breakpointStore = new BreakpointStore();
|
||||
ThreadSources._blackBoxedSources = new Set(["self-hosted"]);
|
||||
ThreadSources._prettyPrintedSources = new Map();
|
||||
};
|
||||
|
||||
exports.unregister = function(handle) {
|
||||
ThreadActor.breakpointStore = null;
|
||||
ThreadSources._blackBoxedSources.clear();
|
||||
ThreadSources._prettyPrintedSources.clear();
|
||||
};
|
||||
|
@ -6,12 +6,13 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
let {Ci,Cu} = require("chrome");
|
||||
let {createExtraActors, appendExtraActors} = require("devtools/server/actors/common");
|
||||
let { Ci, Cu } = require("chrome");
|
||||
let Services = require("Services");
|
||||
let { createExtraActors, appendExtraActors } = require("devtools/server/actors/common");
|
||||
let { AddonThreadActor, ThreadActor } = require("devtools/server/actors/script");
|
||||
let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
|
||||
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
|
||||
|
@ -6,17 +6,13 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
let {Cc, Ci, Cu} = require("chrome");
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
const Debugger = require("Debugger");
|
||||
const { DebuggerServer, ActorPool } = require("devtools/server/main");
|
||||
const { EnvironmentActor, LongStringActor, ObjectActor, ThreadActor } = require("devtools/server/actors/script");
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
let { DebuggerServer, ActorPool } = require("devtools/server/main");
|
||||
// Symbols from script.js
|
||||
let { ThreadActor, EnvironmentActor, ObjectActor, LongStringActor } = DebuggerServer;
|
||||
|
||||
Cu.import("resource://gre/modules/jsdebugger.jsm");
|
||||
addDebuggerToGlobal(this);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
XPCOMUtils.defineLazyGetter(this, "NetworkMonitor", () => {
|
||||
|
@ -346,7 +346,8 @@ var DebuggerServer = {
|
||||
|
||||
if (!restrictPrivileges) {
|
||||
this.addTabActors();
|
||||
this.addGlobalActor(this.ChromeDebuggerActor, "chromeDebugger");
|
||||
let { ChromeDebuggerActor } = require("devtools/server/actors/script");
|
||||
this.addGlobalActor(ChromeDebuggerActor, "chromeDebugger");
|
||||
this.registerModule("devtools/server/actors/preference");
|
||||
}
|
||||
|
||||
@ -377,7 +378,7 @@ var DebuggerServer = {
|
||||
* Install tab actors.
|
||||
*/
|
||||
addTabActors: function() {
|
||||
this.addActors("resource://gre/modules/devtools/server/actors/script.js");
|
||||
this.registerModule("devtools/server/actors/script");
|
||||
this.registerModule("devtools/server/actors/webconsole");
|
||||
this.registerModule("devtools/server/actors/inspector");
|
||||
this.registerModule("devtools/server/actors/call-watcher");
|
||||
|
@ -8,11 +8,11 @@ const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
|
||||
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const Services = devtools.require("Services");
|
||||
const { ActorPool, createExtraActors, appendExtraActors } = devtools.require("devtools/server/actors/common");
|
||||
const DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js");
|
||||
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
|
||||
const Services = devtools.require("Services");
|
||||
const DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js");
|
||||
|
||||
// Always log packets when running tests. runxpcshelltests.py will throw
|
||||
// the output away anyway, unless you give it the --verbose flag.
|
||||
Services.prefs.setBoolPref("devtools.debugger.log", true);
|
||||
@ -34,6 +34,8 @@ tryImport("resource://gre/modules/devtools/dbg-client.jsm");
|
||||
tryImport("resource://gre/modules/devtools/Loader.jsm");
|
||||
tryImport("resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
let { BreakpointStore, LongStringActor, ThreadActor } = devtools.require("devtools/server/actors/script");
|
||||
|
||||
function testExceptionHook(ex) {
|
||||
try {
|
||||
do_report_unexpected_exception(ex);
|
||||
@ -182,7 +184,7 @@ function attachTestTabAndResume(aClient, aTitle, aCallback) {
|
||||
function initTestDebuggerServer()
|
||||
{
|
||||
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/root.js");
|
||||
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/script.js");
|
||||
DebuggerServer.registerModule("devtools/server/actors/script");
|
||||
DebuggerServer.addActors("resource://test/testactors.js");
|
||||
// Allow incoming connections.
|
||||
DebuggerServer.init(function () { return true; });
|
||||
@ -191,7 +193,7 @@ function initTestDebuggerServer()
|
||||
function initTestTracerServer()
|
||||
{
|
||||
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/root.js");
|
||||
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/script.js");
|
||||
DebuggerServer.registerModule("devtools/server/actors/script");
|
||||
DebuggerServer.addActors("resource://test/testactors.js");
|
||||
DebuggerServer.registerModule("devtools/server/actors/tracer");
|
||||
// Allow incoming connections.
|
||||
|
@ -8,9 +8,6 @@ function run_test()
|
||||
{
|
||||
Cu.import("resource://gre/modules/jsdebugger.jsm");
|
||||
addDebuggerToGlobal(this);
|
||||
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Components.interfaces.mozIJSSubScriptLoader);
|
||||
loader.loadSubScript("resource://gre/modules/devtools/server/actors/script.js");
|
||||
|
||||
test_has_breakpoint();
|
||||
test_bug_754251();
|
||||
|
@ -6,9 +6,6 @@ function run_test()
|
||||
{
|
||||
Cu.import("resource://gre/modules/jsdebugger.jsm");
|
||||
addDebuggerToGlobal(this);
|
||||
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Components.interfaces.mozIJSSubScriptLoader);
|
||||
loader.loadSubScript("resource://gre/modules/devtools/server/actors/script.js");
|
||||
|
||||
test_LSA_disconnect();
|
||||
test_LSA_grip();
|
||||
|
@ -1,6 +1,10 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const Cu = Components.utils;
|
||||
const devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
|
||||
const { ThreadActor } = devtools.require("devtools/server/actors/script");
|
||||
|
||||
var gTestGlobals = [];
|
||||
DebuggerServer.addTestGlobal = function(aGlobal) {
|
||||
gTestGlobals.push(aGlobal);
|
||||
|
@ -20,6 +20,16 @@ XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
"resource://gre/modules/osfile.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
const INTERNAL_FIELDS = new Set(["_level", "_message", "_time", "_namespace"]);
|
||||
|
||||
|
||||
/*
|
||||
* Dump a message everywhere we can if we have a failure.
|
||||
*/
|
||||
function dumpError(text) {
|
||||
dump(text + "\n");
|
||||
Cu.reportError(text);
|
||||
}
|
||||
|
||||
this.Log = {
|
||||
Level: {
|
||||
@ -80,6 +90,7 @@ this.Log = {
|
||||
FileAppender: FileAppender,
|
||||
BoundedFileAppender: BoundedFileAppender,
|
||||
|
||||
ParameterFormatter: ParameterFormatter,
|
||||
// Logging helper:
|
||||
// let logger = Log.repository.getLogger("foo");
|
||||
// logger.info(Log.enumerateInterfaces(someObject).join(","));
|
||||
@ -100,14 +111,13 @@ this.Log = {
|
||||
// Logging helper:
|
||||
// let logger = Log.repository.getLogger("foo");
|
||||
// logger.info(Log.enumerateProperties(someObject).join(","));
|
||||
enumerateProperties: function Log_enumerateProps(aObject,
|
||||
aExcludeComplexTypes) {
|
||||
enumerateProperties: function (aObject, aExcludeComplexTypes) {
|
||||
let properties = [];
|
||||
|
||||
for (p in aObject) {
|
||||
try {
|
||||
if (aExcludeComplexTypes &&
|
||||
(typeof aObject[p] == "object" || typeof aObject[p] == "function"))
|
||||
(typeof(aObject[p]) == "object" || typeof(aObject[p]) == "function"))
|
||||
continue;
|
||||
properties.push(p + " = " + aObject[p]);
|
||||
}
|
||||
@ -117,10 +127,81 @@ this.Log = {
|
||||
}
|
||||
|
||||
return properties;
|
||||
},
|
||||
|
||||
_formatError: function _formatError(e) {
|
||||
let result = e.toString();
|
||||
if (e.fileName) {
|
||||
result += " (" + e.fileName;
|
||||
if (e.lineNumber) {
|
||||
result += ":" + e.lineNumber;
|
||||
}
|
||||
if (e.columnNumber) {
|
||||
result += ":" + e.columnNumber;
|
||||
}
|
||||
result += ")";
|
||||
}
|
||||
return result + " " + Log.stackTrace(e);
|
||||
},
|
||||
|
||||
// This is for back compatibility with services/common/utils.js; we duplicate
|
||||
// some of the logic in ParameterFormatter
|
||||
exceptionStr: function exceptionStr(e) {
|
||||
if (!e) {
|
||||
return "" + e;
|
||||
}
|
||||
if (e instanceof Ci.nsIException) {
|
||||
return e.toString() + " " + Log.stackTrace(e);
|
||||
}
|
||||
else if (isError(e)) {
|
||||
return Log._formatError(e);
|
||||
}
|
||||
// else
|
||||
let message = e.message ? e.message : e;
|
||||
return message + " " + Log.stackTrace(e);
|
||||
},
|
||||
|
||||
stackTrace: function stackTrace(e) {
|
||||
// Wrapped nsIException
|
||||
if (e.location) {
|
||||
let frame = e.location;
|
||||
let output = [];
|
||||
while (frame) {
|
||||
// Works on frames or exceptions, munges file:// URIs to shorten the paths
|
||||
// FIXME: filename munging is sort of hackish, might be confusing if
|
||||
// there are multiple extensions with similar filenames
|
||||
let str = "<file:unknown>";
|
||||
|
||||
let file = frame.filename || frame.fileName;
|
||||
if (file) {
|
||||
str = file.replace(/^(?:chrome|file):.*?([^\/\.]+\.\w+)$/, "$1");
|
||||
}
|
||||
|
||||
if (frame.lineNumber) {
|
||||
str += ":" + frame.lineNumber;
|
||||
}
|
||||
|
||||
if (frame.name) {
|
||||
str = frame.name + "()@" + str;
|
||||
}
|
||||
|
||||
if (str) {
|
||||
output.push(str);
|
||||
}
|
||||
frame = frame.caller;
|
||||
}
|
||||
return "Stack trace: " + output.join(" < ");
|
||||
}
|
||||
// Standard JS exception
|
||||
if (e.stack) {
|
||||
return "JS Stack trace: " + e.stack.trim().replace(/\n/g, " < ").
|
||||
replace(/@[^@]*?([^\/\.]+\.\w+:)/g, "@$1");
|
||||
}
|
||||
|
||||
return "No traceback available";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* LogMessage
|
||||
* Encapsulates a single log event's data
|
||||
@ -128,8 +209,21 @@ this.Log = {
|
||||
function LogMessage(loggerName, level, message, params) {
|
||||
this.loggerName = loggerName;
|
||||
this.level = level;
|
||||
this.message = message;
|
||||
this.params = params;
|
||||
/*
|
||||
* Special case to handle "log./level/(object)", for example logging a caught exception
|
||||
* without providing text or params like: catch(e) { logger.warn(e) }
|
||||
* Treating this as an empty text with the object in the 'params' field causes the
|
||||
* object to be formatted properly by BasicFormatter.
|
||||
*/
|
||||
if (!params && message && (typeof(message) == "object") &&
|
||||
(typeof(message.valueOf()) != "string")) {
|
||||
this.message = null;
|
||||
this.params = message;
|
||||
} else {
|
||||
// If the message text is empty, or a string, or a String object, normal handling
|
||||
this.message = message;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
// The _structured field will correspond to whether this message is to
|
||||
// be interpreted as a structured message.
|
||||
@ -143,7 +237,7 @@ LogMessage.prototype = {
|
||||
return "UNKNOWN";
|
||||
},
|
||||
|
||||
toString: function LogMsg_toString(){
|
||||
toString: function LogMsg_toString() {
|
||||
let msg = "LogMessage [" + this.time + " " + this.level + " " +
|
||||
this.message;
|
||||
if (this.params) {
|
||||
@ -178,7 +272,7 @@ Logger.prototype = {
|
||||
return this._level;
|
||||
if (this.parent)
|
||||
return this.parent.level;
|
||||
dump("Log warning: root logger configuration error: no level defined\n");
|
||||
dumpError("Log warning: root logger configuration error: no level defined");
|
||||
return Log.Level.All;
|
||||
},
|
||||
set level(level) {
|
||||
@ -246,7 +340,8 @@ Logger.prototype = {
|
||||
* (object) Parameters to be included in the message.
|
||||
* If _level is included as a key and the corresponding value
|
||||
* is a number or known level name, the message will be logged
|
||||
* at the indicated level.
|
||||
* at the indicated level. If _message is included as a key, the
|
||||
* value is used as the descriptive text for the message.
|
||||
*/
|
||||
logStructured: function (action, params) {
|
||||
if (!action) {
|
||||
@ -255,13 +350,18 @@ Logger.prototype = {
|
||||
if (!params) {
|
||||
return this.log(this.level, undefined, {"action": action});
|
||||
}
|
||||
if (typeof params != "object") {
|
||||
if (typeof(params) != "object") {
|
||||
throw "The params argument is required to be an object.";
|
||||
}
|
||||
|
||||
let level = params._level || this.level;
|
||||
if ((typeof level == "string") && level in Log.Level.Numbers) {
|
||||
level = Log.Level.Numbers[level];
|
||||
let level = params._level;
|
||||
if (level) {
|
||||
let ulevel = level.toUpperCase();
|
||||
if (ulevel in Log.Level.Numbers) {
|
||||
level = Log.Level.Numbers[ulevel];
|
||||
}
|
||||
} else {
|
||||
level = this.level;
|
||||
}
|
||||
|
||||
params.action = action;
|
||||
@ -428,17 +528,69 @@ Formatter.prototype = {
|
||||
|
||||
// Basic formatter that doesn't do anything fancy.
|
||||
function BasicFormatter(dateFormat) {
|
||||
if (dateFormat)
|
||||
if (dateFormat) {
|
||||
this.dateFormat = dateFormat;
|
||||
}
|
||||
this.parameterFormatter = new ParameterFormatter();
|
||||
}
|
||||
BasicFormatter.prototype = {
|
||||
__proto__: Formatter.prototype,
|
||||
|
||||
/**
|
||||
* Format the text of a message with optional parameters.
|
||||
* If the text contains ${identifier}, replace that with
|
||||
* the value of params[identifier]; if ${}, replace that with
|
||||
* the entire params object. If no params have been substituted
|
||||
* into the text, format the entire object and append that
|
||||
* to the message.
|
||||
*/
|
||||
formatText: function (message) {
|
||||
let params = message.params;
|
||||
if (!params) {
|
||||
return message.message || "";
|
||||
}
|
||||
// Defensive handling of non-object params
|
||||
// We could add a special case for NSRESULT values here...
|
||||
let pIsObject = (typeof(params) == 'object' || typeof(params) == 'function');
|
||||
|
||||
// if we have params, try and find substitutions.
|
||||
if (message.params && this.parameterFormatter) {
|
||||
// have we successfully substituted any parameters into the message?
|
||||
// in the log message
|
||||
let subDone = false;
|
||||
let regex = /\$\{(\S*)\}/g;
|
||||
let textParts = [];
|
||||
if (message.message) {
|
||||
textParts.push(message.message.replace(regex, (_, sub) => {
|
||||
// ${foo} means use the params['foo']
|
||||
if (sub) {
|
||||
if (pIsObject && sub in message.params) {
|
||||
subDone = true;
|
||||
return this.parameterFormatter.format(message.params[sub]);
|
||||
}
|
||||
return '${' + sub + '}';
|
||||
}
|
||||
// ${} means use the entire params object.
|
||||
subDone = true;
|
||||
return this.parameterFormatter.format(message.params);
|
||||
}));
|
||||
}
|
||||
if (!subDone) {
|
||||
// There were no substitutions in the text, so format the entire params object
|
||||
let rest = this.parameterFormatter.format(message.params);
|
||||
if (rest !== null && rest != "{}") {
|
||||
textParts.push(rest);
|
||||
}
|
||||
}
|
||||
return textParts.join(': ');
|
||||
}
|
||||
},
|
||||
|
||||
format: function BF_format(message) {
|
||||
return message.time + "\t" +
|
||||
message.loggerName + "\t" +
|
||||
message.levelDesc + "\t" +
|
||||
message.message + "\n";
|
||||
this.formatText(message);
|
||||
}
|
||||
};
|
||||
|
||||
@ -451,7 +603,7 @@ MessageOnlyFormatter.prototype = Object.freeze({
|
||||
__proto__: Formatter.prototype,
|
||||
|
||||
format: function (message) {
|
||||
return message.message + "\n";
|
||||
return message.message;
|
||||
},
|
||||
});
|
||||
|
||||
@ -485,6 +637,67 @@ StructuredFormatter.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test an object to see if it is a Mozilla JS Error.
|
||||
*/
|
||||
function isError(aObj) {
|
||||
return (aObj && typeof(aObj) == 'object' && "name" in aObj && "message" in aObj &&
|
||||
"fileName" in aObj && "lineNumber" in aObj && "stack" in aObj);
|
||||
};
|
||||
|
||||
/*
|
||||
* Parameter Formatters
|
||||
* These massage an object used as a parameter for a LogMessage into
|
||||
* a string representation of the object.
|
||||
*/
|
||||
|
||||
function ParameterFormatter() {
|
||||
this._name = "ParameterFormatter"
|
||||
}
|
||||
ParameterFormatter.prototype = {
|
||||
format: function(ob) {
|
||||
try {
|
||||
if (ob === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
if (ob === null) {
|
||||
return "null";
|
||||
}
|
||||
// Pass through primitive types and objects that unbox to primitive types.
|
||||
if ((typeof(ob) != "object" || typeof(ob.valueOf()) != "object") &&
|
||||
typeof(ob) != "function") {
|
||||
return ob;
|
||||
}
|
||||
if (ob instanceof Ci.nsIException) {
|
||||
return ob.toString() + " " + Log.stackTrace(ob);
|
||||
}
|
||||
else if (isError(ob)) {
|
||||
return Log._formatError(ob);
|
||||
}
|
||||
// Just JSONify it. Filter out our internal fields and those the caller has
|
||||
// already handled.
|
||||
return JSON.stringify(ob, (key, val) => {
|
||||
if (INTERNAL_FIELDS.has(key)) {
|
||||
return undefined;
|
||||
}
|
||||
return val;
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
dumpError("Exception trying to format object for log message: " + Log.exceptionStr(e));
|
||||
}
|
||||
// Fancy formatting failed. Just toSource() it - but even this may fail!
|
||||
try {
|
||||
return ob.toSource();
|
||||
} catch (_) { }
|
||||
try {
|
||||
return "" + ob;
|
||||
} catch (_) {
|
||||
return "[object]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Appenders
|
||||
* These can be attached to Loggers to log to different places
|
||||
@ -504,10 +717,10 @@ Appender.prototype = {
|
||||
}
|
||||
},
|
||||
toString: function App_toString() {
|
||||
return this._name + " [level=" + this._level +
|
||||
return this._name + " [level=" + this.level +
|
||||
", formatter=" + this._formatter + "]";
|
||||
},
|
||||
doAppend: function App_doAppend(message) {}
|
||||
doAppend: function App_doAppend(formatted) {}
|
||||
};
|
||||
|
||||
/*
|
||||
@ -522,8 +735,8 @@ function DumpAppender(formatter) {
|
||||
DumpAppender.prototype = {
|
||||
__proto__: Appender.prototype,
|
||||
|
||||
doAppend: function DApp_doAppend(message) {
|
||||
dump(message);
|
||||
doAppend: function DApp_doAppend(formatted) {
|
||||
dump(formatted + "\n");
|
||||
}
|
||||
};
|
||||
|
||||
@ -539,13 +752,21 @@ function ConsoleAppender(formatter) {
|
||||
ConsoleAppender.prototype = {
|
||||
__proto__: Appender.prototype,
|
||||
|
||||
doAppend: function CApp_doAppend(message) {
|
||||
if (message.level > Log.Level.Warn) {
|
||||
Cu.reportError(message);
|
||||
return;
|
||||
// XXX this should be replaced with calls to the Browser Console
|
||||
append: function App_append(message) {
|
||||
if (message) {
|
||||
let m = this._formatter.format(message);
|
||||
if (message.level > Log.Level.Warn) {
|
||||
Cu.reportError(m);
|
||||
return;
|
||||
}
|
||||
this.doAppend(m);
|
||||
}
|
||||
},
|
||||
|
||||
doAppend: function CApp_doAppend(formatted) {
|
||||
Cc["@mozilla.org/consoleservice;1"].
|
||||
getService(Ci.nsIConsoleService).logStringMessage(message);
|
||||
getService(Ci.nsIConsoleService).logStringMessage(formatted);
|
||||
}
|
||||
};
|
||||
|
||||
@ -614,19 +835,19 @@ StorageStreamAppender.prototype = {
|
||||
this._ss = null;
|
||||
},
|
||||
|
||||
doAppend: function (message) {
|
||||
if (!message) {
|
||||
doAppend: function (formatted) {
|
||||
if (!formatted) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.outputStream.writeString(message);
|
||||
this.outputStream.writeString(formatted + "\n");
|
||||
} catch(ex) {
|
||||
if (ex.result == Cr.NS_BASE_STREAM_CLOSED) {
|
||||
// The underlying output stream is closed, so let's open a new one
|
||||
// and try again.
|
||||
this._outputStream = null;
|
||||
} try {
|
||||
this.outputStream.writeString(message);
|
||||
this.outputStream.writeString(formatted + "\n");
|
||||
} catch (ex) {
|
||||
// Ah well, we tried, but something seems to be hosed permanently.
|
||||
}
|
||||
@ -682,8 +903,8 @@ FileAppender.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
doAppend: function (message) {
|
||||
let array = this._encoder.encode(message);
|
||||
doAppend: function (formatted) {
|
||||
let array = this._encoder.encode(formatted + "\n");
|
||||
if (this._file) {
|
||||
this._lastWritePromise = this._file.write(array);
|
||||
} else {
|
||||
@ -709,7 +930,7 @@ FileAppender.prototype = {
|
||||
* Bounded File appender
|
||||
*
|
||||
* Writes output to file using OS.File. After the total message size
|
||||
* (as defined by message.length) exceeds maxSize, existing messages
|
||||
* (as defined by formatted.length) exceeds maxSize, existing messages
|
||||
* will be discarded, and subsequent writes will be appended to a new log file.
|
||||
*/
|
||||
function BoundedFileAppender(path, formatter, maxSize=2*ONE_MEGABYTE) {
|
||||
@ -723,17 +944,17 @@ function BoundedFileAppender(path, formatter, maxSize=2*ONE_MEGABYTE) {
|
||||
BoundedFileAppender.prototype = {
|
||||
__proto__: FileAppender.prototype,
|
||||
|
||||
doAppend: function (message) {
|
||||
doAppend: function (formatted) {
|
||||
if (!this._removeFilePromise) {
|
||||
if (this._size < this._maxSize) {
|
||||
this._size += message.length;
|
||||
return FileAppender.prototype.doAppend.call(this, message);
|
||||
this._size += formatted.length;
|
||||
return FileAppender.prototype.doAppend.call(this, formatted);
|
||||
}
|
||||
this._removeFilePromise = this.reset();
|
||||
}
|
||||
this._removeFilePromise.then(_ => {
|
||||
this._removeFilePromise = null;
|
||||
this.doAppend(message);
|
||||
this.doAppend(formatted);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -12,7 +12,7 @@ let testFormatter = {
|
||||
format: function format(message) {
|
||||
return message.loggerName + "\t" +
|
||||
message.levelDesc + "\t" +
|
||||
message.message + "\n";
|
||||
message.message;
|
||||
}
|
||||
};
|
||||
|
||||
@ -32,7 +32,7 @@ function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(function test_Logger() {
|
||||
add_task(function test_Logger() {
|
||||
let log = Log.repository.getLogger("test.logger");
|
||||
let appender = new MockAppender(new Log.BasicFormatter());
|
||||
|
||||
@ -46,11 +46,9 @@ add_test(function test_Logger() {
|
||||
|
||||
let msgRe = /\d+\ttest.logger\t\INFO\tinfo test/;
|
||||
do_check_true(msgRe.test(appender.messages[0]));
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_Logger_parent() {
|
||||
add_task(function test_Logger_parent() {
|
||||
// Check whether parenting is correct
|
||||
let grandparentLog = Log.repository.getLogger("grandparent");
|
||||
let childLog = Log.repository.getLogger("grandparent.parent.child");
|
||||
@ -68,8 +66,6 @@ add_test(function test_Logger_parent() {
|
||||
|
||||
do_check_eq(gpAppender.messages.length, 1);
|
||||
do_check_true(gpAppender.messages[0].indexOf("child info test") > 0);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_LoggerWithMessagePrefix() {
|
||||
@ -85,17 +81,19 @@ add_test(function test_LoggerWithMessagePrefix() {
|
||||
|
||||
Assert.equal(appender.messages.length, 2, "2 messages were logged.");
|
||||
Assert.deepEqual(appender.messages, [
|
||||
"no prefix\n",
|
||||
"prefix: with prefix\n",
|
||||
"no prefix",
|
||||
"prefix: with prefix",
|
||||
], "Prefix logger works.");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
// A utility method for checking object equivalence.
|
||||
// Fields with a reqular expression value in expected will be tested
|
||||
// against the corresponding value in actual. Otherwise objects
|
||||
// are expected to have the same keys and equal values.
|
||||
/*
|
||||
* A utility method for checking object equivalence.
|
||||
* Fields with a reqular expression value in expected will be tested
|
||||
* against the corresponding value in actual. Otherwise objects
|
||||
* are expected to have the same keys and equal values.
|
||||
*/
|
||||
function checkObjects(expected, actual) {
|
||||
do_check_true(expected instanceof Object);
|
||||
do_check_true(actual instanceof Object);
|
||||
@ -117,7 +115,7 @@ function checkObjects(expected, actual) {
|
||||
}
|
||||
}
|
||||
|
||||
add_test(function test_StructuredLogCommands() {
|
||||
add_task(function test_StructuredLogCommands() {
|
||||
let appender = new MockAppender(new Log.StructuredFormatter());
|
||||
let logger = Log.repository.getLogger("test.StructuredOutput");
|
||||
logger.addAppender(appender);
|
||||
@ -195,11 +193,9 @@ add_test(function test_StructuredLogCommands() {
|
||||
|
||||
checkObjects(messageOne, JSON.parse(appender.messages[0]));
|
||||
checkObjects(messageTwo, JSON.parse(appender.messages[1]));
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_StorageStreamAppender() {
|
||||
add_task(function test_StorageStreamAppender() {
|
||||
let appender = new Log.StorageStreamAppender(testFormatter);
|
||||
do_check_eq(appender.getInputStream(), null);
|
||||
|
||||
@ -227,8 +223,6 @@ add_test(function test_StorageStreamAppender() {
|
||||
data = NetUtil.readInputStreamToString(inputStream,
|
||||
inputStream.available());
|
||||
do_check_eq(data, "test.StorageStreamAppender\tDEBUG\twut?!?\n");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
function fileContents(path) {
|
||||
@ -335,3 +329,244 @@ add_task(function test_BoundedFileAppender() {
|
||||
|
||||
});
|
||||
|
||||
/*
|
||||
* Test parameter formatting.
|
||||
*/
|
||||
add_task(function log_message_with_params() {
|
||||
let formatter = new Log.BasicFormatter();
|
||||
|
||||
function formatMessage(text, params) {
|
||||
let full = formatter.format(new Log.LogMessage("test.logger", Log.Level.Warn, text, params));
|
||||
return full.split("\t")[3];
|
||||
}
|
||||
|
||||
// Strings are substituted directly.
|
||||
do_check_eq(formatMessage("String is ${foo}", {foo: "bar"}),
|
||||
"String is bar");
|
||||
|
||||
// Numbers are substituted.
|
||||
do_check_eq(formatMessage("Number is ${number}", {number: 47}),
|
||||
"Number is 47")
|
||||
|
||||
// The entire params object is JSON-formatted and substituted.
|
||||
do_check_eq(formatMessage("Object is ${}", {foo: "bar"}),
|
||||
'Object is {"foo":"bar"}');
|
||||
|
||||
// An object nested inside params is JSON-formatted and substituted.
|
||||
do_check_eq(formatMessage("Sub object is ${sub}", {sub: {foo: "bar"}}),
|
||||
'Sub object is {"foo":"bar"}');
|
||||
|
||||
// The substitution field is missing from params. Leave the placeholder behind
|
||||
// to make the mistake obvious.
|
||||
do_check_eq(formatMessage("Missing object is ${missing}", {}),
|
||||
'Missing object is ${missing}');
|
||||
|
||||
// Make sure we don't treat the parameter name 'false' as a falsey value.
|
||||
do_check_eq(formatMessage("False is ${false}", {false: true}),
|
||||
'False is true');
|
||||
|
||||
// If an object has a .toJSON method, the formatter uses it.
|
||||
let ob = function() {};
|
||||
ob.toJSON = function() {return {sneaky: "value"}};
|
||||
do_check_eq(formatMessage("JSON is ${sub}", {sub: ob}),
|
||||
'JSON is {"sneaky":"value"}');
|
||||
|
||||
// Fall back to .toSource() if JSON.stringify() fails on an object.
|
||||
let ob = function() {};
|
||||
ob.toJSON = function() {throw "oh noes JSON"};
|
||||
do_check_eq(formatMessage("Fail is ${sub}", {sub: ob}),
|
||||
'Fail is (function () {})');
|
||||
|
||||
// Fall back to .toString if both .toJSON and .toSource fail.
|
||||
ob.toSource = function() {throw "oh noes SOURCE"};
|
||||
do_check_eq(formatMessage("Fail is ${sub}", {sub: ob}),
|
||||
'Fail is function () {}');
|
||||
|
||||
// Fall back to '[object]' if .toJSON, .toSource and .toString fail.
|
||||
ob.toString = function() {throw "oh noes STRING"};
|
||||
do_check_eq(formatMessage("Fail is ${sub}", {sub: ob}),
|
||||
'Fail is [object]');
|
||||
|
||||
// If params are passed but there are no substitution in the text
|
||||
// we JSON format and append the entire parameters object.
|
||||
do_check_eq(formatMessage("Text with no subs", {a: "b", c: "d"}),
|
||||
'Text with no subs: {"a":"b","c":"d"}');
|
||||
|
||||
// If we substitute one parameter but not the other,
|
||||
// we ignore any params that aren't substituted.
|
||||
do_check_eq(formatMessage("Text with partial sub ${a}", {a: "b", c: "d"}),
|
||||
'Text with partial sub b');
|
||||
|
||||
// We don't format internal fields stored in params.
|
||||
do_check_eq(formatMessage("Params with _ ${}", {a: "b", _c: "d", _level:20, _message:"froo",
|
||||
_time:123456, _namespace:"here.there"}),
|
||||
'Params with _ {"a":"b","_c":"d"}');
|
||||
|
||||
// Don't print an empty params holder if all params are internal.
|
||||
do_check_eq(formatMessage("All params internal", {_level:20, _message:"froo",
|
||||
_time:123456, _namespace:"here.there"}),
|
||||
'All params internal');
|
||||
|
||||
// Format params with null and undefined values.
|
||||
do_check_eq(formatMessage("Null ${n} undefined ${u}", {n: null, u: undefined}),
|
||||
'Null null undefined undefined');
|
||||
|
||||
// Format params with number, bool, and Object/String type.
|
||||
do_check_eq(formatMessage("number ${n} boolean ${b} boxed Boolean ${bx} String ${s}",
|
||||
{n: 45, b: false, bx: new Boolean(true), s: new String("whatevs")}),
|
||||
'number 45 boolean false boxed Boolean true String whatevs');
|
||||
|
||||
/*
|
||||
* Check that errors get special formatting if they're formatted directly as
|
||||
* a named param or they're the only param, but not if they're a field in a
|
||||
* larger structure.
|
||||
*/
|
||||
let err = Components.Exception("test exception", Components.results.NS_ERROR_FAILURE);
|
||||
let str = formatMessage("Exception is ${}", err);
|
||||
do_check_true(str.contains('Exception is [Exception... "test exception"'));
|
||||
do_check_true(str.contains("(NS_ERROR_FAILURE)"));
|
||||
let str = formatMessage("Exception is", err);
|
||||
do_check_true(str.contains('Exception is: [Exception... "test exception"'));
|
||||
let str = formatMessage("Exception is ${error}", {error: err});
|
||||
do_check_true(str.contains('Exception is [Exception... "test exception"'));
|
||||
let str = formatMessage("Exception is", {_error: err});
|
||||
do_print(str);
|
||||
// Exceptions buried inside objects are formatted badly.
|
||||
do_check_true(str.contains('Exception is: {"_error":{}'));
|
||||
// If the message text is null, the message contains only the formatted params object.
|
||||
let str = formatMessage(null, err);
|
||||
do_check_true(str.startsWith('[Exception... "test exception"'));
|
||||
// If the text is null and 'params' is a String object, the message is exactly that string.
|
||||
let str = formatMessage(null, new String("String in place of params"));
|
||||
do_check_eq(str, "String in place of params");
|
||||
|
||||
// We use object.valueOf() internally; make sure a broken valueOf() method
|
||||
// doesn't cause the logger to fail.
|
||||
let vOf = {a: 1, valueOf: function() {throw "oh noes valueOf"}};
|
||||
do_check_eq(formatMessage("Broken valueOf ${}", vOf),
|
||||
'Broken valueOf ({a:1, valueOf:(function () {throw "oh noes valueOf"})})');
|
||||
|
||||
// Test edge cases of bad data to formatter:
|
||||
// If 'params' is not an object, format it as a basic type.
|
||||
do_check_eq(formatMessage("non-object no subst", 1),
|
||||
'non-object no subst: 1');
|
||||
do_check_eq(formatMessage("non-object all subst ${}", 2),
|
||||
'non-object all subst 2');
|
||||
// If 'params' is not an object, no named substitutions can succeed;
|
||||
// therefore we leave the placeholder and append the formatted params.
|
||||
do_check_eq(formatMessage("non-object named subst ${junk} space", 3),
|
||||
'non-object named subst ${junk} space: 3');
|
||||
// If there are no params, we leave behind the placeholders in the text.
|
||||
do_check_eq(formatMessage("no params ${missing}", undefined),
|
||||
'no params ${missing}');
|
||||
// If params doesn't contain any of the tags requested in the text,
|
||||
// we leave them all behind and append the formatted params.
|
||||
do_check_eq(formatMessage("object missing tag ${missing} space", {mising: "not here"}),
|
||||
'object missing tag ${missing} space: {"mising":"not here"}');
|
||||
// If we are given null text and no params, the resulting formatted message is empty.
|
||||
do_check_eq(formatMessage(null), '');
|
||||
});
|
||||
|
||||
/*
|
||||
* If we call a log function with a non-string object in place of the text
|
||||
* argument, and no parameters, treat that the same as logging empty text
|
||||
* with the object argument as parameters. This makes the log useful when the
|
||||
* caller does "catch(err) {logger.error(err)}"
|
||||
*/
|
||||
add_task(function test_log_err_only() {
|
||||
let log = Log.repository.getLogger("error.only");
|
||||
let testFormatter = { format: msg => msg };
|
||||
let appender = new MockAppender(testFormatter);
|
||||
log.addAppender(appender);
|
||||
|
||||
/*
|
||||
* Check that log.error(err) is treated the same as
|
||||
* log.error(null, err) by the logMessage constructor; the formatMessage()
|
||||
* tests above ensure that the combination of null text and an error object
|
||||
* is formatted correctly.
|
||||
*/
|
||||
try {
|
||||
eval("javascript syntax error");
|
||||
}
|
||||
catch (e) {
|
||||
log.error(e);
|
||||
msg = appender.messages.pop();
|
||||
do_check_eq(msg.message, null);
|
||||
do_check_eq(msg.params, e);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Test logStructured() messages through basic formatter.
|
||||
*/
|
||||
add_task(function test_structured_basic() {
|
||||
let log = Log.repository.getLogger("test.logger");
|
||||
let appender = new MockAppender(new Log.BasicFormatter());
|
||||
|
||||
log.level = Log.Level.Info;
|
||||
appender.level = Log.Level.Info;
|
||||
log.addAppender(appender);
|
||||
|
||||
// A structured entry with no _message is treated the same as log./level/(null, params)
|
||||
// except the 'action' field is added to the object.
|
||||
log.logStructured("action", {data: "structure"});
|
||||
do_check_eq(appender.messages.length, 1);
|
||||
do_check_true(appender.messages[0].contains('{"data":"structure","action":"action"}'));
|
||||
|
||||
// A structured entry with _message and substitution is treated the same as
|
||||
// log./level/(null, params).
|
||||
log.logStructured("action", {_message: "Structured sub ${data}", data: "structure"});
|
||||
do_check_eq(appender.messages.length, 2);
|
||||
do_print(appender.messages[1]);
|
||||
do_check_true(appender.messages[1].contains('Structured sub structure'));
|
||||
});
|
||||
|
||||
/*
|
||||
* Test that all the basic logger methods pass the message and params through to the appender.
|
||||
*/
|
||||
add_task(function log_message_with_params() {
|
||||
let log = Log.repository.getLogger("error.logger");
|
||||
let testFormatter = { format: msg => msg };
|
||||
let appender = new MockAppender(testFormatter);
|
||||
log.addAppender(appender);
|
||||
|
||||
let testParams = {a:1, b:2};
|
||||
log.fatal("Test fatal", testParams);
|
||||
log.error("Test error", testParams);
|
||||
log.warn("Test warn", testParams);
|
||||
log.info("Test info", testParams);
|
||||
log.config("Test config", testParams);
|
||||
log.debug("Test debug", testParams);
|
||||
log.trace("Test trace", testParams);
|
||||
do_check_eq(appender.messages.length, 7);
|
||||
for (let msg of appender.messages) {
|
||||
do_check_true(msg.params === testParams);
|
||||
do_check_true(msg.message.startsWith("Test "));
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Check that we format JS Errors reasonably.
|
||||
*/
|
||||
add_task(function format_errors() {
|
||||
let pFormat = new Log.ParameterFormatter();
|
||||
|
||||
// Test that subclasses of Error are recognized as errors.
|
||||
err = new ReferenceError("Ref Error", "ERROR_FILE", 28);
|
||||
str = pFormat.format(err);
|
||||
do_check_true(str.contains("ReferenceError"));
|
||||
do_check_true(str.contains("ERROR_FILE:28"));
|
||||
do_check_true(str.contains("Ref Error"));
|
||||
|
||||
// Test that JS-generated Errors are recognized and formatted.
|
||||
try {
|
||||
eval("javascript syntax error");
|
||||
}
|
||||
catch (e) {
|
||||
str = pFormat.format(e);
|
||||
do_check_true(str.contains("SyntaxError: missing ;"));
|
||||
// Make sure we identified it as an Error and formatted the error location as
|
||||
// lineNumber:columnNumber.
|
||||
do_check_true(str.contains(":1:11)"));
|
||||
}
|
||||
});
|
||||
|
@ -13,8 +13,6 @@ Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/AddonManager.jsm");
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
@ -25,6 +23,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository_SQLiteMigrator",
|
||||
"resource://gre/modules/addons/AddonRepository_SQLiteMigrator.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
|
||||
|
||||
this.EXPORTED_SYMBOLS = [ "AddonRepository" ];
|
||||
|
||||
@ -50,7 +51,6 @@ const FILE_DATABASE = "addons.json";
|
||||
const DB_SCHEMA = 5;
|
||||
const DB_MIN_JSON_SCHEMA = 5;
|
||||
const DB_BATCH_TIMEOUT_MS = 50;
|
||||
const DB_DATA_WRITTEN_TOPIC = "addon-repository-data-written"
|
||||
|
||||
const BLANK_DB = function() {
|
||||
return {
|
||||
@ -496,12 +496,6 @@ this.AddonRepository = {
|
||||
// An array of callbacks pending the retrieval of add-ons from AddonDatabase
|
||||
_pendingCallbacks: null,
|
||||
|
||||
// Whether a migration in currently in progress
|
||||
_migrationInProgress: false,
|
||||
|
||||
// A callback to be called when migration finishes
|
||||
_postMigrationCallback: null,
|
||||
|
||||
// Whether a search is currently in progress
|
||||
_searching: false,
|
||||
|
||||
@ -553,7 +547,7 @@ this.AddonRepository = {
|
||||
* @param aCallback
|
||||
* The callback to pass the result back to
|
||||
*/
|
||||
getCachedAddonByID: function AddonRepo_getCachedAddonByID(aId, aCallback) {
|
||||
getCachedAddonByID: Task.async(function* (aId, aCallback) {
|
||||
if (!aId || !this.cacheEnabled) {
|
||||
aCallback(null);
|
||||
return;
|
||||
@ -569,20 +563,20 @@ this.AddonRepository = {
|
||||
// Data has not been retrieved from the database, so retrieve it
|
||||
this._pendingCallbacks = [];
|
||||
this._pendingCallbacks.push(getAddon);
|
||||
AddonDatabase.retrieveStoredData(function getCachedAddonByID_retrieveData(aAddons) {
|
||||
let pendingCallbacks = self._pendingCallbacks;
|
||||
|
||||
// Check if cache was shutdown or deleted before callback was called
|
||||
if (pendingCallbacks == null)
|
||||
return;
|
||||
let addons = yield AddonDatabase.retrieveStoredData();
|
||||
let pendingCallbacks = self._pendingCallbacks;
|
||||
|
||||
// Callbacks may want to trigger a other caching operations that may
|
||||
// affect _addons and _pendingCallbacks, so set to final values early
|
||||
self._pendingCallbacks = null;
|
||||
self._addons = aAddons;
|
||||
// Check if cache was shutdown or deleted before callback was called
|
||||
if (pendingCallbacks == null)
|
||||
return;
|
||||
|
||||
pendingCallbacks.forEach(function(aCallback) aCallback(aAddons));
|
||||
});
|
||||
// Callbacks may want to trigger a other caching operations that may
|
||||
// affect _addons and _pendingCallbacks, so set to final values early
|
||||
self._pendingCallbacks = null;
|
||||
self._addons = addons;
|
||||
|
||||
pendingCallbacks.forEach(function(aCallback) aCallback(addons));
|
||||
|
||||
return;
|
||||
}
|
||||
@ -594,7 +588,7 @@ this.AddonRepository = {
|
||||
|
||||
// Data has been retrieved, so immediately return result
|
||||
getAddon(this._addons);
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* Asynchronously repopulate cache so it only contains the add-ons
|
||||
@ -1430,6 +1424,7 @@ this.AddonRepository = {
|
||||
this._request.addEventListener("error", aEvent => this._reportFailure(), false);
|
||||
this._request.addEventListener("timeout", aEvent => this._reportFailure(), false);
|
||||
this._request.addEventListener("load", aEvent => {
|
||||
logger.debug("Got metadata search load event");
|
||||
let request = aEvent.target;
|
||||
let responseXML = request.responseXML;
|
||||
|
||||
@ -1518,119 +1513,104 @@ this.AddonRepository = {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
flush: function() {
|
||||
return AddonDatabase.flush();
|
||||
}
|
||||
};
|
||||
|
||||
var AddonDatabase = {
|
||||
// true if the database connection has been opened
|
||||
initialized: false,
|
||||
// false if there was an unrecoverable error openning the database
|
||||
// false if there was an unrecoverable error opening the database
|
||||
databaseOk: true,
|
||||
|
||||
connectionPromise: null,
|
||||
// the in-memory database
|
||||
DB: BLANK_DB(),
|
||||
|
||||
/**
|
||||
* A getter to retrieve an nsIFile pointer to the DB
|
||||
* A getter to retrieve the path to the DB
|
||||
*/
|
||||
get jsonFile() {
|
||||
delete this.jsonFile;
|
||||
return this.jsonFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
|
||||
},
|
||||
return OS.Path.join(OS.Constants.Path.profileDir, FILE_DATABASE);
|
||||
},
|
||||
|
||||
/**
|
||||
* Synchronously opens a new connection to the database file.
|
||||
* Asynchronously opens a new connection to the database file.
|
||||
*
|
||||
* @return {Promise} a promise that resolves to the database.
|
||||
*/
|
||||
openConnection: function() {
|
||||
this.DB = BLANK_DB();
|
||||
this.initialized = true;
|
||||
delete this.connection;
|
||||
if (!this.connectionPromise) {
|
||||
this.connectionPromise = Task.spawn(function*() {
|
||||
this.DB = BLANK_DB();
|
||||
|
||||
let inputDB, fstream, cstream, schema;
|
||||
let inputDB, schema;
|
||||
|
||||
try {
|
||||
let data = "";
|
||||
fstream = Cc["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Ci.nsIFileInputStream);
|
||||
cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
|
||||
.createInstance(Ci.nsIConverterInputStream);
|
||||
try {
|
||||
let data = yield OS.File.read(this.jsonFile, { encoding: "utf-8"})
|
||||
inputDB = JSON.parse(data);
|
||||
|
||||
fstream.init(this.jsonFile, -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);
|
||||
}
|
||||
if (!inputDB.hasOwnProperty("addons") ||
|
||||
!Array.isArray(inputDB.addons)) {
|
||||
throw new Error("No addons array.");
|
||||
}
|
||||
|
||||
inputDB = JSON.parse(data);
|
||||
if (!inputDB.hasOwnProperty("schema")) {
|
||||
throw new Error("No schema specified.");
|
||||
}
|
||||
|
||||
if (!inputDB.hasOwnProperty("addons") ||
|
||||
!Array.isArray(inputDB.addons)) {
|
||||
throw new Error("No addons array.");
|
||||
}
|
||||
schema = parseInt(inputDB.schema, 10);
|
||||
|
||||
if (!inputDB.hasOwnProperty("schema")) {
|
||||
throw new Error("No schema specified.");
|
||||
}
|
||||
if (!Number.isInteger(schema) ||
|
||||
schema < DB_MIN_JSON_SCHEMA) {
|
||||
throw new Error("Invalid schema value.");
|
||||
}
|
||||
} catch (e if e instanceof OS.File.Error && e.becauseNoSuchFile) {
|
||||
logger.debug("No " + FILE_DATABASE + " found.");
|
||||
|
||||
schema = parseInt(inputDB.schema, 10);
|
||||
// Create a blank addons.json file
|
||||
this._saveDBToDisk();
|
||||
|
||||
if (!Number.isInteger(schema) ||
|
||||
schema < DB_MIN_JSON_SCHEMA) {
|
||||
throw new Error("Invalid schema value.");
|
||||
}
|
||||
let dbSchema = 0;
|
||||
try {
|
||||
dbSchema = Services.prefs.getIntPref(PREF_GETADDONS_DB_SCHEMA);
|
||||
} catch (e) {}
|
||||
|
||||
} catch (e if e.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
|
||||
logger.debug("No " + FILE_DATABASE + " found.");
|
||||
if (dbSchema < DB_MIN_JSON_SCHEMA) {
|
||||
let results = yield new Promise((resolve, reject) => {
|
||||
AddonRepository_SQLiteMigrator.migrate(resolve);
|
||||
});
|
||||
|
||||
// Create a blank addons.json file
|
||||
this._saveDBToDisk();
|
||||
if (results.length) {
|
||||
yield this._insertAddons(results);
|
||||
}
|
||||
|
||||
let dbSchema = 0;
|
||||
try {
|
||||
dbSchema = Services.prefs.getIntPref(PREF_GETADDONS_DB_SCHEMA);
|
||||
} catch (e) {}
|
||||
Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
|
||||
}
|
||||
|
||||
if (dbSchema < DB_MIN_JSON_SCHEMA) {
|
||||
this._migrationInProgress = AddonRepository_SQLiteMigrator.migrate((results) => {
|
||||
if (results.length)
|
||||
this.insertAddons(results);
|
||||
return this.DB;
|
||||
} catch (e) {
|
||||
logger.error("Malformed " + FILE_DATABASE + ": " + e);
|
||||
this.databaseOk = false;
|
||||
|
||||
if (this._postMigrationCallback) {
|
||||
this._postMigrationCallback();
|
||||
this._postMigrationCallback = null;
|
||||
}
|
||||
return this.DB;
|
||||
}
|
||||
|
||||
this._migrationInProgress = false;
|
||||
});
|
||||
Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
|
||||
|
||||
Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
|
||||
}
|
||||
// We use _insertAddon manually instead of calling
|
||||
// insertAddons to avoid the write to disk which would
|
||||
// be a waste since this is the data that was just read.
|
||||
for (let addon of inputDB.addons) {
|
||||
this._insertAddon(addon);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
} catch (e) {
|
||||
logger.error("Malformed " + FILE_DATABASE + ": " + e);
|
||||
this.databaseOk = false;
|
||||
return;
|
||||
|
||||
} finally {
|
||||
cstream.close();
|
||||
fstream.close();
|
||||
return this.DB;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
|
||||
|
||||
// We use _insertAddon manually instead of calling
|
||||
// insertAddons to avoid the write to disk which would
|
||||
// be a waste since this is the data that was just read.
|
||||
for (let addon of inputDB.addons) {
|
||||
this._insertAddon(addon);
|
||||
}
|
||||
return this.connectionPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1653,18 +1633,14 @@ var AddonDatabase = {
|
||||
shutdown: function AD_shutdown(aSkipFlush) {
|
||||
this.databaseOk = true;
|
||||
|
||||
if (!this.initialized) {
|
||||
return Promise.resolve(0);
|
||||
if (!this.connectionPromise) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.initialized = false;
|
||||
|
||||
this.__defineGetter__("connection", function shutdown_connectionGetter() {
|
||||
return this.openConnection();
|
||||
});
|
||||
this.connectionPromise = null;
|
||||
|
||||
if (aSkipFlush) {
|
||||
return Promise.resolve(0);
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return this.Writer.flush();
|
||||
}
|
||||
@ -1680,13 +1656,14 @@ var AddonDatabase = {
|
||||
delete: function AD_delete(aCallback) {
|
||||
this.DB = BLANK_DB();
|
||||
|
||||
this.Writer.flush()
|
||||
this._deleting = this.Writer.flush()
|
||||
.then(null, () => {})
|
||||
// shutdown(true) never rejects
|
||||
.then(() => this.shutdown(true))
|
||||
.then(() => OS.File.remove(this.jsonFile.path, {}))
|
||||
.then(() => OS.File.remove(this.jsonFile, {}))
|
||||
.then(null, error => logger.error("Unable to delete Addon Repository file " +
|
||||
this.jsonFile.path, error))
|
||||
this.jsonFile, error))
|
||||
.then(() => this._deleting = null)
|
||||
.then(aCallback);
|
||||
},
|
||||
|
||||
@ -1710,13 +1687,26 @@ var AddonDatabase = {
|
||||
get Writer() {
|
||||
delete this.Writer;
|
||||
this.Writer = new DeferredSave(
|
||||
this.jsonFile.path,
|
||||
this.jsonFile,
|
||||
() => { return JSON.stringify(this); },
|
||||
DB_BATCH_TIMEOUT_MS
|
||||
);
|
||||
return this.Writer;
|
||||
},
|
||||
|
||||
/**
|
||||
* Flush any pending I/O on the addons.json file
|
||||
* @return: Promise{null}
|
||||
* Resolves when the pending I/O (writing out or deleting
|
||||
* addons.json) completes
|
||||
*/
|
||||
flush: function() {
|
||||
if (this._deleting) {
|
||||
return this._deleting;
|
||||
}
|
||||
return this.Writer.flush();
|
||||
},
|
||||
|
||||
/**
|
||||
* Asynchronously retrieve all add-ons from the database, and pass it
|
||||
* to the specified callback
|
||||
@ -1724,23 +1714,16 @@ var AddonDatabase = {
|
||||
* @param aCallback
|
||||
* The callback to pass the add-ons back to
|
||||
*/
|
||||
retrieveStoredData: function AD_retrieveStoredData(aCallback) {
|
||||
if (!this.initialized)
|
||||
this.openConnection();
|
||||
retrieveStoredData: Task.async(function* (){
|
||||
let db = yield this.openConnection();
|
||||
let result = {};
|
||||
|
||||
let gatherResults = () => {
|
||||
let result = {};
|
||||
for (let [key, value] of this.DB.addons)
|
||||
result[key] = value;
|
||||
for (let [key, value] of db.addons) {
|
||||
result[key] = value;
|
||||
}
|
||||
|
||||
executeSoon(function() aCallback(result));
|
||||
};
|
||||
|
||||
if (this._migrationInProgress)
|
||||
this._postMigrationCallback = gatherResults;
|
||||
else
|
||||
gatherResults();
|
||||
},
|
||||
return result;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Asynchronously repopulates the database so it only contains the
|
||||
@ -1764,19 +1747,19 @@ var AddonDatabase = {
|
||||
* @param aCallback
|
||||
* An optional callback to call once complete
|
||||
*/
|
||||
insertAddons: function AD_insertAddons(aAddons, aCallback) {
|
||||
if (!this.initialized)
|
||||
this.openConnection();
|
||||
insertAddons: Task.async(function* (aAddons, aCallback) {
|
||||
yield this.openConnection();
|
||||
yield this._insertAddons(aAddons, aCallback);
|
||||
}),
|
||||
|
||||
_insertAddons: Task.async(function* (aAddons, aCallback) {
|
||||
for (let addon of aAddons) {
|
||||
this._insertAddon(addon);
|
||||
}
|
||||
|
||||
this._saveDBToDisk();
|
||||
|
||||
if (aCallback)
|
||||
executeSoon(aCallback);
|
||||
},
|
||||
yield this._saveDBToDisk();
|
||||
aCallback && aCallback();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Inserts an individual add-on into the database. If the add-on already
|
||||
@ -1919,8 +1902,8 @@ var AddonDatabase = {
|
||||
*/
|
||||
_saveDBToDisk: function() {
|
||||
return this.Writer.saveChanges().then(
|
||||
function() Services.obs.notifyObservers(null, DB_DATA_WRITTEN_TOPIC, null),
|
||||
logger.error);
|
||||
null,
|
||||
e => logger.error("SaveDBToDisk failed", e));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1980,7 +1963,3 @@ var AddonDatabase = {
|
||||
appMaxVersion);
|
||||
},
|
||||
};
|
||||
|
||||
function executeSoon(aCallback) {
|
||||
Services.tm.mainThread.dispatch(aCallback, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
}
|
||||
|
@ -398,6 +398,25 @@ function startupManager(aAppChanged) {
|
||||
loadAddonsList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to spin the event loop until a promise resolves or rejects
|
||||
*/
|
||||
function loopUntilPromise(aPromise) {
|
||||
let done = false;
|
||||
aPromise.then(
|
||||
() => done = true,
|
||||
err => {
|
||||
do_report_unexpected_exception(err);
|
||||
done = true;
|
||||
});
|
||||
|
||||
let thr = Services.tm.mainThread;
|
||||
|
||||
while (!done) {
|
||||
thr.processNextEvent(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts the add-on manager as if the host application was restarted.
|
||||
*
|
||||
@ -407,51 +426,58 @@ function startupManager(aAppChanged) {
|
||||
* the application version has changed.
|
||||
*/
|
||||
function restartManager(aNewVersion) {
|
||||
shutdownManager();
|
||||
if (aNewVersion) {
|
||||
gAppInfo.version = aNewVersion;
|
||||
startupManager(true);
|
||||
}
|
||||
else {
|
||||
startupManager(false);
|
||||
}
|
||||
loopUntilPromise(promiseRestartManager(aNewVersion));
|
||||
}
|
||||
|
||||
function promiseRestartManager(aNewVersion) {
|
||||
return promiseShutdownManager()
|
||||
.then(null, err => do_report_unexpected_exception(err))
|
||||
.then(() => {
|
||||
if (aNewVersion) {
|
||||
gAppInfo.version = aNewVersion;
|
||||
startupManager(true);
|
||||
}
|
||||
else {
|
||||
startupManager(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function shutdownManager() {
|
||||
if (!gInternalManager)
|
||||
return;
|
||||
loopUntilPromise(promiseShutdownManager());
|
||||
}
|
||||
|
||||
let shutdownDone = false;
|
||||
|
||||
Services.obs.notifyObservers(null, "quit-application-granted", null);
|
||||
MockAsyncShutdown.hook().then(
|
||||
() => shutdownDone = true,
|
||||
err => shutdownDone = true);
|
||||
|
||||
let thr = Services.tm.mainThread;
|
||||
|
||||
// Wait until we observe the shutdown notifications
|
||||
while (!shutdownDone) {
|
||||
thr.processNextEvent(true);
|
||||
function promiseShutdownManager() {
|
||||
if (!gInternalManager) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
gInternalManager = null;
|
||||
let hookErr = null;
|
||||
Services.obs.notifyObservers(null, "quit-application-granted", null);
|
||||
return MockAsyncShutdown.hook()
|
||||
.then(null, err => hookErr = err)
|
||||
.then( () => {
|
||||
gInternalManager = null;
|
||||
|
||||
// Load the add-ons list as it was after application shutdown
|
||||
loadAddonsList();
|
||||
// Load the add-ons list as it was after application shutdown
|
||||
loadAddonsList();
|
||||
|
||||
// Clear any crash report annotations
|
||||
gAppInfo.annotations = {};
|
||||
// Clear any crash report annotations
|
||||
gAppInfo.annotations = {};
|
||||
|
||||
// Force the XPIProvider provider to reload to better
|
||||
// simulate real-world usage.
|
||||
let XPIscope = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm");
|
||||
// This would be cleaner if I could get it as the rejection reason from
|
||||
// the AddonManagerInternal.shutdown() promise
|
||||
gXPISaveError = XPIscope.XPIProvider._shutdownError;
|
||||
do_print("gXPISaveError set to: " + gXPISaveError);
|
||||
AddonManagerPrivate.unregisterProvider(XPIscope.XPIProvider);
|
||||
Components.utils.unload("resource://gre/modules/addons/XPIProvider.jsm");
|
||||
// Force the XPIProvider provider to reload to better
|
||||
// simulate real-world usage.
|
||||
let XPIscope = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm");
|
||||
// This would be cleaner if I could get it as the rejection reason from
|
||||
// the AddonManagerInternal.shutdown() promise
|
||||
gXPISaveError = XPIscope.XPIProvider._shutdownError;
|
||||
do_print("gXPISaveError set to: " + gXPISaveError);
|
||||
AddonManagerPrivate.unregisterProvider(XPIscope.XPIProvider);
|
||||
Components.utils.unload("resource://gre/modules/addons/XPIProvider.jsm");
|
||||
if (hookErr) {
|
||||
throw hookErr;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadAddonsList() {
|
||||
|
@ -405,31 +405,25 @@ const WITH_EXTENSION_CACHE = [{
|
||||
/*
|
||||
* Trigger an AddonManager background update check
|
||||
*
|
||||
* @param aCallback
|
||||
* Callback to call once the background update is complete
|
||||
* @return Promise{null}
|
||||
* Resolves when the background update notification is received
|
||||
*/
|
||||
function trigger_background_update(aCallback) {
|
||||
Services.obs.addObserver({
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
Services.obs.removeObserver(this, "addons-background-update-complete");
|
||||
do_execute_soon(aCallback);
|
||||
}
|
||||
}, "addons-background-update-complete", false);
|
||||
function trigger_background_update() {
|
||||
return new Promise((resolve, reject) => {
|
||||
Services.obs.addObserver({
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
do_print("Observed " + aTopic);
|
||||
Services.obs.removeObserver(this, "addons-background-update-complete");
|
||||
resolve();
|
||||
}
|
||||
}, "addons-background-update-complete", false);
|
||||
|
||||
gInternalManager.notify(null);
|
||||
gInternalManager.notify(null);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether or not the add-ons database exists
|
||||
*
|
||||
* @param aExpectedExists
|
||||
* Whether or not the database is expected to exist
|
||||
*/
|
||||
function check_database_exists(aExpectedExists) {
|
||||
let file = gProfD.clone();
|
||||
file.append(FILE_DATABASE);
|
||||
do_check_eq(file.exists(), aExpectedExists);
|
||||
}
|
||||
let gDBFile = gProfD.clone();
|
||||
gDBFile.append(FILE_DATABASE);
|
||||
|
||||
/*
|
||||
* Check the actual add-on results against the expected add-on results
|
||||
@ -471,302 +465,271 @@ function check_results(aActualAddons, aExpectedAddons, aFromRepository) {
|
||||
* A boolean representing if results from the cache are expected
|
||||
* immediately. Results are not immediate if the cache has not been
|
||||
* initialized yet.
|
||||
* @param aCallback
|
||||
* A callback to call once the checks are complete
|
||||
* @return Promise{null}
|
||||
* Resolves once the checks are complete
|
||||
*/
|
||||
function check_cache(aExpectedToFind, aExpectedImmediately, aCallback) {
|
||||
function check_cache(aExpectedToFind, aExpectedImmediately) {
|
||||
do_check_eq(aExpectedToFind.length, REPOSITORY_ADDONS.length);
|
||||
|
||||
let pendingAddons = REPOSITORY_ADDONS.length;
|
||||
let immediatelyFound = true;
|
||||
let lookups = [];
|
||||
|
||||
for (let i = 0; i < REPOSITORY_ADDONS.length; i++) {
|
||||
let expected = aExpectedToFind[i] ? REPOSITORY_ADDONS[i] : null;
|
||||
AddonRepository.getCachedAddonByID(REPOSITORY_ADDONS[i].id, function(aAddon) {
|
||||
do_check_eq(immediatelyFound, aExpectedImmediately);
|
||||
|
||||
if (expected == null)
|
||||
do_check_eq(aAddon, null);
|
||||
else
|
||||
check_results([aAddon], [expected], true);
|
||||
|
||||
if (--pendingAddons == 0)
|
||||
do_execute_soon(aCallback);
|
||||
});
|
||||
for (let i = 0 ; i < REPOSITORY_ADDONS.length ; i++) {
|
||||
lookups.push(new Promise((resolve, reject) => {
|
||||
let immediatelyFound = true;
|
||||
let expected = aExpectedToFind[i] ? REPOSITORY_ADDONS[i] : null;
|
||||
// can't Promise-wrap this because we're also testing whether the callback is
|
||||
// sync or async
|
||||
AddonRepository.getCachedAddonByID(REPOSITORY_ADDONS[i].id, function(aAddon) {
|
||||
do_check_eq(immediatelyFound, aExpectedImmediately);
|
||||
if (expected == null)
|
||||
do_check_eq(aAddon, null);
|
||||
else
|
||||
check_results([aAddon], [expected], true);
|
||||
resolve();
|
||||
});
|
||||
immediatelyFound = false;
|
||||
}));
|
||||
}
|
||||
|
||||
immediatelyFound = false;
|
||||
return Promise.all(lookups);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check an initialized cache by checking the cache, then restarting the
|
||||
* Task to check an initialized cache by checking the cache, then restarting the
|
||||
* manager, and checking the cache. This checks that the cache is consistent
|
||||
* across manager restarts.
|
||||
*
|
||||
* @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 restart_initialized_cache() {
|
||||
restartManager();
|
||||
function* check_initialized_cache(aExpectedToFind) {
|
||||
yield check_cache(aExpectedToFind, true);
|
||||
yield promiseRestartManager();
|
||||
|
||||
// If cache is disabled, then expect results immediately
|
||||
let cacheEnabled = Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED);
|
||||
check_cache(aExpectedToFind, !cacheEnabled, aCallback);
|
||||
});
|
||||
}
|
||||
|
||||
// Waits for the data to be written from the in-memory DB to the addons.json
|
||||
// file that is done asynchronously through OS.File
|
||||
function waitForFlushedData(aCallback) {
|
||||
Services.obs.addObserver({
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
Services.obs.removeObserver(this, "addon-repository-data-written");
|
||||
aCallback(aData == "true");
|
||||
}
|
||||
}, "addon-repository-data-written", false);
|
||||
// If cache is disabled, then expect results immediately
|
||||
let cacheEnabled = Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED);
|
||||
yield check_cache(aExpectedToFind, !cacheEnabled);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* setup() {
|
||||
// Setup for test
|
||||
do_test_pending("test_AddonRepository_cache");
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
|
||||
|
||||
startupManager();
|
||||
|
||||
// Install XPI add-ons
|
||||
installAllFiles(ADDON_FILES, function first_installs() {
|
||||
restartManager();
|
||||
yield promiseInstallAllFiles(ADDON_FILES);
|
||||
yield promiseRestartManager();
|
||||
|
||||
gServer = new HttpServer();
|
||||
gServer.registerDirectory("/data/", do_get_file("data"));
|
||||
gServer.start(PORT);
|
||||
|
||||
do_execute_soon(run_test_1);
|
||||
});
|
||||
}
|
||||
|
||||
function end_test() {
|
||||
gServer.stop(function() {do_test_finished("test_AddonRepository_cache");});
|
||||
}
|
||||
gServer = new HttpServer();
|
||||
gServer.registerDirectory("/data/", do_get_file("data"));
|
||||
gServer.start(PORT);
|
||||
});
|
||||
|
||||
// Tests AddonRepository.cacheEnabled
|
||||
function run_test_1() {
|
||||
add_task(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);
|
||||
|
||||
do_execute_soon(run_test_2);
|
||||
}
|
||||
});
|
||||
|
||||
// Tests that the cache and database begin as empty
|
||||
function run_test_2() {
|
||||
check_database_exists(false);
|
||||
check_cache([false, false, false], false, function(){});
|
||||
waitForFlushedData(run_test_3);
|
||||
}
|
||||
add_task(function* run_test_2() {
|
||||
do_check_false(gDBFile.exists());
|
||||
yield check_cache([false, false, false], false);
|
||||
yield AddonRepository.flush();
|
||||
});
|
||||
|
||||
// Tests repopulateCache when the search fails
|
||||
function run_test_3() {
|
||||
check_database_exists(true);
|
||||
add_task(function* run_test_3() {
|
||||
do_check_true(gDBFile.exists());
|
||||
Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true);
|
||||
Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_FAILED);
|
||||
|
||||
AddonRepository.repopulateCache(ADDON_IDS, function test_3_repopulated() {
|
||||
check_initialized_cache([false, false, false], run_test_4);
|
||||
});
|
||||
}
|
||||
yield new Promise((resolve, reject) =>
|
||||
AddonRepository.repopulateCache(ADDON_IDS, resolve));
|
||||
yield check_initialized_cache([false, false, false]);
|
||||
});
|
||||
|
||||
// Tests repopulateCache when search returns no results
|
||||
function run_test_4() {
|
||||
add_task(function* run_test_4() {
|
||||
Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_EMPTY);
|
||||
|
||||
AddonRepository.repopulateCache(ADDON_IDS, function() {
|
||||
check_initialized_cache([false, false, false], run_test_5);
|
||||
});
|
||||
}
|
||||
yield new Promise((resolve, reject) =>
|
||||
AddonRepository.repopulateCache(ADDON_IDS, resolve));
|
||||
yield check_initialized_cache([false, false, false]);
|
||||
});
|
||||
|
||||
// Tests repopulateCache when search returns results
|
||||
function run_test_5() {
|
||||
add_task(function* run_test_5() {
|
||||
Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS);
|
||||
|
||||
AddonRepository.repopulateCache(ADDON_IDS, function() {
|
||||
check_initialized_cache([true, true, true], run_test_5_1);
|
||||
});
|
||||
}
|
||||
yield new Promise((resolve, reject) =>
|
||||
AddonRepository.repopulateCache(ADDON_IDS, resolve));
|
||||
yield check_initialized_cache([true, true, true]);
|
||||
});
|
||||
|
||||
// Tests repopulateCache when caching is disabled for a single add-on
|
||||
function run_test_5_1() {
|
||||
add_task(function* run_test_5_1() {
|
||||
Services.prefs.setBoolPref(PREF_ADDON0_CACHE_ENABLED, false);
|
||||
|
||||
AddonRepository.repopulateCache(ADDON_IDS, function() {
|
||||
// Reset pref for next test
|
||||
Services.prefs.setBoolPref(PREF_ADDON0_CACHE_ENABLED, true);
|
||||
check_initialized_cache([false, true, true], run_test_6);
|
||||
});
|
||||
}
|
||||
yield new Promise((resolve, reject) =>
|
||||
AddonRepository.repopulateCache(ADDON_IDS, resolve));
|
||||
|
||||
// Reset pref for next test
|
||||
Services.prefs.setBoolPref(PREF_ADDON0_CACHE_ENABLED, true);
|
||||
|
||||
yield check_initialized_cache([false, true, true]);
|
||||
});
|
||||
|
||||
// Tests repopulateCache when caching is disabled
|
||||
function run_test_6() {
|
||||
check_database_exists(true);
|
||||
add_task(function* run_test_6() {
|
||||
do_check_true(gDBFile.exists());
|
||||
Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false);
|
||||
|
||||
AddonRepository.repopulateCache(ADDON_IDS, function() {
|
||||
// Database should have been deleted
|
||||
check_database_exists(false);
|
||||
yield new Promise((resolve, reject) =>
|
||||
AddonRepository.repopulateCache(ADDON_IDS, resolve));
|
||||
// Database should have been deleted
|
||||
do_check_false(gDBFile.exists());
|
||||
|
||||
Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true);
|
||||
check_cache([false, false, false], false, function() {});
|
||||
|
||||
waitForFlushedData(run_test_7);
|
||||
});
|
||||
}
|
||||
Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true);
|
||||
yield check_cache([false, false, false], false);
|
||||
yield AddonRepository.flush();
|
||||
});
|
||||
|
||||
// Tests cacheAddons when the search fails
|
||||
function run_test_7() {
|
||||
check_database_exists(true);
|
||||
add_task(function* run_test_7() {
|
||||
do_check_true(gDBFile.exists());
|
||||
Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_FAILED);
|
||||
|
||||
AddonRepository.cacheAddons(ADDON_IDS, function() {
|
||||
check_initialized_cache([false, false, false], run_test_8);
|
||||
});
|
||||
}
|
||||
yield new Promise((resolve, reject) =>
|
||||
AddonRepository.cacheAddons(ADDON_IDS, resolve));
|
||||
yield check_initialized_cache([false, false, false]);
|
||||
});
|
||||
|
||||
// Tests cacheAddons when the search returns no results
|
||||
function run_test_8() {
|
||||
add_task(function* run_test_8() {
|
||||
Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_EMPTY);
|
||||
|
||||
AddonRepository.cacheAddons(ADDON_IDS, function() {
|
||||
check_initialized_cache([false, false, false], run_test_9);
|
||||
});
|
||||
}
|
||||
yield new Promise((resolve, reject) =>
|
||||
AddonRepository.cacheAddons(ADDON_IDS, resolve));
|
||||
yield check_initialized_cache([false, false, false]);
|
||||
});
|
||||
|
||||
// Tests cacheAddons for a single add-on when search returns results
|
||||
function run_test_9() {
|
||||
add_task(function* run_test_9() {
|
||||
Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS);
|
||||
|
||||
AddonRepository.cacheAddons([ADDON_IDS[0]], function() {
|
||||
check_initialized_cache([true, false, false], run_test_9_1);
|
||||
});
|
||||
}
|
||||
yield new Promise((resolve, reject) =>
|
||||
AddonRepository.cacheAddons([ADDON_IDS[0]], resolve));
|
||||
yield check_initialized_cache([true, false, false]);
|
||||
});
|
||||
|
||||
// Tests cacheAddons when caching is disabled for a single add-on
|
||||
function run_test_9_1() {
|
||||
add_task(function* run_test_9_1() {
|
||||
Services.prefs.setBoolPref(PREF_ADDON1_CACHE_ENABLED, false);
|
||||
|
||||
AddonRepository.cacheAddons(ADDON_IDS, function() {
|
||||
// Reset pref for next test
|
||||
Services.prefs.setBoolPref(PREF_ADDON1_CACHE_ENABLED, true);
|
||||
check_initialized_cache([true, false, true], run_test_10);
|
||||
});
|
||||
}
|
||||
yield new Promise((resolve, reject) =>
|
||||
AddonRepository.cacheAddons(ADDON_IDS, resolve));
|
||||
|
||||
// Reset pref for next test
|
||||
Services.prefs.setBoolPref(PREF_ADDON1_CACHE_ENABLED, true);
|
||||
|
||||
yield check_initialized_cache([true, false, true]);
|
||||
});
|
||||
|
||||
// Tests cacheAddons for multiple add-ons, some already in the cache,
|
||||
function run_test_10() {
|
||||
AddonRepository.cacheAddons(ADDON_IDS, function() {
|
||||
check_initialized_cache([true, true, true], run_test_11);
|
||||
});
|
||||
}
|
||||
add_task(function* run_test_10() {
|
||||
yield new Promise((resolve, reject) =>
|
||||
AddonRepository.cacheAddons(ADDON_IDS, resolve));
|
||||
yield check_initialized_cache([true, true, true]);
|
||||
});
|
||||
|
||||
// Tests cacheAddons when caching is disabled
|
||||
function run_test_11() {
|
||||
check_database_exists(true);
|
||||
add_task(function* run_test_11() {
|
||||
do_check_true(gDBFile.exists());
|
||||
Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false);
|
||||
|
||||
AddonRepository.cacheAddons(ADDON_IDS, function() {
|
||||
// Database deleted for repopulateCache, not cacheAddons
|
||||
check_database_exists(true);
|
||||
yield new Promise((resolve, reject) =>
|
||||
AddonRepository.cacheAddons(ADDON_IDS, resolve));
|
||||
do_check_true(gDBFile.exists());
|
||||
|
||||
Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true);
|
||||
check_initialized_cache([true, true, true], run_test_12);
|
||||
});
|
||||
}
|
||||
Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true);
|
||||
yield check_initialized_cache([true, true, true]);
|
||||
});
|
||||
|
||||
// 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);
|
||||
add_task(function* run_test_12() {
|
||||
do_check_true(gDBFile.exists());
|
||||
Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false);
|
||||
Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS);
|
||||
|
||||
AddonManager.getAddonsByIDs(ADDON_IDS, function test_12_check(aAddons) {
|
||||
check_results(aAddons, WITHOUT_CACHE);
|
||||
do_execute_soon(run_test_13);
|
||||
});
|
||||
}
|
||||
let aAddons = yield promiseAddonsByIDs(ADDON_IDS);
|
||||
check_results(aAddons, WITHOUT_CACHE);
|
||||
});
|
||||
|
||||
// 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() {
|
||||
check_database_exists(true);
|
||||
add_task(function* run_test_13() {
|
||||
do_check_true(gDBFile.exists());
|
||||
Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, GETADDONS_EMPTY);
|
||||
|
||||
trigger_background_update(function() {
|
||||
// Database should have been deleted
|
||||
check_database_exists(false);
|
||||
yield trigger_background_update();
|
||||
// Database should have been deleted
|
||||
do_check_false(gDBFile.exists());
|
||||
|
||||
AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) {
|
||||
check_results(aAddons, WITHOUT_CACHE);
|
||||
do_execute_soon(run_test_14);
|
||||
});
|
||||
});
|
||||
}
|
||||
let aAddons = yield promiseAddonsByIDs(ADDON_IDS);
|
||||
check_results(aAddons, WITHOUT_CACHE);
|
||||
});
|
||||
|
||||
// Tests that the XPI add-ons have the correct properties if caching is
|
||||
// enabled but has no information
|
||||
function run_test_14() {
|
||||
add_task(function* run_test_14() {
|
||||
Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true);
|
||||
|
||||
waitForFlushedData(function() {
|
||||
check_database_exists(true);
|
||||
yield trigger_background_update();
|
||||
yield AddonRepository.flush();
|
||||
do_check_true(gDBFile.exists());
|
||||
|
||||
AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) {
|
||||
check_results(aAddons, WITHOUT_CACHE);
|
||||
do_execute_soon(run_test_15);
|
||||
});
|
||||
});
|
||||
|
||||
trigger_background_update();
|
||||
}
|
||||
let aAddons = yield promiseAddonsByIDs(ADDON_IDS);
|
||||
check_results(aAddons, WITHOUT_CACHE);
|
||||
});
|
||||
|
||||
// Tests that the XPI add-ons correctly use the repository properties when
|
||||
// caching is enabled and the repository information is available
|
||||
function run_test_15() {
|
||||
add_task(function* run_test_15() {
|
||||
Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERFORMANCE, GETADDONS_RESULTS);
|
||||
|
||||
trigger_background_update(function() {
|
||||
AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) {
|
||||
check_results(aAddons, WITH_CACHE);
|
||||
do_execute_soon(run_test_16);
|
||||
});
|
||||
});
|
||||
}
|
||||
yield trigger_background_update();
|
||||
let aAddons = yield promiseAddonsByIDs(ADDON_IDS);
|
||||
check_results(aAddons, WITH_CACHE);
|
||||
});
|
||||
|
||||
// Tests that restarting the manager does not change the checked properties
|
||||
// on the XPI add-ons (repository properties still exist and are still properly
|
||||
// used)
|
||||
function run_test_16() {
|
||||
restartManager();
|
||||
add_task(function* run_test_16() {
|
||||
yield promiseRestartManager();
|
||||
|
||||
AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) {
|
||||
check_results(aAddons, WITH_CACHE);
|
||||
do_execute_soon(run_test_17);
|
||||
});
|
||||
}
|
||||
let aAddons = yield promiseAddonsByIDs(ADDON_IDS);
|
||||
check_results(aAddons, WITH_CACHE);
|
||||
});
|
||||
|
||||
// Tests that setting a list of types to cache works
|
||||
function run_test_17() {
|
||||
add_task(function* run_test_17() {
|
||||
Services.prefs.setCharPref(PREF_GETADDONS_CACHE_TYPES, "foo,bar,extension,baz");
|
||||
|
||||
trigger_background_update(function() {
|
||||
AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) {
|
||||
check_results(aAddons, WITH_EXTENSION_CACHE);
|
||||
end_test();
|
||||
});
|
||||
});
|
||||
}
|
||||
yield trigger_background_update();
|
||||
let aAddons = yield promiseAddonsByIDs(ADDON_IDS);
|
||||
check_results(aAddons, WITH_EXTENSION_CACHE);
|
||||
});
|
||||
|
||||
add_task(function* end_test() {
|
||||
yield new Promise((resolve, reject) => gServer.stop(resolve));
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user