Backed out changeset e9e43e8256e1 (bug 1287827) for breaking tests

This commit is contained in:
Mark Banner 2016-07-29 18:14:08 +01:00
parent 9b96b9599c
commit b5f9f1309f
21 changed files with 894 additions and 4 deletions

View File

@ -1477,4 +1477,4 @@ pref("print.use_simplify_page", true);
// Space separated list of URLS that are allowed to send objects (instead of
// only strings) through webchannels. This list is duplicated in mobile/android/app/mobile.js
pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");
pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://hello.firefox.com https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");

View File

@ -295,6 +295,18 @@
noautofocus="true"
position="topcenter topright"/>
<panel id="loop-notification-panel"
class="loop-panel social-panel"
type="arrow"
hidden="true"
noautofocus="true"/>
<panel id="loop-panel"
class="loop-panel social-panel"
type="arrow"
orient="horizontal"
hidden="true"/>
<menupopup id="toolbar-context-menu"
onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('viewToolbarsMenuSeparator'));">
<menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"

View File

@ -20,6 +20,16 @@ let whitelist = [
// Tracked in bug 1004428.
{sourceName: /aboutaccounts\/(main|normalize)\.css$/i,
isFromDevTools: false},
// TokBox SDK assets, see bug 1032469.
{sourceName: /loop\/.*sdk-content\/.*\.css$/i,
isFromDevTools: false},
// Loop standalone client CSS uses placeholder cross browser pseudo-element
{sourceName: /loop\/.*\.css$/i,
errorMessage: /Unknown pseudo-class.*placeholder/i,
isFromDevTools: false},
{sourceName: /loop\/.*shared\/css\/common.css$/i,
errorMessage: /Unknown property .user-select./i,
isFromDevTools: false},
// Highlighter CSS uses a UA-only pseudo-class, see bug 985597.
{sourceName: /highlighters\.css$/i,
errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i,

View File

@ -6,5 +6,7 @@ support-files =
[browser_devices_get_user_media.js]
skip-if = buildapp == 'mulet' || (os == "linux" && debug) # linux: bug 976544
[browser_devices_get_user_media_about_urls.js]
skip-if = e10s && debug
[browser_devices_get_user_media_anim.js]
[browser_devices_get_user_media_in_frame.js]

View File

@ -0,0 +1,219 @@
/* 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/. */
const PREF_LOOP_CSP = "loop.CSP";
var gTab;
// Taken from dom/media/tests/mochitest/head.js
function isMacOSX10_6orOlder() {
var is106orOlder = false;
if (navigator.platform.indexOf("Mac") == 0) {
var version = Cc["@mozilla.org/system-info;1"]
.getService(Ci.nsIPropertyBag2)
.getProperty("version");
// the next line is correct: Mac OS 10.6 corresponds to Darwin version 10.x !
// Mac OS 10.7 is Darwin version 11.x. the |version| string we've got here
// is the Darwin version.
is106orOlder = (parseFloat(version) < 11.0);
}
return is106orOlder;
}
// Screensharing is disabled on older platforms (WinXP and Mac 10.6).
function isOldPlatform() {
const isWinXP = navigator.userAgent.indexOf("Windows NT 5.1") != -1;
if (isMacOSX10_6orOlder() || isWinXP) {
info(true, "Screensharing disabled for OSX10.6 and WinXP");
return true;
}
return false;
}
// Linux prompts aren't working for screensharing.
function isLinux() {
return navigator.platform.indexOf("Linux") != -1;
}
function loadPage(aUrl) {
let deferred = Promise.defer();
gTab.linkedBrowser.addEventListener("load", function onload() {
gTab.linkedBrowser.removeEventListener("load", onload, true);
is(PopupNotifications._currentNotifications.length, 0,
"should start the test without any prior popup notification");
deferred.resolve();
}, true);
content.location = aUrl;
return deferred.promise;
}
registerCleanupFunction(function() {
gBrowser.removeCurrentTab();
});
const permissionError = "error: NotAllowedError: The request is not allowed " +
"by the user agent or the platform in the current context.";
var gTests = [
{
desc: "getUserMedia about:loopconversation shouldn't prompt",
run: function checkAudioVideoLoop() {
yield SpecialPowers.pushPrefEnv({
"set": [[PREF_LOOP_CSP, "default-src 'unsafe-inline'"]],
});
yield loadPage("about:loopconversation");
info("requesting devices");
let promise = promiseObserverCalled("recording-device-events");
yield promiseRequestDevice(true, true);
yield promise;
// Wait for the devices to actually be captured and running before
// proceeding.
yield promisePopupNotification("webRTC-sharingDevices");
is((yield getMediaCaptureState()), "CameraAndMicrophone",
"expected camera and microphone to be shared");
yield closeStream();
yield SpecialPowers.popPrefEnv();
}
},
{
desc: "getUserMedia about:loopconversation should prompt for window sharing",
run: function checkShareScreenLoop() {
if (isOldPlatform() || isLinux()) {
return;
}
yield SpecialPowers.pushPrefEnv({
"set": [[PREF_LOOP_CSP, "default-src 'unsafe-inline'"]],
});
yield loadPage("about:loopconversation");
info("requesting screen");
let promise = promiseObserverCalled("getUserMedia:request");
yield promiseRequestDevice(false, true, null, "window");
// Wait for the devices to actually be captured and running before
// proceeding.
yield promisePopupNotification("webRTC-shareDevices");
is((yield getMediaCaptureState()), "none",
"expected camera and microphone not to be shared");
yield promiseMessage(permissionError, () => {
PopupNotifications.panel.firstChild.button.click();
});
yield expectObserverCalled("getUserMedia:response:deny");
yield expectObserverCalled("recording-window-ended");
yield SpecialPowers.popPrefEnv();
}
},
{
desc: "getUserMedia about:evil should prompt",
run: function checkAudioVideoNonLoop() {
yield loadPage("about:evil");
let promise = promiseObserverCalled("getUserMedia:request");
yield promiseRequestDevice(true, true);
yield promise;
is((yield getMediaCaptureState()), "none",
"expected camera and microphone not to be shared");
}
},
];
function test() {
waitForExplicitFinish();
gTab = gBrowser.addTab();
gBrowser.selectedTab = gTab;
gTab.linkedBrowser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
Task.spawn(function () {
yield ContentTask.spawn(gBrowser.selectedBrowser,
getRootDirectory(gTestPath) + "get_user_media.html",
function* (url) {
const Ci = Components.interfaces;
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
/* A fake about module to map get_user_media.html to different about urls. */
function fakeLoopAboutModule() {
}
fakeLoopAboutModule.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
newChannel: function (aURI, aLoadInfo) {
let uri = Services.io.newURI(url, null, null);
let chan = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
chan.owner = Services.scriptSecurityManager.getSystemPrincipal();
return chan;
},
getURIFlags: function (aURI) {
return Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
Ci.nsIAboutModule.ALLOW_SCRIPT |
Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD |
Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT;
}
};
var factory = XPCOMUtils._getFactory(fakeLoopAboutModule);
var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
let UUIDGenerator = Components.classes["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator);
registrar.registerFactory(UUIDGenerator.generateUUID(), "",
"@mozilla.org/network/protocol/about;1?what=loopconversation",
factory);
registrar.registerFactory(UUIDGenerator.generateUUID(), "",
"@mozilla.org/network/protocol/about;1?what=evil",
factory);
});
yield SpecialPowers.pushPrefEnv({
"set": [[PREF_PERMISSION_FAKE, true],
["media.getusermedia.screensharing.enabled", true]],
});
for (let test of gTests) {
info(test.desc);
yield test.run();
// Cleanup before the next test
expectNoObserverCalled();
}
yield ContentTask.spawn(gBrowser.selectedBrowser, null,
function* () {
const Ci = Components.interfaces;
const Cc = Components.classes;
var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
let cid = Cc["@mozilla.org/network/protocol/about;1?what=loopconversation"];
registrar.unregisterFactory(cid,
registrar.getClassObject(cid, Ci.nsIFactory));
cid = Cc["@mozilla.org/network/protocol/about;1?what=evil"];
registrar.unregisterFactory(cid,
registrar.getClassObject(cid, Ci.nsIFactory));
});
}).then(finish, ex => {
ok(false, "Unexpected Exception: " + ex);
finish();
});
}

View File

@ -102,6 +102,22 @@ static RedirEntry kRedirMap[] = {
#endif
{ "accounts", "chrome://browser/content/aboutaccounts/aboutaccounts.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
// Linkable because of indexeddb use (bug 1228118)
{ "loopconversation", "chrome://loop/content/panels/conversation.html",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT |
nsIAboutModule::MAKE_LINKABLE |
nsIAboutModule::ENABLE_INDEXED_DB },
// Linkable because of indexeddb use (bug 1228118)
{ "looppanel", "chrome://loop/content/panels/panel.html",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT |
nsIAboutModule::MAKE_LINKABLE |
nsIAboutModule::ENABLE_INDEXED_DB,
// Shares an IndexedDB origin with about:loopconversation.
"loopconversation" },
{ "reader", "chrome://global/content/reader/aboutReader.html",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |

View File

@ -108,6 +108,8 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
#ifdef MOZ_SERVICES_HEALTHREPORT
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "healthreport", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#endif
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "looppanel", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "loopconversation", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "reader", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#if defined(XP_WIN)
{ NS_IEHISTORYENUMERATOR_CONTRACTID, &kNS_WINIEHISTORYENUMERATOR_CID },

View File

@ -64,6 +64,7 @@ var kVersion = 6;
* version the button is removed in as the value. e.g. "pocket-button": 5
*/
var ObsoleteBuiltinButtons = {
"loop-button": 5,
"pocket-button": 6
};
@ -237,6 +238,7 @@ var CustomizableUIInternal = {
"bookmarks-menu-button",
"downloads-button",
"home-button",
"loop-button",
];
if (AppConstants.MOZ_DEV_EDITION) {

View File

@ -242,6 +242,8 @@
<panelview id="PanelUI-socialapi" flex="1"/>
<panelview id="PanelUI-loopapi" flex="1"/>
<panelview id="PanelUI-feeds" flex="1" oncommand="FeedHandler.subscribeToFeed(null, event);">
<label value="&feedsMenu2.label;" class="panel-subview-header"/>
</panelview>

View File

@ -153,6 +153,54 @@ this.UITour = {
query: "#panic-button",
widgetName: "panic-button",
}],
["loop", {
allowAdd: true,
query: "#loop-button",
widgetName: "loop-button",
}],
["loop-newRoom", {
infoPanelPosition: "leftcenter topright",
query: (aDocument) => {
let loopUI = aDocument.defaultView.LoopUI;
// Use the parentElement full-width container of the button so our arrow
// doesn't overlap the panel contents much.
return loopUI.browser.contentDocument.querySelector(".new-room-button").parentElement;
},
}],
["loop-roomList", {
infoPanelPosition: "leftcenter topright",
query: (aDocument) => {
let loopUI = aDocument.defaultView.LoopUI;
return loopUI.browser.contentDocument.querySelector(".room-list");
},
}],
["loop-selectedRoomButtons", {
infoPanelOffsetY: -20,
infoPanelPosition: "start_after",
query: (aDocument) => {
let chatbox = aDocument.querySelector("chatbox[src^='about\:loopconversation'][selected]");
// Check that the real target actually exists
if (!chatbox || !chatbox.contentDocument ||
!chatbox.contentDocument.querySelector(".call-action-group")) {
return null;
}
// But anchor on the <browser> in the chatbox so the panel doesn't jump to undefined
// positions when the copy/email buttons disappear e.g. when the feedback form opens or
// somebody else joins the room.
return chatbox.content;
},
}],
["loop-signInUpLink", {
query: (aDocument) => {
let loopBrowser = aDocument.defaultView.LoopUI.browser;
if (!loopBrowser) {
return null;
}
return loopBrowser.contentDocument.querySelector(".signin-link");
},
}],
["pocket", {
allowAdd: true,
query: "#pocket-button",
@ -847,12 +895,16 @@ this.UITour = {
this.hideInfo(aWindow);
// Ensure the menu panel is hidden before calling recreatePopup so popup events occur.
this.hideMenu(aWindow, "appMenu");
this.hideMenu(aWindow, "loop");
this.hideMenu(aWindow, "controlCenter");
// Clean up panel listeners after calling hideMenu above.
aWindow.PanelUI.panel.removeEventListener("popuphiding", this.hideAppMenuAnnotations);
aWindow.PanelUI.panel.removeEventListener("ViewShowing", this.hideAppMenuAnnotations);
aWindow.PanelUI.panel.removeEventListener("popuphidden", this.onPanelHidden);
let loopPanel = aWindow.document.getElementById("loop-notification-panel");
loopPanel.removeEventListener("popuphidden", this.onPanelHidden);
loopPanel.removeEventListener("popuphiding", this.hideLoopPanelAnnotations);
let controlCenterPanel = aWindow.gIdentityHandler._identityPopup;
controlCenterPanel.removeEventListener("popuphidden", this.onPanelHidden);
controlCenterPanel.removeEventListener("popuphiding", this.hideControlCenterAnnotations);
@ -1683,6 +1735,31 @@ this.UITour = {
popup.addEventListener("popupshown", onPopupShown);
}
aWindow.document.getElementById("identity-box").click();
} else if (aMenuName == "loop") {
let toolbarButton = aWindow.LoopUI.toolbarButton;
// It's possible to have a node that isn't placed anywhere
if (!toolbarButton || !toolbarButton.node ||
!CustomizableUI.getPlacementOfWidget(toolbarButton.node.id)) {
log.debug("Can't show the Loop menu since the toolbarButton isn't placed");
return;
}
let panel = aWindow.document.getElementById("loop-notification-panel");
panel.setAttribute("noautohide", true);
if (panel.state != "open") {
this.recreatePopup(panel);
this.clearAvailableTargetsCache();
}
// An event object is expected but we don't want to toggle the panel with a click if the panel
// is already open.
aWindow.LoopUI.openPanel({ target: toolbarButton.node, }, "rooms").then(() => {
if (aOpenCallback) {
aOpenCallback();
}
});
panel.addEventListener("popuphidden", this.onPanelHidden);
panel.addEventListener("popuphiding", this.hideLoopPanelAnnotations);
} else if (aMenuName == "pocket") {
this.getTarget(aWindow, "pocket").then(Task.async(function* onPocketTarget(target) {
let widgetGroupWrapper = CustomizableUI.getWidget(target.widgetName);
@ -1741,6 +1818,9 @@ this.UITour = {
} else if (aMenuName == "controlCenter") {
let panel = aWindow.gIdentityHandler._identityPopup;
panel.hidePopup();
} else if (aMenuName == "loop") {
let panel = aWindow.document.getElementById("loop-notification-panel");
panel.hidePopup();
}
},
@ -1773,6 +1853,12 @@ this.UITour = {
UITour.hideAnnotationsForPanel(aEvent, UITour.targetIsInAppMenu);
},
hideLoopPanelAnnotations: function(aEvent) {
UITour.hideAnnotationsForPanel(aEvent, (aTarget) => {
return aTarget.targetName.startsWith("loop-") && aTarget.targetName != "loop-selectedRoomButtons";
});
},
hideControlCenterAnnotations(aEvent) {
UITour.hideAnnotationsForPanel(aEvent, (aTarget) => {
return aTarget.targetName.startsWith("controlCenter-");
@ -1845,6 +1931,12 @@ this.UITour = {
case "availableTargets":
this.getAvailableTargets(aMessageManager, aWindow, aCallbackID);
break;
case "loop":
const FTU_VERSION = 1;
this.sendPageCallback(aMessageManager, aCallbackID, {
gettingStartedSeen: (Services.prefs.getIntPref("loop.gettingStarted.latestFTUVersion") >= FTU_VERSION),
});
break;
case "search":
case "selectedSearchEngine":
Services.search.init(rv => {
@ -1888,6 +1980,10 @@ this.UITour = {
}
} catch (e) {}
break;
case "Loop:ResumeTourOnFirstJoin":
// Ignore aValue in this case to avoid accidentally setting it to false.
Services.prefs.setBoolPref("loop.gettingStarted.resumeOnFirstJoin", true);
break;
default:
log.error("setConfiguration: Unknown configuration requested: " + aConfiguration);
break;

View File

@ -31,6 +31,9 @@ skip-if = os == "linux" # Linux: Bug 986760, Bug 989101.
[browser_UITour_detach_tab.js]
[browser_UITour_forceReaderMode.js]
[browser_UITour_heartbeat.js]
[browser_UITour_loop.js]
skip-if = true # Bug 1225832 - New Loop architecture is not compatible with test.
[browser_UITour_loop_panel.js]
[browser_UITour_modalDialog.js]
skip-if = os != "mac" # modal dialog disabling only working on OS X.
[browser_UITour_observe.js]

View File

@ -21,6 +21,7 @@ add_UITour_task(function* test_availableTargets() {
"customize",
"help",
"home",
"loop",
"devtools",
...(hasPocket ? ["pocket"] : []),
"privateWindow",
@ -49,6 +50,7 @@ add_UITour_task(function* test_availableTargets_changeWidgets() {
"backForward",
"customize",
"help",
"loop",
"devtools",
"home",
...(hasPocket ? ["pocket"] : []),
@ -84,6 +86,7 @@ add_UITour_task(function* test_availableTargets_exceptionFromGetTarget() {
"customize",
"help",
"home",
"loop",
"devtools",
...(hasPocket ? ["pocket"] : []),
"privateWindow",

View File

@ -0,0 +1,414 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var gTestTab;
var gContentAPI;
var gContentWindow;
var gMessageHandlers;
var loopButton;
var fakeRoom;
var loopPanel = document.getElementById("loop-notification-panel");
const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
const { LoopRooms } = Cu.import("chrome://loop/content/modules/LoopRooms.jsm", {});
const { MozLoopServiceInternal } = Cu.import("chrome://loop/content/modules/MozLoopService.jsm", {});
const FTU_VERSION = 1;
function test() {
UITourTest();
}
function runOffline(fun) {
return (done) => {
Services.io.offline = true;
fun(function onComplete() {
Services.io.offline = false;
done();
});
}
}
var tests = [
taskify(function* test_gettingStartedClicked_linkOpenedWithExpectedParams() {
// Set latestFTUVersion to lower number to show FTU panel.
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 0);
Services.prefs.setCharPref("loop.gettingStarted.url", "http://example.com");
is(loopButton.open, false, "Menu should initially be closed");
loopButton.click();
yield waitForConditionPromise(() => {
return loopButton.open;
}, "Menu should be visible after showMenu()");
gContentAPI.registerPageID("hello-tour_OpenPanel_testPage");
yield new Promise(resolve => {
gContentAPI.ping(() => resolve());
});
let loopDoc = document.getElementById("loop-notification-panel").children[0].contentDocument;
yield waitForConditionPromise(() => {
return loopDoc.readyState == 'complete';
}, "Loop notification panel document should be fully loaded.", 50);
let gettingStartedButton = loopDoc.getElementById("fte-button");
ok(gettingStartedButton, "Getting Started button should be found");
let newTabPromise = waitForConditionPromise(() => {
return gBrowser.currentURI.path.includes("utm_source=firefox-browser");
}, "New tab with utm_content=testPageNewID should have opened");
gettingStartedButton.click();
yield newTabPromise;
ok(gBrowser.currentURI.path.includes("utm_content=hello-tour_OpenPanel_testPage"),
"Expected URL opened (" + gBrowser.currentURI.path + ")");
yield gBrowser.removeCurrentTab();
checkLoopPanelIsHidden();
}),
taskify(function* test_gettingStartedClicked_linkOpenedWithExpectedParams2() {
// Set latestFTUVersion to lower number to show FTU panel.
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 0);
// Force a refresh of the loop panel since going from seen -> unseen doesn't trigger
// automatic re-rendering.
let loopWin = document.getElementById("loop-notification-panel").children[0].contentWindow;
var event = new loopWin.CustomEvent("GettingStartedSeen", { detail: false });
loopWin.dispatchEvent(event);
UITour.pageIDsForSession.clear();
Services.prefs.setCharPref("loop.gettingStarted.url", "http://example.com");
is(loopButton.open, false, "Menu should initially be closed");
loopButton.click();
yield waitForConditionPromise(() => {
return loopButton.open;
}, "Menu should be visible after showMenu()");
gContentAPI.registerPageID("hello-tour_OpenPanel_testPageOldId");
yield new Promise(resolve => {
gContentAPI.ping(() => resolve());
});
// Set the time of the page ID to 10 hours earlier, so that it is considered "expired".
UITour.pageIDsForSession.set("hello-tour_OpenPanel_testPageOldId",
{lastSeen: Date.now() - (10 * 60 * 60 * 1000)});
let loopDoc = loopWin.document;
let gettingStartedButton = loopDoc.getElementById("fte-button");
ok(gettingStartedButton, "Getting Started button should be found");
let newTabPromise = waitForConditionPromise(() => {
Services.console.logStringMessage(gBrowser.currentURI.path);
return gBrowser.currentURI.path.includes("utm_source=firefox-browser");
}, "New tab with utm_content=testPageNewID should have opened");
gettingStartedButton.click();
yield newTabPromise;
ok(!gBrowser.currentURI.path.includes("utm_content=hello-tour_OpenPanel_testPageOldId"),
"Expected URL opened without the utm_content parameter (" +
gBrowser.currentURI.path + ")");
yield gBrowser.removeCurrentTab();
checkLoopPanelIsHidden();
}),
// Test the menu was cleaned up in teardown.
taskify(function* setup_menu_cleanup() {
gContentAPI.showMenu("loop");
yield waitForConditionPromise(() => {
return loopButton.open;
}, "Menu should be visible after showMenu()");
// Leave it open so it gets torn down and we can test below that teardown was succesful.
}),
taskify(function* test_menu_cleanup() {
// Test that the open menu from above was torn down fully.
checkLoopPanelIsHidden();
}),
function test_availableTargets(done) {
gContentAPI.showMenu("loop");
gContentAPI.getConfiguration("availableTargets", (data) => {
for (let targetName of ["loop-newRoom", "loop-roomList", "loop-signInUpLink"]) {
isnot(data.targets.indexOf(targetName), -1, targetName + " should exist");
}
done();
});
},
function test_getConfigurationLoop(done) {
let gettingStartedSeen = Services.prefs.getIntPref("loop.gettingStarted.latestFTUVersion") >= FTU_VERSION;
gContentAPI.getConfiguration("loop", (data) => {
is(data.gettingStartedSeen, gettingStartedSeen,
"The configuration property should equal that of the pref");
done();
});
},
function test_hideMenuHidesAnnotations(done) {
let infoPanel = document.getElementById("UITourTooltip");
let highlightPanel = document.getElementById("UITourHighlightContainer");
gContentAPI.showMenu("loop", function menuCallback() {
gContentAPI.showHighlight("loop-roomList");
gContentAPI.showInfo("loop-newRoom", "Make a new room", "AKA. conversation");
UITour.getTarget(window, "loop-newRoom").then((target) => {
waitForPopupAtAnchor(infoPanel, target.node, Task.async(function* checkPanelIsOpen() {
isnot(loopPanel.state, "closed", "Loop panel should still be open");
ok(loopPanel.hasAttribute("noautohide"), "@noautohide should still be on the loop panel");
is(highlightPanel.getAttribute("targetName"), "loop-roomList", "Check highlight @targetname");
is(infoPanel.getAttribute("targetName"), "loop-newRoom", "Check info panel @targetname");
info("Close the loop menu and make sure the annotations inside disappear");
let hiddenPromises = [promisePanelElementHidden(window, infoPanel),
promisePanelElementHidden(window, highlightPanel)];
gContentAPI.hideMenu("loop");
yield Promise.all(hiddenPromises);
isnot(infoPanel.state, "open", "Info panel should have automatically hid");
isnot(highlightPanel.state, "open", "Highlight panel should have automatically hid");
done();
}), "Info panel should be anchored to the new room button");
});
});
},
runOffline(function test_notifyLoopChatWindowOpenedClosed(done) {
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowOpened", "Check Loop:ChatWindowOpened notification");
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowShown", "Check Loop:ChatWindowShown notification");
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowClosed", "Check Loop:ChatWindowClosed notification");
gContentAPI.observe((event, params) => {
ok(false, "No more notifications should have arrived");
});
});
done();
});
document.querySelector("#pinnedchats > chatbox").close();
});
LoopRooms.open("fakeTourRoom");
}),
runOffline(function test_notifyLoopRoomURLCopied(done) {
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowOpened", "Loop chat window should've opened");
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowShown", "Check Loop:ChatWindowShown notification");
let chat = document.querySelector("#pinnedchats > chatbox");
gContentAPI.observe((event, params) => {
is(event, "Loop:RoomURLCopied", "Check Loop:RoomURLCopied notification");
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowClosed", "Check Loop:ChatWindowClosed notification");
});
chat.close();
done();
});
let window = chat.content.contentWindow;
waitForConditionPromise(
() => chat.content.contentDocument.querySelector(".btn-copy"),
"Copy button should be there"
).then(() => chat.content.contentDocument.querySelector(".btn-copy").click());
});
});
LoopRooms.open("fakeTourRoom");
}),
runOffline(function test_notifyLoopRoomURLEmailed(done) {
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowOpened", "Loop chat window should've opened");
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowShown", "Check Loop:ChatWindowShown notification");
let chat = document.querySelector("#pinnedchats > chatbox");
let composeEmailCalled = false;
gContentAPI.observe((event, params) => {
is(event, "Loop:RoomURLEmailed", "Check Loop:RoomURLEmailed notification");
ok(composeEmailCalled, "mozLoop.composeEmail should be called");
gContentAPI.observe((event, params) => {
is(event, "Loop:ChatWindowClosed", "Check Loop:ChatWindowClosed notification");
});
chat.close();
done();
});
gMessageHandlers.ComposeEmail = function(message, reply) {
let [subject, body, recipient] = message.data;
ok(subject, "composeEmail should be invoked with at least a subject value");
composeEmailCalled = true;
reply();
};
waitForConditionPromise(
() => chat.content.contentDocument.querySelector(".btn-email"),
"Email button should be there"
).then(() => chat.content.contentDocument.querySelector(".btn-email").click());
});
});
LoopRooms.open("fakeTourRoom");
}),
taskify(function* test_arrow_panel_position() {
is(loopButton.open, false, "Menu should initially be closed");
let popup = document.getElementById("UITourTooltip");
yield showMenuPromise("loop");
let currentTarget = "loop-newRoom";
yield showInfoPromise(currentTarget, "This is " + currentTarget, "My arrow should be on the side");
is(popup.popupBoxObject.alignmentPosition, "start_before", "Check " + currentTarget + " position");
currentTarget = "loop-roomList";
yield showInfoPromise(currentTarget, "This is " + currentTarget, "My arrow should be on the side");
is(popup.popupBoxObject.alignmentPosition, "start_before", "Check " + currentTarget + " position");
currentTarget = "loop-signInUpLink";
yield showInfoPromise(currentTarget, "This is " + currentTarget, "My arrow should be underneath");
is(popup.popupBoxObject.alignmentPosition, "after_end", "Check " + currentTarget + " position");
}),
taskify(function* test_setConfiguration() {
is(Services.prefs.getBoolPref("loop.gettingStarted.resumeOnFirstJoin"), false, "pref should be false but exist");
gContentAPI.setConfiguration("Loop:ResumeTourOnFirstJoin", true);
yield waitForConditionPromise(() => {
return Services.prefs.getBoolPref("loop.gettingStarted.resumeOnFirstJoin");
}, "Pref should change to true via setConfiguration");
Services.prefs.clearUserPref("loop.gettingStarted.resumeOnFirstJoin");
}),
taskify(function* test_resumeViaMenuPanel_roomClosedTabOpen() {
Services.prefs.setBoolPref("loop.gettingStarted.resumeOnFirstJoin", true);
// Create a fake room and then add a fake non-owner participant
let roomsMap = setupFakeRoom();
roomsMap.get("fakeTourRoom").participants = [{
owner: false,
}];
// Set the tour URL to be the current page with a different query param
let gettingStartedURL = gTestTab.linkedBrowser.currentURI.resolve("?gettingstarted=1");
Services.prefs.setCharPref("loop.gettingStarted.url", gettingStartedURL);
let observationPromise = new Promise((resolve) => {
gContentAPI.observe((event, params) => {
is(event, "Loop:IncomingConversation", "Page should have been notified about incoming conversation");
is(params.conversationOpen, false, "conversationOpen should be false");
is(gBrowser.selectedTab, gTestTab, "The same tab should be selected");
resolve();
});
});
// Now open the menu while that non-owner is in the fake room to trigger resuming the tour
yield showMenuPromise("loop");
yield observationPromise;
Services.prefs.clearUserPref("loop.gettingStarted.resumeOnFirstJoin");
}),
taskify(function* test_resumeViaMenuPanel_roomClosedTabClosed() {
Services.prefs.setBoolPref("loop.gettingStarted.resumeOnFirstJoin", true);
// Create a fake room and then add a fake non-owner participant
let roomsMap = setupFakeRoom();
roomsMap.get("fakeTourRoom").participants = [{
owner: false,
}];
// Set the tour URL to a page that's not open yet
Services.prefs.setCharPref("loop.gettingStarted.url", gBrowser.currentURI.prePath);
let newTabPromise = waitForConditionPromise(() => {
return gBrowser.currentURI.path.includes("incomingConversation=waiting");
}, "New tab with incomingConversation=waiting should have opened");
// Now open the menu while that non-owner is in the fake room to trigger resuming the tour
yield showMenuPromise("loop");
yield newTabPromise;
yield gBrowser.removeCurrentTab();
Services.prefs.clearUserPref("loop.gettingStarted.resumeOnFirstJoin");
}),
];
// End tests
function checkLoopPanelIsHidden() {
ok(!loopPanel.hasAttribute("noautohide"), "@noautohide on the loop panel should have been cleaned up");
ok(!loopPanel.hasAttribute("panelopen"), "The panel shouldn't have @panelopen");
isnot(loopPanel.state, "open", "The panel shouldn't be open");
is(loopButton.hasAttribute("open"), false, "Loop button should know that the panel is closed");
}
function setupFakeRoom() {
let room = Object.create(fakeRoom);
let roomsMap = new Map([
[room.roomToken, room]
]);
LoopRooms.stubCache(roomsMap);
return roomsMap;
}
if (Services.prefs.getBoolPref("loop.enabled")) {
loopButton = window.LoopUI.toolbarButton.node;
fakeRoom = {
decryptedContext: { roomName: "fakeTourRoom" },
participants: [],
maxSize: 2,
ctime: Date.now()
};
for (let prop of ["roomToken", "roomOwner", "roomUrl"])
fakeRoom[prop] = "fakeTourRoom";
LoopAPI.stubMessageHandlers(gMessageHandlers = {
// Stub the rooms object API to fully control the test behavior.
"Rooms:*": function(action, message, reply) {
switch (action.split(":").pop()) {
case "GetAll":
reply([fakeRoom]);
break;
case "Get":
reply(fakeRoom);
break;
case "Join":
reply({
apiKey: "fakeTourRoom",
sessionToken: "fakeTourRoom",
sessionId: "fakeTourRoom",
expires: Date.now() + 240000
});
break;
case "RefreshMembership":
reply({ expires: Date.now() + 240000 });
default:
reply();
}
},
// Stub the metadata retrieval to suppress console warnings and return faster.
GetSelectedTabMetadata: function(message, reply) {
reply({ favicon: null });
}
});
registerCleanupFunction(() => {
Services.prefs.clearUserPref("loop.gettingStarted.resumeOnFirstJoin");
Services.prefs.clearUserPref("loop.gettingStarted.latestFTUVersion");
Services.prefs.clearUserPref("loop.gettingStarted.url");
Services.io.offline = false;
// Copied from browser/components/loop/test/mochitest/head.js
// Remove the iframe after each test. This also avoids mochitest complaining
// about leaks on shutdown as we intentionally hold the iframe open for the
// life of the application.
let frameId = loopButton.getAttribute("notificationFrameId");
let frame = document.getElementById(frameId);
if (frame) {
frame.remove();
}
// Remove the stubbed rooms.
LoopRooms.stubCache(null);
// Restore the stubbed handlers.
LoopAPI.restore();
});
} else {
ok(true, "Loop is disabled so skip the UITour Loop tests");
tests = [];
}

View File

@ -0,0 +1,67 @@
"use strict";
var gTestTab;
var gContentAPI;
var gContentWindow;
var gMessageHandlers;
var loopButton;
var fakeRoom;
var loopPanel = document.getElementById("loop-notification-panel");
const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
const { LoopRooms } = Cu.import("chrome://loop/content/modules/LoopRooms.jsm", {});
if (!Services.prefs.getBoolPref("loop.enabled")) {
ok(true, "Loop is disabled so skip the UITour Loop tests");
} else {
function checkLoopPanelIsHidden() {
ok(!loopPanel.hasAttribute("noautohide"), "@noautohide on the loop panel should have been cleaned up");
ok(!loopPanel.hasAttribute("panelopen"), "The panel shouldn't have @panelopen");
isnot(loopPanel.state, "open", "The panel shouldn't be open");
is(loopButton.hasAttribute("open"), false, "Loop button should know that the panel is closed");
}
add_task(setup_UITourTest);
add_task(function() {
loopButton = window.LoopUI.toolbarButton.node;
registerCleanupFunction(() => {
Services.prefs.clearUserPref("loop.gettingStarted.latestFTUVersion");
Services.io.offline = false;
// Copied from browser/components/loop/test/mochitest/head.js
// Remove the iframe after each test. This also avoids mochitest complaining
// about leaks on shutdown as we intentionally hold the iframe open for the
// life of the application.
let frameId = loopButton.getAttribute("notificationFrameId");
let frame = document.getElementById(frameId);
if (frame) {
frame.remove();
}
});
});
add_UITour_task(function* test_menu_show_hide() {
// The targets to highlight only appear after getting started is launched.
// Set latestFTUVersion to lower number to show FTU panel.
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 0);
is(loopButton.open, false, "Menu should initially be closed");
gContentAPI.showMenu("loop");
yield waitForConditionPromise(() => {
return loopPanel.state == "open";
}, "Menu should be visible after showMenu()");
ok(loopPanel.hasAttribute("noautohide"), "@noautohide should be on the loop panel");
ok(loopPanel.hasAttribute("panelopen"), "The panel should have @panelopen");
ok(loopButton.hasAttribute("open"), "Loop button should know that the menu is open");
gContentAPI.hideMenu("loop");
yield waitForConditionPromise(() => {
return !loopButton.open;
}, "Menu should be hidden after hideMenu()");
checkLoopPanelIsHidden();
});
}

View File

@ -99,6 +99,19 @@ quit-button.tooltiptext.linux2 = Quit %1$S (%2$S)
# %2$S is the keyboard shortcut
quit-button.tooltiptext.mac = Quit %1$S (%2$S)
# LOCALIZATION NOTE(loop-call-button3.label): This is a brand name, request
# approval before you change it.
loop-call-button3.label = Hello
loop-call-button3.tooltiptext2 = Browse this page with a friend
loop-call-button3-error.tooltiptext = Error!
loop-call-button3-donotdisturb.tooltiptext = Do not disturb
loop-call-button3-screensharing.tooltiptext = You are sharing your screen
loop-call-button3-active.tooltiptext2 = You are sharing your tabs
loop-call-button3-participantswaiting.tooltiptext2 = Someone is waiting for you
# LOCALIZATION NOTE(loop-call-button3-pb.tooltiptext): Shown when the button is
# placed inside a Private Browsing window. %S is the value of loop-call-button3.label.
loop-call-button3-pb.tooltiptext = %S is not available in Private Browsing
social-share-button.label = Share This Page
social-share-button.tooltiptext = Share this page

View File

@ -0,0 +1,9 @@
# 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/.
# Panel Strings
## LOCALIZATION NOTE(clientShortname2): This should not be localized and
## should remain "Firefox Hello" for all locales.
clientShortname2=Firefox Hello

View File

@ -27,6 +27,7 @@
locale/browser/browser.properties (%chrome/browser/browser.properties)
locale/browser/customizableui/customizableWidgets.properties (%chrome/browser/customizableui/customizableWidgets.properties)
locale/browser/lightweightThemes.properties (%chrome/browser/lightweightThemes.properties)
locale/browser/loop/loop.properties (%chrome/browser/loop/loop.properties)
locale/browser/newTab.dtd (%chrome/browser/newTab.dtd)
locale/browser/newTab.properties (%chrome/browser/newTab.properties)
locale/browser/pageInfo.dtd (%chrome/browser/pageInfo.dtd)

View File

@ -81,6 +81,11 @@ var PanelFrameInternal = {
attrs["message"] = "true";
attrs["messagemanagergroup"] = aType;
}
if (aType == "loop") {
attrs.message = true;
attrs.messagemanagergroup = "social";
attrs.autocompletepopup = "PopupAutoComplete";
}
for (let [k, v] of Iterator(attrs)) {
frame.setAttribute(k, v);
}

View File

@ -270,8 +270,15 @@ function getHost(uri, href) {
} catch (ex) {}
if (!host) {
if (uri && uri.scheme.toLowerCase() == "about") {
// For about URIs, just use the full spec, without any #hash parts.
host = uri.specIgnoringRef;
// Special case-ing Loop/ Hello gUM requests.
if (uri.specIgnoringRef == "about:loopconversation") {
const kBundleURI = "chrome://browser/locale/loop/loop.properties";
let bundle = Services.strings.createBundle(kBundleURI);
host = bundle.GetStringFromName("clientShortname2");
} else {
// For other about URIs, just use the full spec, without any #hash parts.
host = uri.specIgnoringRef;
}
} else {
// This is unfortunate, but we should display *something*...
const kBundleURI = "chrome://browser/locale/browser.properties";

View File

@ -923,4 +923,4 @@ pref("dom.audiochannel.mediaControl", true);
// Space separated list of URLS that are allowed to send objects (instead of
// only strings) through webchannels. This list is duplicated in browser/app/profile/firefox.js
pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");
pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://hello.firefox.com https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");

View File

@ -287,6 +287,13 @@ user_pref("dom.apps.customization.enabled", true);
user_pref("browser.newtabpage.directory.source", 'data:application/json,{"testing":1}');
user_pref("browser.newtabpage.directory.ping", "");
// Enable Loop
user_pref("loop.debug.loglevel", "All");
user_pref("loop.enabled", true);
user_pref("loop.throttled", false);
user_pref("loop.server", "http://%(server)s/browser/browser/extensions/loop/chrome/test/mochitest/loop_fxa.sjs?");
user_pref("loop.CSP","default-src 'self' about: file: chrome: data: wss://* http://* https://*");
// Ensure UITour won't hit the network
user_pref("browser.uitour.pinnedTabUrl", "http://%(server)s/uitour-dummy/pinnedTab");
user_pref("browser.uitour.url", "http://%(server)s/uitour-dummy/tour");