Merge fx-team to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-09-29 15:06:48 -04:00
commit d97f0896d0
28 changed files with 680 additions and 208 deletions

View File

@ -515,6 +515,9 @@ var gPopupBlockerObserver = {
var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION;
pm.add(gBrowser.currentURI, "popup", perm);
if (!shouldBlock)
this.showAllBlockedPopups(gBrowser.selectedBrowser);
gBrowser.getNotificationBox().removeCurrentNotification();
},
@ -569,6 +572,7 @@ var gPopupBlockerObserver = {
// xxxdz this should make the option say "Show file picker" and do it (Bug 590306)
if (!blockedPopup.popupWindowURI)
continue;
var popupURIspec = blockedPopup.popupWindowURI.spec;
// Sometimes the popup URI that we get back from the blockedPopup
@ -591,9 +595,6 @@ var gPopupBlockerObserver = {
var label = gNavigatorBundle.getFormattedString("popupShowPopupPrefix",
[popupURIspec]);
menuitem.setAttribute("label", label);
menuitem.setAttribute("popupWindowURI", popupURIspec);
menuitem.setAttribute("popupWindowFeatures", blockedPopup.popupWindowFeatures);
menuitem.setAttribute("popupWindowName", blockedPopup.popupWindowName);
menuitem.setAttribute("oncommand", "gPopupBlockerObserver.showBlockedPopup(event);");
menuitem.setAttribute("popupReportIndex", i);
menuitem.popupReportBrowser = browser;
@ -640,6 +641,18 @@ var gPopupBlockerObserver = {
browser.unblockPopup(popupReportIndex);
},
showAllBlockedPopups: function (aBrowser)
{
let popups = aBrowser.blockedPopups;
if (!popups)
return;
for (let i = 0; i < popups.length; i++) {
if (popups[i].popupWindowURI)
aBrowser.unblockPopup(i);
}
},
editPopupSettings: function ()
{
var host = "";

View File

@ -331,13 +331,23 @@ input[type=button] {
#newtab-search-logo {
display: -moz-box;
width: 77px; /* 65 image width + 6 left "padding" + 6 right "padding" */
width: 38px;
height: 38px; /* 26 image height + 6 top "padding" + 6 bottom "padding" */
border: 1px solid transparent;
-moz-margin-end: 8px;
background-repeat: no-repeat;
background-position: center;
background-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon);
background-size: 26px 26px;
}
#newtab-search-logo[type="logo"] {
background-size: 65px 26px;
width: 77px; /* 65 image width + 6 left "padding" + 6 right "padding" */
}
#newtab-search-logo[type="favicon"] {
background-size: 16px 16px;
}
#newtab-search-logo[hidden] {

View File

@ -160,6 +160,14 @@ let gSearch = {
}
},
// Converts favicon array buffer into data URI of the right size and dpi.
_getFaviconURIFromBuffer: function (buffer) {
let blob = new Blob([buffer]);
let dpiSize = Math.round(16 * window.devicePixelRatio);
let sizeStr = dpiSize + "," + dpiSize;
return URL.createObjectURL(blob) + "#-moz-resolution=" + sizeStr;
},
_makePanelEngine: function (panel, engine) {
let box = document.createElementNS(XUL_NAMESPACE, "hbox");
box.className = "newtab-search-panel-engine";
@ -173,10 +181,7 @@ let gSearch = {
let image = document.createElementNS(XUL_NAMESPACE, "image");
if (engine.iconBuffer) {
let blob = new Blob([engine.iconBuffer]);
let size = Math.round(16 * window.devicePixelRatio);
let sizeStr = size + "," + size;
let uri = URL.createObjectURL(blob) + "#-moz-resolution=" + sizeStr;
let uri = this._getFaviconURIFromBuffer(engine.iconBuffer);
image.setAttribute("src", uri);
}
box.appendChild(image);
@ -191,17 +196,27 @@ let gSearch = {
_setCurrentEngine: function (engine) {
this.currentEngineName = engine.name;
// Set the logo.
let logoBuf = window.devicePixelRatio == 2 ? engine.logo2xBuffer :
let type = "";
let uri;
let logoBuf = window.devicePixelRatio >= 2 ?
engine.logo2xBuffer || engine.logoBuffer :
engine.logoBuffer || engine.logo2xBuffer;
if (logoBuf) {
this._nodes.logo.hidden = false;
let uri = URL.createObjectURL(new Blob([logoBuf]));
uri = URL.createObjectURL(new Blob([logoBuf]));
type = "logo";
}
else if (engine.iconBuffer) {
uri = this._getFaviconURIFromBuffer(engine.iconBuffer);
type = "favicon";
}
this._nodes.logo.setAttribute("type", type);
if (uri) {
this._nodes.logo.style.backgroundImage = "url(" + uri + ")";
this._nodes.text.placeholder = "";
}
else {
this._nodes.logo.hidden = true;
this._nodes.logo.style.backgroundImage = "";
this._nodes.text.placeholder = engine.name;
}

View File

@ -65,6 +65,7 @@ support-files =
offlineQuotaNotification.html
page_style_sample.html
parsingTestHelpers.jsm
popup_blocker.html
print_postdata.sjs
redirect_bug623155.sjs
searchSuggestionEngine.sjs
@ -109,6 +110,8 @@ skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliabil
skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux
[browser_addKeywordSearch.js]
skip-if = e10s
[browser_search_favicon.js]
skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux
[browser_alltabslistener.js]
skip-if = os == "linux" || e10s # Linux: Intermittent failures, bug 951680; e10s: Bug ?????? - notifications don't work correctly.
[browser_autocomplete_a11y_label.js]
@ -369,6 +372,7 @@ skip-if = asan # Disabled because it takes a long time (see test for more inform
skip-if = e10s # Bug ?????? - test directly manipulates content (creates and fetches elements directly from content document)
[browser_popupUI.js]
skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (tries to get a popup element directly from content)
[browser_popup_blocker.js]
[browser_printpreview.js]
skip-if = buildapp == 'mulet' || e10s # Bug ?????? - timeout after logging "Error: Channel closing: too late to send/recv, messages will be lost"
[browser_private_browsing_window.js]

View File

@ -0,0 +1,64 @@
/* 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 baseURL = "http://example.com/browser/browser/base/content/test/general/";
function test() {
waitForExplicitFinish();
Task.spawn(function* () {
// Enable the popup blocker.
yield pushPrefs(["dom.disable_open_during_load", true]);
// Open the test page.
let tab = gBrowser.loadOneTab(baseURL + "popup_blocker.html", { inBackground: false });
yield promiseTabLoaded(tab);
// Wait for the popup-blocked notification.
let notification;
yield promiseWaitForCondition(() =>
notification = gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked"));
// Show the menu.
let popupShown = promiseWaitForEvent(window, "popupshown");
notification.querySelector("button").doCommand();
let popup_event = yield popupShown;
let menu = popup_event.target;
is(menu.id, "blockedPopupOptions", "Blocked popup menu shown");
// Check the menu contents.
let sep = menu.querySelector("menuseparator");
let popupCount = 0;
for (let i = sep.nextElementSibling; i; i = i.nextElementSibling) {
popupCount++;
}
is(popupCount, 2, "Two popups were blocked");
// Pressing "allow" should open all blocked popups.
let popupTabs = [];
function onTabOpen(event) {
popupTabs.push(event.target);
}
gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen);
// Press the button.
let allow = menu.querySelector("[observes='blockedPopupAllowSite']");
allow.doCommand();
yield promiseWaitForCondition(() =>
popupTabs.length == 2 &&
popupTabs.every(tab => tab.linkedBrowser.currentURI.spec != "about:blank"));
gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen);
is(popupTabs[0].linkedBrowser.currentURI.spec, "data:text/plain;charset=utf-8,a", "Popup a");
is(popupTabs[1].linkedBrowser.currentURI.spec, "data:text/plain;charset=utf-8,b", "Popup b");
// Clean up.
gBrowser.removeTab(tab);
for (let popup of popupTabs) {
gBrowser.removeTab(popup);
}
clearAllPermissionsByPrefix("popup");
finish();
});
}

View File

@ -0,0 +1,121 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
let gOriginalEngine;
let gEngine;
let gUnifiedCompletePref = "browser.urlbar.unifiedcomplete";
/**
* Asynchronously adds visits to a page.
*
* @param aPlaceInfo
* Can be an nsIURI, in such a case a single LINK visit will be added.
* Otherwise can be an object describing the visit to add, or an array
* of these objects:
* { uri: nsIURI of the page,
* transition: one of the TRANSITION_* from nsINavHistoryService,
* [optional] title: title of the page,
* [optional] visitDate: visit date in microseconds from the epoch
* [optional] referrer: nsIURI of the referrer for this visit
* }
*
* @return {Promise}
* @resolves When all visits have been added successfully.
* @rejects JavaScript exception.
*/
function promiseAddVisits(aPlaceInfo) {
return new Promise((resolve, reject) => {
let places = [];
if (aPlaceInfo instanceof Ci.nsIURI) {
places.push({ uri: aPlaceInfo });
}
else if (Array.isArray(aPlaceInfo)) {
places = places.concat(aPlaceInfo);
} else {
places.push(aPlaceInfo)
}
// Create mozIVisitInfo for each entry.
let now = Date.now();
for (let i = 0, len = places.length; i < len; ++i) {
if (!places[i].title) {
places[i].title = "test visit for " + places[i].uri.spec;
}
places[i].visits = [{
transitionType: places[i].transition === undefined ? Ci.nsINavHistoryService.TRANSITION_LINK
: places[i].transition,
visitDate: places[i].visitDate || (now++) * 1000,
referrerURI: places[i].referrer
}];
}
PlacesUtils.asyncHistory.updatePlaces(
places,
{
handleError: function AAV_handleError(aResultCode, aPlaceInfo) {
let ex = new Components.Exception("Unexpected error in adding visits.",
aResultCode);
reject(ex);
},
handleResult: function () {},
handleCompletion: function UP_handleCompletion() {
resolve();
}
}
);
});
}
function* promiseAutocompleteResultPopup(inputText) {
gURLBar.focus();
gURLBar.value = inputText.slice(0, -1);
EventUtils.synthesizeKey(inputText.slice(-1) , {});
yield promiseSearchComplete();
return gURLBar.popup.richlistbox.children;
}
registerCleanupFunction(() => {
Services.prefs.clearUserPref(gUnifiedCompletePref);
Services.search.currentEngine = gOriginalEngine;
Services.search.removeEngine(gEngine);
return promiseClearHistory();
});
add_task(function*() {
Services.prefs.setBoolPref(gUnifiedCompletePref, true);
});
add_task(function*() {
Services.search.addEngineWithDetails("SearchEngine", "", "", "",
"GET", "http://s.example.com/search");
gEngine = Services.search.getEngineByName("SearchEngine");
gEngine.addParam("q", "{searchTerms}", null);
gOriginalEngine = Services.search.currentEngine;
Services.search.currentEngine = gEngine;
let uri = NetUtil.newURI("http://s.example.com/search?q=foo&client=1");
yield promiseAddVisits({ uri: uri, title: "Foo - SearchEngine Search" });
// The first autocomplete result has the action searchengine, while
// the second result is the "search favicon" element.
let result = yield promiseAutocompleteResultPopup("foo");
result = result[1];
isnot(result, null, "Expect a search result");
is(result.getAttribute("type"), "search favicon", "Expect correct `type` attribute");
is_element_visible(result._title, "Title element should be visible");
is_element_visible(result._extraBox, "Extra box element should be visible");
is(result._extraBox.pack, "start", "Extra box element should start after the title");
let iconElem = result._extraBox.nextSibling;
is_element_visible(iconElem,
"The element containing the magnifying glass icon should be visible");
ok(iconElem.classList.contains("ac-result-type-keyword"),
"That icon should have the same class use for `keyword` results");
is_element_visible(result._url, "URL element should be visible");
is(result._url.textContent, "Search with SearchEngine");
});

View File

@ -150,15 +150,25 @@ function setTestPluginEnabledState(newEnabledState, pluginName) {
// after a test is done using the plugin doorhanger, we should just clear
// any permissions that may have crept in
function clearAllPluginPermissions() {
clearAllPermissionsByPrefix("plugin");
}
function clearAllPermissionsByPrefix(aPrefix) {
let perms = Services.perms.enumerator;
while (perms.hasMoreElements()) {
let perm = perms.getNext();
if (perm.type.startsWith('plugin')) {
if (perm.type.startsWith(aPrefix)) {
Services.perms.remove(perm.host, perm.type);
}
}
}
function pushPrefs(...aPrefs) {
let deferred = Promise.defer();
SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve);
return deferred.promise;
}
function updateBlocklist(aCallback) {
var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
.getService(Ci.nsITimerCallback);

View File

@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Page creating two popups</title>
</head>
<body>
<script type="text/javascript">
window.open("data:text/plain;charset=utf-8,a", "a");
window.open("data:text/plain;charset=utf-8,b", "b");
</script>
</body>
</html>

View File

@ -35,6 +35,7 @@ support-files =
[browser_newtab_search.js]
support-files =
searchEngineNoLogo.xml
searchEngineFavicon.xml
searchEngine1xLogo.xml
searchEngine2xLogo.xml
searchEngine1x2xLogo.xml

View File

@ -4,11 +4,45 @@
// See browser/components/search/test/browser_*_behavior.js for tests of actual
// searches.
const ENGINE_NO_LOGO = "searchEngineNoLogo.xml";
const ENGINE_1X_LOGO = "searchEngine1xLogo.xml";
const ENGINE_2X_LOGO = "searchEngine2xLogo.xml";
const ENGINE_1X_2X_LOGO = "searchEngine1x2xLogo.xml";
const ENGINE_SUGGESTIONS = "searchSuggestionEngine.xml";
Cu.import("resource://gre/modules/Task.jsm");
const ENGINE_NO_LOGO = {
name: "searchEngineNoLogo.xml",
numLogos: 0,
};
const ENGINE_FAVICON = {
name: "searchEngineFavicon.xml",
logoPrefix1x: "data:image/png;base64,AAABAAIAICAAAAEAIACoEAAAJgAAABAQAAABACAAaAQAAM4QAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAATCwAAEwsA",
numLogos: 1,
};
ENGINE_FAVICON.logoPrefix2x = ENGINE_FAVICON.logoPrefix1x;
const ENGINE_1X_LOGO = {
name: "searchEngine1xLogo.xml",
logoPrefix1x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAIAAABn3KYmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADEw",
numLogos: 1,
};
ENGINE_1X_LOGO.logoPrefix2x = ENGINE_1X_LOGO.logoPrefix1x;
const ENGINE_2X_LOGO = {
name: "searchEngine2xLogo.xml",
logoPrefix2x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAIAAADJ8nfCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADMU",
numLogos: 1,
};
ENGINE_2X_LOGO.logoPrefix1x = ENGINE_2X_LOGO.logoPrefix2x;
const ENGINE_1X_2X_LOGO = {
name: "searchEngine1x2xLogo.xml",
logoPrefix1x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAIAAABn3KYmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADIG",
logoPrefix2x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAIAAADJ8nfCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADMo",
numLogos: 2,
};
const ENGINE_SUGGESTIONS = {
name: "searchSuggestionEngine.xml",
numLogos: 0,
};
const SERVICE_EVENT_NAME = "ContentSearchService";
@ -28,9 +62,14 @@ var gExpectedSearchEventQueue = [];
var gNewEngines = [];
function runTests() {
runTaskifiedTests().then(TestRunner.next, TestRunner.next);
yield;
}
let runTaskifiedTests = Task.async(function* () {
let oldCurrentEngine = Services.search.currentEngine;
yield addNewTabPageTab();
yield addNewTabPageTabPromise();
// The tab is removed at the end of the test, so there's no need to remove
// this listener at the end of the test.
@ -45,66 +84,40 @@ function runTests() {
panel.setAttribute("animate", "false");
// Add the engine without any logos and switch to it.
let noLogoEngine = null;
yield promiseNewSearchEngine(ENGINE_NO_LOGO, 0).then(engine => {
noLogoEngine = engine;
TestRunner.next();
});
ok(!noLogoEngine.getIconURLBySize(...LOGO_1X_DPI_SIZE),
"Sanity check: engine should not have 1x logo");
ok(!noLogoEngine.getIconURLBySize(...LOGO_2X_DPI_SIZE),
"Sanity check: engine should not have 2x logo");
let noLogoEngine = yield promiseNewSearchEngine(ENGINE_NO_LOGO);
Services.search.currentEngine = noLogoEngine;
yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
yield checkCurrentEngine(ENGINE_NO_LOGO, false, false);
yield promiseSearchEvents(["CurrentEngine"]);
yield checkCurrentEngine(ENGINE_NO_LOGO);
// Add the engine with favicon and switch to it.
let faviconEngine = yield promiseNewSearchEngine(ENGINE_FAVICON);
Services.search.currentEngine = faviconEngine;
yield promiseSearchEvents(["CurrentEngine"]);
yield checkCurrentEngine(ENGINE_FAVICON);
// Add the engine with a 1x-DPI logo and switch to it.
let logo1xEngine = null;
yield promiseNewSearchEngine(ENGINE_1X_LOGO, 1).then(engine => {
logo1xEngine = engine;
TestRunner.next();
});
ok(!!logo1xEngine.getIconURLBySize(...LOGO_1X_DPI_SIZE),
"Sanity check: engine should have 1x logo");
ok(!logo1xEngine.getIconURLBySize(...LOGO_2X_DPI_SIZE),
"Sanity check: engine should not have 2x logo");
let logo1xEngine = yield promiseNewSearchEngine(ENGINE_1X_LOGO);
Services.search.currentEngine = logo1xEngine;
yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
yield checkCurrentEngine(ENGINE_1X_LOGO, true, false);
yield promiseSearchEvents(["CurrentEngine"]);
yield checkCurrentEngine(ENGINE_1X_LOGO);
// Add the engine with a 2x-DPI logo and switch to it.
let logo2xEngine = null;
yield promiseNewSearchEngine(ENGINE_2X_LOGO, 1).then(engine => {
logo2xEngine = engine;
TestRunner.next();
});
ok(!logo2xEngine.getIconURLBySize(...LOGO_1X_DPI_SIZE),
"Sanity check: engine should not have 1x logo");
ok(!!logo2xEngine.getIconURLBySize(...LOGO_2X_DPI_SIZE),
"Sanity check: engine should have 2x logo");
let logo2xEngine = yield promiseNewSearchEngine(ENGINE_2X_LOGO);
Services.search.currentEngine = logo2xEngine;
yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
yield checkCurrentEngine(ENGINE_2X_LOGO, false, true);
yield promiseSearchEvents(["CurrentEngine"]);
yield checkCurrentEngine(ENGINE_2X_LOGO);
// Add the engine with 1x- and 2x-DPI logos and switch to it.
let logo1x2xEngine = null;
yield promiseNewSearchEngine(ENGINE_1X_2X_LOGO, 2).then(engine => {
logo1x2xEngine = engine;
TestRunner.next();
});
ok(!!logo1x2xEngine.getIconURLBySize(...LOGO_1X_DPI_SIZE),
"Sanity check: engine should have 1x logo");
ok(!!logo1x2xEngine.getIconURLBySize(...LOGO_2X_DPI_SIZE),
"Sanity check: engine should have 2x logo");
let logo1x2xEngine = yield promiseNewSearchEngine(ENGINE_1X_2X_LOGO);
Services.search.currentEngine = logo1x2xEngine;
yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
yield checkCurrentEngine(ENGINE_1X_2X_LOGO, true, true);
yield promiseSearchEvents(["CurrentEngine"]);
yield checkCurrentEngine(ENGINE_1X_2X_LOGO);
// Click the logo to open the search panel.
yield Promise.all([
promisePanelShown(panel),
promiseClick(logoImg()),
]).then(TestRunner.next);
]);
// In the search panel, click the no-logo engine. It should become the
// current engine.
@ -119,20 +132,20 @@ function runTests() {
yield Promise.all([
promiseSearchEvents(["CurrentEngine"]),
promiseClick(noLogoBox),
]).then(TestRunner.next);
]);
yield checkCurrentEngine(ENGINE_NO_LOGO, false, false);
yield checkCurrentEngine(ENGINE_NO_LOGO);
// Switch back to the 1x-and-2x logo engine.
Services.search.currentEngine = logo1x2xEngine;
yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
yield checkCurrentEngine(ENGINE_1X_2X_LOGO, true, true);
yield promiseSearchEvents(["CurrentEngine"]);
yield checkCurrentEngine(ENGINE_1X_2X_LOGO);
// Open the panel again.
yield Promise.all([
promisePanelShown(panel),
promiseClick(logoImg()),
]).then(TestRunner.next);
]);
// In the search panel, click the Manage Engines box.
let manageBox = $("manage");
@ -140,17 +153,13 @@ function runTests() {
yield Promise.all([
promiseManagerOpen(),
promiseClick(manageBox),
]).then(TestRunner.next);
]);
// Add the engine that provides search suggestions and switch to it.
let suggestionEngine = null;
yield promiseNewSearchEngine(ENGINE_SUGGESTIONS, 0).then(engine => {
suggestionEngine = engine;
TestRunner.next();
});
let suggestionEngine = yield promiseNewSearchEngine(ENGINE_SUGGESTIONS);
Services.search.currentEngine = suggestionEngine;
yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
yield checkCurrentEngine(ENGINE_SUGGESTIONS, false, false);
yield promiseSearchEvents(["CurrentEngine"]);
yield checkCurrentEngine(ENGINE_SUGGESTIONS);
// Avoid intermittent failures.
gSearch()._suggestionController.remoteTimeout = 5000;
@ -165,21 +174,22 @@ function runTests() {
// Wait for the search suggestions to become visible and for the Suggestions
// message.
let suggestionsUnhiddenDefer = Promise.defer();
let table = getContentDocument().getElementById("searchSuggestionTable");
info("Waiting for suggestions table to open");
let observer = new MutationObserver(() => {
if (input.getAttribute("aria-expanded") == "true") {
observer.disconnect();
ok(!table.hidden, "Search suggestion table unhidden");
TestRunner.next();
suggestionsUnhiddenDefer.resolve();
}
});
observer.observe(input, {
attributes: true,
attributeFilter: ["aria-expanded"],
});
yield undefined;
yield suggestionsPromise.then(TestRunner.next);
yield suggestionsUnhiddenDefer.promise;
yield suggestionsPromise;
// Empty the search input, causing the suggestions to be hidden.
EventUtils.synthesizeKey("a", { accelKey: true });
@ -190,12 +200,12 @@ function runTests() {
CustomizableUI.removeWidgetFromArea("search-container");
// Focus a different element than the search input from the page.
let btn = getContentDocument().getElementById("newtab-customize-button");
yield promiseClick(btn).then(TestRunner.next);
yield promiseClick(btn);
isnot(input, getContentDocument().activeElement, "Search input should not be focused");
// Test that Ctrl/Cmd + K will focus the input field from the page.
EventUtils.synthesizeKey("k", { accelKey: true });
yield promiseSearchEvents(["FocusInput"]).then(TestRunner.next);
yield promiseSearchEvents(["FocusInput"]);
is(input, getContentDocument().activeElement, "Search input should be focused");
// Reset changes made to toolbar
CustomizableUI.reset();
@ -207,13 +217,13 @@ function runTests() {
// Test that Ctrl/Cmd + K will focus the search bar from a new about:home page if
// the newtab is disabled from `NewTabUtils.allPages.enabled`.
yield addNewTabPageTab();
yield addNewTabPageTabPromise();
// Remove the search bar from toolbar
CustomizableUI.removeWidgetFromArea("search-container");
NewTabUtils.allPages.enabled = false;
EventUtils.synthesizeKey("k", { accelKey: true });
let waitEvent = "AboutHomeLoadSnippetsCompleted";
yield promiseTabLoadEvent(gWindow.gBrowser.selectedTab, "about:home", waitEvent).then(TestRunner.next);
yield promiseTabLoadEvent(gWindow.gBrowser.selectedTab, "about:home", waitEvent);
is(getContentDocument().documentURI.toLowerCase(), "about:home", "New tab's uri should be about:home");
let searchInput = getContentDocument().getElementById("searchText");
@ -225,38 +235,29 @@ function runTests() {
// Done. Revert the current engine and remove the new engines.
Services.search.currentEngine = oldCurrentEngine;
yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
yield promiseSearchEvents(["CurrentEngine"]);
let events = [];
for (let engine of gNewEngines) {
Services.search.removeEngine(engine);
events.push("CurrentState");
}
yield promiseSearchEvents(events).then(TestRunner.next);
}
yield promiseSearchEvents(events);
});
function searchEventListener(event) {
info("Got search event " + event.detail.type);
let passed = false;
let nonempty = gExpectedSearchEventQueue.length > 0;
ok(nonempty, "Expected search event queue should be nonempty");
if (nonempty) {
let { type, deferred } = gExpectedSearchEventQueue.shift();
is(event.detail.type, type, "Got expected search event " + type);
if (event.detail.type == type) {
passed = true;
// Let gSearch respond to the event before continuing.
executeSoon(() => deferred.resolve());
deferred.resolve();
} else {
deferred.reject();
}
}
if (!passed) {
info("Didn't get expected event, stopping the test");
getContentWindow().removeEventListener(SERVICE_EVENT_NAME,
searchEventListener);
// Set next() to a no-op so the test really does stop.
TestRunner.next = function () {};
TestRunner.finish();
}
}
function $(idSuffix) {
@ -270,7 +271,7 @@ function promiseSearchEvents(events) {
return Promise.all(events.map(e => e.deferred.promise));
}
function promiseNewSearchEngine(basename, numLogos) {
function promiseNewSearchEngine({name: basename, numLogos}) {
info("Waiting for engine to be added: " + basename);
// Wait for the search events triggered by adding the new engine.
@ -297,19 +298,40 @@ function promiseNewSearchEngine(basename, numLogos) {
},
});
// Make a new promise that wraps the previous promises. The only point of
// this is to pass the new engine to the yielder via deferred.resolve(),
// which is a little nicer than passing an array whose first element is the
// new engine.
let deferred = Promise.defer();
Promise.all([addDeferred.promise, eventPromise]).then(values => {
let newEngine = values[0];
deferred.resolve(newEngine);
}, () => deferred.reject());
return deferred.promise;
return Promise.all([addDeferred.promise, eventPromise]).then(([newEngine, _]) => {
return newEngine;
});
}
function checkCurrentEngine(basename, has1xLogo, has2xLogo) {
function objectURLToBlob(url) {
return new Promise(function (resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open("get", url, true);
xhr.responseType = "blob";
xhr.overrideMimeType("image/png");
xhr.onload = function(e) {
if (this.status == 200) {
return resolve(this.response);
}
reject("Failed to get logo, xhr returned status: " + this.status);
};
xhr.onerror = reject;
xhr.send();
});
}
function blobToBase64(blob) {
return new Promise(function (resolve, reject) {
var reader = new FileReader();
reader.onload = function() {
resolve(reader.result);
}
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
let checkCurrentEngine = Task.async(function* ({name: basename, logoPrefix1x, logoPrefix2x}) {
let engine = Services.search.currentEngine;
ok(engine.name.contains(basename),
"Sanity check: current engine: engine.name=" + engine.name +
@ -319,41 +341,23 @@ function checkCurrentEngine(basename, has1xLogo, has2xLogo) {
is(gSearch().currentEngineName, engine.name,
"currentEngineName: " + engine.name);
// search bar logo
let logoURI = null;
if (window.devicePixelRatio == 2) {
if (has2xLogo) {
logoURI = engine.getIconURLBySize(...LOGO_2X_DPI_SIZE);
ok(!!logoURI, "Sanity check: engine should have 2x logo");
}
}
else {
if (has1xLogo) {
logoURI = engine.getIconURLBySize(...LOGO_1X_DPI_SIZE);
ok(!!logoURI, "Sanity check: engine should have 1x logo");
}
else if (has2xLogo) {
logoURI = engine.getIconURLBySize(...LOGO_2X_DPI_SIZE);
ok(!!logoURI, "Sanity check: engine should have 2x logo");
}
}
let expectedLogoPrefix = window.devicePixelRatio >= 2 ? logoPrefix2x : logoPrefix1x;
// Check that the right logo is set.
let logo = logoImg();
is(logo.hidden, !logoURI,
"Logo should be visible iff engine has a logo: " + engine.name);
if (logoURI) {
// The URLs of blobs created with the same ArrayBuffer are different, so
// just check that the URI is a blob URI.
ok(/^url\("blob:/.test(logo.style.backgroundImage), "Logo URI"); //"
}
if (expectedLogoPrefix) {
let objectURL = logo.style.backgroundImage.match(/^url\("([^"]*)"\)$/)[1];
ok(objectURL, "ObjectURL should be there.");
if (logo.hidden) {
executeSoon(TestRunner.next);
return;
}
let blob = yield objectURLToBlob(objectURL);
let base64 = yield blobToBase64(blob);
ok(base64.startsWith(expectedLogoPrefix), "Checking image prefix.");
let panel = searchPanel();
panel.openPopup(logo);
yield promisePanelShown(panel);
// "selected" attributes of engines in the panel
let panel = searchPanel();
promisePanelShown(panel).then(() => {
panel.hidePopup();
for (let engineBox of panel.childNodes) {
let engineName = engineBox.getAttribute("engine");
@ -368,10 +372,11 @@ function checkCurrentEngine(basename, has1xLogo, has2xLogo) {
"non-selected engine: " + engineName);
}
}
TestRunner.next();
});
panel.openPopup(logo);
}
}
else {
is(logo.style.backgroundImage, "", "backgroundImage should be empty");
}
});
function promisePanelShown(panel) {
let deferred = Promise.defer();
@ -379,7 +384,7 @@ function promisePanelShown(panel) {
panel.addEventListener("popupshown", function onEvent() {
panel.removeEventListener("popupshown", onEvent);
is(panel.state, "open", "Panel state");
executeSoon(() => deferred.resolve());
deferred.resolve();
});
return deferred.promise;
}
@ -410,10 +415,8 @@ function promiseManagerOpen() {
is(subj.opener, gWindow,
"Search engine manager opener should be the chrome browser " +
"window containing the newtab page");
executeSoon(() => {
subj.close();
deferred.resolve();
});
subj.close();
deferred.resolve();
}
});
}

View File

@ -327,6 +327,12 @@ function restore() {
* Creates a new tab containing 'about:newtab'.
*/
function addNewTabPageTab() {
addNewTabPageTabPromise().then(TestRunner.next);
}
function addNewTabPageTabPromise() {
let deferred = Promise.defer();
let tab = gWindow.gBrowser.selectedTab = gWindow.gBrowser.addTab("about:newtab");
let browser = tab.linkedBrowser;
@ -334,20 +340,17 @@ function addNewTabPageTab() {
if (NewTabUtils.allPages.enabled) {
// Continue when the link cache has been populated.
NewTabUtils.links.populateCache(function () {
whenSearchInitDone();
deferred.resolve(whenSearchInitDone());
});
} else {
// It's important that we call next() asynchronously.
// 'yield addNewTabPageTab()' would fail if next() is called
// synchronously because the iterator is already executing.
executeSoon(TestRunner.next);
deferred.resolve();
}
}
// The new tab page might have been preloaded in the background.
if (browser.contentDocument.readyState == "complete") {
whenNewTabLoaded();
return;
return deferred.promise;
}
// Wait for the new tab page to be loaded.
@ -355,6 +358,8 @@ function addNewTabPageTab() {
browser.removeEventListener("load", onLoad, true);
whenNewTabLoaded();
}, true);
return deferred.promise;
}
/**
@ -637,15 +642,16 @@ function whenPagesUpdated(aCallback, aOnlyIfHidden=false) {
* Waits for the response to the page's initial search state request.
*/
function whenSearchInitDone() {
let deferred = Promise.defer();
if (getContentWindow().gSearch._initialStateReceived) {
executeSoon(TestRunner.next);
return;
return Promise.resolve();
}
let eventName = "ContentSearchService";
getContentWindow().addEventListener(eventName, function onEvent(event) {
if (event.detail.type == "State") {
getContentWindow().removeEventListener(eventName, onEvent);
TestRunner.next();
deferred.resolve();
}
});
return deferred.promise;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -234,6 +234,12 @@ let SessionFileInternal = {
}
}
}
// All files are corrupted if files found but none could deliver a result.
let allCorrupt = !noFilesFound && !result;
Telemetry.getHistogramById("FX_SESSION_RESTORE_ALL_FILES_CORRUPT").
add(allCorrupt);
if (!result) {
// If everything fails, start with an empty session.
result = {

View File

@ -2202,12 +2202,9 @@ let SessionStoreInternal = {
if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
this.onLoad(aWindow);
let root;
try {
var root = typeof aState == "string" ? JSON.parse(aState) : aState;
if (!root.windows[0]) {
this._sendRestoreCompletedNotifications();
return; // nothing to restore
}
root = (typeof aState == "string") ? JSON.parse(aState) : aState;
}
catch (ex) { // invalid state object - don't restore anything
debug(ex);
@ -2215,15 +2212,23 @@ let SessionStoreInternal = {
return;
}
// Restore closed windows if any.
if (root._closedWindows) {
this._closedWindows = root._closedWindows;
}
// We're done here if there are no windows.
if (!root.windows || !root.windows.length) {
this._sendRestoreCompletedNotifications();
return;
}
TelemetryStopwatch.start("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
// We're not returning from this before we end up calling restoreTabs
// for this window, so make sure we send the SSWindowStateBusy event.
this._setWindowStateBusy(aWindow);
if (root._closedWindows)
this._closedWindows = root._closedWindows;
var winData;
if (!root.selectedWindow || root.selectedWindow > root.windows.length) {
root.selectedWindow = 0;

View File

@ -0,0 +1,114 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* The primary purpose of this test is to ensure that
* the sessionstore component records information about
* corrupted backup files into a histogram.
*/
"use strict";
Cu.import("resource://gre/modules/osfile.jsm", this);
const Telemetry = Services.telemetry;
const Path = OS.Path;
const HistogramId = "FX_SESSION_RESTORE_ALL_FILES_CORRUPT";
// Prepare the session file.
let profd = do_get_profile();
Cu.import("resource:///modules/sessionstore/SessionFile.jsm", this);
/**
* A utility function for resetting the histogram and the contents
* of the backup directory.
*/
function reset_session(backups = {}) {
// Reset the histogram.
Telemetry.getHistogramById(HistogramId).clear();
// Reset the contents of the backups directory
OS.File.makeDir(SessionFile.Paths.backups);
for (let key of SessionFile.Paths.loadOrder) {
if (backups.hasOwnProperty(key)) {
OS.File.copy(backups[key], SessionFile.Paths[key]);
} else {
OS.File.remove(SessionFile.Paths[key]);
}
}
}
/**
* In order to use FX_SESSION_RESTORE_ALL_FILES_CORRUPT histogram
* it has to be registered in "toolkit/components/telemetry/Histograms.json".
* This test ensures that the histogram is registered and empty.
*/
add_task(function* test_ensure_histogram_exists_and_empty() {
let s = Telemetry.getHistogramById(HistogramId).snapshot();
Assert.equal(s.sum, 0, "Initially, the sum of probes is 0");
});
/**
* Makes sure that the histogram is negatively updated when no
* backup files are present.
*/
add_task(function* test_no_files_exist() {
// No session files are available to SessionFile.
reset_session();
yield SessionFile.read();
// Checking if the histogram is updated negatively
let h = Telemetry.getHistogramById(HistogramId);
let s = h.snapshot();
Assert.equal(s.counts[0], 1, "One probe for the 'false' bucket.");
Assert.equal(s.counts[1], 0, "No probes in the 'true' bucket.");
});
/**
* Makes sure that the histogram is negatively updated when at least one
* backup file is not corrupted.
*/
add_task(function* test_one_file_valid() {
// Corrupting some backup files.
let invalidSession = "data/sessionstore_invalid.js";
let validSession = "data/sessionstore_valid.js";
reset_session({
clean : invalidSession,
cleanBackup: validSession,
recovery: invalidSession,
recoveryBackup: invalidSession
});
yield SessionFile.read();
// Checking if the histogram is updated negatively.
let h = Telemetry.getHistogramById(HistogramId);
let s = h.snapshot();
Assert.equal(s.counts[0], 1, "One probe for the 'false' bucket.");
Assert.equal(s.counts[1], 0, "No probes in the 'true' bucket.");
});
/**
* Makes sure that the histogram is positively updated when all
* backup files are corrupted.
*/
add_task(function* test_all_files_corrupt() {
// Corrupting all backup files.
let invalidSession = "data/sessionstore_invalid.js";
reset_session({
clean : invalidSession,
cleanBackup: invalidSession,
recovery: invalidSession,
recoveryBackup: invalidSession
});
yield SessionFile.read();
// Checking if the histogram is positively updated.
let h = Telemetry.getHistogramById(HistogramId);
let s = h.snapshot();
Assert.equal(s.counts[1], 1, "One probe for the 'true' bucket.");
Assert.equal(s.counts[0], 0, "No probes in the 'false' bucket.");
});
function run_test() {
run_next_test();
}

View File

@ -11,3 +11,4 @@ support-files =
[test_startup_nosession_async.js]
[test_startup_session_async.js]
[test_startup_invalid_session.js]
[test_histogram_corrupt_files.js]

View File

@ -7,7 +7,7 @@ pref("devtools.webide.showProjectEditor", true);
pref("devtools.webide.templatesURL", "https://code.cdn.mozilla.net/templates/list.json");
pref("devtools.webide.autoinstallADBHelper", true);
pref("devtools.webide.restoreLastProject", true);
pref("devtools.webide.enableLocalRuntime", false);
pref("devtools.webide.enableLocalRuntime", true);
pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/#VERSION#/#OS#/fxos_#SLASHED_VERSION#_simulator-#OS#-latest.xpi");
pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozilla.org");

View File

@ -386,10 +386,12 @@ this.ContentSearch = {
_currentEngineObj: Task.async(function* () {
let engine = Services.search.currentEngine;
let favicon = engine.getIconURLBySize(16, 16);
let uri1x = engine.getIconURLBySize(65, 26);
let uri2x = engine.getIconURLBySize(130, 52);
let obj = {
name: engine.name,
iconBuffer: yield this._arrayBufferFromDataURI(favicon),
logoBuffer: yield this._arrayBufferFromDataURI(uri1x),
logo2xBuffer: yield this._arrayBufferFromDataURI(uri2x),
};

View File

@ -258,9 +258,43 @@ add_task(function* GetSuggestions_AddFormHistoryEntry_RemoveFormHistoryEntry() {
yield waitForTestMsg("CurrentState");
});
function buffersEqual(actualArrayBuffer, expectedArrayBuffer) {
let expectedView = new Int8Array(expectedArrayBuffer);
let actualView = new Int8Array(actualArrayBuffer);
for (let i = 0; i < expectedView.length; i++) {
if (actualView[i] != expectedView[i]) {
return false;
}
}
return true;
}
function arrayBufferEqual(actualArrayBuffer, expectedArrayBuffer) {
ok(actualArrayBuffer instanceof ArrayBuffer, "Actual value is ArrayBuffer.");
ok(expectedArrayBuffer instanceof ArrayBuffer, "Expected value is ArrayBuffer.");
Assert.equal(actualArrayBuffer.byteLength, expectedArrayBuffer.byteLength,
"Array buffers have the same length.");
ok(buffersEqual(actualArrayBuffer, expectedArrayBuffer), "Buffers are equal.");
}
function checkArrayBuffers(actual, expected) {
if (actual instanceof ArrayBuffer) {
arrayBufferEqual(actual, expected);
}
if (typeof actual == "object") {
for (let i in actual) {
checkArrayBuffers(actual[i], expected[i]);
}
}
}
function checkMsg(actualMsg, expectedMsgData) {
let actualMsgData = actualMsg.data;
SimpleTest.isDeeply(actualMsg.data, expectedMsgData, "Checking message");
// Engines contain ArrayBuffers which we have to compare byte by byte and
// not as Objects (like SimpleTest.isDeeply does).
checkArrayBuffers(actualMsgData, expectedMsgData);
}
function waitForMsg(name, type) {
@ -330,35 +364,33 @@ function addTab() {
return deferred.promise;
}
function currentStateObj() {
return Task.spawn(function* () {
let state = {
engines: [],
currentEngine: yield currentEngineObj(),
};
for (let engine of Services.search.getVisibleEngines()) {
let uri = engine.getIconURLBySize(16, 16);
state.engines.push({
name: engine.name,
iconBuffer: yield arrayBufferFromDataURI(uri),
});
}
return state;
}.bind(this));
}
function currentEngineObj() {
return Task.spawn(function* () {
let engine = Services.search.currentEngine;
let uri1x = engine.getIconURLBySize(65, 26);
let uri2x = engine.getIconURLBySize(130, 52);
return {
let currentStateObj = Task.async(function* () {
let state = {
engines: [],
currentEngine: yield currentEngineObj(),
};
for (let engine of Services.search.getVisibleEngines()) {
let uri = engine.getIconURLBySize(16, 16);
state.engines.push({
name: engine.name,
logoBuffer: yield arrayBufferFromDataURI(uri1x),
logo2xBuffer: yield arrayBufferFromDataURI(uri2x),
};
}.bind(this));
}
iconBuffer: yield arrayBufferFromDataURI(uri),
});
}
return state;
});
let currentEngineObj = Task.async(function* () {
let engine = Services.search.currentEngine;
let uri1x = engine.getIconURLBySize(65, 26);
let uri2x = engine.getIconURLBySize(130, 52);
let uriFavicon = engine.getIconURLBySize(16, 16);
return {
name: engine.name,
logoBuffer: yield arrayBufferFromDataURI(uri1x),
logo2xBuffer: yield arrayBufferFromDataURI(uri2x),
iconBuffer: yield arrayBufferFromDataURI(uriFavicon),
};
});
function arrayBufferFromDataURI(uri) {
if (!uri) {

View File

@ -3823,6 +3823,11 @@
"kind": "boolean",
"description": "Session restore: Whether the file read on startup contained parse-able JSON"
},
"FX_SESSION_RESTORE_ALL_FILES_CORRUPT": {
"expires_in_version": "default",
"kind": "boolean",
"description": "Session restore: Whether none of the backup files contained parse-able JSON"
},
"FX_SESSION_RESTORE_RESTORE_WINDOW_MS": {
"expires_in_version": "default",
"kind": "exponential",

View File

@ -1567,6 +1567,7 @@ extends="chrome://global/content/bindings/popup.xml#popup">
this._titleOverflowEllipsis.hidden = false;
let types = new Set(type.split(/\s+/));
let initialTypes = new Set(types);
// If the type includes an action, set up the item appropriately.
if (types.has("action")) {
@ -1655,7 +1656,8 @@ extends="chrome://global/content/bindings/popup.xml#popup">
type = "bookmark";
// keyword and favicon type results for search engines
// have an extra magnifying glass icon after them
} else if (type == "keyword" || type == "search favicon") {
} else if (type == "keyword" || (initialTypes.has("search") &&
initialTypes.has("favicon"))) {
// Configure the extra box for keyword display
this._extraBox.hidden = false;
this._extraBox.childNodes[0].hidden = true;
@ -1680,6 +1682,9 @@ extends="chrome://global/content/bindings/popup.xml#popup">
// Don't emphasize keyword searches in the title or url
this.setAttribute("text", "");
} else {
// Don't show any description for non keyword types.
this._setUpDescription(this._extra, "", true);
}
// If the result has the type favicon and a known search provider,
// customize it the same way as a keyword result.

View File

@ -1061,6 +1061,14 @@ let Front = Class({
this.actorID = null;
},
manage: function(front) {
if (!front.actorID) {
throw new Error("Can't manage front without an actor ID.\n" +
"Ensure server supports " + front.typeName + ".");
}
return Pool.prototype.manage.call(this, front);
},
/**
* @returns a promise that will resolve to the actorID this front
* represents.

View File

@ -488,9 +488,15 @@ PopupNotifications.prototype = {
* Hides the notification popup.
*/
_hidePanel: function PopupNotifications_hide() {
// We need to disable the closing animation when setting _ignoreDismissal
// to true, otherwise the popuphidden event will fire after we have set
// _ignoreDismissal back to false.
let transitionsEnabled = this.transitionsEnabled;
this.transitionsEnabled = false;
this._ignoreDismissal = true;
this.panel.hidePopup();
this._ignoreDismissal = false;
this.transitionsEnabled = transitionsEnabled;
},
/**

View File

@ -437,16 +437,19 @@ var gEventManager = {
contextMenu.setAttribute("addontype", addon.type);
var menuSep = document.getElementById("addonitem-menuseparator");
var countEnabledMenuCmds = 0;
var countMenuItemsBeforeSep = 0;
for (let child of contextMenu.children) {
if (child == menuSep) {
break;
}
if (child.nodeName == "menuitem" &&
gViewController.isCommandEnabled(child.command)) {
countEnabledMenuCmds++;
countMenuItemsBeforeSep++;
}
}
// with only one menu item, we hide the menu separator
menuSep.hidden = (countEnabledMenuCmds <= 1);
// Hide the separator if there are no visible menu items before it
menuSep.hidden = (countMenuItemsBeforeSep == 0);
}, false);
},

View File

@ -197,6 +197,21 @@ add_task(function* testInstalledDetails() {
el = doc.getElementById("detail-warning");
is_element_hidden(el, "Warning notification is hidden.");
el = doc.getElementsByTagName("setting")[0];
let contextMenu = doc.getElementById("addonitem-popup");
let deferred = Promise.defer();
let listener = () => {
contextMenu.removeEventListener("popupshown", listener, false);
deferred.resolve();
};
contextMenu.addEventListener("popupshown", listener, false);
el = doc.getElementsByClassName("detail-view-container")[0];
EventUtils.synthesizeMouse(el, 4, 4, { }, gManagerWindow);
EventUtils.synthesizeMouse(el, 4, 4, { type: "contextmenu", button: 2 }, gManagerWindow);
yield deferred.promise;
let menuSep = doc.getElementById("addonitem-menuseparator");
is_element_hidden(menuSep, "Menu separator is hidden.");
contextMenu.hidePopup();
});
add_task(function* testPreferencesButton() {