merge m-c to fx-team; a=merge

This commit is contained in:
Tim Taubert 2014-07-06 09:16:35 -07:00
commit 1106fb6fb5
34 changed files with 1592 additions and 1377 deletions

View File

@ -346,9 +346,9 @@ function setupSearchEngine()
/**
* Inform the test harness that we're done loading the page.
*/
function loadSucceeded()
function loadCompleted()
{
var event = new CustomEvent("AboutHomeLoadSnippetsSucceeded", {bubbles:true});
var event = new CustomEvent("AboutHomeLoadSnippetsCompleted", {bubbles:true});
document.dispatchEvent(event);
}
@ -381,32 +381,29 @@ function loadSnippets()
if (updateURL && shouldUpdate) {
// Try to update from network.
let xhr = new XMLHttpRequest();
xhr.timeout = 5000;
try {
xhr.open("GET", updateURL, true);
} catch (ex) {
showSnippets();
loadSucceeded();
loadCompleted();
return;
}
// Even if fetching should fail we don't want to spam the server, thus
// set the last update time regardless its results. Will retry tomorrow.
gSnippetsMap.set("snippets-last-update", Date.now());
xhr.onerror = function (event) {
showSnippets();
};
xhr.onload = function (event)
{
xhr.onloadend = function (event) {
if (xhr.status == 200) {
gSnippetsMap.set("snippets", xhr.responseText);
gSnippetsMap.set("snippets-cached-version", currentVersion);
}
showSnippets();
loadSucceeded();
loadCompleted();
};
xhr.send(null);
} else {
showSnippets();
loadSucceeded();
loadCompleted();
}
}

View File

@ -5184,18 +5184,22 @@ function middleMousePaste(event) {
function stripUnsafeProtocolOnPaste(pasteData) {
// Don't allow pasting in full URIs which inherit the security context.
const URI_INHERITS_SECURITY_CONTEXT = Ci.nsIProtocolHandler.URI_INHERITS_SECURITY_CONTEXT;
let pastedURI;
pasteData = pasteData.trim();
do {
if (pastedURI) {
pasteData = pastedURI.path.trim();
}
try {
pastedURI = makeURI(pasteData.trim());
} catch (ex) {
return pasteData;
}
while (Services.netutil.URIChainHasFlags(pastedURI, URI_INHERITS_SECURITY_CONTEXT)) {
pasteData = pastedURI.path.trim();
try {
pastedURI = makeURI(pasteData);
} catch (ex) {
break;
}
} while (Services.netutil.URIChainHasFlags(pastedURI, URI_INHERITS_SECURITY_CONTEXT));
}
return pasteData;
}

View File

@ -334,8 +334,6 @@ skip-if = e10s # Bug ?????? - test directly manipulates content
skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
[browser_plainTextLinks.js]
skip-if = e10s # Bug ?????? - test directly manipulates content (creates and fetches elements directly from content document)
[browser_popupNotification.js]
skip-if = toolkit == "windows" || e10s # Disabled on Windows due to frequent failures (bugs 825739, 841341) / e10s - Bug ?????? - popup notification test probably confused re content process notifications etc
[browser_popupUI.js]
skip-if = e10s # Bug ?????? - test directly manipulates content (tries to get a popup element directly from content)
[browser_printpreview.js]

View File

@ -397,7 +397,7 @@ function test()
let snippetsPromise = promiseSetupSnippetsMap(tab, test.setup);
// Start loading about:home and wait for it to complete.
yield promiseTabLoadEvent(tab, "about:home", "AboutHomeLoadSnippetsSucceeded");
yield promiseTabLoadEvent(tab, "about:home", "AboutHomeLoadSnippetsCompleted");
// This promise should already be resolved since the page is done,
// but we still want to get the snippets map out of it.
@ -414,35 +414,6 @@ function test()
});
}
/**
* Starts a load in an existing tab and waits for it to finish (via some event).
*
* @param aTab
* The tab to load into.
* @param aUrl
* The url to load.
* @param aEvent
* The load event type to wait for. Defaults to "load".
* @return {Promise} resolved when the event is handled.
*/
function promiseTabLoadEvent(aTab, aURL, aEventType="load")
{
let deferred = Promise.defer();
info("Wait tab event: " + aEventType);
aTab.linkedBrowser.addEventListener(aEventType, function load(event) {
if (event.originalTarget != aTab.linkedBrowser.contentDocument ||
event.target.location.href == "about:blank") {
info("skipping spurious load event");
return;
}
aTab.linkedBrowser.removeEventListener(aEventType, load, true);
info("Tab event received: " + aEventType);
deferred.resolve();
}, true, true);
aTab.linkedBrowser.loadURI(aURL);
return deferred.promise;
}
/**
* Cleans up snippets and ensures that by default we don't try to check for
* remote snippets since that may cause network bustage or slowness.

File diff suppressed because it is too large Load Diff

View File

@ -219,11 +219,7 @@ function whenNewTabLoaded(aWindow, aCallback) {
}
function whenTabLoaded(aTab, aCallback) {
let browser = aTab.linkedBrowser;
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
executeSoon(aCallback);
}, true);
promiseTabLoadEvent(aTab).then(aCallback);
}
function promiseTabLoaded(aTab) {
@ -311,6 +307,7 @@ function promiseHistoryClearedState(aURIs, aShouldBeCleared) {
function promiseTopicObserved(topic)
{
let deferred = Promise.defer();
info("Waiting for observer topic " + topic);
Services.obs.addObserver(function PTO_observe(subject, topic, data) {
Services.obs.removeObserver(PTO_observe, topic);
deferred.resolve([subject, data]);
@ -397,8 +394,7 @@ let FullZoomHelper = {
let didLoad = false;
let didZoom = false;
tab.linkedBrowser.addEventListener("load", function (event) {
event.currentTarget.removeEventListener("load", arguments.callee, true);
promiseTabLoadEvent(tab).then(event => {
didLoad = true;
if (didZoom)
deferred.resolve();
@ -472,3 +468,46 @@ let FullZoomHelper = {
},
};
/**
* Waits for a load (or custom) event to finish in a given tab. If provided
* load an uri into the tab.
*
* @param tab
* The tab to load into.
* @param [optional] url
* The url to load, or the current url.
* @param [optional] event
* The load event type to wait for. Defaults to "load".
* @return {Promise} resolved when the event is handled.
* @resolves to the received event
* @rejects if a valid load event is not received within a meaningful interval
*/
function promiseTabLoadEvent(tab, url, eventType="load")
{
let deferred = Promise.defer();
info("Wait tab event: " + eventType);
function handle(event) {
if (event.originalTarget != tab.linkedBrowser.contentDocument ||
event.target.location.href == "about:blank" ||
(url && event.target.location.href != url)) {
info("Skipping spurious '" + eventType + "'' event" +
" for " + event.target.location.href);
return;
}
clearTimeout(timeout);
tab.linkedBrowser.removeEventListener(eventType, handle, true);
info("Tab event received: " + eventType);
deferred.resolve(event);
}
let timeout = setTimeout(() => {
tab.linkedBrowser.removeEventListener(eventType, handle, true);
deferred.reject(new Error("Timed out while waiting for a '" + eventType + "'' event"));
}, 30000);
tab.linkedBrowser.addEventListener(eventType, handle, true, true);
if (url)
tab.linkedBrowser.loadURI(url);
return deferred.promise;
}

View File

@ -0,0 +1,12 @@
[DEFAULT]
support-files =
head.js
[browser_popupNotification.js]
skip-if = (os == "linux" && debug) || e10s # e10s - Bug ?????? - popup notification test probably confused re content process notifications etc
[browser_popupNotification_2.js]
skip-if = (os == "linux" && debug) || e10s # e10s - Bug ?????? - popup notification test probably confused re content process notifications etc
[browser_popupNotification_3.js]
skip-if = (os == "linux" && debug) || e10s # e10s - Bug ?????? - popup notification test probably confused re content process notifications etc
[browser_popupNotification_4.js]
skip-if = (os == "linux" && debug) || e10s # e10s - Bug ?????? - popup notification test probably confused re content process notifications etc

View File

@ -0,0 +1,203 @@
/* 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/. */
// These are shared between test #4 to #5
let wrongBrowserNotificationObject = new BasicNotification("wrongBrowser");
let wrongBrowserNotification;
function test() {
waitForExplicitFinish();
ok(PopupNotifications, "PopupNotifications object exists");
ok(PopupNotifications.panel, "PopupNotifications panel exists");
setup();
goNext();
}
let tests = [
{ id: "Test#1",
run: function () {
this.notifyObj = new BasicNotification(this.id);
showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
triggerMainCommand(popup);
},
onHidden: function (popup) {
ok(this.notifyObj.mainActionClicked, "mainAction was clicked");
ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
}
},
{ id: "Test#2",
run: function () {
this.notifyObj = new BasicNotification(this.id);
showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
triggerSecondaryCommand(popup, 0);
},
onHidden: function (popup) {
ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked");
ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
}
},
{ id: "Test#3",
run: function () {
this.notifyObj = new BasicNotification(this.id);
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
dismissNotification(popup);
},
onHidden: function (popup) {
ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
this.notification.remove();
ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
}
},
// test opening a notification for a background browser
// Note: test 4 to 6 share a tab.
{ id: "Test#4",
run: function* () {
let tab = gBrowser.addTab("about:blank");
isnot(gBrowser.selectedTab, tab, "new tab isn't selected");
wrongBrowserNotificationObject.browser = gBrowser.getBrowserForTab(tab);
let promiseTopic = promiseTopicObserved("PopupNotifications-backgroundShow");
wrongBrowserNotification = showNotification(wrongBrowserNotificationObject);
yield promiseTopic;
is(PopupNotifications.isPanelOpen, false, "panel isn't open");
ok(!wrongBrowserNotificationObject.mainActionClicked, "main action wasn't clicked");
ok(!wrongBrowserNotificationObject.secondaryActionClicked, "secondary action wasn't clicked");
ok(!wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback wasn't called");
goNext();
}
},
// now select that browser and test to see that the notification appeared
{ id: "Test#5",
run: function () {
this.oldSelectedTab = gBrowser.selectedTab;
gBrowser.selectedTab = gBrowser.tabs[gBrowser.tabs.length - 1];
},
onShown: function (popup) {
checkPopup(popup, wrongBrowserNotificationObject);
is(PopupNotifications.isPanelOpen, true, "isPanelOpen getter doesn't lie");
// switch back to the old browser
gBrowser.selectedTab = this.oldSelectedTab;
},
onHidden: function (popup) {
// actually remove the notification to prevent it from reappearing
ok(wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback triggered due to tab switch");
wrongBrowserNotification.remove();
ok(wrongBrowserNotificationObject.removedCallbackTriggered, "removed callback triggered");
wrongBrowserNotification = null;
}
},
// test that the removed notification isn't shown on browser re-select
{ id: "Test#6",
run: function* () {
let promiseTopic = promiseTopicObserved("PopupNotifications-updateNotShowing");
gBrowser.selectedTab = gBrowser.tabs[gBrowser.tabs.length - 1];
yield promiseTopic;
is(PopupNotifications.isPanelOpen, false, "panel isn't open");
gBrowser.removeTab(gBrowser.selectedTab);
goNext();
}
},
// Test that two notifications with the same ID result in a single displayed
// notification.
{ id: "Test#7",
run: function () {
this.notifyObj = new BasicNotification(this.id);
// Show the same notification twice
this.notification1 = showNotification(this.notifyObj);
this.notification2 = showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
this.notification2.remove();
},
onHidden: function (popup) {
ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
}
},
// Test that two notifications with different IDs are displayed
{ id: "Test#8",
run: function () {
this.testNotif1 = new BasicNotification(this.id);
this.testNotif1.message += " 1";
showNotification(this.testNotif1);
this.testNotif2 = new BasicNotification(this.id);
this.testNotif2.message += " 2";
this.testNotif2.id += "-2";
showNotification(this.testNotif2);
},
onShown: function (popup) {
is(popup.childNodes.length, 2, "two notifications are shown");
// Trigger the main command for the first notification, and the secondary
// for the second. Need to do mainCommand first since the secondaryCommand
// triggering is async.
triggerMainCommand(popup);
is(popup.childNodes.length, 1, "only one notification left");
triggerSecondaryCommand(popup, 0);
},
onHidden: function (popup) {
ok(this.testNotif1.mainActionClicked, "main action #1 was clicked");
ok(!this.testNotif1.secondaryActionClicked, "secondary action #1 wasn't clicked");
ok(!this.testNotif1.dismissalCallbackTriggered, "dismissal callback #1 wasn't called");
ok(!this.testNotif2.mainActionClicked, "main action #2 wasn't clicked");
ok(this.testNotif2.secondaryActionClicked, "secondary action #2 was clicked");
ok(!this.testNotif2.dismissalCallbackTriggered, "dismissal callback #2 wasn't called");
}
},
// Test notification without mainAction
{ id: "Test#9",
run: function () {
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.mainAction = null;
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
dismissNotification(popup);
},
onHidden: function (popup) {
this.notification.remove();
}
},
// Test two notifications with different anchors
{ id: "Test#10",
run: function () {
this.notifyObj = new BasicNotification(this.id);
this.firstNotification = showNotification(this.notifyObj);
this.notifyObj2 = new BasicNotification(this.id);
this.notifyObj2.id += "-2";
this.notifyObj2.anchorID = "addons-notification-icon";
// Second showNotification() overrides the first
this.secondNotification = showNotification(this.notifyObj2);
},
onShown: function (popup) {
// This also checks that only one element is shown.
checkPopup(popup, this.notifyObj2);
is(document.getElementById("geo-notification-icon").boxObject.width, 0,
"geo anchor shouldn't be visible");
dismissNotification(popup);
},
onHidden: function (popup) {
// Remove the notifications
this.firstNotification.remove();
this.secondNotification.remove();
ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered");
}
}
];

View File

@ -0,0 +1,242 @@
/* 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/. */
function test() {
waitForExplicitFinish();
ok(PopupNotifications, "PopupNotifications object exists");
ok(PopupNotifications.panel, "PopupNotifications panel exists");
setup();
goNext();
}
let tests = [
// Test optional params
{ id: "Test#1",
run: function () {
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.secondaryActions = undefined;
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
dismissNotification(popup);
},
onHidden: function (popup) {
ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
this.notification.remove();
ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
}
},
// Test that icons appear
{ id: "Test#2",
run: function () {
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.id = "geolocation";
this.notifyObj.anchorID = "geo-notification-icon";
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
"geo anchor should be visible");
dismissNotification(popup);
},
onHidden: function (popup) {
let icon = document.getElementById("geo-notification-icon");
isnot(icon.boxObject.width, 0,
"geo anchor should be visible after dismissal");
this.notification.remove();
is(icon.boxObject.width, 0,
"geo anchor should not be visible after removal");
}
},
// Test that persistence allows the notification to persist across reloads
{ id: "Test#3",
run: function* () {
this.oldSelectedTab = gBrowser.selectedTab;
gBrowser.selectedTab = gBrowser.addTab("about:blank");
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.addOptions({
persistence: 2
});
this.notification = showNotification(this.notifyObj);
},
onShown: function* (popup) {
this.complete = false;
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/")
// Next load will remove the notification
this.complete = true;
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
},
onHidden: function (popup) {
ok(this.complete, "Should only have hidden the notification after 3 page loads");
ok(this.notifyObj.removedCallbackTriggered, "removal callback triggered");
gBrowser.removeTab(gBrowser.selectedTab);
gBrowser.selectedTab = this.oldSelectedTab;
}
},
// Test that a timeout allows the notification to persist across reloads
{ id: "Test#4",
run: function* () {
this.oldSelectedTab = gBrowser.selectedTab;
gBrowser.selectedTab = gBrowser.addTab("about:blank");
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
this.notifyObj = new BasicNotification(this.id);
// Set a timeout of 10 minutes that should never be hit
this.notifyObj.addOptions({
timeout: Date.now() + 600000
});
this.notification = showNotification(this.notifyObj);
},
onShown: function* (popup) {
this.complete = false;
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
// Next load will hide the notification
this.notification.options.timeout = Date.now() - 1;
this.complete = true;
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
},
onHidden: function (popup) {
ok(this.complete, "Should only have hidden the notification after the timeout was passed");
this.notification.remove();
gBrowser.removeTab(gBrowser.selectedTab);
gBrowser.selectedTab = this.oldSelectedTab;
}
},
// Test that setting persistWhileVisible allows a visible notification to
// persist across location changes
{ id: "Test#5",
run: function* () {
this.oldSelectedTab = gBrowser.selectedTab;
gBrowser.selectedTab = gBrowser.addTab("about:blank");
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.addOptions({
persistWhileVisible: true
});
this.notification = showNotification(this.notifyObj);
},
onShown: function* (popup) {
this.complete = false;
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
// Notification should persist across location changes
this.complete = true;
dismissNotification(popup);
},
onHidden: function (popup) {
ok(this.complete, "Should only have hidden the notification after it was dismissed");
this.notification.remove();
gBrowser.removeTab(gBrowser.selectedTab);
gBrowser.selectedTab = this.oldSelectedTab;
}
},
// Test that nested icon nodes correctly activate popups
{ id: "Test#6",
run: function() {
// Add a temporary box as the anchor with a button
this.box = document.createElement("box");
PopupNotifications.iconBox.appendChild(this.box);
let button = document.createElement("button");
button.setAttribute("label", "Please click me!");
this.box.appendChild(button);
// The notification should open up on the box
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.anchorID = this.box.id = "nested-box";
this.notifyObj.addOptions({dismissed: true});
this.notification = showNotification(this.notifyObj);
// This test places a normal button in the notification area, which has
// standard GTK styling and dimensions. Due to the clip-path, this button
// gets clipped off, which makes it necessary to synthesize the mouse click
// a little bit downward. To be safe, I adjusted the x-offset with the same
// amount.
EventUtils.synthesizeMouse(button, 4, 4, {});
},
onShown: function(popup) {
checkPopup(popup, this.notifyObj);
dismissNotification(popup);
},
onHidden: function(popup) {
this.notification.remove();
this.box.parentNode.removeChild(this.box);
}
},
// Test that popupnotifications without popups have anchor icons shown
{ id: "Test#7",
run: function* () {
let notifyObj = new BasicNotification(this.id);
notifyObj.anchorID = "geo-notification-icon";
notifyObj.addOptions({neverShow: true});
let promiseTopic = promiseTopicObserved("PopupNotifications-updateNotShowing");
showNotification(notifyObj);
yield promiseTopic;
isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
"geo anchor should be visible");
goNext();
}
},
// Test notification "Not Now" menu item
{ id: "Test#8",
run: function () {
this.notifyObj = new BasicNotification(this.id);
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
triggerSecondaryCommand(popup, 1);
},
onHidden: function (popup) {
ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
this.notification.remove();
ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
}
},
// Test notification close button
{ id: "Test#9",
run: function () {
this.notifyObj = new BasicNotification(this.id);
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
let notification = popup.childNodes[0];
EventUtils.synthesizeMouseAtCenter(notification.closebutton, {});
},
onHidden: function (popup) {
ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
this.notification.remove();
ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
}
},
// Test notification when chrome is hidden
{ id: "Test#10",
run: function () {
window.locationbar.visible = false;
this.notifyObj = new BasicNotification(this.id);
this.notification = showNotification(this.notifyObj);
window.locationbar.visible = true;
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
is(popup.anchorNode.className, "tabbrowser-tab", "notification anchored to tab");
dismissNotification(popup);
},
onHidden: function (popup) {
ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
this.notification.remove();
ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
}
}
];

View File

@ -0,0 +1,310 @@
/* 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/. */
function test() {
waitForExplicitFinish();
ok(PopupNotifications, "PopupNotifications object exists");
ok(PopupNotifications.panel, "PopupNotifications panel exists");
setup();
goNext();
}
let tests = [
// Test notification is removed when dismissed if removeOnDismissal is true
{ id: "Test#1",
run: function () {
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.addOptions({
removeOnDismissal: true
});
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
dismissNotification(popup);
},
onHidden: function (popup) {
ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
}
},
// Test multiple notification icons are shown
{ id: "Test#2",
run: function () {
this.notifyObj1 = new BasicNotification(this.id);
this.notifyObj1.id += "_1";
this.notifyObj1.anchorID = "default-notification-icon";
this.notification1 = showNotification(this.notifyObj1);
this.notifyObj2 = new BasicNotification(this.id);
this.notifyObj2.id += "_2";
this.notifyObj2.anchorID = "geo-notification-icon";
this.notification2 = showNotification(this.notifyObj2);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj2);
// check notifyObj1 anchor icon is showing
isnot(document.getElementById("default-notification-icon").boxObject.width, 0,
"default anchor should be visible");
// check notifyObj2 anchor icon is showing
isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
"geo anchor should be visible");
dismissNotification(popup);
},
onHidden: function (popup) {
this.notification1.remove();
ok(this.notifyObj1.removedCallbackTriggered, "removed callback triggered");
this.notification2.remove();
ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered");
}
},
// Test that multiple notification icons are removed when switching tabs
{ id: "Test#3",
run: function () {
// show the notification on old tab.
this.notifyObjOld = new BasicNotification(this.id);
this.notifyObjOld.anchorID = "default-notification-icon";
this.notificationOld = showNotification(this.notifyObjOld);
// switch tab
this.oldSelectedTab = gBrowser.selectedTab;
gBrowser.selectedTab = gBrowser.addTab("about:blank");
// show the notification on new tab.
this.notifyObjNew = new BasicNotification(this.id);
this.notifyObjNew.anchorID = "geo-notification-icon";
this.notificationNew = showNotification(this.notifyObjNew);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObjNew);
// check notifyObjOld anchor icon is removed
is(document.getElementById("default-notification-icon").boxObject.width, 0,
"default anchor shouldn't be visible");
// check notifyObjNew anchor icon is showing
isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
"geo anchor should be visible");
dismissNotification(popup);
},
onHidden: function (popup) {
this.notificationNew.remove();
gBrowser.removeTab(gBrowser.selectedTab);
gBrowser.selectedTab = this.oldSelectedTab;
this.notificationOld.remove();
}
},
// test security delay - too early
{ id: "Test#4",
run: function () {
// Set the security delay to 100s
PopupNotifications.buttonDelay = 100000;
this.notifyObj = new BasicNotification(this.id);
showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
triggerMainCommand(popup);
// Wait to see if the main command worked
executeSoon(function delayedDismissal() {
dismissNotification(popup);
});
},
onHidden: function (popup) {
ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked because it was too soon");
ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
}
},
// test security delay - after delay
{ id: "Test#5",
run: function () {
// Set the security delay to 10ms
PopupNotifications.buttonDelay = 10;
this.notifyObj = new BasicNotification(this.id);
showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
// Wait until after the delay to trigger the main action
setTimeout(function delayedDismissal() {
triggerMainCommand(popup);
}, 500);
},
onHidden: function (popup) {
ok(this.notifyObj.mainActionClicked, "mainAction was clicked after the delay");
ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback was not triggered");
PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL;
}
},
// reload removes notification
{ id: "Test#6",
run: function* () {
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
let notifyObj = new BasicNotification(this.id);
notifyObj.options.eventCallback = function (eventName) {
if (eventName == "removed") {
ok(true, "Notification removed in background tab after reloading");
goNext();
}
};
showNotification(notifyObj);
executeSoon(function () {
gBrowser.selectedBrowser.reload();
});
}
},
// location change in background tab removes notification
{ id: "Test#7",
run: function* () {
let oldSelectedTab = gBrowser.selectedTab;
let newTab = gBrowser.addTab("about:blank");
gBrowser.selectedTab = newTab;
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
gBrowser.selectedTab = oldSelectedTab;
let browser = gBrowser.getBrowserForTab(newTab);
let notifyObj = new BasicNotification(this.id);
notifyObj.browser = browser;
notifyObj.options.eventCallback = function (eventName) {
if (eventName == "removed") {
ok(true, "Notification removed in background tab after reloading");
executeSoon(function () {
gBrowser.removeTab(newTab);
goNext();
});
}
};
showNotification(notifyObj);
executeSoon(function () {
browser.reload();
});
}
},
// Popup notification anchor shouldn't disappear when a notification with the same ID is re-added in a background tab
{ id: "Test#8",
run: function* () {
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
let originalTab = gBrowser.selectedTab;
let bgTab = gBrowser.addTab("about:blank");
gBrowser.selectedTab = bgTab;
yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
let anchor = document.createElement("box");
anchor.id = "test26-anchor";
anchor.className = "notification-anchor-icon";
PopupNotifications.iconBox.appendChild(anchor);
gBrowser.selectedTab = originalTab;
let fgNotifyObj = new BasicNotification(this.id);
fgNotifyObj.anchorID = anchor.id;
fgNotifyObj.options.dismissed = true;
let fgNotification = showNotification(fgNotifyObj);
let bgNotifyObj = new BasicNotification(this.id);
bgNotifyObj.anchorID = anchor.id;
bgNotifyObj.browser = gBrowser.getBrowserForTab(bgTab);
// show the notification in the background tab ...
let bgNotification = showNotification(bgNotifyObj);
// ... and re-show it
bgNotification = showNotification(bgNotifyObj);
ok(fgNotification.id, "notification has id");
is(fgNotification.id, bgNotification.id, "notification ids are the same");
is(anchor.getAttribute("showing"), "true", "anchor still showing");
fgNotification.remove();
gBrowser.removeTab(bgTab);
goNext();
}
},
// location change in an embedded frame should not remove a notification
{ id: "Test#9",
run: function* () {
yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html;charset=utf8,<iframe%20id='iframe'%20src='http://example.com/'>");
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.options.eventCallback = function (eventName) {
if (eventName == "removed") {
ok(false, "Notification removed from browser when subframe navigated");
}
};
showNotification(this.notifyObj);
},
onShown: function (popup) {
let self = this;
let progressListener = {
onLocationChange: function onLocationChange(aBrowser) {
if (aBrowser != gBrowser.selectedBrowser) {
return;
}
let notification = PopupNotifications.getNotification(self.notifyObj.id,
self.notifyObj.browser);
ok(notification != null, "Notification remained when subframe navigated");
self.notifyObj.options.eventCallback = undefined;
notification.remove();
gBrowser.removeTabsProgressListener(progressListener);
},
};
info("Adding progress listener and performing navigation");
gBrowser.addTabsProgressListener(progressListener);
content.document.getElementById("iframe")
.setAttribute("src", "http://example.org/");
},
onHidden: function () {}
},
// Popup Notifications should catch exceptions from callbacks
{ id: "Test#10",
run: function () {
let callbackCount = 0;
this.testNotif1 = new BasicNotification(this.id);
this.testNotif1.message += " 1";
this.notification1 = showNotification(this.testNotif1);
this.testNotif1.options.eventCallback = function (eventName) {
info("notifyObj1.options.eventCallback: " + eventName);
if (eventName == "dismissed") {
throw new Error("Oops 1!");
if (++callbackCount == 2) {
goNext();
}
}
};
this.testNotif2 = new BasicNotification(this.id);
this.testNotif2.message += " 2";
this.testNotif2.id += "-2";
this.testNotif2.options.eventCallback = function (eventName) {
info("notifyObj2.options.eventCallback: " + eventName);
if (eventName == "dismissed") {
throw new Error("Oops 2!");
if (++callbackCount == 2) {
goNext();
}
}
};
this.notification2 = showNotification(this.testNotif2);
},
onShown: function (popup) {
is(popup.childNodes.length, 2, "two notifications are shown");
dismissNotification(popup);
},
onHidden: function () {
this.notification1.remove();
this.notification2.remove();
}
}
];

View File

@ -0,0 +1,210 @@
/* 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/. */
function test() {
waitForExplicitFinish();
ok(PopupNotifications, "PopupNotifications object exists");
ok(PopupNotifications.panel, "PopupNotifications panel exists");
setup();
goNext();
}
let tests = [
// Popup Notifications main actions should catch exceptions from callbacks
{ id: "Test#1",
run: function () {
this.testNotif = new ErrorNotification();
showNotification(this.testNotif);
},
onShown: function (popup) {
checkPopup(popup, this.testNotif);
triggerMainCommand(popup);
},
onHidden: function (popup) {
ok(this.testNotif.mainActionClicked, "main action has been triggered");
}
},
// Popup Notifications secondary actions should catch exceptions from callbacks
{ id: "Test#2",
run: function () {
this.testNotif = new ErrorNotification();
showNotification(this.testNotif);
},
onShown: function (popup) {
checkPopup(popup, this.testNotif);
triggerSecondaryCommand(popup, 0);
},
onHidden: function (popup) {
ok(this.testNotif.secondaryActionClicked, "secondary action has been triggered");
}
},
// Existing popup notification shouldn't disappear when adding a dismissed notification
{ id: "Test#3",
run: function () {
this.notifyObj1 = new BasicNotification(this.id);
this.notifyObj1.id += "_1";
this.notifyObj1.anchorID = "default-notification-icon";
this.notification1 = showNotification(this.notifyObj1);
},
onShown: function (popup) {
// Now show a dismissed notification, and check that it doesn't clobber
// the showing one.
this.notifyObj2 = new BasicNotification(this.id);
this.notifyObj2.id += "_2";
this.notifyObj2.anchorID = "geo-notification-icon";
this.notifyObj2.options.dismissed = true;
this.notification2 = showNotification(this.notifyObj2);
checkPopup(popup, this.notifyObj1);
// check that both anchor icons are showing
is(document.getElementById("default-notification-icon").getAttribute("showing"), "true",
"notification1 anchor should be visible");
is(document.getElementById("geo-notification-icon").getAttribute("showing"), "true",
"notification2 anchor should be visible");
dismissNotification(popup);
},
onHidden: function(popup) {
this.notification1.remove();
this.notification2.remove();
}
},
// Showing should be able to modify the popup data
{ id: "Test#4",
run: function() {
this.notifyObj = new BasicNotification(this.id);
let normalCallback = this.notifyObj.options.eventCallback;
this.notifyObj.options.eventCallback = function (eventName) {
if (eventName == "showing") {
this.mainAction.label = "Alternate Label";
}
normalCallback.call(this, eventName);
};
showNotification(this.notifyObj);
},
onShown: function(popup) {
// checkPopup checks for the matching label. Note that this assumes that
// this.notifyObj.mainAction is the same as notification.mainAction,
// which could be a problem if we ever decided to deep-copy.
checkPopup(popup, this.notifyObj);
triggerMainCommand(popup);
},
onHidden: function() { }
},
// Moving a tab to a new window should remove non-swappable notifications.
{ id: "Test#5",
run: function() {
gBrowser.selectedTab = gBrowser.addTab("about:blank");
let notifyObj = new BasicNotification(this.id);
showNotification(notifyObj);
let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
whenDelayedStartupFinished(win, function() {
let [tab] = win.gBrowser.tabs;
let anchor = win.document.getElementById("default-notification-icon");
win.PopupNotifications._reshowNotifications(anchor);
ok(win.PopupNotifications.panel.childNodes.length == 0,
"no notification displayed in new window");
ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
ok(notifyObj.removedCallbackTriggered, "the removed callback was triggered");
win.close();
goNext();
});
}
},
// Moving a tab to a new window should preserve swappable notifications.
{ id: "Test#6",
run: function() {
gBrowser.selectedTab = gBrowser.addTab("about:blank");
let notifyObj = new BasicNotification(this.id);
let originalCallback = notifyObj.options.eventCallback;
notifyObj.options.eventCallback = function (eventName) {
originalCallback(eventName);
return eventName == "swapping";
};
showNotification(notifyObj);
let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
whenDelayedStartupFinished(win, function() {
let [tab] = win.gBrowser.tabs;
let anchor = win.document.getElementById("default-notification-icon");
win.PopupNotifications._reshowNotifications(anchor);
checkPopup(win.PopupNotifications.panel, notifyObj);
ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
win.close();
goNext();
});
}
},
// the hideNotNow option
{ id: "Test#7",
run: function () {
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.options.hideNotNow = true;
this.notifyObj.mainAction.dismiss = true;
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
// checkPopup verifies that the Not Now item is hidden, and that no separator is added.
checkPopup(popup, this.notifyObj);
triggerMainCommand(popup);
},
onHidden: function (popup) {
this.notification.remove();
}
},
// the main action callback can keep the notification.
{ id: "Test#8",
run: function () {
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.mainAction.dismiss = true;
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
triggerMainCommand(popup);
},
onHidden: function (popup) {
ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered");
this.notification.remove();
}
},
// a secondary action callback can keep the notification.
{ id: "Test#9",
run: function () {
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.secondaryActions[0].dismiss = true;
this.notification = showNotification(this.notifyObj);
},
onShown: function (popup) {
checkPopup(popup, this.notifyObj);
triggerSecondaryCommand(popup, 0);
},
onHidden: function (popup) {
ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered");
this.notification.remove();
}
},
// returning true in the showing callback should dismiss the notification.
{ id: "Test#10",
run: function() {
let notifyObj = new BasicNotification(this.id);
let originalCallback = notifyObj.options.eventCallback;
notifyObj.options.eventCallback = function (eventName) {
originalCallback(eventName);
return eventName == "showing";
};
let notification = showNotification(notifyObj);
ok(notifyObj.showingCallbackTriggered, "the showing callback was triggered");
ok(!notifyObj.shownCallbackTriggered, "the shown callback wasn't triggered");
notification.remove();
goNext();
}
}
];

View File

@ -0,0 +1,313 @@
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
function whenDelayedStartupFinished(aWindow, aCallback) {
Services.obs.addObserver(function observer(aSubject, aTopic) {
if (aWindow == aSubject) {
Services.obs.removeObserver(observer, aTopic);
executeSoon(aCallback);
}
}, "browser-delayed-startup-finished", false);
}
/**
* Allows waiting for an observer notification once.
*
* @param topic
* Notification topic to observe.
*
* @return {Promise}
* @resolves The array [subject, data] from the observed notification.
* @rejects Never.
*/
function promiseTopicObserved(topic)
{
let deferred = Promise.defer();
info("Waiting for observer topic " + topic);
Services.obs.addObserver(function PTO_observe(subject, topic, data) {
Services.obs.removeObserver(PTO_observe, topic);
deferred.resolve([subject, data]);
}, topic, false);
return deferred.promise;
}
/**
* Waits for a load (or custom) event to finish in a given tab. If provided
* load an uri into the tab.
*
* @param tab
* The tab to load into.
* @param [optional] url
* The url to load, or the current url.
* @param [optional] event
* The load event type to wait for. Defaults to "load".
* @return {Promise} resolved when the event is handled.
* @resolves to the received event
* @rejects if a valid load event is not received within a meaningful interval
*/
function promiseTabLoadEvent(tab, url, eventType="load")
{
let deferred = Promise.defer();
info("Wait tab event: " + eventType);
function handle(event) {
if (event.originalTarget != tab.linkedBrowser.contentDocument ||
event.target.location.href == "about:blank" ||
(url && event.target.location.href != url)) {
info("Skipping spurious '" + eventType + "'' event" +
" for " + event.target.location.href);
return;
}
clearTimeout(timeout);
tab.linkedBrowser.removeEventListener(eventType, handle, true);
info("Tab event received: " + eventType);
deferred.resolve(event);
}
let timeout = setTimeout(() => {
if (tab.linkedBrowser)
tab.linkedBrowser.removeEventListener(eventType, handle, true);
deferred.reject(new Error("Timed out while waiting for a '" + eventType + "'' event"));
}, 30000);
tab.linkedBrowser.addEventListener(eventType, handle, true, true);
if (url)
tab.linkedBrowser.loadURI(url);
return deferred.promise;
}
const PREF_SECURITY_DELAY_INITIAL = Services.prefs.getIntPref("security.notification_enable_delay");
function setup() {
// Disable transitions as they slow the test down and we want to click the
// mouse buttons in a predictable location.
PopupNotifications.transitionsEnabled = false;
registerCleanupFunction(() => {
PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL;
PopupNotifications.transitionsEnabled = true;
});
}
function goNext() {
executeSoon(() => executeSoon(Task.async(runNextTest)));
}
function* runNextTest() {
if (tests.length == 0) {
executeSoon(finish);
return;
}
let nextTest = tests.shift();
if (nextTest.onShown) {
let shownState = false;
onPopupEvent("popupshowing", function () {
info("[" + nextTest.id + "] popup showing");
});
onPopupEvent("popupshown", function () {
shownState = true;
info("[" + nextTest.id + "] popup shown");
Task.spawn(() => nextTest.onShown(this))
.then(undefined , ex => Assert.ok(false, "onShown failed: " + ex));
});
onPopupEvent("popuphidden", function () {
info("[" + nextTest.id + "] popup hidden");
nextTest.onHidden(this);
goNext();
}, () => shownState);
info("[" + nextTest.id + "] added listeners; panel is open: " + PopupNotifications.isPanelOpen);
}
info("[" + nextTest.id + "] running test");
yield nextTest.run();
}
function showNotification(notifyObj) {
info("Showing notification " + notifyObj.id);
return PopupNotifications.show(notifyObj.browser,
notifyObj.id,
notifyObj.message,
notifyObj.anchorID,
notifyObj.mainAction,
notifyObj.secondaryActions,
notifyObj.options);
}
function dismissNotification(popup) {
info("Dismissing notification " + popup.childNodes[0].id);
executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
}
function BasicNotification(testId) {
this.browser = gBrowser.selectedBrowser;
this.id = "test-notification-" + testId;
this.message = "This is popup notification for " + testId;
this.anchorID = null;
this.mainAction = {
label: "Main Action",
accessKey: "M",
callback: () => this.mainActionClicked = true
};
this.secondaryActions = [
{
label: "Secondary Action",
accessKey: "S",
callback: () => this.secondaryActionClicked = true
}
];
this.options = {
eventCallback: eventName => {
switch (eventName) {
case "dismissed":
this.dismissalCallbackTriggered = true;
break;
case "showing":
this.showingCallbackTriggered = true;
break;
case "shown":
this.shownCallbackTriggered = true;
break;
case "removed":
this.removedCallbackTriggered = true;
break;
case "swapping":
this.swappingCallbackTriggered = true;
break;
}
}
};
}
BasicNotification.prototype.addOptions = function(options) {
for (let [name, value] in Iterator(options))
this.options[name] = value;
};
function ErrorNotification() {
this.mainAction.callback = () => {
this.mainActionClicked = true;
throw new Error("Oops!");
};
this.secondaryActions[0].callback = () => {
this.secondaryActionClicked = true;
throw new Error("Oops!");
};
}
ErrorNotification.prototype = new BasicNotification();
ErrorNotification.prototype.constructor = ErrorNotification;
function checkPopup(popup, notifyObj) {
info("Checking notification " + notifyObj.id);
ok(notifyObj.showingCallbackTriggered, "showing callback was triggered");
ok(notifyObj.shownCallbackTriggered, "shown callback was triggered");
let notifications = popup.childNodes;
is(notifications.length, 1, "one notification displayed");
let notification = notifications[0];
if (!notification)
return;
let icon = document.getAnonymousElementByAttribute(notification, "class",
"popup-notification-icon");
if (notifyObj.id == "geolocation") {
isnot(icon.boxObject.width, 0, "icon for geo displayed");
is(popup.anchorNode.className, "notification-anchor-icon",
"notification anchored to icon");
}
is(notification.getAttribute("label"), notifyObj.message, "message matches");
is(notification.id, notifyObj.id + "-notification", "id matches");
if (notifyObj.mainAction) {
is(notification.getAttribute("buttonlabel"), notifyObj.mainAction.label,
"main action label matches");
is(notification.getAttribute("buttonaccesskey"),
notifyObj.mainAction.accessKey, "main action accesskey matches");
}
let actualSecondaryActions =
Array.filter(notification.childNodes, child => child.nodeName == "menuitem");
let secondaryActions = notifyObj.secondaryActions || [];
let actualSecondaryActionsCount = actualSecondaryActions.length;
if (notifyObj.options.hideNotNow) {
is(notification.getAttribute("hidenotnow"), "true", "'Not Now' item hidden");
if (secondaryActions.length)
is(notification.lastChild.tagName, "menuitem", "no menuseparator");
}
else if (secondaryActions.length) {
is(notification.lastChild.tagName, "menuseparator", "menuseparator exists");
}
is(actualSecondaryActionsCount, secondaryActions.length,
actualSecondaryActions.length + " secondary actions");
secondaryActions.forEach(function (a, i) {
is(actualSecondaryActions[i].getAttribute("label"), a.label,
"label for secondary action " + i + " matches");
is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey,
"accessKey for secondary action " + i + " matches");
});
}
XPCOMUtils.defineLazyGetter(this, "gActiveListeners", () => {
let listeners = new Map();
registerCleanupFunction(() => {
for (let [listener, eventName] of listeners) {
PopupNotifications.panel.removeEventListener(eventName, listener, false);
}
});
return listeners;
});
function onPopupEvent(eventName, callback, condition) {
let listener = event => {
if (event.target != PopupNotifications.panel ||
(condition && !condition()))
return;
PopupNotifications.panel.removeEventListener(eventName, listener, false);
gActiveListeners.delete(listener);
executeSoon(() => callback.call(PopupNotifications.panel));
}
gActiveListeners.set(listener, eventName);
PopupNotifications.panel.addEventListener(eventName, listener, false);
}
function triggerMainCommand(popup) {
let notifications = popup.childNodes;
ok(notifications.length > 0, "at least one notification displayed");
let notification = notifications[0];
info("Triggering main command for notification " + notification.id);
// 20, 10 so that the inner button is hit
EventUtils.synthesizeMouse(notification.button, 20, 10, {});
}
function triggerSecondaryCommand(popup, index) {
let notifications = popup.childNodes;
ok(notifications.length > 0, "at least one notification displayed");
let notification = notifications[0];
info("Triggering secondary command for notification " + notification.id);
// Cancel the arrow panel slide-in transition (bug 767133) such that
// it won't interfere with us interacting with the dropdown.
document.getAnonymousNodes(popup)[0].style.transition = "none";
notification.button.focus();
popup.addEventListener("popupshown", function handle() {
popup.removeEventListener("popupshown", handle, false);
info("Command popup open for notification " + notification.id);
// Press down until the desired command is selected
for (let i = 0; i <= index; i++) {
EventUtils.synthesizeKey("VK_DOWN", {});
}
// Activate
EventUtils.synthesizeKey("VK_RETURN", {});
}, false);
// One down event to open the popup
info("Open the popup to trigger secondary command for notification " + notification.id);
EventUtils.synthesizeKey("VK_DOWN", { altKey: !navigator.platform.contains("Mac") });
}

View File

@ -17,6 +17,7 @@ BROWSER_CHROME_MANIFESTS += [
'content/test/general/browser.ini',
'content/test/newtab/browser.ini',
'content/test/plugins/browser.ini',
'content/test/popupNotifications/browser.ini',
'content/test/social/browser.ini',
]

View File

@ -10,6 +10,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
const NEW_SOURCE_IGNORED_URLS = ["debugger eval code", "self-hosted", "XStringBundle"];
const NEW_SOURCE_DISPLAY_DELAY = 200; // ms
const EDITOR_BREAKPOINTS_UPDATE_DELAY = 200; // ms
const FETCH_SOURCE_RESPONSE_DELAY = 200; // ms
const FETCH_EVENT_LISTENERS_DELAY = 200; // ms
const FRAME_STEP_CLEAR_DELAY = 100; // ms
@ -1167,8 +1168,10 @@ SourceScripts.prototype = {
// If there are any stored breakpoints for this source, display them again,
// both in the editor and the breakpoints pane.
DebuggerController.Breakpoints.updateEditorBreakpoints();
DebuggerController.Breakpoints.updatePaneBreakpoints();
setNamedTimeout("update-editor-bp", EDITOR_BREAKPOINTS_UPDATE_DELAY, () => {
DebuggerController.Breakpoints.updateEditorBreakpoints();
});
// Make sure the events listeners are up to date.
if (DebuggerView.instrumentsPaneTab == "events-tab") {
@ -1219,8 +1222,8 @@ SourceScripts.prototype = {
// If there are any stored breakpoints for the sources, display them again,
// both in the editor and the breakpoints pane.
DebuggerController.Breakpoints.updateEditorBreakpoints();
DebuggerController.Breakpoints.updatePaneBreakpoints();
DebuggerController.Breakpoints.updateEditorBreakpoints();
// Signal that sources have been added.
window.emit(EVENTS.SOURCES_ADDED);
@ -1711,6 +1714,10 @@ EventListeners.prototype = {
throw "Error getting event listeners: " + aResponse.message;
}
// Make sure all the listeners are sorted by the event type, since
// they're not guaranteed to be clustered together.
aResponse.listeners.sort((a, b) => a.type > b.type ? 1 : -1);
// Add all the listeners in the debugger view event linsteners container.
for (let listener of aResponse.listeners) {
let definitionSite = yield this._getDefinitionSite(listener.function);

View File

@ -78,6 +78,7 @@ support-files =
doc_scope-variable-4.html
doc_script-switching-01.html
doc_script-switching-02.html
doc_split-console-paused-reload.html
doc_step-out.html
doc_terminate-on-tab-close.html
doc_tracing-01.html
@ -237,6 +238,7 @@ skip-if = os == "linux" || e10s # Bug 888811 & bug 891176
[browser_dbg_sources-cache.js]
[browser_dbg_sources-labels.js]
[browser_dbg_sources-sorting.js]
[browser_dbg_split-console-paused-reload.js]
[browser_dbg_stack-01.js]
[browser_dbg_stack-02.js]
[browser_dbg_stack-03.js]

View File

@ -30,16 +30,16 @@ function test() {
is(gEvents.widget._parent.querySelectorAll(".side-menu-widget-item-checkbox").length, 4,
"There should be a checkbox for each item shown in the view.");
testEventItem(0, "doc_event-listeners-02.html", "keydown", ["window", "body"], false);
testEventItem(0, "doc_event-listeners-02.html", "change", ["body > input:nth-child(2)"], false);
testEventItem(1, "doc_event-listeners-02.html", "click", ["body > button:nth-child(1)"], false);
testEventItem(2, "doc_event-listeners-02.html", "change", ["body > input:nth-child(2)"], false);
testEventItem(2, "doc_event-listeners-02.html", "keydown", ["window", "body"], false);
testEventItem(3, "doc_event-listeners-02.html", "keyup", ["body > input:nth-child(2)"], false);
testEventGroup("interactionEvents", false);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
is(gEvents.getAllEvents().toString(), "keydown,click,change,keyup",
is(gEvents.getAllEvents().toString(), "change,click,keydown,keyup",
"The getAllEvents() method returns the correct stuff.");
is(gEvents.getCheckedEvents().toString(), "",
"The getCheckedEvents() method returns the correct stuff.");

View File

@ -30,7 +30,7 @@ function test() {
testEventGroup("interactionEvents", false);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
testEventArrays("keydown,click,change,keyup", "");
testEventArrays("change,click,keydown,keyup", "");
let updated = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
@ -43,7 +43,7 @@ function test() {
testEventGroup("interactionEvents", false);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
testEventArrays("keydown,click,change,keyup", "keydown");
testEventArrays("change,click,keydown,keyup", "change");
let updated = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
@ -56,7 +56,7 @@ function test() {
testEventGroup("interactionEvents", false);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
testEventArrays("keydown,click,change,keyup", "");
testEventArrays("change,click,keydown,keyup", "");
yield ensureThreadClientState(aPanel, "resumed");
yield closeDebuggerAndFinish(aPanel);

View File

@ -31,46 +31,46 @@ function test() {
testEventGroup("interactionEvents", false);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
testEventArrays("keydown,click,change,keyup", "");
testEventArrays("change,click,keydown,keyup", "");
let updated = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("interactionEvents"), gDebugger);
yield updated;
testEventItem(0, false);
testEventItem(1, false);
testEventItem(2, true);
testEventItem(3, false);
testEventGroup("interactionEvents", true);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
testEventArrays("keydown,click,change,keyup", "change");
let updated = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("interactionEvents"), gDebugger);
yield updated;
testEventItem(0, false);
testEventItem(1, false);
testEventItem(2, false);
testEventItem(3, false);
testEventGroup("interactionEvents", false);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
testEventArrays("keydown,click,change,keyup", "");
let updated = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("keyboardEvents"), gDebugger);
yield updated;
testEventItem(0, true);
testEventItem(1, false);
testEventItem(2, false);
testEventItem(3, false);
testEventGroup("interactionEvents", true);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
testEventArrays("change,click,keydown,keyup", "change");
let updated = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("interactionEvents"), gDebugger);
yield updated;
testEventItem(0, false);
testEventItem(1, false);
testEventItem(2, false);
testEventItem(3, false);
testEventGroup("interactionEvents", false);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
testEventArrays("change,click,keydown,keyup", "");
let updated = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("keyboardEvents"), gDebugger);
yield updated;
testEventItem(0, false);
testEventItem(1, false);
testEventItem(2, true);
testEventItem(3, true);
testEventGroup("interactionEvents", false);
testEventGroup("keyboardEvents", true);
testEventGroup("mouseEvents", false);
testEventArrays("keydown,click,change,keyup", "keydown,keyup");
testEventArrays("change,click,keydown,keyup", "keydown,keyup");
let updated = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("keyboardEvents"), gDebugger);
@ -83,7 +83,7 @@ function test() {
testEventGroup("interactionEvents", false);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
testEventArrays("keydown,click,change,keyup", "");
testEventArrays("change,click,keydown,keyup", "");
yield ensureThreadClientState(aPanel, "resumed");
yield closeDebuggerAndFinish(aPanel);

View File

@ -30,7 +30,7 @@ function test() {
testEventGroup("interactionEvents", false);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
testEventArrays("keydown,click,change,keyup", "");
testEventArrays("change,click,keydown,keyup", "");
let updated = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
@ -45,7 +45,7 @@ function test() {
testEventGroup("interactionEvents", false);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
testEventArrays("keydown,click,change,keyup", "keydown,click,change");
testEventArrays("change,click,keydown,keyup", "change,click,keydown");
yield reloadActiveTab(aPanel, gDebugger.EVENTS.EVENT_LISTENERS_FETCHED);
@ -56,7 +56,7 @@ function test() {
testEventGroup("interactionEvents", false);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
testEventArrays("keydown,click,change,keyup", "keydown,click,change");
testEventArrays("change,click,keydown,keyup", "change,click,keydown");
let updated = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
@ -71,7 +71,7 @@ function test() {
testEventGroup("interactionEvents", false);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
testEventArrays("keydown,click,change,keyup", "");
testEventArrays("change,click,keydown,keyup", "");
yield reloadActiveTab(aPanel, gDebugger.EVENTS.EVENT_LISTENERS_FETCHED);
@ -82,7 +82,7 @@ function test() {
testEventGroup("interactionEvents", false);
testEventGroup("keyboardEvents", false);
testEventGroup("mouseEvents", false);
testEventArrays("keydown,click,change,keyup", "");
testEventArrays("change,click,keydown,keyup", "");
yield ensureThreadClientState(aPanel, "resumed");
yield closeDebuggerAndFinish(aPanel);

View File

@ -60,7 +60,13 @@ function test() {
verify("foo = bar", e => e.name == "bar", [1, 6], [1, 9]);
verify("\nfoo\n=\nbar\n", e => e.name == "bar", [4, 0], [4, 3]);
// LabeledStatement and ContinueStatement, because it's 1968 again
// LabeledStatement, BreakStatement and ContinueStatement, because it's 1968 again
verify("foo: bar", e => e.name == "foo", [1, 0], [1, 3]);
verify("\nfoo\n:\nbar\n", e => e.name == "foo", [2, 0], [2, 3]);
verify("foo: for(;;) break foo", e => e.name == "foo", [1, 19], [1, 22]);
verify("\nfoo\n:\nfor(\n;\n;\n)\nbreak\nfoo\n", e => e.name == "foo", [9, 0], [9, 3]);
verify("foo: bar", e => e.name == "foo", [1, 0], [1, 3]);
verify("\nfoo\n:\nbar\n", e => e.name == "foo", [2, 0], [2, 3]);

View File

@ -0,0 +1,40 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Hitting ESC to open the split console when paused on reload should not stop
* the pending navigation.
*/
function test() {
Task.spawn(runTests);
}
function* runTests() {
const TAB_URL = EXAMPLE_URL + "doc_split-console-paused-reload.html";
let [,, panel] = yield initDebugger(TAB_URL);
let dbgWin = panel.panelWin;
let frames = dbgWin.DebuggerView.StackFrames;
let toolbox = gDevTools.getToolbox(panel.target);
yield panel.addBreakpoint({ url: TAB_URL, line: 16 });
info("Breakpoint was set.");
dbgWin.DebuggerController._target.activeTab.reload();
info("Page reloaded.");
yield waitForSourceAndCaretAndScopes(panel, ".html", 16);
yield ensureThreadClientState(panel, "paused");
info("Breakpoint was hit.");
EventUtils.sendMouseEvent({ type: "mousedown" },
frames.selectedItem.target,
dbgWin);
info("The breadcrumb received focus.");
// This is the meat of the test.
let result = toolbox.once("webconsole-ready", () => {
ok(toolbox.splitConsole, "Split console is shown.");
is(dbgWin.gThreadClient.state, "paused", "Execution is still paused.");
});
EventUtils.synthesizeKey("VK_ESCAPE", {}, dbgWin);
yield result;
yield resumeDebuggerThenCloseAndFinish(panel);
}

View File

@ -0,0 +1,20 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Test page for opening a split-console when execution is paused</title>
</head>
<body>
<script type="text/javascript">
function runDebuggerStatement() {
debugger;
}
window.foobar = 1;
</script>
</body>
</html>

View File

@ -294,6 +294,12 @@ Toolbox.prototype = {
let responsiveModeActive = this._isResponsiveModeActive();
if (e.keyCode === e.DOM_VK_ESCAPE && !responsiveModeActive) {
this.toggleSplitConsole();
// If the debugger is paused, don't let the ESC key stop any pending
// navigation.
let jsdebugger = this.getPanel("jsdebugger");
if (jsdebugger && jsdebugger.panelWin.gThreadClient.state == "paused") {
e.preventDefault();
}
}
},

View File

@ -450,8 +450,8 @@ let ParserHelpers = {
loc.end.column = loc.start.column + aNode.name.length;
return loc;
}
if (parentType == "ContinueStatement") {
// e.g. continue label
if (parentType == "ContinueStatement" || parentType == "BreakStatement") {
// e.g. continue label; or break label;
// The location is unavailable for the identifier node "label".
let loc = Cu.cloneInto(parentLocation, {});
loc.start.line = loc.end.line;

View File

@ -39,8 +39,8 @@ let testData = [
["VK_ESCAPE", {}, null, -1, 0]
];
let TEST_URL = "data:text/html,<h1 style='border: 1px solid red'>Filename:"+
" browser_bug894376_css_value_completion_new_property_value_pair.js</h1>";
let TEST_URL = "data:text/html,<style>h1{border: 1px solid red}</style>" +
"<h1>Test element</h1>";
let test = asyncTest(function*() {
yield addTab(TEST_URL);
@ -50,7 +50,7 @@ let test = asyncTest(function*() {
yield selectNode("h1", inspector);
info("Focusing a new css property editable property");
let brace = view.doc.querySelectorAll(".ruleview-ruleclose")[0];
let brace = view.doc.querySelectorAll(".ruleview-ruleclose")[1];
let editor = yield focusEditableField(brace);
info("Starting to test for css property completion");
@ -70,7 +70,7 @@ function* testCompletion([key, modifiers, completion, index, total], editor, vie
if (/tab/ig.test(key)) {
info("Waiting for the new property or value editor to get focused");
let brace = view.doc.querySelector(".ruleview-ruleclose");
let brace = view.doc.querySelectorAll(".ruleview-ruleclose")[1];
onKeyPress = once(brace.parentNode, "focus", true);
} else if (/(right|back_space|escape|return)/ig.test(key) ||
(modifiers.accelKey || modifiers.ctrlKey)) {

View File

@ -1,5 +1,4 @@
[DEFAULT]
skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
subsuite = devtools
support-files =
doc_simple-context.html

View File

@ -608,13 +608,10 @@
#resume {
list-style-image: url(debugger-pause.png);
-moz-image-region: rect(0px,16px,16px,0px);
transition: background 0.15s ease-in-out;
}
#resume[checked] {
background: none;
list-style-image: url(debugger-play.png);
-moz-image-region: rect(0px,32px,16px,16px);
}
@media (min-resolution: 2dppx) {

View File

@ -56,6 +56,7 @@
border-radius: 0;
margin: 2px 3px;
color: inherit;
transition: background 0.05s ease-in-out;
}
.devtools-menulist:-moz-focusring,
@ -176,24 +177,32 @@
}
/* Menu type buttons and checked states */
.theme-dark .devtools-menulist[open=true],
.theme-dark .devtools-toolbarbutton[open=true],
.theme-dark .devtools-toolbarbutton[open=true]:hover,
.theme-dark .devtools-toolbarbutton[open=true]:hover:active,
.theme-dark .devtools-toolbarbutton[checked=true],
.theme-dark .devtools-toolbarbutton[checked=true]:hover,
.theme-dark #toolbox-buttons .devtools-toolbarbutton[text-as-image][checked] {
background: rgba(29, 79, 115, .7); /* Select highlight blue */
color: #f5f7fa;
}
.theme-light .devtools-toolbarbutton[checked=true],
.theme-light #toolbox-buttons .devtools-toolbarbutton[text-as-image][checked] {
background: rgba(76, 158, 217, .2); /* Select highlight blue */
}
.theme-dark .devtools-menulist[open=true],
.theme-dark .devtools-toolbarbutton[open=true],
.theme-dark .devtools-toolbarbutton[open=true]:hover,
.theme-dark .devtools-toolbarbutton[open=true]:hover:active,
.theme-dark .devtools-toolbarbutton[checked=true]:hover {
background: rgba(29, 79, 115, .8); /* Select highlight blue */
color: #f5f7fa;
}
.theme-light .devtools-menulist[open=true],
.theme-light .devtools-toolbarbutton[open=true],
.theme-light .devtools-toolbarbutton[open=true]:hover,
.theme-light .devtools-toolbarbutton[open=true]:hover:active,
.theme-light .devtools-toolbarbutton[checked=true],
.theme-light .devtools-toolbarbutton[checked=true]:hover,
.theme-light #toolbox-buttons .devtools-toolbarbutton[text-as-image][checked] {
background: rgba(76, 158, 217, .2); /* Select highlight blue */
.theme-light .devtools-toolbarbutton[checked=true]:hover {
background: rgba(76, 158, 217, .4); /* Select highlight blue */
}
.devtools-option-toolbarbutton {
@ -827,7 +836,6 @@
.theme-light .command-button-invertable[checked=true]:not(:active) > image,
.theme-light .devtools-tab[icon-invertable][selected] > image,
.theme-light .devtools-tab[icon-invertable][highlighted] > image,
.theme-light #resume[checked] > image,
.theme-light #record-snapshot[checked] > image,
.theme-light #profiler-start[checked] > image {
filter: none !important;

View File

@ -381,11 +381,14 @@ case "$target" in
AC_SUBST(ANDROID_MEDIAROUTER_RES)
fi
MOZ_PATH_PROG(ZIPALIGN, zipalign, :, [$ANDROID_TOOLS])
MOZ_PATH_PROG(DX, dx, :, [$ANDROID_BUILD_TOOLS])
MOZ_PATH_PROG(AAPT, aapt, :, [$ANDROID_BUILD_TOOLS])
MOZ_PATH_PROG(AIDL, aidl, :, [$ANDROID_BUILD_TOOLS])
MOZ_PATH_PROG(ADB, adb, :, [$ANDROID_PLATFORM_TOOLS])
dnl Google has a history of moving the Android tools around. We don't
dnl care where they are, so let's try to find them anywhere we can.
ALL_ANDROID_TOOLS_PATHS="$ANDROID_TOOLS:$ANDROID_BUILD_TOOLS:$ANDROID_PLATFORM_TOOLS"
MOZ_PATH_PROG(ZIPALIGN, zipalign, :, [$ALL_ANDROID_TOOLS_PATHS])
MOZ_PATH_PROG(DX, dx, :, [$ALL_ANDROID_TOOLS_PATHS])
MOZ_PATH_PROG(AAPT, aapt, :, [$ALL_ANDROID_TOOLS_PATHS])
MOZ_PATH_PROG(AIDL, aidl, :, [$ALL_ANDROID_TOOLS_PATHS])
MOZ_PATH_PROG(ADB, adb, :, [$ALL_ANDROID_TOOLS_PATHS])
if test -z "$ZIPALIGN" -o "$ZIPALIGN" = ":"; then
AC_MSG_ERROR([The program zipalign was not found. Use --with-android-sdk={android-sdk-dir}.])

View File

@ -9,10 +9,12 @@ import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.mozglue.RobocopTarget;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
@ -21,6 +23,10 @@ public class ReferrerReceiver extends BroadcastReceiver {
private static final String ACTION_INSTALL_REFERRER = "com.android.vending.INSTALL_REFERRER";
// Sent when we're done.
@RobocopTarget
public static final String ACTION_REFERRER_RECEIVED = "org.mozilla.fennec.REFERRER_RECEIVED";
/**
* If the install intent has this source, we'll track the campaign ID.
*/
@ -52,6 +58,10 @@ public class ReferrerReceiver extends BroadcastReceiver {
if (TextUtils.equals(referrer.source, MOZILLA_UTM_SOURCE)) {
propagateMozillaCampaign(referrer);
}
// Broadcast a secondary, local intent to allow test code to respond.
final Intent receivedIntent = new Intent(ACTION_REFERRER_RECEIVED);
LocalBroadcastManager.getInstance(context).sendBroadcast(receivedIntent);
}

View File

@ -17,13 +17,17 @@ import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.distribution.ReferrerDescriptor;
import org.mozilla.gecko.distribution.ReferrerReceiver;
import org.mozilla.gecko.mozglue.RobocopTarget;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
/**
@ -139,41 +143,65 @@ public class testDistribution extends ContentProviderTest {
doTestInvalidReferrerIntent();
}
private void doReferrerTest(String ref, final TestableDistribution distribution, final Runnable distributionReady) throws InterruptedException {
final Intent intent = new Intent(ACTION_INSTALL_REFERRER);
intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, CLASS_REFERRER_RECEIVER);
intent.putExtra("referrer", ref);
final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(LOGTAG, "Test received " + intent.getAction());
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
distribution.addOnDistributionReadyCallback(distributionReady);
distribution.go();
}
});
}
};
IntentFilter intentFilter = new IntentFilter(ReferrerReceiver.ACTION_REFERRER_RECEIVED);
final LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(mActivity);
localBroadcastManager.registerReceiver(receiver, intentFilter);
Log.i(LOGTAG, "Broadcasting referrer intent.");
try {
mActivity.sendBroadcast(intent, null);
synchronized (distribution) {
distribution.wait(WAIT_TIMEOUT_MSEC);
}
} finally {
localBroadcastManager.unregisterReceiver(receiver);
}
}
public void doTestValidReferrerIntent() throws Exception {
// Send the faux-download intent.
// Equivalent to
// am broadcast -a com.android.vending.INSTALL_REFERRER \
// -n org.mozilla.fennec/org.mozilla.gecko.distribution.ReferrerReceiver \
// --es "referrer" "utm_source=mozilla&utm_medium=testmedium&utm_term=testterm&utm_content=testcontent&utm_campaign=distribution"
final String ref = "utm_source=mozilla&utm_medium=testmedium&utm_term=testterm&utm_content=testcontent&utm_campaign=distribution";
final Intent intent = new Intent(ACTION_INSTALL_REFERRER);
intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, CLASS_REFERRER_RECEIVER);
intent.putExtra("referrer", ref);
mActivity.sendBroadcast(intent);
// Wait for the intent to be processed.
final TestableDistribution distribution = new TestableDistribution(mActivity);
final Object wait = new Object();
distribution.addOnDistributionReadyCallback(new Runnable() {
final Runnable distributionReady = new Runnable() {
@Override
public void run() {
Log.i(LOGTAG, "Test told distribution is ready.");
mAsserter.ok(!distribution.exists(), "Not processed.", "No download because we're offline.");
ReferrerDescriptor referrerValue = TestableDistribution.getReferrerDescriptorForTesting();
mAsserter.dumpLog("Referrer was " + referrerValue);
mAsserter.is(referrerValue.content, "testcontent", "Referrer content");
mAsserter.is(referrerValue.medium, "testmedium", "Referrer medium");
mAsserter.is(referrerValue.campaign, "distribution", "Referrer campaign");
synchronized (wait) {
wait.notifyAll();
synchronized (distribution) {
distribution.notifyAll();
}
}
});
};
distribution.go();
synchronized (wait) {
wait.wait(WAIT_TIMEOUT_MSEC);
}
doReferrerTest(ref, distribution, distributionReady);
}
/**
@ -182,37 +210,25 @@ public class testDistribution extends ContentProviderTest {
* even if we *do* include it in a Campaign:Set message.
*/
public void doTestInvalidReferrerIntent() throws Exception {
// Send the faux-download intent.
// Equivalent to
// am broadcast -a com.android.vending.INSTALL_REFERRER \
// -n org.mozilla.fennec/org.mozilla.gecko.distribution.ReferrerReceiver \
// --es "referrer" "utm_source=mozilla&utm_medium=testmedium&utm_term=testterm&utm_content=testcontent&utm_campaign=testname"
final String ref = "utm_source=mozilla&utm_medium=testmedium&utm_term=testterm&utm_content=testcontent&utm_campaign=testname";
final Intent intent = new Intent(ACTION_INSTALL_REFERRER);
intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, CLASS_REFERRER_RECEIVER);
intent.putExtra("referrer", ref);
mActivity.sendBroadcast(intent);
// Wait for the intent to be processed.
final TestableDistribution distribution = new TestableDistribution(mActivity);
final Object wait = new Object();
distribution.addOnDistributionReadyCallback(new Runnable() {
final Runnable distributionReady = new Runnable() {
@Override
public void run() {
mAsserter.ok(!distribution.exists(), "Not processed.", "No download because campaign was wrong.");
ReferrerDescriptor referrerValue = TestableDistribution.getReferrerDescriptorForTesting();
mAsserter.is(referrerValue, null, "No referrer.");
synchronized (wait) {
wait.notifyAll();
synchronized (distribution) {
distribution.notifyAll();
}
}
});
};
distribution.go();
synchronized (wait) {
wait.wait(WAIT_TIMEOUT_MSEC);
}
doReferrerTest(ref, distribution, distributionReady);
}
// Initialize the distribution from the mock package.

View File

@ -154,10 +154,16 @@ public final class ThreadUtils {
return;
}
final String message = "Expected thread " +
expectedThreadId + " (\"" + expectedThread.getName() +
"\"), but running on thread " +
currentThreadId + " (\"" + currentThread.getName() + ")";
final String message;
if (expected) {
message = "Expected thread " + expectedThreadId +
" (\"" + expectedThread.getName() + "\"), but running on thread " +
currentThreadId + " (\"" + currentThread.getName() + "\")";
} else {
message = "Expected anything but " + expectedThreadId +
" (\"" + expectedThread.getName() + "\"), but running there.";
}
final IllegalThreadStateException e = new IllegalThreadStateException(message);
switch (behavior) {

View File

@ -196,6 +196,12 @@ public class GeckoSwipeRefreshLayout extends ViewGroup {
super.onAttachedToWindow();
removeCallbacks(mCancel);
removeCallbacks(mReturnToStartPosition);
// Sometimes the inner view doesn't get a proper layout
// pass when re-attached to the view tree (see bug 1010986).
if (getChildCount() > 0) {
getChildAt(0).forceLayout();
}
}
@Override

View File

@ -153,7 +153,11 @@ EventEmitter.prototype = {
if (!isWorker) {
caller = components.stack.caller.caller;
func = caller.name;
path = caller.filename.split(/ -> /)[1] + ":" + caller.lineNumber;
let file = caller.filename;
if (file.contains(" -> ")) {
file = caller.filename.split(/ -> /)[1];
}
path = file + ":" + caller.lineNumber;
}
let argOut = "(";