mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-02 05:17:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
44
browser/components/BrowserComponents.manifest
Normal file
44
browser/components/BrowserComponents.manifest
Normal 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
|
||||
235
browser/components/about/AboutRedirector.cpp
Normal file
235
browser/components/about/AboutRedirector.cpp
Normal 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
|
||||
32
browser/components/about/AboutRedirector.h
Normal file
32
browser/components/about/AboutRedirector.h
Normal 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__
|
||||
19
browser/components/about/moz.build
Normal file
19
browser/components/about/moz.build
Normal 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',
|
||||
]
|
||||
8
browser/components/build/Makefile.in
Normal file
8
browser/components/build/Makefile.in
Normal 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 =
|
||||
24
browser/components/build/moz.build
Normal file
24
browser/components/build/moz.build
Normal 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',
|
||||
]
|
||||
43
browser/components/build/nsBrowserCompsCID.h
Normal file
43
browser/components/build/nsBrowserCompsCID.h
Normal 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 } }
|
||||
131
browser/components/build/nsModule.cpp
Normal file
131
browser/components/build/nsModule.cpp
Normal 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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
6
browser/components/contextualidentity/jar.mn
Normal file
6
browser/components/contextualidentity/jar.mn
Normal 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)
|
||||
14
browser/components/contextualidentity/moz.build
Normal file
14
browser/components/contextualidentity/moz.build
Normal 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')
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../../testing/mochitest/browser.eslintrc.js"
|
||||
],
|
||||
|
||||
"rules": {
|
||||
"no-undef": "error"
|
||||
}
|
||||
};
|
||||
|
|
@ -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]
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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.");
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
@ -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");
|
||||
}
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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 |
|
|
@ -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>
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1 @@
|
|||
// empty worker, always succeed!
|
||||
189
browser/components/controlcenter/content/panel.inc.xul
Normal file
189
browser/components/controlcenter/content/panel.inc.xul
Normal 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>
|
||||
4420
browser/components/customizableui/CustomizableUI.jsm
Normal file
4420
browser/components/customizableui/CustomizableUI.jsm
Normal file
File diff suppressed because it is too large
Load diff
1281
browser/components/customizableui/CustomizableWidgets.jsm
Normal file
1281
browser/components/customizableui/CustomizableWidgets.jsm
Normal file
File diff suppressed because it is too large
Load diff
2341
browser/components/customizableui/CustomizeMode.jsm
Normal file
2341
browser/components/customizableui/CustomizeMode.jsm
Normal file
File diff suppressed because it is too large
Load diff
420
browser/components/customizableui/DragPositionManager.jsm
Normal file
420
browser/components/customizableui/DragPositionManager.jsm
Normal 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);
|
||||
172
browser/components/customizableui/PanelWideWidgetTracker.jsm
Normal file
172
browser/components/customizableui/PanelWideWidgetTracker.jsm
Normal 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);
|
||||
},
|
||||
};
|
||||
65
browser/components/customizableui/ScrollbarSampler.jsm
Normal file
65
browser/components/customizableui/ScrollbarSampler.jsm
Normal 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);
|
||||
|
|
@ -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>
|
||||
10
browser/components/customizableui/content/jar.mn
Normal file
10
browser/components/customizableui/content/jar.mn
Normal 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
|
||||
|
||||
7
browser/components/customizableui/content/moz.build
Normal file
7
browser/components/customizableui/content/moz.build
Normal 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']
|
||||
31
browser/components/customizableui/content/panelUI.css
Normal file
31
browser/components/customizableui/content/panelUI.css
Normal 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);
|
||||
}
|
||||
407
browser/components/customizableui/content/panelUI.inc.xul
Normal file
407
browser/components/customizableui/content/panelUI.inc.xul
Normal 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>
|
||||
558
browser/components/customizableui/content/panelUI.js
Normal file
558
browser/components/customizableui/content/panelUI.js
Normal 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";
|
||||
}
|
||||
}
|
||||
509
browser/components/customizableui/content/panelUI.xml
Normal file
509
browser/components/customizableui/content/panelUI.xml
Normal 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>
|
||||
618
browser/components/customizableui/content/toolbar.xml
Normal file
618
browser/components/customizableui/content/toolbar.xml
Normal 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>
|
||||
26
browser/components/customizableui/moz.build
Normal file
26
browser/components/customizableui/moz.build
Normal 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')
|
||||
7
browser/components/customizableui/test/.eslintrc.js
Normal file
7
browser/components/customizableui/test/.eslintrc.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../testing/mochitest/browser.eslintrc.js"
|
||||
]
|
||||
};
|
||||
154
browser/components/customizableui/test/browser.ini
Normal file
154
browser/components/customizableui/test/browser.ini
Normal 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]
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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.");
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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.")
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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: []
|
||||
});
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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.");
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue