Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2015-11-20 14:34:24 +01:00
commit 335f4f23c8
79 changed files with 1276 additions and 912 deletions

View File

@ -528,6 +528,9 @@ pref("layout.frame_rate.precise", true);
// Handle hardware buttons in the b2g chrome package
pref("b2g.keys.menu.enabled", true);
// Display simulator software buttons
pref("b2g.software-buttons", false);
// Screen timeout in seconds
pref("power.screen.timeout", 60);

View File

@ -15,13 +15,24 @@ function enableTouch() {
touchEventSimulator.start();
}
// Some additional buttons are displayed on simulators to fake hardware buttons.
function setupButtons() {
let homeButton = document.getElementById('home-button');
if (!homeButton) {
// The toolbar only exists in b2g desktop build with
// FXOS_SIMULATOR turned on.
return;
}
let link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = 'chrome://b2g/content/desktop.css';
document.head.appendChild(link);
let footer = document.createElement('footer');
footer.id = 'controls';
document.body.appendChild(footer);
let homeButton = document.createElement('button');
homeButton.id = 'home-button';
footer.appendChild(homeButton);
let rotateButton = document.createElement('button');
rotateButton.id = 'rotate-button';
footer.appendChild(rotateButton);
homeButton.addEventListener('mousedown', function() {
let window = shell.contentBrowser.contentWindow;
let e = new window.KeyboardEvent('keydown', {key: 'Home'});
@ -36,7 +47,6 @@ function setupButtons() {
});
Cu.import("resource://gre/modules/GlobalSimulatorScreen.jsm");
let rotateButton = document.getElementById('rotate-button');
rotateButton.addEventListener('mousedown', function() {
rotateButton.classList.add('active');
});
@ -165,7 +175,9 @@ window.addEventListener('ContentStart', function() {
if (!isMulet) {
enableTouch();
}
if (Services.prefs.getBoolPref('b2g.software-buttons')) {
setupButtons();
}
checkDebuggerPort();
setupStorage();
// On Firefox mulet, we automagically enable the responsive mode

View File

@ -18,9 +18,6 @@
<head>
<link rel="stylesheet" href="shell.css" type="text/css">
#ifdef FXOS_SIMULATOR
<link rel="stylesheet" href="desktop.css" type="text/css">
#endif
<script type="text/javascript">
<!-- Add raptor performance marker -->
window.performance.mark('gecko-shell-html-load');
@ -52,17 +49,6 @@
src="chrome://b2g/content/devtools/debugger.js"> </script>
</head>
<body id="container">
#ifdef FXOS_SIMULATOR
#ifndef MOZ_MULET
<!--
Some additional buttons are displayed on desktop to fake hardware buttons.
-->
<footer id="controls">
<button id="home-button"></button>
<button id="rotate-button"></button>
</footer>
#endif
#endif
#ifndef MOZ_GRAPHENE
#ifdef MOZ_WIDGET_COCOA
<!--

View File

@ -22,13 +22,11 @@ chrome.jar:
#endif
* content/devtools/debugger.js (content/devtools/debugger.js)
content/devtools/hud.js (content/devtools/hud.js)
#ifdef FXOS_SIMULATOR
#ifndef MOZ_WIDGET_GONK
content/desktop.css (content/desktop.css)
content/images/desktop/home-black.png (content/images/desktop/home-black.png)
content/images/desktop/home-white.png (content/images/desktop/home-white.png)
content/images/desktop/rotate.png (content/images/desktop/rotate.png)
#endif
#ifndef MOZ_WIDGET_GONK
content/desktop.js (content/desktop.js)
content/screen.js (content/screen.js)
content/runapp.js (content/runapp.js)

View File

@ -1,6 +1,7 @@
user_pref("devtools.debugger.prompt-connection", false);
user_pref("devtools.debugger.forbid-certified-apps", false);
user_pref("devtools.apps.forbidden-permissions", "");
user_pref("b2g.software-buttons", true);
// Required for Mulet in order to run the debugger server from the command line
user_pref("devtools.debugger.remote-enabled", true);

View File

@ -6,6 +6,8 @@ var loop = loop || {};
(function() {
"use strict";
var _slice = Array.prototype.slice;
var kMessageName = "Loop:Message";
var kPushMessageName = "Loop:Message:Push";
var kBatchMessage = "Batch";
@ -51,7 +53,7 @@ var loop = loop || {};
* script sent a reply. It never gets rejected.
*/
loop.request = function request() {
var args = Array.slice(arguments);
var args = _slice.call(arguments);
return new Promise(function(resolve) {
var payload = buildRequestArray(args);
@ -107,7 +109,7 @@ var loop = loop || {};
throw new Error("loop.requestMulti: please pass in a list of calls to process in parallel.");
}
var calls = Array.slice(arguments);
var calls = _slice.call(arguments);
calls.forEach(function(call) {
if (!Array.isArray(call)) {
throw new Error("loop.requestMulti: each call must be an array of options, " +

View File

@ -66,6 +66,8 @@ var gCurrentUser = null;
var gRoomsCache = null;
// Global variable that keeps track of the link clicker channel.
var gLinkClickerChannel = null;
// Global variable that keeps track of in-progress getAll requests.
var gGetAllPromise = null;
/**
* Extend a `target` object with the properties defined in `source`.
@ -530,23 +532,19 @@ var LoopRoomsInternal = {
*
* @param {String} [version] If set, we will fetch a list of changed rooms since
* `version`. Optional.
* @param {Function} callback Function that will be invoked once the operation
* finished. The first argument passed will be an
* `Error` object or `null`. The second argument will
* be the list of rooms, if it was fetched successfully.
* @return {Promise} A promise that is resolved with a list of rooms
* on success, or rejected with an error if it fails.
*/
getAll: function(version = null, callback = null) {
if (!callback) {
callback = version;
version = null;
getAll: function(version) {
if (gGetAllPromise && !version) {
return gGetAllPromise;
}
Task.spawn(function* () {
if (!gDirty) {
callback(null, [...this.rooms.values()]);
return;
return Promise.resolve([...this.rooms.values()]);
}
gGetAllPromise = Task.spawn(function* () {
// Fetch the rooms from the server.
let url = "/rooms" + (version ? "?version=" + encodeURIComponent(version) : "");
let response = yield MozLoopService.hawkRequest(this.sessionType, url, "GET");
@ -585,10 +583,15 @@ var LoopRoomsInternal = {
// Set the 'dirty' flag back to FALSE, since the list is as fresh as can be now.
gDirty = false;
callback(null, [...this.rooms.values()]);
gGetAllPromise = null;
return [...this.rooms.values()];
}.bind(this)).catch(error => {
callback(error);
gGetAllPromise = null;
// Re-throw error so callers get notified.
throw error;
});
return gGetAllPromise;
},
/**
@ -940,6 +943,7 @@ var LoopRoomsInternal = {
let oldDirty = gDirty;
gDirty = true;
// If we were already dirty, then get the full set of rooms. For example,
// we'd already be dirty if we had started up but not got the list of rooms
// yet.
@ -1037,7 +1041,13 @@ this.LoopRooms = {
},
getAll: function(version, callback) {
return LoopRoomsInternal.getAll(version, callback);
if (!callback) {
callback = version;
version = null;
}
LoopRoomsInternal.getAll(version).then(result => callback(null, result))
.catch(error => callback(error));
},
get: function(roomToken, callback) {
@ -1107,6 +1117,10 @@ this.LoopRooms = {
},
promise: function(method, ...params) {
if (method == "getAll") {
return LoopRoomsInternal.getAll(...params);
}
return new Promise((resolve, reject) => {
this[method](...params, (error, result) => {
if (error) {
@ -1140,6 +1154,7 @@ this.LoopRooms = {
for (let [key, value] of roomsCache) {
LoopRoomsInternal.rooms.set(key, value);
}
gGetAllPromise = null;
gDirty = false;
}
}

View File

@ -0,0 +1,159 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://services-common/utils.js");
Cu.import("resource:///modules/loop/LoopRooms.jsm");
Cu.import("resource:///modules/Chat.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
timerHandlers.startTimer = callback => callback();
var openChatOrig = Chat.open;
const kKey = "uGIs-kGbYt1hBBwjyW7MLQ";
// Rooms details as responded by the server.
const kRoomsResponses = new Map([
["_nxD4V4FflQ", {
roomToken: "_nxD4V4FflQ",
// Encrypted with roomKey "FliIGLUolW-xkKZVWstqKw".
// roomKey is wrapped with kKey.
context: {
wrappedKey: "F3V27oPB+FgjFbVPML2PupONYqoIZ53XRU4BqG46Lr3eyIGumgCEqgjSe/MXAXiQ//8=",
value: "df7B4SNxhOI44eJjQavCevADyCCxz6/DEZbkOkRUMVUxzS42FbzN6C2PqmCKDYUGyCJTwJ0jln8TLw==",
alg: "AES-GCM"
},
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
maxSize: 2,
ctime: 1405517546,
participants: [{
displayName: "Alexis",
account: "alexis@example.com",
roomConnectionId: "2a1787a6-4a73-43b5-ae3e-906ec1e763cb"
}, {
displayName: "Adam",
roomConnectionId: "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7"
}]
}],
["QzBbvGmIZWU", {
roomToken: "QzBbvGmIZWU",
roomName: "Second Room Name",
roomUrl: "http://localhost:3000/rooms/QzBbvGmIZWU",
maxSize: 2,
ctime: 140551741
}],
["3jKS_Els9IU", {
roomToken: "3jKS_Els9IU",
roomName: "Third Room Name",
roomUrl: "http://localhost:3000/rooms/3jKS_Els9IU",
maxSize: 3,
clientMaxSize: 2,
ctime: 1405518241
}]
]);
// LoopRooms emits various events. Test if they work as expected here.
var gQueuedResponses = [];
add_task(function* setup_server() {
loopServer.registerPathHandler("/registration", (req, res) => {
res.setStatusLine(null, 200, "OK");
res.processAsync();
res.finish();
});
loopServer.registerPathHandler("/rooms", (req, res) => {
res.processAsync();
gQueuedResponses.push({
req: req,
res: res
});
});
mockPushHandler.registrationPushURL = kEndPointUrl;
yield MozLoopService.promiseRegisteredWithServers();
});
function handleQueuedResponses() {
gQueuedResponses.forEach((xhr) => {
xhr.res.setStatusLine(null, 200, "OK");
xhr.res.write(JSON.stringify([...kRoomsResponses.values()]));
xhr.res.finish();
});
gQueuedResponses = [];
}
function rejectQueuedResponses() {
gQueuedResponses.forEach((xhr) => {
xhr.res.setStatusLine(null, 404, "Not found");
xhr.res.finish();
});
gQueuedResponses = [];
}
add_task(function* test_getAllRooms_failure() {
let firstGetAllPromise = LoopRooms.promise("getAll");
// Check the first one hasn't been resolved yet.
yield waitForCondition(() => gQueuedResponses.length == 1);
rejectQueuedResponses();
yield Assert.rejects(firstGetAllPromise, "Not found", "should throw failure");
});
// Test fetching getAll with multiple requests.
add_task(function* test_getAllRooms_multiple() {
let resolvedFirstCall = false;
let resolvedSecondCall = false;
let firstGetAllPromise = LoopRooms.promise("getAll").then(() => {
resolvedFirstCall = true;
});
// Check the first one hasn't been resolved yet.
yield waitForCondition(() => gQueuedResponses.length == 1);
Assert.equal(resolvedFirstCall, false);
// Queue a second one.
let secondGetAllPromise = LoopRooms.promise("getAll").then(() => {
resolvedSecondCall = true;
});
// Check the second one hasn't been resolved yet.
yield waitForCondition(() => gQueuedResponses.length == 1);
Assert.equal(resolvedFirstCall, false);
Assert.equal(resolvedSecondCall, false);
// Now resolve the requests, and check the calls returned.
handleQueuedResponses();
// Ensure the promises have resolved.
yield firstGetAllPromise;
yield secondGetAllPromise;
yield waitForCondition(() => resolvedFirstCall == true);
yield waitForCondition(() => resolvedSecondCall == true);
});
function run_test() {
setupFakeLoopServer();
Services.prefs.setCharPref("loop.key", kKey);
do_register_cleanup(function() {
// Revert original Chat.open implementation
Chat.open = openChatOrig;
Services.prefs.clearUserPref("loop.key");
Services.prefs.clearUserPref("loop.key.fxa");
MozLoopServiceInternal.fxAOAuthTokenData = null;
MozLoopServiceInternal.fxAOAuthProfile = null;
});
run_next_test();
}

View File

@ -11,6 +11,7 @@ skip-if = toolkit == 'gonk'
[test_looprooms.js]
[test_looprooms_encryption_in_fxa.js]
[test_looprooms_first_notification.js]
[test_looprooms_getall.js]
[test_looprooms_upgrade_to_encryption.js]
[test_loopservice_dnd.js]
[test_loopservice_encryptionkey.js]

View File

@ -784,14 +784,17 @@ nsDefaultCommandLineHandler.prototype = {
var params = new URLSearchParams(url.query);
// We don't want to rewrite all Bing URLs coming from external apps. Look
// for the magic URL parm that's present in searches from the task bar.
// (Typed searches use "form=WNSGPH", Cortana voice searches use "FORM=WNSBOX"
// for direct results, or "FORM=WNSFC2" for "see more results on
// Bing.com")
// * Typed searches use "form=WNSGPH"
// * Cortana voice searches use "FORM=WNSBOX" or direct results, or "FORM=WNSFC2"
// for "see more results on Bing.com")
// * Cortana voice searches started from "Hey, Cortana" use "form=WNSHCO"
// or "form=WNSSSV"
var allowedParams = ["WNSGPH", "WNSBOX", "WNSFC2", "WNSHCO", "WNSSSV"];
var formParam = params.get("form");
if (!formParam) {
formParam = params.get("FORM");
}
if (formParam == "WNSGPH" || formParam == "WNSBOX" || formParam == "WNSFC2") {
if (allowedParams.indexOf(formParam) != -1) {
var term = params.get("q");
var ss = Components.classes["@mozilla.org/browser/search-service;1"]
.getService(nsIBrowserSearchService);

View File

@ -78,7 +78,9 @@ const MESSAGES = [
];
// The list of messages we accept from <xul:browser>s that have no tab
// assigned. Those are for example the ones that preload about:newtab pages.
// assigned, or whose windows have gone away. Those are for example the
// ones that preload about:newtab pages, or from browsers where the window
// has just been closed.
const NOTAB_MESSAGES = new Set([
// For a description see above.
"SessionStore:setupSyncHandler",
@ -126,6 +128,8 @@ const TAB_EVENTS = [
"TabUnpinned"
];
const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
@ -396,6 +400,11 @@ var SessionStoreInternal = {
// properly handle final update message.
_closedTabs: new WeakMap(),
// A map (xul:browser -> object) that maps a browser associated with a
// recently closed tab due to a window closure to the tab state information
// that is being stored in _closedWindows for that tab.
_closedWindowTabs: new WeakMap(),
// whether a setBrowserState call is in progress
_browserSetState: false,
@ -652,14 +661,15 @@ var SessionStoreInternal = {
// If we got here, that means we're dealing with a frame message
// manager message, so the target will be a <xul:browser>.
var browser = aMessage.target;
var win = browser.ownerDocument.defaultView;
let tab = win.gBrowser.getTabForBrowser(browser);
let win = browser.ownerDocument.defaultView;
let tab = win ? win.gBrowser.getTabForBrowser(browser) : null;
// Ensure we receive only specific messages from <xul:browser>s that
// have no tab assigned, e.g. the ones that preload about:newtab pages.
// have no tab or window assigned, e.g. the ones that preload
// about:newtab pages, or windows that have closed.
if (!tab && !NOTAB_MESSAGES.has(aMessage.name)) {
throw new Error(`received unexpected message '${aMessage.name}' ` +
`from a browser that has no tab`);
`from a browser that has no tab or window`);
}
let data = aMessage.data || {};
@ -877,7 +887,10 @@ var SessionStoreInternal = {
this.onBrowserCrashed(win, target);
break;
case "XULFrameLoaderCreated":
if (target.tagName == "browser" && target.frameLoader && target.permanentKey) {
if (target.namespaceURI == NS_XUL &&
target.localName == "browser" &&
target.frameLoader &&
target.permanentKey) {
this._lastKnownFrameLoader.set(target.permanentKey, target.frameLoader);
this.resetEpoch(target);
}
@ -1202,11 +1215,14 @@ var SessionStoreInternal = {
// Collect window data only when *not* closed during shutdown.
if (RunState.isRunning) {
// Flush all data queued in the content script before the window is gone.
TabState.flushWindow(aWindow);
// Grab the most recent window data. The tab data will be updated
// once we finish flushing all of the messages from the tabs.
let tabMap = this._collectWindowData(aWindow);
// update all window data for a last time
this._collectWindowData(aWindow);
for (let [tab, tabData] of tabMap) {
let permanentKey = tab.linkedBrowser.permanentKey;
this._closedWindowTabs.set(permanentKey, tabData);
}
if (isFullyLoaded) {
winData.title = tabbrowser.selectedBrowser.contentTitle || tabbrowser.selectedTab.label;
@ -1226,12 +1242,107 @@ var SessionStoreInternal = {
// recently something was closed.
winData.closedAt = Date.now();
// Save non-private windows if they have at
// least one saveable tab or are the last window.
// we don't want to save the busy state
delete winData.busy;
// Now we have to figure out if this window is worth saving in the _closedWindows
// Object.
//
// We're about to flush the tabs from this window, but it's possible that we
// might never hear back from the content process(es) in time before the user
// chooses to restore the closed window. So we do the following:
//
// 1) Use the tab state cache to determine synchronously if the window is
// worth stashing in _closedWindows.
// 2) Flush the window.
// 3) When the flush is complete, revisit our decision to store the window
// in _closedWindows, and add/remove as necessary.
if (!winData.isPrivate) {
// Remove any open private tabs the window may contain.
PrivacyFilter.filterPrivateTabs(winData);
this.maybeSaveClosedWindow(winData);
}
// The tabbrowser binding will go away once the window is closed,
// so we'll hold a reference to the browsers in the closure here.
let browsers = tabbrowser.browsers;
TabStateFlusher.flushWindow(aWindow).then(() => {
// At this point, aWindow is closed! You should probably not try to
// access any DOM elements from aWindow within this callback unless
// you're holding on to them in the closure.
// We can still access tabbrowser.browsers, thankfully.
for (let browser of browsers) {
if (this._closedWindowTabs.has(browser.permanentKey)) {
let tabData = this._closedWindowTabs.get(browser.permanentKey);
TabState.copyFromCache(browser, tabData);
this._closedWindowTabs.delete(browser.permanentKey);
}
}
// Save non-private windows if they have at
// least one saveable tab or are the last window.
if (!winData.isPrivate) {
// It's possible that a tab switched its privacy state at some point
// before our flush, so we need to filter again.
PrivacyFilter.filterPrivateTabs(winData);
this.maybeSaveClosedWindow(winData);
}
// clear this window from the list
delete this._windows[aWindow.__SSi];
// Update the tabs data now that we've got the most
// recent information.
this.cleanUpWindow(aWindow, winData);
// save the state without this window to disk
this.saveStateDelayed();
});
} else {
this.cleanUpWindow(aWindow, winData);
}
for (let i = 0; i < tabbrowser.tabs.length; i++) {
this.onTabRemove(aWindow, tabbrowser.tabs[i], true);
}
},
/**
* Clean up the message listeners on a window that has finally
* gone away. Call this once you're sure you don't want to hear
* from any of this windows tabs from here forward.
*
* @param aWindow
* The browser window we're cleaning up.
* @param winData
* The data for the window that we should hold in the
* DyingWindowCache in case anybody is still holding a
* reference to it.
*/
cleanUpWindow(aWindow, winData) {
// Cache the window state until it is completely gone.
DyingWindowCache.set(aWindow, winData);
let mm = aWindow.getGroupMessageManager("browsers");
MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
delete aWindow.__SSi;
},
/**
* Decides whether or not a closed window should be put into the
* _closedWindows Object. This might be called multiple times per
* window, and will do the right thing of moving the window data
* in or out of _closedWindows if the winData indicates that our
* need for saving it has changed.
*
* @param winData
* The data for the closed window that we might save.
*/
maybeSaveClosedWindow(winData) {
if (RunState.isRunning) {
// Determine whether the window has any tabs worth saving.
let hasSaveableTabs = winData.tabs.some(this._shouldSaveTabState);
@ -1245,33 +1356,30 @@ var SessionStoreInternal = {
Object.keys(this._windows).length == 1 &&
!this._closedWindows.some(win => win._shouldRestore || false);
if (hasSaveableTabs || isLastWindow) {
// we don't want to save the busy state
delete winData.busy;
// Note that we might already have this window stored in
// _closedWindows from a previous call to this function.
let winIndex = this._closedWindows.indexOf(winData);
let alreadyStored = (winIndex != -1);
let shouldStore = (hasSaveableTabs || isLastWindow);
this._closedWindows.unshift(winData);
if (shouldStore && !alreadyStored) {
let index = this._closedWindows.findIndex(win => {
return win.closedAt < winData.closedAt;
});
// If we found no tab closed before our
// tab then just append it to the list.
if (index == -1) {
index = this._closedWindows.length;
}
// Insert tabData at the right position.
this._closedWindows.splice(index, 0, winData);
this._capClosedWindows();
} else if (!shouldStore && alreadyStored) {
this._closedWindows.splice(winIndex, 1);
}
}
// clear this window from the list
delete this._windows[aWindow.__SSi];
// save the state without this window to disk
this.saveStateDelayed();
}
for (let i = 0; i < tabbrowser.tabs.length; i++) {
this.onTabRemove(aWindow, tabbrowser.tabs[i], true);
}
// Cache the window state until it is completely gone.
DyingWindowCache.set(aWindow, winData);
let mm = aWindow.getGroupMessageManager("browsers");
MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
delete aWindow.__SSi;
},
/**
@ -1620,6 +1728,7 @@ var SessionStoreInternal = {
// not add it back to the list of closed tabs again.
if (closedTab.permanentKey) {
this._closedTabs.delete(closedTab.permanentKey);
this._closedWindowTabs.delete(closedTab.permanentKey);
delete closedTab.permanentKey;
}
@ -2578,9 +2687,20 @@ var SessionStoreInternal = {
return { windows: windows };
},
/**
* Gathers data about a window and its tabs, and updates its
* entry in this._windows.
*
* @param aWindow
* Window references.
* @returns a Map mapping the browser tabs from aWindow to the tab
* entry that was put into the window data in this._windows.
*/
_collectWindowData: function ssi_collectWindowData(aWindow) {
let tabMap = new Map();
if (!this._isWindowLoaded(aWindow))
return;
return tabMap;
let tabbrowser = aWindow.gBrowser;
let tabs = tabbrowser.tabs;
@ -2589,7 +2709,9 @@ var SessionStoreInternal = {
// update the internal state data for this window
for (let tab of tabs) {
tabsData.push(TabState.collect(tab));
let tabData = TabState.collect(tab);
tabMap.set(tab, tabData);
tabsData.push(tabData);
}
winData.selected = tabbrowser.mTabBox.selectedIndex + 1;
@ -2602,6 +2724,7 @@ var SessionStoreInternal = {
aWindow.__SS_lastSessionWindowID;
DirtyWindows.remove(aWindow);
return tabMap;
},
/* ........ Restoring Functionality .............. */

View File

@ -27,6 +27,14 @@ this.TabStateFlusher = Object.freeze({
return TabStateFlusherInternal.flush(browser);
},
/**
* Requests an async flush for all browsers of a given window. Returns a Promise
* that will resolve when we've heard back from all browsers.
*/
flushWindow(window) {
return TabStateFlusherInternal.flushWindow(window);
},
/**
* Resolves the flush request with the given flush ID.
*/
@ -75,6 +83,16 @@ var TabStateFlusherInternal = {
});
},
/**
* Requests an async flush for all browsers of a given window. Returns a Promise
* that will resolve when we've heard back from all browsers.
*/
flushWindow(window) {
let browsers = window.gBrowser.browsers;
let promises = browsers.map((browser) => this.flush(browser));
return Promise.all(promises);
},
/**
* Resolves the flush request with the given flush ID.
*/

View File

@ -26,10 +26,10 @@ function test() {
"//input[@type='file']": filePath
};
registerCleanupFunction(function() {
windowsToClose.forEach(function(win) {
win.close();
});
registerCleanupFunction(function* () {
for (let win of windowsToClose) {
yield BrowserTestUtils.closeWindow(win);
}
});
function test(aLambda) {

View File

@ -4,54 +4,7 @@
/**
* Checks that restoring the last browser window in session is actually
* working:
* 1.1) Open a new browser window
* 1.2) Add some tabs
* 1.3) Close that window
* 1.4) Opening another window
* --> State is restored
*
* 2.1) Open a new browser window
* 2.2) Add some tabs
* 2.3) Enter private browsing mode
* 2.4) Close the window while still in private browsing mode
* 2.5) Opening a new window
* --> State is not restored, because private browsing mode is still active
* 2.6) Leaving private browsing mode
* 2.7) Open another window
* --> State (that was before entering PBM) is restored
*
* 3.1) Open a new browser window
* 3.2) Add some tabs
* 3.4) Open some popups
* 3.5) Add another tab to one popup (so that it gets stored) and close it again
* 3.5) Close the browser window
* 3.6) Open another browser window
* --> State of the closed browser window, but not of the popup, is restored
*
* 4.1) Open a popup
* 4.2) Add another tab to the popup (so that it gets stored) and close it again
* 4.3) Open a window
* --> Nothing at all should be restored
*
* 5.1) Open two browser windows and close them again
* 5.2) undoCloseWindow() one
* 5.3) Open another browser window
* --> Nothing at all should be restored
*
* Checks the new notifications are correctly posted and processed, that is
* for each successful -requested a -granted is received, but omitted if
* -requested was cnceled
* Said notifications are:
* - browser-lastwindow-close-requested
* - browser-lastwindow-close-granted
* Tests are:
* 6) Cancel closing when first observe a -requested
* --> Window is kept open
* 7) Count the number of notifications
* --> count(-requested) == count(-granted) + 1
* --> (The first -requested was canceled, so off-by-one)
* 8) (Mac only) Mac version of Test 5 additionally preparing Test 6
* working.
*
* @see https://bugzilla.mozilla.org/show_bug.cgi?id=354894
* @note It is implicitly tested that restoring the last window works when
@ -71,28 +24,6 @@
* notifications. The latter won't.
*/
function browserWindowsCount(expected, msg) {
if (typeof expected == "number")
expected = [expected, expected];
let count = 0;
let e = Services.wm.getEnumerator("navigator:browser");
while (e.hasMoreElements()) {
if (!e.getNext().closed)
++count;
}
is(count, expected[0], msg + " (nsIWindowMediator)");
let state = ss.getBrowserState();
is(JSON.parse(state).windows.length, expected[1], msg + " (getBrowserState)");
}
function test() {
browserWindowsCount(1, "Only one browser window should be open initially");
waitForExplicitFinish();
// This test takes some time to run, and it could timeout randomly.
// So we require a longer timeout. See bug 528219.
requestLongerTimeout(2);
// Some urls that might be opened in tabs and/or popups
// Do not use about:blank:
// That one is reserved for special purposes in the tests
@ -108,11 +39,71 @@ function test() {
// Window features of browser windows
const CHROME_FEATURES = "chrome,all,dialog=no";
// Store the old window type for cleanup
let oldWinType = "";
// Store the old tabs.warnOnClose pref so that we may reset it during
// cleanup
let oldWarnTabsOnClose = gPrefService.getBoolPref("browser.tabs.warnOnClose");
const IS_MAC = navigator.platform.match(/Mac/);
/**
* Returns an Object with two properties:
* open (int):
* A count of how many non-closed navigator:browser windows there are.
* winstates (int):
* A count of how many windows there are in the SessionStore state.
*/
function getBrowserWindowsCount() {
let open = 0;
let e = Services.wm.getEnumerator("navigator:browser");
while (e.hasMoreElements()) {
if (!e.getNext().closed)
++open;
}
let winstates = JSON.parse(ss.getBrowserState()).windows.length;
return { open, winstates };
}
add_task(function* setup() {
// Make sure we've only got one browser window to start with
let { open, winstates } = getBrowserWindowsCount();
is(open, 1, "Should only be one open window");
is(winstates, 1, "Should only be one window state in SessionStore");
// This test takes some time to run, and it could timeout randomly.
// So we require a longer timeout. See bug 528219.
requestLongerTimeout(2);
// Make the main test window not count as a browser window any longer
let oldWinType = document.documentElement.getAttribute("windowtype");
document.documentElement.setAttribute("windowtype", "navigator:testrunner");
registerCleanupFunction(() => {
document.documentElement.setAttribute("windowtype", "navigator:browser");
});
});
/**
* Sets up one of our tests by setting the right preferences, and
* then opening up a browser window preloaded with some tabs.
*
* @param options (Object)
* An object that can contain the following properties:
*
* private:
* Whether or not the opened window should be private.
*
* denyFirst:
* Whether or not the first window that attempts to close
* via closeWindowForRestoration should be denied.
*
* @param testFunction (Function*)
* A generator function that yields Promises to be run
* once the test has been set up.
*
* @returns Promise
* Resolves once the test has been cleaned up.
*/
let setupTest = Task.async(function*(options, testFunction) {
yield pushPrefs(["browser.startup.page", 3],
["browser.tabs.warnOnClose", false]);
// Observe these, and also use to count the number of hits
let observing = {
@ -129,355 +120,355 @@ function test() {
observing[aTopic]++;
// handle some tests
if (++hitCount == 1) {
// Test 6
if (options.denyFirst && ++hitCount == 1) {
aCancel.QueryInterface(Ci.nsISupportsPRBool).data = true;
}
}
/**
* Helper: Sets prefs as the testsuite requires
* @note Will be reset in cleanTestSuite just before finishing the tests
*/
function setPrefs() {
gPrefService.setIntPref("browser.startup.page", 3);
gPrefService.setBoolPref("browser.tabs.warnOnClose", false);
}
/**
* Helper: Sets up this testsuite
*/
function setupTestsuite(testFn) {
// Register our observers
for (let o in observing)
for (let o in observing) {
Services.obs.addObserver(observer, o, false);
// Make the main test window not count as a browser window any longer
oldWinType = document.documentElement.getAttribute("windowtype");
document.documentElement.setAttribute("windowtype", "navigator:testrunner");
}
/**
* Helper: Cleans up behind the testsuite
*/
function cleanupTestsuite(callback) {
// Finally remove observers again
for (let o in observing)
let private = options.private || false;
let newWin = yield promiseNewWindowLoaded({ private });
injectTestTabs(newWin);
yield testFunction(newWin, observing);
let count = getBrowserWindowsCount();
is(count.open, 0, "Got right number of open windows");
is(count.winstates, 1, "Got right number of stored window states");
for (let o in observing) {
Services.obs.removeObserver(observer, o);
// Reset the prefs we touched
let pref = "browser.startup.page";
if (gPrefService.prefHasUserValue(pref))
gPrefService.clearUserPref(pref);
gPrefService.setBoolPref("browser.tabs.warnOnClose", oldWarnTabsOnClose);
// Reset the window type
document.documentElement.setAttribute("windowtype", oldWinType);
}
yield popPrefs();
});
/**
* Helper: sets the prefs and a new window with our test tabs
* Loads a TEST_URLS into a browser window.
*
* @param win (Window)
* The browser window to load the tabs in
*/
function setupTestAndRun(aIsPrivateWindow, testFn) {
// Prepare the prefs
setPrefs();
// Prepare a window; open it and add more tabs
let options = {};
if (aIsPrivateWindow) {
options = {private: true};
}
whenNewWindowLoaded(options, function (newWin) {
function injectTestTabs(win) {
TEST_URLS.forEach(function (url) {
newWin.gBrowser.addTab(url);
});
executeSoon(() => testFn(newWin));
win.gBrowser.addTab(url);
});
}
/**
* Test 1: Normal in-session restore
* @note: Non-Mac only
* Attempts to close a window via BrowserTryToCloseWindow so that
* we get the browser-lastwindow-close-requested and
* browser-lastwindow-close-granted observer notifications.
*
* @param win (Window)
* The window to try to close
* @returns Promise
* Resolves to true if the window closed, or false if the window
* was denied the ability to close.
*/
function testOpenCloseNormal(nextFn) {
setupTestAndRun(false, function(newWin) {
// Close the window
// window.close doesn't push any close events,
// so use BrowserTryToCloseWindow
newWin.BrowserTryToCloseWindow();
// The first request to close is denied by our observer (Test 6)
ok(!newWin.closed, "First close request was denied");
if (!newWin.closed) {
newWin.BrowserTryToCloseWindow();
ok(newWin.closed, "Second close request was granted");
function closeWindowForRestoration(win) {
return new Promise((resolve) => {
let closePromise = BrowserTestUtils.windowClosed(win);
win.BrowserTryToCloseWindow();
if (!win.closed) {
resolve(false);
return;
}
// Open a new window
// The previously closed window should be restored
whenNewWindowLoaded({}, function (newWin) {
closePromise.then(() => {
resolve(true);
});
});
}
/**
* Normal in-session restore
*
* @note: Non-Mac only
*
* Should do the following:
* 1. Open a new browser window
* 2. Add some tabs
* 3. Close that window
* 4. Opening another window
* 5. Checks that state is restored
*/
add_task(function* test_open_close_normal() {
if (IS_MAC) {
return;
}
yield setupTest({ denyFirst: true }, function*(newWin, obs) {
let closed = yield closeWindowForRestoration(newWin);
ok(!closed, "First close request should have been denied");
closed = yield closeWindowForRestoration(newWin);
ok(closed, "Second close request should be accepted");
newWin = yield promiseNewWindowLoaded();
is(newWin.gBrowser.browsers.length, TEST_URLS.length + 2,
"Restored window in-session with otherpopup windows around");
// Cleanup
newWin.close();
// Note that this will not result in the the browser-lastwindow-close
// notifications firing for this other newWin.
yield BrowserTestUtils.closeWindow(newWin);
// Next please
executeSoon(nextFn);
// setupTest gave us a window which was denied for closing once, and then
// closed.
is(obs["browser-lastwindow-close-requested"], 2,
"Got expected browser-lastwindow-close-requested notifications");
is(obs["browser-lastwindow-close-granted"], 1,
"Got expected browser-lastwindow-close-granted notifications");
});
});
}
/**
* Test 2: PrivateBrowsing in-session restore
* PrivateBrowsing in-session restore
*
* @note: Non-Mac only
*
* Should do the following:
* 1. Open a new browser window A
* 2. Add some tabs
* 3. Close the window A as the last window
* 4. Open a private browsing window B
* 5. Make sure that B didn't restore the tabs from A
* 6. Close private browsing window B
* 7. Open a new window C
* 8. Make sure that new window C has restored tabs from A
*/
function testOpenClosePrivateBrowsing(nextFn) {
setupTestAndRun(false, function(newWin) {
// Close the window
newWin.BrowserTryToCloseWindow();
add_task(function* test_open_close_private_browsing() {
if (IS_MAC) {
return;
}
// Enter private browsing mode
// Open a new window.
// The previously closed window should NOT be restored
whenNewWindowLoaded({private: true}, function (newWin) {
yield setupTest({}, function*(newWin, obs) {
let closed = yield closeWindowForRestoration(newWin);
ok(closed, "Should be able to close the window");
newWin = yield promiseNewWindowLoaded({private: true});
is(newWin.gBrowser.browsers.length, 1,
"Did not restore in private browing mode");
// Cleanup
newWin.BrowserTryToCloseWindow();
closed = yield closeWindowForRestoration(newWin);
ok(closed, "Should be able to close the window");
// Exit private browsing mode again
whenNewWindowLoaded({}, function (newWin) {
newWin = yield promiseNewWindowLoaded();
is(newWin.gBrowser.browsers.length, TEST_URLS.length + 2,
"Restored after leaving private browsing again");
"Restored tabs in a new non-private window");
newWin.close();
// Note that this will not result in the the browser-lastwindow-close
// notifications firing for this other newWin.
yield BrowserTestUtils.closeWindow(newWin);
// Next please
executeSoon(nextFn);
// We closed two windows with closeWindowForRestoration, and both
// should have been successful.
is(obs["browser-lastwindow-close-requested"], 2,
"Got expected browser-lastwindow-close-requested notifications");
is(obs["browser-lastwindow-close-granted"], 2,
"Got expected browser-lastwindow-close-granted notifications");
});
});
});
}
/**
* Test 3: Open some popup windows to check those aren't restored, but
* the browser window is
* Open some popup windows to check those aren't restored, but the browser
* window is.
*
* @note: Non-Mac only
*
* Should do the following:
* 1. Open a new browser window
* 2. Add some tabs
* 3. Open some popups
* 4. Add another tab to one popup (so that it gets stored) and close it again
* 5. Close the browser window
* 6. Open another browser window
* 7. Make sure that the tabs of the closed browser window, but not the popup,
* are restored
*/
function testOpenCloseWindowAndPopup(nextFn) {
setupTestAndRun(false, function(newWin) {
// open some popups
let popup = openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[0]);
let popup2 = openDialog(location, "popup2", POPUP_FEATURES, TEST_URLS[1]);
popup2.addEventListener("load", function() {
popup2.removeEventListener("load", arguments.callee, false);
popup2.gBrowser.addEventListener("load", function() {
popup2.gBrowser.removeEventListener("load", arguments.callee, true);
add_task(function* test_open_close_window_and_popup() {
if (IS_MAC) {
return;
}
yield setupTest({}, function*(newWin, obs) {
let popupPromise = BrowserTestUtils.waitForNewWindow();
openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[0]);
let popup = yield popupPromise;
let popup2Promise = BrowserTestUtils.waitForNewWindow();
openDialog(location, "popup2", POPUP_FEATURES, TEST_URLS[1]);
let popup2 = yield popup2Promise;
popup2.gBrowser.addTab(TEST_URLS[0]);
// close the window
newWin.BrowserTryToCloseWindow();
// Close the popup window
// The test is successful when not this popup window is restored
// but instead newWin
popup2.close();
let closed = yield closeWindowForRestoration(newWin);
ok(closed, "Should be able to close the window");
yield BrowserTestUtils.closeWindow(popup2);
newWin = yield promiseNewWindowLoaded();
// open a new window the previously closed window should be restored to
whenNewWindowLoaded({}, function (newWin) {
is(newWin.gBrowser.browsers.length, TEST_URLS.length + 2,
"Restored window and associated tabs in session");
// Cleanup
newWin.close();
popup.close();
yield BrowserTestUtils.closeWindow(popup);
yield BrowserTestUtils.closeWindow(newWin);
// Next please
executeSoon(nextFn);
// We closed one window with closeWindowForRestoration, and it should
// have been successful.
is(obs["browser-lastwindow-close-requested"], 1,
"Got expected browser-lastwindow-close-requested notifications");
is(obs["browser-lastwindow-close-granted"], 1,
"Got expected browser-lastwindow-close-granted notifications");
});
}, true);
}, false);
});
}
/**
* Test 4: Open some popup window to check it isn't restored.
* Instead nothing at all should be restored
* Open some popup window to check it isn't restored. Instead nothing at all
* should be restored
*
* @note: Non-Mac only
*
* Should do the following:
* 1. Open a popup
* 2. Add another tab to the popup (so that it gets stored) and close it again
* 3. Open a window
* 4. Check that nothing at all is restored
* 5. Open two browser windows and close them again
* 6. undoCloseWindow() one
* 7. Open another browser window
* 8. Check that nothing at all is restored
*/
function testOpenCloseOnlyPopup(nextFn) {
// prepare the prefs
setPrefs();
add_task(function* test_open_close_only_popup() {
if (IS_MAC) {
return;
}
yield setupTest({}, function*(newWin, obs) {
// We actually don't care about the initial window in this test.
yield BrowserTestUtils.closeWindow(newWin);
// This will cause nsSessionStore to restore a window the next time it
// gets a chance.
let popup = openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[1]);
popup.addEventListener("load", function() {
this.removeEventListener("load", arguments.callee, true);
let popupPromise = BrowserTestUtils.waitForNewWindow();
openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[1]);
let popup = yield popupPromise;
is(popup.gBrowser.browsers.length, 1,
"Did not restore the popup window (1)");
popup.BrowserTryToCloseWindow();
// Real tests
popup = openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[1]);
popup.addEventListener("load", function() {
popup.removeEventListener("load", arguments.callee, false);
popup.gBrowser.addEventListener("load", function() {
popup.gBrowser.removeEventListener("load", arguments.callee, true);
let closed = yield closeWindowForRestoration(popup);
ok(closed, "Should be able to close the window");
popupPromise = BrowserTestUtils.waitForNewWindow();
openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[1]);
popup = yield popupPromise;
popup.gBrowser.addTab(TEST_URLS[0]);
is(popup.gBrowser.browsers.length, 2,
"Did not restore to the popup window (2)");
// Close the popup window
// The test is successful when not this popup window is restored
// but instead a new window is opened without restoring anything
popup.close();
yield BrowserTestUtils.closeWindow(popup);
whenNewWindowLoaded({}, function (newWin) {
newWin = yield promiseNewWindowLoaded();
isnot(newWin.gBrowser.browsers.length, 2,
"Did not restore the popup window");
is(TEST_URLS.indexOf(newWin.gBrowser.browsers[0].currentURI.spec), -1,
"Did not restore the popup window (2)");
yield BrowserTestUtils.closeWindow(newWin);
// Cleanup
newWin.close();
// Next please
executeSoon(nextFn);
// We closed one popup window with closeWindowForRestoration, and popup
// windows should never fire the browser-lastwindow notifications.
is(obs["browser-lastwindow-close-requested"], 0,
"Got expected browser-lastwindow-close-requested notifications");
is(obs["browser-lastwindow-close-granted"], 0,
"Got expected browser-lastwindow-close-granted notifications");
});
});
}, true);
}, false);
}, true);
}
/**
* Test 5: Open some windows and do undoCloseWindow. This should prevent any
* Open some windows and do undoCloseWindow. This should prevent any
* restoring later in the test
*
* @note: Non-Mac only
*
* Should do the following:
* 1. Open two browser windows and close them again
* 2. undoCloseWindow() one
* 3. Open another browser window
* 4. Make sure nothing at all is restored
*/
function testOpenCloseRestoreFromPopup(nextFn) {
setupTestAndRun(false, function(newWin) {
setupTestAndRun(false, function(newWin2) {
newWin.BrowserTryToCloseWindow();
newWin2.BrowserTryToCloseWindow();
add_task(function* test_open_close_restore_from_popup() {
if (IS_MAC) {
return;
}
browserWindowsCount([0, 1], "browser windows while running testOpenCloseRestoreFromPopup");
yield setupTest({}, function*(newWin, obs) {
let newWin2 = yield promiseNewWindowLoaded();
yield injectTestTabs(newWin2);
let closed = yield closeWindowForRestoration(newWin);
ok(closed, "Should be able to close the window");
closed = yield closeWindowForRestoration(newWin2);
ok(closed, "Should be able to close the window");
let counts = getBrowserWindowsCount();
is(counts.open, 0, "Got right number of open windows");
is(counts.winstates, 1, "Got right number of window states");
newWin = undoCloseWindow(0);
newWin.addEventListener("load", function whenloaded() {
newWin.removeEventListener("load", whenloaded, false);
yield BrowserTestUtils.waitForEvent(newWin, "load");
newWin.gBrowser.tabContainer.addEventListener("SSTabRestored", function whenSSTabRestored() {
newWin.gBrowser.tabContainer.removeEventListener("SSTabRestored", whenSSTabRestored, false);
// Make sure we wait until this window is restored.
yield BrowserTestUtils.waitForEvent(newWin.gBrowser.tabContainer,
"SSTabRestored");
newWin2 = yield promiseNewWindowLoaded();
whenNewWindowLoaded({}, function (newWin2) {
is(newWin2.gBrowser.browsers.length, 1,
"Did not restore, as undoCloseWindow() was last called");
is(TEST_URLS.indexOf(newWin2.gBrowser.browsers[0].currentURI.spec), -1,
"Did not restore, as undoCloseWindow() was last called (2)");
browserWindowsCount([2, 3], "browser windows while running testOpenCloseRestoreFromPopup");
counts = getBrowserWindowsCount();
is(counts.open, 2, "Got right number of open windows");
is(counts.winstates, 3, "Got right number of window states");
// Cleanup
newWin.close();
newWin2.close();
yield BrowserTestUtils.closeWindow(newWin);
yield BrowserTestUtils.closeWindow(newWin2);
browserWindowsCount([0, 1], "browser windows while running testOpenCloseRestoreFromPopup");
// Next please
executeSoon(nextFn);
});
}, false);
}, false);
counts = getBrowserWindowsCount();
is(counts.open, 0, "Got right number of open windows");
is(counts.winstates, 1, "Got right number of window states");
});
});
}
/**
* Test 7: Check whether the right number of notifications was received during
* the tests
*/
function testNotificationCount(nextFn) {
is(observing["browser-lastwindow-close-requested"], NOTIFICATIONS_EXPECTED,
"browser-lastwindow-close-requested notifications observed");
// -request must be one more as we cancel the first one we hit,
// and hence won't produce a corresponding -grant
// @see observer.observe
is(observing["browser-lastwindow-close-requested"],
observing["browser-lastwindow-close-granted"] + 1,
"Notification count for -request and -grant matches");
executeSoon(nextFn);
}
/**
* Test 8: Test if closing can be denied on Mac
* Futhermore prepares the testNotificationCount test (Test 7)
* Test if closing can be denied on Mac.
* @note: Mac only
*/
function testMacNotifications(nextFn, iteration) {
iteration = iteration || 1;
setupTestAndRun(false, function(newWin) {
// close the window
// window.close doesn't push any close events,
// so use BrowserTryToCloseWindow
newWin.BrowserTryToCloseWindow();
if (iteration == 1) {
ok(!newWin.closed, "First close attempt denied");
if (!newWin.closed) {
newWin.BrowserTryToCloseWindow();
ok(newWin.closed, "Second close attempt granted");
}
add_task(function* test_mac_notifications() {
if (!IS_MAC) {
return;
}
if (iteration < NOTIFICATIONS_EXPECTED - 1) {
executeSoon(() => testMacNotifications(nextFn, ++iteration));
}
else {
executeSoon(nextFn);
}
});
}
yield setupTest({ denyFirst: true }, function*(newWin, obs) {
let closed = yield closeWindowForRestoration(newWin);
ok(!closed, "First close attempt should be denied");
closed = yield closeWindowForRestoration(newWin);
ok(closed, "Second close attempt should be granted");
// Execution starts here
// We tried closing once, and got denied. Then we tried again and
// succeeded. That means 2 close requests, and 1 close granted.
is(obs["browser-lastwindow-close-requested"], 2,
"Got expected browser-lastwindow-close-requested notifications");
is(obs["browser-lastwindow-close-granted"], 1,
"Got expected browser-lastwindow-close-granted notifications");
});
});
setupTestsuite();
if (navigator.platform.match(/Mac/)) {
// Mac tests
testMacNotifications(function () {
testNotificationCount(function () {
cleanupTestsuite();
browserWindowsCount(1, "Only one browser window should be open eventually");
finish();
});
});
}
else {
// Non-Mac Tests
testOpenCloseNormal(function () {
browserWindowsCount([0, 1], "browser windows after testOpenCloseNormal");
testOpenClosePrivateBrowsing(function () {
browserWindowsCount([0, 1], "browser windows after testOpenClosePrivateBrowsing");
testOpenCloseWindowAndPopup(function () {
browserWindowsCount([0, 1], "browser windows after testOpenCloseWindowAndPopup");
testOpenCloseOnlyPopup(function () {
browserWindowsCount([0, 1], "browser windows after testOpenCloseOnlyPopup");
testOpenCloseRestoreFromPopup(function () {
browserWindowsCount([0, 1], "browser windows after testOpenCloseRestoreFromPopup");
testNotificationCount(function () {
cleanupTestsuite();
browserWindowsCount(1, "browser windows after testNotificationCount");
finish();
});
});
});
});
});
});
}
}

View File

@ -32,10 +32,7 @@ function test() {
let browser = newWin.gBrowser.selectedBrowser;
setInputChecked(browser, {id: "chk", checked: true}).then(() => {
newWin.close();
// Now give it time to close
executeSoon(function() {
BrowserTestUtils.closeWindow(newWin).then(() => {
is(ss.getClosedWindowCount(), 1,
"The closed window was added to Recently Closed Windows");
let data = JSON.parse(ss.getClosedWindowData())[0];
@ -75,8 +72,7 @@ function test() {
"The window correctly restored the data associated with it");
// clean up
newWin2.close();
finish();
BrowserTestUtils.closeWindow(newWin2).then(finish);
}, true);
});
});

View File

@ -1,19 +1,44 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Test helper function that opens a series of windows, closes them
* and then checks the closed window data from SessionStore against
* expected results.
*
* @param windowsToOpen (Array)
* An array of Objects, where each object must define a single
* property "isPopup" for whether or not the opened window should
* be a popup.
* @param expectedResults (Array)
* An Object with two properies: mac and other, where each points
* at yet another Object, with the following properties:
*
* popup (int):
* The number of popup windows we expect to be in the closed window
* data.
* normal (int):
* The number of normal windows we expect to be in the closed window
* data.
* @returns Promise
*/
function testWindows(windowsToOpen, expectedResults) {
return Task.spawn(function*() {
for (let winData of windowsToOpen) {
let features = "chrome,dialog=no," +
(winData.isPopup ? "all=no" : "all");
let url = "http://example.com/?window=" + windowsToOpen.length;
function test() {
// This test takes quite some time, and timeouts frequently, so we require
// more time to run.
// See Bug 518970.
requestLongerTimeout(2);
let openWindowPromise = BrowserTestUtils.waitForNewWindow();
openDialog(getBrowserURL(), "", features, url);
let win = yield openWindowPromise;
yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
waitForExplicitFinish();
if (win.gMultiProcessBrowser) {
let tab = win.gBrowser.selectedTab;
yield promiseTabRestored(tab);
}
yield BrowserTestUtils.closeWindow(win);
}
// helper function that does the actual testing
function openWindowRec(windowsToOpen, expectedResults, recCallback) {
// do actual checking
if (!windowsToOpen.length) {
let closedWindowData = JSON.parse(ss.getClosedWindowData());
let numPopups = closedWindowData.filter(function(el, i, arr) {
return el.isPopup;
@ -23,41 +48,17 @@ function test() {
let oResults = navigator.platform.match(/Mac/) ? expectedResults.mac
: expectedResults.other;
is(numPopups, oResults.popup,
"There were " + oResults.popup + " popup windows to repoen");
"There were " + oResults.popup + " popup windows to reopen");
is(numNormal, oResults.normal,
"There were " + oResults.normal + " normal windows to repoen");
// cleanup & return
executeSoon(recCallback);
return;
}
// hack to force window to be considered a popup (toolbar=no didn't work)
let winData = windowsToOpen.shift();
let settings = "chrome,dialog=no," +
(winData.isPopup ? "all=no" : "all");
let url = "http://example.com/?window=" + windowsToOpen.length;
provideWindow(function onTestURLLoaded(win) {
let tabReady = () => {
win.close();
// Give it time to close
executeSoon(function() {
openWindowRec(windowsToOpen, expectedResults, recCallback);
});
};
}
if (win.gMultiProcessBrowser) {
let tab = win.gBrowser.selectedTab;
tab.addEventListener("SSTabRestored", function onTabRestored() {
tab.removeEventListener("SSTabRestored", onTabRestored);
tabReady();
});
} else {
tabReady();
}
}, url, settings);
}
add_task(function* test_closed_window_states() {
// This test takes quite some time, and timeouts frequently, so we require
// more time to run.
// See Bug 518970.
requestLongerTimeout(2);
let windowsToOpen = [{isPopup: false},
{isPopup: false},
@ -66,6 +67,10 @@ function test() {
{isPopup: true}];
let expectedResults = {mac: {popup: 3, normal: 0},
other: {popup: 3, normal: 1}};
yield testWindows(windowsToOpen, expectedResults);
let windowsToOpen2 = [{isPopup: false},
{isPopup: false},
{isPopup: false},
@ -73,7 +78,6 @@ function test() {
{isPopup: false}];
let expectedResults2 = {mac: {popup: 0, normal: 3},
other: {popup: 0, normal: 3}};
openWindowRec(windowsToOpen, expectedResults, function() {
openWindowRec(windowsToOpen2, expectedResults2, finish);
yield testWindows(windowsToOpen2, expectedResults2);
});
}

View File

@ -15,25 +15,27 @@ const TESTS = [
function promiseTestOpenCloseWindow(aIsPrivate, aTest) {
return Task.spawn(function*() {
let win = yield promiseNewWindowLoaded({ "private": aIsPrivate });
let win = yield BrowserTestUtils.openNewBrowserWindow({ "private": aIsPrivate });
win.gBrowser.selectedBrowser.loadURI(aTest.url);
yield promiseBrowserLoaded(win.gBrowser.selectedBrowser);
yield Promise.resolve();
// Mark the window with some unique data to be restored later on.
ss.setWindowValue(win, aTest.key, aTest.value);
yield TabStateFlusher.flushWindow(win);
// Close.
yield promiseWindowClosed(win);
yield BrowserTestUtils.closeWindow(win);
});
}
function promiseTestOnWindow(aIsPrivate, aValue) {
return Task.spawn(function*() {
let win = yield promiseNewWindowLoaded({ "private": aIsPrivate });
let win = yield BrowserTestUtils.openNewBrowserWindow({ "private": aIsPrivate });
yield TabStateFlusher.flushWindow(win);
let data = JSON.parse(ss.getClosedWindowData())[0];
is(ss.getClosedWindowCount(), 1, "Check that the closed window count hasn't changed");
ok(JSON.stringify(data).indexOf(aValue) > -1,
"Check the closed window data was stored correctly");
registerCleanupFunction(() => promiseWindowClosed(win));
registerCleanupFunction(() => BrowserTestUtils.closeWindow(win));
});
}

View File

@ -66,8 +66,7 @@ function test() {
if (gPrefService.prefHasUserValue("browser.sessionstore.interval"))
gPrefService.clearUserPref("browser.sessionstore.interval");
cs.removeAll();
newWin.close();
finish();
BrowserTestUtils.closeWindow(newWin).then(finish);
};
function flushAndReady() {

View File

@ -67,8 +67,7 @@ function test() {
"... and tabs not specifically forgetten weren't.");
// clean up
newWin.close();
gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
finish();
BrowserTestUtils.closeWindow(newWin).then(finish);
});
}

View File

@ -78,9 +78,8 @@ function test() {
"... and tabs to be remembered weren't.");
// clean up
newWin.close();
gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
finish();
BrowserTestUtils.closeWindow(newWin).then(finish);
});
});
}

View File

@ -40,7 +40,6 @@ function test() {
"window value was correctly overwritten");
// clean up
newWin.close();
finish();
BrowserTestUtils.closeWindow(newWin).then(finish);
});
}

View File

@ -52,8 +52,7 @@ function test() {
isnot(newWin.windowState, newWin.STATE_MAXIMIZED,
"the window was explicitly unmaximized");
newWin.close();
finish();
BrowserTestUtils.closeWindow(newWin).then(finish);
}, 0);
}, 0);
}, 0);

View File

@ -2,73 +2,9 @@
* 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/. */
function test() {
/** Test for Bug 490040 **/
waitForExplicitFinish();
function testWithState(aState) {
// Ensure we can store the window if needed.
let curClosedWindowCount = ss.getClosedWindowCount();
gPrefService.setIntPref("browser.sessionstore.max_windows_undo",
curClosedWindowCount + 1);
var origWin;
function windowObserver(aSubject, aTopic, aData) {
let theWin = aSubject.QueryInterface(Ci.nsIDOMWindow);
if (origWin && theWin != origWin)
return;
switch (aTopic) {
case "domwindowopened":
origWin = theWin;
theWin.addEventListener("load", function () {
theWin.removeEventListener("load", arguments.callee, false);
executeSoon(function () {
// Close the window as soon as the first tab loads, or
// immediately if there are no tabs.
if (aState.windowState.windows[0].tabs[0].entries.length) {
theWin.gBrowser.addEventListener("load", function() {
theWin.gBrowser.removeEventListener("load",
arguments.callee, true);
theWin.close();
}, true);
} else {
executeSoon(function () {
theWin.close();
});
}
ss.setWindowState(theWin, JSON.stringify(aState.windowState),
true);
});
}, false);
break;
case "domwindowclosed":
Services.ww.unregisterNotification(windowObserver);
// Use executeSoon to ensure this happens after SS observer.
executeSoon(function () {
is(ss.getClosedWindowCount(),
curClosedWindowCount + (aState.shouldBeAdded ? 1 : 0),
"That window should " + (aState.shouldBeAdded ? "" : "not ") +
"be restorable");
executeSoon(runNextTest);
});
break;
}
}
Services.ww.registerNotification(windowObserver);
Services.ww.openWindow(null,
location,
"_blank",
"chrome,all,dialog=no",
null);
}
// Only windows with open tabs are restorable. Windows where a lone tab is
// detached may have _closedTabs, but is left with just an empty tab.
let states = [
{
const STATES = [{
shouldBeAdded: true,
windowState: {
windows: [{
@ -77,8 +13,7 @@ function test() {
_closedTabs: []
}]
}
},
{
}, {
shouldBeAdded: false,
windowState: {
windows: [{
@ -86,8 +21,7 @@ function test() {
_closedTabs: []
}]
}
},
{
}, {
shouldBeAdded: false,
windowState: {
windows: [{
@ -95,8 +29,7 @@ function test() {
_closedTabs: [{ state: { entries: [{ url: "http://example.com", index: 1 }] } }]
}]
}
},
{
}, {
shouldBeAdded: false,
windowState: {
windows: [{
@ -105,19 +38,28 @@ function test() {
extData: { keyname: "pi != " + Math.random() }
}]
}
}
];
}];
function runNextTest() {
if (states.length) {
let state = states.shift();
testWithState(state);
}
else {
gPrefService.clearUserPref("browser.sessionstore.max_windows_undo");
finish();
}
}
runNextTest();
add_task(function* test_bug_490040() {
for (let state of STATES) {
// Ensure we can store the window if needed.
let startingClosedWindowCount = ss.getClosedWindowCount();
yield pushPrefs(["browser.sessionstore.max_windows_undo",
startingClosedWindowCount + 1]);
let curClosedWindowCount = ss.getClosedWindowCount();
let win = yield BrowserTestUtils.openNewBrowserWindow();
ss.setWindowState(win, JSON.stringify(state.windowState), true);
if (state.windowState.windows[0].tabs.length) {
yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
}
yield BrowserTestUtils.closeWindow(win);
is(ss.getClosedWindowCount(),
curClosedWindowCount + (state.shouldBeAdded ? 1 : 0),
"That window should " + (state.shouldBeAdded ? "" : "not ") +
"be restorable");
}
});

View File

@ -114,8 +114,7 @@ function test() {
"... and windows not specifically forgetten weren't.");
// clean up
newWin.close();
gPrefService.clearUserPref("browser.sessionstore.max_windows_undo");
finish();
BrowserTestUtils.closeWindow(newWin).then(finish);
});
}

View File

@ -10,13 +10,12 @@ function test() {
let newWin = openDialog(location, "_blank", "chrome,all,dialog=no,toolbar=yes");
promiseWindowLoaded(newWin).then(() => {
let state1 = ss.getWindowState(newWin);
newWin.close();
BrowserTestUtils.closeWindow(newWin).then(() => {
newWin = openDialog(location, "_blank",
"chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar=no,location,personal,directories,dialog=no");
promiseWindowLoaded(newWin).then(() => {
let state2 = ss.getWindowState(newWin);
newWin.close();
function testState(state, expected, callback) {
let win = openDialog(location, "_blank", "chrome,all,dialog=no");
@ -32,14 +31,16 @@ function test() {
is(win.gURLBar.getAttribute("enablehistory"), expected.enablehistory,
"URL bar autocomplete state should be restored correctly");
win.close();
executeSoon(callback);
BrowserTestUtils.closeWindow(win).then(callback);
});
}
BrowserTestUtils.closeWindow(newWin).then(() => {
testState(state1, {readOnly: false, enablehistory: "true"}, function() {
testState(state2, {readOnly: true, enablehistory: "false"}, finish);
});
});
});
});
});
}

View File

@ -31,8 +31,7 @@ function test() {
gotError = true;
}
ok(!gotError, "Didn't get a malformed URI error.");
theWin.close();
finish();
BrowserTestUtils.closeWindow(theWin).then(finish);
});
}, false);
}

View File

@ -35,8 +35,7 @@ function test() {
// Cleanup
window.restore();
window_B.close();
finish();
BrowserTestUtils.closeWindow(window_B).then(finish);
});
}, window_B);
});

View File

@ -11,16 +11,11 @@ function browserWindowsCount(expected) {
"number of open browser windows according to getBrowserState");
}
function test() {
waitForExplicitFinish();
add_task(function() {
browserWindowsCount(1);
var win = openDialog(location, "", "chrome,all,dialog=no");
promiseWindowLoaded(win).then(() => {
let win = yield BrowserTestUtils.openNewBrowserWindow();
browserWindowsCount(2);
win.close();
yield BrowserTestUtils.closeWindow(win);
browserWindowsCount(1);
finish();
});
}

View File

@ -22,10 +22,17 @@ function test() {
function closeFirstWin(win) {
win.gBrowser.pinTab(win.gBrowser.tabs[0]);
win.gBrowser.pinTab(win.gBrowser.tabs[1]);
let winClosed = BrowserTestUtils.windowClosed(win);
// We need to call BrowserTryToCloseWindow in order to trigger
// the machinery that chooses whether or not to save the session
// for the last window.
win.BrowserTryToCloseWindow();
ok(win.closed, "window closed");
winClosed.then(() => {
openWinWithCb(checkSecondWin, URIS_NORMAL_B, URIS_PINNED.concat(URIS_NORMAL_B));
});
}
function checkSecondWin(win) {
@ -33,11 +40,12 @@ function checkSecondWin(win) {
is(win.gBrowser.browsers[1].currentURI.spec, URIS_PINNED[1], "second pinned tab restored");
ok(win.gBrowser.tabs[0].pinned, "first pinned tab is still pinned");
ok(win.gBrowser.tabs[1].pinned, "second pinned tab is still pinned");
win.close();
BrowserTestUtils.closeWindow(win).then(() => {
// cleanup
document.documentElement.setAttribute("windowtype", "navigator:browser");
finish();
});
}
function openWinWithCb(cb, argURIs, expectedURIs) {

View File

@ -10,7 +10,7 @@ function test() {
waitForExplicitFinish();
newWindowWithState(state, function (win) {
registerCleanupFunction(() => win.close());
registerCleanupFunction(() => BrowserTestUtils.closeWindow(win));
is(win.gBrowser.tabs.length, 2, "two tabs were restored");
is(win.gBrowser.visibleTabs.length, 1, "one tab is visible");

View File

@ -234,8 +234,9 @@ function onWindowUnloaded() {
function afterTestCleanup(aNewWin) {
executeSoon(function() {
aNewWin.close();
BrowserTestUtils.closeWindow(aNewWin).then(() => {
document.documentElement.setAttribute("windowtype", originalWindowType);
runNextTestOrFinish();
});
});
}

View File

@ -17,7 +17,7 @@ function test() {
waitForExplicitFinish();
newWindowWithState(state, function (win) {
registerCleanupFunction(() => win.close());
registerCleanupFunction(() => BrowserTestUtils.closeWindow(win));
is(gBrowser.tabs.length, 1, "The total number of tabs should be 1");
is(gBrowser.visibleTabs.length, 1, "The total number of visible tabs should be 1");

View File

@ -101,7 +101,7 @@ function newWindowWithState(state, callback) {
let opts = "chrome,all,dialog=no,height=800,width=800";
let win = window.openDialog(getBrowserURL(), "_blank", opts);
registerCleanupFunction(() => win.close());
registerCleanupFunction(() => BrowserTestUtils.closeWindow(win));
whenWindowLoaded(win, function onWindowLoaded(aWin) {
TabsProgressListener.init(aWin);

View File

@ -19,16 +19,16 @@ add_task(function test_close_last_nonpopup_window() {
ss.setWindowState(window, JSON.stringify(popupState), true);
// Open a new window with a tab.
let win = yield promiseNewWindowLoaded({private: false});
let win = yield BrowserTestUtils.openNewBrowserWindow({private: false});
let tab = win.gBrowser.addTab("http://example.com/");
yield promiseBrowserLoaded(tab.linkedBrowser);
yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
// Make sure sessionstore sees this window.
let state = JSON.parse(ss.getBrowserState());
is(state.windows.length, 2, "sessionstore knows about this window");
// Closed the window and check the closed window count.
yield promiseWindowClosed(win);
yield BrowserTestUtils.closeWindow(win);
is(ss.getClosedWindowCount(), 1, "correct closed window count");
// Cleanup.

View File

@ -66,13 +66,16 @@ function done() {
// Enumerate windows and close everything but our primary window. We can't
// use waitForFocus() because apparently it's buggy. See bug 599253.
let windowsEnum = Services.wm.getEnumerator("navigator:browser");
let closeWinPromises = [];
while (windowsEnum.hasMoreElements()) {
let currentWindow = windowsEnum.getNext();
if (currentWindow != window)
currentWindow.close();
closeWinPromises.push(BrowserTestUtils.closeWindow(currentWindow));
}
Promise.all(closeWinPromises).then(() => {
waitForBrowserState(stateBackup, finish);
});
}
// Count up the number of tabs in the state data

View File

@ -61,15 +61,15 @@ function runNextTest() {
// Enumerate windows and close everything but our primary window. We can't
// use waitForFocus() because apparently it's buggy. See bug 599253.
var windowsEnum = Services.wm.getEnumerator("navigator:browser");
let closeWinPromises = [];
while (windowsEnum.hasMoreElements()) {
var currentWindow = windowsEnum.getNext();
if (currentWindow != window) {
currentWindow.close();
closeWinPromises.push(BrowserTestUtils.closeWindow(currentWindow));
}
}
// If we closed a window, give it time to close
executeSoon(function() {
Promise.all(closeWinPromises).then(() => {
let currentTest = tests.shift();
info("prepping for " + currentTest.name);
waitForBrowserState(testState, currentTest);
@ -319,9 +319,8 @@ function test_undoCloseWindow() {
waitForBrowserState(lameMultiWindowState, function() {
// Close the window which isn't window
newWindow.close();
BrowserTestUtils.closeWindow(newWindow).then(() => {
// Now give it time to close
executeSoon(function() {
reopenedWindow = ss.undoCloseWindow(0);
reopenedWindow.addEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
reopenedWindow.addEventListener("SSWindowStateReady", onSSWindowStateReady, false);
@ -357,9 +356,6 @@ function test_undoCloseWindow() {
reopenedWindow.removeEventListener("SSWindowStateReady", onSSWindowStateReady, false);
reopenedWindow.gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, false);
reopenedWindow.close();
// Give it time to close
executeSoon(runNextTest);
BrowserTestUtils.closeWindow(reopenedWindow).then(runNextTest);
}
}

View File

@ -27,16 +27,19 @@ function runNextTest() {
// Enumerate windows and close everything but our primary window. We can't
// use waitForFocus() because apparently it's buggy. See bug 599253.
var windowsEnum = Services.wm.getEnumerator("navigator:browser");
let closeWinPromises = [];
while (windowsEnum.hasMoreElements()) {
var currentWindow = windowsEnum.getNext();
if (currentWindow != window) {
currentWindow.close();
closeWinPromises.push(BrowserTestUtils.closeWindow(currentWindow));
}
}
Promise.all(closeWinPromises).then(() => {
let currentTest = tests.shift();
info("running " + currentTest.name);
waitForBrowserState(testState, currentTest);
});
}
else {
ss.setBrowserState(stateBackup);

View File

@ -31,7 +31,7 @@ add_task(function* new_window() {
// Double check that we have no closed windows
is(ss.getClosedWindowCount(), 0, "no closed windows on first save");
yield promiseWindowClosed(newWin);
yield BrowserTestUtils.closeWindow(newWin);
newWin = null;
let state = JSON.parse((yield promiseRecoveryFileContents()));
@ -45,7 +45,7 @@ add_task(function* new_window() {
"observe1: 1 closed window according to API");
} finally {
if (newWin) {
yield promiseWindowClosed(newWin);
yield BrowserTestUtils.closeWindow(newWin);
}
yield forceSaveState();
}

View File

@ -55,7 +55,7 @@ add_task(function* test() {
checkWindows();
// Cleanup.
yield promiseWindowClosed(win);
yield BrowserTestUtils.closeWindow(win);
yield promiseBrowserState(backupState);
});

View File

@ -47,8 +47,9 @@ function windowObserver(aSubject, aTopic, aData) {
win.Scratchpad.removeObserver(this);
let state = win.Scratchpad.getState();
win.close();
BrowserTestUtils.closeWindow(win).then(() => {
addState(state);
});
},
});
}

View File

@ -14,10 +14,11 @@ function test() {
win.addEventListener("SSWindowClosing", function onWindowClosing() {
win.removeEventListener("SSWindowClosing", onWindowClosing, false);
eventReceived = true;
waitForFocus(finish);
}, false);
win.close();
BrowserTestUtils.closeWindow(win).then(() => {
waitForFocus(finish);
});
});
}

View File

@ -32,7 +32,7 @@ function newWindowWithState(aState, aCallback) {
let opts = "chrome,all,dialog=no,height=800,width=800";
let win = window.openDialog(getBrowserURL(), "_blank", opts);
registerCleanupFunction(() => win.close());
registerCleanupFunction(() => BrowserTestUtils.closeWindow(win));
whenWindowLoaded(win, function onWindowLoaded(aWin) {
ss.setWindowState(aWin, JSON.stringify(aState), true);

View File

@ -82,7 +82,7 @@ add_task(function* test_3() {
is(curState.windows[2].isPrivate, true, "Window 2 is private");
is(curState.selectedWindow, 4, "Last window opened is the one selected");
yield promiseWindowClosed(normalWindow);
yield BrowserTestUtils.closeWindow(normalWindow);
// Pin and unpin a tab before checking the written state so that
// the list of restoring windows gets cleared. Otherwise the

View File

@ -30,7 +30,7 @@ add_task(function* () {
// Wait until the new window was restored.
let win = yield waitForNewWindow();
yield promiseWindowClosed(win);
yield BrowserTestUtils.closeWindow(win);
let [{tabs: [{entries: [{url}]}]}] = JSON.parse(ss.getClosedWindowData());
is(url, "about:mozilla", "session was restored correctly");

View File

@ -74,7 +74,7 @@ add_task(function flush_on_windowclose() {
let browser = tab.linkedBrowser;
yield modifySessionStorage(browser, {test: "on-window-close"});
yield closeWindow(win);
yield BrowserTestUtils.closeWindow(win);
let [{tabs: [_, {storage}]}] = JSON.parse(ss.getClosedWindowData());
is(storage["http://example.com"].test, "on-window-close",
@ -138,24 +138,6 @@ function promiseNewWindow() {
return deferred.promise;
}
function closeWindow(win) {
let deferred = Promise.defer();
let outerID = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
Services.obs.addObserver(function obs(subject, topic) {
let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (id == outerID) {
Services.obs.removeObserver(obs, topic);
deferred.resolve();
}
}, "outer-window-destroyed", false);
win.close();
return deferred.promise;
}
function createTabWithStorageData(urls, win = window) {
return Task.spawn(function task() {
let tab = win.gBrowser.addTab();

View File

@ -55,7 +55,8 @@ add_task(function* test_open_and_close() {
yield promiseBrowserLoaded(tab.linkedBrowser);
yield TabStateFlusher.flushWindow(window);
yield TabStateFlusher.flushWindow(newWin);
info("1. Making sure that before closing, we don't have closedAt");
// For the moment, no "closedAt"
@ -65,12 +66,10 @@ add_task(function* test_open_and_close() {
is(state.windows[0].tabs[0].closedAt || false, false, "1. First tab doesn't have closedAt");
is(state.windows[0].tabs[1].closedAt || false, false, "1. Second tab doesn't have closedAt");
info("2. Making sure that after closing, we have closedAt");
// Now close stuff, this should add closeAt
yield promiseWindowClosed(newWin);
yield BrowserTestUtils.closeWindow(newWin);
yield promiseRemoveTab(newTab1);
yield promiseRemoveTab(newTab2);
@ -103,7 +102,7 @@ add_task(function* test_restore() {
is(state.windows[0].tabs[0].closedAt || false, false, "3. First tab doesn't have closedAt");
is(state.windows[0].tabs[1].closedAt || false, false, "3. Second tab doesn't have closedAt");
yield promiseWindowClosed(newWin);
yield BrowserTestUtils.closeWindow(newWin);
gBrowser.removeTab(newTab1);
gBrowser.removeTab(newTab2);
});

View File

@ -31,7 +31,7 @@ add_task(function* test() {
let closedTabData = ss.getClosedTabData(win);
// Close our window.
yield promiseWindowClosed(win);
yield BrowserTestUtils.closeWindow(win);
// SessionStore should no longer track our window
// but it should still report the same state.

View File

@ -65,7 +65,7 @@ add_task(function () {
// Clean up.
gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
win.close();
yield BrowserTestUtils.closeWindow(win);
});

View File

@ -97,7 +97,7 @@ add_task(function () {
// Check that all private tabs are removed when the non-private
// window is closed and we don't save windows without any tabs.
yield promiseWindowClosed(win);
yield BrowserTestUtils.closeWindow(win);
is(ss.getClosedWindowCount(), 0, "no windows to restore");
});
@ -123,7 +123,7 @@ add_task(function () {
is(ss.getClosedTabCount(win), 1, "there is a single tab to restore");
// Ensure that closed private windows can never be restored.
yield promiseWindowClosed(win);
yield BrowserTestUtils.closeWindow(win);
is(ss.getClosedWindowCount(), 0, "no windows to restore");
});

View File

@ -14,7 +14,7 @@ add_task(function* () {
let browser = win.gBrowser.browsers[1];
is(browser.currentURI.spec, "about:mozilla", "tab was duplicated");
yield promiseWindowClosed(win);
yield BrowserTestUtils.closeWindow(win);
});
function promiseDelayedStartupFinished(win) {

View File

@ -19,9 +19,8 @@ function test() {
executeSoon(function () {
is(ss.getClosedWindowCount(), 0,
"The private window should not have been stored");
finish();
});
}, false);
win.close();
BrowserTestUtils.closeWindow(win).then(finish);
});
}

View File

@ -400,7 +400,7 @@ function promiseAllButPrimaryWindowClosed() {
}
}
return Promise.all(windows.map(promiseWindowClosed));
return Promise.all(windows.map(BrowserTestUtils.closeWindow));
}
// Forget all closed windows.
@ -438,7 +438,8 @@ function whenNewWindowLoaded(aOptions, aCallback) {
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad);
resolve(promiseBrowserLoaded(win.gBrowser.selectedBrowser));
let browser = win.gBrowser.selectedBrowser;
promiseBrowserLoaded(browser).then(resolve);
});
});
@ -448,24 +449,6 @@ function promiseNewWindowLoaded(aOptions) {
return new Promise(resolve => whenNewWindowLoaded(aOptions, resolve));
}
/**
* Chrome windows aren't closed synchronously. Provide a helper method to close
* a window and wait until we received the "domwindowclosed" notification for it.
*/
function promiseWindowClosed(win) {
let promise = new Promise(resolve => {
Services.obs.addObserver(function obs(subject, topic) {
if (subject == win) {
Services.obs.removeObserver(obs, topic);
resolve();
}
}, "domwindowclosed", false);
});
win.close();
return promise;
}
function runInContent(browser, func, arg, callback = null) {
let deferred = Promise.defer();
@ -567,3 +550,15 @@ function modifySessionStorage(browser, data, options = {}) {
});
});
}
function pushPrefs(...aPrefs) {
return new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve);
});
}
function popPrefs() {
return new Promise(resolve => {
SpecialPowers.popPrefEnv(resolve);
});
}

View File

@ -55,8 +55,7 @@ const ActorRegistryActor = protocol.ActorClass({
},
registerActor: method(function (sourceText, fileName, options) {
registerActor(sourceText, fileName, options);
return registerActor(sourceText, fileName, options).then(() => {
let { constructor, type } = options;
return ActorActor(this.conn, {
@ -64,6 +63,7 @@ const ActorRegistryActor = protocol.ActorClass({
tab: type.tab,
global: type.global
});
});
}, {
request: {
sourceText: Arg(0, "string"),

View File

@ -9,6 +9,7 @@
var { Cu, CC, Ci, Cc } = require("chrome");
const { DebuggerServer } = require("devtools/server/main");
const promise = require("promise");
/**
* Support for actor registration. Main used by ActorRegistryActor
@ -45,12 +46,14 @@ exports.registerActor = function(sourceText, fileName, options) {
// Also register in all child processes in case the current scope
// is chrome parent process.
if (!DebuggerServer.isInChildProcess) {
DebuggerServer.setupInChild({
return DebuggerServer.setupInChild({
module: "devtools/server/actors/utils/actor-registry-utils",
setupChild: "registerActor",
args: [sourceText, fileName, options]
args: [sourceText, fileName, options],
waitForEval: true
});
}
return promise.resolve();
}
exports.unregisterActor = function(options) {

View File

@ -69,7 +69,6 @@ var chromeGlobal = this;
m[setupChild].apply(m, args);
return true;
} catch(e) {
let error_msg = "exception during actor module setup running in the child process: ";
DevToolsUtils.reportException(error_msg + e);
@ -78,6 +77,12 @@ var chromeGlobal = this;
DevToolsUtils.safeErrorString(e));
return false;
}
if (msg.data.id) {
// Send a message back to know when it is processed
sendAsyncMessage("debug:setup-in-child-response",
{id: msg.data.id});
}
return true;
});
addMessageListener("debug:setup-in-child", onSetupInChild);

View File

@ -29,6 +29,10 @@ DevToolsUtils.defineLazyGetter(this, "DebuggerSocket", () => {
DevToolsUtils.defineLazyGetter(this, "Authentication", () => {
return require("devtools/shared/security/auth");
});
DevToolsUtils.defineLazyGetter(this, "generateUUID", () => {
let { generateUUID } = Cc['@mozilla.org/uuid-generator;1'].getService(Ci.nsIUUIDGenerator);
return generateUUID;
});
// On B2G, `this` != Global scope, so `Ci` won't be binded on `this`
// (i.e. this.Ci is undefined) Then later, when using loadSubScript,
@ -389,8 +393,7 @@ var DebuggerServer = {
type: { global: true }
});
}
let win = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
if (win && win.navigator.mozSettings) {
if (Services.prefs.getBoolPref("dom.mozSettings.enabled")) {
this.registerModule("devtools/server/actors/settings", {
prefix: "settings",
constructor: "SettingsActor",
@ -907,19 +910,51 @@ var DebuggerServer = {
* @param setupChild
* The name of the setup helper exported by the above module
* (setup helper signature: function ({mm}) { ... })
* @param waitForEval (optional)
* If true, the returned promise only resolves once code in child
* is evaluated
*/
setupInChild: function({ module, setupChild, args }) {
if (this.isInChildProcess) {
return;
setupInChild: function({ module, setupChild, args, waitForEval }) {
if (this.isInChildProcess || this._childMessageManagers.size == 0) {
return Promise.resolve();
}
let deferred = Promise.defer();
// If waitForEval is set, pass a unique id and expect child.js to send
// a message back once the code in child is evaluated.
if (typeof(waitForEval) != "boolean") {
waitForEval = false;
}
let count = this._childMessageManagers.size;
let id = waitForEval ? generateUUID().toString() : null;
this._childMessageManagers.forEach(mm => {
if (waitForEval) {
// Listen for the end of each child execution
let evalListener = msg => {
if (msg.data.id !== id) {
return;
}
mm.removeMessageListener("debug:setup-in-child-response", evalListener);
if (--count === 0) {
deferred.resolve();
}
};
mm.addMessageListener("debug:setup-in-child-response", evalListener);
}
mm.sendAsyncMessage("debug:setup-in-child", {
module: module,
setupChild: setupChild,
args: args,
id: id,
});
});
if (waitForEval) {
return deferred.promise;
} else {
return Promise.resolve();
}
},
/**
@ -1194,10 +1229,13 @@ var DebuggerServer = {
(handler.id && handler.id == aActor.id)) {
delete DebuggerServer.tabActorFactories[name];
for (let connID of Object.getOwnPropertyNames(this._connections)) {
// DebuggerServerConnection in child process don't have rootActor
if (this._connections[connID].rootActor) {
this._connections[connID].rootActor.removeActorByName(name);
}
}
}
}
},
/**
@ -1698,9 +1736,10 @@ DebuggerServerConnection.prototype = {
// Ignore this call if the connection is already closed.
return;
}
this._actorPool = null;
events.emit(this, "closed", aStatus);
this._actorPool = null;
this._extraPools.map(function(p) { p.destroy(); });
this._extraPools = null;

View File

@ -93,3 +93,4 @@ skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still di
skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
[browser_directorscript_actors.js]
skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
[browser_register_actor.js]

View File

@ -1,30 +1,7 @@
<!DOCTYPE HTML>
<html>
<!--
Bug 895360 - [app manager] Device meta data actor
-->
<head>
<meta charset="utf-8">
<title>Mozilla Bug</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script>
var gClient;
window.onload = function() {
var Cu = Components.utils;
var Cc = Components.classes;
var Ci = Components.interfaces;
var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
var {DebuggerClient} = require("devtools/shared/client/main");
var {DebuggerServer} = require("devtools/server/main");
Cu.import("resource://gre/modules/Services.jsm");
SimpleTest.waitForExplicitFinish();
function test() {
waitForExplicitFinish();
var {ActorRegistryFront} = require("devtools/server/actors/actor-registry");
var actorURL = "chrome://mochitests/content/chrome/devtools/server/tests/mochitest/hello-actor.js";
@ -56,7 +33,8 @@ window.onload = function() {
actorFront.unregister().then(() => {
gClient.close(() => {
DebuggerServer.destroy();
SimpleTest.finish();
gClient = null;
finish();
});
});
});
@ -96,7 +74,3 @@ function getCount(actor, callback) {
type: "count"
}, callback);
}
</script>
</pre>
</body>
</html>

View File

@ -98,7 +98,6 @@ skip-if = buildapp == 'mulet'
[test_memory_gc_events.html]
[test_memprof.html]
[test_preference.html]
[test_registerActor.html]
[test_settings.html]
[test_setupInParentChild.html]
[test_styles-applied.html]

View File

@ -228,8 +228,8 @@ public class GeckoMenu extends ListView
mItems.add(menuItem);
}
} else if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) {
if (actionView instanceof MenuItemActionView) {
final MenuItemActionView quickShareView = (MenuItemActionView) actionView;
if (actionView instanceof MenuItemSwitcherLayout) {
final MenuItemSwitcherLayout quickShareView = (MenuItemSwitcherLayout) actionView;
// We don't want to add the quick share bar if we don't have any quick share items.
if (quickShareView.getActionButtonCount() > 0 &&
@ -265,14 +265,14 @@ public class GeckoMenu extends ListView
return false;
}
});
} else if (actionView instanceof MenuItemActionView) {
((MenuItemActionView) actionView).setMenuItemClickListener(new View.OnClickListener() {
} else if (actionView instanceof MenuItemSwitcherLayout) {
((MenuItemSwitcherLayout) actionView).setMenuItemClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
handleMenuItemClick(menuItem);
}
});
((MenuItemActionView) actionView).setMenuItemLongClickListener(new View.OnLongClickListener() {
((MenuItemSwitcherLayout) actionView).setMenuItemLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (handleMenuItemLongClick(menuItem)) {
@ -607,7 +607,7 @@ public class GeckoMenu extends ListView
if (actionView instanceof MenuItemActionBar) {
((MenuItemActionBar) actionView).initialize(item);
} else {
((MenuItemActionView) actionView).initialize(item);
((MenuItemSwitcherLayout) actionView).initialize(item);
}
} else {
actionView.setVisibility(View.GONE);
@ -738,8 +738,8 @@ public class GeckoMenu extends ListView
params = new LinearLayout.LayoutParams(0, mRowHeight);
}
if (actionItem instanceof MenuItemActionView) {
params.weight = ((MenuItemActionView) actionItem).getChildCount();
if (actionItem instanceof MenuItemSwitcherLayout) {
params.weight = ((MenuItemSwitcherLayout) actionItem).getChildCount();
} else {
params.weight = 1.0f;
}
@ -818,17 +818,17 @@ public class GeckoMenu extends ListView
view = (GeckoMenuItem.Layout) convertView;
}
if (view == null || view instanceof MenuItemActionView) {
if (view == null || view instanceof MenuItemSwitcherLayout) {
// Always get from the menu item.
// This will ensure that the default activity is refreshed.
view = (MenuItemActionView) item.getActionView();
view = (MenuItemSwitcherLayout) item.getActionView();
// ListView will not perform an item click if the row has a focusable view in it.
// Hence, forward the click event on the menu item in the action-view to the ListView.
final View actionView = (View) view;
final int pos = position;
final long id = getItemId(position);
((MenuItemActionView) view).setMenuItemClickListener(new View.OnClickListener() {
((MenuItemSwitcherLayout) view).setMenuItemClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GeckoMenu listView = GeckoMenu.this;

View File

@ -20,7 +20,14 @@ import android.view.View;
import android.widget.ImageButton;
import android.widget.LinearLayout;
public class MenuItemActionView extends LinearLayout
/**
* This class is a container view for menu items that:
* * Shows text if there is enough space and there are
* no action buttons ({@link #mActionButtons}).
* * Shows an icon if there is not enough space for text,
* or there are action buttons.
*/
public class MenuItemSwitcherLayout extends LinearLayout
implements GeckoMenuItem.Layout,
View.OnClickListener {
private final MenuItemDefault mMenuItem;
@ -28,19 +35,19 @@ public class MenuItemActionView extends LinearLayout
private final List<ImageButton> mActionButtons;
private final List<View.OnClickListener> mActionButtonListeners = new ArrayList<View.OnClickListener>();
public MenuItemActionView(Context context) {
public MenuItemSwitcherLayout(Context context) {
this(context, null);
}
public MenuItemActionView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.menuItemActionViewStyle);
public MenuItemSwitcherLayout(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.menuItemSwitcherLayoutStyle);
}
@TargetApi(14)
public MenuItemActionView(Context context, AttributeSet attrs, int defStyle) {
public MenuItemSwitcherLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.menu_item_action_view, this);
LayoutInflater.from(context).inflate(R.layout.menu_item_switcher_layout, this);
mMenuItem = (MenuItemDefault) findViewById(R.id.menu_item);
mMenuButton = (MenuItemActionBar) findViewById(R.id.menu_item_button);
mActionButtons = new ArrayList<ImageButton>();
@ -170,7 +177,7 @@ public class MenuItemActionView extends LinearLayout
* Update the styles if this view is being used in the context menus.
*
* Ideally, we just use different layout files and styles to set this, but
* MenuItemActionView is too integrated into GeckoActionProvider to provide
* MenuItemSwitcherLayout is too integrated into GeckoActionProvider to provide
* an easy separation so instead I provide this hack. I'm sorry.
*/
public void initContextMenuStyles() {

View File

@ -19,10 +19,10 @@ import org.mozilla.gecko.R;
* uplifted, and this implementation will soon be replaced with the old implementation for adding
* the share plane to the menu (see https://bug1122302.bugzilla.mozilla.org/attachment.cgi?id=8572126).
*/
public class QuickShareBarActionView extends MenuItemActionView {
public class QuickShareBarActionView extends MenuItemSwitcherLayout {
public QuickShareBarActionView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.menuItemActionViewStyle);
this(context, attrs, R.attr.menuItemSwitcherLayoutStyle);
}
@TargetApi(14)

View File

@ -424,8 +424,8 @@ gbjar.sources += [
'menu/GeckoMenuItem.java',
'menu/GeckoSubMenu.java',
'menu/MenuItemActionBar.java',
'menu/MenuItemActionView.java',
'menu/MenuItemDefault.java',
'menu/MenuItemSwitcherLayout.java',
'menu/MenuPanel.java',
'menu/MenuPopup.java',
'menu/QuickShareBarActionView.java',

View File

@ -104,7 +104,7 @@ public class SendTabDeviceListArrayAdapter extends ArrayAdapter<RemoteClient> {
if (currentState != State.LIST) {
// If we're in a special "Button-like" state, use the override string and a generic icon.
final Drawable sendTabIcon = context.getResources().getDrawable(R.drawable.overlay_send_tab_icon);
final Drawable sendTabIcon = context.getResources().getDrawable(R.drawable.shareplane);
row.setText(dummyRecordName);
row.setDrawable(sendTabIcon);
}

View File

@ -1,17 +1,11 @@
package org.mozilla.gecko.prompts;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.menu.MenuItemActionView;
import org.mozilla.gecko.menu.MenuItemSwitcherLayout;
import org.mozilla.gecko.widget.GeckoActionProvider;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@ -25,7 +19,6 @@ import android.widget.CheckedTextView;
import android.widget.TextView;
import android.widget.ListView;
import android.widget.ArrayAdapter;
import android.widget.AdapterView;
import android.util.TypedValue;
import java.util.ArrayList;
@ -179,7 +172,7 @@ public class PromptListAdapter extends ArrayAdapter<PromptListItem> {
final GeckoActionProvider provider = GeckoActionProvider.getForType(item.getIntent().getType(), getContext());
provider.setIntent(item.getIntent());
final MenuItemActionView view = (MenuItemActionView) provider.onCreateActionView(
final MenuItemSwitcherLayout view = (MenuItemSwitcherLayout) provider.onCreateActionView(
GeckoActionProvider.ActionViewType.CONTEXT_MENU);
// If a quickshare button is clicked, we need to close the dialog.
view.addActionButtonClickListener(new View.OnClickListener() {
@ -195,7 +188,7 @@ public class PromptListAdapter extends ArrayAdapter<PromptListItem> {
return view;
}
private void updateActionView(final PromptListItem item, final MenuItemActionView view, final ListView list, final int position) {
private void updateActionView(final PromptListItem item, final MenuItemSwitcherLayout view, final ListView list, final int position) {
view.setTitle(item.label);
view.setIcon(item.getIcon());
view.setSubMenuIndicator(item.isParent);
@ -260,7 +253,7 @@ public class PromptListAdapter extends ArrayAdapter<PromptListItem> {
}
if (type == VIEW_TYPE_ACTIONS) {
updateActionView(item, (MenuItemActionView) convertView, (ListView) parent, position);
updateActionView(item, (MenuItemSwitcherLayout) convertView, (ListView) parent, position);
} else {
viewHolder.textView.setText(item.label);
maybeUpdateCheckedState((ListView) parent, position, item, viewHolder);

View File

Before

Width:  |  Height:  |  Size: 947 B

After

Width:  |  Height:  |  Size: 947 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -44,7 +44,7 @@
<item name="android:panelBackground">@drawable/menu_panel_bg</item>
<item name="android:spinnerDropDownItemStyle">@style/Widget.DropDownItem.Spinner</item>
<item name="android:spinnerItemStyle">@style/Widget.TextView.SpinnerItem</item>
<item name="menuItemActionViewStyle">@style/Widget.MenuItemActionView</item>
<item name="menuItemSwitcherLayoutStyle">@style/Widget.MenuItemSwitcherLayout</item>
<item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
<item name="menuItemSecondaryActionBarStyle">@style/Widget.MenuItemSecondaryActionBar</item>
<item name="tabGridLayoutViewStyle">@style/Widget.TabsGridLayout</item>

View File

@ -20,7 +20,7 @@
<item name="android:panelBackground">@drawable/menu_panel_bg</item>
<item name="android:spinnerDropDownItemStyle">@style/Widget.DropDownItem.Spinner</item>
<item name="android:spinnerItemStyle">@style/Widget.TextView.SpinnerItem</item>
<item name="menuItemActionViewStyle">@style/Widget.MenuItemActionView</item>
<item name="menuItemSwitcherLayoutStyle">@style/Widget.MenuItemSwitcherLayout</item>
<item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
<item name="menuItemSecondaryActionBarStyle">@style/Widget.MenuItemSecondaryActionBar</item>
</style>

View File

@ -40,7 +40,7 @@
<item name="android:panelBackground">@drawable/menu_panel_bg</item>
<item name="android:spinnerDropDownItemStyle">@style/Widget.DropDownItem.Spinner</item>
<item name="android:spinnerItemStyle">@style/Widget.TextView.SpinnerItem</item>
<item name="menuItemActionViewStyle">@style/Widget.MenuItemActionView</item>
<item name="menuItemSwitcherLayoutStyle">@style/Widget.MenuItemSwitcherLayout</item>
<item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
<item name="menuItemSecondaryActionBarStyle">@style/Widget.MenuItemSecondaryActionBar</item>
<item name="tabGridLayoutViewStyle">@style/Widget.TabsGridLayout</item>

View File

@ -16,8 +16,8 @@
<!-- Style for MenuItemActionBar -->
<attr name="menuItemActionModeStyle" format="reference"/>
<!-- Style for MenuItemActionView -->
<attr name="menuItemActionViewStyle" format="reference"/>
<!-- Style for MenuItemSwitcherLayout -->
<attr name="menuItemSwitcherLayoutStyle" format="reference"/>
<!-- Style for MenuItemDefault -->
<attr name="menuItemDefaultStyle" format="reference"/>
@ -25,7 +25,7 @@
<!-- Style for MenuItemActionBar when shown in SecondaryActionBar -->
<attr name="menuItemSecondaryActionBarStyle" format="reference"/>
<!-- Style for MenuItemActionView's ShareActionButton -->
<!-- Style for MenuItemSwitcherLayout's ShareActionButton -->
<attr name="menuItemShareActionButtonStyle" format="reference"/>
<!-- Default style for the BookmarksListView -->

View File

@ -99,7 +99,7 @@
<item name="drawableTintList">@color/action_bar_secondary_menu_item_colors</item>
</style>
<style name="Widget.MenuItemActionView">
<style name="Widget.MenuItemSwitcherLayout">
<item name="android:gravity">left</item>
</style>

View File

@ -10,9 +10,9 @@ import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.menu.MenuItemActionView;
import org.mozilla.gecko.menu.QuickShareBarActionView;
import org.mozilla.gecko.overlays.ui.ShareDialog;
import org.mozilla.gecko.menu.MenuItemSwitcherLayout;
import org.mozilla.gecko.util.ThreadUtils;
import android.content.Context;
@ -97,10 +97,10 @@ public class GeckoActionProvider {
public View onCreateActionView(final int maxHistorySize, final ActionViewType viewType) {
// Create the view and set its data model.
ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
final MenuItemActionView view;
final MenuItemSwitcherLayout view;
switch (viewType) {
case DEFAULT:
view = new MenuItemActionView(mContext, null);
view = new MenuItemSwitcherLayout(mContext, null);
break;
case QUICK_SHARE_ICON:
@ -108,7 +108,7 @@ public class GeckoActionProvider {
break;
case CONTEXT_MENU:
view = new MenuItemActionView(mContext, null);
view = new MenuItemSwitcherLayout(mContext, null);
view.initContextMenuStyles();
break;

View File

@ -257,18 +257,43 @@ this.BrowserTestUtils = {
}),
/**
* @param win (optional)
* The window we should wait to have "domwindowopened" sent through
* the observer service for. If this is not supplied, we'll just
* resolve when the first "domwindowopened" notification is seen.
* @return {Promise}
* A Promise which resolves when a "domwindowopened" notification
* has been fired by the window watcher.
*/
domWindowOpened() {
domWindowOpened(win) {
return new Promise(resolve => {
function observer(subject, topic, data) {
if (topic != "domwindowopened") { return; }
if (topic == "domwindowopened" && (!win || subject === win)) {
Services.ww.unregisterNotification(observer);
resolve(subject.QueryInterface(Ci.nsIDOMWindow));
}
}
Services.ww.registerNotification(observer);
});
},
/**
* @param win (optional)
* The window we should wait to have "domwindowclosed" sent through
* the observer service for. If this is not supplied, we'll just
* resolve when the first "domwindowclosed" notification is seen.
* @return {Promise}
* A Promise which resolves when a "domwindowclosed" notification
* has been fired by the window watcher.
*/
domWindowClosed(win) {
return new Promise((resolve) => {
function observer(subject, topic, data) {
if (topic == "domwindowclosed" && (!win || subject === win)) {
Services.ww.unregisterNotification(observer);
resolve(subject.QueryInterface(Ci.nsIDOMWindow));
}
}
Services.ww.registerNotification(observer);
});
},
@ -318,19 +343,55 @@ this.BrowserTestUtils = {
* A window to close.
*
* @return {Promise}
* Resolves when the provided window has been closed.
* Resolves when the provided window has been closed. For browser
* windows, the Promise will also wait until all final SessionStore
* messages have been sent up from all browser tabs.
*/
closeWindow(win) {
return new Promise(resolve => {
function observer(subject, topic, data) {
if (topic == "domwindowclosed" && subject === win) {
Services.ww.unregisterNotification(observer);
resolve();
}
}
Services.ww.registerNotification(observer);
let closedPromise = BrowserTestUtils.windowClosed(win);
win.close();
return closedPromise;
},
/**
* Returns a Promise that resolves when a window has finished closing.
*
* @param {Window}
* The closing window.
*
* @return {Promise}
* Resolves when the provided window has been fully closed. For
* browser windows, the Promise will also wait until all final
* SessionStore messages have been sent up from all browser tabs.
*/
windowClosed(win) {
let domWinClosedPromise = BrowserTestUtils.domWindowClosed(win);
let promises = [domWinClosedPromise];
let winType = win.document.documentElement.getAttribute("windowtype");
if (winType == "navigator:browser") {
let finalMsgsPromise = new Promise((resolve) => {
let browserSet = new Set(win.gBrowser.browsers);
let mm = win.getGroupMessageManager("browsers");
mm.addMessageListener("SessionStore:update", function onMessage(msg) {
if (browserSet.has(msg.target) && msg.data.isFinal) {
browserSet.delete(msg.target);
if (!browserSet.size) {
mm.removeMessageListener("SessionStore:update", onMessage);
// Give the TabStateFlusher a chance to react to this final
// update and for the TabStateFlusher.flushWindow promise
// to resolve before we resolve.
TestUtils.executeSoon(resolve);
}
}
}, true);
});
promises.push(finalMsgsPromise);
}
return Promise.all(promises);
},
/**

View File

@ -44,7 +44,7 @@ elif [[ $MULET =~ 'linux' ]] ; then
PLATFORM=linux
elif [[ $MULET =~ 'win32' ]] ; then
PLATFORM=win32
elif [[ $MULET =~ 'mac64' ]] ; then
elif [[ $MULET =~ 'mac' ]] ; then
PLATFORM=mac64
fi
if [ -z $PLATFORM ]; then
@ -110,14 +110,24 @@ if [[ $MULET =~ .dmg$ ]]; then
done
# Now we can copy everything out of the $MOUNTPOINT directory into the target directory
mkdir -p $ADDON_DIR/firefox
# Try builds are not branded as Firefox
if [ -d $WORK_DIR/dmg/Nightly.app ]; then
cp -r $WORK_DIR/dmg/Nightly.app $ADDON_DIR/firefox/FirefoxNightly.app
else
cp -r $WORK_DIR/dmg/FirefoxNightly.app $ADDON_DIR/firefox/
fi
hdiutil detach $WORK_DIR/DMG
else
7z x -o$WORK_DIR/dmg $MULET
mkdir -p $WORK_DIR/dmg/hfs
sudo mount -o loop,ro -t hfsplus $WORK_DIR/dmg/2.hfs $WORK_DIR/dmg/hfs
mkdir -p $ADDON_DIR/firefox
# Try builds are not branded as Firefox
if [ -d $WORK_DIR/dmg/hfs/Nightly.app ]; then
cp -r $WORK_DIR/dmg/hfs/Nightly.app $ADDON_DIR/firefox/FirefoxNightly.app
else
cp -r $WORK_DIR/dmg/hfs/FirefoxNightly.app $ADDON_DIR/firefox/
fi
sudo umount $WORK_DIR/dmg/hfs
fi
rm -rf $WORK_DIR/dmg

View File

@ -29,6 +29,7 @@
const SAMPLE_URL = "http://www.mozilla.org/";
const SAMPLE_TEXT = "Some text in a text field.";
const SEARCH_TEXT = "Text Test";
const NOT_FOUND_TEXT = "This text is not on the page."
var gFindBar = null;
var gBrowser;
@ -122,6 +123,7 @@
yield testFailedStringReset();
gFindBar.close();
yield testQuickFindClose();
yield Task.spawn(testFindAgainNotFound);
finish();
});
@ -544,6 +546,38 @@
"testClipboardSearchString: search string not set to '" + aExpected +
"', instead found '" + searchStr + "'");
}
// See bug 967982.
function* testFindAgainNotFound() {
yield openFindbar();
yield findAndWaitForResult(NOT_FOUND_TEXT);
gFindBar.close();
ok(gFindBar.hidden, "The findbar is closed.");
yield findAndWaitForResult();
ok(!gFindBar.hidden, "Unsuccessful Find Again opens the find bar.");
yield findAndWaitForResult(SEARCH_TEXT);
gFindBar.close();
ok(gFindBar.hidden, "The findbar is closed.");
yield findAndWaitForResult();
ok(gFindBar.hidden, "Successful Find Again leaves the find bar closed.");
}
function findAndWaitForResult(aText) {
return new Promise(resolve => {
gFindBar.browser.finder.addResultListener({
onFindResult: function(aData) {
gFindBar.browser.finder.removeResultListener(this);
resolve(aData);
}
});
if (aText) {
enterStringIntoFindField(aText);
} else {
gFindBar.onFindAgainCommand();
}
});
}
]]></script>
<commandset>

View File

@ -1152,14 +1152,20 @@
<method name="onFindResult">
<parameter name="aData"/>
<body><![CDATA[
if (aData.result == this.nsITypeAheadFind.FIND_NOTFOUND) {
// If an explicit Find Again command fails, re-open the toolbar.
if (aData.storeResult && this.open()) {
this._findField.select();
this._findField.focus();
}
this._findFailedString = aData.searchString;
} else {
this._findFailedString = null;
}
this._updateStatusUI(aData.result, aData.findBackwards);
this._updateStatusUIBar(aData.linkURL);
if (aData.result == this.nsITypeAheadFind.FIND_NOTFOUND)
this._findFailedString = aData.searchString;
else
this._findFailedString = null;
if (this._findMode != this.FIND_NORMAL)
this._setFindCloseTimeout();
]]></body>