import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,44 @@
# nsBrowserContentHandler.js
component {5d0ce354-df01-421a-83fb-7ead0990c24e} nsBrowserContentHandler.js application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/browser/clh;1 {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
component {47cd0651-b1be-4a0f-b5c4-10e5a573ef71} nsBrowserContentHandler.js application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/browser/final-clh;1 {47cd0651-b1be-4a0f-b5c4-10e5a573ef71} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=text/html {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=application/vnd.mozilla.xul+xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=image/svg+xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=text/rdf {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=text/xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=application/xhtml+xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=text/css {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=text/plain {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=image/gif {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=image/jpeg {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=image/jpg {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=image/png {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=image/bmp {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=image/x-icon {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=image/vnd.microsoft.icon {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
contract @mozilla.org/uriloader/content-handler;1?type=application/http-index-format {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
category command-line-handler m-browser @mozilla.org/browser/clh;1 application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
category command-line-handler x-default @mozilla.org/browser/final-clh;1 application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
category command-line-validator b-browser @mozilla.org/browser/clh;1 application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
# nsBrowserGlue.js
# This component must restrict its registration for the app-startup category
# to the specific list of apps that use it so it doesn't get loaded in xpcshell.
# Thus we restrict it to these apps:
#
# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61}
# browser: {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66}
# graphene: {d1bfe7d9-c01e-4237-998b-7b5f960a4314}
component {eab9012e-5f74-4cbc-b2b5-a590235513cc} nsBrowserGlue.js
contract @mozilla.org/browser/browserglue;1 {eab9012e-5f74-4cbc-b2b5-a590235513cc}
category app-startup nsBrowserGlue service,@mozilla.org/browser/browserglue;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66} application={d1bfe7d9-c01e-4237-998b-7b5f960a4314}
component {d8903bf6-68d5-4e97-bcd1-e4d3012f721a} nsBrowserGlue.js
#ifndef MOZ_MULET
contract @mozilla.org/content-permission/prompt;1 {d8903bf6-68d5-4e97-bcd1-e4d3012f721a}
#endif

View file

@ -0,0 +1,235 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
// See also: docshell/base/nsAboutRedirector.cpp
#include "AboutRedirector.h"
#include "nsNetUtil.h"
#include "nsIAboutNewTabService.h"
#include "nsIChannel.h"
#include "nsIURI.h"
#include "nsIScriptSecurityManager.h"
#include "nsIProtocolHandler.h"
#include "mozilla/ArrayUtils.h"
#include "nsServiceManagerUtils.h"
namespace mozilla {
namespace browser {
NS_IMPL_ISUPPORTS(AboutRedirector, nsIAboutModule)
struct RedirEntry {
const char* id;
const char* url;
uint32_t flags;
};
/*
Entries which do not have URI_SAFE_FOR_UNTRUSTED_CONTENT will run with chrome
privileges. This is potentially dangerous. Please use
URI_SAFE_FOR_UNTRUSTED_CONTENT in the third argument to each map item below
unless your about: page really needs chrome privileges. Security review is
required before adding new map entries without
URI_SAFE_FOR_UNTRUSTED_CONTENT.
*/
static RedirEntry kRedirMap[] = {
{ "blocked", "chrome://browser/content/blockedSite.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
{ "certerror", "chrome://browser/content/aboutNetError.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
{ "socialerror", "chrome://browser/content/aboutSocialError.xhtml",
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
{ "providerdirectory", "chrome://browser/content/aboutProviderDirectory.xhtml",
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
{ "tabcrashed", "chrome://browser/content/aboutTabCrashed.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
{ "feeds", "chrome://browser/content/feeds/subscribe.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
{ "privatebrowsing", "chrome://browser/content/aboutPrivateBrowsing.xhtml",
nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
nsIAboutModule::ALLOW_SCRIPT },
{ "rights",
"chrome://global/content/aboutRights.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::MAKE_LINKABLE |
nsIAboutModule::ALLOW_SCRIPT },
{ "robots", "chrome://browser/content/aboutRobots.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT },
{ "searchreset", "chrome://browser/content/search/searchReset.xhtml",
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
{ "sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
{ "welcomeback", "chrome://browser/content/aboutWelcomeBack.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
{ "sync-tabs", "chrome://browser/content/sync/aboutSyncTabs.xul",
nsIAboutModule::ALLOW_SCRIPT },
// Linkable because of indexeddb use (bug 1228118)
{ "home", "chrome://browser/content/abouthome/aboutHome.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::MAKE_LINKABLE |
nsIAboutModule::ENABLE_INDEXED_DB },
// the newtab's actual URL will be determined when the channel is created
{ "newtab", "about:blank",
nsIAboutModule::ALLOW_SCRIPT },
{ "preferences", "chrome://browser/content/preferences/in-content/preferences.xul",
nsIAboutModule::ALLOW_SCRIPT },
{ "downloads", "chrome://browser/content/downloads/contentAreaDownloadsView.xul",
nsIAboutModule::ALLOW_SCRIPT },
#ifdef MOZ_SERVICES_HEALTHREPORT
{ "healthreport", "chrome://browser/content/abouthealthreport/abouthealth.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
#endif
{ "accounts", "chrome://browser/content/aboutaccounts/aboutaccounts.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
{ "reader", "chrome://global/content/reader/aboutReader.html",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
};
static const int kRedirTotal = ArrayLength(kRedirMap);
static nsAutoCString
GetAboutModuleName(nsIURI *aURI)
{
nsAutoCString path;
aURI->GetPath(path);
int32_t f = path.FindChar('#');
if (f >= 0)
path.SetLength(f);
f = path.FindChar('?');
if (f >= 0)
path.SetLength(f);
ToLowerCase(path);
return path;
}
NS_IMETHODIMP
AboutRedirector::NewChannel(nsIURI* aURI,
nsILoadInfo* aLoadInfo,
nsIChannel** result)
{
NS_ENSURE_ARG_POINTER(aURI);
NS_ASSERTION(result, "must not be null");
nsAutoCString path = GetAboutModuleName(aURI);
nsresult rv;
nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
NS_ENSURE_SUCCESS(rv, rv);
for (int i = 0; i < kRedirTotal; i++) {
if (!strcmp(path.get(), kRedirMap[i].id)) {
nsAutoCString url;
if (path.EqualsLiteral("newtab")) {
// let the aboutNewTabService decide where to redirect
nsCOMPtr<nsIAboutNewTabService> aboutNewTabService =
do_GetService("@mozilla.org/browser/aboutnewtab-service;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = aboutNewTabService->GetDefaultURL(url);
NS_ENSURE_SUCCESS(rv, rv);
// if about:newtab points to an external resource we have to make sure
// the content is signed and trusted
bool remoteEnabled = false;
rv = aboutNewTabService->GetRemoteEnabled(&remoteEnabled);
NS_ENSURE_SUCCESS(rv, rv);
if (remoteEnabled) {
NS_ENSURE_ARG_POINTER(aLoadInfo);
aLoadInfo->SetVerifySignedContent(true);
}
}
// fall back to the specified url in the map
if (url.IsEmpty()) {
url.AssignASCII(kRedirMap[i].url);
}
nsCOMPtr<nsIChannel> tempChannel;
nsCOMPtr<nsIURI> tempURI;
rv = NS_NewURI(getter_AddRefs(tempURI), url);
NS_ENSURE_SUCCESS(rv, rv);
// If tempURI links to an external URI (i.e. something other than
// chrome:// or resource://) then set the LOAD_REPLACE flag on the
// channel which forces the channel owner to reflect the displayed
// URL rather then being the systemPrincipal.
bool isUIResource = false;
rv = NS_URIChainHasFlags(tempURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
&isUIResource);
NS_ENSURE_SUCCESS(rv, rv);
nsLoadFlags loadFlags = isUIResource
? static_cast<nsLoadFlags>(nsIChannel::LOAD_NORMAL)
: static_cast<nsLoadFlags>(nsIChannel::LOAD_REPLACE);
rv = NS_NewChannelInternal(getter_AddRefs(tempChannel),
tempURI,
aLoadInfo,
nullptr, // aLoadGroup
nullptr, // aCallbacks
loadFlags);
NS_ENSURE_SUCCESS(rv, rv);
tempChannel->SetOriginalURI(aURI);
NS_ADDREF(*result = tempChannel);
return rv;
}
}
return NS_ERROR_ILLEGAL_VALUE;
}
NS_IMETHODIMP
AboutRedirector::GetURIFlags(nsIURI *aURI, uint32_t *result)
{
NS_ENSURE_ARG_POINTER(aURI);
nsAutoCString name = GetAboutModuleName(aURI);
for (int i = 0; i < kRedirTotal; i++) {
if (name.Equals(kRedirMap[i].id)) {
*result = kRedirMap[i].flags;
return NS_OK;
}
}
return NS_ERROR_ILLEGAL_VALUE;
}
nsresult
AboutRedirector::Create(nsISupports *aOuter, REFNSIID aIID, void **result)
{
AboutRedirector* about = new AboutRedirector();
if (about == nullptr)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(about);
nsresult rv = about->QueryInterface(aIID, result);
NS_RELEASE(about);
return rv;
}
} // namespace browser
} // namespace mozilla

View file

@ -0,0 +1,32 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef AboutRedirector_h__
#define AboutRedirector_h__
#include "nsIAboutModule.h"
namespace mozilla {
namespace browser {
class AboutRedirector : public nsIAboutModule
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIABOUTMODULE
AboutRedirector() {}
static nsresult
Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
protected:
virtual ~AboutRedirector() {}
};
} // namespace browser
} // namespace mozilla
#endif // AboutRedirector_h__

View file

@ -0,0 +1,19 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXPORTS.mozilla.browser += [
'AboutRedirector.h',
]
SOURCES += [
'AboutRedirector.cpp',
]
FINAL_LIBRARY = 'browsercomps'
LOCAL_INCLUDES += [
'../build',
]

View file

@ -0,0 +1,8 @@
# 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/.
include $(topsrcdir)/config/rules.mk
# Ensure that we don't embed a manifest referencing the CRT.
EMBED_MANIFEST_AT =

View file

@ -0,0 +1,24 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXPORTS += [
'nsBrowserCompsCID.h',
]
SOURCES += [
'nsModule.cpp',
]
Library('browsercomps')
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'../about',
'../dirprovider',
'../feeds',
'../migration',
'../shell',
]

View file

@ -0,0 +1,43 @@
/* 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/. */
/////////////////////////////////////////////////////////////////////////////
#ifdef XP_WIN
#define NS_WINIEHISTORYENUMERATOR_CID \
{ 0x93480624, 0x806e, 0x4756, { 0xb7, 0xcb, 0x0f, 0xb7, 0xdd, 0x74, 0x6a, 0x8f } }
#define NS_IEHISTORYENUMERATOR_CONTRACTID \
"@mozilla.org/profile/migrator/iehistoryenumerator;1"
#endif
#define NS_SHELLSERVICE_CID \
{ 0x63c7b9f4, 0xcc8, 0x43f8, { 0xb6, 0x66, 0xa, 0x66, 0x16, 0x55, 0xcb, 0x73 } }
#define NS_SHELLSERVICE_CONTRACTID \
"@mozilla.org/browser/shell-service;1"
#define NS_RDF_FORWARDPROXY_INFER_DATASOURCE_CID \
{ 0x7a024bcf, 0xedd5, 0x4d9a, { 0x86, 0x14, 0xd4, 0x4b, 0xe1, 0xda, 0xda, 0xd3 } }
#define NS_FEEDSNIFFER_CID \
{ 0x6893e69, 0x71d8, 0x4b23, { 0x81, 0xeb, 0x80, 0x31, 0x4d, 0xaf, 0x3e, 0x66 } }
#define NS_FEEDSNIFFER_CONTRACTID \
"@mozilla.org/browser/feeds/sniffer;1"
#define NS_ABOUTFEEDS_CID \
{ 0x12ff56ec, 0x58be, 0x402c, { 0xb0, 0x57, 0x1, 0xf9, 0x61, 0xde, 0x96, 0x9b } }
// 136e2c4d-c5a4-477c-b131-d93d7d704f64
#define NS_PRIVATE_BROWSING_SERVICE_WRAPPER_CID \
{ 0x136e2c4d, 0xc5a4, 0x477c, { 0xb1, 0x31, 0xd9, 0x3d, 0x7d, 0x70, 0x4f, 0x64 } }
// 7e4bb6ad-2fc4-4dc6-89ef-23e8e5ccf980
#define NS_BROWSER_ABOUT_REDIRECTOR_CID \
{ 0x7e4bb6ad, 0x2fc4, 0x4dc6, { 0x89, 0xef, 0x23, 0xe8, 0xe5, 0xcc, 0xf9, 0x80 } }
// {6DEB193C-F87D-4078-BC78-5E64655B4D62}
#define NS_BROWSERDIRECTORYPROVIDER_CID \
{ 0x6deb193c, 0xf87d, 0x4078, { 0xbc, 0x78, 0x5e, 0x64, 0x65, 0x5b, 0x4d, 0x62 } }

View file

@ -0,0 +1,131 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
#include "mozilla/ModuleUtils.h"
#include "nsBrowserCompsCID.h"
#include "DirectoryProvider.h"
#if defined(XP_WIN)
#include "nsWindowsShellService.h"
#elif defined(XP_MACOSX)
#include "nsMacShellService.h"
#elif defined(MOZ_WIDGET_GTK)
#include "nsGNOMEShellService.h"
#endif
#if defined(XP_WIN)
#include "nsIEHistoryEnumerator.h"
#endif
#include "rdf.h"
#include "nsFeedSniffer.h"
#include "AboutRedirector.h"
#include "nsIAboutModule.h"
#include "nsNetCID.h"
using namespace mozilla::browser;
/////////////////////////////////////////////////////////////////////////////
NS_GENERIC_FACTORY_CONSTRUCTOR(DirectoryProvider)
#if defined(XP_WIN)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindowsShellService)
#elif defined(XP_MACOSX)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacShellService)
#elif defined(MOZ_WIDGET_GTK)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGNOMEShellService, Init)
#endif
#if defined(XP_WIN)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsIEHistoryEnumerator)
#endif
NS_GENERIC_FACTORY_CONSTRUCTOR(nsFeedSniffer)
NS_DEFINE_NAMED_CID(NS_BROWSERDIRECTORYPROVIDER_CID);
#if defined(XP_WIN)
NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
#elif defined(MOZ_WIDGET_GTK)
NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
#endif
NS_DEFINE_NAMED_CID(NS_FEEDSNIFFER_CID);
NS_DEFINE_NAMED_CID(NS_BROWSER_ABOUT_REDIRECTOR_CID);
#if defined(XP_WIN)
NS_DEFINE_NAMED_CID(NS_WINIEHISTORYENUMERATOR_CID);
#elif defined(XP_MACOSX)
NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
#endif
static const mozilla::Module::CIDEntry kBrowserCIDs[] = {
{ &kNS_BROWSERDIRECTORYPROVIDER_CID, false, nullptr, DirectoryProviderConstructor },
#if defined(XP_WIN)
{ &kNS_SHELLSERVICE_CID, false, nullptr, nsWindowsShellServiceConstructor },
#elif defined(MOZ_WIDGET_GTK)
{ &kNS_SHELLSERVICE_CID, false, nullptr, nsGNOMEShellServiceConstructor },
#endif
{ &kNS_FEEDSNIFFER_CID, false, nullptr, nsFeedSnifferConstructor },
{ &kNS_BROWSER_ABOUT_REDIRECTOR_CID, false, nullptr, AboutRedirector::Create },
#if defined(XP_WIN)
{ &kNS_WINIEHISTORYENUMERATOR_CID, false, nullptr, nsIEHistoryEnumeratorConstructor },
#elif defined(XP_MACOSX)
{ &kNS_SHELLSERVICE_CID, false, nullptr, nsMacShellServiceConstructor },
#endif
{ nullptr }
};
static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
{ NS_BROWSERDIRECTORYPROVIDER_CONTRACTID, &kNS_BROWSERDIRECTORYPROVIDER_CID },
#if defined(XP_WIN)
{ NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
#elif defined(MOZ_WIDGET_GTK)
{ NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
#endif
{ NS_FEEDSNIFFER_CONTRACTID, &kNS_FEEDSNIFFER_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "blocked", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "certerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "socialerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "providerdirectory", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "tabcrashed", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "feeds", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "privatebrowsing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "rights", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "robots", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "searchreset", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "sessionrestore", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "welcomeback", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "sync-tabs", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "home", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "newtab", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "preferences", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "downloads", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "accounts", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#ifdef MOZ_SERVICES_HEALTHREPORT
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "healthreport", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#endif
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "reader", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#if defined(XP_WIN)
{ NS_IEHISTORYENUMERATOR_CONTRACTID, &kNS_WINIEHISTORYENUMERATOR_CID },
#elif defined(XP_MACOSX)
{ NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
#endif
{ nullptr }
};
static const mozilla::Module::CategoryEntry kBrowserCategories[] = {
{ XPCOM_DIRECTORY_PROVIDER_CATEGORY, "browser-directory-provider", NS_BROWSERDIRECTORYPROVIDER_CONTRACTID },
{ NS_CONTENT_SNIFFER_CATEGORY, "Feed Sniffer", NS_FEEDSNIFFER_CONTRACTID },
{ nullptr }
};
static const mozilla::Module kBrowserModule = {
mozilla::Module::kVersion,
kBrowserCIDs,
kBrowserContracts,
kBrowserCategories
};
NSMODULE_DEFN(nsBrowserCompsModule) = &kBrowserModule;

View file

@ -0,0 +1,91 @@
[data-identity-color="blue"] {
--identity-tab-color: #0996f8;
--identity-icon-color: #00a7e0;
}
[data-identity-color="turquoise"] {
--identity-tab-color: #01bdad;
--identity-icon-color: #01bdad;
}
[data-identity-color="green"] {
--identity-tab-color: #57bd35;
--identity-icon-color: #7dc14c;
}
[data-identity-color="yellow"] {
--identity-tab-color: #ffcb00;
--identity-icon-color: #ffcb00;
}
[data-identity-color="orange"] {
--identity-tab-color: #ff9216;
--identity-icon-color: #ff9216;
}
[data-identity-color="red"] {
--identity-tab-color: #d92215;
--identity-icon-color: #d92215;
}
[data-identity-color="pink"] {
--identity-tab-color: #ea385e;
--identity-icon-color: #ee5195;
}
[data-identity-color="purple"] {
--identity-tab-color: #7a2f7a;
--identity-icon-color: #7a2f7a;
}
[data-identity-icon="fingerprint"] {
--identity-icon: url("chrome://browser/content/usercontext.svg#fingerprint");
}
[data-identity-icon="briefcase"] {
--identity-icon: url("chrome://browser/content/usercontext.svg#briefcase");
}
[data-identity-icon="dollar"] {
--identity-icon: url("chrome://browser/content/usercontext.svg#dollar");
}
[data-identity-icon="cart"] {
--identity-icon: url("chrome://browser/content/usercontext.svg#cart");
}
[data-identity-icon="circle"] {
--identity-icon: url("chrome://browser/content/usercontext.svg#circle");
}
#userContext-indicator {
height: 16px;
width: 16px;
}
#userContext-label {
margin-inline-end: 3px;
color: var(--identity-tab-color);
}
#userContext-icons {
-moz-box-align: center;
}
.tabbrowser-tab[usercontextid] {
background-image: linear-gradient(to right, transparent 20%, var(--identity-tab-color) 30%, var(--identity-tab-color) 70%, transparent 80%);
background-size: auto 2px;
background-repeat: no-repeat;
}
.userContext-icon,
.menuitem-iconic[data-usercontextid] > .menu-iconic-left > .menu-iconic-icon,
.subviewbutton[usercontextid] > .toolbarbutton-icon,
#userContext-indicator {
background-image: var(--identity-icon);
filter: url(chrome://browser/skin/filters.svg#fill);
fill: var(--identity-icon-color);
background-size: contain;
background-repeat: no-repeat;
background-position: center center;
}

View file

@ -0,0 +1,6 @@
# 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/.
browser.jar:
content/browser/usercontext/usercontext.css (content/usercontext.css)

View file

@ -0,0 +1,14 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
BROWSER_CHROME_MANIFESTS += [
'test/browser/browser.ini',
]
JAR_MANIFESTS += ['jar.mn']
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Contextual Identity')

View file

@ -0,0 +1,11 @@
"use strict";
module.exports = {
"extends": [
"../../../../../testing/mochitest/browser.eslintrc.js"
],
"rules": {
"no-undef": "error"
}
};

View file

@ -0,0 +1,30 @@
[DEFAULT]
support-files =
empty_file.html
file_reflect_cookie_into_title.html
favicon-normal32.png
file_set_storages.html
serviceworker.html
worker.js
[browser_aboutURLs.js]
[browser_eme.js]
[browser_favicon.js]
[browser_forgetaboutsite.js]
[browser_forgetAPI_cookie_getCookiesWithOriginAttributes.js]
[browser_forgetAPI_EME_forgetThisSite.js]
[browser_forgetAPI_quota_clearStoragesForPrincipal.js]
[browser_newtabButton.js]
[browser_usercontext.js]
[browser_usercontextid_tabdrop.js]
skip-if = os == "mac" || os == "win" # Intermittent failure - bug 1268276
[browser_windowName.js]
tags = openwindow
[browser_windowOpen.js]
tags = openwindow
[browser_serviceworkers.js]
[browser_broadcastchannel.js]
[browser_blobUrl.js]
[browser_middleClick.js]
[browser_imageCache.js]
[browser_count_and_remove.js]

View file

@ -0,0 +1,49 @@
"use strict";
// For some about: URLs, they will take more time to load and cause timeout.
// See Bug 1270998.
requestLongerTimeout(2);
add_task(function* () {
let aboutURLs = [];
// List of about: URLs that will initiate network requests.
let networkURLs = [
"credits",
"telemetry" // about:telemetry will fetch Telemetry asynchrounously and takes
// longer, we skip this for now.
];
let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
for (let cid in Cc) {
let result = cid.match(/@mozilla.org\/network\/protocol\/about;1\?what\=(.*)$/);
if (!result) {
continue;
}
let aboutType = result[1];
let contract = "@mozilla.org/network/protocol/about;1?what=" + aboutType;
try {
let am = Cc[contract].getService(Ci.nsIAboutModule);
let uri = ios.newURI("about:"+aboutType, null, null);
let flags = am.getURIFlags(uri);
if (!(flags & Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT) &&
networkURLs.indexOf(aboutType) == -1) {
aboutURLs.push(aboutType);
}
} catch (e) {
// getService might have thrown if the component doesn't actually
// implement nsIAboutModule
}
}
for (let url of aboutURLs) {
info("Loading about:" + url);
let tab = gBrowser.addTab("about:"+url, {userContextId: 1});
yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
ok(true, "Done loading about:" + url);
yield BrowserTestUtils.removeTab(tab);
}
});

View file

@ -0,0 +1,78 @@
"use strict";
// Here we want to test that blob URLs are not available cross containers.
const BASE_URI = "http://mochi.test:8888/browser/browser/components/"
+ "contextualidentity/test/browser/empty_file.html";
add_task(function* setup() {
yield new Promise((resolve) => {
SpecialPowers.pushPrefEnv({"set": [
["privacy.userContext.enabled", true]
]}, resolve);
});
});
add_task(function* test() {
info("Creating a tab with UCI = 1...");
let tab1 = gBrowser.addTab(BASE_URI, {userContextId: 1});
is(tab1.getAttribute('usercontextid'), 1, "New tab has UCI equal 1");
let browser1 = gBrowser.getBrowserForTab(tab1);
yield BrowserTestUtils.browserLoaded(browser1);
let blobURL;
info("Creating a blob URL...");
yield ContentTask.spawn(browser1, null, function() {
return Promise.resolve(content.window.URL.createObjectURL(new content.window.Blob([123])));
}).then(newURL => { blobURL = newURL });
info("Blob URL: " + blobURL);
info("Creating a tab with UCI = 2...");
let tab2 = gBrowser.addTab(BASE_URI, {userContextId: 2});
is(tab2.getAttribute('usercontextid'), 2, "New tab has UCI equal 2");
let browser2 = gBrowser.getBrowserForTab(tab2);
yield BrowserTestUtils.browserLoaded(browser2);
yield ContentTask.spawn(browser2, blobURL, function(url) {
return new Promise(resolve => {
var xhr = new content.window.XMLHttpRequest();
xhr.onerror = function() { resolve("SendErrored"); }
xhr.onload = function() { resolve("SendLoaded"); }
xhr.open("GET", url);
xhr.send();
});
}).then(status => {
is(status, "SendErrored", "Using a blob URI from one user context id in another should not work");
});
info("Creating a tab with UCI = 1...");
let tab3 = gBrowser.addTab(BASE_URI, {userContextId: 1});
is(tab3.getAttribute('usercontextid'), 1, "New tab has UCI equal 1");
let browser3 = gBrowser.getBrowserForTab(tab3);
yield BrowserTestUtils.browserLoaded(browser3);
yield ContentTask.spawn(browser3, blobURL, function(url) {
return new Promise(resolve => {
var xhr = new content.window.XMLHttpRequest();
xhr.open("GET", url);
try {
xhr.send();
resolve("SendSucceeded");
} catch (e) {
resolve("SendThrew");
}
});
}).then(status => {
is(status, "SendSucceeded", "Using a blob URI within a single user context id should work");
});
yield BrowserTestUtils.removeTab(tab1);
yield BrowserTestUtils.removeTab(tab2);
yield BrowserTestUtils.removeTab(tab3);
});

View file

@ -0,0 +1,80 @@
let { classes: Cc, interfaces: Ci } = Components;
const BASE_ORIGIN = "http://example.com";
const URI = BASE_ORIGIN +
"/browser/browser/components/contextualidentity/test/browser/empty_file.html";
// opens `uri' in a new tab with the provided userContextId and focuses it.
// returns the newly opened tab
function* openTabInUserContext(uri, userContextId) {
// open the tab in the correct userContextId
let tab = gBrowser.addTab(uri, {userContextId});
// select tab and make sure its browser is focused
gBrowser.selectedTab = tab;
tab.ownerGlobal.focus();
let browser = gBrowser.getBrowserForTab(tab);
yield BrowserTestUtils.browserLoaded(browser);
return {tab, browser};
}
add_task(function* setup() {
// make sure userContext is enabled.
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [
["privacy.userContext.enabled", true]
]}, resolve);
});
});
add_task(function* test() {
let receiver = yield* openTabInUserContext(URI, 2);
let channelName = "contextualidentity-broadcastchannel";
// reflect the received message on title
yield ContentTask.spawn(receiver.browser, channelName,
function (name) {
content.window.testPromise = new content.window.Promise(resolve => {
content.window.bc = new content.window.BroadcastChannel(name);
content.window.bc.onmessage = function (e) {
content.document.title += e.data;
resolve();
}
});
}
);
let sender1 = yield* openTabInUserContext(URI, 1);
let sender2 = yield* openTabInUserContext(URI, 2);
sender1.message = "Message from user context #1";
sender2.message = "Message from user context #2";
// send a message from a tab in different user context first
// then send a message from a tab in the same user context
for (let sender of [sender1, sender2]) {
yield ContentTask.spawn(
sender.browser,
{ name: channelName, message: sender.message },
function (opts) {
let bc = new content.window.BroadcastChannel(opts.name);
bc.postMessage(opts.message);
});
}
// Since sender1 sends before sender2, if the title is exactly
// sender2's message, sender1's message must've been blocked
yield ContentTask.spawn(receiver.browser, sender2.message,
function* (message) {
yield content.window.testPromise.then(function() {
is(content.document.title, message,
"should only receive messages from the same user context");
});
}
);
gBrowser.removeTab(sender1.tab);
gBrowser.removeTab(sender2.tab);
gBrowser.removeTab(receiver.tab);
});

View file

@ -0,0 +1,34 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/ContextualIdentityService.jsm");
function openTabInUserContext(userContextId) {
let tab = gBrowser.addTab("about:blank", {userContextId});
gBrowser.selectedTab = tab;
}
add_task(function* setup() {
// make sure userContext is enabled.
yield SpecialPowers.pushPrefEnv({"set": [
["privacy.userContext.enabled", true]
]});
});
add_task(function* test() {
is(ContextualIdentityService.countContainerTabs(), 0, "0 container tabs by default.");
openTabInUserContext(1);
is(ContextualIdentityService.countContainerTabs(), 1, "1 container tab created");
openTabInUserContext(1);
is(ContextualIdentityService.countContainerTabs(), 2, "2 container tab created");
openTabInUserContext(2);
is(ContextualIdentityService.countContainerTabs(), 3, "3 container tab created");
ContextualIdentityService.closeAllContainerTabs();
is(ContextualIdentityService.countContainerTabs(), 0, "0 container tab at the end.");
});

View file

@ -0,0 +1,186 @@
/*
* Bug 1283325 - A test case to test the EME is originAttributes aware or not.
*/
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
const TEST_HOST = "example.com";
const TEST_URL = "http://" + TEST_HOST + "/browser/browser/components/contextualidentity/test/browser/";
const TESTKEY = {
initDataType: 'keyids',
initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"], "type":"persistent-license"}',
kid: "LwVHf8JLtPrv2GUXFW2v_A",
key: "97b9ddc459c8d5ff23c1f2754c95abe8",
sessionType: 'persistent-license',
};
const USER_ID_DEFAULT = 0;
const USER_ID_PERSONAL = 1;
function* openTabInUserContext(uri, userContextId) {
// Open the tab in the correct userContextId.
let tab = gBrowser.addTab(uri, {userContextId});
// Select tab and make sure its browser is focused.
gBrowser.selectedTab = tab;
tab.ownerDocument.defaultView.focus();
let browser = gBrowser.getBrowserForTab(tab);
yield BrowserTestUtils.browserLoaded(browser);
return {tab, browser};
}
function HexToBase64(hex)
{
var bin = "";
for (var i = 0; i < hex.length; i += 2) {
bin += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return window.btoa(bin).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
}
function Base64ToHex(str)
{
var bin = window.atob(str.replace(/-/g, "+").replace(/_/g, "/"));
var res = "";
for (var i = 0; i < bin.length; i++) {
res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2);
}
return res;
}
function ByteArrayToHex(array) {
let bin = String.fromCharCode.apply(null, new Uint8Array(array));
let res = "";
for (let i = 0; i < bin.length; i++) {
res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2);
}
return res;
}
function generateKeyObject(aKid, aKey) {
let keyObj = {
kty: 'oct',
kid: aKid,
k: HexToBase64(aKey),
};
return new TextEncoder().encode(JSON.stringify({
keys: [keyObj]
}));
}
function generateKeyInfo(aData) {
let keyInfo = {
initDataType: aData.initDataType,
initData: new TextEncoder().encode(aData.initData),
sessionType: aData.sessionType,
keyObj: generateKeyObject(aData.kid, aData.key),
};
return keyInfo;
}
add_task(function* setup() {
// Make sure userContext is enabled.
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [
[ "privacy.userContext.enabled", true ],
[ "media.mediasource.enabled", true ],
[ "media.eme.apiVisible", true ],
[ "media.mediasource.webm.enabled", true ],
[ "media.clearkey.persistent-license.enabled", true ],
]}, resolve);
});
});
add_task(function* test() {
// Open a tab with the default container.
let defaultContainer = yield openTabInUserContext(TEST_URL + "empty_file.html", USER_ID_DEFAULT);
// Generate the key info for the default container.
let keyInfo = generateKeyInfo(TESTKEY);
// Update the media key for the default container.
let result = yield ContentTask.spawn(defaultContainer.browser, keyInfo, function* (aKeyInfo) {
let access = yield content.navigator.requestMediaKeySystemAccess('org.w3.clearkey',
[{
initDataTypes: [aKeyInfo.initDataType],
videoCapabilities: [{contentType: 'video/webm'}],
sessionTypes: ['persistent-license'],
persistentState: 'required',
}]);
let mediaKeys = yield access.createMediaKeys();
let session = mediaKeys.createSession(aKeyInfo.sessionType);
let res = {};
// Insert the media key.
yield new Promise(resolve => {
session.addEventListener("message", function(event) {
session.update(aKeyInfo.keyObj).then(
() => { resolve(); }
).catch(
() => {
ok(false, "Update the media key fail.");
resolve();
}
);
});
session.generateRequest(aKeyInfo.initDataType, aKeyInfo.initData);
});
let map = session.keyStatuses;
is(map.size, 1, "One media key has been added.");
if (map.size === 1) {
res.keyId = map.keys().next().value;
res.sessionId = session.sessionId;
}
// Close the session.
session.close();
yield session.closed;
return res;
});
// Check the media key ID.
is(ByteArrayToHex(result.keyId), Base64ToHex(TESTKEY.kid), "The key Id of the default container is correct.");
// Store the sessionId for the further checking.
keyInfo.sessionId = result.sessionId;
// Open a tab with personal container.
let personalContainer = yield openTabInUserContext(TEST_URL + "empty_file.html", USER_ID_PERSONAL);
yield ContentTask.spawn(personalContainer.browser, keyInfo, function* (aKeyInfo) {
let access = yield content.navigator.requestMediaKeySystemAccess('org.w3.clearkey',
[{
initDataTypes: [aKeyInfo.initDataType],
videoCapabilities: [{contentType: 'video/webm'}],
sessionTypes: ['persistent-license'],
persistentState: 'required',
}]);
let mediaKeys = yield access.createMediaKeys();
let session = mediaKeys.createSession(aKeyInfo.sessionType);
// First, load the session to check that mediakeys do not share with
// default container.
yield session.load(aKeyInfo.sessionId);
let map = session.keyStatuses;
// Check that there is no media key here.
is(map.size, 0, "No media key should be here for the personal container.");
});
// Close default container tab.
yield BrowserTestUtils.removeTab(defaultContainer.tab);
// Close personal container tab.
yield BrowserTestUtils.removeTab(personalContainer.tab);
});

View file

@ -0,0 +1,140 @@
/*
* Bug 1270678 - A test case to test does the favicon obey originAttributes.
*/
let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/PlacesUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
const USER_CONTEXTS = [
"default",
"personal",
"work",
];
let gHttpServer = null;
let gUserContextId;
let gFaviconData;
function getIconFile() {
new Promise(resolve => {
NetUtil.asyncFetch({
uri: "http://www.example.com/browser/browser/components/contextualidentity/test/browser/favicon-normal32.png",
loadUsingSystemPrincipal: true,
contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON
}, function(inputStream, status) {
let size = inputStream.available();
gFaviconData = NetUtil.readInputStreamToString(inputStream, size);
resolve();
});
});
}
function* openTabInUserContext(uri, userContextId) {
// open the tab in the correct userContextId
let tab = gBrowser.addTab(uri, {userContextId});
// select tab and make sure its browser is focused
gBrowser.selectedTab = tab;
tab.ownerGlobal.focus();
let browser = gBrowser.getBrowserForTab(tab);
yield BrowserTestUtils.browserLoaded(browser);
return {tab, browser};
}
function loadIndexHandler(metadata, response) {
response.setStatusLine(metadata.httpVersion, 200, "Ok");
response.setHeader("Content-Type", "text/html", false);
let body = `
<!DOCTYPE HTML>
<html>
<head>
<meta charset='utf-8'>
<title>Favicon Test</title>
</head>
<body>
Favicon!!
</body>
</html>`;
response.bodyOutputStream.write(body, body.length);
}
function loadFaviconHandler(metadata, response) {
let expectedCookie = "userContext=" + USER_CONTEXTS[gUserContextId];
if (metadata.hasHeader("Cookie")) {
is(metadata.getHeader("Cookie"), expectedCookie, "The cookie has matched with the expected cookie.");
} else {
ok(false, "The request should have a cookie.");
}
response.setStatusLine(metadata.httpVersion, 200, "Ok");
response.setHeader("Content-Type", "image/png", false);
response.bodyOutputStream.write(gFaviconData, gFaviconData.length);
}
add_task(function* setup() {
// Make sure userContext is enabled.
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [
["privacy.userContext.enabled", true]
]}, resolve);
});
// Create a http server for the image cache test.
if (!gHttpServer) {
gHttpServer = new HttpServer();
gHttpServer.registerPathHandler('/', loadIndexHandler);
gHttpServer.registerPathHandler('/favicon.png', loadFaviconHandler);
gHttpServer.start(-1);
}
});
registerCleanupFunction(() => {
gHttpServer.stop(() => {
gHttpServer = null;
});
});
add_task(function* test() {
waitForExplicitFinish();
// First, get the icon data.
yield getIconFile();
let serverPort = gHttpServer.identity.primaryPort;
let testURL = "http://localhost:" + serverPort + "/";
let testFaviconURL = "http://localhost:" + serverPort + "/favicon.png";
for (let userContextId of Object.keys(USER_CONTEXTS)) {
gUserContextId = userContextId;
// Load the page in 3 different contexts and set a cookie
// which should only be visible in that context.
// Open our tab in the given user context.
let tabInfo = yield* openTabInUserContext(testURL, userContextId);
// Write a cookie according to the userContext.
yield ContentTask.spawn(tabInfo.browser, { userContext: USER_CONTEXTS[userContextId] }, function (arg) {
content.document.cookie = "userContext=" + arg.userContext;
});
let pageURI = NetUtil.newURI(testURL);
let favIconURI = NetUtil.newURI(testFaviconURL);
yield new Promise(resolve => {
PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, favIconURI,
true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, {
onComplete() {
resolve();
},
},
tabInfo.browser.contentPrincipal);
});
yield BrowserTestUtils.removeTab(tabInfo.tab);
}
});

View file

@ -0,0 +1,219 @@
/*
* Bug 1278037 - A Test case for checking whether forgetting APIs are working for the media key.
*/
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
const TEST_HOST = "example.com";
const TEST_URL = "http://" + TEST_HOST + "/browser/browser/components/contextualidentity/test/browser/";
const USER_CONTEXTS = [
"default",
"personal",
];
const TEST_EME_KEY = {
initDataType: 'keyids',
initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"], "type":"persistent-license"}',
kid: "LwVHf8JLtPrv2GUXFW2v_A",
key: "97b9ddc459c8d5ff23c1f2754c95abe8",
sessionType: 'persistent-license',
};
//
// Support functions.
//
function* openTabInUserContext(uri, userContextId) {
// Open the tab in the correct userContextId.
let tab = gBrowser.addTab(uri, {userContextId});
// Select tab and make sure its browser is focused.
gBrowser.selectedTab = tab;
tab.ownerGlobal.focus();
let browser = gBrowser.getBrowserForTab(tab);
yield BrowserTestUtils.browserLoaded(browser);
return {tab, browser};
}
function HexToBase64(hex) {
var bin = "";
for (var i = 0; i < hex.length; i += 2) {
bin += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return window.btoa(bin).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
}
function Base64ToHex(str) {
var bin = window.atob(str.replace(/-/g, "+").replace(/_/g, "/"));
var res = "";
for (var i = 0; i < bin.length; i++) {
res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2);
}
return res;
}
function ByteArrayToHex(array) {
let bin = String.fromCharCode.apply(null, new Uint8Array(array));
let res = "";
for (let i = 0; i < bin.length; i++) {
res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2);
}
return res;
}
function generateKeyObject(aKid, aKey) {
let keyObj = {
kty: 'oct',
kid: aKid,
k: HexToBase64(aKey),
};
return new TextEncoder().encode(JSON.stringify({
keys: [keyObj]
}));
}
function generateKeyInfo(aData) {
let keyInfo = {
initDataType: aData.initDataType,
initData: new TextEncoder().encode(aData.initData),
sessionType: aData.sessionType,
keyObj: generateKeyObject(aData.kid, aData.key),
};
return keyInfo;
}
// Setup a EME key for the given browser, and return the sessionId.
function* setupEMEKey(browser) {
// Generate the key info.
let keyInfo = generateKeyInfo(TEST_EME_KEY);
// Setup the EME key.
let result = yield ContentTask.spawn(browser, keyInfo, function* (aKeyInfo) {
let access = yield content.navigator.requestMediaKeySystemAccess('org.w3.clearkey',
[{
initDataTypes: [aKeyInfo.initDataType],
videoCapabilities: [{contentType: 'video/webm'}],
sessionTypes: ['persistent-license'],
persistentState: 'required',
}]);
let mediaKeys = yield access.createMediaKeys();
let session = mediaKeys.createSession(aKeyInfo.sessionType);
let res = {};
// Insert the EME key.
yield new Promise(resolve => {
session.addEventListener("message", function(event) {
session.update(aKeyInfo.keyObj).then(
() => { resolve(); }
).catch(
() => {
ok(false, "Update the EME key fail.");
resolve();
}
);
});
session.generateRequest(aKeyInfo.initDataType, aKeyInfo.initData);
});
let map = session.keyStatuses;
is(map.size, 1, "One EME key has been added.");
if (map.size === 1) {
res.keyId = map.keys().next().value;
res.sessionId = session.sessionId;
}
// Close the session.
session.close();
yield session.closed;
return res;
});
// Check the EME key ID.
is(ByteArrayToHex(result.keyId), Base64ToHex(TEST_EME_KEY.kid), "The key Id is correct.");
return result.sessionId;
}
// Check whether the EME key has been cleared.
function* checkEMEKey(browser, emeSessionId) {
// Generate the key info.
let keyInfo = generateKeyInfo(TEST_EME_KEY);
keyInfo.sessionId = emeSessionId;
yield ContentTask.spawn(browser, keyInfo, function* (aKeyInfo) {
let access = yield content.navigator.requestMediaKeySystemAccess('org.w3.clearkey',
[{
initDataTypes: [aKeyInfo.initDataType],
videoCapabilities: [{contentType: 'video/webm'}],
sessionTypes: ['persistent-license'],
persistentState: 'required',
}]);
let mediaKeys = yield access.createMediaKeys();
let session = mediaKeys.createSession(aKeyInfo.sessionType);
// First, load the session with the sessionId.
yield session.load(aKeyInfo.sessionId);
let map = session.keyStatuses;
// Check that there is no media key here.
is(map.size, 0, "No media key should be here after forgetThisSite() was called.");
});
}
//
// Test functions.
//
add_task(function* setup() {
// Make sure userContext is enabled.
yield SpecialPowers.pushPrefEnv({"set": [
[ "privacy.userContext.enabled", true ],
[ "media.mediasource.enabled", true ],
[ "media.eme.apiVisible", true ],
[ "media.mediasource.webm.enabled", true ],
[ "media.clearkey.persistent-license.enabled", true ],
]});
});
add_task(function* test_EME_forgetThisSite() {
let tabs = [];
let emeSessionIds = [];
for (let userContextId of Object.keys(USER_CONTEXTS)) {
// Open our tab in the given user context.
tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "empty_file.html", userContextId);
// Setup EME Key.
emeSessionIds[userContextId] = yield setupEMEKey(tabs[userContextId].browser);
// Close this tab.
yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
}
// Clear all EME data for a given domain with originAttributes pattern.
let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
getService(Ci.mozIGeckoMediaPluginChromeService);
mps.forgetThisSite(TEST_HOST, JSON.stringify({}));
// Open tabs again to check EME keys have been cleared.
for (let userContextId of Object.keys(USER_CONTEXTS)) {
// Open our tab in the given user context.
tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "empty_file.html", userContextId);
// Check whether EME Key has been cleared.
yield checkEMEKey(tabs[userContextId].browser, emeSessionIds[userContextId]);
// Close this tab.
yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
}
});

View file

@ -0,0 +1,86 @@
/*
* Bug 1278037 - A Test case for checking whether forgetting APIs are working for cookies.
*/
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
const TEST_HOST = "example.com";
const TEST_URL = "http://" + TEST_HOST + "/browser/browser/components/contextualidentity/test/browser/";
const USER_CONTEXTS = [
"default",
"personal",
];
//
// Support functions.
//
function* openTabInUserContext(uri, userContextId) {
// Open the tab in the correct userContextId.
let tab = gBrowser.addTab(uri, {userContextId});
// Select tab and make sure its browser is focused.
gBrowser.selectedTab = tab;
tab.ownerGlobal.focus();
let browser = gBrowser.getBrowserForTab(tab);
yield BrowserTestUtils.browserLoaded(browser);
return {tab, browser};
}
function getCookiesForOA(host, userContextId) {
return Services.cookies.getCookiesFromHost(host, {userContextId});
}
//
// Test functions.
//
add_task(function* setup() {
// Make sure userContext is enabled.
yield SpecialPowers.pushPrefEnv({"set": [
[ "privacy.userContext.enabled", true ],
]});
});
add_task(function* test_cookie_getCookiesWithOriginAttributes() {
let tabs = [];
let cookieName = "userContextId";
for (let userContextId of Object.keys(USER_CONTEXTS)) {
// Load the page in 2 different contexts and set a cookie
// which should only be visible in that context.
let value = USER_CONTEXTS[userContextId];
// Open our tab in the given user context.
tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "file_reflect_cookie_into_title.html?" + value, userContextId);
// Close this tab.
yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
}
// Check that cookies have been set properly.
for (let userContextId of Object.keys(USER_CONTEXTS)) {
let enumerator = getCookiesForOA(TEST_HOST, userContextId);
ok(enumerator.hasMoreElements(), "Cookies available");
let foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
is(foundCookie["name"], cookieName, "Check cookie name");
is(foundCookie["value"], USER_CONTEXTS[userContextId], "Check cookie value");
}
// Using getCookiesWithOriginAttributes() to get all cookies for a certain
// domain by using the originAttributes pattern, and clear all these cookies.
let enumerator = Services.cookies.getCookiesWithOriginAttributes(JSON.stringify({}), TEST_HOST);
while (enumerator.hasMoreElements()) {
let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
Services.cookies.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
}
// Check that whether cookies has been cleared.
for (let userContextId of Object.keys(USER_CONTEXTS)) {
let enumerator = getCookiesForOA(TEST_HOST, userContextId);
ok(!enumerator.hasMoreElements(), "No Cookie should be here");
}
});

View file

@ -0,0 +1,147 @@
/*
* Bug 1278037 - A Test case for checking whether forgetting APIs are working for the quota manager.
*/
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
const TEST_HOST = "example.com";
const TEST_URL = "http://" + TEST_HOST + "/browser/browser/components/contextualidentity/test/browser/";
const USER_CONTEXTS = [
"default",
"personal",
];
//
// Support functions.
//
function* openTabInUserContext(uri, userContextId) {
// Open the tab in the correct userContextId.
let tab = gBrowser.addTab(uri, {userContextId});
// Select tab and make sure its browser is focused.
gBrowser.selectedTab = tab;
tab.ownerGlobal.focus();
let browser = gBrowser.getBrowserForTab(tab);
yield BrowserTestUtils.browserLoaded(browser);
return {tab, browser};
}
// Setup an entry for the indexedDB.
function* setupIndexedDB(browser) {
yield ContentTask.spawn(browser, { input: "TestForgetAPIs" }, function* (arg) {
let request = content.indexedDB.open("idb", 1);
request.onerror = function() {
throw new Error("error opening db connection");
};
request.onupgradeneeded = event => {
let db = event.target.result;
let store = db.createObjectStore("obj", { keyPath: "id" });
store.createIndex("userContext", "userContext", { unique: false });
};
let db = yield new Promise(resolve => {
request.onsuccess = event => {
resolve(event.target.result);
};
});
// Add an entry into the indexedDB.
let transaction = db.transaction(["obj"], "readwrite");
let store = transaction.objectStore("obj");
store.add({id: 1, userContext: arg.input});
yield new Promise(resolve => {
transaction.oncomplete = () => {
resolve();
};
});
// Check the indexedDB has been set properly.
transaction = db.transaction(["obj"], "readonly");
store = transaction.objectStore("obj");
let getRequest = store.get(1);
yield new Promise(resolve => {
getRequest.onsuccess = () => {
let res = getRequest.result;
is(res.userContext, arg.input, "Check the indexedDB value");
resolve();
};
});
});
}
// Check whether the indexedDB has been cleared.
function* checkIndexedDB(browser) {
yield ContentTask.spawn(browser, null, function* () {
let request = content.indexedDB.open("idb", 1);
let db = yield new Promise(done => {
request.onsuccess = event => {
done(event.target.result);
};
});
try {
db.transaction(["obj"], "readonly");
ok(false, "The indexedDB should not exist");
} catch (e) {
is(e.name, "NotFoundError", "The indexedDB does not exist as expected");
}
});
}
//
// Test functions.
//
add_task(function* setup() {
// Make sure userContext is enabled.
yield SpecialPowers.pushPrefEnv({"set": [
[ "privacy.userContext.enabled", true ],
]});
});
add_task(function* test_quota_clearStoragesForPrincipal() {
let tabs = [];
for (let userContextId of Object.keys(USER_CONTEXTS)) {
// Open our tab in the given user context.
tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "empty_file.html", userContextId);
// Setup an entry for the indexedDB.
yield setupIndexedDB(tabs[userContextId].browser);
// Close this tab.
yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
}
// Using quota manager to clear all indexed DB for a given domain.
let qms = Cc["@mozilla.org/dom/quota-manager-service;1"].
getService(Ci.nsIQuotaManagerService);
let caUtils = {};
let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
getService(Ci.mozIJSSubScriptLoader);
scriptLoader.loadSubScript("chrome://global/content/contentAreaUtils.js",
caUtils);
let httpURI = caUtils.makeURI("http://" + TEST_HOST);
let httpPrincipal = Services.scriptSecurityManager
.createCodebasePrincipal(httpURI, {});
qms.clearStoragesForPrincipal(httpPrincipal, null, true);
for (let userContextId of Object.keys(USER_CONTEXTS)) {
// Open our tab in the given user context.
tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "empty_file.html", userContextId);
// Check whether indexed DB has been cleared.
yield checkIndexedDB(tabs[userContextId].browser);
// Close this tab.
yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
}
});

View file

@ -0,0 +1,352 @@
/*
* Bug 1238183 - Test cases for forgetAboutSite with userContextId.
*/
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/ForgetAboutSite.jsm");
Cu.import("resource://gre/modules/Services.jsm");
let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
let LoadContextInfo = Cc["@mozilla.org/load-context-info-factory;1"]
.getService(Ci.nsILoadContextInfoFactory);
let css = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
.getService(Ci.nsICacheStorageService);
const USER_CONTEXTS = [
"default",
"personal",
];
const TEST_HOST = "example.com";
const TEST_URL = "http://" + TEST_HOST + "/browser/browser/components/contextualidentity/test/browser/";
const COOKIE_NAME = "userContextId";
// Counter for image load hits.
let gHits = 0;
let gHttpServer = null;
function imageHandler(metadata, response) {
// A 1x1 PNG image.
// Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
const IMAGE = atob("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
"ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=");
gHits++;
response.setHeader("Cache-Control", "max-age=10000", false);
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.setHeader("Content-Type", "image/png", false);
response.write(IMAGE);
}
function loadImagePageHandler(metadata, response) {
response.setHeader("Cache-Control", "max-age=10000", false);
response.setStatusLine(metadata.httpVersion, 200, "Ok");
response.setHeader("Content-Type", "text/html", false);
let body = "<!DOCTYPE HTML>\
<html>\
<head>\
<meta charset='utf-8'>\
<title>Load Image</title>\
</head>\
<body>\
<img src='image.png'>\
</body>\
</html>";
response.bodyOutputStream.write(body, body.length);
}
function* openTabInUserContext(uri, userContextId) {
// Open the tab in the correct userContextId.
let tab = gBrowser.addTab(uri, {userContextId});
// Select tab and make sure its browser is focused.
gBrowser.selectedTab = tab;
tab.ownerGlobal.focus();
let browser = gBrowser.getBrowserForTab(tab);
yield BrowserTestUtils.browserLoaded(browser);
return {tab, browser};
}
function getCookiesForOA(host, userContextId) {
return Services.cookies.getCookiesFromHost(host, {userContextId});
}
function createURI(uri)
{
let ioServ = Cc["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
return ioServ.newURI(uri, null, null);
}
function getCacheStorage(where, lci, appcache)
{
if (!lci) lci = LoadContextInfo.default;
switch (where) {
case "disk": return css.diskCacheStorage(lci, false);
case "memory": return css.memoryCacheStorage(lci);
case "appcache": return css.appCacheStorage(lci, appcache);
case "pin": return css.pinningCacheStorage(lci);
}
return null;
}
function OpenCacheEntry(key, where, flags, lci)
{
return new Promise(resolve => {
key = createURI(key);
function CacheListener() { }
CacheListener.prototype = {
_appCache: null,
QueryInterface: function (iid) {
if (iid.equals(Components.interfaces.nsICacheEntryOpenCallback) ||
iid.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
onCacheEntryCheck: function(entry, appCache) {
return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
},
onCacheEntryAvailable: function (entry, isnew, appCache, status) {
resolve();
},
run: function () {
let storage = getCacheStorage(where, lci, this._appCache);
storage.asyncOpenURI(key, "", flags, this);
}
};
(new CacheListener()).run();
});
}
//
// Test functions.
//
// Cookies
function* test_cookie_cleared() {
let tabs = [];
for (let userContextId of Object.keys(USER_CONTEXTS)) {
// Load the page in 2 different contexts and set a cookie
// which should only be visible in that context.
let value = USER_CONTEXTS[userContextId];
// Open our tab in the given user context.
tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "file_reflect_cookie_into_title.html?" + value, userContextId);
// Close this tab.
yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
}
// Check that cookies have been set properly.
for (let userContextId of Object.keys(USER_CONTEXTS)) {
let enumerator = getCookiesForOA(TEST_HOST, userContextId);
ok(enumerator.hasMoreElements(), "Cookies available");
let foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
Assert.equal(foundCookie["name"], COOKIE_NAME, "Check cookie name");
Assert.equal(foundCookie["value"], USER_CONTEXTS[userContextId], "Check cookie value");
}
// Forget the site.
ForgetAboutSite.removeDataFromDomain(TEST_HOST);
// Check that whether cookies has been cleared or not.
for (let userContextId of Object.keys(USER_CONTEXTS)) {
let enumerator = getCookiesForOA(TEST_HOST, userContextId);
ok(!enumerator.hasMoreElements(), "No Cookie should be here");
}
}
// Cache
function* test_cache_cleared() {
// First, add some caches.
for (let userContextId of Object.keys(USER_CONTEXTS)) {
yield OpenCacheEntry("http://" + TEST_HOST + "/",
"disk",
Ci.nsICacheStorage.OPEN_NORMALLY,
LoadContextInfo.custom(false, {userContextId}));
yield OpenCacheEntry("http://" + TEST_HOST + "/",
"memory",
Ci.nsICacheStorage.OPEN_NORMALLY,
LoadContextInfo.custom(false, {userContextId}));
}
// Check that caches have been set correctly.
for (let userContextId of Object.keys(USER_CONTEXTS)) {
let mem = getCacheStorage("memory", LoadContextInfo.custom(false, {userContextId}));
let disk = getCacheStorage("disk", LoadContextInfo.custom(false, {userContextId}));
Assert.ok(mem.exists(createURI("http://" + TEST_HOST + "/"), ""), "The memory cache has been set correctly");
Assert.ok(disk.exists(createURI("http://" + TEST_HOST + "/"), ""), "The disk cache has been set correctly");
}
// Forget the site.
ForgetAboutSite.removeDataFromDomain(TEST_HOST);
// Check that do caches be removed or not?
for (let userContextId of Object.keys(USER_CONTEXTS)) {
let mem = getCacheStorage("memory", LoadContextInfo.custom(false, {userContextId}));
let disk = getCacheStorage("disk", LoadContextInfo.custom(false, {userContextId}));
Assert.ok(!mem.exists(createURI("http://" + TEST_HOST + "/"), ""), "The memory cache is cleared");
Assert.ok(!disk.exists(createURI("http://" + TEST_HOST + "/"), ""), "The disk cache is cleared");
}
}
// Image Cache
function* test_image_cache_cleared() {
let tabs = [];
for (let userContextId of Object.keys(USER_CONTEXTS)) {
// Open our tab in the given user context to cache image.
tabs[userContextId] = yield* openTabInUserContext('http://localhost:' + gHttpServer.identity.primaryPort + '/loadImage.html',
userContextId);
yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
}
let expectedHits = USER_CONTEXTS.length;
// Check that image cache works with the userContextId.
is(gHits, expectedHits, "The image should be loaded" + expectedHits + "times.");
// Reset the cache count.
gHits = 0;
// Forget the site.
ForgetAboutSite.removeDataFromDomain("localhost:" + gHttpServer.identity.primaryPort + "/");
// Load again.
for (let userContextId of Object.keys(USER_CONTEXTS)) {
// Open our tab in the given user context to cache image.
tabs[userContextId] = yield* openTabInUserContext('http://localhost:' + gHttpServer.identity.primaryPort + '/loadImage.html',
userContextId);
yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
}
// Check that image cache was cleared and the server gets another two hits.
is(gHits, expectedHits, "The image should be loaded" + expectedHits + "times.");
}
// Offline Storage
function* test_storage_cleared() {
for (let userContextId of Object.keys(USER_CONTEXTS)) {
// Load the page in 2 different contexts and set the local storage
// which should only be visible in that context.
let value = USER_CONTEXTS[userContextId];
// Open our tab in the given user context.
let tabInfo = yield* openTabInUserContext(TEST_URL+ "file_set_storages.html?" + value, userContextId);
// Check that the storages has been set correctly.
yield ContentTask.spawn(tabInfo.browser, { userContext: USER_CONTEXTS[userContextId] }, function* (arg) {
// Check that the local storage has been set correctly.
Assert.equal(content.localStorage.getItem("userContext"), arg.userContext, "Check the local storage value");
// Check that the session storage has been set correctly.
Assert.equal(content.sessionStorage.getItem("userContext"), arg.userContext, "Check the session storage value");
// Check that the indexedDB has been set correctly.
let request = content.indexedDB.open("idb", 1);
let db = yield new Promise(done => {
request.onsuccess = event => {
done(event.target.result);
};
});
let transaction = db.transaction(["obj"], "readonly");
let store = transaction.objectStore("obj");
let storeRequest = store.get(1);
yield new Promise(done => {
storeRequest.onsuccess = event => {
let res = storeRequest.result;
Assert.equal(res.userContext, arg.userContext, "Check the indexedDB value");
done();
};
});
});
// Close this tab.
yield BrowserTestUtils.removeTab(tabInfo.tab);
}
// Forget the site.
ForgetAboutSite.removeDataFromDomain(TEST_HOST);
// Open the tab again without setting the localStorage and check that the
// local storage has been cleared or not.
for (let userContextId of Object.keys(USER_CONTEXTS)) {
// Open our tab in the given user context without setting local storage.
let tabInfo = yield* openTabInUserContext(TEST_URL+ "file_set_storages.html", userContextId);
// Check that do storages be cleared or not.
yield ContentTask.spawn(tabInfo.browser, null, function* () {
// Check that does the local storage be cleared or not.
Assert.ok(!content.localStorage.getItem("userContext"), "The local storage has been cleared");
// Check that does the session storage be cleared or not.
Assert.ok(!content.sessionStorage.getItem("userContext"), "The session storage has been cleared");
// Check that does the indexedDB be cleared or not.
let request = content.indexedDB.open("idb", 1);
let db = yield new Promise(done => {
request.onsuccess = event => {
done(event.target.result);
};
});
try {
db.transaction(["obj"], "readonly");
Assert.ok(false, "The indexedDB should not exist");
} catch (e) {
Assert.equal(e.name, "NotFoundError", "The indexedDB does not exist as expected");
}
});
// Close the tab.
yield BrowserTestUtils.removeTab(tabInfo.tab);
}
}
add_task(function* setup() {
// Make sure userContext is enabled.
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [
["privacy.userContext.enabled", true]
]}, resolve);
});
// Create a http server for the image cache test.
if (!gHttpServer) {
gHttpServer = new HttpServer();
gHttpServer.registerPathHandler('/image.png', imageHandler);
gHttpServer.registerPathHandler('/loadImage.html', loadImagePageHandler);
gHttpServer.start(-1);
}
});
let tests = [
test_cookie_cleared,
test_cache_cleared,
test_image_cache_cleared,
test_storage_cleared,
];
add_task(function* test() {
for (let i = 0; i < tests.length; i++)
add_task(tests[i]);
});
registerCleanupFunction(() => {
gHttpServer.stop(() => {
gHttpServer = null;
});
});

View file

@ -0,0 +1,59 @@
let Cu = Components.utils;
let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
const NUM_USER_CONTEXTS = 3;
let gHits = 0;
let server = new HttpServer();
server.registerPathHandler('/image.png', imageHandler);
server.registerPathHandler('/file.html', fileHandler);
server.start(-1);
let BASE_URI = 'http://localhost:' + server.identity.primaryPort;
let IMAGE_URI = BASE_URI + '/image.png';
let FILE_URI = BASE_URI + '/file.html';
function imageHandler(metadata, response) {
gHits++;
response.setHeader("Cache-Control", "max-age=10000", false);
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.setHeader("Content-Type", "image/png", false);
var body = "iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=";
response.bodyOutputStream.write(body, body.length);
}
function fileHandler(metadata, response) {
response.setStatusLine(metadata.httpVersion, 200, "OK");
response.setHeader("Content-Type", "text/html", false);
let body = `<html><body><image src=${IMAGE_URI}></body></html>`;
response.bodyOutputStream.write(body, body.length);
}
add_task(function* setup() {
// make sure userContext is enabled.
yield SpecialPowers.pushPrefEnv({"set": [["privacy.userContext.enabled", true]]});
});
// opens `uri' in a new tab with the provided userContextId and focuses it.
// returns the newly opened tab
function* openTabInUserContext(uri, userContextId) {
// open the tab in the correct userContextId
let tab = gBrowser.addTab(uri, {userContextId});
// select tab and make sure its browser is focused
gBrowser.selectedTab = tab;
tab.ownerDocument.defaultView.focus();
let browser = gBrowser.getBrowserForTab(tab);
yield BrowserTestUtils.browserLoaded(browser);
return tab;
}
add_task(function* test() {
for (let userContextId = 0; userContextId < NUM_USER_CONTEXTS; userContextId++) {
let tab = yield* openTabInUserContext(FILE_URI, userContextId);
gBrowser.removeTab(tab);
}
is(gHits, NUM_USER_CONTEXTS, "should get an image request for each user contexts");
});

View file

@ -0,0 +1,41 @@
"use strict";
const BASE_ORIGIN = "http://example.com";
const URI = BASE_ORIGIN +
"/browser/browser/components/contextualidentity/test/browser/empty_file.html";
add_task(function* () {
info("Opening a new container tab...");
let tab = gBrowser.addTab(URI, { userContextId: 1 });
gBrowser.selectedTab = tab;
let browser = gBrowser.getBrowserForTab(tab);
yield BrowserTestUtils.browserLoaded(browser);
info("Create a HTMLAnchorElement...");
yield ContentTask.spawn(browser, URI,
function(URI) {
let anchor = content.document.createElement("a");
anchor.setAttribute('id', 'clickMe');
anchor.setAttribute("href", URI);
anchor.appendChild(content.document.createTextNode("click me!"));
content.document.body.appendChild(anchor);
}
);
info("Synthesize a mouse click and wait for a new tab...");
let newTab = yield new Promise((resolve, reject) => {
gBrowser.tabContainer.addEventListener("TabOpen", function onTabOpen(openEvent) {
gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen);
resolve(openEvent.target);
})
BrowserTestUtils.synthesizeMouseAtCenter("#clickMe", { button: 1 }, browser);
});
is(newTab.getAttribute("usercontextid"), 1, "Correct UserContextId?");
yield BrowserTestUtils.removeTab(tab);
yield BrowserTestUtils.removeTab(newTab);
});

View file

@ -0,0 +1,35 @@
"use strict";
// Testing that when the user opens the add tab menu and clicks menu items
// the correct context id is opened
add_task(function* test() {
yield SpecialPowers.pushPrefEnv({"set": [
["privacy.userContext.enabled", true]
]});
let newTab = document.getElementById('tabbrowser-tabs');
let newTabButton = document.getAnonymousElementByAttribute(newTab, "anonid", "tabs-newtab-button");
ok(newTabButton, "New tab button exists");
ok(!newTabButton.hidden, "New tab button is visible");
yield BrowserTestUtils.waitForCondition(() => !!document.getAnonymousElementByAttribute(newTab, "anonid", "newtab-popup"), "Wait for popup to exist");
let popup = document.getAnonymousElementByAttribute(newTab, "anonid", "newtab-popup");
for (let i = 1; i <= 4; i++) {
let popupShownPromise = BrowserTestUtils.waitForEvent(popup, "popupshown");
EventUtils.synthesizeMouseAtCenter(newTabButton, {type: "mousedown"});
yield popupShownPromise;
let contextIdItem = popup.querySelector(`menuitem[data-usercontextid="${i}"]`);
ok(contextIdItem, `User context id ${i} exists`);
let waitForTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
EventUtils.synthesizeMouseAtCenter(contextIdItem, {});
let tab = yield waitForTabPromise;
is(tab.getAttribute('usercontextid'), i, `New tab has UCI equal ${i}`);
yield BrowserTestUtils.removeTab(tab);
}
});

View file

@ -0,0 +1,108 @@
let { classes: Cc, interfaces: Ci } = Components;
let swm = Cc["@mozilla.org/serviceworkers/manager;1"].
getService(Ci.nsIServiceWorkerManager);
const BASE_ORIGIN = "https://example.com";
const URI = BASE_ORIGIN +
"/browser/browser/components/contextualidentity/test/browser/serviceworker.html";
const NUM_USER_CONTEXTS = 3;
// opens `uri' in a new tab with the provided userContextId and focuses it.
// returns the newly opened tab
function openTabInUserContext(uri, userContextId) {
// open the tab in the correct userContextId
let tab = gBrowser.addTab(uri, {userContextId});
// select tab and make sure its browser is focused
gBrowser.selectedTab = tab;
tab.ownerGlobal.focus();
return tab;
}
add_task(function* setup() {
// make sure userContext is enabled.
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [
["privacy.userContext.enabled", true],
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.openWindow.enabled", true],
["dom.ipc.processCount", 1]
]}, resolve);
});
});
let infos = [];
add_task(function* test() {
// Open the same URI in multiple user contexts, and make sure we have a
// separate service worker in each of the contexts
for (let userContextId = 0; userContextId < NUM_USER_CONTEXTS; userContextId++) {
// Open a tab in given user contexts
let tab = openTabInUserContext(URI, userContextId);
// wait for tab load
yield BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab));
// remove the tab
gBrowser.removeTab(tab);
}
if (!allRegistered()) {
yield promiseAllRegistered();
}
ok(true, "all service workers are registered");
// Unregistered all service workers added in this test
for (let info of infos) {
yield promiseUnregister(info);
}
});
function allRegistered() {
let results = [];
let registrations = swm.getAllRegistrations();
for (let i = 0; i < registrations.length; i++) {
let info = registrations.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
let principal = info.principal;
if (principal.originNoSuffix === BASE_ORIGIN) {
results[principal.userContextId] = true;
infos[principal.userContextId] = info;
}
}
for (let userContextId = 0; userContextId < NUM_USER_CONTEXTS; userContextId++) {
if (!results[userContextId]) {
return false;
}
}
return true;
}
function promiseAllRegistered() {
return new Promise(function(resolve) {
let listener = {
onRegister: function() {
if (allRegistered()) {
swm.removeListener(listener);
resolve();
}
}
}
swm.addListener(listener);
});
}
function promiseUnregister(info) {
return new Promise(function(resolve) {
swm.unregister(info.principal, {
unregisterSucceeded: function(aState) {
ok(aState, "ServiceWorkerRegistration exists");
resolve();
},
unregisterFailed: function(aState) {
ok(false, "unregister should succeed");
}
}, info.scope);
});
}

View file

@ -0,0 +1,86 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const USER_CONTEXTS = [
"default",
"personal",
"work",
];
const BASE_URI = "http://mochi.test:8888/browser/browser/components/"
+ "contextualidentity/test/browser/file_reflect_cookie_into_title.html";
// opens `uri' in a new tab with the provided userContextId and focuses it.
// returns the newly opened tab
function openTabInUserContext(uri, userContextId) {
// open the tab in the correct userContextId
let tab = gBrowser.addTab(uri, {userContextId});
// select tab and make sure its browser is focused
gBrowser.selectedTab = tab;
tab.ownerGlobal.focus();
return tab;
}
add_task(function* setup() {
// make sure userContext is enabled.
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [
["privacy.userContext.enabled", true],
["dom.ipc.processCount", 1]
]}, resolve);
});
});
add_task(function* test() {
for (let userContextId of Object.keys(USER_CONTEXTS)) {
// load the page in 3 different contexts and set a cookie
// which should only be visible in that context
let cookie = USER_CONTEXTS[userContextId];
// open our tab in the given user context
let tab = openTabInUserContext(BASE_URI+"?"+cookie, userContextId);
// wait for tab load
yield BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab));
// remove the tab
gBrowser.removeTab(tab);
}
{
// Set a cookie in a different context so we can detect if that affects
// cross-context properly. If we don't do that, we get an UNEXPECTED-PASS
// for the localStorage case for the last tab we set.
let tab = openTabInUserContext(BASE_URI+"?foo", 9999);
yield BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab));
gBrowser.removeTab(tab);
}
for (let userContextId of Object.keys(USER_CONTEXTS)) {
// Load the page without setting the cookie this time
let expectedContext = USER_CONTEXTS[userContextId];
let tab = openTabInUserContext(BASE_URI, userContextId);
// wait for load
let browser = gBrowser.getBrowserForTab(tab);
yield BrowserTestUtils.browserLoaded(browser);
// get the title
let title = browser.contentDocument.title.trim().split("|");
// check each item in the title and validate it meets expectatations
for (let part of title) {
let [storageMethodName, value] = part.split("=");
is(value, expectedContext,
"the title reflects the expected contextual identity of " +
expectedContext + " for method " + storageMethodName + ": " + value);
}
gBrowser.removeTab(tab);
}
});

View file

@ -0,0 +1,134 @@
"use strict";
let EventUtils = {};
Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
/**
* Dragging an URL to a tab without userContextId set.
*/
add_task(function* () {
let tab = gBrowser.addTab("http://example.com/");
yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
let awaitDrop = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "drop");
let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "http://test1.example.com/");
// A drop type of "link" onto an existing tab would normally trigger a
// load in that same tab, but tabbrowser code in _getDragTargetTab treats
// drops on the outer edges of a tab differently (loading a new tab
// instead). Make events created by synthesizeDrop have all of their
// coordinates set to 0 (screenX/screenY), so they're treated as drops
// on the outer edge of the tab, thus they open new tabs.
let event = {
clientX: 0,
clientY: 0,
screenX: 0,
screenY: 0,
};
EventUtils.synthesizeDrop(tab, tab, [[{type: "text/plain", data: "http://test1.example.com/"}]], "link", window, undefined, event);
yield awaitDrop;
let tab2 = yield newTabPromise;
Assert.ok(!tab2.hasAttribute("usercontextid"), "Tab shouldn't have usercontextid attribute");
yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
yield ContentTask.spawn(tab2.linkedBrowser, {}, function* () {
Assert.equal(content.document.documentURI, "http://test1.example.com/");
Assert.equal(content.document.nodePrincipal.originAttributes.userContextId, 0);
// referrer is empty when urls are dragged to new or existing tabs.
// If this changes in the future, it would be okay to send the referrer
// in this case because we are creating a new tab with the default
// usercontextid as the original tab.
Assert.equal(content.document.referrer, "", "referrer should be empty");
});
yield BrowserTestUtils.removeTab(tab);
yield BrowserTestUtils.removeTab(tab2);
});
/**
* When dragging an URL to a new tab, the new tab should have the same
* userContextId as the original tab.
*/
add_task(function* () {
let tab = gBrowser.addTab("http://example.com/", {userContextId: 1});
yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
let awaitDrop = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "drop");
let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "http://test1.example.com/");
// A drop type of "link" onto an existing tab would normally trigger a
// load in that same tab, but tabbrowser code in _getDragTargetTab treats
// drops on the outer edges of a tab differently (loading a new tab
// instead). Make events created by synthesizeDrop have all of their
// coordinates set to 0 (screenX/screenY), so they're treated as drops
// on the outer edge of the tab, thus they open new tabs.
let event = {
clientX: 0,
clientY: 0,
screenX: 0,
screenY: 0,
};
EventUtils.synthesizeDrop(tab, tab, [[{type: "text/plain", data: "http://test1.example.com/"}]], "link", window, undefined, event);
yield awaitDrop;
let tab2 = yield newTabPromise;
Assert.equal(tab2.getAttribute("usercontextid"), 1);
yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
yield ContentTask.spawn(tab2.linkedBrowser, {}, function* () {
Assert.equal(content.document.documentURI, "http://test1.example.com/");
Assert.equal(content.document.nodePrincipal.originAttributes.userContextId, 1);
// referrer is empty when urls are dragged to new or existing tabs.
// If this changes in the future, it would be okay to send the referrer
// in this case because we are creating a new tab with the same
// usercontextid as the original tab.
Assert.equal(content.document.referrer, "", "referrer should be empty");
});
yield BrowserTestUtils.removeTab(tab);
yield BrowserTestUtils.removeTab(tab2);
});
/**
* When dragging a URL from one tab or link on a tab to an existing tab, the
* existing tab should not change its userContextId.
* Ex: if you drag a link from tab 1 with userContext 1 to tab 2 with
* userContext 2, the link will open in tab 2 with userContext 2.
*/
add_task(function* () {
let tab = gBrowser.addTab("http://example.com/", {userContextId: 1});
yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
let tab2 = gBrowser.addTab("http://example.org/", {userContextId: 2});
yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
let awaitDrop = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "drop");
EventUtils.synthesizeDrop(tab, tab2, [[{type: "text/plain", data: "http://test1.example.com/"}]], "link", window);
yield awaitDrop;
Assert.equal(tab2.getAttribute("usercontextid"), 2);
yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
yield ContentTask.spawn(tab2.linkedBrowser, {}, function* () {
Assert.equal(content.document.documentURI, "http://test1.example.com/");
Assert.equal(content.document.nodePrincipal.originAttributes.userContextId, 2);
// referrer is empty when urls are dragged to new or existing tabs.
// If this changes in the future, we should ensure that we are not sending
// a referrer for this case! When opening links across user contexts, we
// don't want the referrer to follow the user from one context to another.
Assert.equal(content.document.referrer, "", "referrer should be empty");
});
yield BrowserTestUtils.removeTab(tab);
yield BrowserTestUtils.removeTab(tab2);
});

View file

@ -0,0 +1,74 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const USER_CONTEXTS = [
"default",
"personal",
"work",
];
const BASE_URI = "http://mochi.test:8888/browser/browser/components/"
+ "contextualidentity/test/browser/empty_file.html";
add_task(function* setup() {
// make sure userContext is enabled.
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [
["privacy.userContext.enabled", true],
["browser.link.open_newwindow", 3],
]}, resolve);
});
});
add_task(function* test() {
info("Creating first tab...");
let tab1 = gBrowser.addTab(BASE_URI + '?old', {userContextId: 1});
let browser1 = gBrowser.getBrowserForTab(tab1);
yield BrowserTestUtils.browserLoaded(browser1);
yield ContentTask.spawn(browser1, null, function(opts) {
content.window.name = 'tab-1';
});
info("Creating second tab...");
let tab2 = gBrowser.addTab(BASE_URI + '?old', {userContextId: 2});
let browser2 = gBrowser.getBrowserForTab(tab2);
yield BrowserTestUtils.browserLoaded(browser2);
yield ContentTask.spawn(browser2, null, function(opts) {
content.window.name = 'tab-2';
});
// Let's try to open a window from tab1 with a name 'tab-2'.
info("Opening a window from the first tab...");
yield ContentTask.spawn(browser1, { url: BASE_URI + '?new' }, function* (opts) {
yield (new content.window.wrappedJSObject.Promise(resolve => {
let w = content.window.wrappedJSObject.open(opts.url, 'tab-2');
w.onload = function() { resolve(); }
}));
});
is(browser1.contentTitle, '?old', "Tab1 title must be 'old'");
is(browser1.contentPrincipal.userContextId, 1, "Tab1 UCI must be 1");
is(browser2.contentTitle, '?old', "Tab2 title must be 'old'");
is(browser2.contentPrincipal.userContextId, 2, "Tab2 UCI must be 2");
let found = false;
for (let i = 0; i < gBrowser.tabContainer.childNodes.length; ++i) {
let tab = gBrowser.tabContainer.childNodes[i];
let browser = gBrowser.getBrowserForTab(tab);
if (browser.contentTitle == '?new') {
is(browser.contentPrincipal.userContextId, 1, "Tab3 UCI must be 1");
isnot(browser, browser1, "Tab3 is not browser 1");
isnot(browser, browser2, "Tab3 is not browser 2");
gBrowser.removeTab(tab);
found = true;
break;
}
}
ok(found, "We have tab3");
gBrowser.removeTab(tab1);
gBrowser.removeTab(tab2);
});

View file

@ -0,0 +1,41 @@
"use strict";
// Here we want to test that a new opened window shows the same UI of the
// parent one if this has been loaded from a particular container.
const BASE_URI = "http://mochi.test:8888/browser/browser/components/"
+ "contextualidentity/test/browser/empty_file.html";
add_task(function* setup() {
yield new Promise((resolve) => {
SpecialPowers.pushPrefEnv({"set": [
["privacy.userContext.enabled", true],
["browser.link.open_newwindow", 2],
]}, resolve);
});
});
add_task(function* test() {
info("Creating a tab with UCI = 1...");
let tab = gBrowser.addTab(BASE_URI, {userContextId: 1});
is(tab.getAttribute('usercontextid'), 1, "New tab has UCI equal 1");
let browser = gBrowser.getBrowserForTab(tab);
yield BrowserTestUtils.browserLoaded(browser);
info("Opening a new window from this tab...");
ContentTask.spawn(browser, BASE_URI, function(url) {
content.window.newWindow = content.window.open(url, "_blank");
});
let newWin = yield BrowserTestUtils.waitForNewWindow();
let newTab = newWin.gBrowser.selectedTab;
yield BrowserTestUtils.browserLoaded(newTab.linkedBrowser);
is(newTab.getAttribute('usercontextid'), 1, "New tab has UCI equal 1");
info("Closing the new window and tab...");
yield BrowserTestUtils.closeWindow(newWin);
yield BrowserTestUtils.removeTab(tab);
});

View file

@ -0,0 +1,5 @@
<html><body>
<script>
document.title = window.location.search;
</script>
</body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

View file

@ -0,0 +1,23 @@
<html>
<head>
<meta charset="UTF-8">
<title>title not set</title>
<script>
// if we have a query string, use it to set the cookie and localStorage
if (window.location.search.length > 0) {
let context_name = window.location.search.substr(1);
document.cookie = "userContextId=" + context_name;
localStorage.setItem("userContext", context_name);
}
// get the cookie
let [name, val] = document.cookie.split("=");
// set the title to reflect the cookie and local storage values we find
document.title = "cookie=" + val + "|"
+ "local=" + localStorage.getItem("userContext");
</script>
</head>
<body></body>
</html>

View file

@ -0,0 +1,41 @@
<html>
<head>
<meta charset="UTF-8">
<title>Bug 1238183</title>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
// if we have a query string, use it to set storages
if (window.location.search.length > 0) {
let context_name = window.location.search.substr(1);
localStorage.setItem("userContext", context_name);
sessionStorage.setItem("userContext", context_name);
let request = indexedDB.open("idb", 1);
request.onerror = function() {
throw new Error("error opening db connection");
};
request.onupgradeneeded = event => {
let db = event.target.result;
let store = db.createObjectStore("obj", { keyPath: "id" });
store.createIndex("userContext", "userContext", { unique: false });
};
request.onsuccess = event => {
let db = request.result;
let transaction = db.transaction(["obj"], "readwrite");
let store = transaction.objectStore("obj");
store.add({id: 1, userContext: context_name});
transaction.oncomplete = () => {
db.close();
};
};
}
</script>
</body>
</html>

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script>
navigator.serviceWorker.register("worker.js");
</script>
</head>
<body>
This is a test page.
</body>
<html>

View file

@ -0,0 +1 @@
// empty worker, always succeed!

View file

@ -0,0 +1,189 @@
<!-- 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 id="identity-popup"
type="arrow"
hidden="true"
role="alertdialog"
onpopupshown="gIdentityHandler.onPopupShown(event);"
onpopuphidden="gIdentityHandler.onPopupHidden(event);"
orient="vertical">
<broadcasterset>
<broadcaster id="identity-popup-mcb-learn-more" class="text-link plain" value="&identity.learnMore;"/>
<broadcaster id="identity-popup-insecure-login-forms-learn-more" class="text-link plain" value="&identity.learnMore;"/>
</broadcasterset>
<panelmultiview id="identity-popup-multiView"
mainViewId="identity-popup-mainView">
<panelview id="identity-popup-mainView" flex="1">
<!-- Security Section -->
<hbox id="identity-popup-security" class="identity-popup-section">
<vbox id="identity-popup-security-content" flex="1">
<label class="plain">
<label class="identity-popup-headline host"></label>
<label class="identity-popup-headline hostless" crop="end"/>
</label>
<description class="identity-popup-connection-not-secure"
value="&identity.connectionNotSecure;"
when-connection="not-secure secure-cert-user-overridden"/>
<description class="identity-popup-connection-secure"
value="&identity.connectionSecure;"
when-connection="secure secure-ev"/>
<description value="&identity.connectionInternal;"
when-connection="chrome"/>
<description value="&identity.connectionFile;"
when-connection="file"/>
<vbox id="identity-popup-security-descriptions">
<description class="identity-popup-warning-gray"
when-mixedcontent="active-blocked">&identity.activeBlocked;</description>
<description class="identity-popup-warning-yellow"
when-mixedcontent="passive-loaded">&identity.passiveLoaded;</description>
<description when-mixedcontent="active-loaded">&identity.activeLoaded;</description>
<description class="identity-popup-warning-yellow"
when-ciphers="weak">&identity.weakEncryption;</description>
<description when-loginforms="insecure">&identity.insecureLoginForms2;</description>
</vbox>
</vbox>
<button id="identity-popup-security-expander"
class="identity-popup-expander"
when-connection="not-secure secure secure-ev secure-cert-user-overridden"
oncommand="gIdentityHandler.toggleSubView('security', this)"/>
</hbox>
<!-- Tracking Protection Section -->
<hbox id="tracking-protection-container"
class="identity-popup-section"
when-connection="not-secure secure secure-ev secure-cert-user-overridden file">
<vbox id="tracking-protection-content" flex="1">
<description class="identity-popup-headline"
crop="end"
value="&trackingProtection.title;" />
<label id="tracking-blocked"
crop="end">&trackingProtection.detectedBlocked3;</label>
<label id="tracking-loaded"
crop="end">&trackingProtection.detectedNotBlocked3;</label>
<label id="tracking-not-detected"
crop="end">&trackingProtection.notDetected3;</label>
<button id="tracking-action-unblock"
label="&trackingProtection.unblock.label;"
accesskey="&trackingProtection.unblock.accesskey;"
oncommand="TrackingProtection.disableForCurrentPage();" />
<button id="tracking-action-unblock-private"
label="&trackingProtection.unblockPrivate.label;"
accesskey="&trackingProtection.unblockPrivate.accesskey;"
oncommand="TrackingProtection.disableForCurrentPage();" />
<button id="tracking-action-block"
label="&trackingProtection.block2.label;"
accesskey="&trackingProtection.block2.accesskey;"
oncommand="TrackingProtection.enableForCurrentPage();" />
</vbox>
</hbox>
<!-- Permissions Section -->
<hbox class="identity-popup-section">
<vbox id="identity-popup-permissions-content" flex="1">
<label id="identity-popup-permissions-headline"
class="identity-popup-headline"
value="&identity.permissions;"/>
<vbox id="identity-popup-permission-list"/>
<description id="identity-popup-permission-reload-hint">&identity.permissionsReloadHint;</description>
<description id="identity-popup-permission-empty-hint">&identity.permissionsEmpty;</description>
</vbox>
</hbox>
</panelview>
<!-- Security SubView -->
<panelview id="identity-popup-securityView" flex="1">
<vbox id="identity-popup-securityView-header">
<label class="plain">
<label class="identity-popup-headline host"></label>
<label class="identity-popup-headline hostless" crop="end"/>
</label>
<description class="identity-popup-connection-not-secure"
value="&identity.connectionNotSecure;"
when-connection="not-secure secure-cert-user-overridden"/>
<description class="identity-popup-connection-secure"
value="&identity.connectionSecure;"
when-connection="secure secure-ev"/>
</vbox>
<vbox id="identity-popup-securityView-body" flex="1">
<!-- (EV) Certificate Information -->
<description id="identity-popup-content-verified-by"
when-connection="secure-ev">&identity.connectionVerified2;</description>
<description id="identity-popup-content-owner"
when-connection="secure-ev"
class="header"/>
<description id="identity-popup-content-supplemental"
when-connection="secure-ev"/>
<description id="identity-popup-content-verifier"
when-connection="secure secure-ev secure-cert-user-overridden"/>
<!-- Remove Certificate Exception -->
<button when-connection="secure-cert-user-overridden"
label="&identity.removeCertException.label;"
accesskey="&identity.removeCertException.accesskey;"
oncommand="gIdentityHandler.removeCertException()"/>
<!-- Connection is Not Secure -->
<description when-connection="not-secure"
and-when-loginforms="secure">&identity.description.insecure;</description>
<!-- Insecure login forms -->
<description when-loginforms="insecure">&identity.description.insecureLoginForms; <label observes="identity-popup-insecure-login-forms-learn-more"/></description>
<!-- Weak Cipher -->
<description when-ciphers="weak">&identity.description.weakCipher;</description>
<description class="identity-popup-warning-yellow"
when-ciphers="weak">&identity.description.weakCipher2;</description>
<!-- Active Mixed Content Blocked -->
<description class="identity-popup-warning-gray"
when-mixedcontent="active-blocked">&identity.description.activeBlocked; <label observes="identity-popup-mcb-learn-more"/></description>
<!-- Passive Mixed Content Loaded -->
<description when-mixedcontent="passive-loaded">&identity.description.passiveLoaded;</description>
<description class="identity-popup-warning-yellow"
when-mixedcontent="passive-loaded">&identity.description.passiveLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
<!-- Passive Mixed Content Loaded, Active Mixed Content Blocked -->
<description when-mixedcontent="passive-loaded active-blocked">&identity.description.passiveLoaded;</description>
<description when-mixedcontent="passive-loaded active-blocked"
class="identity-popup-warning-yellow">&identity.description.passiveLoaded3; <label observes="identity-popup-mcb-learn-more"/></description>
<!-- Active Mixed Content Blocking Disabled -->
<description when-mixedcontent="active-loaded"
and-when-loginforms="secure">&identity.description.activeLoaded;</description>
<description when-mixedcontent="active-loaded"
and-when-loginforms="secure">&identity.description.activeLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
<!-- Show only the first message when there are insecure login forms,
and make sure the Learn More link is included. -->
<description when-mixedcontent="active-loaded"
and-when-loginforms="insecure">&identity.description.activeLoaded; <label observes="identity-popup-mcb-learn-more"/></description>
<!-- Buttons to enable/disable mixed content blocking. -->
<button when-mixedcontent="active-blocked"
label="&identity.disableMixedContentBlocking.label;"
accesskey="&identity.disableMixedContentBlocking.accesskey;"
oncommand="gIdentityHandler.disableMixedContentProtection()"/>
<button when-mixedcontent="active-loaded"
label="&identity.enableMixedContentBlocking.label;"
accesskey="&identity.enableMixedContentBlocking.accesskey;"
oncommand="gIdentityHandler.enableMixedContentProtection()"/>
</vbox>
<vbox id="identity-popup-securityView-footer">
<!-- More Security Information -->
<button label="&identity.moreInfoLinkText2;"
oncommand="gIdentityHandler.handleMoreInfoClick(event);"/>
</vbox>
</panelview>
</panelmultiview>
</panel>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,420 @@
/* 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/. */
"use strict";
Components.utils.import("resource:///modules/CustomizableUI.jsm");
var gManagers = new WeakMap();
const kPaletteId = "customization-palette";
const kPlaceholderClass = "panel-customization-placeholder";
this.EXPORTED_SYMBOLS = ["DragPositionManager"];
function AreaPositionManager(aContainer) {
// Caching the direction and bounds of the container for quick access later:
let window = aContainer.ownerGlobal;
this._dir = window.getComputedStyle(aContainer).direction;
let containerRect = aContainer.getBoundingClientRect();
this._containerInfo = {
left: containerRect.left,
right: containerRect.right,
top: containerRect.top,
width: containerRect.width
};
this._inPanel = aContainer.id == CustomizableUI.AREA_PANEL;
this._horizontalDistance = null;
this.update(aContainer);
}
AreaPositionManager.prototype = {
_nodePositionStore: null,
_wideCache: null,
update: function(aContainer) {
this._nodePositionStore = new WeakMap();
this._wideCache = new Set();
let last = null;
let singleItemHeight;
for (let child of aContainer.children) {
if (child.hidden) {
continue;
}
let isNodeWide = this._checkIfWide(child);
if (isNodeWide) {
this._wideCache.add(child.id);
}
let coordinates = this._lazyStoreGet(child);
// We keep a baseline horizontal distance between non-wide nodes around
// for use when we can't compare with previous/next nodes
if (!this._horizontalDistance && last && !isNodeWide) {
this._horizontalDistance = coordinates.left - last.left;
}
// We also keep the basic height of non-wide items for use below:
if (!isNodeWide && !singleItemHeight) {
singleItemHeight = coordinates.height;
}
last = !isNodeWide ? coordinates : null;
}
if (this._inPanel) {
this._heightToWidthFactor = CustomizableUI.PANEL_COLUMN_COUNT;
} else {
this._heightToWidthFactor = this._containerInfo.width / singleItemHeight;
}
},
/**
* Find the closest node in the container given the coordinates.
* "Closest" is defined in a somewhat strange manner: we prefer nodes
* which are in the same row over nodes that are in a different row.
* In order to implement this, we use a weighted cartesian distance
* where dy is more heavily weighted by a factor corresponding to the
* ratio between the container's width and the height of its elements.
*/
find: function(aContainer, aX, aY, aDraggedItemId) {
let closest = null;
let minCartesian = Number.MAX_VALUE;
let containerX = this._containerInfo.left;
let containerY = this._containerInfo.top;
for (let node of aContainer.children) {
let coordinates = this._lazyStoreGet(node);
let offsetX = coordinates.x - containerX;
let offsetY = coordinates.y - containerY;
let hDiff = offsetX - aX;
let vDiff = offsetY - aY;
// For wide widgets, we're always going to be further from the center
// horizontally. Compensate:
if (this.isWide(node)) {
hDiff /= CustomizableUI.PANEL_COLUMN_COUNT;
}
// Then compensate for the height/width ratio so that we prefer items
// which are in the same row:
hDiff /= this._heightToWidthFactor;
let cartesianDiff = hDiff * hDiff + vDiff * vDiff;
if (cartesianDiff < minCartesian) {
minCartesian = cartesianDiff;
closest = node;
}
}
// Now correct this node based on what we're dragging
if (closest) {
let doc = aContainer.ownerDocument;
let draggedItem = doc.getElementById(aDraggedItemId);
// If dragging a wide item, always pick the first item in a row:
if (this._inPanel && draggedItem &&
draggedItem.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
return this._firstInRow(closest);
}
let targetBounds = this._lazyStoreGet(closest);
let farSide = this._dir == "ltr" ? "right" : "left";
let outsideX = targetBounds[farSide];
// Check if we're closer to the next target than to this one:
// Only move if we're not targeting a node in a different row:
if (aY > targetBounds.top && aY < targetBounds.bottom) {
if ((this._dir == "ltr" && aX > outsideX) ||
(this._dir == "rtl" && aX < outsideX)) {
return closest.nextSibling || aContainer;
}
}
}
return closest;
},
/**
* "Insert" a "placeholder" by shifting the subsequent children out of the
* way. We go through all the children, and shift them based on the position
* they would have if we had inserted something before aBefore. We use CSS
* transforms for this, which are CSS transitioned.
*/
insertPlaceholder: function(aContainer, aBefore, aWide, aSize, aIsFromThisArea) {
let isShifted = false;
let shiftDown = aWide;
for (let child of aContainer.children) {
// Don't need to shift hidden nodes:
if (child.getAttribute("hidden") == "true") {
continue;
}
// If this is the node before which we're inserting, start shifting
// everything that comes after. One exception is inserting at the end
// of the menupanel, in which case we do not shift the placeholders:
if (child == aBefore && !child.classList.contains(kPlaceholderClass)) {
isShifted = true;
// If the node before which we're inserting is wide, we should
// shift everything one row down:
if (!shiftDown && this.isWide(child)) {
shiftDown = true;
}
}
// If we're moving items before a wide node that were already there,
// it's possible it's not necessary to shift nodes
// including & after the wide node.
if (this.__undoShift) {
isShifted = false;
}
if (isShifted) {
// Conversely, if we're adding something before a wide node, for
// simplicity's sake we move everything including the wide node down:
if (this.__moveDown) {
shiftDown = true;
}
if (aIsFromThisArea && !this._lastPlaceholderInsertion) {
child.setAttribute("notransition", "true");
}
// Determine the CSS transform based on the next node:
child.style.transform = this._getNextPos(child, shiftDown, aSize);
} else {
// If we're not shifting this node, reset the transform
child.style.transform = "";
}
}
if (aContainer.lastChild && aIsFromThisArea &&
!this._lastPlaceholderInsertion) {
// Flush layout:
aContainer.lastChild.getBoundingClientRect();
// then remove all the [notransition]
for (let child of aContainer.children) {
child.removeAttribute("notransition");
}
}
delete this.__moveDown;
delete this.__undoShift;
this._lastPlaceholderInsertion = aBefore;
},
isWide: function(aNode) {
return this._wideCache.has(aNode.id);
},
_checkIfWide: function(aNode) {
return this._inPanel && aNode && aNode.firstChild &&
aNode.firstChild.classList.contains(CustomizableUI.WIDE_PANEL_CLASS);
},
/**
* Reset all the transforms in this container, optionally without
* transitioning them.
* @param aContainer the container in which to reset transforms
* @param aNoTransition if truthy, adds a notransition attribute to the node
* while resetting the transform.
*/
clearPlaceholders: function(aContainer, aNoTransition) {
for (let child of aContainer.children) {
if (aNoTransition) {
child.setAttribute("notransition", true);
}
child.style.transform = "";
if (aNoTransition) {
// Need to force a reflow otherwise this won't work.
child.getBoundingClientRect();
child.removeAttribute("notransition");
}
}
// We snapped back, so we can assume there's no more
// "last" placeholder insertion point to keep track of.
if (aNoTransition) {
this._lastPlaceholderInsertion = null;
}
},
_getNextPos: function(aNode, aShiftDown, aSize) {
// Shifting down is easy:
if (this._inPanel && aShiftDown) {
return "translate(0, " + aSize.height + "px)";
}
return this._diffWithNext(aNode, aSize);
},
_diffWithNext: function(aNode, aSize) {
let xDiff;
let yDiff = null;
let nodeBounds = this._lazyStoreGet(aNode);
let side = this._dir == "ltr" ? "left" : "right";
let next = this._getVisibleSiblingForDirection(aNode, "next");
// First we determine the transform along the x axis.
// Usually, there will be a next node to base this on:
if (next) {
let otherBounds = this._lazyStoreGet(next);
xDiff = otherBounds[side] - nodeBounds[side];
// If the next node is a wide item in the panel, check if we could maybe
// just move further out in the same row, without snapping to the next
// one. This happens, for example, if moving an item that's before a wide
// node within its own row of items. There will be space to drop this
// item within the row, and the rest of the items do not need to shift.
if (this.isWide(next)) {
let otherXDiff = this._moveNextBasedOnPrevious(aNode, nodeBounds,
this._firstInRow(aNode));
// If this has the same sign as our original shift, we're still
// snapping to the start of the row. In this case, we should move
// everything after us a row down, so as not to display two nodes on
// top of each other:
// (we would be able to get away with checking for equality instead of
// equal signs here, but one of these is based on the x coordinate of
// the first item in row N and one on that for row N - 1, so this is
// safer, as their margins might differ)
if ((otherXDiff < 0) == (xDiff < 0)) {
this.__moveDown = true;
} else {
// Otherwise, we succeeded and can move further out. This also means
// we can stop shifting the rest of the content:
xDiff = otherXDiff;
this.__undoShift = true;
}
} else {
// We set this explicitly because otherwise some strange difference
// between the height and the actual difference between line creeps in
// and messes with alignments
yDiff = otherBounds.top - nodeBounds.top;
}
} else {
// We don't have a sibling whose position we can use. First, let's see
// if we're also the first item (which complicates things):
let firstNode = this._firstInRow(aNode);
if (aNode == firstNode) {
// Maybe we stored the horizontal distance between non-wide nodes,
// if not, we'll use the width of the incoming node as a proxy:
xDiff = this._horizontalDistance || aSize.width;
} else {
// If not, we should be able to get the distance to the previous node
// and use the inverse, unless there's no room for another node (ie we
// are the last node and there's no room for another one)
xDiff = this._moveNextBasedOnPrevious(aNode, nodeBounds, firstNode);
}
}
// If we've not determined the vertical difference yet, check it here
if (yDiff === null) {
// If the next node is behind rather than in front, we must have moved
// vertically:
if ((xDiff > 0 && this._dir == "rtl") || (xDiff < 0 && this._dir == "ltr")) {
yDiff = aSize.height;
} else {
// Otherwise, we haven't
yDiff = 0;
}
}
return "translate(" + xDiff + "px, " + yDiff + "px)";
},
/**
* Helper function to find the transform a node if there isn't a next node
* to base that on.
* @param aNode the node to transform
* @param aNodeBounds the bounding rect info of this node
* @param aFirstNodeInRow the first node in aNode's row
*/
_moveNextBasedOnPrevious: function(aNode, aNodeBounds, aFirstNodeInRow) {
let next = this._getVisibleSiblingForDirection(aNode, "previous");
let otherBounds = this._lazyStoreGet(next);
let side = this._dir == "ltr" ? "left" : "right";
let xDiff = aNodeBounds[side] - otherBounds[side];
// If, however, this means we move outside the container's box
// (i.e. the row in which this item is placed is full)
// we should move it to align with the first item in the next row instead
let bound = this._containerInfo[this._dir == "ltr" ? "right" : "left"];
if ((this._dir == "ltr" && xDiff + aNodeBounds.right > bound) ||
(this._dir == "rtl" && xDiff + aNodeBounds.left < bound)) {
xDiff = this._lazyStoreGet(aFirstNodeInRow)[side] - aNodeBounds[side];
}
return xDiff;
},
/**
* Get position details from our cache. If the node is not yet cached, get its position
* information and cache it now.
* @param aNode the node whose position info we want
* @return the position info
*/
_lazyStoreGet: function(aNode) {
let rect = this._nodePositionStore.get(aNode);
if (!rect) {
// getBoundingClientRect() returns a DOMRect that is live, meaning that
// as the element moves around, the rects values change. We don't want
// that - we want a snapshot of what the rect values are right at this
// moment, and nothing else. So we have to clone the values.
let clientRect = aNode.getBoundingClientRect();
rect = {
left: clientRect.left,
right: clientRect.right,
width: clientRect.width,
height: clientRect.height,
top: clientRect.top,
bottom: clientRect.bottom,
};
rect.x = rect.left + rect.width / 2;
rect.y = rect.top + rect.height / 2;
Object.freeze(rect);
this._nodePositionStore.set(aNode, rect);
}
return rect;
},
_firstInRow: function(aNode) {
// XXXmconley: I'm not entirely sure why we need to take the floor of these
// values - it looks like, periodically, we're getting fractional pixels back
// from lazyStoreGet. I've filed bug 994247 to investigate.
let bound = Math.floor(this._lazyStoreGet(aNode).top);
let rv = aNode;
let prev;
while (rv && (prev = this._getVisibleSiblingForDirection(rv, "previous"))) {
if (Math.floor(this._lazyStoreGet(prev).bottom) <= bound) {
return rv;
}
rv = prev;
}
return rv;
},
_getVisibleSiblingForDirection: function(aNode, aDirection) {
let rv = aNode;
do {
rv = rv[aDirection + "Sibling"];
} while (rv && rv.getAttribute("hidden") == "true")
return rv;
}
}
var DragPositionManager = {
start: function(aWindow) {
let areas = CustomizableUI.areas.filter((area) => CustomizableUI.getAreaType(area) != "toolbar");
areas = areas.map((area) => CustomizableUI.getCustomizeTargetForArea(area, aWindow));
areas.push(aWindow.document.getElementById(kPaletteId));
for (let areaNode of areas) {
let positionManager = gManagers.get(areaNode);
if (positionManager) {
positionManager.update(areaNode);
} else {
gManagers.set(areaNode, new AreaPositionManager(areaNode));
}
}
},
add: function(aWindow, aArea, aContainer) {
if (CustomizableUI.getAreaType(aArea) != "toolbar") {
return;
}
gManagers.set(aContainer, new AreaPositionManager(aContainer));
},
remove: function(aWindow, aArea, aContainer) {
if (CustomizableUI.getAreaType(aArea) != "toolbar") {
return;
}
gManagers.delete(aContainer);
},
stop: function() {
gManagers = new WeakMap();
},
getManagerForArea: function(aArea) {
return gManagers.get(aArea);
}
};
Object.freeze(DragPositionManager);

View file

@ -0,0 +1,172 @@
/* 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/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
this.EXPORTED_SYMBOLS = ["PanelWideWidgetTracker"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
var gPanel = CustomizableUI.AREA_PANEL;
// We keep track of the widget placements for the panel locally:
var gPanelPlacements = [];
// All the wide widgets we know of:
var gWideWidgets = new Set();
// All the widgets we know of:
var gSeenWidgets = new Set();
var PanelWideWidgetTracker = {
// Listeners used to validate panel contents whenever they change:
onWidgetAdded: function(aWidgetId, aArea, aPosition) {
if (aArea == gPanel) {
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
let moveForward = this.shouldMoveForward(aWidgetId, aPosition);
this.adjustWidgets(aWidgetId, moveForward);
}
},
onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
if (aArea == gPanel) {
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
let moveForward = this.shouldMoveForward(aWidgetId, aNewPosition);
this.adjustWidgets(aWidgetId, moveForward);
}
},
onWidgetRemoved: function(aWidgetId, aPrevArea) {
if (aPrevArea == gPanel) {
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
this.adjustWidgets(aWidgetId, false);
}
},
onWidgetReset: function(aWidgetId) {
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
},
// Listener to keep abreast of any new nodes. We use the DOM one because
// we need access to the actual node's classlist, so we can't use the ones above.
// Furthermore, onWidgetCreated only fires for API-based widgets, not for XUL ones.
onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer) {
if (!gSeenWidgets.has(aNode.id)) {
if (aNode.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
gWideWidgets.add(aNode.id);
}
gSeenWidgets.add(aNode.id);
}
},
// When widgets get destroyed, we remove them from our sets of stuff we care about:
onWidgetDestroyed: function(aWidgetId) {
gSeenWidgets.delete(aWidgetId);
gWideWidgets.delete(aWidgetId);
},
shouldMoveForward: function(aWidgetId, aPosition) {
let currentWidgetAtPosition = gPanelPlacements[aPosition + 1];
let rv = gWideWidgets.has(currentWidgetAtPosition) && !gWideWidgets.has(aWidgetId);
// We might now think we can move forward, but for that we need at least 2 more small
// widgets to be present:
if (rv) {
let furtherWidgets = gPanelPlacements.slice(aPosition + 2);
let realWidgets = 0;
if (furtherWidgets.length >= 2) {
while (furtherWidgets.length && realWidgets < 2) {
let w = furtherWidgets.shift();
if (!gWideWidgets.has(w) && this.checkWidgetStatus(w)) {
realWidgets++;
}
}
}
if (realWidgets < 2) {
rv = false;
}
}
return rv;
},
adjustWidgets: function(aWidgetId, aMoveForwards) {
if (this.adjusting) {
return;
}
this.adjusting = true;
let widgetsAffected = gPanelPlacements.filter((w) => gWideWidgets.has(w));
// If we're moving the wide widgets forwards (down/to the right in the panel)
// we want to start with the last widgets. Otherwise we move widgets over other wide
// widgets, which might mess up their order. Likewise, if moving backwards we should start with
// the first widget and work our way down/right from there.
let compareFn = aMoveForwards ? ((a, b) => a < b) : ((a, b) => a > b);
widgetsAffected.sort((a, b) => compareFn(gPanelPlacements.indexOf(a),
gPanelPlacements.indexOf(b)));
for (let widget of widgetsAffected) {
this.adjustPosition(widget, aMoveForwards);
}
this.adjusting = false;
},
// This function is called whenever an item gets moved in the menu panel. It
// adjusts the position of widgets within the panel to prevent "gaps" between
// wide widgets that could be filled up with single column widgets
adjustPosition: function(aWidgetId, aMoveForwards) {
// Make sure that there are n % columns = 0 narrow buttons before the widget.
let placementIndex = gPanelPlacements.indexOf(aWidgetId);
let prevSiblingCount = 0;
let fixedPos = null;
while (placementIndex--) {
let thisWidgetId = gPanelPlacements[placementIndex];
if (gWideWidgets.has(thisWidgetId)) {
continue;
}
let widgetStatus = this.checkWidgetStatus(thisWidgetId);
if (!widgetStatus) {
continue;
}
if (widgetStatus == "public-only") {
fixedPos = !fixedPos ? placementIndex : Math.min(fixedPos, placementIndex);
prevSiblingCount = 0;
} else {
prevSiblingCount++;
}
}
if (fixedPos !== null || prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT) {
let desiredPos = (fixedPos !== null) ? fixedPos : gPanelPlacements.indexOf(aWidgetId);
let desiredChange = -(prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT);
if (aMoveForwards && fixedPos == null) {
// +1 because otherwise we'd count ourselves:
desiredChange = CustomizableUI.PANEL_COLUMN_COUNT + desiredChange + 1;
}
desiredPos += desiredChange;
CustomizableUI.moveWidgetWithinArea(aWidgetId, desiredPos);
}
},
/*
* Check whether a widget id is actually known anywhere.
* @returns false if the widget doesn't exist,
* "public-only" if it's not shown in private windows
* "real" if it does exist and is shown even in private windows
*/
checkWidgetStatus: function(aWidgetId) {
let widgetWrapper = CustomizableUI.getWidget(aWidgetId);
// This widget might not actually exist:
if (!widgetWrapper) {
return false;
}
// This widget might still not actually exist:
if (widgetWrapper.provider == CustomizableUI.PROVIDER_XUL &&
widgetWrapper.instances.length == 0) {
return false;
}
// Or it might only be there some of the time:
if (widgetWrapper.provider == CustomizableUI.PROVIDER_API &&
widgetWrapper.showInPrivateBrowsing === false) {
return "public-only";
}
return "real";
},
init: function() {
// Initialize our local placements copy and register the listener
gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
CustomizableUI.addListener(this);
},
};

View file

@ -0,0 +1,65 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["ScrollbarSampler"];
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
var gSystemScrollbarWidth = null;
this.ScrollbarSampler = {
getSystemScrollbarWidth: function() {
if (gSystemScrollbarWidth !== null) {
return Promise.resolve(gSystemScrollbarWidth);
}
return new Promise(resolve => {
this._sampleSystemScrollbarWidth().then(function(systemScrollbarWidth) {
gSystemScrollbarWidth = systemScrollbarWidth;
resolve(gSystemScrollbarWidth);
});
});
},
resetSystemScrollbarWidth: function() {
gSystemScrollbarWidth = null;
},
_sampleSystemScrollbarWidth: function() {
let hwin = Services.appShell.hiddenDOMWindow;
let hdoc = hwin.document.documentElement;
let iframe = hwin.document.createElementNS("http://www.w3.org/1999/xhtml",
"html:iframe");
iframe.setAttribute("srcdoc", '<body style="overflow-y: scroll"></body>');
hdoc.appendChild(iframe);
let cwindow = iframe.contentWindow;
let utils = cwindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
return new Promise(resolve => {
cwindow.addEventListener("load", function onLoad(aEvent) {
cwindow.removeEventListener("load", onLoad);
let sbWidth = {};
try {
utils.getScrollbarSize(true, sbWidth, {});
} catch (e) {
Cu.reportError("Could not sample scrollbar size: " + e + " -- " +
e.stack);
sbWidth.value = 0;
}
// Minimum width of 10 so that we have enough padding:
sbWidth.value = Math.max(sbWidth.value, 10);
resolve(sbWidth.value);
iframe.remove();
});
});
}
};
Object.freeze(this.ScrollbarSampler);

View file

@ -0,0 +1,82 @@
<!-- 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/. -->
<hbox id="customization-container" flex="1" hidden="true">
<vbox flex="1" id="customization-palette-container">
<label id="customization-header">
&customizeMode.menuAndToolbars.header2;
</label>
<hbox id="customization-empty" hidden="true">
<label>&customizeMode.menuAndToolbars.empty;</label>
<label onclick="BrowserOpenAddonsMgr('addons://discover/');"
onkeypress="BrowserOpenAddonsMgr('addons://discover/');"
id="customization-more-tools"
class="text-link">
&customizeMode.menuAndToolbars.emptyLink;
</label>
</hbox>
<vbox id="customization-palette" class="customization-palette"/>
<spacer id="customization-spacer"/>
<hbox id="customization-footer">
#ifdef CAN_DRAW_IN_TITLEBAR
<button id="customization-titlebar-visibility-button" class="customizationmode-button"
label="&customizeMode.titlebar;" type="checkbox"
#NB: because oncommand fires after click, by the time we've fired, the checkbox binding
# will already have switched the button's state, so this is correct:
oncommand="gCustomizeMode.toggleTitlebar(this.hasAttribute('checked'))"/>
#endif
<button id="customization-toolbar-visibility-button" label="&customizeMode.toolbars;" class="customizationmode-button" type="menu">
<menupopup id="customization-toolbar-menu" onpopupshowing="onViewToolbarsPopupShowing(event)"/>
</button>
<button id="customization-lwtheme-button" label="&customizeMode.lwthemes;" class="customizationmode-button" type="menu">
<panel type="arrow" id="customization-lwtheme-menu"
onpopupshowing="gCustomizeMode.onLWThemesMenuShowing(event);"
position="topcenter bottomleft"
flip="none"
role="menu">
<label id="customization-lwtheme-menu-header" value="&customizeMode.lwthemes.myThemes;"/>
<label id="customization-lwtheme-menu-recommended" value="&customizeMode.lwthemes.recommended;"/>
<hbox id="customization-lwtheme-menu-footer">
<toolbarbutton class="customization-lwtheme-menu-footeritem"
label="&customizeMode.lwthemes.menuManage;"
accesskey="&customizeMode.lwthemes.menuManage.accessKey;"
tabindex="0"
oncommand="gCustomizeMode.openAddonsManagerThemes(event);"/>
<toolbarbutton class="customization-lwtheme-menu-footeritem"
label="&customizeMode.lwthemes.menuGetMore;"
accesskey="&customizeMode.lwthemes.menuGetMore.accessKey;"
tabindex="0"
oncommand="gCustomizeMode.getMoreThemes(event);"/>
</hbox>
</panel>
</button>
<spacer id="customization-footer-spacer"/>
<button id="customization-undo-reset-button"
class="customizationmode-button"
hidden="true"
oncommand="gCustomizeMode.undoReset();"
label="&undoCmd.label;"/>
<button id="customization-reset-button"
oncommand="gCustomizeMode.reset();"
label="&customizeMode.restoreDefaults;"
class="customizationmode-button"/>
</hbox>
</vbox>
<vbox id="customization-panel-container">
<vbox id="customization-panelWrapper">
<html:style html:type="text/html" scoped="scoped">
@import url(chrome://global/skin/popup.css);
</html:style>
<box class="panel-arrowbox">
<box flex="1"/>
<image class="panel-arrow" side="top"/>
</box>
<box class="panel-arrowcontent" side="top" flex="1">
<hbox id="customization-panelHolder"/>
<box class="panel-inner-arrowcontentfooter" hidden="true"/>
</box>
</vbox>
</vbox>
</hbox>

View file

@ -0,0 +1,10 @@
# 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/.
browser.jar:
content/browser/customizableui/panelUI.css
content/browser/customizableui/panelUI.js
content/browser/customizableui/panelUI.xml
content/browser/customizableui/toolbar.xml

View file

@ -0,0 +1,7 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
JAR_MANIFESTS += ['jar.mn']

View file

@ -0,0 +1,31 @@
/* 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-viewstack[viewtype="main"] > .panel-clickcapturer {
pointer-events: none;
}
.panel-mainview,
.panel-viewcontainer,
.panel-viewstack {
overflow: hidden;
}
.panel-viewstack {
position: relative;
}
.panel-subviews {
-moz-stack-sizing: ignore;
transform: translateX(0);
overflow-y: auto;
}
.panel-subviews[panelopen] {
transition: transform var(--panelui-subview-transition-duration);
}
.panel-viewcontainer[panelopen]:-moz-any(:not([viewtype="main"]),[transitioning="true"]) {
transition: height var(--panelui-subview-transition-duration);
}

View file

@ -0,0 +1,407 @@
<!-- 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 id="PanelUI-popup"
role="group"
type="arrow"
hidden="true"
flip="slide"
position="bottomcenter topright"
noautofocus="true">
<panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">
<panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
<vbox id="PanelUI-contents-scroller">
<vbox id="PanelUI-contents" class="panelUI-grid"/>
</vbox>
<footer id="PanelUI-footer">
<toolbarbutton id="PanelUI-update-status"
oncommand="gMenuButtonUpdateBadge.onMenuPanelCommand(event);"
wrap="true"
hidden="true"/>
<hbox id="PanelUI-footer-fxa">
<hbox id="PanelUI-fxa-status"
defaultlabel="&fxaSignIn.label;"
signedinTooltiptext="&fxaSignedIn.tooltip;"
tooltiptext="&fxaSignedIn.tooltip;"
errorlabel="&fxaSignInError.label;"
unverifiedlabel="&fxaUnverified.label;"
onclick="if (event.which == 1) gFxAccounts.onMenuPanelCommand();">
<image id="PanelUI-fxa-avatar"/>
<toolbarbutton id="PanelUI-fxa-label"
fxabrandname="&syncBrand.fxAccount.label;"/>
</hbox>
<toolbarseparator/>
<toolbarbutton id="PanelUI-fxa-icon"
oncommand="gSyncUI.doSync();"
closemenu="none">
<observes element="sync-status" attribute="syncstatus"/>
<observes element="sync-status" attribute="tooltiptext"/>
</toolbarbutton>
</hbox>
<hbox id="PanelUI-footer-inner">
<toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
exitLabel="&appMenuCustomizeExit.label;"
tooltiptext="&appMenuCustomize.tooltip;"
exitTooltiptext="&appMenuCustomizeExit.tooltip;"
closemenu="none"
oncommand="gCustomizeMode.toggle();"/>
<toolbarseparator/>
<toolbarbutton id="PanelUI-help" label="&helpMenu.label;"
closemenu="none"
tooltiptext="&appMenuHelp.tooltip;"
oncommand="PanelUI.showHelpView(this);"/>
<toolbarseparator/>
<toolbarbutton id="PanelUI-quit"
#ifdef XP_WIN
label="&quitApplicationCmdWin2.label;"
tooltiptext="&quitApplicationCmdWin2.tooltip;"
#else
#ifdef XP_MACOSX
label="&quitApplicationCmdMac2.label;"
#else
label="&quitApplicationCmd.label;"
#endif
#endif
command="cmd_quitApplication"/>
</hbox>
</footer>
</panelview>
<panelview id="PanelUI-history" flex="1">
<label value="&appMenuHistory.label;" class="panel-subview-header"/>
<vbox class="panel-subview-body">
<toolbarbutton id="appMenuViewHistorySidebar"
label="&appMenuHistory.viewSidebar.label;"
type="checkbox"
class="subviewbutton"
key="key_gotoHistory"
oncommand="SidebarUI.toggle('viewHistorySidebar'); PanelUI.hide();">
<observes element="viewHistorySidebar" attribute="checked"/>
</toolbarbutton>
<toolbarbutton id="appMenuClearRecentHistory"
label="&appMenuHistory.clearRecent.label;"
class="subviewbutton"
command="Tools:Sanitize"/>
<toolbarbutton id="appMenuRestoreLastSession"
label="&appMenuHistory.restoreSession.label;"
class="subviewbutton"
command="Browser:RestoreLastSession"/>
<menuseparator id="PanelUI-recentlyClosedTabs-separator"/>
<vbox id="PanelUI-recentlyClosedTabs" tooltip="bhTooltip"/>
<menuseparator id="PanelUI-recentlyClosedWindows-separator"/>
<vbox id="PanelUI-recentlyClosedWindows" tooltip="bhTooltip"/>
<menuseparator id="PanelUI-historyItems-separator"/>
<vbox id="PanelUI-historyItems" tooltip="bhTooltip"/>
</vbox>
<toolbarbutton id="PanelUI-historyMore"
class="panel-subview-footer subviewbutton"
label="&appMenuHistory.showAll.label;"
oncommand="PlacesCommandHook.showPlacesOrganizer('History'); CustomizableUI.hidePanelForNode(this);"/>
</panelview>
<panelview id="PanelUI-remotetabs" flex="1" class="PanelUI-subView">
<label value="&appMenuRemoteTabs.label;" class="panel-subview-header"/>
<vbox class="panel-subview-body">
<!-- this widget has 3 boxes in the body, but only 1 is ever visible -->
<!-- When Sync is ready to sync -->
<vbox id="PanelUI-remotetabs-main" observes="sync-syncnow-state">
<vbox id="PanelUI-remotetabs-buttons">
<toolbarbutton id="PanelUI-remotetabs-view-sidebar"
class="subviewbutton"
observes="viewTabsSidebar"
label="&appMenuRemoteTabs.sidebar.label;"/>
<toolbarbutton id="PanelUI-remotetabs-syncnow"
observes="sync-status"
class="subviewbutton"
oncommand="gSyncUI.doSync();"
closemenu="none"/>
<menuseparator id="PanelUI-remotetabs-separator"/>
</vbox>
<deck id="PanelUI-remotetabs-deck">
<!-- Sync is ready to Sync and the "tabs" engine is enabled -->
<vbox id="PanelUI-remotetabs-tabspane">
<vbox id="PanelUI-remotetabs-tabslist"
notabsforclientlabel="&appMenuRemoteTabs.notabs.label;"
/>
</vbox>
<!-- Sync is ready to Sync but the "tabs" engine isn't enabled-->
<hbox id="PanelUI-remotetabs-tabsdisabledpane" pack="center" flex="1">
<vbox class="PanelUI-remotetabs-instruction-box">
<hbox pack="center">
<image class="fxaSyncIllustration" alt=""/>
</hbox>
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.tabsnotsyncing.label;</label>
<hbox pack="center">
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
label="&appMenuRemoteTabs.openprefs.label;"
oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
</hbox>
</vbox>
</hbox>
<!-- Sync is ready to Sync but we are still fetching the tabs to show -->
<vbox id="PanelUI-remotetabs-fetching">
<!-- Show intentionally blank panel, see bug 1239845 -->
</vbox>
<!-- Sync has only 1 (ie, this) device connected -->
<hbox id="PanelUI-remotetabs-nodevicespane" pack="center" flex="1">
<vbox class="PanelUI-remotetabs-instruction-box">
<hbox pack="center">
<image class="fxaSyncIllustration" alt=""/>
</hbox>
<label class="PanelUI-remotetabs-instruction-title">&appMenuRemoteTabs.noclients.title;</label>
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.noclients.subtitle;</label>
<!-- The inner HTML for PanelUI-remotetabs-mobile-promo is built at runtime -->
<label id="PanelUI-remotetabs-mobile-promo" fxAccountsBrand="&syncBrand.fxAccount.label;"/>
</vbox>
</hbox>
</deck>
</vbox>
<!-- a box to ensure contained boxes are centered horizonally -->
<hbox pack="center" flex="1">
<!-- When Sync is not configured -->
<vbox id="PanelUI-remotetabs-setupsync"
flex="1"
align="center"
class="PanelUI-remotetabs-instruction-box"
observes="sync-setup-state">
<image class="fxaSyncIllustration" alt=""/>
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
label="&appMenuRemoteTabs.signin.label;"
oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
</vbox>
<!-- When Sync needs re-authentication. This uses the exact same messaging
as "Sync is not configured" but remains a separate box so we get
the goodness of observing broadcasters to manage the hidden states -->
<vbox id="PanelUI-remotetabs-reauthsync"
flex="1"
align="center"
class="PanelUI-remotetabs-instruction-box"
observes="sync-reauth-state">
<image class="fxaSyncIllustration" alt=""/>
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
label="&appMenuRemoteTabs.signin.label;"
oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
</vbox>
</hbox>
</vbox>
</panelview>
<panelview id="PanelUI-bookmarks" flex="1" class="PanelUI-subView">
<label value="&bookmarksMenu.label;" class="panel-subview-header"/>
<vbox class="panel-subview-body">
<toolbarbutton id="panelMenuBookmarkThisPage"
class="subviewbutton"
observes="bookmarkThisPageBroadcaster"
command="Browser:AddBookmarkAs"
onclick="PanelUI.hide();"/>
<toolbarseparator/>
<toolbarbutton id="panelMenu_viewBookmarksSidebar"
label="&viewBookmarksSidebar2.label;"
class="subviewbutton"
key="viewBookmarksSidebarKb"
oncommand="SidebarUI.toggle('viewBookmarksSidebar'); PanelUI.hide();">
<observes element="viewBookmarksSidebar" attribute="checked"/>
</toolbarbutton>
<toolbarbutton id="panelMenu_viewBookmarksToolbar"
label="&viewBookmarksToolbar.label;"
type="checkbox"
toolbarId="PersonalToolbar"
class="subviewbutton"
oncommand="onViewToolbarCommand(event); PanelUI.hide();"/>
<toolbarseparator/>
<toolbarbutton id="panelMenu_bookmarksToolbar"
label="&personalbarCmd.label;"
class="subviewbutton cui-withicon"
oncommand="PlacesCommandHook.showPlacesOrganizer('BookmarksToolbar'); PanelUI.hide();"/>
<toolbarbutton id="panelMenu_unsortedBookmarks"
label="&otherBookmarksCmd.label;"
class="subviewbutton cui-withicon"
oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks'); PanelUI.hide();"/>
<toolbarseparator class="small-separator"/>
<toolbaritem id="panelMenu_bookmarksMenu"
orient="vertical"
smoothscroll="false"
onclick="if (event.button == 1) BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
oncommand="BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
flatList="true"
tooltip="bhTooltip">
<!-- bookmarks menu items will go here -->
</toolbaritem>
</vbox>
<toolbarbutton id="panelMenu_showAllBookmarks"
label="&showAllBookmarks2.label;"
class="subviewbutton panel-subview-footer"
command="Browser:ShowAllBookmarks"
onclick="PanelUI.hide();"/>
</panelview>
<panelview id="PanelUI-socialapi" flex="1"/>
<panelview id="PanelUI-feeds" flex="1" oncommand="FeedHandler.subscribeToFeed(null, event);">
<label value="&feedsMenu2.label;" class="panel-subview-header"/>
</panelview>
<panelview id="PanelUI-containers" flex="1">
<label value="&containersMenu.label;" class="panel-subview-header"/>
<vbox id="PanelUI-containersItems"/>
</panelview>
<panelview id="PanelUI-helpView" flex="1" class="PanelUI-subView">
<label value="&helpMenu.label;" class="panel-subview-header"/>
<vbox id="PanelUI-helpItems" class="panel-subview-body"/>
</panelview>
<panelview id="PanelUI-developer" flex="1">
<label value="&webDeveloperMenu.label;" class="panel-subview-header"/>
<vbox id="PanelUI-developerItems" class="panel-subview-body"/>
</panelview>
<panelview id="PanelUI-sidebar" flex="1">
<label value="&appMenuSidebars.label;" class="panel-subview-header"/>
<vbox id="PanelUI-sidebarItems" class="panel-subview-body"/>
</panelview>
<panelview id="PanelUI-characterEncodingView" flex="1">
<label value="&charsetMenu2.label;" class="panel-subview-header"/>
<vbox class="panel-subview-body">
<vbox id="PanelUI-characterEncodingView-pinned"
class="PanelUI-characterEncodingView-list"/>
<toolbarseparator/>
<vbox id="PanelUI-characterEncodingView-charsets"
class="PanelUI-characterEncodingView-list"/>
<toolbarseparator/>
<vbox>
<label id="PanelUI-characterEncodingView-autodetect-label"/>
<vbox id="PanelUI-characterEncodingView-autodetect"
class="PanelUI-characterEncodingView-list"/>
</vbox>
</vbox>
</panelview>
<panelview id="PanelUI-panicView" flex="1">
<vbox class="panel-subview-body">
<hbox id="PanelUI-panic-timeframe">
<image id="PanelUI-panic-timeframe-icon" alt=""/>
<vbox flex="1">
<hbox id="PanelUI-panic-header">
<image id="PanelUI-panic-timeframe-icon-small" alt=""/>
<description id="PanelUI-panic-mainDesc" flex="1">&panicButton.view.mainTimeframeDesc;</description>
</hbox>
<radiogroup id="PanelUI-panic-timeSpan" aria-labelledby="PanelUI-panic-mainDesc" closemenu="none">
<radio id="PanelUI-panic-5min" label="&panicButton.view.5min;" selected="true"
value="5" class="subviewradio"/>
<radio id="PanelUI-panic-2hr" label="&panicButton.view.2hr;"
value="2" class="subviewradio"/>
<radio id="PanelUI-panic-day" label="&panicButton.view.day;"
value="6" class="subviewradio"/>
</radiogroup>
</vbox>
</hbox>
<vbox id="PanelUI-panic-explanations">
<label id="PanelUI-panic-actionlist-main-label">&panicButton.view.mainActionDesc;</label>
<label id="PanelUI-panic-actionlist-windows" class="PanelUI-panic-actionlist">&panicButton.view.deleteTabsAndWindows;</label>
<label id="PanelUI-panic-actionlist-cookies" class="PanelUI-panic-actionlist">&panicButton.view.deleteCookies;</label>
<label id="PanelUI-panic-actionlist-history" class="PanelUI-panic-actionlist">&panicButton.view.deleteHistory;</label>
<label id="PanelUI-panic-actionlist-newwindow" class="PanelUI-panic-actionlist">&panicButton.view.openNewWindow;</label>
<label id="PanelUI-panic-warning">&panicButton.view.undoWarning;</label>
</vbox>
<button id="PanelUI-panic-view-button"
label="&panicButton.view.forgetButton;"/>
</vbox>
</panelview>
</panelmultiview>
<!-- These menupopups are located here to prevent flickering,
see bug 492960 comment 20. -->
<menupopup id="customizationPanelItemContextMenu">
<menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
closemenu="single"
class="customize-context-moveToToolbar"
accesskey="&customizeMenu.moveToToolbar.accesskey;"
label="&customizeMenu.moveToToolbar.label;"/>
<menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode)"
closemenu="single"
class="customize-context-removeFromPanel"
accesskey="&customizeMenu.removeFromMenu.accesskey;"
label="&customizeMenu.removeFromMenu.label;"/>
<menuseparator/>
<menuitem command="cmd_CustomizeToolbars"
class="viewCustomizeToolbar"
accesskey="&viewCustomizeToolbar.accesskey;"
label="&viewCustomizeToolbar.label;"/>
</menupopup>
<menupopup id="customizationPaletteItemContextMenu">
<menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
class="customize-context-addToToolbar"
accesskey="&customizeMenu.addToToolbar.accesskey;"
label="&customizeMenu.addToToolbar.label;"/>
<menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
class="customize-context-addToPanel"
accesskey="&customizeMenu.addToPanel.accesskey;"
label="&customizeMenu.addToPanel.label;"/>
</menupopup>
<menupopup id="customizationPanelContextMenu">
<menuitem command="cmd_CustomizeToolbars"
accesskey="&customizeMenu.addMoreItems.accesskey;"
label="&customizeMenu.addMoreItems.label;"/>
</menupopup>
</panel>
<panel id="widget-overflow"
role="group"
type="arrow"
noautofocus="true"
context="toolbar-context-menu"
position="bottomcenter topright"
hidden="true">
<vbox id="widget-overflow-scroller">
<vbox id="widget-overflow-list" class="widget-overflow-list"
overflowfortoolbar="nav-bar"/>
</vbox>
</panel>
<panel id="customization-tipPanel"
type="arrow"
flip="none"
side="left"
position="leftcenter topright"
noautohide="true"
hidden="true">
<hbox class="customization-tipPanel-wrapper">
<vbox class="customization-tipPanel-infoBox"/>
<vbox class="customization-tipPanel-content" flex="1">
<description class="customization-tipPanel-contentMessage"/>
<image class="customization-tipPanel-contentImage"/>
</vbox>
<vbox pack="start" align="end" class="customization-tipPanel-closeBox">
<toolbarbutton oncommand="gCustomizeMode.hideTip()" class="close-icon"/>
</vbox>
</hbox>
</panel>
<panel id="panic-button-success-notification"
type="arrow"
position="bottomcenter topright"
hidden="true"
role="alert"
orient="vertical">
<hbox id="panic-button-success-header">
<image id="panic-button-success-icon" alt=""/>
<vbox>
<description>&panicButton.thankyou.msg1;</description>
<description>&panicButton.thankyou.msg2;</description>
</vbox>
</hbox>
<button label="&panicButton.thankyou.buttonlabel;"
id="panic-button-success-closebutton"
oncommand="PanicButtonNotifier.close()"/>
</panel>

View file

@ -0,0 +1,558 @@
/* 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/. */
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ScrollbarSampler",
"resource:///modules/ScrollbarSampler.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
"resource://gre/modules/ShortcutUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
/**
* Maintains the state and dispatches events for the main menu panel.
*/
const PanelUI = {
/** Panel events that we listen for. **/
get kEvents() {
return ["popupshowing", "popupshown", "popuphiding", "popuphidden"];
},
/**
* Used for lazily getting and memoizing elements from the document. Lazy
* getters are set in init, and memoizing happens after the first retrieval.
*/
get kElements() {
return {
contents: "PanelUI-contents",
mainView: "PanelUI-mainView",
multiView: "PanelUI-multiView",
helpView: "PanelUI-helpView",
menuButton: "PanelUI-menu-button",
panel: "PanelUI-popup",
scroller: "PanelUI-contents-scroller"
};
},
_initialized: false,
init: function() {
for (let [k, v] of Object.entries(this.kElements)) {
// Need to do fresh let-bindings per iteration
let getKey = k;
let id = v;
this.__defineGetter__(getKey, function() {
delete this[getKey];
return this[getKey] = document.getElementById(id);
});
}
this.menuButton.addEventListener("mousedown", this);
this.menuButton.addEventListener("keypress", this);
this._overlayScrollListenerBoundFn = this._overlayScrollListener.bind(this);
window.matchMedia("(-moz-overlay-scrollbars)").addListener(this._overlayScrollListenerBoundFn);
CustomizableUI.addListener(this);
this._initialized = true;
},
_eventListenersAdded: false,
_ensureEventListenersAdded: function() {
if (this._eventListenersAdded)
return;
this._addEventListeners();
},
_addEventListeners: function() {
for (let event of this.kEvents) {
this.panel.addEventListener(event, this);
}
this.helpView.addEventListener("ViewShowing", this._onHelpViewShow, false);
this._eventListenersAdded = true;
},
uninit: function() {
for (let event of this.kEvents) {
this.panel.removeEventListener(event, this);
}
this.helpView.removeEventListener("ViewShowing", this._onHelpViewShow);
this.menuButton.removeEventListener("mousedown", this);
this.menuButton.removeEventListener("keypress", this);
window.matchMedia("(-moz-overlay-scrollbars)").removeListener(this._overlayScrollListenerBoundFn);
CustomizableUI.removeListener(this);
this._overlayScrollListenerBoundFn = null;
},
/**
* Customize mode extracts the mainView and puts it somewhere else while the
* user customizes. Upon completion, this function can be called to put the
* panel back to where it belongs in normal browsing mode.
*
* @param aMainView
* The mainView node to put back into place.
*/
setMainView: function(aMainView) {
this._ensureEventListenersAdded();
this.multiView.setMainView(aMainView);
},
/**
* Opens the menu panel if it's closed, or closes it if it's
* open.
*
* @param aEvent the event that triggers the toggle.
*/
toggle: function(aEvent) {
// Don't show the panel if the window is in customization mode,
// since this button doubles as an exit path for the user in this case.
if (document.documentElement.hasAttribute("customizing")) {
return;
}
this._ensureEventListenersAdded();
if (this.panel.state == "open") {
this.hide();
} else if (this.panel.state == "closed") {
this.show(aEvent);
}
},
/**
* Opens the menu panel. If the event target has a child with the
* toolbarbutton-icon attribute, the panel will be anchored on that child.
* Otherwise, the panel is anchored on the event target itself.
*
* @param aEvent the event (if any) that triggers showing the menu.
*/
show: function(aEvent) {
return new Promise(resolve => {
this.ensureReady().then(() => {
if (this.panel.state == "open" ||
document.documentElement.hasAttribute("customizing")) {
resolve();
return;
}
let editControlPlacement = CustomizableUI.getPlacementOfWidget("edit-controls");
if (editControlPlacement && editControlPlacement.area == CustomizableUI.AREA_PANEL) {
updateEditUIVisibility();
}
let personalBookmarksPlacement = CustomizableUI.getPlacementOfWidget("personal-bookmarks");
if (personalBookmarksPlacement &&
personalBookmarksPlacement.area == CustomizableUI.AREA_PANEL) {
PlacesToolbarHelper.customizeChange();
}
let anchor;
if (!aEvent ||
aEvent.type == "command") {
anchor = this.menuButton;
} else {
anchor = aEvent.target;
}
this.panel.addEventListener("popupshown", function onPopupShown() {
this.removeEventListener("popupshown", onPopupShown);
resolve();
});
let iconAnchor =
document.getAnonymousElementByAttribute(anchor, "class",
"toolbarbutton-icon");
this.panel.openPopup(iconAnchor || anchor);
}, (reason) => {
console.error("Error showing the PanelUI menu", reason);
});
});
},
/**
* If the menu panel is being shown, hide it.
*/
hide: function() {
if (document.documentElement.hasAttribute("customizing")) {
return;
}
this.panel.hidePopup();
},
handleEvent: function(aEvent) {
// Ignore context menus and menu button menus showing and hiding:
if (aEvent.type.startsWith("popup") &&
aEvent.target != this.panel) {
return;
}
switch (aEvent.type) {
case "popupshowing":
this._adjustLabelsForAutoHyphens();
// Fall through
case "popupshown":
// Fall through
case "popuphiding":
// Fall through
case "popuphidden":
this._updatePanelButton(aEvent.target);
break;
case "mousedown":
if (aEvent.button == 0)
this.toggle(aEvent);
break;
case "keypress":
this.toggle(aEvent);
break;
}
},
get isReady() {
return !!this._isReady;
},
/**
* Registering the menu panel is done lazily for performance reasons. This
* method is exposed so that CustomizationMode can force panel-readyness in the
* event that customization mode is started before the panel has been opened
* by the user.
*
* @param aCustomizing (optional) set to true if this was called while entering
* customization mode. If that's the case, we trust that customization
* mode will handle calling beginBatchUpdate and endBatchUpdate.
*
* @return a Promise that resolves once the panel is ready to roll.
*/
ensureReady: function(aCustomizing=false) {
if (this._readyPromise) {
return this._readyPromise;
}
this._readyPromise = Task.spawn(function*() {
if (!this._initialized) {
yield new Promise(resolve => {
let delayedStartupObserver = (aSubject, aTopic, aData) => {
if (aSubject == window) {
Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
resolve();
}
};
Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished", false);
});
}
this.contents.setAttributeNS("http://www.w3.org/XML/1998/namespace", "lang",
getLocale());
if (!this._scrollWidth) {
// In order to properly center the contents of the panel, while ensuring
// that we have enough space on either side to show a scrollbar, we have to
// do a bit of hackery. In particular, we calculate a new width for the
// scroller, based on the system scrollbar width.
this._scrollWidth =
(yield ScrollbarSampler.getSystemScrollbarWidth()) + "px";
let cstyle = window.getComputedStyle(this.scroller);
let widthStr = cstyle.width;
// Get the calculated padding on the left and right sides of
// the scroller too. We'll use that in our final calculation so
// that if a scrollbar appears, we don't have the contents right
// up against the edge of the scroller.
let paddingLeft = cstyle.paddingLeft;
let paddingRight = cstyle.paddingRight;
let calcStr = [widthStr, this._scrollWidth,
paddingLeft, paddingRight].join(" + ");
this.scroller.style.width = "calc(" + calcStr + ")";
}
if (aCustomizing) {
CustomizableUI.registerMenuPanel(this.contents);
} else {
this.beginBatchUpdate();
try {
CustomizableUI.registerMenuPanel(this.contents);
} finally {
this.endBatchUpdate();
}
}
this._updateQuitTooltip();
this.panel.hidden = false;
this._isReady = true;
}.bind(this)).then(null, Cu.reportError);
return this._readyPromise;
},
/**
* Switch the panel to the main view if it's not already
* in that view.
*/
showMainView: function() {
this._ensureEventListenersAdded();
this.multiView.showMainView();
},
/**
* Switch the panel to the help view if it's not already
* in that view.
*/
showHelpView: function(aAnchor) {
this._ensureEventListenersAdded();
this.multiView.showSubView("PanelUI-helpView", aAnchor);
},
/**
* Shows a subview in the panel with a given ID.
*
* @param aViewId the ID of the subview to show.
* @param aAnchor the element that spawned the subview.
* @param aPlacementArea the CustomizableUI area that aAnchor is in.
*/
showSubView: Task.async(function*(aViewId, aAnchor, aPlacementArea) {
this._ensureEventListenersAdded();
let viewNode = document.getElementById(aViewId);
if (!viewNode) {
Cu.reportError("Could not show panel subview with id: " + aViewId);
return;
}
if (!aAnchor) {
Cu.reportError("Expected an anchor when opening subview with id: " + aViewId);
return;
}
if (aPlacementArea == CustomizableUI.AREA_PANEL) {
this.multiView.showSubView(aViewId, aAnchor);
} else if (!aAnchor.open) {
aAnchor.open = true;
let tempPanel = document.createElement("panel");
tempPanel.setAttribute("type", "arrow");
tempPanel.setAttribute("id", "customizationui-widget-panel");
tempPanel.setAttribute("class", "cui-widget-panel");
tempPanel.setAttribute("viewId", aViewId);
if (aAnchor.getAttribute("tabspecific")) {
tempPanel.setAttribute("tabspecific", true);
}
if (this._disableAnimations) {
tempPanel.setAttribute("animate", "false");
}
tempPanel.setAttribute("context", "");
document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel);
// If the view has a footer, set a convenience class on the panel.
tempPanel.classList.toggle("cui-widget-panelWithFooter",
viewNode.querySelector(".panel-subview-footer"));
let multiView = document.createElement("panelmultiview");
multiView.setAttribute("id", "customizationui-widget-multiview");
multiView.setAttribute("nosubviews", "true");
tempPanel.appendChild(multiView);
multiView.setAttribute("mainViewIsSubView", "true");
multiView.setMainView(viewNode);
viewNode.classList.add("cui-widget-panelview");
let viewShown = false;
let panelRemover = () => {
viewNode.classList.remove("cui-widget-panelview");
if (viewShown) {
CustomizableUI.removePanelCloseListeners(tempPanel);
tempPanel.removeEventListener("popuphidden", panelRemover);
let evt = new CustomEvent("ViewHiding", {detail: viewNode});
viewNode.dispatchEvent(evt);
}
aAnchor.open = false;
this.multiView.appendChild(viewNode);
tempPanel.remove();
};
// Emit the ViewShowing event so that the widget definition has a chance
// to lazily populate the subview with things.
let detail = {
blockers: new Set(),
addBlocker(aPromise) {
this.blockers.add(aPromise);
},
};
let evt = new CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
viewNode.dispatchEvent(evt);
let cancel = evt.defaultPrevented;
if (detail.blockers.size) {
try {
let results = yield Promise.all(detail.blockers);
cancel = cancel || results.some(val => val === false);
} catch (e) {
Components.utils.reportError(e);
cancel = true;
}
}
if (cancel) {
panelRemover();
return;
}
viewShown = true;
CustomizableUI.addPanelCloseListeners(tempPanel);
tempPanel.addEventListener("popuphidden", panelRemover);
let iconAnchor =
document.getAnonymousElementByAttribute(aAnchor, "class",
"toolbarbutton-icon");
if (iconAnchor && aAnchor.id) {
iconAnchor.setAttribute("consumeanchor", aAnchor.id);
}
tempPanel.openPopup(iconAnchor || aAnchor, "bottomcenter topright");
}
}),
/**
* NB: The enable- and disableSingleSubviewPanelAnimations methods only
* affect the hiding/showing animations of single-subview panels (tempPanel
* in the showSubView method).
*/
disableSingleSubviewPanelAnimations: function() {
this._disableAnimations = true;
},
enableSingleSubviewPanelAnimations: function() {
this._disableAnimations = false;
},
onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer, aWasRemoval) {
if (aContainer != this.contents) {
return;
}
if (aWasRemoval) {
aNode.removeAttribute("auto-hyphens");
}
},
onWidgetBeforeDOMChange: function(aNode, aNextNode, aContainer, aIsRemoval) {
if (aContainer != this.contents) {
return;
}
if (!aIsRemoval &&
(this.panel.state == "open" ||
document.documentElement.hasAttribute("customizing"))) {
this._adjustLabelsForAutoHyphens(aNode);
}
},
/**
* Signal that we're about to make a lot of changes to the contents of the
* panels all at once. For performance, we ignore the mutations.
*/
beginBatchUpdate: function() {
this._ensureEventListenersAdded();
this.multiView.ignoreMutations = true;
},
/**
* Signal that we're done making bulk changes to the panel. We now pay
* attention to mutations. This automatically synchronizes the multiview
* container with whichever view is displayed if the panel is open.
*/
endBatchUpdate: function(aReason) {
this._ensureEventListenersAdded();
this.multiView.ignoreMutations = false;
},
_adjustLabelsForAutoHyphens: function(aNode) {
let toolbarButtons = aNode ? [aNode] :
this.contents.querySelectorAll(".toolbarbutton-1");
for (let node of toolbarButtons) {
let label = node.getAttribute("label");
if (!label) {
continue;
}
if (label.includes("\u00ad")) {
node.setAttribute("auto-hyphens", "off");
} else {
node.removeAttribute("auto-hyphens");
}
}
},
/**
* Sets the anchor node into the open or closed state, depending
* on the state of the panel.
*/
_updatePanelButton: function() {
this.menuButton.open = this.panel.state == "open" ||
this.panel.state == "showing";
},
_onHelpViewShow: function(aEvent) {
// Call global menu setup function
buildHelpMenu();
let helpMenu = document.getElementById("menu_HelpPopup");
let items = this.getElementsByTagName("vbox")[0];
let attrs = ["oncommand", "onclick", "label", "key", "disabled"];
let NSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// Remove all buttons from the view
while (items.firstChild) {
items.removeChild(items.firstChild);
}
// Add the current set of menuitems of the Help menu to this view
let menuItems = Array.prototype.slice.call(helpMenu.getElementsByTagName("menuitem"));
let fragment = document.createDocumentFragment();
for (let node of menuItems) {
if (node.hidden)
continue;
let button = document.createElementNS(NSXUL, "toolbarbutton");
// Copy specific attributes from a menuitem of the Help menu
for (let attrName of attrs) {
if (!node.hasAttribute(attrName))
continue;
button.setAttribute(attrName, node.getAttribute(attrName));
}
button.setAttribute("class", "subviewbutton");
fragment.appendChild(button);
}
items.appendChild(fragment);
},
_updateQuitTooltip: function() {
if (AppConstants.platform == "win") {
return;
}
let tooltipId = AppConstants.platform == "macosx" ?
"quit-button.tooltiptext.mac" :
"quit-button.tooltiptext.linux2";
let brands = Services.strings.createBundle("chrome://branding/locale/brand.properties");
let stringArgs = [brands.GetStringFromName("brandShortName")];
let key = document.getElementById("key_quitApplication");
stringArgs.push(ShortcutUtils.prettifyShortcut(key));
let tooltipString = CustomizableUI.getLocalizedProperty({x: tooltipId}, "x", stringArgs);
let quitButton = document.getElementById("PanelUI-quit");
quitButton.setAttribute("tooltiptext", tooltipString);
},
_overlayScrollListenerBoundFn: null,
_overlayScrollListener: function(aMQL) {
ScrollbarSampler.resetSystemScrollbarWidth();
this._scrollWidth = null;
},
};
XPCOMUtils.defineConstant(this, "PanelUI", PanelUI);
/**
* Gets the currently selected locale for display.
* @return the selected locale or "en-US" if none is selected
*/
function getLocale() {
try {
let chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"]
.getService(Ci.nsIXULChromeRegistry);
return chromeRegistry.getSelectedLocale("browser");
} catch (ex) {
return "en-US";
}
}

View file

@ -0,0 +1,509 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="browserPanelUIBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="panelmultiview">
<resources>
<stylesheet src="chrome://browser/content/customizableui/panelUI.css"/>
</resources>
<content>
<xul:box anonid="viewContainer" class="panel-viewcontainer" xbl:inherits="panelopen,viewtype,transitioning">
<xul:stack anonid="viewStack" xbl:inherits="viewtype,transitioning" viewtype="main" class="panel-viewstack">
<xul:vbox anonid="mainViewContainer" class="panel-mainview" xbl:inherits="viewtype"/>
<!-- Used to capture click events over the PanelUI-mainView if we're in
subview mode. That way, any click on the PanelUI-mainView causes us
to revert to the mainView mode, whereupon PanelUI-click-capture then
allows click events to go through it. -->
<xul:vbox anonid="clickCapturer" class="panel-clickcapturer"/>
<!-- We manually set display: none (via a CSS attribute selector) on the
subviews that are not being displayed. We're using this over a deck
because a deck assumes the size of its largest child, regardless of
whether or not it is shown. That's not good for our case, since we
want to allow each subview to be uniquely sized. -->
<xul:vbox anonid="subViews" class="panel-subviews" xbl:inherits="panelopen">
<children includes="panelview"/>
</xul:vbox>
</xul:stack>
</xul:box>
</content>
<implementation implements="nsIDOMEventListener">
<field name="_clickCapturer" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "clickCapturer");
</field>
<field name="_viewContainer" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "viewContainer");
</field>
<field name="_mainViewContainer" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "mainViewContainer");
</field>
<field name="_subViews" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "subViews");
</field>
<field name="_viewStack" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "viewStack");
</field>
<field name="_panel" readonly="true">
this.parentNode;
</field>
<field name="_currentSubView">null</field>
<field name="_anchorElement">null</field>
<field name="_mainViewHeight">0</field>
<field name="_subViewObserver">null</field>
<field name="__transitioning">false</field>
<field name="_ignoreMutations">false</field>
<property name="showingSubView" readonly="true"
onget="return this._viewStack.getAttribute('viewtype') == 'subview'"/>
<property name="_mainViewId" onget="return this.getAttribute('mainViewId');" onset="this.setAttribute('mainViewId', val); return val;"/>
<property name="_mainView" readonly="true"
onget="return this._mainViewId ? document.getElementById(this._mainViewId) : null;"/>
<property name="showingSubViewAsMainView" readonly="true"
onget="return this.getAttribute('mainViewIsSubView') == 'true'"/>
<property name="ignoreMutations">
<getter>
return this._ignoreMutations;
</getter>
<setter><![CDATA[
this._ignoreMutations = val;
if (!val && this._panel.state == "open") {
if (this.showingSubView) {
this._syncContainerWithSubView();
} else {
this._syncContainerWithMainView();
}
}
]]></setter>
</property>
<property name="_transitioning">
<getter>
return this.__transitioning;
</getter>
<setter><![CDATA[
this.__transitioning = val;
if (val) {
this.setAttribute("transitioning", "true");
} else {
this.removeAttribute("transitioning");
}
]]></setter>
</property>
<constructor><![CDATA[
this._clickCapturer.addEventListener("click", this);
this._panel.addEventListener("popupshowing", this);
this._panel.addEventListener("popupshown", this);
this._panel.addEventListener("popuphidden", this);
this._subViews.addEventListener("overflow", this);
this._mainViewContainer.addEventListener("overflow", this);
// Get a MutationObserver ready to react to subview size changes. We
// only attach this MutationObserver when a subview is being displayed.
this._subViewObserver =
new MutationObserver(this._syncContainerWithSubView.bind(this));
this._mainViewObserver =
new MutationObserver(this._syncContainerWithMainView.bind(this));
this._mainViewContainer.setAttribute("panelid",
this._panel.id);
if (this._mainView) {
this.setMainView(this._mainView);
}
this.setAttribute("viewtype", "main");
]]></constructor>
<destructor><![CDATA[
if (this._mainView) {
this._mainView.removeAttribute("mainview");
}
this._mainViewObserver.disconnect();
this._subViewObserver.disconnect();
this._panel.removeEventListener("popupshowing", this);
this._panel.removeEventListener("popupshown", this);
this._panel.removeEventListener("popuphidden", this);
this._subViews.removeEventListener("overflow", this);
this._mainViewContainer.removeEventListener("overflow", this);
this._clickCapturer.removeEventListener("click", this);
]]></destructor>
<method name="setMainView">
<parameter name="aNewMainView"/>
<body><![CDATA[
if (this._mainView) {
this._mainViewObserver.disconnect();
this._subViews.appendChild(this._mainView);
this._mainView.removeAttribute("mainview");
}
this._mainViewId = aNewMainView.id;
aNewMainView.setAttribute("mainview", "true");
this._mainViewContainer.appendChild(aNewMainView);
]]></body>
</method>
<method name="showMainView">
<body><![CDATA[
if (this.showingSubView) {
let viewNode = this._currentSubView;
let evt = document.createEvent("CustomEvent");
evt.initCustomEvent("ViewHiding", true, true, viewNode);
viewNode.dispatchEvent(evt);
viewNode.removeAttribute("current");
this._currentSubView = null;
this._subViewObserver.disconnect();
this._setViewContainerHeight(this._mainViewHeight);
this.setAttribute("viewtype", "main");
}
this._shiftMainView();
]]></body>
</method>
<method name="showSubView">
<parameter name="aViewId"/>
<parameter name="aAnchor"/>
<body><![CDATA[
Task.spawn(function*() {
let viewNode = this.querySelector("#" + aViewId);
viewNode.setAttribute("current", true);
// Emit the ViewShowing event so that the widget definition has a chance
// to lazily populate the subview with things.
let detail = {
blockers: new Set(),
addBlocker(aPromise) {
this.blockers.add(aPromise);
},
};
let evt = new CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
viewNode.dispatchEvent(evt);
let cancel = evt.defaultPrevented;
if (detail.blockers.size) {
try {
let results = yield Promise.all(detail.blockers);
cancel = cancel || results.some(val => val === false);
} catch (e) {
Components.utils.reportError(e);
cancel = true;
}
}
if (cancel) {
return;
}
this._currentSubView = viewNode;
// Now we have to transition the panel. There are a few parts to this:
//
// 1) The main view content gets shifted so that the center of the anchor
// node is at the left-most edge of the panel.
// 2) The subview deck slides in so that it takes up almost all of the
// panel.
// 3) If the subview is taller then the main panel contents, then the panel
// must grow to meet that new height. Otherwise, it must shrink.
//
// All three of these actions make use of CSS transformations, so they
// should all occur simultaneously.
this.setAttribute("viewtype", "subview");
this._shiftMainView(aAnchor);
this._mainViewHeight = this._viewStack.clientHeight;
let newHeight = this._heightOfSubview(viewNode, this._subViews);
this._setViewContainerHeight(newHeight);
this._subViewObserver.observe(viewNode, {
attributes: true,
characterData: true,
childList: true,
subtree: true
});
}.bind(this));
]]></body>
</method>
<method name="_setViewContainerHeight">
<parameter name="aHeight"/>
<body><![CDATA[
let container = this._viewContainer;
this._transitioning = true;
let onTransitionEnd = () => {
container.removeEventListener("transitionend", onTransitionEnd);
this._transitioning = false;
};
container.addEventListener("transitionend", onTransitionEnd);
container.style.height = `${aHeight}px`;
]]></body>
</method>
<method name="_shiftMainView">
<parameter name="aAnchor"/>
<body><![CDATA[
if (aAnchor) {
// We need to find the edge of the anchor, relative to the main panel.
// Then we need to add half the width of the anchor. This is the target
// that we need to transition to.
let anchorRect = aAnchor.getBoundingClientRect();
let mainViewRect = this._mainViewContainer.getBoundingClientRect();
let center = aAnchor.clientWidth / 2;
let direction = aAnchor.ownerDocument.defaultView.getComputedStyle(aAnchor, null).direction;
let edge;
if (direction == "ltr") {
edge = anchorRect.left - mainViewRect.left;
} else {
edge = mainViewRect.right - anchorRect.right;
}
// If the anchor is an element on the far end of the mainView we
// don't want to shift the mainView too far, we would reveal empty
// space otherwise.
let cstyle = window.getComputedStyle(document.documentElement, null);
let exitSubViewGutterWidth =
cstyle.getPropertyValue("--panel-ui-exit-subview-gutter-width");
let maxShift = mainViewRect.width - parseInt(exitSubViewGutterWidth);
let target = Math.min(maxShift, edge + center);
let neg = direction == "ltr" ? "-" : "";
this._mainViewContainer.style.transform = `translateX(${neg}${target}px)`;
aAnchor.setAttribute("panel-multiview-anchor", true);
} else {
this._mainViewContainer.style.transform = "";
if (this.anchorElement)
this.anchorElement.removeAttribute("panel-multiview-anchor");
}
this.anchorElement = aAnchor;
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
if (aEvent.type.startsWith("popup") && aEvent.target != this._panel) {
// Shouldn't act on e.g. context menus being shown from within the panel.
return;
}
switch (aEvent.type) {
case "click":
if (aEvent.originalTarget == this._clickCapturer) {
this.showMainView();
}
break;
case "overflow":
if (aEvent.target.localName == "vbox") {
// Resize the right view on the next tick.
if (this.showingSubView) {
setTimeout(this._syncContainerWithSubView.bind(this), 0);
} else if (!this.transitioning) {
setTimeout(this._syncContainerWithMainView.bind(this), 0);
}
}
break;
case "popupshowing":
this.setAttribute("panelopen", "true");
// Bug 941196 - The panel can get taller when opening a subview. Disabling
// autoPositioning means that the panel won't jump around if an opened
// subview causes the panel to exceed the dimensions of the screen in the
// direction that the panel originally opened in. This property resets
// every time the popup closes, which is why we have to set it each time.
this._panel.autoPosition = false;
this._syncContainerWithMainView();
this._mainViewObserver.observe(this._mainView, {
attributes: true,
characterData: true,
childList: true,
subtree: true
});
break;
case "popupshown":
this._setMaxHeight();
break;
case "popuphidden":
this.removeAttribute("panelopen");
this._mainView.style.removeProperty("height");
this.showMainView();
this._mainViewObserver.disconnect();
break;
}
]]></body>
</method>
<method name="_shouldSetPosition">
<body><![CDATA[
return this.getAttribute("nosubviews") == "true";
]]></body>
</method>
<method name="_shouldSetHeight">
<body><![CDATA[
return this.getAttribute("nosubviews") != "true";
]]></body>
</method>
<method name="_setMaxHeight">
<body><![CDATA[
if (!this._shouldSetHeight())
return;
// Ignore the mutation that'll fire when we set the height of
// the main view.
this.ignoreMutations = true;
this._mainView.style.height =
this.getBoundingClientRect().height + "px";
this.ignoreMutations = false;
]]></body>
</method>
<method name="_adjustContainerHeight">
<body><![CDATA[
if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
let height;
if (this.showingSubViewAsMainView) {
height = this._heightOfSubview(this._mainView);
} else {
height = this._mainView.scrollHeight;
}
this._viewContainer.style.height = height + "px";
}
]]></body>
</method>
<method name="_syncContainerWithSubView">
<body><![CDATA[
// Check that this panel is still alive:
if (!this._panel || !this._panel.parentNode) {
return;
}
if (!this.ignoreMutations && this.showingSubView) {
let newHeight = this._heightOfSubview(this._currentSubView, this._subViews);
this._viewContainer.style.height = newHeight + "px";
}
]]></body>
</method>
<method name="_syncContainerWithMainView">
<body><![CDATA[
// Check that this panel is still alive:
if (!this._panel || !this._panel.parentNode) {
return;
}
if (this._shouldSetPosition()) {
this._panel.adjustArrowPosition();
}
if (this._shouldSetHeight()) {
this._adjustContainerHeight();
}
]]></body>
</method>
<!-- Call this when the height of one of your views (the main view or a
subview) changes and you want the heights of the multiview and panel
to be the same as the view's height.
If the caller can give a hint of the expected height change with the
optional aExpectedChange parameter, it prevents flicker. -->
<method name="setHeightToFit">
<parameter name="aExpectedChange"/>
<body><![CDATA[
// Set the max-height to zero, wait until the height is actually
// updated, and then remove it. If it's not removed, weird things can
// happen, like widgets in the panel won't respond to clicks even
// though they're visible.
let count = 5;
let height = getComputedStyle(this).height;
if (aExpectedChange)
this.style.maxHeight = (parseInt(height) + aExpectedChange) + "px";
else
this.style.maxHeight = "0";
let interval = setInterval(() => {
if (height != getComputedStyle(this).height || --count == 0) {
clearInterval(interval);
this.style.removeProperty("max-height");
}
}, 0);
]]></body>
</method>
<method name="_heightOfSubview">
<parameter name="aSubview"/>
<parameter name="aContainerToCheck"/>
<body><![CDATA[
function getFullHeight(element) {
// XXXgijs: unfortunately, scrollHeight rounds values, and there's no alternative
// that works with overflow: auto elements. Fortunately for us,
// we have exactly 1 (potentially) scrolling element in here (the subview body),
// and rounding 1 value is OK - rounding more than 1 and adding them means we get
// off-by-1 errors. Now we might be off by a subpixel, but we care less about that.
// So, use scrollHeight *only* if the element is vertically scrollable.
let height;
let elementCS;
if (element.scrollTopMax) {
height = element.scrollHeight;
// Bounding client rects include borders, scrollHeight doesn't:
elementCS = win.getComputedStyle(element);
height += parseFloat(elementCS.borderTopWidth) +
parseFloat(elementCS.borderBottomWidth);
} else {
height = element.getBoundingClientRect().height;
if (height > 0) {
elementCS = win.getComputedStyle(element);
}
}
if (elementCS) {
// Include margins - but not borders or paddings because they
// were dealt with above.
height += parseFloat(elementCS.marginTop) + parseFloat(elementCS.marginBottom);
}
return height;
}
let win = aSubview.ownerDocument.defaultView;
let body = aSubview.querySelector(".panel-subview-body");
let height = getFullHeight(body || aSubview);
if (body) {
let header = aSubview.querySelector(".panel-subview-header");
let footer = aSubview.querySelector(".panel-subview-footer");
height += (header ? getFullHeight(header) : 0) +
(footer ? getFullHeight(footer) : 0);
}
if (aContainerToCheck) {
let containerCS = win.getComputedStyle(aContainerToCheck);
height += parseFloat(containerCS.paddingTop) + parseFloat(containerCS.paddingBottom);
}
return Math.ceil(height);
]]></body>
</method>
</implementation>
</binding>
<binding id="panelview">
<implementation>
<property name="panelMultiView" readonly="true">
<getter><![CDATA[
if (this.parentNode.localName != "panelmultiview") {
return document.getBindingParent(this.parentNode);
}
return this.parentNode;
]]></getter>
</property>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,618 @@
<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="browserToolbarBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="toolbar" role="xul:toolbar">
<resources>
<stylesheet src="chrome://global/skin/toolbar.css"/>
</resources>
<implementation>
<field name="overflowedDuringConstruction">null</field>
<constructor><![CDATA[
let scope = {};
Cu.import("resource:///modules/CustomizableUI.jsm", scope);
// Add an early overflow event listener that will mark if the
// toolbar overflowed during construction.
if (scope.CustomizableUI.isAreaOverflowable(this.id)) {
this.addEventListener("overflow", this);
this.addEventListener("underflow", this);
}
if (document.readyState == "complete") {
this._init();
} else {
// Need to wait until XUL overlays are loaded. See bug 554279.
let self = this;
document.addEventListener("readystatechange", function onReadyStateChange() {
if (document.readyState != "complete")
return;
document.removeEventListener("readystatechange", onReadyStateChange, false);
self._init();
}, false);
}
]]></constructor>
<method name="_init">
<body><![CDATA[
let scope = {};
Cu.import("resource:///modules/CustomizableUI.jsm", scope);
let CustomizableUI = scope.CustomizableUI;
// Bug 989289: Forcibly set the now unsupported "mode" and "iconsize"
// attributes, just in case they accidentally get restored from
// persistence from a user that's been upgrading and downgrading.
if (CustomizableUI.isBuiltinToolbar(this.id)) {
const kAttributes = new Map([["mode", "icons"], ["iconsize", "small"]]);
for (let [attribute, value] of kAttributes) {
if (this.getAttribute(attribute) != value) {
this.setAttribute(attribute, value);
document.persist(this.id, attribute);
}
if (this.toolbox) {
if (this.toolbox.getAttribute(attribute) != value) {
this.toolbox.setAttribute(attribute, value);
document.persist(this.toolbox.id, attribute);
}
}
}
}
// Searching for the toolbox palette in the toolbar binding because
// toolbars are constructed first.
let toolbox = this.toolbox;
if (toolbox && !toolbox.palette) {
for (let node of toolbox.children) {
if (node.localName == "toolbarpalette") {
// Hold on to the palette but remove it from the document.
toolbox.palette = node;
toolbox.removeChild(node);
break;
}
}
}
// pass the current set of children for comparison with placements:
let children = Array.from(this.childNodes)
.filter(node => node.getAttribute("skipintoolbarset") != "true" && node.id)
.map(node => node.id);
CustomizableUI.registerToolbarNode(this, children);
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
if (aEvent.type == "overflow" && aEvent.detail > 0) {
if (this.overflowable && this.overflowable.initialized) {
this.overflowable.onOverflow(aEvent);
} else {
this.overflowedDuringConstruction = aEvent;
}
} else if (aEvent.type == "underflow" && aEvent.detail > 0) {
this.overflowedDuringConstruction = null;
}
]]></body>
</method>
<method name="insertItem">
<parameter name="aId"/>
<parameter name="aBeforeElt"/>
<parameter name="aWrapper"/>
<body><![CDATA[
if (aWrapper) {
Cu.reportError("Can't insert " + aId + ": using insertItem " +
"no longer supports wrapper elements.");
return null;
}
// Hack, the customizable UI code makes this be the last position
let pos = null;
if (aBeforeElt) {
let beforeInfo = CustomizableUI.getPlacementOfWidget(aBeforeElt.id);
if (beforeInfo.area != this.id) {
Cu.reportError("Can't insert " + aId + " before " +
aBeforeElt.id + " which isn't in this area (" +
this.id + ").");
return null;
}
pos = beforeInfo.position;
}
CustomizableUI.addWidgetToArea(aId, this.id, pos);
return this.ownerDocument.getElementById(aId);
]]></body>
</method>
<property name="toolbarName"
onget="return this.getAttribute('toolbarname');"
onset="this.setAttribute('toolbarname', val); return val;"/>
<property name="customizationTarget" readonly="true">
<getter><![CDATA[
if (this._customizationTarget)
return this._customizationTarget;
let id = this.getAttribute("customizationtarget");
if (id)
this._customizationTarget = document.getElementById(id);
if (this._customizationTarget)
this._customizationTarget.insertItem = this.insertItem.bind(this);
else
this._customizationTarget = this;
return this._customizationTarget;
]]></getter>
</property>
<property name="toolbox" readonly="true">
<getter><![CDATA[
if (this._toolbox)
return this._toolbox;
let toolboxId = this.getAttribute("toolboxid");
if (toolboxId) {
let toolbox = document.getElementById(toolboxId);
if (toolbox) {
if (toolbox.externalToolbars.indexOf(this) == -1)
toolbox.externalToolbars.push(this);
this._toolbox = toolbox;
}
}
if (!this._toolbox && this.parentNode &&
this.parentNode.localName == "toolbox") {
this._toolbox = this.parentNode;
}
return this._toolbox;
]]></getter>
</property>
<property name="currentSet">
<getter><![CDATA[
let currentWidgets = new Set();
for (let node of this.customizationTarget.children) {
let realNode = node.localName == "toolbarpaletteitem" ? node.firstChild : node;
if (realNode.getAttribute("skipintoolbarset") != "true") {
currentWidgets.add(realNode.id);
}
}
if (this.getAttribute("overflowing") == "true") {
let overflowTarget = this.getAttribute("overflowtarget");
let overflowList = this.ownerDocument.getElementById(overflowTarget);
for (let node of overflowList.children) {
let realNode = node.localName == "toolbarpaletteitem" ? node.firstChild : node;
if (realNode.getAttribute("skipintoolbarset") != "true") {
currentWidgets.add(realNode.id);
}
}
}
let orderedPlacements = CustomizableUI.getWidgetIdsInArea(this.id);
return orderedPlacements.filter((x) => currentWidgets.has(x)).join(',');
]]></getter>
<setter><![CDATA[
// Get list of new and old ids:
let newVal = (val || '').split(',').filter(x => x);
let oldIds = CustomizableUI.getWidgetIdsInArea(this.id);
// Get a list of items only in the new list
let newIds = newVal.filter(id => oldIds.indexOf(id) == -1);
CustomizableUI.beginBatchUpdate();
try {
for (let newId of newIds) {
oldIds = CustomizableUI.getWidgetIdsInArea(this.id);
let nextId = newId;
let pos;
do {
// Get the next item
nextId = newVal[newVal.indexOf(nextId) + 1];
// Figure out where it is in the old list
pos = oldIds.indexOf(nextId);
// If it's not in the old list, repeat:
} while (pos == -1 && nextId);
if (pos == -1) {
pos = null; // We didn't find anything, insert at the end
}
CustomizableUI.addWidgetToArea(newId, this.id, pos);
}
let currentIds = this.currentSet.split(',');
let removedIds = currentIds.filter(id => newIds.indexOf(id) == -1 && newVal.indexOf(id) == -1);
for (let removedId of removedIds) {
CustomizableUI.removeWidgetFromArea(removedId);
}
} finally {
CustomizableUI.endBatchUpdate();
}
]]></setter>
</property>
</implementation>
</binding>
<binding id="toolbar-menubar-stub">
<implementation>
<property name="toolbox" readonly="true">
<getter><![CDATA[
if (this._toolbox)
return this._toolbox;
if (this.parentNode && this.parentNode.localName == "toolbox") {
this._toolbox = this.parentNode;
}
return this._toolbox;
]]></getter>
</property>
<property name="currentSet" readonly="true">
<getter><![CDATA[
return this.getAttribute("defaultset");
]]></getter>
</property>
<method name="insertItem">
<body><![CDATA[
return null;
]]></body>
</method>
</implementation>
</binding>
<!-- The toolbar-menubar-autohide and toolbar-drag bindings are almost
verbatim copies of their toolkit counterparts - they just inherit from
the customizableui's toolbar binding instead of toolkit's. We're currently
OK with the maintainance burden of having two copies of a binding, since
the long term goal is to move the customization framework into toolkit. -->
<binding id="toolbar-menubar-autohide"
extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
<implementation>
<constructor>
this._setInactive();
</constructor>
<destructor>
this._setActive();
</destructor>
<field name="_inactiveTimeout">null</field>
<field name="_contextMenuListener"><![CDATA[({
toolbar: this,
contextMenu: null,
get active () {
return !!this.contextMenu;
},
init: function (event) {
let node = event.target;
while (node != this.toolbar) {
if (node.localName == "menupopup")
return;
node = node.parentNode;
}
let contextMenuId = this.toolbar.getAttribute("context");
if (!contextMenuId)
return;
this.contextMenu = document.getElementById(contextMenuId);
if (!this.contextMenu)
return;
this.contextMenu.addEventListener("popupshown", this, false);
this.contextMenu.addEventListener("popuphiding", this, false);
this.toolbar.addEventListener("mousemove", this, false);
},
handleEvent: function (event) {
switch (event.type) {
case "popupshown":
this.toolbar.removeEventListener("mousemove", this, false);
break;
case "popuphiding":
case "mousemove":
this.toolbar._setInactiveAsync();
this.toolbar.removeEventListener("mousemove", this, false);
this.contextMenu.removeEventListener("popuphiding", this, false);
this.contextMenu.removeEventListener("popupshown", this, false);
this.contextMenu = null;
break;
}
}
})]]></field>
<method name="_setInactive">
<body><![CDATA[
this.setAttribute("inactive", "true");
]]></body>
</method>
<method name="_setInactiveAsync">
<body><![CDATA[
this._inactiveTimeout = setTimeout(function (self) {
if (self.getAttribute("autohide") == "true") {
self._inactiveTimeout = null;
self._setInactive();
}
}, 0, this);
]]></body>
</method>
<method name="_setActive">
<body><![CDATA[
if (this._inactiveTimeout) {
clearTimeout(this._inactiveTimeout);
this._inactiveTimeout = null;
}
this.removeAttribute("inactive");
]]></body>
</method>
</implementation>
<handlers>
<handler event="DOMMenuBarActive" action="this._setActive();"/>
<handler event="popupshowing" action="this._setActive();"/>
<handler event="mousedown" button="2" action="this._contextMenuListener.init(event);"/>
<handler event="DOMMenuBarInactive"><![CDATA[
if (!this._contextMenuListener.active)
this._setInactiveAsync();
]]></handler>
</handlers>
</binding>
<binding id="toolbar-drag"
extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
<implementation>
<field name="_dragBindingAlive">true</field>
<constructor><![CDATA[
if (!this._draggableStarted) {
this._draggableStarted = true;
try {
let tmp = {};
Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
let draggableThis = new tmp.WindowDraggingElement(this);
draggableThis.mouseDownCheck = function(e) {
return this._dragBindingAlive;
};
} catch (e) {}
}
]]></constructor>
</implementation>
</binding>
<!-- This is a peculiar binding. It is here to deal with overlayed/inserted add-on content,
and immediately direct such content elsewhere. -->
<binding id="addonbar-delegating">
<implementation>
<constructor><![CDATA[
// Reading these immediately so nobody messes with them anymore:
this._delegatingToolbar = this.getAttribute("toolbar-delegate");
this._wasCollapsed = this.getAttribute("collapsed") == "true";
// Leaving those in here to unbreak some code:
if (document.readyState == "complete") {
this._init();
} else {
// Need to wait until XUL overlays are loaded. See bug 554279.
let self = this;
document.addEventListener("readystatechange", function onReadyStateChange() {
if (document.readyState != "complete")
return;
document.removeEventListener("readystatechange", onReadyStateChange, false);
self._init();
}, false);
}
]]></constructor>
<method name="_init">
<body><![CDATA[
// Searching for the toolbox palette in the toolbar binding because
// toolbars are constructed first.
let toolbox = this.toolbox;
if (toolbox && !toolbox.palette) {
for (let node of toolbox.children) {
if (node.localName == "toolbarpalette") {
// Hold on to the palette but remove it from the document.
toolbox.palette = node;
toolbox.removeChild(node);
}
}
}
// pass the current set of children for comparison with placements:
let children = [];
for (let node of this.childNodes) {
if (node.getAttribute("skipintoolbarset") != "true" && node.id) {
// Force everything to be removable so that buildArea can chuck stuff
// out if the user has customized things / we've been here before:
if (!this._whiteListed.has(node.id)) {
node.setAttribute("removable", "true");
}
children.push(node);
}
}
CustomizableUI.registerToolbarNode(this, children);
let existingMigratedItems = (this.getAttribute("migratedset") || "").split(',');
for (let migratedItem of existingMigratedItems.filter((x) => !!x)) {
this._currentSetMigrated.add(migratedItem);
}
this.evictNodes();
// We can't easily use |this| or strong bindings for the observer fn here
// because that creates leaky circular references when the node goes away,
// and XBL destructors are unreliable.
let mutationObserver = new MutationObserver(function(mutations) {
if (!mutations.length) {
return;
}
let toolbar = mutations[0].target;
// Can't use our own attribute because we might not have one if we're set to
// collapsed
let areCustomizing = toolbar.ownerDocument.documentElement.getAttribute("customizing");
if (!toolbar._isModifying && !areCustomizing) {
toolbar.evictNodes();
}
});
mutationObserver.observe(this, {childList: true});
]]></body>
</method>
<method name="evictNodes">
<body><![CDATA[
this._isModifying = true;
let i = this.childNodes.length;
while (i--) {
let node = this.childNodes[i];
if (this.childNodes[i].id) {
this.evictNode(this.childNodes[i]);
} else {
node.remove();
}
}
this._isModifying = false;
this._updateMigratedSet();
]]></body>
</method>
<method name="evictNode">
<parameter name="aNode"/>
<body>
<![CDATA[
if (this._whiteListed.has(aNode.id) || CustomizableUI.isSpecialWidget(aNode.id)) {
return;
}
const kItemMaxWidth = 100;
let oldParent = aNode.parentNode;
aNode.setAttribute("removable", "true");
this._currentSetMigrated.add(aNode.id);
let movedOut = false;
if (!this._wasCollapsed) {
try {
let nodeWidth = aNode.getBoundingClientRect().width;
if (nodeWidth == 0 || nodeWidth > kItemMaxWidth) {
throw new Error(aNode.id + " is too big (" + nodeWidth +
"px wide), moving to the palette");
}
CustomizableUI.addWidgetToArea(aNode.id, this._delegatingToolbar);
movedOut = true;
} catch (ex) {
// This will throw if the node is too big, or can't be moved there for
// some reason. Report this:
Cu.reportError(ex);
}
}
/* We won't have moved the widget if either the add-on bar was collapsed,
* or if it was too wide to be inserted into the navbar. */
if (!movedOut) {
try {
CustomizableUI.removeWidgetFromArea(aNode.id);
} catch (ex) {
Cu.reportError(ex);
aNode.remove();
}
}
// Surprise: addWidgetToArea(palette) will get you nothing if the palette
// is not constructed yet. Fix:
if (aNode.parentNode == oldParent) {
let palette = this.toolbox.palette;
if (palette && oldParent != palette) {
palette.appendChild(aNode);
}
}
]]></body>
</method>
<method name="insertItem">
<parameter name="aId"/>
<parameter name="aBeforeElt"/>
<parameter name="aWrapper"/>
<body><![CDATA[
if (aWrapper) {
Cu.reportError("Can't insert " + aId + ": using insertItem " +
"no longer supports wrapper elements.");
return null;
}
let widget = CustomizableUI.getWidget(aId);
widget = widget && widget.forWindow(window);
let node = widget && widget.node;
if (!node) {
return null;
}
this._isModifying = true;
// Temporarily add it here so it can have a width, then ditch it:
this.appendChild(node);
this.evictNode(node);
this._isModifying = false;
this._updateMigratedSet();
// We will now have moved stuff around; kick off some events
// so add-ons know we've just moved their stuff:
// XXXgijs: only in this window. It's hard to know for sure what's the right
// thing to do here - typically insertItem is used on each window, so
// this seems to make the most sense, even if some of the effects of
// evictNode might affect multiple windows.
CustomizableUI.dispatchToolboxEvent("customizationchange", {}, window);
CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window);
return node;
]]></body>
</method>
<method name="getMigratedItems">
<body><![CDATA[
return [... this._currentSetMigrated];
]]></body>
</method>
<method name="_updateMigratedSet">
<body><![CDATA[
let newMigratedItems = this.getMigratedItems().join(',');
if (this.getAttribute("migratedset") != newMigratedItems) {
this.setAttribute("migratedset", newMigratedItems);
this.ownerDocument.persist(this.id, "migratedset");
}
]]></body>
</method>
<property name="customizationTarget" readonly="true">
<getter><![CDATA[
return this;
]]></getter>
</property>
<property name="currentSet">
<getter><![CDATA[
return Array.from(this.children, node => node.id).join(",");
]]></getter>
<setter><![CDATA[
let v = val.split(',');
let newButtons = v.filter(x => x && (!this._whiteListed.has(x) &&
!CustomizableUI.isSpecialWidget(x) &&
!this._currentSetMigrated.has(x)));
for (let newButton of newButtons) {
this._currentSetMigrated.add(newButton);
this.insertItem(newButton);
}
this._updateMigratedSet();
]]></setter>
</property>
<property name="toolbox" readonly="true">
<getter><![CDATA[
if (!this._toolbox && this.parentNode &&
this.parentNode.localName == "toolbox") {
this._toolbox = this.parentNode;
}
return this._toolbox;
]]></getter>
</property>
<field name="_whiteListed" readonly="true">new Set(["addonbar-closebutton", "status-bar"])</field>
<field name="_isModifying">false</field>
<field name="_currentSetMigrated">new Set()</field>
</implementation>
</binding>
</bindings>

View file

@ -0,0 +1,26 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
DIRS += [
'content',
]
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
EXTRA_JS_MODULES += [
'CustomizableUI.jsm',
'CustomizableWidgets.jsm',
'CustomizeMode.jsm',
'DragPositionManager.jsm',
'PanelWideWidgetTracker.jsm',
'ScrollbarSampler.jsm',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'):
DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Toolbars and Customization')

View file

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"../../../../testing/mochitest/browser.eslintrc.js"
]
};

View file

@ -0,0 +1,154 @@
[DEFAULT]
support-files =
head.js
support/test_967000_charEncoding_page.html
support/feeds_test_page.html
support/test-feed.xml
[browser_873501_handle_specials.js]
[browser_876926_customize_mode_wrapping.js]
[browser_876944_customize_mode_create_destroy.js]
[browser_877006_missing_view.js]
[browser_877178_unregisterArea.js]
[browser_877447_skip_missing_ids.js]
[browser_878452_drag_to_panel.js]
[browser_880164_customization_context_menus.js]
[browser_880382_drag_wide_widgets_in_panel.js]
[browser_884402_customize_from_overflow.js]
skip-if = os == "linux"
[browser_885052_customize_mode_observers_disabed.js]
tags = fullscreen
# Bug 951403 - Disabled on OSX for frequent failures
skip-if = os == "mac"
[browser_885530_showInPrivateBrowsing.js]
[browser_886323_buildArea_removable_nodes.js]
[browser_887438_currentset_shim.js]
[browser_888817_currentset_updating.js]
[browser_890140_orphaned_placeholders.js]
[browser_890262_destroyWidget_after_add_to_panel.js]
[browser_892955_isWidgetRemovable_for_removed_widgets.js]
[browser_892956_destroyWidget_defaultPlacements.js]
[browser_909779_overflow_toolbars_new_window.js]
skip-if = os == "linux"
[browser_901207_searchbar_in_panel.js]
[browser_913972_currentset_overflow.js]
skip-if = os == "linux"
[browser_914138_widget_API_overflowable_toolbar.js]
skip-if = os == "linux"
[browser_914863_disabled_help_quit_buttons.js]
[browser_918049_skipintoolbarset_dnd.js]
[browser_923857_customize_mode_event_wrapping_during_reset.js]
[browser_927717_customize_drag_empty_toolbar.js]
# Bug 1163231 - Causes failures on Developer Edition on Windows 7.
# [browser_932928_show_notice_when_palette_empty.js]
[browser_934113_menubar_removable.js]
# Because this test is about the menubar, it can't be run on mac
skip-if = os == "mac"
[browser_934951_zoom_in_toolbar.js]
[browser_938980_navbar_collapsed.js]
[browser_938995_indefaultstate_nonremovable.js]
[browser_940013_registerToolbarNode_calls_registerArea.js]
[browser_940307_panel_click_closure_handling.js]
[browser_940946_removable_from_navbar_customizemode.js]
[browser_941083_invalidate_wrapper_cache_createWidget.js]
[browser_942581_unregisterArea_keeps_placements.js]
[browser_943683_migration_test.js]
[browser_944887_destroyWidget_should_destroy_in_palette.js]
[browser_945739_showInPrivateBrowsing_customize_mode.js]
[browser_947914_button_addons.js]
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_copy.js]
subsuite = clipboard
skip-if = os == "linux" # Intermittent failures on Linux
[browser_947914_button_cut.js]
subsuite = clipboard
skip-if = os == "linux" # Intermittent failures on Linux
[browser_947914_button_find.js]
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_history.js]
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_newPrivateWindow.js]
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_newWindow.js]
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_paste.js]
subsuite = clipboard
skip-if = os == "linux" # Intermittent failures on Linux
[browser_947914_button_print.js]
skip-if = os == "linux" # Intermittent failures on Linux
[browser_947914_button_savePage.js]
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_zoomIn.js]
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_zoomOut.js]
skip-if = os == "linux" # Intermittent failures
[browser_947914_button_zoomReset.js]
skip-if = os == "linux" # Intermittent failures
[browser_947987_removable_default.js]
[browser_948985_non_removable_defaultArea.js]
[browser_952963_areaType_getter_no_area.js]
[browser_956602_remove_special_widget.js]
[browser_962069_drag_to_overflow_chevron.js]
[browser_962884_opt_in_disable_hyphens.js]
[browser_963639_customizing_attribute_non_customizable_toolbar.js]
[browser_967000_button_charEncoding.js]
[browser_967000_button_feeds.js]
[browser_967000_button_sync.js]
[browser_968447_bookmarks_toolbar_items_in_panel.js]
skip-if = os == "linux" # Intemittent failures - bug 979207
[browser_968565_insert_before_hidden_items.js]
[browser_969427_recreate_destroyed_widget_after_reset.js]
[browser_969661_character_encoding_navbar_disabled.js]
[browser_970511_undo_restore_default.js]
[browser_972267_customizationchange_events.js]
[browser_973641_button_addon.js]
[browser_973932_addonbar_currentset.js]
[browser_975719_customtoolbars_behaviour.js]
[browser_976792_insertNodeInWindow.js]
skip-if = os == "linux"
[browser_978084_dragEnd_after_move.js]
[browser_980155_add_overflow_toolbar.js]
[browser_981305_separator_insertion.js]
[browser_981418-widget-onbeforecreated-handler.js]
[browser_982656_restore_defaults_builtin_widgets.js]
[browser_984455_bookmarks_items_reparenting.js]
skip-if = os == "linux"
[browser_985815_propagate_setToolbarVisibility.js]
[browser_987177_destroyWidget_xul.js]
[browser_987177_xul_wrapper_updating.js]
[browser_987185_syncButton.js]
[browser_987492_window_api.js]
[browser_987640_charEncoding.js]
[browser_988072_sidebar_events.js]
[browser_989338_saved_placements_not_resaved.js]
[browser_989751_subviewbutton_class.js]
[browser_992747_toggle_noncustomizable_toolbar.js]
[browser_993322_widget_notoolbar.js]
[browser_995164_registerArea_during_customize_mode.js]
[browser_996364_registerArea_different_properties.js]
[browser_996635_remove_non_widgets.js]
[browser_1003588_no_specials_in_panel.js]
[browser_1007336_lwthemes_in_customize_mode.js]
skip-if = os == "linux" # crashing on Linux due to bug 1271683
[browser_1008559_anchor_undo_restore.js]
[browser_1042100_default_placements_update.js]
[browser_1058573_showToolbarsDropdown.js]
[browser_1087303_button_fullscreen.js]
tags = fullscreen
skip-if = os == "mac"
[browser_1087303_button_preferences.js]
[browser_1089591_still_customizable_after_reset.js]
[browser_1096763_seen_widgets_post_reset.js]
[browser_1161838_inserted_new_default_buttons.js]
[browser_bootstrapped_custom_toolbar.js]
[browser_customizemode_contextmenu_menubuttonstate.js]
[browser_panel_toggle.js]
[browser_switch_to_customize_mode.js]
[browser_check_tooltips_in_navbar.js]

View file

@ -0,0 +1,107 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
function simulateItemDragAndEnd(aToDrag, aTarget) {
var ds = Components.classes["@mozilla.org/widget/dragservice;1"].
getService(Components.interfaces.nsIDragService);
ds.startDragSession();
try {
var [result, dataTransfer] = EventUtils.synthesizeDragOver(aToDrag.parentNode, aTarget);
EventUtils.synthesizeDropAfterDragOver(result, dataTransfer, aTarget);
// Send dragend to move dragging item back to initial place.
EventUtils.sendDragEvent({ type: "dragend", dataTransfer: dataTransfer },
aToDrag.parentNode);
} finally {
ds.endDragSession(true);
}
}
add_task(function* checkNoAddingToPanel() {
let area = CustomizableUI.AREA_PANEL;
let previousPlacements = getAreaWidgetIds(area);
CustomizableUI.addWidgetToArea("separator", area);
CustomizableUI.addWidgetToArea("spring", area);
CustomizableUI.addWidgetToArea("spacer", area);
assertAreaPlacements(area, previousPlacements);
let oldNumberOfItems = previousPlacements.length;
if (getAreaWidgetIds(area).length != oldNumberOfItems) {
CustomizableUI.reset();
}
});
add_task(function* checkAddingToToolbar() {
let area = CustomizableUI.AREA_NAVBAR;
let previousPlacements = getAreaWidgetIds(area);
CustomizableUI.addWidgetToArea("separator", area);
CustomizableUI.addWidgetToArea("spring", area);
CustomizableUI.addWidgetToArea("spacer", area);
let expectedPlacements = [...previousPlacements].concat([
/separator/,
/spring/,
/spacer/
]);
assertAreaPlacements(area, expectedPlacements);
let newlyAddedElements = getAreaWidgetIds(area).slice(-3);
while (newlyAddedElements.length) {
CustomizableUI.removeWidgetFromArea(newlyAddedElements.shift());
}
assertAreaPlacements(area, previousPlacements);
let oldNumberOfItems = previousPlacements.length;
if (getAreaWidgetIds(area).length != oldNumberOfItems) {
CustomizableUI.reset();
}
});
add_task(function* checkDragging() {
let startArea = CustomizableUI.AREA_NAVBAR;
let targetArea = CustomizableUI.AREA_PANEL;
let startingToolbarPlacements = getAreaWidgetIds(startArea);
let startingTargetPlacements = getAreaWidgetIds(targetArea);
CustomizableUI.addWidgetToArea("separator", startArea);
CustomizableUI.addWidgetToArea("spring", startArea);
CustomizableUI.addWidgetToArea("spacer", startArea);
let placementsWithSpecials = getAreaWidgetIds(startArea);
let elementsToMove = [];
for (let id of placementsWithSpecials) {
if (CustomizableUI.isSpecialWidget(id)) {
elementsToMove.push(id);
}
}
is(elementsToMove.length, 3, "Should have 3 elements to try and drag.");
yield startCustomizing();
for (let id of elementsToMove) {
simulateItemDragAndEnd(document.getElementById(id), PanelUI.contents);
}
assertAreaPlacements(startArea, placementsWithSpecials);
assertAreaPlacements(targetArea, startingTargetPlacements);
for (let id of elementsToMove) {
simulateItemDrag(document.getElementById(id), gCustomizeMode.visiblePalette);
}
assertAreaPlacements(startArea, startingToolbarPlacements);
assertAreaPlacements(targetArea, startingTargetPlacements);
ok(!gCustomizeMode.visiblePalette.querySelector("toolbarspring,toolbarseparator,toolbarspacer"),
"No specials should make it to the palette alive.");
yield endCustomizing();
});
add_task(function* asyncCleanup() {
yield endCustomizing();
CustomizableUI.reset();
});

View file

@ -0,0 +1,108 @@
/* 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/. */
"use strict";
const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}";
const {LightweightThemeManager} = Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", {});
add_task(function* () {
Services.prefs.clearUserPref("lightweightThemes.usedThemes");
Services.prefs.clearUserPref("lightweightThemes.recommendedThemes");
LightweightThemeManager.clearBuiltInThemes();
yield startCustomizing();
let themesButton = document.getElementById("customization-lwtheme-button");
let popup = document.getElementById("customization-lwtheme-menu");
let popupShownPromise = popupShown(popup);
EventUtils.synthesizeMouseAtCenter(themesButton, {});
info("Clicked on themes button");
yield popupShownPromise;
// close current tab and re-open Customize menu to confirm correct number of Themes
yield endCustomizing();
info("Exited customize mode");
yield startCustomizing();
info("Started customizing a second time");
popupShownPromise = popupShown(popup);
EventUtils.synthesizeMouseAtCenter(themesButton, {});
info("Clicked on themes button a second time");
yield popupShownPromise;
let header = document.getElementById("customization-lwtheme-menu-header");
let recommendedHeader = document.getElementById("customization-lwtheme-menu-recommended");
is(header.nextSibling.nextSibling, recommendedHeader,
"There should only be one theme (default) in the 'My Themes' section by default");
is(header.nextSibling.theme.id, DEFAULT_THEME_ID, "That theme should be the default theme");
let firstLWTheme = recommendedHeader.nextSibling;
let firstLWThemeId = firstLWTheme.theme.id;
let themeChangedPromise = promiseObserverNotified("lightweight-theme-changed");
firstLWTheme.doCommand();
info("Clicked on first theme");
yield themeChangedPromise;
popupShownPromise = popupShown(popup);
EventUtils.synthesizeMouseAtCenter(themesButton, {});
info("Clicked on themes button");
yield popupShownPromise;
is(header.nextSibling.theme.id, DEFAULT_THEME_ID, "The first theme should be the Default theme");
let installedThemeId = header.nextSibling.nextSibling.theme.id;
ok(installedThemeId.startsWith(firstLWThemeId),
"The second theme in the 'My Themes' section should be the newly installed theme: " +
"Installed theme id: " + installedThemeId + "; First theme ID: " + firstLWThemeId);
is(header.nextSibling.nextSibling.nextSibling, recommendedHeader,
"There should be two themes in the 'My Themes' section");
let defaultTheme = header.nextSibling;
defaultTheme.doCommand();
is(Services.prefs.getCharPref("lightweightThemes.selectedThemeID"), "", "No lwtheme should be selected");
// ensure current theme isn't set to "Default"
popupShownPromise = popupShown(popup);
EventUtils.synthesizeMouseAtCenter(themesButton, {});
info("Clicked on themes button a second time");
yield popupShownPromise;
firstLWTheme = recommendedHeader.nextSibling;
themeChangedPromise = promiseObserverNotified("lightweight-theme-changed");
firstLWTheme.doCommand();
info("Clicked on first theme again");
yield themeChangedPromise;
// check that "Restore Defaults" button resets theme
yield gCustomizeMode.reset();
is(LightweightThemeManager.currentTheme, null, "Current theme reset to default");
yield endCustomizing();
Services.prefs.setCharPref("lightweightThemes.usedThemes", "[]");
Services.prefs.setCharPref("lightweightThemes.recommendedThemes", "[]");
info("Removed all recommended themes");
yield startCustomizing();
popupShownPromise = popupShown(popup);
EventUtils.synthesizeMouseAtCenter(themesButton, {});
info("Clicked on themes button a second time");
yield popupShownPromise;
header = document.getElementById("customization-lwtheme-menu-header");
is(header.hidden, false, "Header should never be hidden");
is(header.nextSibling.theme.id, DEFAULT_THEME_ID, "The first theme should be the Default theme");
is(header.nextSibling.hidden, false, "The default theme should never be hidden");
recommendedHeader = document.getElementById("customization-lwtheme-menu-recommended");
is(header.nextSibling.nextSibling, recommendedHeader,
"There should only be one theme (default) in the 'My Themes' section by default");
let footer = document.getElementById("customization-lwtheme-menu-footer");
is(recommendedHeader.nextSibling.id, footer.id, "There should be no recommended themes in the menu");
is(recommendedHeader.hidden, true, "The recommendedHeader should be hidden since there are no recommended themes");
});
add_task(function* asyncCleanup() {
yield endCustomizing();
Services.prefs.clearUserPref("lightweightThemes.usedThemes");
Services.prefs.clearUserPref("lightweightThemes.recommendedThemes");
});

View file

@ -0,0 +1,71 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const kAnchorAttribute = "cui-anchorid";
/**
* Check that anchor gets set correctly when moving an item from the panel to the toolbar
* using 'undo'
*/
add_task(function*() {
yield startCustomizing();
let button = document.getElementById("history-panelmenu");
is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button",
"Button (" + button.id + ") starts out with correct anchor");
let navbar = document.getElementById("nav-bar").customizationTarget;
simulateItemDrag(button, navbar);
is(CustomizableUI.getPlacementOfWidget(button.id).area, "nav-bar",
"Button (" + button.id + ") ends up in nav-bar");
ok(!button.hasAttribute(kAnchorAttribute),
"Button (" + button.id + ") has no anchor in toolbar");
let resetButton = document.getElementById("customization-reset-button");
ok(!resetButton.hasAttribute("disabled"), "Should be able to reset now.");
yield gCustomizeMode.reset();
is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button",
"Button (" + button.id + ") has anchor again");
let undoButton = document.getElementById("customization-undo-reset-button");
ok(!undoButton.hasAttribute("disabled"), "Should be able to undo now.");
yield gCustomizeMode.undoReset();
ok(!button.hasAttribute(kAnchorAttribute),
"Button (" + button.id + ") once again has no anchor in toolbar");
yield gCustomizeMode.reset();
yield endCustomizing();
});
/**
* Check that anchor gets set correctly when moving an item from the panel to the toolbar
* using 'reset'
*/
add_task(function*() {
yield startCustomizing();
let button = document.getElementById("bookmarks-menu-button");
ok(!button.hasAttribute(kAnchorAttribute),
"Button (" + button.id + ") has no anchor in toolbar");
let panel = document.getElementById("PanelUI-contents");
simulateItemDrag(button, panel);
is(CustomizableUI.getPlacementOfWidget(button.id).area, "PanelUI-contents",
"Button (" + button.id + ") ends up in panel");
is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button",
"Button (" + button.id + ") has correct anchor in the panel");
let resetButton = document.getElementById("customization-reset-button");
ok(!resetButton.hasAttribute("disabled"), "Should be able to reset now.");
yield gCustomizeMode.reset();
ok(!button.hasAttribute(kAnchorAttribute),
"Button (" + button.id + ") once again has no anchor in toolbar");
yield endCustomizing();
});

View file

@ -0,0 +1,107 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// NB: This uses some ugly hacks to get into the CUI module from elsewhere...
// don't try this at home, kids.
function test() {
// Customize something to make sure stuff changed:
CustomizableUI.addWidgetToArea("feed-button", CustomizableUI.AREA_NAVBAR);
// Check what version we're on:
let CustomizableUIBSPass = Cu.import("resource:///modules/CustomizableUI.jsm", {});
is(CustomizableUIBSPass.gFuturePlacements.size, 0,
"All future placements should be dealt with by now.");
let {CustomizableUIInternal, gFuturePlacements, gPalette} = CustomizableUIBSPass;
CustomizableUIInternal._introduceNewBuiltinWidgets();
is(gFuturePlacements.size, 0,
"No change to future placements initially.");
let currentVersion = CustomizableUIBSPass.kVersion;
// Add our widget to the defaults:
let testWidgetNew = {
id: "test-messing-with-default-placements-new",
label: "Test messing with default placements - should be inserted",
defaultArea: CustomizableUI.AREA_NAVBAR,
introducedInVersion: currentVersion + 1,
};
let normalizedWidget = CustomizableUIInternal.normalizeWidget(testWidgetNew,
CustomizableUI.SOURCE_BUILTIN);
ok(normalizedWidget, "Widget should be normalizable");
if (!normalizedWidget) {
return;
}
CustomizableUIBSPass.gPalette.set(testWidgetNew.id, normalizedWidget);
let testWidgetOld = {
id: "test-messing-with-default-placements-old",
label: "Test messing with default placements - should NOT be inserted",
defaultArea: CustomizableUI.AREA_NAVBAR,
introducedInVersion: currentVersion,
};
normalizedWidget = CustomizableUIInternal.normalizeWidget(testWidgetOld,
CustomizableUI.SOURCE_BUILTIN);
ok(normalizedWidget, "Widget should be normalizable");
if (!normalizedWidget) {
return;
}
CustomizableUIBSPass.gPalette.set(testWidgetOld.id, normalizedWidget);
// Now increase the version in the module:
CustomizableUIBSPass.kVersion++;
let hadSavedState = !!CustomizableUIBSPass.gSavedState
if (!hadSavedState) {
CustomizableUIBSPass.gSavedState = {currentVersion: CustomizableUIBSPass.kVersion - 1};
}
// Then call the re-init routine so we re-add the builtin widgets
CustomizableUIInternal._introduceNewBuiltinWidgets();
is(gFuturePlacements.size, 1,
"Should have 1 more future placement");
let haveNavbarPlacements = gFuturePlacements.has(CustomizableUI.AREA_NAVBAR);
ok(haveNavbarPlacements, "Should have placements for nav-bar");
if (haveNavbarPlacements) {
let placements = [...gFuturePlacements.get(CustomizableUI.AREA_NAVBAR)];
// Ignore widgets that are placed using the pref facility and not the
// versioned facility. They're independent of kVersion and the saved
// state's current version, so they may be present in the placements.
for (let i = 0; i < placements.length; ) {
if (placements[i] == testWidgetNew.id) {
i++;
continue;
}
let pref = "browser.toolbarbuttons.introduced." + placements[i];
let introduced = false;
try {
introduced = Services.prefs.getBoolPref(pref);
} catch (ex) {}
if (!introduced) {
i++;
continue;
}
placements.splice(i, 1);
}
is(placements.length, 1, "Should have 1 newly placed widget in nav-bar");
is(placements[0], testWidgetNew.id, "Should have our test widget to be placed in nav-bar");
}
gFuturePlacements.delete(CustomizableUI.AREA_NAVBAR);
CustomizableUIBSPass.kVersion--;
gPalette.delete(testWidgetNew.id);
gPalette.delete(testWidgetOld.id);
if (!hadSavedState) {
CustomizableUIBSPass.gSavedState = null;
}
}

View file

@ -0,0 +1,25 @@
/* 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/. */
"use strict";
add_task(function*() {
info("Check that toggleable toolbars dropdown in always shown");
info("Remove all possible custom toolbars");
yield removeCustomToolbars();
info("Enter customization mode");
yield startCustomizing();
let toolbarsToggle = document.getElementById("customization-toolbar-visibility-button");
ok(toolbarsToggle, "The toolbars toggle dropdown exists");
ok(!toolbarsToggle.hasAttribute("hidden"),
"The toolbars toggle dropdown is displayed");
});
add_task(function* asyncCleanup() {
info("Exit customization mode");
yield endCustomizing();
});

View file

@ -0,0 +1,46 @@
/* 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/. */
"use strict";
add_task(function*() {
info("Check fullscreen button existence and functionality");
yield PanelUI.show();
let fullscreenButton = document.getElementById("fullscreen-button");
ok(fullscreenButton, "Fullscreen button appears in Panel Menu");
let fullscreenPromise = promiseFullscreenChange();
fullscreenButton.click();
yield fullscreenPromise;
ok(window.fullScreen, "Fullscreen mode was opened");
// exit full screen mode
fullscreenPromise = promiseFullscreenChange();
window.fullScreen = !window.fullScreen;
yield fullscreenPromise;
ok(!window.fullScreen, "Successfully exited fullscreen");
});
function promiseFullscreenChange() {
let deferred = Promise.defer();
info("Wait for fullscreen change");
let timeoutId = setTimeout(() => {
window.removeEventListener("fullscreen", onFullscreenChange, true);
deferred.reject("Fullscreen change did not happen within " + 20000 + "ms");
}, 20000);
function onFullscreenChange(event) {
clearTimeout(timeoutId);
window.removeEventListener("fullscreen", onFullscreenChange, true);
info("Fullscreen event received");
deferred.resolve();
}
window.addEventListener("fullscreen", onFullscreenChange, true);
return deferred.promise;
}

View file

@ -0,0 +1,50 @@
/* 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/. */
"use strict";
var newTab = null;
add_task(function*() {
info("Check preferences button existence and functionality");
yield PanelUI.show();
info("Menu panel was opened");
let preferencesButton = document.getElementById("preferences-button");
ok(preferencesButton, "Preferences button exists in Panel Menu");
preferencesButton.click();
newTab = gBrowser.selectedTab;
yield waitForPageLoad(newTab);
let openedPage = gBrowser.currentURI.spec;
is(openedPage, "about:preferences", "Preferences page was opened");
});
add_task(function asyncCleanup() {
if (gBrowser.tabs.length == 1)
gBrowser.addTab("about:blank");
gBrowser.removeTab(gBrowser.selectedTab);
info("Tabs were restored");
});
function waitForPageLoad(aTab) {
let deferred = Promise.defer();
let timeoutId = setTimeout(() => {
aTab.linkedBrowser.removeEventListener("load", onTabLoad, true);
deferred.reject("Page didn't load within " + 20000 + "ms");
}, 20000);
function onTabLoad(event) {
clearTimeout(timeoutId);
aTab.linkedBrowser.removeEventListener("load", onTabLoad, true);
info("Tab event received: " + "load");
deferred.resolve();
}
aTab.linkedBrowser.addEventListener("load", onTabLoad, true, true);
return deferred.promise;
}

View file

@ -0,0 +1,24 @@
"use strict";
// Dragging the elements again after a reset should work
add_task(function* () {
yield startCustomizing();
let historyButton = document.getElementById("wrapper-history-panelmenu");
let devButton = document.getElementById("wrapper-developer-button");
ok(historyButton && devButton, "Draggable elements should exist");
simulateItemDrag(historyButton, devButton);
yield gCustomizeMode.reset();
ok(CustomizableUI.inDefaultState, "Should be back in default state");
historyButton = document.getElementById("wrapper-history-panelmenu");
devButton = document.getElementById("wrapper-developer-button");
ok(historyButton && devButton, "Draggable elements should exist");
simulateItemDrag(historyButton, devButton);
yield endCustomizing();
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});

View file

@ -0,0 +1,31 @@
"use strict";
const BUTTONID = "test-seenwidget-post-reset";
add_task(function*() {
CustomizableUI.createWidget({
id: BUTTONID,
label: "Test widget seen post reset",
defaultArea: CustomizableUI.AREA_NAVBAR
});
const kPrefCustomizationState = "browser.uiCustomization.state";
let bsPass = Cu.import("resource:///modules/CustomizableUI.jsm", {});
ok(bsPass.gSeenWidgets.has(BUTTONID), "Widget should be seen after createWidget is called.");
CustomizableUI.reset();
ok(bsPass.gSeenWidgets.has(BUTTONID), "Widget should still be seen after reset.");
ok(!Services.prefs.prefHasUserValue(kPrefCustomizationState), "Pref shouldn't be set right now, because that'd break undo.");
CustomizableUI.addWidgetToArea(BUTTONID, CustomizableUI.AREA_NAVBAR);
gCustomizeMode.removeFromArea(document.getElementById(BUTTONID));
let hasUserValue = Services.prefs.prefHasUserValue(kPrefCustomizationState);
ok(hasUserValue, "Pref should be set right now.");
if (hasUserValue) {
let seenArray = JSON.parse(Services.prefs.getCharPref(kPrefCustomizationState)).seen;
isnot(seenArray.indexOf(BUTTONID), -1, "Widget should be in saved 'seen' list.");
}
});
registerCleanupFunction(function() {
CustomizableUI.destroyWidget(BUTTONID);
CustomizableUI.reset();
});

View file

@ -0,0 +1,78 @@
"use strict";
// NB: This uses some ugly hacks to get into the CUI module from elsewhere...
// don't try this at home, kids.
function test() {
// Customize something to make sure stuff changed:
CustomizableUI.addWidgetToArea("feed-button", CustomizableUI.AREA_NAVBAR);
let CustomizableUIBSPass = Cu.import("resource:///modules/CustomizableUI.jsm", {});
is(CustomizableUIBSPass.gFuturePlacements.size, 0,
"All future placements should be dealt with by now.");
let {CustomizableUIInternal, gFuturePlacements, gPalette} = CustomizableUIBSPass;
// Force us to have a saved state:
CustomizableUIInternal.saveState();
CustomizableUIInternal.loadSavedState();
CustomizableUIInternal._introduceNewBuiltinWidgets();
is(gFuturePlacements.size, 0,
"No change to future placements initially.");
// Add our widget to the defaults:
let testWidgetNew = {
id: "test-messing-with-default-placements-new-pref",
label: "Test messing with default placements - pref-based",
defaultArea: CustomizableUI.AREA_NAVBAR,
introducedInVersion: "pref",
};
let normalizedWidget = CustomizableUIInternal.normalizeWidget(testWidgetNew,
CustomizableUI.SOURCE_BUILTIN);
ok(normalizedWidget, "Widget should be normalizable");
if (!normalizedWidget) {
return;
}
CustomizableUIBSPass.gPalette.set(testWidgetNew.id, normalizedWidget);
// Now adjust default placements for area:
let navbarArea = CustomizableUIBSPass.gAreas.get(CustomizableUI.AREA_NAVBAR);
let navbarPlacements = navbarArea.get("defaultPlacements");
navbarPlacements.splice(navbarPlacements.indexOf("bookmarks-menu-button") + 1, 0, testWidgetNew.id);
let savedPlacements = CustomizableUIBSPass.gSavedState.placements[CustomizableUI.AREA_NAVBAR];
// Then call the re-init routine so we re-add the builtin widgets
CustomizableUIInternal._introduceNewBuiltinWidgets();
is(gFuturePlacements.size, 1,
"Should have 1 more future placement");
let futureNavbarPlacements = gFuturePlacements.get(CustomizableUI.AREA_NAVBAR);
ok(futureNavbarPlacements, "Should have placements for nav-bar");
if (futureNavbarPlacements) {
ok(futureNavbarPlacements.has(testWidgetNew.id), "widget should be in future placements");
}
CustomizableUIInternal._placeNewDefaultWidgetsInArea(CustomizableUI.AREA_NAVBAR);
let indexInSavedPlacements = savedPlacements.indexOf(testWidgetNew.id);
info("Saved placements: " + savedPlacements.join(', '));
isnot(indexInSavedPlacements, -1, "Widget should have been inserted");
is(indexInSavedPlacements, savedPlacements.indexOf("bookmarks-menu-button") + 1,
"Widget should be in the right place.");
if (futureNavbarPlacements) {
ok(!futureNavbarPlacements.has(testWidgetNew.id), "widget should be out of future placements");
}
if (indexInSavedPlacements != -1) {
savedPlacements.splice(indexInSavedPlacements, 1);
}
gFuturePlacements.delete(CustomizableUI.AREA_NAVBAR);
let indexInDefaultPlacements = navbarPlacements.indexOf(testWidgetNew.id);
if (indexInDefaultPlacements != -1) {
navbarPlacements.splice(indexInDefaultPlacements, 1);
}
gPalette.delete(testWidgetNew.id);
CustomizableUI.reset();
}

View file

@ -0,0 +1,79 @@
/* 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/. */
"use strict";
const kToolbarName = "test-specials-toolbar";
registerCleanupFunction(removeCustomToolbars);
// Add a toolbar with two springs and the downloads button.
add_task(function* addToolbarWith2SpringsAndDownloadsButton() {
// Create the toolbar with a single spring:
createToolbarWithPlacements(kToolbarName, ["spring"]);
ok(document.getElementById(kToolbarName), "Toolbar should be created.");
// Check it's there with a generated ID:
assertAreaPlacements(kToolbarName, [/customizableui-special-spring\d+/]);
let [springId] = getAreaWidgetIds(kToolbarName);
// Add a second spring, check if that's there and doesn't share IDs
CustomizableUI.addWidgetToArea("spring", kToolbarName);
assertAreaPlacements(kToolbarName, [springId,
/customizableui-special-spring\d+/]);
let [, spring2Id] = getAreaWidgetIds(kToolbarName);
isnot(springId, spring2Id, "Springs shouldn't have identical IDs.");
// Try moving the downloads button to this new toolbar, between the two springs:
CustomizableUI.addWidgetToArea("downloads-button", kToolbarName, 1);
assertAreaPlacements(kToolbarName, [springId, "downloads-button", spring2Id]);
yield removeCustomToolbars();
});
// Add separators around the downloads button.
add_task(function* addSeparatorsAroundDownloadsButton() {
createToolbarWithPlacements(kToolbarName, ["separator"]);
ok(document.getElementById(kToolbarName), "Toolbar should be created.");
// Check it's there with a generated ID:
assertAreaPlacements(kToolbarName, [/customizableui-special-separator\d+/]);
let [separatorId] = getAreaWidgetIds(kToolbarName);
CustomizableUI.addWidgetToArea("separator", kToolbarName);
assertAreaPlacements(kToolbarName, [separatorId,
/customizableui-special-separator\d+/]);
let [, separator2Id] = getAreaWidgetIds(kToolbarName);
isnot(separatorId, separator2Id, "Separator ids shouldn't be equal.");
CustomizableUI.addWidgetToArea("downloads-button", kToolbarName, 1);
assertAreaPlacements(kToolbarName, [separatorId, "downloads-button", separator2Id]);
yield removeCustomToolbars();
});
// Add spacers around the downloads button.
add_task(function* addSpacersAroundDownloadsButton() {
createToolbarWithPlacements(kToolbarName, ["spacer"]);
ok(document.getElementById(kToolbarName), "Toolbar should be created.");
// Check it's there with a generated ID:
assertAreaPlacements(kToolbarName, [/customizableui-special-spacer\d+/]);
let [spacerId] = getAreaWidgetIds(kToolbarName);
CustomizableUI.addWidgetToArea("spacer", kToolbarName);
assertAreaPlacements(kToolbarName, [spacerId,
/customizableui-special-spacer\d+/]);
let [, spacer2Id] = getAreaWidgetIds(kToolbarName);
isnot(spacerId, spacer2Id, "Spacer ids shouldn't be equal.");
CustomizableUI.addWidgetToArea("downloads-button", kToolbarName, 1);
assertAreaPlacements(kToolbarName, [spacerId, "downloads-button", spacer2Id]);
yield removeCustomToolbars();
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});

View file

@ -0,0 +1,185 @@
/* 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/. */
"use strict";
const kXULWidgetId = "a-test-button"; // we'll create a button with this ID.
const kAPIWidgetId = "feed-button";
const kPanel = CustomizableUI.AREA_PANEL;
const kToolbar = CustomizableUI.AREA_NAVBAR;
const kVisiblePalette = "customization-palette";
const kPlaceholderClass = "panel-customization-placeholder";
function checkWrapper(id) {
is(document.querySelectorAll("#wrapper-" + id).length, 1, "There should be exactly 1 wrapper for " + id + " in the customizing window.");
}
var move = {
"drag": function(id, target) {
let targetNode = document.getElementById(target);
if (targetNode.customizationTarget) {
targetNode = targetNode.customizationTarget;
}
simulateItemDrag(document.getElementById(id), targetNode);
},
"dragToItem": function(id, target) {
let targetNode = document.getElementById(target);
if (targetNode.customizationTarget) {
targetNode = targetNode.customizationTarget;
}
let items = targetNode.querySelectorAll("toolbarpaletteitem:not(." + kPlaceholderClass + ")");
if (target == kPanel) {
targetNode = items[items.length - 1];
} else {
targetNode = items[0];
}
simulateItemDrag(document.getElementById(id), targetNode);
},
"API": function(id, target) {
if (target == kVisiblePalette) {
return CustomizableUI.removeWidgetFromArea(id);
}
return CustomizableUI.addWidgetToArea(id, target, null);
}
};
function isLast(containerId, defaultPlacements, id) {
assertAreaPlacements(containerId, defaultPlacements.concat([id]));
is(document.getElementById(containerId).customizationTarget.lastChild.firstChild.id, id,
"Widget " + id + " should be in " + containerId + " in customizing window.");
is(otherWin.document.getElementById(containerId).customizationTarget.lastChild.id, id,
"Widget " + id + " should be in " + containerId + " in other window.");
}
function getLastVisibleNodeInToolbar(containerId, win=window) {
let container = win.document.getElementById(containerId).customizationTarget;
let rv = container.lastChild;
while (rv && (rv.getAttribute('hidden') == 'true' || (rv.firstChild && rv.firstChild.getAttribute('hidden') == 'true'))) {
rv = rv.previousSibling;
}
return rv;
}
function isLastVisibleInToolbar(containerId, defaultPlacements, id) {
let newPlacements;
for (let i = defaultPlacements.length - 1; i >= 0; i--) {
let el = document.getElementById(defaultPlacements[i]);
if (el && el.getAttribute('hidden') != 'true') {
newPlacements = [...defaultPlacements];
newPlacements.splice(i + 1, 0, id);
break;
}
}
if (!newPlacements) {
assertAreaPlacements(containerId, defaultPlacements.concat([id]));
} else {
assertAreaPlacements(containerId, newPlacements);
}
is(getLastVisibleNodeInToolbar(containerId).firstChild.id, id,
"Widget " + id + " should be in " + containerId + " in customizing window.");
is(getLastVisibleNodeInToolbar(containerId, otherWin).id, id,
"Widget " + id + " should be in " + containerId + " in other window.");
}
function isFirst(containerId, defaultPlacements, id) {
assertAreaPlacements(containerId, [id].concat(defaultPlacements));
is(document.getElementById(containerId).customizationTarget.firstChild.firstChild.id, id,
"Widget " + id + " should be in " + containerId + " in customizing window.");
is(otherWin.document.getElementById(containerId).customizationTarget.firstChild.id, id,
"Widget " + id + " should be in " + containerId + " in other window.");
}
function checkToolbar(id, method) {
// Place at start of the toolbar:
let toolbarPlacements = getAreaWidgetIds(kToolbar);
move[method](id, kToolbar);
if (method == "dragToItem") {
isFirst(kToolbar, toolbarPlacements, id);
} else if (method == "drag") {
isLastVisibleInToolbar(kToolbar, toolbarPlacements, id);
} else {
isLast(kToolbar, toolbarPlacements, id);
}
checkWrapper(id);
}
function checkPanel(id, method) {
let panelPlacements = getAreaWidgetIds(kPanel);
move[method](id, kPanel);
let children = document.getElementById(kPanel).querySelectorAll("toolbarpaletteitem:not(." + kPlaceholderClass + ")");
let otherChildren = otherWin.document.getElementById(kPanel).children;
let newPlacements = panelPlacements.concat([id]);
// Relative position of the new item from the end:
let position = -1;
// For the drag to item case, we drag to the last item, making the dragged item the
// penultimate item. We can't well use the first item because the panel has complicated
// rules about rearranging wide items (which, by default, the first two items are).
if (method == "dragToItem") {
newPlacements.pop();
newPlacements.splice(panelPlacements.length - 1, 0, id);
position = -2;
}
assertAreaPlacements(kPanel, newPlacements);
is(children[children.length + position].firstChild.id, id,
"Widget " + id + " should be in " + kPanel + " in customizing window.");
is(otherChildren[otherChildren.length + position].id, id,
"Widget " + id + " should be in " + kPanel + " in other window.");
checkWrapper(id);
}
function checkPalette(id, method) {
// Move back to palette:
move[method](id, kVisiblePalette);
ok(CustomizableUI.inDefaultState, "Should end in default state");
let visibleChildren = gCustomizeMode.visiblePalette.children;
let expectedChild = method == "dragToItem" ? visibleChildren[0] : visibleChildren[visibleChildren.length - 1];
is(expectedChild.firstChild.id, id, "Widget " + id + " was moved using " + method + " and should now be wrapped in palette in customizing window.");
if (id == kXULWidgetId) {
ok(otherWin.gNavToolbox.palette.querySelector("#" + id), "Widget " + id + " should be in invisible palette in other window.");
}
checkWrapper(id);
}
// This test needs a XUL button that's in the palette by default. No such
// button currently exists, so we create a simple one.
function createXULButtonForWindow(win) {
createDummyXULButton(kXULWidgetId, "test-button", win);
}
function removeXULButtonForWindow(win) {
win.gNavToolbox.palette.querySelector(`#${kXULWidgetId}`).remove();
}
var otherWin;
// Moving widgets in two windows, one with customize mode and one without, should work.
add_task(function* MoveWidgetsInTwoWindows() {
yield startCustomizing();
otherWin = yield openAndLoadWindow(null, true);
yield otherWin.PanelUI.ensureReady();
// Create the XUL button to use in the test in both windows.
createXULButtonForWindow(window);
createXULButtonForWindow(otherWin);
ok(CustomizableUI.inDefaultState, "Should start in default state");
for (let widgetId of [kXULWidgetId, kAPIWidgetId]) {
for (let method of ["API", "drag", "dragToItem"]) {
info("Moving widget " + widgetId + " using " + method);
checkToolbar(widgetId, method);
checkPanel(widgetId, method);
checkPalette(widgetId, method);
checkPanel(widgetId, method);
checkToolbar(widgetId, method);
checkPalette(widgetId, method);
}
}
yield promiseWindowClosed(otherWin);
otherWin = null;
yield endCustomizing();
removeXULButtonForWindow(window);
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});

View file

@ -0,0 +1,61 @@
/* 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/. */
"use strict";
const kTestWidget1 = "test-customize-mode-create-destroy1";
const kTestWidget2 = "test-customize-mode-create-destroy2";
// Creating and destroying a widget should correctly wrap/unwrap stuff
add_task(function* testWrapUnwrap() {
yield startCustomizing();
CustomizableUI.createWidget({id: kTestWidget1, label: 'Pretty label', tooltiptext: 'Pretty tooltip'});
let elem = document.getElementById(kTestWidget1);
let wrapper = document.getElementById("wrapper-" + kTestWidget1);
ok(elem, "There should be an item");
ok(wrapper, "There should be a wrapper");
is(wrapper.firstChild.id, kTestWidget1, "Wrapper should have test widget");
is(wrapper.parentNode.id, "customization-palette", "Wrapper should be in palette");
CustomizableUI.destroyWidget(kTestWidget1);
wrapper = document.getElementById("wrapper-" + kTestWidget1);
ok(!wrapper, "There should be a wrapper");
let item = document.getElementById(kTestWidget1);
ok(!item, "There should no longer be an item");
});
// Creating and destroying a widget should correctly deal with panel placeholders
add_task(function* testPanelPlaceholders() {
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
// The value of expectedPlaceholders depends on the default palette layout.
// Bug 1229236 is for these tests to be smarter so the test doesn't need to
// change when the default placements change.
let expectedPlaceholders = 1 + (isInDevEdition() ? 1 : 0);
is(panel.querySelectorAll(".panel-customization-placeholder").length, expectedPlaceholders, "The number of placeholders should be correct.");
CustomizableUI.createWidget({id: kTestWidget2, label: 'Pretty label', tooltiptext: 'Pretty tooltip', defaultArea: CustomizableUI.AREA_PANEL});
let elem = document.getElementById(kTestWidget2);
let wrapper = document.getElementById("wrapper-" + kTestWidget2);
ok(elem, "There should be an item");
ok(wrapper, "There should be a wrapper");
is(wrapper.firstChild.id, kTestWidget2, "Wrapper should have test widget");
is(wrapper.parentNode, panel, "Wrapper should be in panel");
expectedPlaceholders = isInDevEdition() ? 1 : 3;
is(panel.querySelectorAll(".panel-customization-placeholder").length, expectedPlaceholders, "The number of placeholders should be correct.");
CustomizableUI.destroyWidget(kTestWidget2);
wrapper = document.getElementById("wrapper-" + kTestWidget2);
ok(!wrapper, "There should be a wrapper");
let item = document.getElementById(kTestWidget2);
ok(!item, "There should no longer be an item");
yield endCustomizing();
});
add_task(function* asyncCleanup() {
yield endCustomizing();
try {
CustomizableUI.destroyWidget(kTestWidget1);
} catch (ex) {}
try {
CustomizableUI.destroyWidget(kTestWidget2);
} catch (ex) {}
yield resetCustomization();
});

View file

@ -0,0 +1,41 @@
/* 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/. */
"use strict";
// Should be able to add broken view widget
add_task(function testAddbrokenViewWidget() {
const kWidgetId = 'test-877006-broken-widget';
let widgetSpec = {
id: kWidgetId,
type: 'view',
viewId: 'idontexist',
/* Empty handler so we try to attach it maybe? */
onViewShowing: function() {
}
};
let noError = true;
try {
CustomizableUI.createWidget(widgetSpec);
CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR);
} catch (ex) {
Cu.reportError(ex);
noError = false;
}
ok(noError, "Should not throw an exception trying to add a broken view widget.");
noError = true;
try {
CustomizableUI.destroyWidget(kWidgetId);
} catch (ex) {
Cu.reportError(ex);
noError = false;
}
ok(noError, "Should not throw an exception trying to remove the broken view widget.");
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});

View file

@ -0,0 +1,50 @@
/* 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/. */
"use strict";
registerCleanupFunction(removeCustomToolbars);
// Sanity checks
add_task(function sanityChecks() {
SimpleTest.doesThrow(() => CustomizableUI.registerArea("@foo"),
"Registering areas with an invalid ID should throw.");
SimpleTest.doesThrow(() => CustomizableUI.registerArea([]),
"Registering areas with an invalid ID should throw.");
SimpleTest.doesThrow(() => CustomizableUI.unregisterArea("@foo"),
"Unregistering areas with an invalid ID should throw.");
SimpleTest.doesThrow(() => CustomizableUI.unregisterArea([]),
"Unregistering areas with an invalid ID should throw.");
SimpleTest.doesThrow(() => CustomizableUI.unregisterArea("unknown"),
"Unregistering an area that's not registered should throw.");
});
// Check areas are loaded with their default placements.
add_task(function checkLoadedAres() {
ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
});
// Check registering and unregistering a new area.
add_task(function checkRegisteringAndUnregistering() {
const kToolbarId = "test-registration-toolbar";
const kButtonId = "test-registration-button";
createDummyXULButton(kButtonId);
createToolbarWithPlacements(kToolbarId, ["spring", kButtonId, "spring"]);
assertAreaPlacements(kToolbarId,
[/customizableui-special-spring\d+/,
kButtonId,
/customizableui-special-spring\d+/]);
ok(!CustomizableUI.inDefaultState, "With a new toolbar it is no longer in a default state.");
removeCustomToolbars(); // Will call unregisterArea for us
ok(CustomizableUI.inDefaultState, "When the toolbar is unregistered, " +
"everything will return to the default state.");
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});

View file

@ -0,0 +1,25 @@
/* 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/. */
"use strict";
registerCleanupFunction(removeCustomToolbars);
add_task(function skipMissingIDS() {
const kButtonId = "look-at-me-disappear-button";
CustomizableUI.reset();
ok(CustomizableUI.inDefaultState, "Should be in the default state.");
let btn = createDummyXULButton(kButtonId, "Gone!");
CustomizableUI.addWidgetToArea(kButtonId, CustomizableUI.AREA_NAVBAR);
ok(!CustomizableUI.inDefaultState, "Should no longer be in the default state.");
is(btn.parentNode.parentNode.id, CustomizableUI.AREA_NAVBAR, "Button should be in navbar");
btn.remove();
is(btn.parentNode, null, "Button is no longer in the navbar");
ok(CustomizableUI.inDefaultState, "Should be back in the default state, " +
"despite unknown button ID in placements.");
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});

View file

@ -0,0 +1,65 @@
/* 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/. */
"use strict";
// Dragging an item from the palette to another button in the panel should work.
add_task(function*() {
yield startCustomizing();
let btn = document.getElementById("feed-button");
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
let lastButtonIndex = placements.length - 1;
let lastButton = placements[lastButtonIndex];
let placementsAfterInsert = placements.slice(0, lastButtonIndex).concat(["feed-button", lastButton]);
let lastButtonNode = document.getElementById(lastButton);
simulateItemDrag(btn, lastButtonNode);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let palette = document.getElementById("customization-palette");
simulateItemDrag(btn, palette);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
// Dragging an item from the palette to the panel itself should also work.
add_task(function*() {
yield startCustomizing();
let btn = document.getElementById("feed-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
let placementsAfterAppend = placements.concat(["feed-button"]);
simulateItemDrag(btn, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let palette = document.getElementById("customization-palette");
simulateItemDrag(btn, palette);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
// Dragging an item from the palette to an empty panel should also work.
add_task(function*() {
let widgetIds = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
while (widgetIds.length) {
CustomizableUI.removeWidgetFromArea(widgetIds.shift());
}
yield startCustomizing();
let btn = document.getElementById("feed-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
assertAreaPlacements(panel.id, []);
let placementsAfterAppend = ["feed-button"];
simulateItemDrag(btn, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let palette = document.getElementById("customization-palette");
simulateItemDrag(btn, palette);
assertAreaPlacements(panel.id, []);
});
add_task(function* asyncCleanup() {
yield endCustomizing();
yield resetCustomization();
});

View file

@ -0,0 +1,414 @@
/* 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/. */
"use strict";
requestLongerTimeout(2);
const isOSX = (Services.appinfo.OS === "Darwin");
// Right-click on the home button should
// show a context menu with options to move it.
add_task(function*() {
let contextMenu = document.getElementById("toolbar-context-menu");
let shownPromise = popupShown(contextMenu);
let homeButton = document.getElementById("home-button");
EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2 });
yield shownPromise;
let expectedEntries = [
[".customize-context-moveToPanel", true],
[".customize-context-removeFromToolbar", true],
["---"]
];
if (!isOSX) {
expectedEntries.push(["#toggle_toolbar-menubar", true]);
}
expectedEntries.push(
["#toggle_PersonalToolbar", true],
["---"],
[".viewCustomizeToolbar", true]
);
checkContextMenu(contextMenu, expectedEntries);
let hiddenPromise = popupHidden(contextMenu);
contextMenu.hidePopup();
yield hiddenPromise;
});
// Right-click on an empty bit of tabstrip should
// show a context menu without options to move it,
// but with tab-specific options instead.
add_task(function*() {
// ensure there are tabs to reload/bookmark:
let extraTab = gBrowser.selectedTab = gBrowser.addTab();
yield promiseTabLoadEvent(extraTab, "http://example.com/");
let contextMenu = document.getElementById("toolbar-context-menu");
let shownPromise = popupShown(contextMenu);
let tabstrip = document.getElementById("tabbrowser-tabs");
let rect = tabstrip.getBoundingClientRect();
EventUtils.synthesizeMouse(tabstrip, rect.width - 2, 2, {type: "contextmenu", button: 2 });
yield shownPromise;
let closedTabsAvailable = SessionStore.getClosedTabCount(window) == 0;
info("Closed tabs: " + closedTabsAvailable);
let expectedEntries = [
["#toolbar-context-reloadAllTabs", true],
["#toolbar-context-bookmarkAllTabs", true],
["#toolbar-context-undoCloseTab", !closedTabsAvailable],
["---"]
];
if (!isOSX) {
expectedEntries.push(["#toggle_toolbar-menubar", true]);
}
expectedEntries.push(
["#toggle_PersonalToolbar", true],
["---"],
[".viewCustomizeToolbar", true]
);
checkContextMenu(contextMenu, expectedEntries);
let hiddenPromise = popupHidden(contextMenu);
contextMenu.hidePopup();
yield hiddenPromise;
gBrowser.removeTab(extraTab);
});
// Right-click on an empty bit of extra toolbar should
// show a context menu with moving options disabled,
// and a toggle option for the extra toolbar
add_task(function*() {
let contextMenu = document.getElementById("toolbar-context-menu");
let shownPromise = popupShown(contextMenu);
let toolbar = createToolbarWithPlacements("880164_empty_toolbar", []);
toolbar.setAttribute("context", "toolbar-context-menu");
toolbar.setAttribute("toolbarname", "Fancy Toolbar for Context Menu");
EventUtils.synthesizeMouseAtCenter(toolbar, {type: "contextmenu", button: 2 });
yield shownPromise;
let expectedEntries = [
[".customize-context-moveToPanel", false],
[".customize-context-removeFromToolbar", false],
["---"]
];
if (!isOSX) {
expectedEntries.push(["#toggle_toolbar-menubar", true]);
}
expectedEntries.push(
["#toggle_PersonalToolbar", true],
["#toggle_880164_empty_toolbar", true],
["---"],
[".viewCustomizeToolbar", true]
);
checkContextMenu(contextMenu, expectedEntries);
let hiddenPromise = popupHidden(contextMenu);
contextMenu.hidePopup();
yield hiddenPromise;
removeCustomToolbars();
});
// Right-click on the urlbar-container should
// show a context menu with disabled options to move it.
add_task(function*() {
let contextMenu = document.getElementById("toolbar-context-menu");
let shownPromise = popupShown(contextMenu);
let urlBarContainer = document.getElementById("urlbar-container");
// Need to make sure not to click within an edit field.
EventUtils.synthesizeMouse(urlBarContainer, 100, 1, {type: "contextmenu", button: 2 });
yield shownPromise;
let expectedEntries = [
[".customize-context-moveToPanel", false],
[".customize-context-removeFromToolbar", false],
["---"]
];
if (!isOSX) {
expectedEntries.push(["#toggle_toolbar-menubar", true]);
}
expectedEntries.push(
["#toggle_PersonalToolbar", true],
["---"],
[".viewCustomizeToolbar", true]
);
checkContextMenu(contextMenu, expectedEntries);
let hiddenPromise = popupHidden(contextMenu);
contextMenu.hidePopup();
yield hiddenPromise;
});
// Right-click on the searchbar and moving it to the menu
// and back should move the search-container instead.
add_task(function*() {
let searchbar = document.getElementById("searchbar");
gCustomizeMode.addToPanel(searchbar);
let placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel");
let shownPanelPromise = promisePanelShown(window);
PanelUI.toggle({type: "command"});
yield shownPanelPromise;
let hiddenPanelPromise = promisePanelHidden(window);
PanelUI.toggle({type: "command"});
yield hiddenPanelPromise;
gCustomizeMode.addToToolbar(searchbar);
placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in navbar");
gCustomizeMode.removeFromArea(searchbar);
placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement, null, "Should be in palette");
CustomizableUI.reset();
placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in navbar");
});
// Right-click on an item within the menu panel should
// show a context menu with options to move it.
add_task(function*() {
let shownPanelPromise = promisePanelShown(window);
PanelUI.toggle({type: "command"});
yield shownPanelPromise;
let contextMenu = document.getElementById("customizationPanelItemContextMenu");
let shownContextPromise = popupShown(contextMenu);
let newWindowButton = document.getElementById("new-window-button");
ok(newWindowButton, "new-window-button was found");
EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
yield shownContextPromise;
is(PanelUI.panel.state, "open", "The PanelUI should still be open.");
let expectedEntries = [
[".customize-context-moveToToolbar", true],
[".customize-context-removeFromPanel", true],
["---"],
[".viewCustomizeToolbar", true]
];
checkContextMenu(contextMenu, expectedEntries);
let hiddenContextPromise = popupHidden(contextMenu);
contextMenu.hidePopup();
yield hiddenContextPromise;
let hiddenPromise = promisePanelHidden(window);
PanelUI.toggle({type: "command"});
yield hiddenPromise;
});
// Right-click on the home button while in customization mode
// should show a context menu with options to move it.
add_task(function*() {
yield startCustomizing();
let contextMenu = document.getElementById("toolbar-context-menu");
let shownPromise = popupShown(contextMenu);
let homeButton = document.getElementById("wrapper-home-button");
EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2});
yield shownPromise;
let expectedEntries = [
[".customize-context-moveToPanel", true],
[".customize-context-removeFromToolbar", true],
["---"]
];
if (!isOSX) {
expectedEntries.push(["#toggle_toolbar-menubar", true]);
}
expectedEntries.push(
["#toggle_PersonalToolbar", true],
["---"],
[".viewCustomizeToolbar", false]
);
checkContextMenu(contextMenu, expectedEntries);
let hiddenContextPromise = popupHidden(contextMenu);
contextMenu.hidePopup();
yield hiddenContextPromise;
});
// Right-click on an item in the palette should
// show a context menu with options to move it.
add_task(function*() {
let contextMenu = document.getElementById("customizationPaletteItemContextMenu");
let shownPromise = popupShown(contextMenu);
let openFileButton = document.getElementById("wrapper-open-file-button");
EventUtils.synthesizeMouse(openFileButton, 2, 2, {type: "contextmenu", button: 2});
yield shownPromise;
let expectedEntries = [
[".customize-context-addToToolbar", true],
[".customize-context-addToPanel", true]
];
checkContextMenu(contextMenu, expectedEntries);
let hiddenContextPromise = popupHidden(contextMenu);
contextMenu.hidePopup();
yield hiddenContextPromise;
});
// Right-click on an item in the panel while in customization mode
// should show a context menu with options to move it.
add_task(function*() {
let contextMenu = document.getElementById("customizationPanelItemContextMenu");
let shownPromise = popupShown(contextMenu);
let newWindowButton = document.getElementById("wrapper-new-window-button");
EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
yield shownPromise;
let expectedEntries = [
[".customize-context-moveToToolbar", true],
[".customize-context-removeFromPanel", true],
["---"],
[".viewCustomizeToolbar", false]
];
checkContextMenu(contextMenu, expectedEntries);
let hiddenContextPromise = popupHidden(contextMenu);
contextMenu.hidePopup();
yield hiddenContextPromise;
yield endCustomizing();
});
// Test the toolbarbutton panel context menu in customization mode
// without opening the panel before customization mode
add_task(function*() {
this.otherWin = yield openAndLoadWindow(null, true);
yield new Promise(resolve => waitForFocus(resolve, this.otherWin));
yield startCustomizing(this.otherWin);
let contextMenu = this.otherWin.document.getElementById("customizationPanelItemContextMenu");
let shownPromise = popupShown(contextMenu);
let newWindowButton = this.otherWin.document.getElementById("wrapper-new-window-button");
EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2}, this.otherWin);
yield shownPromise;
let expectedEntries = [
[".customize-context-moveToToolbar", true],
[".customize-context-removeFromPanel", true],
["---"],
[".viewCustomizeToolbar", false]
];
checkContextMenu(contextMenu, expectedEntries, this.otherWin);
let hiddenContextPromise = popupHidden(contextMenu);
contextMenu.hidePopup();
yield hiddenContextPromise;
yield endCustomizing(this.otherWin);
yield promiseWindowClosed(this.otherWin);
this.otherWin = null;
yield new Promise(resolve => waitForFocus(resolve, window));
});
// Bug 945191 - Combined buttons show wrong context menu options
// when they are in the toolbar.
add_task(function*() {
yield startCustomizing();
let contextMenu = document.getElementById("customizationPanelItemContextMenu");
let shownPromise = popupShown(contextMenu);
let zoomControls = document.getElementById("wrapper-zoom-controls");
EventUtils.synthesizeMouse(zoomControls, 2, 2, {type: "contextmenu", button: 2});
yield shownPromise;
// Execute the command to move the item from the panel to the toolbar.
contextMenu.childNodes[0].doCommand();
let hiddenPromise = popupHidden(contextMenu);
contextMenu.hidePopup();
yield hiddenPromise;
yield endCustomizing();
zoomControls = document.getElementById("zoom-controls");
is(zoomControls.parentNode.id, "nav-bar-customization-target", "Zoom-controls should be on the nav-bar");
contextMenu = document.getElementById("toolbar-context-menu");
shownPromise = popupShown(contextMenu);
EventUtils.synthesizeMouse(zoomControls, 2, 2, {type: "contextmenu", button: 2});
yield shownPromise;
let expectedEntries = [
[".customize-context-moveToPanel", true],
[".customize-context-removeFromToolbar", true],
["---"]
];
if (!isOSX) {
expectedEntries.push(["#toggle_toolbar-menubar", true]);
}
expectedEntries.push(
["#toggle_PersonalToolbar", true],
["---"],
[".viewCustomizeToolbar", true]
);
checkContextMenu(contextMenu, expectedEntries);
hiddenPromise = popupHidden(contextMenu);
contextMenu.hidePopup();
yield hiddenPromise;
yield resetCustomization();
});
// Bug 947586 - After customization, panel items show wrong context menu options
add_task(function*() {
yield startCustomizing();
yield endCustomizing();
yield PanelUI.show();
let contextMenu = document.getElementById("customizationPanelItemContextMenu");
let shownContextPromise = popupShown(contextMenu);
let newWindowButton = document.getElementById("new-window-button");
ok(newWindowButton, "new-window-button was found");
EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
yield shownContextPromise;
is(PanelUI.panel.state, "open", "The PanelUI should still be open.");
let expectedEntries = [
[".customize-context-moveToToolbar", true],
[".customize-context-removeFromPanel", true],
["---"],
[".viewCustomizeToolbar", true]
];
checkContextMenu(contextMenu, expectedEntries);
let hiddenContextPromise = popupHidden(contextMenu);
contextMenu.hidePopup();
yield hiddenContextPromise;
let hiddenPromise = promisePanelHidden(window);
PanelUI.hide();
yield hiddenPromise;
});
// Bug 982027 - moving icon around removes custom context menu.
add_task(function*() {
let widgetId = "custom-context-menu-toolbarbutton";
let expectedContext = "myfancycontext";
let widget = createDummyXULButton(widgetId, "Test ctxt menu");
widget.setAttribute("context", expectedContext);
CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_NAVBAR);
is(widget.getAttribute("context"), expectedContext, "Should have context menu when added to the toolbar.");
yield startCustomizing();
is(widget.getAttribute("context"), "", "Should not have own context menu in the toolbar now that we're customizing.");
is(widget.getAttribute("wrapped-context"), expectedContext, "Should keep own context menu wrapped when in toolbar.");
let panel = PanelUI.contents;
simulateItemDrag(widget, panel);
is(widget.getAttribute("context"), "", "Should not have own context menu when in the panel.");
is(widget.getAttribute("wrapped-context"), expectedContext, "Should keep own context menu wrapped now that we're in the panel.");
simulateItemDrag(widget, document.getElementById("nav-bar").customizationTarget);
is(widget.getAttribute("context"), "", "Should not have own context menu when back in toolbar because we're still customizing.");
is(widget.getAttribute("wrapped-context"), expectedContext, "Should keep own context menu wrapped now that we're back in the toolbar.");
yield endCustomizing();
is(widget.getAttribute("context"), expectedContext, "Should have context menu again now that we're out of customize mode.");
CustomizableUI.removeWidgetFromArea(widgetId);
widget.remove();
ok(CustomizableUI.inDefaultState, "Should be in default state after removing button.");
});

View file

@ -0,0 +1,497 @@
/* 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/. */
"use strict";
requestLongerTimeout(5);
// Dragging the zoom controls to be before the print button should not move any controls.
add_task(function*() {
yield startCustomizing();
let zoomControls = document.getElementById("zoom-controls");
let printButton = document.getElementById("print-button");
let placementsAfterMove = ["edit-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"zoom-controls",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"developer-button",
"sync-button",
];
removeDeveloperButtonIfDevEdition(placementsAfterMove);
simulateItemDrag(zoomControls, printButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let newWindowButton = document.getElementById("new-window-button");
simulateItemDrag(zoomControls, newWindowButton);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
// Dragging the zoom controls to be before the save button should not move any controls.
add_task(function*() {
yield startCustomizing();
let zoomControls = document.getElementById("zoom-controls");
let savePageButton = document.getElementById("save-page-button");
let placementsAfterMove = ["edit-controls",
"zoom-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"developer-button",
"sync-button",
];
removeDeveloperButtonIfDevEdition(placementsAfterMove);
simulateItemDrag(zoomControls, savePageButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
ok(CustomizableUI.inDefaultState, "Should be in default state.");
});
// Dragging the zoom controls to be before the new-window button should not move any widgets.
add_task(function*() {
yield startCustomizing();
let zoomControls = document.getElementById("zoom-controls");
let newWindowButton = document.getElementById("new-window-button");
let placementsAfterMove = ["edit-controls",
"zoom-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"developer-button",
"sync-button",
];
removeDeveloperButtonIfDevEdition(placementsAfterMove);
simulateItemDrag(zoomControls, newWindowButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
});
// Dragging the zoom controls to be before the history-panelmenu should move the zoom-controls in to the row higher than the history-panelmenu.
add_task(function*() {
yield startCustomizing();
let zoomControls = document.getElementById("zoom-controls");
let historyPanelMenu = document.getElementById("history-panelmenu");
let placementsAfterMove = ["edit-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"zoom-controls",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"developer-button",
"sync-button",
];
removeDeveloperButtonIfDevEdition(placementsAfterMove);
simulateItemDrag(zoomControls, historyPanelMenu);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let newWindowButton = document.getElementById("new-window-button");
simulateItemDrag(zoomControls, newWindowButton);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
// Dragging the zoom controls to be before the preferences-button should move the zoom-controls
// in to the row higher than the preferences-button.
add_task(function*() {
yield startCustomizing();
let zoomControls = document.getElementById("zoom-controls");
let preferencesButton = document.getElementById("preferences-button");
let placementsAfterMove = ["edit-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"zoom-controls",
"find-button",
"preferences-button",
"add-ons-button",
"developer-button",
"sync-button",
];
removeDeveloperButtonIfDevEdition(placementsAfterMove);
simulateItemDrag(zoomControls, preferencesButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let newWindowButton = document.getElementById("new-window-button");
simulateItemDrag(zoomControls, newWindowButton);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
// Dragging an item from the palette to before the zoom-controls should move it and two other buttons before the zoom controls.
add_task(function*() {
yield startCustomizing();
let openFileButton = document.getElementById("open-file-button");
let zoomControls = document.getElementById("zoom-controls");
let placementsAfterInsert = ["edit-controls",
"open-file-button",
"new-window-button",
"privatebrowsing-button",
"zoom-controls",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"developer-button",
"sync-button",
];
removeDeveloperButtonIfDevEdition(placementsAfterInsert);
simulateItemDrag(openFileButton, zoomControls);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let palette = document.getElementById("customization-palette");
// Check that the palette items are re-wrapped correctly.
let feedWrapper = document.getElementById("wrapper-feed-button");
let feedButton = document.getElementById("feed-button");
is(feedButton.parentNode, feedWrapper,
"feed-button should be a child of wrapper-feed-button");
is(feedWrapper.getAttribute("place"), "palette",
"The feed-button wrapper should have it's place set to 'palette'");
simulateItemDrag(openFileButton, palette);
is(openFileButton.parentNode.tagName, "toolbarpaletteitem",
"The open-file-button should be wrapped by a toolbarpaletteitem");
let newWindowButton = document.getElementById("new-window-button");
simulateItemDrag(zoomControls, newWindowButton);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
// Dragging an item from the palette to before the edit-controls
// should move it and two other buttons before the edit and zoom controls.
add_task(function*() {
yield startCustomizing();
let openFileButton = document.getElementById("open-file-button");
let editControls = document.getElementById("edit-controls");
let placementsAfterInsert = ["open-file-button",
"new-window-button",
"privatebrowsing-button",
"edit-controls",
"zoom-controls",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"developer-button",
"sync-button",
];
removeDeveloperButtonIfDevEdition(placementsAfterInsert);
simulateItemDrag(openFileButton, editControls);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
let palette = document.getElementById("customization-palette");
// Check that the palette items are re-wrapped correctly.
let feedWrapper = document.getElementById("wrapper-feed-button");
let feedButton = document.getElementById("feed-button");
is(feedButton.parentNode, feedWrapper,
"feed-button should be a child of wrapper-feed-button");
is(feedWrapper.getAttribute("place"), "palette",
"The feed-button wrapper should have it's place set to 'palette'");
simulateItemDrag(openFileButton, palette);
is(openFileButton.parentNode.tagName, "toolbarpaletteitem",
"The open-file-button should be wrapped by a toolbarpaletteitem");
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
// Dragging the edit-controls to be before the zoom-controls button
// should not move any widgets.
add_task(function*() {
yield startCustomizing();
let editControls = document.getElementById("edit-controls");
let zoomControls = document.getElementById("zoom-controls");
let placementsAfterMove = ["edit-controls",
"zoom-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"developer-button",
"sync-button",
];
removeDeveloperButtonIfDevEdition(placementsAfterMove);
simulateItemDrag(editControls, zoomControls);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
});
// Dragging the edit-controls to be before the new-window-button should
// move the zoom-controls before the edit-controls.
add_task(function*() {
yield startCustomizing();
let editControls = document.getElementById("edit-controls");
let newWindowButton = document.getElementById("new-window-button");
let placementsAfterMove = ["zoom-controls",
"edit-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"developer-button",
"sync-button",
];
removeDeveloperButtonIfDevEdition(placementsAfterMove);
simulateItemDrag(editControls, newWindowButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(editControls, zoomControls);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
});
// Dragging the edit-controls to be before the privatebrowsing-button
// should move the edit-controls in to the row higher than the
// privatebrowsing-button.
add_task(function*() {
yield startCustomizing();
let editControls = document.getElementById("edit-controls");
let privateBrowsingButton = document.getElementById("privatebrowsing-button");
let placementsAfterMove = ["zoom-controls",
"edit-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"developer-button",
"sync-button",
];
removeDeveloperButtonIfDevEdition(placementsAfterMove);
simulateItemDrag(editControls, privateBrowsingButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(editControls, zoomControls);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
});
// Dragging the edit-controls to be before the save-page-button
// should move the edit-controls in to the row higher than the
// save-page-button.
add_task(function*() {
yield startCustomizing();
let editControls = document.getElementById("edit-controls");
let savePageButton = document.getElementById("save-page-button");
let placementsAfterMove = ["zoom-controls",
"edit-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"developer-button",
"sync-button",
];
removeDeveloperButtonIfDevEdition(placementsAfterMove);
simulateItemDrag(editControls, savePageButton);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(editControls, zoomControls);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
});
// Dragging the edit-controls to the panel itself should append
// the edit controls to the bottom of the panel.
add_task(function*() {
yield startCustomizing();
let editControls = document.getElementById("edit-controls");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let placementsAfterMove = ["zoom-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"edit-controls",
"developer-button",
"sync-button",
];
removeDeveloperButtonIfDevEdition(placementsAfterMove);
simulateItemDrag(editControls, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(editControls, zoomControls);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
});
// Dragging the edit-controls to the customization-palette and
// back should work.
add_task(function*() {
yield startCustomizing();
let editControls = document.getElementById("edit-controls");
let palette = document.getElementById("customization-palette");
let placementsAfterMove = ["zoom-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"developer-button",
"sync-button",
];
removeDeveloperButtonIfDevEdition(placementsAfterMove);
let paletteChildElementCount = palette.childElementCount;
simulateItemDrag(editControls, palette);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
is(paletteChildElementCount + 1, palette.childElementCount,
"The palette should have a new child, congratulations!");
is(editControls.parentNode.id, "wrapper-edit-controls",
"The edit-controls should be properly wrapped.");
is(editControls.parentNode.getAttribute("place"), "palette",
"The edit-controls should have the place of 'palette'.");
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(editControls, zoomControls);
is(paletteChildElementCount, palette.childElementCount,
"The palette child count should have returned to its prior value.");
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
});
// Dragging the edit-controls to each of the panel placeholders
// should append the edit-controls to the bottom of the panel.
add_task(function*() {
yield startCustomizing();
let editControls = document.getElementById("edit-controls");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let numPlaceholders = 2;
for (let i = 0; i < numPlaceholders; i++) {
// This test relies on there being a specific number of widgets in the
// panel. The addition of sync-button screwed this up, so we remove it
// here. We should either fix the tests to not rely on the specific layout,
// or fix bug 1007910 which would change the placeholder logic in different
// ways. Bug 1229236 is for these tests to be smarter.
CustomizableUI.removeWidgetFromArea("sync-button");
// NB: We can't just iterate over all of the placeholders
// because each drag-drop action recreates them.
let placeholder = panel.getElementsByClassName("panel-customization-placeholder")[i];
let placementsAfterMove = ["zoom-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"edit-controls",
"developer-button"];
removeDeveloperButtonIfDevEdition(placementsAfterMove);
simulateItemDrag(editControls, placeholder);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(editControls, zoomControls);
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
}
});
// Dragging the open-file-button back on to itself should work.
add_task(function*() {
yield startCustomizing();
let openFileButton = document.getElementById("open-file-button");
is(openFileButton.parentNode.tagName, "toolbarpaletteitem",
"open-file-button should be wrapped by a toolbarpaletteitem");
simulateItemDrag(openFileButton, openFileButton);
is(openFileButton.parentNode.tagName, "toolbarpaletteitem",
"open-file-button should be wrapped by a toolbarpaletteitem");
let editControls = document.getElementById("edit-controls");
is(editControls.parentNode.tagName, "toolbarpaletteitem",
"edit-controls should be wrapped by a toolbarpaletteitem");
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
});
// Dragging a small button onto the last big button should work.
add_task(function*() {
// Bug 1007910 requires there be a placeholder on the final row for this
// test to work as written. The addition of sync-button meant that's not true
// so we remove it from here. Bug 1229236 is for these tests to be smarter.
CustomizableUI.removeWidgetFromArea("sync-button");
yield startCustomizing();
let editControls = document.getElementById("edit-controls");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let target = panel.getElementsByClassName("panel-customization-placeholder")[0];
let placementsAfterMove = ["zoom-controls",
"new-window-button",
"privatebrowsing-button",
"save-page-button",
"print-button",
"history-panelmenu",
"fullscreen-button",
"find-button",
"preferences-button",
"add-ons-button",
"edit-controls",
"developer-button"];
removeDeveloperButtonIfDevEdition(placementsAfterMove);
simulateItemDrag(editControls, target);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
let itemToDrag = "email-link-button"; // any button in the palette by default.
let button = document.getElementById(itemToDrag);
placementsAfterMove.splice(11, 0, itemToDrag);
simulateItemDrag(button, editControls);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
// Put stuff back:
let palette = document.getElementById("customization-palette");
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(button, palette);
simulateItemDrag(editControls, zoomControls);
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
add_task(function* asyncCleanup() {
yield endCustomizing();
yield resetCustomization();
});

View file

@ -0,0 +1,81 @@
"use strict";
var overflowPanel = document.getElementById("widget-overflow");
const isOSX = (Services.appinfo.OS === "Darwin");
var originalWindowWidth;
registerCleanupFunction(function() {
overflowPanel.removeAttribute("animate");
window.resizeTo(originalWindowWidth, window.outerHeight);
});
// Right-click on an item within the overflow panel should
// show a context menu with options to move it.
add_task(function*() {
overflowPanel.setAttribute("animate", "false");
originalWindowWidth = window.outerWidth;
let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
window.resizeTo(400, window.outerHeight);
yield waitForCondition(() => navbar.hasAttribute("overflowing"));
ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
let chevron = document.getElementById("nav-bar-overflow-button");
let shownPanelPromise = promisePanelElementShown(window, overflowPanel);
chevron.click();
yield shownPanelPromise;
let contextMenu = document.getElementById("toolbar-context-menu");
let shownContextPromise = popupShown(contextMenu);
let homeButton = document.getElementById("home-button");
ok(homeButton, "home-button was found");
is(homeButton.getAttribute("overflowedItem"), "true", "Home button is overflowing");
EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2});
yield shownContextPromise;
is(overflowPanel.state, "open", "The widget overflow panel should still be open.");
let expectedEntries = [
[".customize-context-moveToPanel", true],
[".customize-context-removeFromToolbar", true],
["---"]
];
if (!isOSX) {
expectedEntries.push(["#toggle_toolbar-menubar", true]);
}
expectedEntries.push(
["#toggle_PersonalToolbar", true],
["---"],
[".viewCustomizeToolbar", true]
);
checkContextMenu(contextMenu, expectedEntries);
let hiddenContextPromise = popupHidden(contextMenu);
let hiddenPromise = promisePanelElementHidden(window, overflowPanel);
let moveToPanel = contextMenu.querySelector(".customize-context-moveToPanel");
if (moveToPanel) {
moveToPanel.click();
}
contextMenu.hidePopup();
yield hiddenContextPromise;
yield hiddenPromise;
let homeButtonPlacement = CustomizableUI.getPlacementOfWidget("home-button");
ok(homeButtonPlacement, "Home button should still have a placement");
is(homeButtonPlacement && homeButtonPlacement.area, "PanelUI-contents", "Home button should be in the panel now");
CustomizableUI.reset();
// In some cases, it can take a tick for the navbar to overflow again. Wait for it:
yield waitForCondition(() => navbar.hasAttribute("overflowing"));
ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
homeButtonPlacement = CustomizableUI.getPlacementOfWidget("home-button");
ok(homeButtonPlacement, "Home button should still have a placement");
is(homeButtonPlacement && homeButtonPlacement.area, "nav-bar", "Home button should be back in the navbar now");
is(homeButton.getAttribute("overflowedItem"), "true", "Home button should still be overflowed");
});

View file

@ -0,0 +1,45 @@
/* 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/. */
"use strict";
function isFullscreenSizeMode() {
let sizemode = document.documentElement.getAttribute("sizemode");
return sizemode == "fullscreen";
}
// Observers should be disabled when in customization mode.
add_task(function*() {
// Open and close the panel to make sure that the
// area is generated before getting a child of the area.
let shownPanelPromise = promisePanelShown(window);
PanelUI.toggle({type: "command"});
yield shownPanelPromise;
let hiddenPanelPromise = promisePanelHidden(window);
PanelUI.toggle({type: "command"});
yield hiddenPanelPromise;
let fullscreenButton = document.getElementById("fullscreen-button");
ok(!fullscreenButton.checked, "Fullscreen button should not be checked when not in fullscreen.")
ok(!isFullscreenSizeMode(), "Should not be in fullscreen sizemode before we enter fullscreen.");
BrowserFullScreen();
yield waitForCondition(() => isFullscreenSizeMode());
ok(fullscreenButton.checked, "Fullscreen button should be checked when in fullscreen.")
yield startCustomizing();
let fullscreenButtonWrapper = document.getElementById("wrapper-fullscreen-button");
ok(fullscreenButtonWrapper.hasAttribute("itemobserves"), "Observer should be moved to wrapper");
fullscreenButton = document.getElementById("fullscreen-button");
ok(!fullscreenButton.hasAttribute("observes"), "Observer should be removed from button");
ok(!fullscreenButton.checked, "Fullscreen button should no longer be checked during customization mode");
yield endCustomizing();
BrowserFullScreen();
fullscreenButton = document.getElementById("fullscreen-button");
yield waitForCondition(() => !isFullscreenSizeMode());
ok(!fullscreenButton.checked, "Fullscreen button should not be checked when not in fullscreen.")
});

View file

@ -0,0 +1,134 @@
/* 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/. */
"use strict";
const kWidgetId = "some-widget";
function assertWidgetExists(aWindow, aExists) {
if (aExists) {
ok(aWindow.document.getElementById(kWidgetId),
"Should have found test widget in the window");
} else {
is(aWindow.document.getElementById(kWidgetId), null,
"Should not have found test widget in the window");
}
}
// A widget that is created with showInPrivateBrowsing undefined should
// have that value default to true.
add_task(function() {
let wrapper = CustomizableUI.createWidget({
id: kWidgetId
});
ok(wrapper.showInPrivateBrowsing,
"showInPrivateBrowsing should have defaulted to true.");
CustomizableUI.destroyWidget(kWidgetId);
});
// Add a widget via the API with showInPrivateBrowsing set to false
// and ensure it does not appear in pre-existing or newly created
// private windows.
add_task(function*() {
let plain1 = yield openAndLoadWindow();
let private1 = yield openAndLoadWindow({private: true});
CustomizableUI.createWidget({
id: kWidgetId,
removable: true,
showInPrivateBrowsing: false
});
CustomizableUI.addWidgetToArea(kWidgetId,
CustomizableUI.AREA_NAVBAR);
assertWidgetExists(plain1, true);
assertWidgetExists(private1, false);
// Now open up some new windows. The widget should exist in the new
// plain window, but not the new private window.
let plain2 = yield openAndLoadWindow();
let private2 = yield openAndLoadWindow({private: true});
assertWidgetExists(plain2, true);
assertWidgetExists(private2, false);
// Try moving the widget around and make sure it doesn't get added
// to the private windows. We'll start by appending it to the tabstrip.
CustomizableUI.addWidgetToArea(kWidgetId,
CustomizableUI.AREA_TABSTRIP);
assertWidgetExists(plain1, true);
assertWidgetExists(plain2, true);
assertWidgetExists(private1, false);
assertWidgetExists(private2, false);
// And then move it to the beginning of the tabstrip.
CustomizableUI.moveWidgetWithinArea(kWidgetId, 0);
assertWidgetExists(plain1, true);
assertWidgetExists(plain2, true);
assertWidgetExists(private1, false);
assertWidgetExists(private2, false);
CustomizableUI.removeWidgetFromArea("some-widget");
assertWidgetExists(plain1, false);
assertWidgetExists(plain2, false);
assertWidgetExists(private1, false);
assertWidgetExists(private2, false);
yield Promise.all([plain1, plain2, private1, private2].map(promiseWindowClosed));
CustomizableUI.destroyWidget("some-widget");
});
// Add a widget via the API with showInPrivateBrowsing set to true,
// and ensure that it appears in pre-existing or newly created
// private browsing windows.
add_task(function*() {
let plain1 = yield openAndLoadWindow();
let private1 = yield openAndLoadWindow({private: true});
CustomizableUI.createWidget({
id: kWidgetId,
removable: true,
showInPrivateBrowsing: true
});
CustomizableUI.addWidgetToArea(kWidgetId,
CustomizableUI.AREA_NAVBAR);
assertWidgetExists(plain1, true);
assertWidgetExists(private1, true);
// Now open up some new windows. The widget should exist in the new
// plain window, but not the new private window.
let plain2 = yield openAndLoadWindow();
let private2 = yield openAndLoadWindow({private: true});
assertWidgetExists(plain2, true);
assertWidgetExists(private2, true);
// Try moving the widget around and make sure it doesn't get added
// to the private windows. We'll start by appending it to the tabstrip.
CustomizableUI.addWidgetToArea(kWidgetId,
CustomizableUI.AREA_TABSTRIP);
assertWidgetExists(plain1, true);
assertWidgetExists(plain2, true);
assertWidgetExists(private1, true);
assertWidgetExists(private2, true);
// And then move it to the beginning of the tabstrip.
CustomizableUI.moveWidgetWithinArea(kWidgetId, 0);
assertWidgetExists(plain1, true);
assertWidgetExists(plain2, true);
assertWidgetExists(private1, true);
assertWidgetExists(private2, true);
CustomizableUI.removeWidgetFromArea("some-widget");
assertWidgetExists(plain1, false);
assertWidgetExists(plain2, false);
assertWidgetExists(private1, false);
assertWidgetExists(private2, false);
yield Promise.all([plain1, plain2, private1, private2].map(promiseWindowClosed));
CustomizableUI.destroyWidget("some-widget");
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});

View file

@ -0,0 +1,46 @@
/* 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/. */
"use strict";
const kButtonId = "test-886323-removable-moved-node";
const kLazyAreaId = "test-886323-lazy-area-for-removability-testing";
var gNavBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
var gLazyArea;
// Removable nodes shouldn't be moved by buildArea
add_task(function*() {
let dummyBtn = createDummyXULButton(kButtonId, "Dummy");
dummyBtn.setAttribute("removable", "true");
gNavBar.customizationTarget.appendChild(dummyBtn);
let popupSet = document.getElementById("mainPopupSet");
gLazyArea = document.createElementNS(kNSXUL, "panel");
gLazyArea.id = kLazyAreaId;
gLazyArea.setAttribute("hidden", "true");
popupSet.appendChild(gLazyArea);
CustomizableUI.registerArea(kLazyAreaId, {
type: CustomizableUI.TYPE_MENU_PANEL,
defaultPlacements: []
});
CustomizableUI.addWidgetToArea(kButtonId, kLazyAreaId);
assertAreaPlacements(kLazyAreaId, [kButtonId],
"Placements should have changed because widget is removable.");
let btn = document.getElementById(kButtonId);
btn.setAttribute("removable", "false");
gLazyArea.customizationTarget = gLazyArea;
CustomizableUI.registerToolbarNode(gLazyArea, []);
assertAreaPlacements(kLazyAreaId, [], "Placements should no longer include widget.");
is(btn.parentNode.id, gNavBar.customizationTarget.id,
"Button shouldn't actually have moved as it's not removable");
btn = document.getElementById(kButtonId);
if (btn) btn.remove();
CustomizableUI.removeWidgetFromArea(kButtonId);
CustomizableUI.unregisterArea(kLazyAreaId);
gLazyArea.remove();
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});

View file

@ -0,0 +1,75 @@
/* 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/. */
"use strict";
var navbar = document.getElementById("nav-bar");
var navbarCT = navbar.customizationTarget;
var overflowPanelList = document.getElementById("widget-overflow-list");
// Reading currentset
add_task(function() {
let nodeIds = [];
for (let node of navbarCT.childNodes) {
if (node.getAttribute("skipintoolbarset") != "true") {
nodeIds.push(node.id);
}
}
for (let node of overflowPanelList.childNodes) {
if (node.getAttribute("skipintoolbarset") != "true") {
nodeIds.push(node.id);
}
}
let currentSet = navbar.currentSet;
is(currentSet.split(',').length, nodeIds.length, "Should be just as many nodes as there are.");
is(currentSet, nodeIds.join(','), "Current set and node IDs should match.");
});
// Insert, then remove items
add_task(function() {
let currentSet = navbar.currentSet;
let newCurrentSet = currentSet.replace('home-button', 'feed-button,sync-button,home-button');
navbar.currentSet = newCurrentSet;
is(newCurrentSet, navbar.currentSet, "Current set should match expected current set.");
let feedBtn = document.getElementById("feed-button");
let syncBtn = document.getElementById("sync-button");
ok(feedBtn, "Feed button should have been added.");
ok(syncBtn, "Sync button should have been added.");
if (feedBtn && syncBtn) {
let feedParent = feedBtn.parentNode;
let syncParent = syncBtn.parentNode;
ok(feedParent == navbarCT || feedParent == overflowPanelList,
"Feed button should be in navbar or overflow");
ok(syncParent == navbarCT || syncParent == overflowPanelList,
"Feed button should be in navbar or overflow");
is(feedBtn.nextElementSibling, syncBtn, "Feed button should be next to sync button.");
let homeBtn = document.getElementById("home-button");
is(syncBtn.nextElementSibling, homeBtn, "Sync button should be next to home button.");
}
navbar.currentSet = currentSet;
is(currentSet, navbar.currentSet, "Should be able to remove the added items.");
});
// Simultaneous insert/remove:
add_task(function() {
let currentSet = navbar.currentSet;
let newCurrentSet = currentSet.replace('home-button', 'feed-button');
navbar.currentSet = newCurrentSet;
is(newCurrentSet, navbar.currentSet, "Current set should match expected current set.");
let feedBtn = document.getElementById("feed-button");
ok(feedBtn, "Feed button should have been added.");
let homeBtn = document.getElementById("home-button");
ok(!homeBtn, "Home button should have been removed.");
if (feedBtn) {
let feedParent = feedBtn.parentNode;
ok(feedParent == navbarCT || feedParent == overflowPanelList,
"Feed button should be in navbar or overflow");
}
navbar.currentSet = currentSet;
is(currentSet, navbar.currentSet, "Should be able to return to original state.");
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});

View file

@ -0,0 +1,57 @@
/* 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/. */
"use strict";
// Adding, moving and removing items should update the relevant currentset attributes
add_task(function*() {
ok(CustomizableUI.inDefaultState, "Should be in the default state when we start");
let personalbar = document.getElementById(CustomizableUI.AREA_BOOKMARKS);
setToolbarVisibility(personalbar, true);
ok(!CustomizableUI.inDefaultState, "Making the bookmarks toolbar visible takes it out of the default state");
let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
personalbar = document.getElementById(CustomizableUI.AREA_BOOKMARKS);
let navbarCurrentset = navbar.getAttribute("currentset") || navbar.currentSet;
let personalbarCurrentset = personalbar.getAttribute("currentset") || personalbar.currentSet;
let otherWin = yield openAndLoadWindow();
let otherNavbar = otherWin.document.getElementById(CustomizableUI.AREA_NAVBAR);
let otherPersonalbar = otherWin.document.getElementById(CustomizableUI.AREA_BOOKMARKS);
CustomizableUI.moveWidgetWithinArea("home-button", 0);
navbarCurrentset = "home-button," + navbarCurrentset.replace(",home-button", "");
is(navbar.getAttribute("currentset"), navbarCurrentset,
"Should have updated currentSet after move.");
is(otherNavbar.getAttribute("currentset"), navbarCurrentset,
"Should have updated other window's currentSet after move.");
CustomizableUI.addWidgetToArea("home-button", CustomizableUI.AREA_BOOKMARKS);
navbarCurrentset = navbarCurrentset.replace("home-button,", "");
personalbarCurrentset = personalbarCurrentset + ",home-button";
is(navbar.getAttribute("currentset"), navbarCurrentset,
"Should have updated navbar currentSet after implied remove.");
is(otherNavbar.getAttribute("currentset"), navbarCurrentset,
"Should have updated other window's navbar currentSet after implied remove.");
is(personalbar.getAttribute("currentset"), personalbarCurrentset,
"Should have updated personalbar currentSet after add.");
is(otherPersonalbar.getAttribute("currentset"), personalbarCurrentset,
"Should have updated other window's personalbar currentSet after add.");
CustomizableUI.removeWidgetFromArea("home-button");
personalbarCurrentset = personalbarCurrentset.replace(",home-button", "");
is(personalbar.getAttribute("currentset"), personalbarCurrentset,
"Should have updated currentSet after remove.");
is(otherPersonalbar.getAttribute("currentset"), personalbarCurrentset,
"Should have updated other window's currentSet after remove.");
yield promiseWindowClosed(otherWin);
// Reset in asyncCleanup will put our button back for us.
});
add_task(function* asyncCleanup() {
let personalbar = document.getElementById(CustomizableUI.AREA_BOOKMARKS);
setToolbarVisibility(personalbar, false);
yield resetCustomization();
});

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/. */
"use strict";
requestLongerTimeout(2);
// One orphaned item should have two placeholders next to it.
add_task(function*() {
yield startCustomizing();
if (isInDevEdition()) {
CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
}
if (!isInDevEdition()) {
ok(CustomizableUI.inDefaultState, "Should be in default state.");
} else {
ok(!CustomizableUI.inDefaultState, "Should not be in default state if on DevEdition.");
}
// This test relies on an exact number of widgets being in the panel.
// Remove the sync-button to satisfy that. (bug 1229236)
CustomizableUI.removeWidgetFromArea("sync-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placements);
is(getVisiblePlaceholderCount(panel), 2, "Should only have 2 visible placeholders before exiting");
yield endCustomizing();
yield startCustomizing();
is(getVisiblePlaceholderCount(panel), 2, "Should only have 2 visible placeholders after re-entering");
if (isInDevEdition()) {
CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2);
}
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
// Two orphaned items should have one placeholder next to them (case 1).
add_task(function*() {
yield startCustomizing();
if (isInDevEdition()) {
CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL);
}
// This test relies on an exact number of widgets being in the panel.
// Remove the sync-button to satisfy that. (bug 1229236)
CustomizableUI.removeWidgetFromArea("sync-button");
let btn = document.getElementById("open-file-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
let placementsAfterAppend = placements;
placementsAfterAppend = placements.concat(["open-file-button"]);
simulateItemDrag(btn, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should not be in default state.");
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder before exiting");
yield endCustomizing();
yield startCustomizing();
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder after re-entering");
let palette = document.getElementById("customization-palette");
simulateItemDrag(btn, palette);
btn = document.getElementById("open-file-button");
simulateItemDrag(btn, palette);
if (isInDevEdition()) {
CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2);
}
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
// Two orphaned items should have one placeholder next to them (case 2).
add_task(function*() {
yield startCustomizing();
if (isInDevEdition()) {
CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL);
}
// This test relies on an exact number of widgets being in the panel.
// Remove the sync-button to satisfy that. (bug 1229236)
CustomizableUI.removeWidgetFromArea("sync-button");
let btn = document.getElementById("add-ons-button");
let btn2 = document.getElementById("developer-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let palette = document.getElementById("customization-palette");
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
let placementsAfterAppend = placements.filter(p => p != btn.id && p != btn2.id);
simulateItemDrag(btn, palette);
simulateItemDrag(btn2, palette);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder before exiting");
yield endCustomizing();
yield startCustomizing();
is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder after re-entering");
simulateItemDrag(btn, panel);
simulateItemDrag(btn2, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placements);
if (isInDevEdition()) {
CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2);
}
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
// A wide widget at the bottom of the panel should have three placeholders after it.
add_task(function*() {
yield startCustomizing();
if (isInDevEdition()) {
CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL);
}
// This test relies on an exact number of widgets being in the panel.
// Remove the sync-button to satisfy that. (bug 1229236)
CustomizableUI.removeWidgetFromArea("sync-button");
let btn = document.getElementById("edit-controls");
let btn2 = document.getElementById("developer-button");
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
let palette = document.getElementById("customization-palette");
let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
placements.pop();
simulateItemDrag(btn2, palette);
let placementsAfterAppend = placements.concat([placements.shift()]);
simulateItemDrag(btn, panel);
assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders before exiting");
yield endCustomizing();
yield startCustomizing();
is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders after re-entering");
simulateItemDrag(btn2, panel);
let zoomControls = document.getElementById("zoom-controls");
simulateItemDrag(btn, zoomControls);
if (isInDevEdition()) {
CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2);
}
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
ok(CustomizableUI.inDefaultState, "Should be in default state again.");
});
// The default placements should have two placeholders at the bottom (or 1 in win8).
add_task(function*() {
yield startCustomizing();
let numPlaceholders = -1;
if (isInDevEdition()) {
numPlaceholders = 3;
} else {
numPlaceholders = 2;
}
let panel = document.getElementById(CustomizableUI.AREA_PANEL);
ok(CustomizableUI.inDefaultState, "Should be in default state.");
// This test relies on an exact number of widgets being in the panel.
// Remove the sync-button to satisfy that. (bug 1229236)
CustomizableUI.removeWidgetFromArea("sync-button");
is(getVisiblePlaceholderCount(panel), numPlaceholders, "Should have " + numPlaceholders + " visible placeholders before exiting");
yield endCustomizing();
yield startCustomizing();
is(getVisiblePlaceholderCount(panel), numPlaceholders, "Should have " + numPlaceholders + " visible placeholders after re-entering");
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
ok(CustomizableUI.inDefaultState, "Should still be in default state.");
});
add_task(function* asyncCleanup() {
yield endCustomizing();
yield resetCustomization();
});
function getVisiblePlaceholderCount(aPanel) {
let visiblePlaceholders = aPanel.querySelectorAll(".panel-customization-placeholder:not([hidden=true])");
return visiblePlaceholders.length;
}

View file

@ -0,0 +1,68 @@
/* 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/. */
"use strict";
const kLazyAreaId = "test-890262-lazy-area";
const kWidget1Id = "test-890262-widget1";
const kWidget2Id = "test-890262-widget2";
setupArea();
// Destroying a widget after defaulting it to a non-legacy area should work.
add_task(function() {
CustomizableUI.createWidget({
id: kWidget1Id,
removable: true,
defaultArea: kLazyAreaId
});
let noError = true;
try {
CustomizableUI.destroyWidget(kWidget1Id);
} catch (ex) {
Cu.reportError(ex);
noError = false;
}
ok(noError, "Shouldn't throw an exception for a widget that was created in a not-yet-constructed area");
});
// Destroying a widget after moving it to a non-legacy area should work.
add_task(function() {
CustomizableUI.createWidget({
id: kWidget2Id,
removable: true,
defaultArea: CustomizableUI.AREA_NAVBAR
});
CustomizableUI.addWidgetToArea(kWidget2Id, kLazyAreaId);
let noError = true;
try {
CustomizableUI.destroyWidget(kWidget2Id);
} catch (ex) {
Cu.reportError(ex);
noError = false;
}
ok(noError, "Shouldn't throw an exception for a widget that was added to a not-yet-constructed area");
});
add_task(function* asyncCleanup() {
let lazyArea = document.getElementById(kLazyAreaId);
if (lazyArea) {
lazyArea.remove();
}
try {
CustomizableUI.unregisterArea(kLazyAreaId);
} catch (ex) {} // If we didn't register successfully for some reason
yield resetCustomization();
});
function setupArea() {
let lazyArea = document.createElementNS(kNSXUL, "hbox");
lazyArea.id = kLazyAreaId;
document.getElementById("nav-bar").appendChild(lazyArea);
CustomizableUI.registerArea(kLazyAreaId, {
type: CustomizableUI.TYPE_TOOLBAR,
defaultPlacements: []
});
}

View file

@ -0,0 +1,30 @@
/* 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/. */
"use strict";
const kWidgetId = "test-892955-remove-widget";
// Removing a destroyed widget should work.
add_task(function*() {
let widgetSpec = {
id: kWidgetId,
defaultArea: CustomizableUI.AREA_NAVBAR
};
CustomizableUI.createWidget(widgetSpec);
CustomizableUI.destroyWidget(kWidgetId);
let noError = true;
try {
CustomizableUI.removeWidgetFromArea(kWidgetId);
} catch (ex) {
noError = false;
Cu.reportError(ex);
}
ok(noError, "Shouldn't throw an error removing a destroyed widget.");
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});

View file

@ -0,0 +1,24 @@
/* 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/. */
"use strict";
const kWidgetId = "test-892956-destroyWidget-defaultPlacement";
// destroyWidget should clean up defaultPlacements if the widget had a defaultArea
add_task(function*() {
ok(CustomizableUI.inDefaultState, "Should be in the default state when we start");
let widgetSpec = {
id: kWidgetId,
defaultArea: CustomizableUI.AREA_NAVBAR
};
CustomizableUI.createWidget(widgetSpec);
CustomizableUI.destroyWidget(kWidgetId);
ok(CustomizableUI.inDefaultState, "Should be in the default state when we finish");
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});

View file

@ -0,0 +1,113 @@
/* 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/. */
"use strict";
logActiveElement();
function* waitForSearchBarFocus()
{
let searchbar = document.getElementById("searchbar");
yield waitForCondition(function () {
logActiveElement();
return document.activeElement === searchbar.textbox.inputField;
});
}
// Ctrl+K should open the menu panel and focus the search bar if the search bar is in the panel.
add_task(function*() {
let searchbar = document.getElementById("searchbar");
gCustomizeMode.addToPanel(searchbar);
let placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel");
let shownPanelPromise = promisePanelShown(window);
sendWebSearchKeyCommand();
yield shownPanelPromise;
yield waitForSearchBarFocus();
let hiddenPanelPromise = promisePanelHidden(window);
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield hiddenPanelPromise;
CustomizableUI.reset();
});
// Ctrl+K should give focus to the searchbar when the searchbar is in the menupanel and the panel is already opened.
add_task(function*() {
let searchbar = document.getElementById("searchbar");
gCustomizeMode.addToPanel(searchbar);
let placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel");
let shownPanelPromise = promisePanelShown(window);
PanelUI.toggle({type: "command"});
yield shownPanelPromise;
sendWebSearchKeyCommand();
yield waitForSearchBarFocus();
let hiddenPanelPromise = promisePanelHidden(window);
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield hiddenPanelPromise;
CustomizableUI.reset();
});
// Ctrl+K should open the overflow panel and focus the search bar if the search bar is overflowed.
add_task(function*() {
this.originalWindowWidth = window.outerWidth;
let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
ok(CustomizableUI.inDefaultState, "Should start in default state.");
window.resizeTo(360, window.outerHeight);
yield waitForCondition(() => navbar.getAttribute("overflowing") == "true");
ok(!navbar.querySelector("#search-container"), "Search container should be overflowing");
let shownPanelPromise = promiseOverflowShown(window);
sendWebSearchKeyCommand();
yield shownPanelPromise;
let chevron = document.getElementById("nav-bar-overflow-button");
yield waitForCondition(() => chevron.open);
yield waitForSearchBarFocus();
let hiddenPanelPromise = promiseOverflowHidden(window);
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield hiddenPanelPromise;
navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
window.resizeTo(this.originalWindowWidth, window.outerHeight);
yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar.");
});
// Ctrl+K should focus the search bar if it is in the navbar and not overflowing.
add_task(function*() {
let placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in nav-bar");
sendWebSearchKeyCommand();
yield waitForSearchBarFocus();
});
function sendWebSearchKeyCommand() {
if (Services.appinfo.OS === "Darwin")
EventUtils.synthesizeKey("k", { accelKey: true });
else
EventUtils.synthesizeKey("k", { ctrlKey: true });
}
function logActiveElement() {
let element = document.activeElement;
let str = "";
while (element && element.parentNode) {
str = " (" + element.localName + "#" + element.id + "." + [...element.classList].join(".") + ") >" + str;
element = element.parentNode;
}
info("Active element: " + element ? str : "null");
}

View file

@ -0,0 +1,31 @@
/* 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/. */
"use strict";
// Resize to a small window, open a new window, check that new window handles overflow properly
add_task(function*() {
let originalWindowWidth = window.outerWidth;
let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
let oldChildCount = navbar.customizationTarget.childElementCount;
window.resizeTo(400, window.outerHeight);
yield waitForCondition(() => navbar.hasAttribute("overflowing"));
ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
ok(navbar.customizationTarget.childElementCount < oldChildCount, "Should have fewer children.");
let newWindow = yield openAndLoadWindow();
let otherNavBar = newWindow.document.getElementById(CustomizableUI.AREA_NAVBAR);
yield waitForCondition(() => otherNavBar.hasAttribute("overflowing"));
ok(otherNavBar.hasAttribute("overflowing"), "Other window should have an overflowing toolbar.");
yield promiseWindowClosed(newWindow);
window.resizeTo(originalWindowWidth, window.outerHeight);
yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
ok(!navbar.hasAttribute("overflowing"), "Should no longer have an overflowing toolbar.");
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});

View file

@ -0,0 +1,55 @@
/* 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/. */
"use strict";
var navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
// Resize to a small window, resize back, shouldn't affect currentSet
add_task(function*() {
let originalWindowWidth = window.outerWidth;
let oldCurrentSet = navbar.currentSet;
ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
ok(CustomizableUI.inDefaultState, "Should start in default state.");
let oldChildCount = navbar.customizationTarget.childElementCount;
window.resizeTo(400, window.outerHeight);
yield waitForCondition(() => navbar.hasAttribute("overflowing"));
ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
is(navbar.currentSet, oldCurrentSet, "Currentset should be the same when overflowing.");
ok(CustomizableUI.inDefaultState, "Should still be in default state when overflowing.");
ok(navbar.customizationTarget.childElementCount < oldChildCount, "Should have fewer children.");
window.resizeTo(originalWindowWidth, window.outerHeight);
yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
ok(!navbar.hasAttribute("overflowing"), "Should no longer have an overflowing toolbar.");
is(navbar.currentSet, oldCurrentSet, "Currentset should still be the same now we're no longer overflowing.");
ok(CustomizableUI.inDefaultState, "Should still be in default state now we're no longer overflowing.");
// Verify actual physical placements match those of the placement array:
let placementCounter = 0;
let placements = CustomizableUI.getWidgetIdsInArea(CustomizableUI.AREA_NAVBAR);
for (let node of navbar.customizationTarget.childNodes) {
if (node.getAttribute("skipintoolbarset") == "true") {
continue;
}
is(placements[placementCounter++], node.id, "Nodes should match after overflow");
}
is(placements.length, placementCounter, "Should have as many nodes as expected");
is(navbar.customizationTarget.childElementCount, oldChildCount, "Number of nodes should match");
});
// Enter and exit customization mode, check that currentSet works
add_task(function*() {
let oldCurrentSet = navbar.currentSet;
ok(CustomizableUI.inDefaultState, "Should start in default state.");
yield startCustomizing();
ok(CustomizableUI.inDefaultState, "Should be in default state in customization mode.");
is(navbar.currentSet, oldCurrentSet, "Currentset should be the same in customization mode.");
yield endCustomizing();
ok(CustomizableUI.inDefaultState, "Should be in default state after customization mode.");
is(navbar.currentSet, oldCurrentSet, "Currentset should be the same after customization mode.");
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});

View file

@ -0,0 +1,131 @@
/* 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/. */
"use strict";
var navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
var overflowList = document.getElementById(navbar.getAttribute("overflowtarget"));
const kTestBtn1 = "test-addWidgetToArea-overflow";
const kTestBtn2 = "test-removeWidgetFromArea-overflow";
const kTestBtn3 = "test-createWidget-overflow";
const kHomeBtn = "home-button";
const kDownloadsBtn = "downloads-button";
const kSearchBox = "search-container";
const kStarBtn = "bookmarks-menu-button";
var originalWindowWidth;
// Adding a widget should add it next to the widget it's being inserted next to.
add_task(function*() {
originalWindowWidth = window.outerWidth;
createDummyXULButton(kTestBtn1, "Test");
ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
ok(CustomizableUI.inDefaultState, "Should start in default state.");
window.resizeTo(400, window.outerHeight);
yield waitForCondition(() => navbar.hasAttribute("overflowing"));
ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
ok(!navbar.querySelector("#" + kHomeBtn), "Home button should no longer be in the navbar");
let homeBtnNode = overflowList.querySelector("#" + kHomeBtn);
ok(homeBtnNode, "Home button should be overflowing");
ok(homeBtnNode && homeBtnNode.getAttribute("overflowedItem") == "true", "Home button should have overflowedItem attribute");
let placementOfHomeButton = CustomizableUI.getWidgetIdsInArea(navbar.id).indexOf(kHomeBtn);
CustomizableUI.addWidgetToArea(kTestBtn1, navbar.id, placementOfHomeButton);
ok(!navbar.querySelector("#" + kTestBtn1), "New button should not be in the navbar");
let newButtonNode = overflowList.querySelector("#" + kTestBtn1);
ok(newButtonNode, "New button should be overflowing");
ok(newButtonNode && newButtonNode.getAttribute("overflowedItem") == "true", "New button should have overflowedItem attribute");
let nextEl = newButtonNode && newButtonNode.nextSibling;
is(nextEl && nextEl.id, kHomeBtn, "Test button should be next to home button.");
window.resizeTo(originalWindowWidth, window.outerHeight);
yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar.");
ok(navbar.querySelector("#" + kHomeBtn), "Home button should be in the navbar");
ok(homeBtnNode && (homeBtnNode.getAttribute("overflowedItem") != "true"), "Home button should no longer have overflowedItem attribute");
ok(!overflowList.querySelector("#" + kHomeBtn), "Home button should no longer be overflowing");
ok(navbar.querySelector("#" + kTestBtn1), "Test button should be in the navbar");
ok(!overflowList.querySelector("#" + kTestBtn1), "Test button should no longer be overflowing");
ok(newButtonNode && (newButtonNode.getAttribute("overflowedItem") != "true"), "New button should no longer have overflowedItem attribute");
let el = document.getElementById(kTestBtn1);
if (el) {
CustomizableUI.removeWidgetFromArea(kTestBtn1);
el.remove();
}
window.resizeTo(originalWindowWidth, window.outerHeight);
});
// Removing a widget should remove it from the overflow list if that is where it is, and update it accordingly.
add_task(function*() {
createDummyXULButton(kTestBtn2, "Test");
ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
ok(CustomizableUI.inDefaultState, "Should start in default state.");
CustomizableUI.addWidgetToArea(kTestBtn2, navbar.id);
ok(!navbar.hasAttribute("overflowing"), "Should still have a non-overflowing toolbar.");
window.resizeTo(400, window.outerHeight);
yield waitForCondition(() => navbar.hasAttribute("overflowing"));
ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
ok(!navbar.querySelector("#" + kTestBtn2), "Test button should not be in the navbar");
ok(overflowList.querySelector("#" + kTestBtn2), "Test button should be overflowing");
CustomizableUI.removeWidgetFromArea(kTestBtn2);
ok(!overflowList.querySelector("#" + kTestBtn2), "Test button should not be overflowing.");
ok(!navbar.querySelector("#" + kTestBtn2), "Test button should not be in the navbar");
ok(gNavToolbox.palette.querySelector("#" + kTestBtn2), "Test button should be in the palette");
window.resizeTo(originalWindowWidth, window.outerHeight);
yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar.");
let el = document.getElementById(kTestBtn2);
if (el) {
CustomizableUI.removeWidgetFromArea(kTestBtn2);
el.remove();
}
window.resizeTo(originalWindowWidth, window.outerHeight);
});
// Constructing a widget while overflown should set the right class on it.
add_task(function*() {
originalWindowWidth = window.outerWidth;
ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
ok(CustomizableUI.inDefaultState, "Should start in default state.");
window.resizeTo(400, window.outerHeight);
yield waitForCondition(() => navbar.hasAttribute("overflowing"));
ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
ok(!navbar.querySelector("#" + kHomeBtn), "Home button should no longer be in the navbar");
let homeBtnNode = overflowList.querySelector("#" + kHomeBtn);
ok(homeBtnNode, "Home button should be overflowing");
ok(homeBtnNode && homeBtnNode.getAttribute("overflowedItem") == "true", "Home button should have overflowedItem class");
let testBtnSpec = {id: kTestBtn3, label: "Overflowable widget test", defaultArea: "nav-bar"};
CustomizableUI.createWidget(testBtnSpec);
let testNode = overflowList.querySelector("#" + kTestBtn3);
ok(testNode, "Test button should be overflowing");
ok(testNode && testNode.getAttribute("overflowedItem") == "true", "Test button should have overflowedItem class");
CustomizableUI.destroyWidget(kTestBtn3);
testNode = document.getElementById(kTestBtn3);
ok(!testNode, "Test button should be gone");
CustomizableUI.createWidget(testBtnSpec);
testNode = overflowList.querySelector("#" + kTestBtn3);
ok(testNode, "Test button should be overflowing");
ok(testNode && testNode.getAttribute("overflowedItem") == "true", "Test button should have overflowedItem class");
CustomizableUI.removeWidgetFromArea(kTestBtn3);
testNode = document.getElementById(kTestBtn3);
ok(!testNode, "Test button should be gone");
CustomizableUI.destroyWidget(kTestBtn3);
window.resizeTo(originalWindowWidth, window.outerHeight);
});
add_task(function* asyncCleanup() {
window.resizeTo(originalWindowWidth, window.outerHeight);
yield resetCustomization();
});

View file

@ -0,0 +1,16 @@
/* 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/. */
// Entering then exiting customization mode should reenable the Help and Exit buttons.
add_task(function*() {
yield startCustomizing();
let helpButton = document.getElementById("PanelUI-help");
let quitButton = document.getElementById("PanelUI-quit");
ok(helpButton.getAttribute("disabled") == "true", "Help button should be disabled while in customization mode.");
ok(quitButton.getAttribute("disabled") == "true", "Quit button should be disabled while in customization mode.");
yield endCustomizing();
ok(!helpButton.hasAttribute("disabled"), "Help button should not be disabled.");
ok(!quitButton.hasAttribute("disabled"), "Quit button should not be disabled.");
});

View file

@ -0,0 +1,38 @@
/* 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/. */
"use strict";
var navbar;
var skippedItem;
// Attempting to drag a skipintoolbarset item should work.
add_task(function*() {
navbar = document.getElementById("nav-bar");
skippedItem = document.createElement("toolbarbutton");
skippedItem.id = "test-skipintoolbarset-item";
skippedItem.setAttribute("label", "Test");
skippedItem.setAttribute("skipintoolbarset", "true");
skippedItem.setAttribute("removable", "true");
navbar.customizationTarget.appendChild(skippedItem);
let downloadsButton = document.getElementById("downloads-button");
yield startCustomizing();
ok(CustomizableUI.inDefaultState, "Should still be in default state");
simulateItemDrag(skippedItem, downloadsButton);
ok(CustomizableUI.inDefaultState, "Should still be in default state");
let skippedItemWrapper = skippedItem.parentNode;
is(skippedItemWrapper.nextSibling && skippedItemWrapper.nextSibling.id,
downloadsButton.parentNode.id, "Should be next to downloads button");
simulateItemDrag(downloadsButton, skippedItem);
let downloadWrapper = downloadsButton.parentNode;
is(downloadWrapper.nextSibling && downloadWrapper.nextSibling.id,
skippedItem.parentNode.id, "Should be next to skipintoolbarset item");
ok(CustomizableUI.inDefaultState, "Should still be in default state");
});
add_task(function* asyncCleanup() {
yield endCustomizing();
skippedItem.remove();
yield resetCustomization();
});

View file

@ -0,0 +1,24 @@
/* 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/. */
"use strict";
// Customize mode reset button should revert correctly
add_task(function*() {
yield startCustomizing();
let devButton = document.getElementById("developer-button");
let downloadsButton = document.getElementById("downloads-button");
let searchBox = document.getElementById("search-container");
let palette = document.getElementById("customization-palette");
ok(devButton && downloadsButton && searchBox && palette, "Stuff should exist");
simulateItemDrag(devButton, downloadsButton);
simulateItemDrag(searchBox, palette);
yield gCustomizeMode.reset();
ok(CustomizableUI.inDefaultState, "Should be back in default state");
yield endCustomizing();
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});

View file

@ -0,0 +1,26 @@
/* 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/. */
"use strict";
const kTestToolbarId = "test-empty-drag";
// Attempting to drag an item to an empty container should work.
add_task(function*() {
yield createToolbarWithPlacements(kTestToolbarId, []);
yield startCustomizing();
let downloadButton = document.getElementById("downloads-button");
let customToolbar = document.getElementById(kTestToolbarId);
simulateItemDrag(downloadButton, customToolbar);
assertAreaPlacements(kTestToolbarId, ["downloads-button"]);
ok(downloadButton.parentNode && downloadButton.parentNode.parentNode == customToolbar,
"Button should really be in toolbar");
yield endCustomizing();
removeCustomToolbars();
});
add_task(function* asyncCleanup() {
yield endCustomizing();
yield resetCustomization();
});

View file

@ -0,0 +1,35 @@
/* 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/. */
"use strict";
// There should be an advert to get more addons when the palette is empty.
add_task(function*() {
yield startCustomizing();
let visiblePalette = document.getElementById("customization-palette");
let emptyPaletteNotice = document.getElementById("customization-empty");
is(emptyPaletteNotice.hidden, true, "The empty palette notice should not be shown when there are items in the palette.");
while (visiblePalette.childElementCount) {
gCustomizeMode.addToToolbar(visiblePalette.children[0]);
}
is(visiblePalette.childElementCount, 0, "There shouldn't be any items remaining in the visible palette.");
is(emptyPaletteNotice.hidden, false, "The empty palette notice should be shown when there are no items in the palette.");
yield endCustomizing();
yield startCustomizing();
visiblePalette = document.getElementById("customization-palette");
emptyPaletteNotice = document.getElementById("customization-empty");
is(emptyPaletteNotice.hidden, false,
"The empty palette notice should be shown when there are no items in the palette and cust. mode is re-entered.");
gCustomizeMode.removeFromArea(document.getElementById("wrapper-home-button"));
is(emptyPaletteNotice.hidden, true,
"The empty palette notice should not be shown when there is at least one item in the palette.");
});
add_task(function* asyncCleanup() {
yield endCustomizing();
yield resetCustomization();
});

View file

@ -0,0 +1,30 @@
/* 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/. */
"use strict";
// Attempting to drag the menubar to the navbar shouldn't work.
add_task(function*() {
yield startCustomizing();
let menuItems = document.getElementById("menubar-items");
let navbar = document.getElementById("nav-bar");
let menubar = document.getElementById("toolbar-menubar");
// Force the menu to be shown.
const kAutohide = menubar.getAttribute("autohide");
menubar.setAttribute("autohide", "false");
simulateItemDrag(menuItems, navbar.customizationTarget);
is(getAreaWidgetIds("nav-bar").indexOf("menubar-items"), -1, "Menu bar shouldn't be in the navbar.");
ok(!navbar.querySelector("#menubar-items"), "Shouldn't find menubar items in the navbar.");
ok(menubar.querySelector("#menubar-items"), "Should find menubar items in the menubar.");
isnot(getAreaWidgetIds("toolbar-menubar").indexOf("menubar-items"), -1,
"Menubar items shouldn't be missing from the navbar.");
menubar.setAttribute("autohide", kAutohide);
yield endCustomizing();
});
add_task(function* asyncCleanup() {
yield endCustomizing();
yield resetCustomization();
});

View file

@ -0,0 +1,89 @@
/* 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/. */
"use strict";
const kTimeoutInMS = 20000;
// Bug 934951 - Zoom controls percentage label doesn't update when it's in the toolbar and you navigate.
add_task(function*() {
CustomizableUI.addWidgetToArea("zoom-controls", CustomizableUI.AREA_NAVBAR);
let tab1 = gBrowser.addTab("about:mozilla");
yield BrowserTestUtils.browserLoaded(tab1.linkedBrowser);
let tab2 = gBrowser.addTab("about:robots");
yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
gBrowser.selectedTab = tab1;
let zoomResetButton = document.getElementById("zoom-reset-button");
registerCleanupFunction(() => {
info("Cleaning up.");
CustomizableUI.reset();
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab1);
});
is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:mozilla");
let zoomChangePromise = promiseObserverNotification("browser-fullZoom:zoomChange");
FullZoom.enlarge();
yield zoomChangePromise;
is(parseInt(zoomResetButton.label, 10), 110, "Zoom is changed to 110% for about:mozilla");
let tabSelectPromise = promiseTabSelect();
gBrowser.selectedTab = tab2;
yield tabSelectPromise;
is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:robots");
gBrowser.selectedTab = tab1;
let zoomResetPromise = promiseObserverNotification("browser-fullZoom:zoomReset");
FullZoom.reset();
yield zoomResetPromise;
is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:mozilla");
// Test zoom label updates while navigating pages in the same tab.
FullZoom.enlarge();
yield zoomChangePromise;
is(parseInt(zoomResetButton.label, 10), 110, "Zoom is changed to 110% for about:mozilla");
let attributeChangePromise = promiseAttributeMutation(zoomResetButton, "label", (v) => {
return parseInt(v, 10) == 100;
});
yield promiseTabLoadEvent(tab1, "about:home");
yield attributeChangePromise;
is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:home");
yield promiseTabHistoryNavigation(-1, function() {
return parseInt(zoomResetButton.label, 10) == 110;
});
is(parseInt(zoomResetButton.label, 10), 110, "Zoom is still 110% for about:mozilla");
FullZoom.reset();
});
function promiseObserverNotification(aObserver) {
let deferred = Promise.defer();
function notificationCallback(e) {
Services.obs.removeObserver(notificationCallback, aObserver, false);
clearTimeout(timeoutId);
deferred.resolve();
}
let timeoutId = setTimeout(() => {
Services.obs.removeObserver(notificationCallback, aObserver, false);
deferred.reject("Notification '" + aObserver + "' did not happen within 20 seconds.");
}, kTimeoutInMS);
Services.obs.addObserver(notificationCallback, aObserver, false);
return deferred.promise;
}
function promiseTabSelect() {
let deferred = Promise.defer();
let container = window.gBrowser.tabContainer;
let timeoutId = setTimeout(() => {
container.removeEventListener("TabSelect", callback);
deferred.reject("TabSelect did not happen within 20 seconds");
}, kTimeoutInMS);
function callback(e) {
container.removeEventListener("TabSelect", callback);
clearTimeout(timeoutId);
executeSoon(deferred.resolve);
}
container.addEventListener("TabSelect", callback);
return deferred.promise;
}

View file

@ -0,0 +1,121 @@
/* 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/. */
"use strict";
requestLongerTimeout(2);
var bookmarksToolbar = document.getElementById("PersonalToolbar");
var navbar = document.getElementById("nav-bar");
var tabsToolbar = document.getElementById("TabsToolbar");
// Customization reset should restore visibility to default-visible toolbars.
add_task(function*() {
is(navbar.collapsed, false, "Test should start with navbar visible");
setToolbarVisibility(navbar, false);
is(navbar.collapsed, true, "navbar should be hidden now");
yield resetCustomization();
is(navbar.collapsed, false, "Customization reset should restore visibility to the navbar");
});
// Customization reset should restore collapsed-state to default-collapsed toolbars.
add_task(function*() {
ok(CustomizableUI.inDefaultState, "Everything should be in its default state");
is(bookmarksToolbar.collapsed, true, "Test should start with bookmarks toolbar collapsed");
ok(bookmarksToolbar.collapsed, "bookmarksToolbar should be collapsed");
ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
is(navbar.collapsed, false, "The nav-bar should be shown by default");
setToolbarVisibility(bookmarksToolbar, true);
setToolbarVisibility(navbar, false);
ok(!bookmarksToolbar.collapsed, "bookmarksToolbar should be visible now");
ok(navbar.collapsed, "navbar should be collapsed");
is(CustomizableUI.inDefaultState, false, "Should no longer be in default state");
yield startCustomizing();
yield gCustomizeMode.reset();
yield endCustomizing();
is(bookmarksToolbar.collapsed, true, "Customization reset should restore collapsed-state to the bookmarks toolbar");
ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
ok(bookmarksToolbar.collapsed, "The bookmarksToolbar should be collapsed after reset");
ok(CustomizableUI.inDefaultState, "Everything should be back to default state");
});
// Check that the menubar will be collapsed by resetting, if the platform supports it.
add_task(function*() {
let menubar = document.getElementById("toolbar-menubar");
const canMenubarCollapse = CustomizableUI.isToolbarDefaultCollapsed(menubar.id);
if (!canMenubarCollapse) {
return;
}
ok(CustomizableUI.inDefaultState, "Everything should be in its default state");
is(menubar.getBoundingClientRect().height, 0, "menubar should be hidden by default");
setToolbarVisibility(menubar, true);
isnot(menubar.getBoundingClientRect().height, 0, "menubar should be visible now");
yield startCustomizing();
yield gCustomizeMode.reset();
is(menubar.getAttribute("autohide"), "true", "The menubar should have autohide=true after reset in customization mode");
is(menubar.getBoundingClientRect().height, 0, "The menubar should have height=0 after reset in customization mode");
yield endCustomizing();
is(menubar.getAttribute("autohide"), "true", "The menubar should have autohide=true after reset");
is(menubar.getBoundingClientRect().height, 0, "The menubar should have height=0 after reset");
});
// Customization reset should restore collapsed-state to default-collapsed toolbars.
add_task(function*() {
ok(CustomizableUI.inDefaultState, "Everything should be in its default state");
ok(bookmarksToolbar.collapsed, "bookmarksToolbar should be collapsed");
ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
setToolbarVisibility(bookmarksToolbar, true);
ok(!bookmarksToolbar.collapsed, "bookmarksToolbar should be visible now");
is(CustomizableUI.inDefaultState, false, "Should no longer be in default state");
yield startCustomizing();
ok(!bookmarksToolbar.collapsed, "The bookmarksToolbar should be visible before reset");
ok(!navbar.collapsed, "The navbar should be visible before reset");
ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
yield gCustomizeMode.reset();
ok(bookmarksToolbar.collapsed, "The bookmarksToolbar should be collapsed after reset");
ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
ok(!navbar.collapsed, "The navbar should still be visible after reset");
ok(CustomizableUI.inDefaultState, "Everything should be back to default state");
yield endCustomizing();
});
// Check that the menubar will be collapsed by resetting, if the platform supports it.
add_task(function*() {
let menubar = document.getElementById("toolbar-menubar");
const canMenubarCollapse = CustomizableUI.isToolbarDefaultCollapsed(menubar.id);
if (!canMenubarCollapse) {
return;
}
ok(CustomizableUI.inDefaultState, "Everything should be in its default state");
yield startCustomizing();
let resetButton = document.getElementById("customization-reset-button");
is(resetButton.disabled, true, "The reset button should be disabled when in default state");
setToolbarVisibility(menubar, true);
is(resetButton.disabled, false, "The reset button should be enabled when not in default state")
ok(!CustomizableUI.inDefaultState, "No longer in default state when the menubar is shown");
yield gCustomizeMode.reset();
is(resetButton.disabled, true, "The reset button should be disabled when in default state");
ok(CustomizableUI.inDefaultState, "Everything should be in its default state");
yield endCustomizing();
});

View file

@ -0,0 +1,25 @@
/* 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/. */
"use strict";
const kWidgetId = "test-non-removable-widget";
// Adding non-removable items to a toolbar or the panel shouldn't change inDefaultState
add_task(function() {
ok(CustomizableUI.inDefaultState, "Should start in default state");
let button = createDummyXULButton(kWidgetId, "Test non-removable inDefaultState handling");
CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR);
button.setAttribute("removable", "false");
ok(CustomizableUI.inDefaultState, "Should still be in default state after navbar addition");
button.remove();
button = createDummyXULButton(kWidgetId, "Test non-removable inDefaultState handling");
CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_PANEL);
button.setAttribute("removable", "false");
ok(CustomizableUI.inDefaultState, "Should still be in default state after panel addition");
button.remove();
ok(CustomizableUI.inDefaultState, "Should be in default state after destroying both widgets");
});

View file

@ -0,0 +1,70 @@
/* 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/. */
"use strict";
const kToolbarId = "test-registerToolbarNode-toolbar";
const kButtonId = "test-registerToolbarNode-button";
registerCleanupFunction(cleanup);
// Registering a toolbar with defaultset attribute should work
add_task(function*() {
ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
let btn = createDummyXULButton(kButtonId);
let toolbar = document.createElement("toolbar");
toolbar.id = kToolbarId;
toolbar.setAttribute("customizable", true);
toolbar.setAttribute("defaultset", kButtonId);
gNavToolbox.appendChild(toolbar);
ok(CustomizableUI.areas.indexOf(kToolbarId) != -1,
"Toolbar should have been registered automatically.");
is(CustomizableUI.getAreaType(kToolbarId), CustomizableUI.TYPE_TOOLBAR,
"Area should be registered as toolbar");
assertAreaPlacements(kToolbarId, [kButtonId]);
ok(!CustomizableUI.inDefaultState, "No longer in default state after toolbar is registered and visible.");
CustomizableUI.unregisterArea(kToolbarId, true);
toolbar.remove();
ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
btn.remove();
});
// Registering a toolbar without a defaultset attribute should
// wait for the registerArea call
add_task(function*() {
ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
let btn = createDummyXULButton(kButtonId);
let toolbar = document.createElement("toolbar");
toolbar.id = kToolbarId;
toolbar.setAttribute("customizable", true);
gNavToolbox.appendChild(toolbar);
ok(CustomizableUI.areas.indexOf(kToolbarId) == -1,
"Toolbar should not yet have been registered automatically.");
CustomizableUI.registerArea(kToolbarId, {defaultPlacements: [kButtonId]});
ok(CustomizableUI.areas.indexOf(kToolbarId) != -1,
"Toolbar should have been registered now.");
is(CustomizableUI.getAreaType(kToolbarId), CustomizableUI.TYPE_TOOLBAR,
"Area should be registered as toolbar");
assertAreaPlacements(kToolbarId, [kButtonId]);
ok(!CustomizableUI.inDefaultState, "No longer in default state after toolbar is registered and visible.");
CustomizableUI.unregisterArea(kToolbarId, true);
toolbar.remove();
ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
btn.remove();
});
add_task(function* asyncCleanup() {
yield resetCustomization();
});
function cleanup() {
let toolbar = document.getElementById(kToolbarId);
if (toolbar) {
toolbar.remove();
}
let btn = document.getElementById(kButtonId) ||
gNavToolbox.querySelector("#" + kButtonId);
if (btn) {
btn.remove();
}
}

View file

@ -0,0 +1,136 @@
/* 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/. */
"use strict";
var button, menuButton;
/* Clicking a button should close the panel */
add_task(function*() {
button = document.createElement("toolbarbutton");
button.id = "browser_940307_button";
button.setAttribute("label", "Button");
PanelUI.contents.appendChild(button);
yield PanelUI.show();
let hiddenAgain = promisePanelHidden(window);
EventUtils.synthesizeMouseAtCenter(button, {});
yield hiddenAgain;
button.remove();
});
/* Clicking a menu button should close the panel, opening the popup shouldn't. */
add_task(function*() {
menuButton = document.createElement("toolbarbutton");
menuButton.setAttribute("type", "menu-button");
menuButton.id = "browser_940307_menubutton";
menuButton.setAttribute("label", "Menu button");
let menuPopup = document.createElement("menupopup");
menuPopup.id = "browser_940307_menupopup";
let menuItem = document.createElement("menuitem");
menuItem.setAttribute("label", "Menu item");
menuItem.id = "browser_940307_menuitem";
menuPopup.appendChild(menuItem);
menuButton.appendChild(menuPopup);
PanelUI.contents.appendChild(menuButton);
yield PanelUI.show();
let hiddenAgain = promisePanelHidden(window);
let innerButton = document.getAnonymousElementByAttribute(menuButton, "anonid", "button");
EventUtils.synthesizeMouseAtCenter(innerButton, {});
yield hiddenAgain;
// Now click the dropmarker to show the menu
yield PanelUI.show();
hiddenAgain = promisePanelHidden(window);
let menuShown = promisePanelElementShown(window, menuPopup);
let dropmarker = document.getAnonymousElementByAttribute(menuButton, "type", "menu-button");
EventUtils.synthesizeMouseAtCenter(dropmarker, {});
yield menuShown;
// Panel should stay open:
ok(isPanelUIOpen(), "Panel should still be open");
let menuHidden = promisePanelElementHidden(window, menuPopup);
// Then click the menu item to close all the things
EventUtils.synthesizeMouseAtCenter(menuItem, {});
yield menuHidden;
yield hiddenAgain;
menuButton.remove();
});
add_task(function*() {
let searchbar = document.getElementById("searchbar");
gCustomizeMode.addToPanel(searchbar);
let placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel");
yield PanelUI.show();
yield waitForCondition(() => "value" in searchbar && searchbar.value === "");
// Focusing a non-empty searchbox will cause us to open the
// autocomplete panel and search for suggestions, which would
// trigger network requests. Temporarily disable suggestions.
yield SpecialPowers.pushPrefEnv({set: [["browser.search.suggest.enabled", false]]});
searchbar.value = "foo";
searchbar.focus();
// Reaching into this context menu is pretty evil, but hey... it's a test.
let textbox = document.getAnonymousElementByAttribute(searchbar.textbox, "anonid", "textbox-input-box");
let contextmenu = document.getAnonymousElementByAttribute(textbox, "anonid", "input-box-contextmenu");
let contextMenuShown = promisePanelElementShown(window, contextmenu);
EventUtils.synthesizeMouseAtCenter(searchbar, {type: "contextmenu", button: 2});
yield contextMenuShown;
ok(isPanelUIOpen(), "Panel should still be open");
let selectAll = contextmenu.querySelector("[cmd='cmd_selectAll']");
let contextMenuHidden = promisePanelElementHidden(window, contextmenu);
EventUtils.synthesizeMouseAtCenter(selectAll, {});
yield contextMenuHidden;
// Hide the suggestion panel.
searchbar.textbox.popup.hidePopup();
ok(isPanelUIOpen(), "Panel should still be open");
let hiddenPanelPromise = promisePanelHidden(window);
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield hiddenPanelPromise;
ok(!isPanelUIOpen(), "Panel should no longer be open");
// We focused the search bar earlier - ensure we don't keep doing that.
gURLBar.select();
CustomizableUI.reset();
});
add_task(function*() {
button = document.createElement("toolbarbutton");
button.id = "browser_946166_button_disabled";
button.setAttribute("disabled", "true");
button.setAttribute("label", "Button");
PanelUI.contents.appendChild(button);
yield PanelUI.show();
EventUtils.synthesizeMouseAtCenter(button, {});
is(PanelUI.panel.state, "open", "Popup stays open");
button.removeAttribute("disabled");
let hiddenAgain = promisePanelHidden(window);
EventUtils.synthesizeMouseAtCenter(button, {});
yield hiddenAgain;
button.remove();
});
registerCleanupFunction(function() {
if (button && button.parentNode) {
button.remove();
}
if (menuButton && menuButton.parentNode) {
menuButton.remove();
}
// Sadly this isn't task.jsm-enabled, so we can't wait for this to happen. But we should
// definitely close it here and hope it won't interfere with other tests.
// Of course, all the tests are meant to do this themselves, but if they fail...
if (isPanelUIOpen()) {
PanelUI.hide();
}
});

View file

@ -0,0 +1,22 @@
/* 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/. */
"use strict";
const kTestBtnId = "test-removable-navbar-customize-mode";
// Items without the removable attribute in the navbar should be considered non-removable
add_task(function*() {
let btn = createDummyXULButton(kTestBtnId, "Test removable in navbar in customize mode");
document.getElementById("nav-bar").customizationTarget.appendChild(btn);
yield startCustomizing();
ok(!CustomizableUI.isWidgetRemovable(kTestBtnId), "Widget should not be considered removable");
yield endCustomizing();
document.getElementById(kTestBtnId).remove();
});
add_task(function* asyncCleanup() {
yield endCustomizing();
yield resetCustomization();
});

Some files were not shown because too many files have changed in this diff Show more