mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
merge mozilla-inbound to mozilla-central a=merge
This commit is contained in:
commit
1b898382e6
3
CLOBBER
3
CLOBBER
@ -22,6 +22,5 @@
|
||||
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
|
||||
# don't change CLOBBER for WebIDL changes any more.
|
||||
|
||||
Bug 1241975 - port B2G branding Makefiles to moz.build;
|
||||
Bug 1241974 - remove testing/mochitest/manifests/Makefile.in;
|
||||
to fix another bustage from bug 1069556 landing;
|
||||
|
||||
|
@ -114,7 +114,9 @@ Makefile: $(BUILD_BACKEND_FILES)
|
||||
@$(TOUCH) $@
|
||||
|
||||
define build_backend_rule
|
||||
$(1): $$(shell cat $(1).in)
|
||||
$(1)_files := $$(shell cat $(1).in)
|
||||
$(1): $$($(1)_files)
|
||||
$$($(1)_files):
|
||||
|
||||
endef
|
||||
$(foreach file,$(BUILD_BACKEND_FILES),$(eval $(call build_backend_rule,$(file))))
|
||||
|
@ -145,7 +145,7 @@ MaiAtkObject::GetAtkHyperlink()
|
||||
void
|
||||
MaiAtkObject::Shutdown()
|
||||
{
|
||||
accWrap = 0;
|
||||
accWrap.SetBits(0);
|
||||
MaiHyperlink* maiHyperlink =
|
||||
(MaiHyperlink*)g_object_get_qdata(G_OBJECT(this), quark_mai_hyperlink);
|
||||
if (maiHyperlink) {
|
||||
@ -559,7 +559,7 @@ initializeCB(AtkObject *aAtkObj, gpointer aData)
|
||||
ATK_OBJECT_CLASS(parent_class)->initialize(aAtkObj, aData);
|
||||
|
||||
/* initialize object */
|
||||
MAI_ATK_OBJECT(aAtkObj)->accWrap = reinterpret_cast<uintptr_t>(aData);
|
||||
MAI_ATK_OBJECT(aAtkObj)->accWrap.SetBits(reinterpret_cast<uintptr_t>(aData));
|
||||
}
|
||||
|
||||
void
|
||||
@ -567,7 +567,7 @@ finalizeCB(GObject *aObj)
|
||||
{
|
||||
if (!IS_MAI_OBJECT(aObj))
|
||||
return;
|
||||
NS_ASSERTION(MAI_ATK_OBJECT(aObj)->accWrap == 0, "AccWrap NOT null");
|
||||
NS_ASSERTION(MAI_ATK_OBJECT(aObj)->accWrap.Bits() == 0, "AccWrap NOT null");
|
||||
|
||||
// call parent finalize function
|
||||
// finalize of GObjectClass will unref the accessible parent if has
|
||||
@ -1064,13 +1064,13 @@ GetAccessibleWrap(AtkObject* aAtkObj)
|
||||
NS_ENSURE_TRUE(isMAIObject || MAI_IS_ATK_SOCKET(aAtkObj),
|
||||
nullptr);
|
||||
|
||||
uintptr_t accWrapPtr = isMAIObject ?
|
||||
MAI_ATK_OBJECT(aAtkObj)->accWrap :
|
||||
reinterpret_cast<uintptr_t>(MAI_ATK_SOCKET(aAtkObj)->accWrap);
|
||||
if (accWrapPtr & IS_PROXY)
|
||||
return nullptr;
|
||||
|
||||
AccessibleWrap* accWrap = reinterpret_cast<AccessibleWrap*>(accWrapPtr);
|
||||
AccessibleWrap* accWrap = nullptr;
|
||||
if (isMAIObject) {
|
||||
Accessible* acc = MAI_ATK_OBJECT(aAtkObj)->accWrap.AsAccessible();
|
||||
accWrap = static_cast<AccessibleWrap*>(acc);
|
||||
} else {
|
||||
accWrap = MAI_ATK_SOCKET(aAtkObj)->accWrap;
|
||||
}
|
||||
|
||||
// Check if the accessible was deconstructed.
|
||||
if (!accWrap)
|
||||
@ -1089,11 +1089,10 @@ ProxyAccessible*
|
||||
GetProxy(AtkObject* aObj)
|
||||
{
|
||||
if (!aObj || !IS_MAI_OBJECT(aObj) ||
|
||||
!(MAI_ATK_OBJECT(aObj)->accWrap & IS_PROXY))
|
||||
!MAI_ATK_OBJECT(aObj)->accWrap.IsProxy())
|
||||
return nullptr;
|
||||
|
||||
return reinterpret_cast<ProxyAccessible*>(MAI_ATK_OBJECT(aObj)->accWrap
|
||||
& ~IS_PROXY);
|
||||
return MAI_ATK_OBJECT(aObj)->accWrap.AsProxy();
|
||||
}
|
||||
|
||||
AtkObject*
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "AccessibleOrProxy.h"
|
||||
#include "AccessibleWrap.h"
|
||||
|
||||
namespace mozilla {
|
||||
@ -95,7 +96,7 @@ struct MaiAtkObject
|
||||
* The AccessibleWrap whose properties and features are exported
|
||||
* via this object instance.
|
||||
*/
|
||||
uintptr_t accWrap;
|
||||
mozilla::a11y::AccessibleOrProxy accWrap;
|
||||
|
||||
/*
|
||||
* Get the AtkHyperlink for this atk object.
|
||||
|
@ -100,7 +100,7 @@ mai_atk_hyperlink_get_type(void)
|
||||
return type;
|
||||
}
|
||||
|
||||
MaiHyperlink::MaiHyperlink(uintptr_t aHyperLink) :
|
||||
MaiHyperlink::MaiHyperlink(AccessibleOrProxy aHyperLink) :
|
||||
mHyperlink(aHyperLink),
|
||||
mMaiAtkHyperlink(nullptr)
|
||||
{
|
||||
|
@ -23,31 +23,29 @@ namespace a11y {
|
||||
class MaiHyperlink
|
||||
{
|
||||
public:
|
||||
explicit MaiHyperlink(uintptr_t aHyperLink);
|
||||
explicit MaiHyperlink(AccessibleOrProxy aHyperLink);
|
||||
~MaiHyperlink();
|
||||
|
||||
public:
|
||||
AtkHyperlink* GetAtkHyperlink() const { return mMaiAtkHyperlink; }
|
||||
Accessible* GetAccHyperlink()
|
||||
{
|
||||
if (!mHyperlink || mHyperlink & IS_PROXY)
|
||||
if (!mHyperlink.IsAccessible())
|
||||
return nullptr;
|
||||
|
||||
Accessible* link = reinterpret_cast<Accessible*>(mHyperlink);
|
||||
Accessible* link = mHyperlink.AsAccessible();
|
||||
if (!link) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NS_ASSERTION(link->IsLink(), "Why isn't it a link!");
|
||||
return link;
|
||||
}
|
||||
|
||||
ProxyAccessible* Proxy() const
|
||||
{
|
||||
if (!(mHyperlink & IS_PROXY))
|
||||
return nullptr;
|
||||
|
||||
return reinterpret_cast<ProxyAccessible*>(mHyperlink & ~IS_PROXY);
|
||||
}
|
||||
ProxyAccessible* Proxy() const { return mHyperlink.AsProxy(); }
|
||||
|
||||
protected:
|
||||
uintptr_t mHyperlink;
|
||||
AccessibleOrProxy mHyperlink;
|
||||
AtkHyperlink* mMaiAtkHyperlink;
|
||||
};
|
||||
|
||||
|
63
accessible/base/AccessibleOrProxy.h
Normal file
63
accessible/base/AccessibleOrProxy.h
Normal file
@ -0,0 +1,63 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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 mozilla_a11y_AccessibleOrProxy_h
|
||||
#define mozilla_a11y_AccessibleOrProxy_h
|
||||
|
||||
#include "mozilla/a11y/Accessible.h"
|
||||
#include "mozilla/a11y/ProxyAccessible.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
|
||||
/**
|
||||
* This class stores an Accessible* or a ProxyAccessible* in a safe manner
|
||||
* with size sizeof(void*).
|
||||
*/
|
||||
class AccessibleOrProxy
|
||||
{
|
||||
public:
|
||||
MOZ_IMPLICIT AccessibleOrProxy(Accessible* aAcc) :
|
||||
mBits(reinterpret_cast<uintptr_t>(aAcc)) {}
|
||||
MOZ_IMPLICIT AccessibleOrProxy(ProxyAccessible* aProxy) :
|
||||
mBits(reinterpret_cast<uintptr_t>(aProxy) | IS_PROXY) {}
|
||||
MOZ_IMPLICIT AccessibleOrProxy(decltype(nullptr)) : mBits(0) {}
|
||||
|
||||
bool IsProxy() const { return mBits & IS_PROXY; }
|
||||
ProxyAccessible* AsProxy() const
|
||||
{
|
||||
if (IsProxy()) {
|
||||
return reinterpret_cast<ProxyAccessible*>(mBits & ~IS_PROXY);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool IsAccessible() const { return !IsProxy(); }
|
||||
Accessible* AsAccessible() const
|
||||
{
|
||||
if (IsAccessible()) {
|
||||
return reinterpret_cast<Accessible*>(mBits);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// XXX these are implementation details that ideally would not be exposed.
|
||||
uintptr_t Bits() const { return mBits; }
|
||||
void SetBits(uintptr_t aBits) { mBits = aBits; }
|
||||
|
||||
private:
|
||||
uintptr_t mBits;
|
||||
static const uintptr_t IS_PROXY = 0x1;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -40,6 +40,8 @@ using namespace mozilla::a11y;
|
||||
using namespace mozilla::dom;
|
||||
|
||||
StaticAutoPtr<nsTArray<DocAccessibleParent*>> DocManager::sRemoteDocuments;
|
||||
nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>*
|
||||
DocManager::sRemoteXPCDocumentCache = nullptr;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// DocManager
|
||||
@ -101,6 +103,16 @@ DocManager::NotifyOfDocumentShutdown(DocAccessible* aDocument,
|
||||
RemoveListeners(aDOMDocument);
|
||||
}
|
||||
|
||||
void
|
||||
DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent* aDoc)
|
||||
{
|
||||
xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
|
||||
if (doc) {
|
||||
doc->Shutdown();
|
||||
sRemoteXPCDocumentCache->Remove(aDoc);
|
||||
}
|
||||
}
|
||||
|
||||
xpcAccessibleDocument*
|
||||
DocManager::GetXPCDocument(DocAccessible* aDocument)
|
||||
{
|
||||
@ -115,6 +127,26 @@ DocManager::GetXPCDocument(DocAccessible* aDocument)
|
||||
return xpcDoc;
|
||||
}
|
||||
|
||||
xpcAccessibleDocument*
|
||||
DocManager::GetXPCDocument(DocAccessibleParent* aDoc)
|
||||
{
|
||||
xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
|
||||
if (doc) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
if (!sRemoteXPCDocumentCache) {
|
||||
sRemoteXPCDocumentCache =
|
||||
new nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>;
|
||||
}
|
||||
|
||||
doc =
|
||||
new xpcAccessibleDocument(aDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
|
||||
sRemoteXPCDocumentCache->Put(aDoc, doc);
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
bool
|
||||
DocManager::IsProcessingRefreshDriverNotification() const
|
||||
|
@ -90,6 +90,21 @@ public:
|
||||
static const nsTArray<DocAccessibleParent*>* TopLevelRemoteDocs()
|
||||
{ return sRemoteDocuments; }
|
||||
|
||||
/**
|
||||
* Remove the xpc document for a remote document if there is one.
|
||||
*/
|
||||
static void NotifyOfRemoteDocShutdown(DocAccessibleParent* adoc);
|
||||
|
||||
/**
|
||||
* Get a XPC document for a remote document.
|
||||
*/
|
||||
static xpcAccessibleDocument* GetXPCDocument(DocAccessibleParent* aDoc);
|
||||
static xpcAccessibleDocument* GetCachedXPCDocument(const DocAccessibleParent* aDoc)
|
||||
{
|
||||
return sRemoteXPCDocumentCache ? sRemoteXPCDocumentCache->GetWeak(aDoc)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
bool IsProcessingRefreshDriverNotification() const;
|
||||
#endif
|
||||
@ -147,6 +162,8 @@ private:
|
||||
typedef nsRefPtrHashtable<nsPtrHashKey<const DocAccessible>, xpcAccessibleDocument>
|
||||
XPCDocumentHashtable;
|
||||
XPCDocumentHashtable mXPCDocumentCache;
|
||||
static nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>*
|
||||
sRemoteXPCDocumentCache;
|
||||
|
||||
/*
|
||||
* The list of remote top level documents.
|
||||
|
@ -16,11 +16,13 @@
|
||||
#include "nsIBoxObject.h"
|
||||
#include "nsIDOMXULElement.h"
|
||||
#include "nsIDocShell.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIPresShell.h"
|
||||
#include "nsPresContext.h"
|
||||
#include "nsIScrollableFrame.h"
|
||||
#include "nsISelectionPrivate.h"
|
||||
#include "nsISelectionController.h"
|
||||
#include "nsISimpleEnumerator.h"
|
||||
#include "mozilla/dom/TouchEvent.h"
|
||||
#include "mozilla/EventListenerManager.h"
|
||||
#include "mozilla/EventStateManager.h"
|
||||
@ -658,3 +660,29 @@ nsCoreUtils::IsWhitespaceString(const nsSubstring& aString)
|
||||
|
||||
return iterBegin == iterEnd;
|
||||
}
|
||||
|
||||
bool
|
||||
nsCoreUtils::AccEventObserversExist()
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
|
||||
NS_ENSURE_TRUE(obsService, false);
|
||||
|
||||
nsCOMPtr<nsISimpleEnumerator> observers;
|
||||
obsService->EnumerateObservers(NS_ACCESSIBLE_EVENT_TOPIC,
|
||||
getter_AddRefs(observers));
|
||||
NS_ENSURE_TRUE(observers, false);
|
||||
|
||||
bool hasObservers = false;
|
||||
observers->HasMoreElements(&hasObservers);
|
||||
|
||||
return hasObservers;
|
||||
}
|
||||
|
||||
void
|
||||
nsCoreUtils::DispatchAccEvent(RefPtr<nsIAccessibleEvent> event)
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
|
||||
NS_ENSURE_TRUE_VOID(obsService);
|
||||
|
||||
obsService->NotifyObservers(event, NS_ACCESSIBLE_EVENT_TOPIC, nullptr);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#define nsCoreUtils_h_
|
||||
|
||||
#include "mozilla/EventForwards.h"
|
||||
#include "nsIAccessibleEvent.h"
|
||||
#include "nsIContent.h"
|
||||
#include "nsIDocument.h" // for GetShell()
|
||||
#include "nsIPresShell.h"
|
||||
@ -319,6 +320,16 @@ public:
|
||||
return aChar == ' ' || aChar == '\n' ||
|
||||
aChar == '\r' || aChar == '\t' || aChar == 0xa0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if there are any observers of accessible events.
|
||||
*/
|
||||
static bool AccEventObserversExist();
|
||||
|
||||
/**
|
||||
* Notify accessible event observers of an event.
|
||||
*/
|
||||
static void DispatchAccEvent(RefPtr<nsIAccessibleEvent> aEvent);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -885,20 +885,8 @@ Accessible::HandleAccEvent(AccEvent* aEvent)
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
|
||||
NS_ENSURE_TRUE(obsService, NS_ERROR_FAILURE);
|
||||
|
||||
nsCOMPtr<nsISimpleEnumerator> observers;
|
||||
obsService->EnumerateObservers(NS_ACCESSIBLE_EVENT_TOPIC,
|
||||
getter_AddRefs(observers));
|
||||
|
||||
NS_ENSURE_STATE(observers);
|
||||
|
||||
bool hasObservers = false;
|
||||
observers->HasMoreElements(&hasObservers);
|
||||
if (hasObservers) {
|
||||
nsCOMPtr<nsIAccessibleEvent> event = MakeXPCEvent(aEvent);
|
||||
return obsService->NotifyObservers(event, NS_ACCESSIBLE_EVENT_TOPIC, nullptr);
|
||||
if (nsCoreUtils::AccEventObserversExist()) {
|
||||
nsCoreUtils::DispatchAccEvent(MakeXPCEvent(aEvent));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
@ -9,6 +9,10 @@
|
||||
#include "mozilla/a11y/Platform.h"
|
||||
#include "ProxyAccessible.h"
|
||||
#include "mozilla/dom/TabParent.h"
|
||||
#include "xpcAccessibleDocument.h"
|
||||
#include "xpcAccEvents.h"
|
||||
#include "nsAccUtils.h"
|
||||
#include "nsCoreUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
@ -144,6 +148,19 @@ DocAccessibleParent::RecvEvent(const uint64_t& aID, const uint32_t& aEventType)
|
||||
}
|
||||
|
||||
ProxyEvent(proxy, aEventType);
|
||||
|
||||
if (!nsCoreUtils::AccEventObserversExist()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
|
||||
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
||||
nsIDOMNode* node = nullptr;
|
||||
bool fromUser = true; // XXX fix me
|
||||
RefPtr<xpcAccEvent> event = new xpcAccEvent(aEventType, xpcAcc, doc, node,
|
||||
fromUser);
|
||||
nsCoreUtils::DispatchAccEvent(Move(event));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -159,6 +176,23 @@ DocAccessibleParent::RecvStateChangeEvent(const uint64_t& aID,
|
||||
}
|
||||
|
||||
ProxyStateChangeEvent(target, aState, aEnabled);
|
||||
|
||||
if (!nsCoreUtils::AccEventObserversExist()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
|
||||
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
||||
uint32_t type = nsIAccessibleEvent::EVENT_STATE_CHANGE;
|
||||
bool extra;
|
||||
uint32_t state = nsAccUtils::To32States(aState, &extra);
|
||||
bool fromUser = true; // XXX fix this
|
||||
nsIDOMNode* node = nullptr; // XXX can we do better?
|
||||
RefPtr<xpcAccStateChangeEvent> event =
|
||||
new xpcAccStateChangeEvent(type, xpcAcc, doc, node, fromUser, state, extra,
|
||||
aEnabled);
|
||||
nsCoreUtils::DispatchAccEvent(Move(event));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -172,6 +206,20 @@ DocAccessibleParent::RecvCaretMoveEvent(const uint64_t& aID, const int32_t& aOff
|
||||
}
|
||||
|
||||
ProxyCaretMoveEvent(proxy, aOffset);
|
||||
|
||||
if (!nsCoreUtils::AccEventObserversExist()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
|
||||
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
||||
nsIDOMNode* node = nullptr;
|
||||
bool fromUser = true; // XXX fix me
|
||||
uint32_t type = nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED;
|
||||
RefPtr<xpcAccCaretMoveEvent> event =
|
||||
new xpcAccCaretMoveEvent(type, xpcAcc, doc, node, fromUser, aOffset);
|
||||
nsCoreUtils::DispatchAccEvent(Move(event));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -191,6 +239,19 @@ DocAccessibleParent::RecvTextChangeEvent(const uint64_t& aID,
|
||||
|
||||
ProxyTextChangeEvent(target, aStr, aStart, aLen, aIsInsert, aFromUser);
|
||||
|
||||
if (!nsCoreUtils::AccEventObserversExist()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
|
||||
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
||||
uint32_t type = nsIAccessibleEvent::EVENT_TEXT_CHANGED;
|
||||
nsIDOMNode* node = nullptr;
|
||||
RefPtr<xpcAccTextChangeEvent> event =
|
||||
new xpcAccTextChangeEvent(type, xpcAcc, doc, node, aFromUser, aStart, aLen,
|
||||
aIsInsert, aStr);
|
||||
nsCoreUtils::DispatchAccEvent(Move(event));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -267,6 +328,8 @@ DocAccessibleParent::Destroy()
|
||||
ProxyDestroyed(iter.Get()->mProxy);
|
||||
iter.Remove();
|
||||
}
|
||||
|
||||
DocManager::NotifyOfRemoteDocShutdown(this);
|
||||
ProxyDestroyed(this);
|
||||
if (mParentDoc)
|
||||
mParentDoc->RemoveChildDoc(this);
|
||||
@ -290,5 +353,13 @@ DocAccessibleParent::CheckDocTree() const
|
||||
return true;
|
||||
}
|
||||
|
||||
xpcAccessibleGeneric*
|
||||
DocAccessibleParent::GetXPCAccessible(ProxyAccessible* aProxy)
|
||||
{
|
||||
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
||||
MOZ_ASSERT(doc);
|
||||
|
||||
return doc->GetXPCAccessible(aProxy);
|
||||
}
|
||||
} // a11y
|
||||
} // mozilla
|
||||
|
@ -17,6 +17,8 @@
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
|
||||
class xpcAccessibleGeneric;
|
||||
|
||||
/*
|
||||
* These objects live in the main process and comunicate with and represent
|
||||
* an accessible document in a content process.
|
||||
@ -159,6 +161,7 @@ private:
|
||||
const nsTArray<AccessibleData>& aNewTree, uint32_t aIdx,
|
||||
uint32_t aIdxInParent);
|
||||
MOZ_WARN_UNUSED_RESULT bool CheckDocTree() const;
|
||||
xpcAccessibleGeneric* GetXPCAccessible(ProxyAccessible* aProxy);
|
||||
|
||||
nsTArray<DocAccessibleParent*> mChildDocs;
|
||||
DocAccessibleParent* mParentDoc;
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "mozilla/a11y/Platform.h"
|
||||
#include "RelationType.h"
|
||||
#include "mozilla/a11y/Role.h"
|
||||
#include "xpcAccessibleDocument.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
@ -23,6 +24,11 @@ ProxyAccessible::Shutdown()
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(!IsDoc());
|
||||
NS_ASSERTION(!mOuterDoc, "Why do we still have a child doc?");
|
||||
xpcAccessibleDocument* xpcDoc =
|
||||
GetAccService()->GetCachedXPCDocument(Document());
|
||||
if (xpcDoc) {
|
||||
xpcDoc->NotifyOfShutdown(this);
|
||||
}
|
||||
|
||||
// XXX Ideally this wouldn't be necessary, but it seems OuterDoc accessibles
|
||||
// can be destroyed before the doc they own.
|
||||
|
@ -24,6 +24,7 @@ if CONFIG['ACCESSIBILITY']:
|
||||
LOCAL_INCLUDES += [
|
||||
'../base',
|
||||
'../generic',
|
||||
'../xpcom',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_ENABLE_GTK']:
|
||||
|
@ -64,3 +64,5 @@ xpc_acc_events_cpp.script = 'AccEventGen.py:gen_cpp_file'
|
||||
xpc_acc_events_cpp.inputs += ['AccEvents.conf']
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
@ -36,7 +36,7 @@ protected:
|
||||
virtual ~xpcAccessibleApplication() {}
|
||||
|
||||
private:
|
||||
ApplicationAccessible* Intl() { return mIntl->AsApplication(); }
|
||||
ApplicationAccessible* Intl() { return mIntl.AsAccessible()->AsApplication(); }
|
||||
|
||||
xpcAccessibleApplication(const xpcAccessibleApplication&) = delete;
|
||||
xpcAccessibleApplication& operator =(const xpcAccessibleApplication&) = delete;
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "xpcAccessibleTable.h"
|
||||
#include "xpcAccessibleTableCell.h"
|
||||
|
||||
#include "mozilla/a11y/DocAccessibleParent.h"
|
||||
#include "DocAccessible-inl.h"
|
||||
#include "nsIDOMDocument.h"
|
||||
|
||||
@ -168,6 +169,7 @@ xpcAccessibleDocument::GetVirtualCursor(nsIAccessiblePivot** aVirtualCursor)
|
||||
xpcAccessibleGeneric*
|
||||
xpcAccessibleDocument::GetAccessible(Accessible* aAccessible)
|
||||
{
|
||||
MOZ_ASSERT(!mRemote);
|
||||
if (ToXPCDocument(aAccessible->Document()) != this) {
|
||||
NS_ERROR("This XPCOM document is not related with given internal accessible!");
|
||||
return nullptr;
|
||||
@ -195,6 +197,27 @@ xpcAccessibleDocument::GetAccessible(Accessible* aAccessible)
|
||||
return xpcAcc;
|
||||
}
|
||||
|
||||
xpcAccessibleGeneric*
|
||||
xpcAccessibleDocument::GetXPCAccessible(ProxyAccessible* aProxy)
|
||||
{
|
||||
MOZ_ASSERT(mRemote);
|
||||
MOZ_ASSERT(aProxy->Document() == mIntl.AsProxy());
|
||||
if (aProxy->IsDoc()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
xpcAccessibleGeneric* acc = mCache.GetWeak(aProxy);
|
||||
if (acc) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
// XXX support exposing optional interfaces.
|
||||
acc = new xpcAccessibleGeneric(aProxy, 0);
|
||||
mCache.Put(aProxy, acc);
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
void
|
||||
xpcAccessibleDocument::Shutdown()
|
||||
{
|
||||
|
@ -25,7 +25,11 @@ class xpcAccessibleDocument : public xpcAccessibleHyperText,
|
||||
{
|
||||
public:
|
||||
explicit xpcAccessibleDocument(DocAccessible* aIntl) :
|
||||
xpcAccessibleHyperText(aIntl), mCache(kDefaultCacheLength) { }
|
||||
xpcAccessibleHyperText(aIntl), mCache(kDefaultCacheLength), mRemote(false) { }
|
||||
|
||||
xpcAccessibleDocument(ProxyAccessible* aProxy, uint32_t aInterfaces) :
|
||||
xpcAccessibleHyperText(aProxy, aInterfaces), mCache(kDefaultCacheLength),
|
||||
mRemote(true) {}
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(xpcAccessibleDocument,
|
||||
@ -51,6 +55,7 @@ public:
|
||||
* Return XPCOM wrapper for the internal accessible.
|
||||
*/
|
||||
xpcAccessibleGeneric* GetAccessible(Accessible* aAccessible);
|
||||
xpcAccessibleGeneric* GetXPCAccessible(ProxyAccessible* aProxy);
|
||||
|
||||
virtual void Shutdown() override;
|
||||
|
||||
@ -58,10 +63,18 @@ protected:
|
||||
virtual ~xpcAccessibleDocument() {}
|
||||
|
||||
private:
|
||||
DocAccessible* Intl() { return mIntl->AsDoc(); }
|
||||
DocAccessible* Intl()
|
||||
{
|
||||
if (Accessible* acc = mIntl.AsAccessible()) {
|
||||
return acc->AsDoc();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NotifyOfShutdown(Accessible* aAccessible)
|
||||
{
|
||||
MOZ_ASSERT(!mRemote);
|
||||
xpcAccessibleGeneric* xpcAcc = mCache.GetWeak(aAccessible);
|
||||
if (xpcAcc)
|
||||
xpcAcc->Shutdown();
|
||||
@ -69,13 +82,26 @@ private:
|
||||
mCache.Remove(aAccessible);
|
||||
}
|
||||
|
||||
void NotifyOfShutdown(ProxyAccessible* aProxy)
|
||||
{
|
||||
MOZ_ASSERT(mRemote);
|
||||
xpcAccessibleGeneric* acc = mCache.GetWeak(aProxy);
|
||||
if (acc) {
|
||||
acc->Shutdown();
|
||||
}
|
||||
|
||||
mCache.Remove(aProxy);
|
||||
}
|
||||
|
||||
friend class DocManager;
|
||||
friend class DocAccessible;
|
||||
friend class ProxyAccessible;
|
||||
|
||||
xpcAccessibleDocument(const xpcAccessibleDocument&) = delete;
|
||||
xpcAccessibleDocument& operator =(const xpcAccessibleDocument&) = delete;
|
||||
|
||||
nsRefPtrHashtable<nsPtrHashKey<const Accessible>, xpcAccessibleGeneric> mCache;
|
||||
nsRefPtrHashtable<nsPtrHashKey<const void>, xpcAccessibleGeneric> mCache;
|
||||
bool mRemote;
|
||||
};
|
||||
|
||||
inline xpcAccessibleGeneric*
|
||||
|
@ -33,7 +33,7 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(xpcAccessibleGeneric)
|
||||
Accessible*
|
||||
xpcAccessibleGeneric::ToInternalAccessible() const
|
||||
{
|
||||
return mIntl;
|
||||
return mIntl.AsAccessible();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "xpcAccessibleValue.h"
|
||||
|
||||
#include "Accessible.h"
|
||||
#include "AccessibleOrProxy.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
@ -29,14 +30,28 @@ public:
|
||||
explicit xpcAccessibleGeneric(Accessible* aInternal) :
|
||||
mIntl(aInternal), mSupportedIfaces(0)
|
||||
{
|
||||
if (mIntl->IsSelect())
|
||||
if (aInternal->IsSelect())
|
||||
mSupportedIfaces |= eSelectable;
|
||||
if (mIntl->HasNumericValue())
|
||||
if (aInternal->HasNumericValue())
|
||||
mSupportedIfaces |= eValue;
|
||||
if (mIntl->IsLink())
|
||||
if (aInternal->IsLink())
|
||||
mSupportedIfaces |= eHyperLink;
|
||||
}
|
||||
|
||||
xpcAccessibleGeneric(ProxyAccessible* aProxy, uint32_t aInterfaces) :
|
||||
mIntl(aProxy)
|
||||
{
|
||||
if (aInterfaces & Interfaces::SELECTION) {
|
||||
mSupportedIfaces |= eSelectable;
|
||||
}
|
||||
if (aInterfaces & Interfaces::VALUE) {
|
||||
mSupportedIfaces |= eValue;
|
||||
}
|
||||
if (aInterfaces & Interfaces::HYPERLINK) {
|
||||
mSupportedIfaces |= eHyperLink;
|
||||
}
|
||||
}
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(xpcAccessibleGeneric, nsIAccessible)
|
||||
|
||||
@ -49,7 +64,7 @@ public:
|
||||
protected:
|
||||
virtual ~xpcAccessibleGeneric() {}
|
||||
|
||||
Accessible* mIntl;
|
||||
AccessibleOrProxy mIntl;
|
||||
|
||||
enum {
|
||||
eSelectable = 1 << 0,
|
||||
@ -73,25 +88,25 @@ private:
|
||||
inline Accessible*
|
||||
xpcAccessible::Intl()
|
||||
{
|
||||
return static_cast<xpcAccessibleGeneric*>(this)->mIntl;
|
||||
return static_cast<xpcAccessibleGeneric*>(this)->mIntl.AsAccessible();
|
||||
}
|
||||
|
||||
inline Accessible*
|
||||
xpcAccessibleHyperLink::Intl()
|
||||
{
|
||||
return static_cast<xpcAccessibleGeneric*>(this)->mIntl;
|
||||
return static_cast<xpcAccessibleGeneric*>(this)->mIntl.AsAccessible();
|
||||
}
|
||||
|
||||
inline Accessible*
|
||||
xpcAccessibleSelectable::Intl()
|
||||
{
|
||||
return static_cast<xpcAccessibleGeneric*>(this)->mIntl;
|
||||
return static_cast<xpcAccessibleGeneric*>(this)->mIntl.AsAccessible();
|
||||
}
|
||||
|
||||
inline Accessible*
|
||||
xpcAccessibleValue::Intl()
|
||||
{
|
||||
return static_cast<xpcAccessibleGeneric*>(this)->mIntl;
|
||||
return static_cast<xpcAccessibleGeneric*>(this)->mIntl.AsAccessible();
|
||||
}
|
||||
|
||||
} // namespace a11y
|
||||
|
@ -26,10 +26,13 @@ public:
|
||||
explicit xpcAccessibleHyperText(Accessible* aIntl) :
|
||||
xpcAccessibleGeneric(aIntl)
|
||||
{
|
||||
if (mIntl->IsHyperText() && mIntl->AsHyperText()->IsTextRole())
|
||||
if (aIntl->IsHyperText() && aIntl->AsHyperText()->IsTextRole())
|
||||
mSupportedIfaces |= eText;
|
||||
}
|
||||
|
||||
xpcAccessibleHyperText(ProxyAccessible* aProxy, uint32_t aInterfaces) :
|
||||
xpcAccessibleGeneric(aProxy, aInterfaces) { mSupportedIfaces |= eText; }
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
NS_DECL_NSIACCESSIBLETEXT
|
||||
@ -40,7 +43,14 @@ protected:
|
||||
virtual ~xpcAccessibleHyperText() {}
|
||||
|
||||
private:
|
||||
HyperTextAccessible* Intl() { return mIntl->AsHyperText(); }
|
||||
HyperTextAccessible* Intl()
|
||||
{
|
||||
if (Accessible* acc = mIntl.AsAccessible()) {
|
||||
return acc->AsHyperText();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
xpcAccessibleHyperText(const xpcAccessibleHyperText&) = delete;
|
||||
xpcAccessibleHyperText& operator =(const xpcAccessibleHyperText&) = delete;
|
||||
|
@ -21,6 +21,9 @@ public:
|
||||
explicit xpcAccessibleImage(Accessible* aIntl) :
|
||||
xpcAccessibleGeneric(aIntl) { }
|
||||
|
||||
xpcAccessibleImage(ProxyAccessible* aProxy, uint32_t aInterfaces) :
|
||||
xpcAccessibleGeneric(aProxy, aInterfaces) {}
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
NS_IMETHOD GetImagePosition(uint32_t aCoordType,
|
||||
@ -31,7 +34,8 @@ protected:
|
||||
virtual ~xpcAccessibleImage() {}
|
||||
|
||||
private:
|
||||
ImageAccessible* Intl() { return mIntl->AsImage(); }
|
||||
ImageAccessible* Intl()
|
||||
{ return mIntl.IsAccessible() ? mIntl.AsAccessible()->AsImage() : nullptr; }
|
||||
|
||||
xpcAccessibleImage(const xpcAccessibleImage&) = delete;
|
||||
xpcAccessibleImage& operator =(const xpcAccessibleImage&) = delete;
|
||||
|
@ -23,6 +23,9 @@ public:
|
||||
explicit xpcAccessibleTable(Accessible* aIntl) :
|
||||
xpcAccessibleGeneric(aIntl) { }
|
||||
|
||||
xpcAccessibleTable(ProxyAccessible* aProxy, uint32_t aInterfaces) :
|
||||
xpcAccessibleGeneric(aProxy, aInterfaces) {}
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
// nsIAccessibleTable
|
||||
@ -80,7 +83,8 @@ protected:
|
||||
virtual ~xpcAccessibleTable() {}
|
||||
|
||||
private:
|
||||
TableAccessible* Intl() { return mIntl->AsTable(); }
|
||||
TableAccessible* Intl()
|
||||
{ return mIntl.IsAccessible() ? mIntl.AsAccessible()->AsTable() : nullptr; }
|
||||
|
||||
xpcAccessibleTable(const xpcAccessibleTable&) = delete;
|
||||
xpcAccessibleTable& operator =(const xpcAccessibleTable&) = delete;
|
||||
|
@ -24,6 +24,9 @@ public:
|
||||
explicit xpcAccessibleTableCell(Accessible* aIntl) :
|
||||
xpcAccessibleHyperText(aIntl) { }
|
||||
|
||||
xpcAccessibleTableCell(ProxyAccessible* aProxy, uint32_t aInterfaces) :
|
||||
xpcAccessibleHyperText(aProxy, aInterfaces) {}
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
// nsIAccessibleTableCell
|
||||
@ -40,7 +43,14 @@ protected:
|
||||
virtual ~xpcAccessibleTableCell() {}
|
||||
|
||||
private:
|
||||
TableCellAccessible* Intl() { return mIntl->AsTableCell(); }
|
||||
TableCellAccessible* Intl()
|
||||
{
|
||||
if (Accessible* acc = mIntl.AsAccessible()) {
|
||||
return acc->AsTableCell();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
xpcAccessibleTableCell(const xpcAccessibleTableCell&) = delete;
|
||||
xpcAccessibleTableCell& operator =(const xpcAccessibleTableCell&) = delete;
|
||||
|
@ -1,5 +1,5 @@
|
||||
[DEFAULT]
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug ?????? - about:newtab tests don't work in e10s
|
||||
skip-if = buildapp == 'mulet'
|
||||
support-files =
|
||||
head.js
|
||||
|
||||
@ -13,7 +13,6 @@ skip-if = true # Bug 1119906
|
||||
[browser_newtab_bug725996.js]
|
||||
[browser_newtab_bug734043.js]
|
||||
[browser_newtab_bug735987.js]
|
||||
skip-if = (os == 'mac' && os_version == '10.10') # bug 1122478 - newtab drag-drop tests fail on OS X 10.10
|
||||
[browser_newtab_bug752841.js]
|
||||
[browser_newtab_bug765628.js]
|
||||
[browser_newtab_bug876313.js]
|
||||
@ -22,9 +21,7 @@ skip-if = (os == 'mac' && os_version == '10.10') # bug 1122478 - newtab drag-dro
|
||||
[browser_newtab_bug998387.js]
|
||||
[browser_newtab_disable.js]
|
||||
[browser_newtab_drag_drop.js]
|
||||
skip-if = os == "win" || (os == 'mac' && os_version == '10.10') # Bug 1152810 - can't do simulateDrop(0,0) on Windows; Bug 1122478 - newtab drag-drop tests fail on OS X 10.10
|
||||
[browser_newtab_drag_drop_ext.js]
|
||||
skip-if = (os == 'mac' && os_version == '10.10') # bug 1122478 - newtab drag-drop tests fail on OS X 10.10
|
||||
[browser_newtab_drop_preview.js]
|
||||
[browser_newtab_enhanced.js]
|
||||
[browser_newtab_focus.js]
|
||||
|
@ -1,8 +1,6 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const PRELOAD_PREF = "browser.newtab.preload";
|
||||
|
||||
gDirectorySource = "data:application/json," + JSON.stringify({
|
||||
"directory": [{
|
||||
url: "http://example1.com/",
|
||||
@ -13,16 +11,16 @@ gDirectorySource = "data:application/json," + JSON.stringify({
|
||||
}]
|
||||
});
|
||||
|
||||
function runTests() {
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(PRELOAD_PREF);
|
||||
});
|
||||
|
||||
Services.prefs.setBoolPref(PRELOAD_PREF, false);
|
||||
add_task(function* () {
|
||||
yield pushPrefs(["browser.newtab.preload", false]);
|
||||
|
||||
// Make the page have a directory link
|
||||
yield setLinks([]);
|
||||
yield addNewTabPageTab();
|
||||
let titleNode = getCell(0).node.querySelector(".newtab-title");
|
||||
is(titleNode.style.backgroundColor, "green", "title bg color is green");
|
||||
}
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
let color = yield performOnCell(0, cell => {
|
||||
return cell.node.querySelector(".newtab-title").style.backgroundColor;
|
||||
});
|
||||
|
||||
is(color, "green", "title bg color is green");
|
||||
});
|
||||
|
@ -8,13 +8,12 @@
|
||||
|
||||
const CAPTURE_PREF = "browser.pagethumbnails.capturing_disabled";
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
let imports = {};
|
||||
Cu.import("resource://gre/modules/PageThumbs.jsm", imports);
|
||||
|
||||
// Disable captures.
|
||||
let originalDisabledState = Services.prefs.getBoolPref(CAPTURE_PREF);
|
||||
Services.prefs.setBoolPref(CAPTURE_PREF, true);
|
||||
yield pushPrefs([CAPTURE_PREF, false]);
|
||||
|
||||
// Make sure the thumbnail doesn't exist yet.
|
||||
let url = "http://example.com/";
|
||||
@ -34,30 +33,32 @@ function runTests() {
|
||||
gBrowser._createPreloadBrowser();
|
||||
|
||||
// Wait for the preloaded browser to load.
|
||||
yield waitForBrowserLoad(gBrowser._preloadedBrowser);
|
||||
if (gBrowser._preloadedBrowser.contentDocument.readyState != "complete") {
|
||||
yield BrowserTestUtils.waitForEvent(gBrowser._preloadedBrowser, "load", true);
|
||||
}
|
||||
|
||||
// We're now ready to use the preloaded browser.
|
||||
BrowserOpenTab();
|
||||
let tab = gBrowser.selectedTab;
|
||||
let doc = tab.linkedBrowser.contentDocument;
|
||||
|
||||
let thumbnailCreatedPromise = new Promise(resolve => {
|
||||
// Showing the preloaded tab should trigger thumbnail capture.
|
||||
Services.obs.addObserver(function onCreate(subj, topic, data) {
|
||||
if (data != url)
|
||||
return;
|
||||
Services.obs.removeObserver(onCreate, "page-thumbnail:create");
|
||||
ok(true, "thumbnail created after preloaded tab was shown");
|
||||
|
||||
resolve();
|
||||
}, "page-thumbnail:create", false);
|
||||
});
|
||||
|
||||
// Enable captures.
|
||||
Services.prefs.setBoolPref(CAPTURE_PREF, false);
|
||||
yield pushPrefs([CAPTURE_PREF, false]);
|
||||
|
||||
// Showing the preloaded tab should trigger thumbnail capture.
|
||||
Services.obs.addObserver(function onCreate(subj, topic, data) {
|
||||
if (data != url)
|
||||
return;
|
||||
Services.obs.removeObserver(onCreate, "page-thumbnail:create");
|
||||
ok(true, "thumbnail created after preloaded tab was shown");
|
||||
yield thumbnailCreatedPromise;
|
||||
|
||||
// Test finished!
|
||||
Services.prefs.setBoolPref(CAPTURE_PREF, originalDisabledState);
|
||||
gBrowser.removeTab(tab);
|
||||
file.remove(false);
|
||||
TestRunner.next();
|
||||
}, "page-thumbnail:create", false);
|
||||
|
||||
info("Waiting for thumbnail capture");
|
||||
yield true;
|
||||
}
|
||||
// Test finished!
|
||||
gBrowser.removeTab(tab);
|
||||
file.remove(false);
|
||||
});
|
||||
|
@ -20,7 +20,7 @@ gDirectorySource = "data:application/json," + JSON.stringify({
|
||||
}]
|
||||
});
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
|
||||
DirectoryLinksProvider.getFrecentSitesName = () => "";
|
||||
let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
|
||||
@ -31,29 +31,29 @@ function runTests() {
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
yield customizeNewTabPage("enhanced"); // Toggle enhanced off
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
yield blockCell(4);
|
||||
checkGrid("0,1,2,3,5,6,7,8,9");
|
||||
yield* checkGrid("0,1,2,3,5,6,7,8,9");
|
||||
|
||||
yield blockCell(4);
|
||||
checkGrid("0,1,2,3,6,7,8,9,");
|
||||
yield* checkGrid("0,1,2,3,6,7,8,9,");
|
||||
|
||||
yield blockCell(4);
|
||||
checkGrid("0,1,2,3,7,8,9,,");
|
||||
yield* checkGrid("0,1,2,3,7,8,9,,");
|
||||
|
||||
// we removed a pinned site
|
||||
yield restore();
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",1");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1p,2,3,4,5,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1p,2,3,4,5,6,7,8");
|
||||
|
||||
yield blockCell(1);
|
||||
checkGrid("0,2,3,4,5,6,7,8,");
|
||||
yield* checkGrid("0,2,3,4,5,6,7,8,");
|
||||
|
||||
// we remove the last site on the grid (which is pinned) and expect the gap
|
||||
// to be re-filled and the new site to be unpinned
|
||||
@ -61,11 +61,11 @@ function runTests() {
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
setPinnedLinks(",,,,,,,,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8p");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8p");
|
||||
|
||||
yield blockCell(8);
|
||||
checkGrid("0,1,2,3,4,5,6,7,9");
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,9");
|
||||
|
||||
// we remove the first site on the grid with the last one pinned. all cells
|
||||
// but the last one should shift to the left and a new site fades in
|
||||
@ -73,22 +73,23 @@ function runTests() {
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
setPinnedLinks(",,,,,,,,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8p");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8p");
|
||||
|
||||
yield blockCell(0);
|
||||
checkGrid("1,2,3,4,5,6,7,9,8p");
|
||||
yield* checkGrid("1,2,3,4,5,6,7,9,8p");
|
||||
|
||||
// Test that blocking the targeted site also removes its associated suggested tile
|
||||
NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
|
||||
yield restore();
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
yield customizeNewTabPage("enhanced"); // Toggle enhanced on
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("http://suggested.com/,0,1,2,3,4,5,6,7,8,9");
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
yield* checkGrid("http://suggested.com/,0,1,2,3,4,5,6,7,8,9");
|
||||
|
||||
yield blockCell(1);
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("1,2,3,4,5,6,7,8,9");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("1,2,3,4,5,6,7,8,9");
|
||||
DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
|
||||
}
|
||||
});
|
||||
|
@ -20,34 +20,35 @@ gDirectorySource = "data:application/json," + JSON.stringify({
|
||||
}]
|
||||
});
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
|
||||
DirectoryLinksProvider.getFrecentSitesName = () => "";
|
||||
|
||||
function getData(cellNum) {
|
||||
let cell = getCell(cellNum);
|
||||
if (!cell.site)
|
||||
return null;
|
||||
let siteNode = cell.site.node;
|
||||
return {
|
||||
type: siteNode.getAttribute("type"),
|
||||
thumbnail: siteNode.querySelector(".newtab-thumbnail").style.backgroundImage,
|
||||
enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
|
||||
title: siteNode.querySelector(".newtab-title").textContent,
|
||||
suggested: siteNode.getAttribute("suggested"),
|
||||
url: siteNode.querySelector(".newtab-link").getAttribute("href"),
|
||||
};
|
||||
return performOnCell(cellNum, cell => {
|
||||
if (!cell.site)
|
||||
return null;
|
||||
let siteNode = cell.site.node;
|
||||
return {
|
||||
type: siteNode.getAttribute("type"),
|
||||
thumbnail: siteNode.querySelector(".newtab-thumbnail").style.backgroundImage,
|
||||
enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
|
||||
title: siteNode.querySelector(".newtab-title").textContent,
|
||||
suggested: siteNode.getAttribute("suggested"),
|
||||
url: siteNode.querySelector(".newtab-link").getAttribute("href"),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
// load another newtab since the first may not get suggested tile
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("http://example.com/landing/page.html,0,1,2,3,4,5,6,7,8,9");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("http://example.com/landing/page.html,0,1,2,3,4,5,6,7,8,9");
|
||||
// evaluate suggested tile
|
||||
let tileData = getData(0);
|
||||
let tileData = yield getData(0);
|
||||
is(tileData.type, "affiliate", "unpinned type");
|
||||
is(tileData.thumbnail, "url(\"data:image/png;base64,helloWORLD3\")", "unpinned thumbnail");
|
||||
is(tileData.enhanced, "url(\"data:image/png;base64,helloWORLD2\")", "unpinned enhanced");
|
||||
@ -58,27 +59,24 @@ function runTests() {
|
||||
is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/landing/page.html"}), false, "suggested tile is not pinned");
|
||||
|
||||
// pin suggested tile
|
||||
whenPagesUpdated();
|
||||
let siteNode = getCell(0).node.querySelector(".newtab-site");
|
||||
let pinButton = siteNode.querySelector(".newtab-control-pin");
|
||||
EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
|
||||
// wait for whenPagesUpdated
|
||||
yield null;
|
||||
let updatedPromise = whenPagesUpdated();
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site > .newtab-control-pin", {}, gBrowser.selectedBrowser);
|
||||
yield updatedPromise;
|
||||
|
||||
// tile should be pinned and turned into history tile
|
||||
is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/landing/page.html"}), true, "suggested tile is pinned");
|
||||
tileData = getData(0);
|
||||
tileData = yield getData(0);
|
||||
is(tileData.type, "history", "pinned type");
|
||||
is(tileData.suggested, null, "no suggested attribute");
|
||||
is(tileData.url, "http://example.com/landing/page.html", "original landing page");
|
||||
|
||||
// set pinned tile endTime into past and reload the page
|
||||
NewTabUtils.pinnedLinks._links[0].endTime = Date.now() - 1000;
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
// check that url is reset to base domain and thumbnail points to moz-page-thumb service
|
||||
is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/"}), true, "baseDomain url is pinned");
|
||||
tileData = getData(0);
|
||||
tileData = yield getData(0);
|
||||
is(tileData.type, "history", "type is history");
|
||||
is(tileData.title, "example.com", "title changed to baseDomain");
|
||||
is(tileData.thumbnail.indexOf("moz-page-thumb") != -1, true, "thumbnail contains moz-page-thumb");
|
||||
@ -86,4 +84,4 @@ function runTests() {
|
||||
is(tileData.url, "http://example.com/", "url points to baseDomian");
|
||||
|
||||
DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
|
||||
}
|
||||
});
|
||||
|
@ -18,34 +18,35 @@ gDirectorySource = "data:application/json," + JSON.stringify({
|
||||
}]
|
||||
});
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
|
||||
DirectoryLinksProvider.getFrecentSitesName = () => "";
|
||||
|
||||
function getData(cellNum) {
|
||||
let cell = getCell(cellNum);
|
||||
if (!cell.site)
|
||||
return null;
|
||||
let siteNode = cell.site.node;
|
||||
return {
|
||||
type: siteNode.getAttribute("type"),
|
||||
thumbnail: siteNode.querySelector(".newtab-thumbnail").style.backgroundImage,
|
||||
enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
|
||||
title: siteNode.querySelector(".newtab-title").textContent,
|
||||
suggested: siteNode.getAttribute("suggested"),
|
||||
url: siteNode.querySelector(".newtab-link").getAttribute("href"),
|
||||
};
|
||||
return performOnCell(cellNum, cell => {
|
||||
if (!cell.site)
|
||||
return null;
|
||||
let siteNode = cell.site.node;
|
||||
return {
|
||||
type: siteNode.getAttribute("type"),
|
||||
thumbnail: siteNode.querySelector(".newtab-thumbnail").style.backgroundImage,
|
||||
enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
|
||||
title: siteNode.querySelector(".newtab-title").textContent,
|
||||
suggested: siteNode.getAttribute("suggested"),
|
||||
url: siteNode.querySelector(".newtab-link").getAttribute("href"),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
// load another newtab since the first may not get suggested tile
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("http://example.com/hardlanding/page.html,0,1,2,3,4,5,6,7,8,9");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("http://example.com/hardlanding/page.html,0,1,2,3,4,5,6,7,8,9");
|
||||
// evaluate suggested tile
|
||||
let tileData = getData(0);
|
||||
let tileData = yield getData(0);
|
||||
is(tileData.type, "affiliate", "unpinned type");
|
||||
is(tileData.thumbnail, "url(\"data:image/png;base64,helloWORLD3\")", "unpinned thumbnail");
|
||||
is(tileData.enhanced, "url(\"data:image/png;base64,helloWORLD2\")", "unpinned enhanced");
|
||||
@ -56,31 +57,27 @@ function runTests() {
|
||||
is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/hardlanding/page.html"}), false, "suggested tile is not pinned");
|
||||
|
||||
// pin suggested tile
|
||||
whenPagesUpdated();
|
||||
let siteNode = getCell(0).node.querySelector(".newtab-site");
|
||||
let pinButton = siteNode.querySelector(".newtab-control-pin");
|
||||
EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
|
||||
// wait for whenPagesUpdated
|
||||
yield null;
|
||||
let updatedPromise = whenPagesUpdated();
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site > .newtab-control-pin", {}, gBrowser.selectedBrowser);
|
||||
yield updatedPromise;
|
||||
|
||||
// tile should be pinned and turned into history tile
|
||||
is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/hardlanding/page.html"}), true, "suggested tile is pinned");
|
||||
tileData = getData(0);
|
||||
tileData = yield getData(0);
|
||||
is(tileData.type, "history", "pinned type");
|
||||
is(tileData.suggested, null, "no suggested attribute");
|
||||
is(tileData.url, "http://example.com/hardlanding/page.html", "original landing page");
|
||||
|
||||
// click the pinned tile
|
||||
siteNode = getCell(0).node.querySelector(".newtab-site");
|
||||
EventUtils.synthesizeMouseAtCenter(siteNode, {}, getContentWindow());
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site", {}, gBrowser.selectedBrowser);
|
||||
// add new page twice to avoid using cached version
|
||||
yield addNewTabPageTab();
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
// check that type and suggested did not change
|
||||
tileData = getData(0);
|
||||
tileData = yield getData(0);
|
||||
is(tileData.type, "history", "pinned type");
|
||||
is(tileData.suggested, null, "no suggested attribute");
|
||||
|
||||
DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
|
||||
}
|
||||
});
|
||||
|
@ -25,59 +25,60 @@ gDirectorySource = "data:application/json," + JSON.stringify({
|
||||
});
|
||||
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
requestLongerTimeout(4);
|
||||
let origEnhanced = NewTabUtils.allPages.enhanced;
|
||||
let origCompareLinks = NewTabUtils.links.compareLinks;
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(PRELOAD_PREF);
|
||||
Services.prefs.clearUserPref(PREF_NEWTAB_ROWS);
|
||||
Services.prefs.clearUserPref(PREF_NEWTAB_COLUMNS);
|
||||
NewTabUtils.allPages.enhanced = origEnhanced;
|
||||
NewTabUtils.links.compareLinks = origCompareLinks;
|
||||
});
|
||||
|
||||
// turn off preload to ensure grid updates on every setLinks
|
||||
Services.prefs.setBoolPref(PRELOAD_PREF, false);
|
||||
yield pushPrefs([PRELOAD_PREF, false]);
|
||||
// set newtab to have three columns only
|
||||
Services.prefs.setIntPref(PREF_NEWTAB_COLUMNS, 3);
|
||||
Services.prefs.setIntPref(PREF_NEWTAB_ROWS, 5);
|
||||
yield pushPrefs([PREF_NEWTAB_COLUMNS, 3]);
|
||||
yield pushPrefs([PREF_NEWTAB_ROWS, 5]);
|
||||
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
yield customizeNewTabPage("enhanced"); // Toggle enhanced off
|
||||
|
||||
// Testing history tiles
|
||||
|
||||
// two rows of tiles should always fit on any screen
|
||||
yield setLinks("0,1,2,3,4,5");
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
// should do not see scrollbar since tiles fit into visible space
|
||||
checkGrid("0,1,2,3,4,5");
|
||||
ok(!hasScrollbar(), "no scrollbar");
|
||||
yield* checkGrid("0,1,2,3,4,5");
|
||||
let scrolling = yield hasScrollbar();
|
||||
ok(!scrolling, "no scrollbar");
|
||||
|
||||
// add enough tiles to cause extra two rows and observe scrollbar
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8,9");
|
||||
ok(hasScrollbar(), "document has scrollbar");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8,9");
|
||||
scrolling = yield hasScrollbar();
|
||||
ok(scrolling, "document has scrollbar");
|
||||
|
||||
// pin the last tile to make it stay at the bottom of the newtab
|
||||
pinCell(9);
|
||||
yield pinCell(9);
|
||||
// block first 6 tiles, which should not remove the scroll bar
|
||||
// since the last tile is pinned in the nineth position
|
||||
for (let i = 0; i < 6; i++) {
|
||||
yield blockCell(0);
|
||||
}
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("6,7,8,,,,,,,9p");
|
||||
ok(hasScrollbar(), "document has scrollbar when tile is pinned to the last row");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("6,7,8,,,,,,,9p");
|
||||
scrolling = yield hasScrollbar();
|
||||
ok(scrolling, "document has scrollbar when tile is pinned to the last row");
|
||||
|
||||
// unpin the site: this will move tile up and make scrollbar disappear
|
||||
yield unpinCell(9);
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("6,7,8,9");
|
||||
ok(!hasScrollbar(), "no scrollbar when bottom row tile is unpinned");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("6,7,8,9");
|
||||
scrolling = yield hasScrollbar();
|
||||
ok(!scrolling, "no scrollbar when bottom row tile is unpinned");
|
||||
|
||||
// reset everything to clean slate
|
||||
NewTabUtils.restore();
|
||||
@ -87,30 +88,35 @@ function runTests() {
|
||||
|
||||
// setup page with no history tiles to test directory only display
|
||||
yield setLinks([]);
|
||||
yield addNewTabPageTab();
|
||||
ok(!hasScrollbar(), "no scrollbar for directory tiles");
|
||||
yield* addNewTabPageTab();
|
||||
ok(!scrolling, "no scrollbar for directory tiles");
|
||||
|
||||
// introduce one history tile - it should occupy the last
|
||||
// available slot at the bottom of newtab and cause scrollbar
|
||||
yield setLinks("41");
|
||||
yield addNewTabPageTab();
|
||||
ok(hasScrollbar(), "adding low frecency history site causes scrollbar");
|
||||
yield* addNewTabPageTab();
|
||||
scrolling = yield hasScrollbar();
|
||||
ok(scrolling, "adding low frecency history site causes scrollbar");
|
||||
|
||||
// set PREF_NEWTAB_ROWS to 4, that should clip off the history tile
|
||||
// and remove scroll bar
|
||||
Services.prefs.setIntPref(PREF_NEWTAB_ROWS, 4);
|
||||
yield addNewTabPageTab();
|
||||
ok(!hasScrollbar(), "no scrollbar if history tiles falls past max rows");
|
||||
yield pushPrefs([PREF_NEWTAB_ROWS, 4]);
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
scrolling = yield hasScrollbar();
|
||||
ok(!scrolling, "no scrollbar if history tiles falls past max rows");
|
||||
|
||||
// restore max rows and watch scrollbar re-appear
|
||||
Services.prefs.setIntPref(PREF_NEWTAB_ROWS, 5);
|
||||
yield addNewTabPageTab();
|
||||
ok(hasScrollbar(), "scrollbar is back when max rows allow for bottom history tile");
|
||||
yield pushPrefs([PREF_NEWTAB_ROWS, 5]);
|
||||
yield* addNewTabPageTab();
|
||||
scrolling = yield hasScrollbar();
|
||||
ok(scrolling, "scrollbar is back when max rows allow for bottom history tile");
|
||||
|
||||
// block that history tile, and watch scrollbar disappear
|
||||
yield blockCell(14);
|
||||
yield addNewTabPageTab();
|
||||
ok(!hasScrollbar(), "no scrollbar after bottom history tiles is blocked");
|
||||
yield* addNewTabPageTab();
|
||||
scrolling = yield hasScrollbar();
|
||||
ok(!scrolling, "no scrollbar after bottom history tiles is blocked");
|
||||
|
||||
// Test well-populated user history - newtab has highly-frecent history sites
|
||||
// redefine compareLinks to always choose history tiles first
|
||||
@ -131,12 +137,14 @@ function runTests() {
|
||||
|
||||
// add a row of history tiles, directory tiles will be clipped off, hence no scrollbar
|
||||
yield setLinks("31,32,33");
|
||||
yield addNewTabPageTab();
|
||||
ok(!hasScrollbar(), "no scrollbar when directory tiles follow history tiles");
|
||||
yield* addNewTabPageTab();
|
||||
scrolling = yield hasScrollbar();
|
||||
ok(!scrolling, "no scrollbar when directory tiles follow history tiles");
|
||||
|
||||
// fill first four rows with history tiles and observer scrollbar
|
||||
yield setLinks("30,31,32,33,34,35,36,37,38,39");
|
||||
yield addNewTabPageTab();
|
||||
ok(hasScrollbar(), "scrollbar appears when history tiles need extra row");
|
||||
yield* addNewTabPageTab();
|
||||
scrolling = yield hasScrollbar();
|
||||
ok(scrolling, "scrollbar appears when history tiles need extra row");
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks([
|
||||
{url: "http://example7.com/", title: ""},
|
||||
@ -9,15 +9,20 @@ function runTests() {
|
||||
{url: "http://example9.com/", title: "http://example9.com/"}
|
||||
]);
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("7p,8p,9p,0,1,2,3,4,5");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("7p,8p,9p,0,1,2,3,4,5");
|
||||
|
||||
checkTooltip(0, "http://example7.com/", "1st tooltip is correct");
|
||||
checkTooltip(1, "title\nhttp://example8.com/", "2nd tooltip is correct");
|
||||
checkTooltip(2, "http://example9.com/", "3rd tooltip is correct");
|
||||
}
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
|
||||
function checkTooltip(aIndex, aExpected, aMessage) {
|
||||
let cell = content.gGrid.cells[aIndex];
|
||||
|
||||
let link = cell.node.querySelector(".newtab-link");
|
||||
is(link.getAttribute("title"), aExpected, aMessage);
|
||||
}
|
||||
|
||||
checkTooltip(0, "http://example7.com/", "1st tooltip is correct");
|
||||
checkTooltip(1, "title\nhttp://example8.com/", "2nd tooltip is correct");
|
||||
checkTooltip(2, "http://example9.com/", "3rd tooltip is correct");
|
||||
});
|
||||
});
|
||||
|
||||
function checkTooltip(aIndex, aExpected, aMessage) {
|
||||
let link = getCell(aIndex).node.querySelector(".newtab-link");
|
||||
is(link.getAttribute("title"), aExpected, aMessage);
|
||||
}
|
||||
|
@ -11,24 +11,20 @@ Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
|
||||
var {Sanitizer} = tmp;
|
||||
|
||||
add_task(function*() {
|
||||
add_task(function* () {
|
||||
yield promiseSanitizeHistory();
|
||||
yield promiseAddFakeVisits();
|
||||
yield addNewTabPageTabPromise();
|
||||
is(getCell(0).site.url, URL, "first site is our fake site");
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
whenPagesUpdated(() => {});
|
||||
let cellUrl = yield performOnCell(0, cell => { return cell.site.url; });
|
||||
is(cellUrl, URL, "first site is our fake site");
|
||||
|
||||
let updatedPromise = whenPagesUpdated();
|
||||
yield promiseSanitizeHistory();
|
||||
yield updatedPromise;
|
||||
|
||||
// Now wait until the grid is updated
|
||||
while (true) {
|
||||
if (!getCell(0).site) {
|
||||
break;
|
||||
}
|
||||
info("the fake site is still present");
|
||||
yield new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
ok(!getCell(0).site, "fake site is gone");
|
||||
let isGone = yield performOnCell(0, cell => { return cell.site == null; });
|
||||
ok(isGone, "fake site is gone");
|
||||
});
|
||||
|
||||
function promiseAddFakeVisits() {
|
||||
|
@ -1,19 +1,25 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
// create a new tab page and hide it.
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
let firstTab = gBrowser.selectedTab;
|
||||
|
||||
yield addNewTabPageTab();
|
||||
gBrowser.removeTab(firstTab);
|
||||
yield* addNewTabPageTab();
|
||||
yield BrowserTestUtils.removeTab(firstTab);
|
||||
|
||||
ok(NewTabUtils.allPages.enabled, "page is enabled");
|
||||
NewTabUtils.allPages.enabled = false;
|
||||
ok(getGrid().node.hasAttribute("page-disabled"), "page is disabled");
|
||||
|
||||
let disabled = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
|
||||
return content.gGrid.node.hasAttribute("page-disabled");
|
||||
});
|
||||
ok(disabled, "page is disabled");
|
||||
|
||||
NewTabUtils.allPages.enabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,30 +1,42 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGridLocked(false, "grid is unlocked");
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
let cell = getCell(0).node;
|
||||
let site = getCell(0).site.node;
|
||||
let link = site.querySelector(".newtab-link");
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
|
||||
let grid = content.gGrid;
|
||||
let cell = grid.cells[0];
|
||||
let site = cell.site.node;
|
||||
let link = site.querySelector(".newtab-link");
|
||||
|
||||
sendDragEvent("dragstart", link);
|
||||
checkGridLocked(true, "grid is now locked");
|
||||
function checkGridLocked(aLocked, aMessage) {
|
||||
is(grid.node.hasAttribute("locked"), aLocked, aMessage);
|
||||
}
|
||||
|
||||
sendDragEvent("dragend", link);
|
||||
checkGridLocked(false, "grid isn't locked anymore");
|
||||
function sendDragEvent(aEventType, aTarget) {
|
||||
let dataTransfer = new content.DataTransfer(aEventType, false);
|
||||
let event = content.document.createEvent("DragEvents");
|
||||
event.initDragEvent(aEventType, true, true, content, 0, 0, 0, 0, 0,
|
||||
false, false, false, false, 0, null, dataTransfer);
|
||||
aTarget.dispatchEvent(event);
|
||||
}
|
||||
|
||||
sendDragEvent("dragstart", cell);
|
||||
checkGridLocked(false, "grid isn't locked - dragstart was ignored");
|
||||
checkGridLocked(false, "grid is unlocked");
|
||||
|
||||
sendDragEvent("dragstart", site);
|
||||
checkGridLocked(false, "grid isn't locked - dragstart was ignored");
|
||||
}
|
||||
sendDragEvent("dragstart", link);
|
||||
checkGridLocked(true, "grid is now locked");
|
||||
|
||||
function checkGridLocked(aLocked, aMessage) {
|
||||
is(getGrid().node.hasAttribute("locked"), aLocked, aMessage);
|
||||
}
|
||||
sendDragEvent("dragend", link);
|
||||
checkGridLocked(false, "grid isn't locked anymore");
|
||||
|
||||
sendDragEvent("dragstart", cell.node);
|
||||
checkGridLocked(false, "grid isn't locked - dragstart was ignored");
|
||||
|
||||
sendDragEvent("dragstart", site);
|
||||
checkGridLocked(false, "grid isn't locked - dragstart was ignored");
|
||||
});
|
||||
});
|
||||
|
@ -1,23 +1,35 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
let cell = getCell(0).node;
|
||||
function doDrop(data) {
|
||||
return ContentTask.spawn(gBrowser.selectedBrowser, { data: data }, function*(args) {
|
||||
let dataTransfer = new content.DataTransfer("dragstart", false);
|
||||
dataTransfer.mozSetDataAt("text/x-moz-url", args.data, 0);
|
||||
let event = content.document.createEvent("DragEvents");
|
||||
event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
|
||||
false, false, false, false, 0, null, dataTransfer);
|
||||
|
||||
sendDragEvent("drop", cell, "http://example99.com/\nblank");
|
||||
let target = content.gGrid.cells[0].node;
|
||||
target.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
|
||||
yield doDrop("http://example99.com/\nblank");
|
||||
is(NewTabUtils.pinnedLinks.links[0].url, "http://example99.com/",
|
||||
"first cell is pinned and contains the dropped site");
|
||||
|
||||
yield whenPagesUpdated();
|
||||
checkGrid("99p,0,1,2,3,4,5,6,7");
|
||||
yield* checkGrid("99p,0,1,2,3,4,5,6,7");
|
||||
|
||||
sendDragEvent("drop", cell, "");
|
||||
yield doDrop("");
|
||||
is(NewTabUtils.pinnedLinks.links[0].url, "http://example99.com/",
|
||||
"first cell is still pinned with the site we dropped before");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,27 +1,34 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
|
||||
content.addEventListener("error", function () {
|
||||
sendAsyncMessage("test:newtab-error", {});
|
||||
});
|
||||
});
|
||||
|
||||
let receivedError = false;
|
||||
let block = getContentDocument().querySelector(".newtab-control-block");
|
||||
|
||||
function onError() {
|
||||
let mm = gBrowser.selectedBrowser.messageManager;
|
||||
mm.addMessageListener("test:newtab-error", function onResponse(message) {
|
||||
mm.removeMessageListener("test:newtab-error", onResponse);
|
||||
ok(false, "Error event happened");
|
||||
receivedError = true;
|
||||
});
|
||||
|
||||
let pagesUpdatedPromise = whenPagesUpdated();
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-control-block", {}, gBrowser.selectedBrowser);
|
||||
}
|
||||
|
||||
let cw = getContentWindow();
|
||||
cw.addEventListener("error", onError);
|
||||
yield pagesUpdatedPromise;
|
||||
|
||||
for (let i = 0; i < 3; i++)
|
||||
EventUtils.synthesizeMouseAtCenter(block, {}, cw);
|
||||
|
||||
yield whenPagesUpdated();
|
||||
ok(!receivedError, "we got here without any errors");
|
||||
cw.removeEventListener("error", onError);
|
||||
}
|
||||
});
|
||||
|
@ -1,21 +1,21 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
yield simulateExternalDrop(1);
|
||||
checkGrid("0,99p,1,2,3,4,5,6,7");
|
||||
yield* simulateExternalDrop(1);
|
||||
yield* checkGrid("0,99p,1,2,3,4,5,6,7");
|
||||
|
||||
yield blockCell(1);
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
yield simulateExternalDrop(1);
|
||||
checkGrid("0,99p,1,2,3,4,5,6,7");
|
||||
yield* simulateExternalDrop(1);
|
||||
yield* checkGrid("0,99p,1,2,3,4,5,6,7");
|
||||
|
||||
// Simulate a restart and force the next about:newtab
|
||||
// instance to read its data from the storage again.
|
||||
@ -24,9 +24,9 @@ function runTests() {
|
||||
// Update all open pages, e.g. preloaded ones.
|
||||
NewTabUtils.allPages.update();
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,99p,1,2,3,4,5,6,7");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,99p,1,2,3,4,5,6,7");
|
||||
|
||||
yield blockCell(1);
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
}
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
});
|
||||
|
@ -4,7 +4,14 @@
|
||||
const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
|
||||
const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
|
||||
|
||||
function runTests() {
|
||||
function getCellsCount()
|
||||
{
|
||||
return ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
|
||||
return content.gGrid.cells.length;
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* () {
|
||||
let testValues = [
|
||||
{row: 0, column: 0},
|
||||
{row: -1, column: -1},
|
||||
@ -20,38 +27,30 @@ function runTests() {
|
||||
// Values before setting new pref values (15 is the default value -> 5 x 3)
|
||||
let previousValues = [15, 1, 1, 1, 1, 8];
|
||||
|
||||
let existingTab, existingTabGridLength, newTab, newTabGridLength;
|
||||
yield addNewTabPageTab();
|
||||
existingTab = gBrowser.selectedTab;
|
||||
yield* addNewTabPageTab();
|
||||
let existingTab = gBrowser.selectedTab;
|
||||
|
||||
for (let i = 0; i < expectedValues.length; i++) {
|
||||
gBrowser.selectedTab = existingTab;
|
||||
existingTabGridLength = getGrid().cells.length;
|
||||
let existingTabGridLength = yield getCellsCount();
|
||||
is(existingTabGridLength, previousValues[i],
|
||||
"Grid length of existing page before update is correctly.");
|
||||
|
||||
Services.prefs.setIntPref(PREF_NEWTAB_ROWS, testValues[i].row);
|
||||
Services.prefs.setIntPref(PREF_NEWTAB_COLUMNS, testValues[i].column);
|
||||
yield pushPrefs([PREF_NEWTAB_ROWS, testValues[i].row]);
|
||||
yield pushPrefs([PREF_NEWTAB_COLUMNS, testValues[i].column]);
|
||||
|
||||
existingTabGridLength = getGrid().cells.length;
|
||||
existingTabGridLength = yield getCellsCount();
|
||||
is(existingTabGridLength, expectedValues[i],
|
||||
"Existing page grid is updated correctly.");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
newTab = gBrowser.selectedTab;
|
||||
newTabGridLength = getGrid().cells.length;
|
||||
yield* addNewTabPageTab();
|
||||
let newTab = gBrowser.selectedTab;
|
||||
let newTabGridLength = yield getCellsCount();
|
||||
is(newTabGridLength, expectedValues[i],
|
||||
"New page grid is updated correctly.");
|
||||
|
||||
gBrowser.removeTab(newTab);
|
||||
|
||||
// Wait until the original tab is visible again.
|
||||
let doc = existingTab.linkedBrowser.contentDocument;
|
||||
yield waitForCondition(() => !doc.hidden).then(TestRunner.next);
|
||||
yield BrowserTestUtils.removeTab(newTab);
|
||||
}
|
||||
|
||||
gBrowser.removeTab(existingTab);
|
||||
});
|
||||
|
||||
Services.prefs.clearUserPref(PREF_NEWTAB_ROWS);
|
||||
Services.prefs.clearUserPref(PREF_NEWTAB_COLUMNS);
|
||||
}
|
||||
|
@ -1,27 +1,32 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const BAD_DRAG_DATA = "javascript:alert('h4ck0rz');\nbad stuff";
|
||||
const GOOD_DRAG_DATA = "http://example99.com/\nsite 99";
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
sendDropEvent(0, BAD_DRAG_DATA);
|
||||
sendDropEvent(1, GOOD_DRAG_DATA);
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
|
||||
const BAD_DRAG_DATA = "javascript:alert('h4ck0rz');\nbad stuff";
|
||||
const GOOD_DRAG_DATA = "http://example99.com/\nsite 99";
|
||||
|
||||
function sendDropEvent(aCellIndex, aDragData) {
|
||||
let dataTransfer = new content.DataTransfer("dragstart", false);
|
||||
dataTransfer.mozSetDataAt("text/x-moz-url", aDragData, 0);
|
||||
let event = content.document.createEvent("DragEvents");
|
||||
event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
|
||||
false, false, false, false, 0, null, dataTransfer);
|
||||
|
||||
let target = content.gGrid.cells[aCellIndex].node;
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
|
||||
sendDropEvent(0, BAD_DRAG_DATA);
|
||||
sendDropEvent(1, GOOD_DRAG_DATA);
|
||||
});
|
||||
|
||||
yield whenPagesUpdated();
|
||||
checkGrid("0,99p,1,2,3,4,5,6,7");
|
||||
}
|
||||
|
||||
function sendDropEvent(aCellIndex, aDragData) {
|
||||
let ifaceReq = getContentWindow().QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
let windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils);
|
||||
|
||||
let event = createDragEvent("drop", aDragData);
|
||||
windowUtils.dispatchDOMEventViaPresShell(getCell(aCellIndex).node, event, true);
|
||||
}
|
||||
yield* checkGrid("0,99p,1,2,3,4,5,6,7");
|
||||
});
|
||||
|
@ -5,20 +5,20 @@
|
||||
* This test makes sure that the changes made by unpinning
|
||||
* a site are actually written to NewTabUtils' storage.
|
||||
*/
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
// Second cell is pinned with page #99.
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",99");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,99p,1,2,3,4,5,6,7");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,99p,1,2,3,4,5,6,7");
|
||||
|
||||
// Unpin the second cell's site.
|
||||
yield unpinCell(1);
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
// Clear the pinned cache to force NewTabUtils to read the pref again.
|
||||
NewTabUtils.pinnedLinks.resetCache();
|
||||
NewTabUtils.allPages.update();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
}
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
});
|
||||
|
@ -1,26 +1,36 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
// set max rows to 1, to avoid scroll events by clicking middle button
|
||||
Services.prefs.setIntPref(PREF_NEWTAB_ROWS, 1);
|
||||
yield pushPrefs(["browser.newtabpage.rows", 1]);
|
||||
yield setLinks("-1");
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
// we need a second newtab to honor max rows
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
// Remember if the click handler was triggered
|
||||
let cell = getCell(0);
|
||||
let clicked = false;
|
||||
cell.site.onClick = e => {
|
||||
clicked = true;
|
||||
executeSoon(TestRunner.next);
|
||||
};
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {index: 0}, function* (args) {
|
||||
let {site} = content.wrappedJSObject.gGrid.cells[args.index];
|
||||
|
||||
let origOnClick = site.onClick;
|
||||
let clicked = false;
|
||||
site.onClick = e => {
|
||||
origOnClick.call(site, e);
|
||||
sendAsyncMessage("test:clicked-on-cell", {});
|
||||
};
|
||||
});
|
||||
|
||||
let mm = gBrowser.selectedBrowser.messageManager;
|
||||
let messagePromise = new Promise(resolve => {
|
||||
mm.addMessageListener("test:clicked-on-cell", function onResponse(message) {
|
||||
mm.removeMessageListener("test:clicked-on-cell", onResponse);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Send a middle-click and make sure it happened
|
||||
yield EventUtils.synthesizeMouseAtCenter(cell.node, {button: 1}, getContentWindow());
|
||||
ok(clicked, "middle click triggered click listener");
|
||||
Services.prefs.clearUserPref(PREF_NEWTAB_ROWS);
|
||||
}
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell",
|
||||
{button: 1}, gBrowser.selectedBrowser);
|
||||
yield messagePromise;
|
||||
ok(true, "middle click triggered click listener");
|
||||
});
|
||||
|
@ -1,11 +1,9 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const PRELOAD_PREF = "browser.newtab.preload";
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
// turn off preload to ensure that a newtab page loads
|
||||
Services.prefs.setBoolPref(PRELOAD_PREF, false);
|
||||
yield pushPrefs(["browser.newtab.preload", false]);
|
||||
|
||||
// add a test provider that waits for load
|
||||
let afterLoadProvider = {
|
||||
@ -17,25 +15,21 @@ function runTests() {
|
||||
NewTabUtils.links.addProvider(afterLoadProvider);
|
||||
|
||||
// wait until about:newtab loads before calling provider callback
|
||||
addNewTabPageTab();
|
||||
let browser = gWindow.gBrowser.selectedBrowser;
|
||||
yield browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
// afterLoadProvider.callback has to be called asynchronously to make grid
|
||||
// initilize after "load" event was handled
|
||||
executeSoon(() => afterLoadProvider.callback([]));
|
||||
}, true);
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab");
|
||||
|
||||
let {_cellMargin, _cellHeight, _cellWidth, node} = getGrid();
|
||||
isnot(_cellMargin, null, "grid has a computed cell margin");
|
||||
isnot(_cellHeight, null, "grid has a computed cell height");
|
||||
isnot(_cellWidth, null, "grid has a computed cell width");
|
||||
let {height, maxHeight, maxWidth} = node.style;
|
||||
isnot(height, "", "grid has a computed grid height");
|
||||
isnot(maxHeight, "", "grid has a computed grid max-height");
|
||||
isnot(maxWidth, "", "grid has a computed grid max-width");
|
||||
afterLoadProvider.callback([]);
|
||||
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
|
||||
let {_cellMargin, _cellHeight, _cellWidth, node} = content.gGrid;
|
||||
isnot(_cellMargin, null, "grid has a computed cell margin");
|
||||
isnot(_cellHeight, null, "grid has a computed cell height");
|
||||
isnot(_cellWidth, null, "grid has a computed cell width");
|
||||
let {height, maxHeight, maxWidth} = node.style;
|
||||
isnot(height, "", "grid has a computed grid height");
|
||||
isnot(maxHeight, "", "grid has a computed grid max-height");
|
||||
isnot(maxWidth, "", "grid has a computed grid max-width");
|
||||
});
|
||||
|
||||
// restore original state
|
||||
NewTabUtils.links.removeProvider(afterLoadProvider);
|
||||
Services.prefs.clearUserPref(PRELOAD_PREF);
|
||||
}
|
||||
});
|
||||
|
@ -1,32 +1,41 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
// set max rows to 1, to avoid scroll events by clicking middle button
|
||||
Services.prefs.setIntPref(PREF_NEWTAB_ROWS, 1);
|
||||
yield pushPrefs(["browser.newtabpage.rows", 1]);
|
||||
yield setLinks("0");
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
// we need a second newtab to honor max rows
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
// Remember if the click handler was triggered
|
||||
let {site} = getCell(0);
|
||||
let origOnClick = site.onClick;
|
||||
let clicked = false;
|
||||
site.onClick = e => {
|
||||
origOnClick.call(site, e);
|
||||
clicked = true;
|
||||
executeSoon(TestRunner.next);
|
||||
};
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {index: 0}, function* (args) {
|
||||
let {site} = content.wrappedJSObject.gGrid.cells[args.index];
|
||||
|
||||
let origOnClick = site.onClick;
|
||||
let clicked = false;
|
||||
site.onClick = e => {
|
||||
origOnClick.call(site, e);
|
||||
sendAsyncMessage("test:clicked-on-cell", {});
|
||||
};
|
||||
});
|
||||
|
||||
let mm = gBrowser.selectedBrowser.messageManager;
|
||||
let messagePromise = new Promise(resolve => {
|
||||
mm.addMessageListener("test:clicked-on-cell", function onResponse(message) {
|
||||
mm.removeMessageListener("test:clicked-on-cell", onResponse);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Send a middle-click and make sure it happened
|
||||
let block = getContentDocument().querySelector(".newtab-control-block");
|
||||
yield EventUtils.synthesizeMouseAtCenter(block, {button: 1}, getContentWindow());
|
||||
ok(clicked, "middle click triggered click listener");
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-control-block",
|
||||
{button: 1}, gBrowser.selectedBrowser);
|
||||
|
||||
yield messagePromise;
|
||||
ok(true, "middle click triggered click listener");
|
||||
|
||||
// Make sure the cell didn't actually get blocked
|
||||
checkGrid("0");
|
||||
Services.prefs.clearUserPref(PREF_NEWTAB_ROWS);
|
||||
}
|
||||
yield* checkGrid("0");
|
||||
});
|
||||
|
||||
|
@ -5,30 +5,45 @@
|
||||
* These tests make sure that the 'New Tab Page' feature can be disabled if the
|
||||
* decides not to use it.
|
||||
*/
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
// create a new tab page and hide it.
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
let gridNode = getGrid().node;
|
||||
let firstTab = yield* addNewTabPageTab();
|
||||
|
||||
ok(!gridNode.hasAttribute("page-disabled"), "page is not disabled");
|
||||
function isGridDisabled(browser = gBrowser.selectedBrowser)
|
||||
{
|
||||
return ContentTask.spawn(browser, {}, function*() {
|
||||
return content.gGrid.node.hasAttribute("page-disabled");
|
||||
});
|
||||
}
|
||||
|
||||
let isDisabled = yield isGridDisabled();
|
||||
ok(!isDisabled, "page is not disabled");
|
||||
|
||||
NewTabUtils.allPages.enabled = false;
|
||||
ok(gridNode.hasAttribute("page-disabled"), "page is disabled");
|
||||
|
||||
let oldGridNode = gridNode;
|
||||
isDisabled = yield isGridDisabled();
|
||||
ok(isDisabled, "page is disabled");
|
||||
|
||||
// create a second new tage page and make sure it's disabled. enable it
|
||||
// create a second new tab page and make sure it's disabled. enable it
|
||||
// again and check if the former page gets enabled as well.
|
||||
yield addNewTabPageTab();
|
||||
ok(gridNode.hasAttribute("page-disabled"), "page is disabled");
|
||||
yield* addNewTabPageTab();
|
||||
isDisabled = yield isGridDisabled(firstTab.linkedBrowser);
|
||||
ok(isDisabled, "page is disabled");
|
||||
|
||||
// check that no sites have been rendered
|
||||
is(0, getContentDocument().querySelectorAll(".site").length, "no sites have been rendered");
|
||||
let sitesLength = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
|
||||
return content.document.querySelectorAll(".site").length;
|
||||
});
|
||||
is(0, sitesLength, "no sites have been rendered");
|
||||
|
||||
NewTabUtils.allPages.enabled = true;
|
||||
ok(!gridNode.hasAttribute("page-disabled"), "page is not disabled");
|
||||
ok(!oldGridNode.hasAttribute("page-disabled"), "old page is not disabled");
|
||||
}
|
||||
|
||||
isDisabled = yield isGridDisabled();
|
||||
ok(!isDisabled, "page is not disabled");
|
||||
|
||||
isDisabled = yield isGridDisabled(firstTab.linkedBrowser);
|
||||
ok(!isDisabled, "old page is not disabled");
|
||||
});
|
||||
|
@ -7,69 +7,89 @@
|
||||
* of the drag-and-drop operation. If the grid is full and we're dragging
|
||||
* a new site into it another one gets pushed out.
|
||||
*/
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
requestLongerTimeout(2);
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
// test a simple drag-and-drop scenario
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
yield simulateDrop(0, 1);
|
||||
checkGrid("1,0p,2,3,4,5,6,7,8");
|
||||
yield doDragEvent(0, 1);
|
||||
yield* checkGrid("1,0p,2,3,4,5,6,7,8");
|
||||
|
||||
// drag a cell to its current cell and make sure it's not pinned afterwards
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
yield simulateDrop(0, 0);
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
yield doDragEvent(0, 0);
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
// ensure that pinned pages aren't moved if that's not necessary
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",1,2");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1p,2p,3,4,5,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1p,2p,3,4,5,6,7,8");
|
||||
|
||||
yield simulateDrop(0, 3);
|
||||
checkGrid("3,1p,2p,0p,4,5,6,7,8");
|
||||
yield doDragEvent(0, 3);
|
||||
yield* checkGrid("3,1p,2p,0p,4,5,6,7,8");
|
||||
|
||||
// pinned sites should always be moved around as blocks. if a pinned site is
|
||||
// moved around, neighboring pinned are affected as well
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("0,1");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0p,1p,2,3,4,5,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0p,1p,2,3,4,5,6,7,8");
|
||||
|
||||
yield simulateDrop(2, 0);
|
||||
checkGrid("2p,0p,1p,3,4,5,6,7,8");
|
||||
yield doDragEvent(2, 0);
|
||||
yield* checkGrid("2p,0p,1p,3,4,5,6,7,8");
|
||||
|
||||
// pinned sites should not be pushed out of the grid (unless there are only
|
||||
// pinned ones left on the grid)
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",,,,,,,7,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7p,8p");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7p,8p");
|
||||
|
||||
yield simulateDrop(2, 5);
|
||||
checkGrid("0,1,3,4,5,2p,6,7p,8p");
|
||||
yield doDragEvent(2, 5);
|
||||
yield* checkGrid("0,1,3,4,5,2p,6,7p,8p");
|
||||
|
||||
// make sure that pinned sites are re-positioned correctly
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("0,1,2,,,5");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0p,1p,2p,3,4,5p,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0p,1p,2p,3,4,5p,6,7,8");
|
||||
|
||||
yield simulateDrop(0, 4);
|
||||
checkGrid("3,1p,2p,4,0p,5p,6,7,8");
|
||||
yield doDragEvent(0, 4);
|
||||
yield* checkGrid("3,1p,2p,4,0p,5p,6,7,8");
|
||||
});
|
||||
|
||||
function doDragEvent(sourceIndex, dropIndex) {
|
||||
return ContentTask.spawn(gBrowser.selectedBrowser,
|
||||
{ sourceIndex: sourceIndex, dropIndex: dropIndex }, function*(args) {
|
||||
let dataTransfer = new content.DataTransfer("dragstart", false);
|
||||
let event = content.document.createEvent("DragEvents");
|
||||
event.initDragEvent("dragstart", true, true, content, 0, 0, 0, 0, 0,
|
||||
false, false, false, false, 0, null, dataTransfer);
|
||||
|
||||
let target = content.gGrid.cells[args.sourceIndex].site.node;
|
||||
target.dispatchEvent(event);
|
||||
|
||||
event = content.document.createEvent("DragEvents");
|
||||
event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
|
||||
false, false, false, false, 0, null, dataTransfer);
|
||||
|
||||
target = content.gGrid.cells[args.dropIndex].node;
|
||||
target.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
|
@ -13,52 +13,51 @@ const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
|
||||
* This is a continuation of browser_newtab_drag_drop.js
|
||||
* to decrease test run time, focusing on external sites.
|
||||
*/
|
||||
function runTests() {
|
||||
registerCleanupFunction(_ => Services.prefs.clearUserPref(PREF_NEWTAB_COLUMNS));
|
||||
yield addNewTabPageTab();
|
||||
add_task(function* () {
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
// drag a new site onto the very first cell
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",,,,,,,7,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7p,8p");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7p,8p");
|
||||
|
||||
yield simulateExternalDrop(0);
|
||||
checkGrid("99p,0,1,2,3,4,5,7p,8p");
|
||||
yield* simulateExternalDrop(0);
|
||||
yield* checkGrid("99p,0,1,2,3,4,5,7p,8p");
|
||||
|
||||
// drag a new site onto the grid and make sure that pinned cells don't get
|
||||
// pushed out
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",,,,,,,7,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7p,8p");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7p,8p");
|
||||
|
||||
// force the grid to be small enough that a pinned cell could be pushed out
|
||||
Services.prefs.setIntPref(PREF_NEWTAB_COLUMNS, 3);
|
||||
yield simulateExternalDrop(5);
|
||||
checkGrid("0,1,2,3,4,99p,5,7p,8p");
|
||||
yield pushPrefs([PREF_NEWTAB_COLUMNS, 3]);
|
||||
yield* simulateExternalDrop(5);
|
||||
yield* checkGrid("0,1,2,3,4,99p,5,7p,8p");
|
||||
|
||||
// drag a new site beneath a pinned cell and make sure the pinned cell is
|
||||
// not moved
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",,,,,,,,8");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1,2,3,4,5,6,7,8p");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8p");
|
||||
|
||||
yield simulateExternalDrop(5);
|
||||
checkGrid("0,1,2,3,4,99p,5,6,8p");
|
||||
yield* simulateExternalDrop(5);
|
||||
yield* checkGrid("0,1,2,3,4,99p,5,6,8p");
|
||||
|
||||
// drag a new site onto a block of pinned sites and make sure they're shifted
|
||||
// around accordingly
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("0,1,2,,,,,,");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0p,1p,2p");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0p,1p,2p");
|
||||
|
||||
yield simulateExternalDrop(1);
|
||||
checkGrid("0p,99p,1p,2p,3,4,5,6,7");
|
||||
}
|
||||
yield* simulateExternalDrop(1);
|
||||
yield* checkGrid("0p,99p,1p,2p,3,4,5,6,7");
|
||||
});
|
||||
|
@ -5,20 +5,37 @@
|
||||
* These tests ensure that the drop preview correctly arranges sites when
|
||||
* dragging them around.
|
||||
*/
|
||||
function runTests() {
|
||||
yield addNewTabPageTab();
|
||||
add_task(function* () {
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
// the first three sites are pinned - make sure they're re-arranged correctly
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("0,1,2,,,5");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0p,1p,2p,3,4,5p,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0p,1p,2p,3,4,5p,6,7,8");
|
||||
|
||||
let cw = getContentWindow();
|
||||
cw.gDrag._draggedSite = getCell(0).site;
|
||||
let sites = cw.gDropPreview.rearrange(getCell(4));
|
||||
cw.gDrag._draggedSite = null;
|
||||
let foundSites = yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function*() {
|
||||
let cells = content.gGrid.cells;
|
||||
content.gDrag._draggedSite = cells[0].site;
|
||||
let sites = content.gDropPreview.rearrange(cells[4]);
|
||||
content.gDrag._draggedSite = null;
|
||||
|
||||
sites = sites.slice(0, 9);
|
||||
return sites.map(function (aSite) {
|
||||
if (!aSite)
|
||||
return "";
|
||||
|
||||
let pinned = aSite.isPinned();
|
||||
if (pinned != aSite.node.hasAttribute("pinned")) {
|
||||
ok(false, "invalid state (site.isPinned() != site[pinned])");
|
||||
}
|
||||
|
||||
return aSite.url.replace(/^http:\/\/example(\d+)\.com\/$/, "$1") + (pinned ? "p" : "");
|
||||
});
|
||||
});
|
||||
|
||||
let expectedSites = "3,1p,2p,4,0p,5p,6,7,8"
|
||||
is(foundSites, expectedSites, "grid status = " + expectedSites);
|
||||
});
|
||||
|
||||
checkGrid("3,1p,2p,4,0p,5p,6,7,8", sites);
|
||||
}
|
||||
|
@ -30,87 +30,91 @@ gDirectorySource = "data:application/json," + JSON.stringify({
|
||||
"suggested": [suggestedLink]
|
||||
});
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
let origEnhanced = NewTabUtils.allPages.enhanced;
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(PRELOAD_PREF);
|
||||
NewTabUtils.allPages.enhanced = origEnhanced;
|
||||
});
|
||||
|
||||
Services.prefs.setBoolPref(PRELOAD_PREF, false);
|
||||
yield pushPrefs([PRELOAD_PREF, false]);
|
||||
|
||||
function getData(cellNum) {
|
||||
let cell = getCell(cellNum);
|
||||
if (!cell.site)
|
||||
return null;
|
||||
let siteNode = cell.site.node;
|
||||
return {
|
||||
type: siteNode.getAttribute("type"),
|
||||
enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
|
||||
title: siteNode.querySelector(".newtab-title").textContent,
|
||||
suggested: siteNode.querySelector(".newtab-suggested").innerHTML
|
||||
};
|
||||
return performOnCell(cellNum, cell => {
|
||||
if (!cell.site)
|
||||
return null;
|
||||
let siteNode = cell.site.node;
|
||||
return {
|
||||
type: siteNode.getAttribute("type"),
|
||||
enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
|
||||
title: siteNode.querySelector(".newtab-title").textContent,
|
||||
suggested: siteNode.querySelector(".newtab-suggested").innerHTML
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Make the page have a directory link, enhanced link, and history link
|
||||
yield setLinks("-1");
|
||||
|
||||
// Test with enhanced = false
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
yield customizeNewTabPage("classic");
|
||||
yield customizeNewTabPage("enhanced"); // Toggle enhanced off
|
||||
let {type, enhanced, title, suggested} = getData(0);
|
||||
let {type, enhanced, title, suggested} = yield getData(0);
|
||||
isnot(type, "enhanced", "history link is not enhanced");
|
||||
is(enhanced, "", "history link has no enhanced image");
|
||||
is(title, "example.com");
|
||||
is(suggested, "", "There is no suggested explanation");
|
||||
|
||||
is(getData(1), null, "there is only one link and it's a history link");
|
||||
let data = yield getData(1);
|
||||
is(data, null, "there is only one link and it's a history link");
|
||||
|
||||
// Test with enhanced = true
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
yield customizeNewTabPage("enhanced"); // Toggle enhanced on
|
||||
({type, enhanced, title, suggested} = getData(0));
|
||||
({type, enhanced, title, suggested} = yield getData(0));
|
||||
is(type, "organic", "directory link is organic");
|
||||
isnot(enhanced, "", "directory link has enhanced image");
|
||||
is(title, "title1");
|
||||
is(suggested, "", "There is no suggested explanation");
|
||||
|
||||
({type, enhanced, title, suggested} = getData(1));
|
||||
({type, enhanced, title, suggested} = yield getData(1));
|
||||
is(type, "enhanced", "history link is enhanced");
|
||||
isnot(enhanced, "", "history link has enhanced image");
|
||||
is(title, "title");
|
||||
is(suggested, "", "There is no suggested explanation");
|
||||
|
||||
is(getData(2), null, "there are only 2 links, directory and enhanced history");
|
||||
data = yield getData(2);
|
||||
is(data, null, "there are only 2 links, directory and enhanced history");
|
||||
|
||||
// Test with a pinned link
|
||||
setPinnedLinks("-1");
|
||||
yield addNewTabPageTab();
|
||||
({type, enhanced, title, suggested} = getData(0));
|
||||
yield* addNewTabPageTab();
|
||||
({type, enhanced, title, suggested} = yield getData(0));
|
||||
is(type, "enhanced", "pinned history link is enhanced");
|
||||
isnot(enhanced, "", "pinned history link has enhanced image");
|
||||
is(title, "title");
|
||||
is(suggested, "", "There is no suggested explanation");
|
||||
|
||||
({type, enhanced, title, suggested} = getData(1));
|
||||
({type, enhanced, title, suggested} = yield getData(1));
|
||||
is(type, "organic", "directory link is organic");
|
||||
isnot(enhanced, "", "directory link has enhanced image");
|
||||
is(title, "title1");
|
||||
is(suggested, "", "There is no suggested explanation");
|
||||
|
||||
is(getData(2), null, "directory link pushed out by pinned history link");
|
||||
data = yield getData(2);
|
||||
is(data, null, "directory link pushed out by pinned history link");
|
||||
|
||||
// Test pinned link with enhanced = false
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
yield customizeNewTabPage("enhanced"); // Toggle enhanced off
|
||||
({type, enhanced, title, suggested} = getData(0));
|
||||
({type, enhanced, title, suggested} = yield getData(0));
|
||||
isnot(type, "enhanced", "history link is not enhanced");
|
||||
is(enhanced, "", "history link has no enhanced image");
|
||||
is(title, "example.com");
|
||||
is(suggested, "", "There is no suggested explanation");
|
||||
|
||||
is(getData(1), null, "directory link still pushed out by pinned history link");
|
||||
data = yield getData(1);
|
||||
is(data, null, "directory link still pushed out by pinned history link");
|
||||
|
||||
yield unpinCell(0);
|
||||
|
||||
@ -122,70 +126,76 @@ function runTests() {
|
||||
yield setLinks("-1,2,3,4,5,6,7,8");
|
||||
|
||||
// Test with enhanced = false
|
||||
yield addNewTabPageTab();
|
||||
({type, enhanced, title, suggested} = getData(0));
|
||||
yield* addNewTabPageTab();
|
||||
({type, enhanced, title, suggested} = yield getData(0));
|
||||
isnot(type, "enhanced", "history link is not enhanced");
|
||||
is(enhanced, "", "history link has no enhanced image");
|
||||
is(title, "example.com");
|
||||
is(suggested, "", "There is no suggested explanation");
|
||||
|
||||
isnot(getData(7), null, "there are 8 history links");
|
||||
is(getData(8), null, "there are 8 history links");
|
||||
data = yield getData(7);
|
||||
isnot(data, null, "there are 8 history links");
|
||||
data = yield getData(8);
|
||||
is(data, null, "there are 8 history links");
|
||||
|
||||
|
||||
// Test with enhanced = true
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
yield customizeNewTabPage("enhanced");
|
||||
|
||||
// Suggested link was not enhanced by directory link with same domain
|
||||
({type, enhanced, title, suggested} = getData(0));
|
||||
({type, enhanced, title, suggested} = yield getData(0));
|
||||
is(type, "affiliate", "suggested link is affiliate");
|
||||
is(enhanced, "", "suggested link has no enhanced image");
|
||||
is(title, "title2");
|
||||
ok(suggested.indexOf("Suggested for <strong> Technology </strong> visitors") > -1, "Suggested for 'Technology'");
|
||||
|
||||
// Enhanced history link shows up second
|
||||
({type, enhanced, title, suggested} = getData(1));
|
||||
({type, enhanced, title, suggested} = yield getData(1));
|
||||
is(type, "enhanced", "pinned history link is enhanced");
|
||||
isnot(enhanced, "", "pinned history link has enhanced image");
|
||||
is(title, "title");
|
||||
is(suggested, "", "There is no suggested explanation");
|
||||
|
||||
is(getData(9), null, "there is a suggested link followed by an enhanced history link and the remaining history links");
|
||||
data = yield getData(9);
|
||||
is(data, null, "there is a suggested link followed by an enhanced history link and the remaining history links");
|
||||
|
||||
|
||||
|
||||
// Test no override category/adgroup name.
|
||||
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
|
||||
"data:application/json," + JSON.stringify({"suggested": [suggestedLink]}));
|
||||
yield watchLinksChangeOnce().then(TestRunner.next);
|
||||
let linksChangedPromise = watchLinksChangeOnce();
|
||||
yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
|
||||
"data:application/json," + JSON.stringify({"suggested": [suggestedLink]})]);
|
||||
yield linksChangedPromise;
|
||||
|
||||
yield addNewTabPageTab();
|
||||
({type, enhanced, title, suggested} = getData(0));
|
||||
yield* addNewTabPageTab();
|
||||
({type, enhanced, title, suggested} = yield getData(0));
|
||||
Cu.reportError("SUGGEST " + suggested);
|
||||
ok(suggested.indexOf("Suggested for <strong> Technology </strong> visitors") > -1, "Suggested for 'Technology'");
|
||||
|
||||
|
||||
// Test server provided explanation string.
|
||||
suggestedLink.explanation = "Suggested for %1$S enthusiasts who visit sites like %2$S";
|
||||
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
|
||||
"data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
|
||||
yield watchLinksChangeOnce().then(TestRunner.next);
|
||||
linksChangedPromise = watchLinksChangeOnce();
|
||||
yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
|
||||
"data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
|
||||
yield linksChangedPromise;
|
||||
|
||||
yield addNewTabPageTab();
|
||||
({type, enhanced, title, suggested} = getData(0));
|
||||
yield* addNewTabPageTab();
|
||||
({type, enhanced, title, suggested} = yield getData(0));
|
||||
Cu.reportError("SUGGEST " + suggested);
|
||||
ok(suggested.indexOf("Suggested for <strong> Technology </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'Technology' enthusiasts");
|
||||
|
||||
|
||||
// Test server provided explanation string with category override.
|
||||
suggestedLink.adgroup_name = "webdev education";
|
||||
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
|
||||
"data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
|
||||
yield watchLinksChangeOnce().then(TestRunner.next);
|
||||
linksChangedPromise = watchLinksChangeOnce();
|
||||
yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
|
||||
"data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
|
||||
yield linksChangedPromise;
|
||||
|
||||
yield addNewTabPageTab();
|
||||
({type, enhanced, title, suggested} = getData(0));
|
||||
yield* addNewTabPageTab();
|
||||
({type, enhanced, title, suggested} = yield getData(0));
|
||||
Cu.reportError("SUGGEST " + suggested);
|
||||
ok(suggested.indexOf("Suggested for <strong> webdev education </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'webdev education' enthusiasts");
|
||||
|
||||
@ -194,24 +204,26 @@ function runTests() {
|
||||
// Test with xml entities in category name
|
||||
suggestedLink.url = "http://example1.com/3";
|
||||
suggestedLink.adgroup_name = ">angles< & \"quotes\'";
|
||||
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
|
||||
"data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
|
||||
yield watchLinksChangeOnce().then(TestRunner.next);
|
||||
linksChangedPromise = watchLinksChangeOnce();
|
||||
yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
|
||||
"data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
|
||||
yield linksChangedPromise;
|
||||
|
||||
yield addNewTabPageTab();
|
||||
({type, enhanced, title, suggested} = getData(0));
|
||||
yield* addNewTabPageTab();
|
||||
({type, enhanced, title, suggested} = yield getData(0));
|
||||
Cu.reportError("SUGGEST " + suggested);
|
||||
ok(suggested.indexOf("Suggested for <strong> >angles< & \"quotes\' </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'xml entities' enthusiasts");
|
||||
|
||||
|
||||
// Test with xml entities in explanation.
|
||||
suggestedLink.explanation = "Testing junk explanation &<>\"'";
|
||||
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE,
|
||||
"data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]})));
|
||||
yield watchLinksChangeOnce().then(TestRunner.next);
|
||||
linksChangedPromise = watchLinksChangeOnce();
|
||||
yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
|
||||
"data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
|
||||
yield linksChangedPromise;
|
||||
|
||||
yield addNewTabPageTab();
|
||||
({type, enhanced, title, suggested} = getData(0));
|
||||
yield* addNewTabPageTab();
|
||||
({type, enhanced, title, suggested} = yield getData(0));
|
||||
Cu.reportError("SUGGEST " + suggested);
|
||||
ok(suggested.indexOf("Testing junk explanation &<>\"'") > -1, "Junk test");
|
||||
}
|
||||
});
|
||||
|
@ -14,7 +14,6 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var browser = null;
|
||||
var aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
|
||||
.getService(Ci.nsIAboutNewTabService);
|
||||
|
||||
@ -22,7 +21,40 @@ const ABOUT_NEWTAB_URI = "about:newtab";
|
||||
const PREF_URI = "http://example.com/browser/browser/base/content/test/newtab/external_newtab.html";
|
||||
const DEFAULT_URI = aboutNewTabService.newTabURL;
|
||||
|
||||
function testPref() {
|
||||
function* loadNewPageAndVerify(browser, uri) {
|
||||
let browserLoadedPromise = BrowserTestUtils.waitForEvent(browser, "load", true);
|
||||
browser.loadURI("about:newtab");
|
||||
yield browserLoadedPromise;
|
||||
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, { uri: uri }, function* (args) {
|
||||
let uri = args.uri;
|
||||
|
||||
is(String(content.document.location), uri, "document.location should match " + uri);
|
||||
is(content.document.documentURI, uri, "document.documentURI should match " + uri);
|
||||
|
||||
if (uri == "about:newtab") {
|
||||
is(content.document.nodePrincipal,
|
||||
Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
"nodePrincipal should match systemPrincipal");
|
||||
}
|
||||
else {
|
||||
is(content.document.nodePrincipal.URI.spec, uri,
|
||||
"nodePrincipal should match " + uri);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
add_task(function* () {
|
||||
// test the default behavior
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
|
||||
ok(!aboutNewTabService.overridden,
|
||||
"sanity check: default URL for about:newtab should not be overriden");
|
||||
|
||||
yield* loadNewPageAndVerify(browser, ABOUT_NEWTAB_URI);
|
||||
|
||||
// set the pref for about:newtab to point to an exteranl resource
|
||||
aboutNewTabService.newTabURL = PREF_URI;
|
||||
ok(aboutNewTabService.overridden,
|
||||
@ -30,46 +62,13 @@ function testPref() {
|
||||
is(aboutNewTabService.newTabURL, PREF_URI,
|
||||
"sanity check: default URL for about:newtab should return the new URL");
|
||||
|
||||
browser.contentWindow.location = ABOUT_NEWTAB_URI;
|
||||
yield* loadNewPageAndVerify(browser, PREF_URI);
|
||||
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
is(content.document.location, PREF_URI, "document.location should match the external resource");
|
||||
is(content.document.documentURI, PREF_URI, "document.documentURI should match the external resource");
|
||||
is(content.document.nodePrincipal.URI.spec, PREF_URI, "nodePrincipal should match the external resource");
|
||||
// reset to about:newtab and perform sanity check
|
||||
aboutNewTabService.resetNewTabURL();
|
||||
is(aboutNewTabService.newTabURL, DEFAULT_URI,
|
||||
"sanity check: resetting the URL to about:newtab should return about:newtab");
|
||||
|
||||
// reset to about:newtab and perform sanity check
|
||||
aboutNewTabService.resetNewTabURL();
|
||||
is(aboutNewTabService.newTabURL, DEFAULT_URI,
|
||||
"sanity check: resetting the URL to about:newtab should return about:newtab");
|
||||
|
||||
// remove the tab and move on
|
||||
gBrowser.removeCurrentTab();
|
||||
TestRunner.next();
|
||||
}, true);
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
// test the default behavior
|
||||
yield addNewTabPageTab();
|
||||
browser = gWindow.gBrowser.selectedBrowser;
|
||||
|
||||
ok(!aboutNewTabService.overridden,
|
||||
"sanity check: default URL for about:newtab should not be overriden");
|
||||
browser.contentWindow.location = ABOUT_NEWTAB_URI;
|
||||
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
is(content.document.location, ABOUT_NEWTAB_URI, "document.location should match about:newtab");
|
||||
is(content.document.documentURI, ABOUT_NEWTAB_URI, "document.documentURI should match about:newtab");
|
||||
is(content.document.nodePrincipal,
|
||||
Services.scriptSecurityManager.getSystemPrincipal(),
|
||||
"nodePrincipal should match systemPrincipal");
|
||||
|
||||
// also test the pref
|
||||
testPref();
|
||||
}, true);
|
||||
|
||||
info("Waiting for about:newtab to load ...");
|
||||
yield true;
|
||||
}
|
||||
// remove the tab and move on
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
@ -2,11 +2,10 @@
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* These tests make sure that focusing the 'New Tage Page' works as expected.
|
||||
* These tests make sure that focusing the 'New Tab Page' works as expected.
|
||||
*/
|
||||
function runTests() {
|
||||
// Handle the OSX full keyboard access setting
|
||||
Services.prefs.setIntPref("accessibility.tabfocus", 7);
|
||||
add_task(function* () {
|
||||
yield pushPrefs(["accessibility.tabfocus", 7]);
|
||||
|
||||
// Focus count in new tab page.
|
||||
// 30 = 9 * 3 + 3 = 9 sites, each with link, pin and remove buttons; search
|
||||
@ -18,43 +17,32 @@ function runTests() {
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
gURLBar.focus();
|
||||
|
||||
// Count the focus with the enabled page.
|
||||
yield countFocus(FOCUS_COUNT);
|
||||
countFocus(FOCUS_COUNT);
|
||||
|
||||
// Disable page and count the focus with the disabled page.
|
||||
NewTabUtils.allPages.enabled = false;
|
||||
yield countFocus(1);
|
||||
|
||||
Services.prefs.clearUserPref("accessibility.tabfocus");
|
||||
countFocus(4);
|
||||
|
||||
NewTabUtils.allPages.enabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Focus the urlbar and count how many focus stops to return again to the urlbar.
|
||||
*/
|
||||
function countFocus(aExpectedCount) {
|
||||
let focusCount = 0;
|
||||
let contentDoc = getContentDocument();
|
||||
|
||||
window.addEventListener("focus", function onFocus() {
|
||||
let focusedElement = document.commandDispatcher.focusedElement;
|
||||
if (focusedElement && focusedElement.classList.contains("urlbar-input")) {
|
||||
window.removeEventListener("focus", onFocus, true);
|
||||
// account for a potential presence of a scroll bar
|
||||
ok(focusCount == aExpectedCount || focusCount == (aExpectedCount + 1),
|
||||
"Validate focus count in the new tab page.");
|
||||
executeSoon(TestRunner.next);
|
||||
} else {
|
||||
if (focusedElement && focusedElement.ownerDocument == contentDoc &&
|
||||
focusedElement instanceof HTMLElement) {
|
||||
focusCount++;
|
||||
}
|
||||
document.commandDispatcher.advanceFocus();
|
||||
do {
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
if (document.activeElement == gBrowser.selectedBrowser) {
|
||||
focusCount++;
|
||||
}
|
||||
}, true);
|
||||
} while (document.activeElement != gURLBar.inputField);
|
||||
|
||||
document.commandDispatcher.advanceFocus();
|
||||
ok(focusCount == aExpectedCount || focusCount == (aExpectedCount + 1),
|
||||
"Validate focus count in the new tab page.");
|
||||
}
|
||||
|
@ -8,55 +8,43 @@
|
||||
* mode.
|
||||
*/
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
// prepare the grid
|
||||
yield testOnWindow(undefined);
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
pinCell(0);
|
||||
checkGrid("0p,1,2,3,4,5,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield pinCell(0);
|
||||
yield* checkGrid("0p,1,2,3,4,5,6,7,8");
|
||||
|
||||
// open private window
|
||||
yield testOnWindow({private: true});
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0p,1,2,3,4,5,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0p,1,2,3,4,5,6,7,8");
|
||||
|
||||
// modify the grid while we're in pb mode
|
||||
yield blockCell(1);
|
||||
checkGrid("0p,2,3,4,5,6,7,8");
|
||||
yield* checkGrid("0p,2,3,4,5,6,7,8");
|
||||
|
||||
yield unpinCell(0);
|
||||
checkGrid("0,2,3,4,5,6,7,8");
|
||||
yield* checkGrid("0,2,3,4,5,6,7,8");
|
||||
|
||||
// open normal window
|
||||
yield testOnWindow(undefined);
|
||||
|
||||
// check that the grid is the same as before entering pb mode
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,2,3,4,5,6,7,8")
|
||||
}
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,2,3,4,5,6,7,8")
|
||||
});
|
||||
|
||||
var windowsToClose = [];
|
||||
function testOnWindow(options) {
|
||||
let newWindowPromise = BrowserTestUtils.waitForNewWindow();
|
||||
var win = OpenBrowserWindow(options);
|
||||
win.addEventListener("load", function onLoad() {
|
||||
win.removeEventListener("load", onLoad, false);
|
||||
windowsToClose.push(win);
|
||||
gWindow = win;
|
||||
whenDelayedStartupFinished(win, TestRunner.next);
|
||||
}, false);
|
||||
}
|
||||
|
||||
function whenDelayedStartupFinished(win, callback) {
|
||||
const topic = "browser-delayed-startup-finished";
|
||||
Services.obs.addObserver(function onStartup(subject) {
|
||||
if (win == subject) {
|
||||
Services.obs.removeObserver(onStartup, topic);
|
||||
executeSoon(callback);
|
||||
}
|
||||
}, topic, false);
|
||||
windowsToClose.push(win);
|
||||
gWindow = win;
|
||||
yield newWindowPromise;
|
||||
}
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
|
@ -9,32 +9,29 @@ const ADDITIONAL_WAIT_MS = 2000;
|
||||
/*
|
||||
* Ensure that loading about:newtab doesn't cause uninterruptible reflows.
|
||||
*/
|
||||
function runTests() {
|
||||
gBrowser.selectedTab = gBrowser.addTab("about:blank", {animate: false});
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
yield whenBrowserLoaded(browser);
|
||||
add_task(function* () {
|
||||
yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
|
||||
return gBrowser.selectedTab = gBrowser.addTab("about:blank", {animate: false});
|
||||
}, false);
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
let mm = browser.messageManager;
|
||||
mm.loadFrameScript(FRAME_SCRIPT, true);
|
||||
mm.addMessageListener("newtab-reflow", ({data: stack}) => {
|
||||
ok(false, `unexpected uninterruptible reflow ${stack}`);
|
||||
});
|
||||
|
||||
let browserLoadedPromise = BrowserTestUtils.waitForEvent(browser, "load", true);
|
||||
browser.loadURI("about:newtab");
|
||||
yield whenBrowserLoaded(browser);
|
||||
yield browserLoadedPromise;
|
||||
|
||||
// Wait some more to catch sync reflows after the page has loaded.
|
||||
yield setTimeout(TestRunner.next, ADDITIONAL_WAIT_MS);
|
||||
yield new Promise(resolve => {
|
||||
setTimeout(resolve, ADDITIONAL_WAIT_MS);
|
||||
});
|
||||
|
||||
// Clean up.
|
||||
gBrowser.removeCurrentTab({animate: false});
|
||||
|
||||
ok(true, "Each test requires at least one pass, fail or todo so here is a pass.");
|
||||
}
|
||||
|
||||
function whenBrowserLoaded(browser) {
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
executeSoon(TestRunner.next);
|
||||
}, true);
|
||||
}
|
||||
});
|
||||
|
@ -13,79 +13,71 @@ gDirectorySource = "data:application/json," + JSON.stringify({
|
||||
}]
|
||||
});
|
||||
|
||||
function runTests() {
|
||||
Services.prefs.setBoolPref(PRELOAD_PREF, false);
|
||||
add_task(function* () {
|
||||
yield pushPrefs([PRELOAD_PREF, false]);
|
||||
|
||||
let originalReportSitesAction = DirectoryLinksProvider.reportSitesAction;
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(PRELOAD_PREF);
|
||||
DirectoryLinksProvider.reportSitesAction = originalReportSitesAction;
|
||||
});
|
||||
|
||||
let expected = {};
|
||||
DirectoryLinksProvider.reportSitesAction = function(sites, action, siteIndex) {
|
||||
let {link} = sites[siteIndex];
|
||||
is(link.type, expected.type, "got expected type");
|
||||
is(action, expected.action, "got expected action");
|
||||
is(NewTabUtils.pinnedLinks.isPinned(link), expected.pinned, "got expected pinned");
|
||||
executeSoon(TestRunner.next);
|
||||
|
||||
function expectReportSitesAction() {
|
||||
return new Promise(resolve => {
|
||||
DirectoryLinksProvider.reportSitesAction = function(sites, action, siteIndex) {
|
||||
let {link} = sites[siteIndex];
|
||||
is(link.type, expected.type, "got expected type");
|
||||
is(action, expected.action, "got expected action");
|
||||
is(NewTabUtils.pinnedLinks.isPinned(link), expected.pinned, "got expected pinned");
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Test that the last visible site (index 1) is reported
|
||||
let reportSitesPromise = expectReportSitesAction();
|
||||
expected.type = "sponsored";
|
||||
expected.action = "view";
|
||||
expected.pinned = false;
|
||||
addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
yield reportSitesPromise;
|
||||
|
||||
// Wait for addNewTabPageTab and reportSitesAction
|
||||
yield null;
|
||||
yield null;
|
||||
|
||||
whenPagesUpdated();
|
||||
// Click the pin button on the link in the 1th tile spot
|
||||
let siteNode = getCell(1).node.querySelector(".newtab-site");
|
||||
let pinButton = siteNode.querySelector(".newtab-control-pin");
|
||||
expected.action = "pin";
|
||||
// tiles become "history" when pinned
|
||||
expected.type = "history";
|
||||
expected.pinned = true;
|
||||
EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
|
||||
let pagesUpdatedPromise = whenPagesUpdated();
|
||||
reportSitesPromise = expectReportSitesAction();
|
||||
|
||||
// Wait for whenPagesUpdated and reportSitesAction
|
||||
yield null;
|
||||
yield null;
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell + .newtab-cell .newtab-control-pin", {}, gBrowser.selectedBrowser);
|
||||
yield pagesUpdatedPromise;
|
||||
yield reportSitesPromise;
|
||||
|
||||
// Unpin that link
|
||||
expected.action = "unpin";
|
||||
expected.pinned = false;
|
||||
whenPagesUpdated();
|
||||
// need to reget siteNode for it could have been re-rendered after pin
|
||||
siteNode = getCell(1).node.querySelector(".newtab-site");
|
||||
pinButton = siteNode.querySelector(".newtab-control-pin");
|
||||
EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
|
||||
|
||||
// Wait for whenPagesUpdated and reportSitesAction
|
||||
yield null;
|
||||
yield null;
|
||||
pagesUpdatedPromise = whenPagesUpdated();
|
||||
reportSitesPromise = expectReportSitesAction();
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell + .newtab-cell .newtab-control-pin", {}, gBrowser.selectedBrowser);
|
||||
yield pagesUpdatedPromise;
|
||||
yield reportSitesPromise;
|
||||
|
||||
// Block the site in the 0th tile spot
|
||||
let blockedSite = getCell(0).node.querySelector(".newtab-site");
|
||||
let blockButton = blockedSite.querySelector(".newtab-control-block");
|
||||
expected.type = "organic";
|
||||
expected.action = "block";
|
||||
expected.pinned = false;
|
||||
whenPagesUpdated();
|
||||
EventUtils.synthesizeMouseAtCenter(blockButton, {}, getContentWindow());
|
||||
|
||||
// Wait for whenPagesUpdated and reportSitesAction
|
||||
yield null;
|
||||
yield null;
|
||||
pagesUpdatedPromise = whenPagesUpdated();
|
||||
reportSitesPromise = expectReportSitesAction();
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site .newtab-control-block", {}, gBrowser.selectedBrowser);
|
||||
yield pagesUpdatedPromise;
|
||||
yield reportSitesPromise;
|
||||
|
||||
// Click the 1th link now in the 0th tile spot
|
||||
expected.type = "history";
|
||||
expected.action = "click";
|
||||
EventUtils.synthesizeMouseAtCenter(siteNode, {}, getContentWindow());
|
||||
|
||||
// Wait for reportSitesAction
|
||||
yield null;
|
||||
}
|
||||
reportSitesPromise = expectReportSitesAction();
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site", {}, gBrowser.selectedBrowser);
|
||||
yield reportSitesPromise;
|
||||
});
|
||||
|
@ -44,182 +44,217 @@ const ENGINE_SUGGESTIONS = {
|
||||
numLogos: 0,
|
||||
};
|
||||
|
||||
const SERVICE_EVENT_NAME = "ContentSearchService";
|
||||
|
||||
const LOGO_1X_DPI_SIZE = [65, 26];
|
||||
const LOGO_2X_DPI_SIZE = [130, 52];
|
||||
|
||||
// The test has an expected search event queue and a search event listener.
|
||||
// Search events that are expected to happen are added to the queue, and the
|
||||
// listener consumes the queue and ensures that each event it receives is at
|
||||
// the head of the queue.
|
||||
//
|
||||
// Each item in the queue is an object { type, deferred }. type is the
|
||||
// expected search event type. deferred is a Promise.defer() value that is
|
||||
// resolved when the event is consumed.
|
||||
var gExpectedSearchEventQueue = [];
|
||||
let gExpectedSearchEventQueue = [];
|
||||
let gExpectedSearchEventResolver = null;
|
||||
|
||||
var gNewEngines = [];
|
||||
let gNewEngines = [];
|
||||
|
||||
function runTests() {
|
||||
runTaskifiedTests().then(TestRunner.next, TestRunner.next);
|
||||
yield;
|
||||
}
|
||||
|
||||
var runTaskifiedTests = Task.async(function* () {
|
||||
add_task(function* () {
|
||||
let oldCurrentEngine = Services.search.currentEngine;
|
||||
|
||||
yield addNewTabPageTabPromise();
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
// The tab is removed at the end of the test, so there's no need to remove
|
||||
// this listener at the end of the test.
|
||||
info("Adding search event listener");
|
||||
getContentWindow().addEventListener(SERVICE_EVENT_NAME, searchEventListener);
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
|
||||
const SERVICE_EVENT_NAME = "ContentSearchService";
|
||||
content.addEventListener(SERVICE_EVENT_NAME, function (event) {
|
||||
sendAsyncMessage("test:search-event", { eventType: event.detail.type });
|
||||
});
|
||||
});
|
||||
|
||||
let mm = gBrowser.selectedBrowser.messageManager;
|
||||
mm.addMessageListener("test:search-event", function (message) {
|
||||
let eventType = message.data.eventType;
|
||||
if (!gExpectedSearchEventResolver) {
|
||||
ok(false, "Got search event " + eventType + " with no promise assigned");
|
||||
}
|
||||
|
||||
let expectedEventType = gExpectedSearchEventQueue.shift();
|
||||
is(eventType, expectedEventType, "Got expected search event " + expectedEventType);
|
||||
if (!gExpectedSearchEventQueue.length) {
|
||||
gExpectedSearchEventResolver();
|
||||
gExpectedSearchEventResolver = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Add the engine without any logos and switch to it.
|
||||
let noLogoEngine = yield promiseNewSearchEngine(ENGINE_NO_LOGO);
|
||||
let searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
|
||||
Services.search.currentEngine = noLogoEngine;
|
||||
yield promiseSearchEvents(["CurrentEngine"]);
|
||||
yield checkCurrentEngine(ENGINE_NO_LOGO);
|
||||
yield searchEventsPromise;
|
||||
yield* checkCurrentEngine(ENGINE_NO_LOGO);
|
||||
|
||||
// Add the engine with favicon and switch to it.
|
||||
let faviconEngine = yield promiseNewSearchEngine(ENGINE_FAVICON);
|
||||
searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
|
||||
Services.search.currentEngine = faviconEngine;
|
||||
yield promiseSearchEvents(["CurrentEngine"]);
|
||||
yield checkCurrentEngine(ENGINE_FAVICON);
|
||||
yield searchEventsPromise;
|
||||
yield* checkCurrentEngine(ENGINE_FAVICON);
|
||||
|
||||
// Add the engine with a 1x-DPI logo and switch to it.
|
||||
let logo1xEngine = yield promiseNewSearchEngine(ENGINE_1X_LOGO);
|
||||
searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
|
||||
Services.search.currentEngine = logo1xEngine;
|
||||
yield promiseSearchEvents(["CurrentEngine"]);
|
||||
yield checkCurrentEngine(ENGINE_1X_LOGO);
|
||||
yield searchEventsPromise;
|
||||
yield* checkCurrentEngine(ENGINE_1X_LOGO);
|
||||
|
||||
// Add the engine with a 2x-DPI logo and switch to it.
|
||||
let logo2xEngine = yield promiseNewSearchEngine(ENGINE_2X_LOGO);
|
||||
searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
|
||||
Services.search.currentEngine = logo2xEngine;
|
||||
yield promiseSearchEvents(["CurrentEngine"]);
|
||||
yield checkCurrentEngine(ENGINE_2X_LOGO);
|
||||
yield searchEventsPromise;
|
||||
yield* checkCurrentEngine(ENGINE_2X_LOGO);
|
||||
|
||||
// Add the engine with 1x- and 2x-DPI logos and switch to it.
|
||||
let logo1x2xEngine = yield promiseNewSearchEngine(ENGINE_1X_2X_LOGO);
|
||||
searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
|
||||
Services.search.currentEngine = logo1x2xEngine;
|
||||
yield promiseSearchEvents(["CurrentEngine"]);
|
||||
yield checkCurrentEngine(ENGINE_1X_2X_LOGO);
|
||||
yield searchEventsPromise;
|
||||
yield* checkCurrentEngine(ENGINE_1X_2X_LOGO);
|
||||
|
||||
// Add the engine that provides search suggestions and switch to it.
|
||||
let suggestionEngine = yield promiseNewSearchEngine(ENGINE_SUGGESTIONS);
|
||||
searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
|
||||
Services.search.currentEngine = suggestionEngine;
|
||||
yield promiseSearchEvents(["CurrentEngine"]);
|
||||
yield checkCurrentEngine(ENGINE_SUGGESTIONS);
|
||||
yield searchEventsPromise;
|
||||
yield* checkCurrentEngine(ENGINE_SUGGESTIONS);
|
||||
|
||||
// Avoid intermittent failures.
|
||||
gSearch().remoteTimeout = 5000;
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
|
||||
content.gSearch._contentSearchController.remoteTimeout = 5000;
|
||||
});
|
||||
|
||||
// Type an X in the search input. This is only a smoke test. See
|
||||
// browser_searchSuggestionUI.js for comprehensive content search suggestion
|
||||
// UI tests.
|
||||
let input = $("text");
|
||||
input.focus();
|
||||
EventUtils.synthesizeKey("x", {});
|
||||
let suggestionsOpenPromise = new Promise(resolve => {
|
||||
mm.addMessageListener("test:newtab-suggestions-open", function onResponse(message) {
|
||||
mm.removeMessageListener("test:newtab-suggestions-open", onResponse);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
|
||||
let table = content.document.getElementById("searchSuggestionTable");
|
||||
|
||||
let input = content.document.getElementById("newtab-search-text");
|
||||
input.focus();
|
||||
|
||||
info("Waiting for suggestions table to open");
|
||||
let observer = new content.MutationObserver(() => {
|
||||
if (input.getAttribute("aria-expanded") == "true") {
|
||||
observer.disconnect();
|
||||
ok(!table.hidden, "Search suggestion table unhidden");
|
||||
sendAsyncMessage("test:newtab-suggestions-open", {});
|
||||
}
|
||||
});
|
||||
observer.observe(input, {
|
||||
attributes: true,
|
||||
attributeFilter: ["aria-expanded"],
|
||||
});
|
||||
});
|
||||
|
||||
let suggestionsPromise = promiseSearchEvents(["Suggestions"]);
|
||||
|
||||
EventUtils.synthesizeKey("x", {});
|
||||
|
||||
// Wait for the search suggestions to become visible and for the Suggestions
|
||||
// message.
|
||||
let suggestionsUnhiddenDefer = Promise.defer();
|
||||
let table = getContentDocument().getElementById("searchSuggestionTable");
|
||||
info("Waiting for suggestions table to open");
|
||||
let observer = new MutationObserver(() => {
|
||||
if (input.getAttribute("aria-expanded") == "true") {
|
||||
observer.disconnect();
|
||||
ok(!table.hidden, "Search suggestion table unhidden");
|
||||
suggestionsUnhiddenDefer.resolve();
|
||||
}
|
||||
});
|
||||
observer.observe(input, {
|
||||
attributes: true,
|
||||
attributeFilter: ["aria-expanded"],
|
||||
});
|
||||
yield suggestionsUnhiddenDefer.promise;
|
||||
yield suggestionsOpenPromise;
|
||||
yield suggestionsPromise;
|
||||
|
||||
// Empty the search input, causing the suggestions to be hidden.
|
||||
EventUtils.synthesizeKey("a", { accelKey: true });
|
||||
EventUtils.synthesizeKey("VK_DELETE", {});
|
||||
ok(table.hidden, "Search suggestion table hidden");
|
||||
|
||||
let tableHidden = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
|
||||
return content.document.getElementById("searchSuggestionTable").hidden;
|
||||
});
|
||||
ok(tableHidden, "Search suggestion table hidden");
|
||||
|
||||
// Remove the search bar from toolbar
|
||||
CustomizableUI.removeWidgetFromArea("search-container");
|
||||
// Focus a different element than the search input from the page.
|
||||
let btn = getContentDocument().getElementById("newtab-customize-button");
|
||||
yield promiseClick(btn);
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter("#newtab-customize-button", { }, gBrowser.selectedBrowser);
|
||||
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () {
|
||||
let input = content.document.getElementById("newtab-search-text");
|
||||
isnot(input, content.document.activeElement, "Search input should not be focused");
|
||||
});
|
||||
|
||||
isnot(input, getContentDocument().activeElement, "Search input should not be focused");
|
||||
// Test that Ctrl/Cmd + K will focus the input field from the page.
|
||||
let focusPromise = promiseSearchEvents(["FocusInput"]);
|
||||
EventUtils.synthesizeKey("k", { accelKey: true });
|
||||
yield promiseSearchEvents(["FocusInput"]);
|
||||
is(input, getContentDocument().activeElement, "Search input should be focused");
|
||||
yield focusPromise;
|
||||
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () {
|
||||
let input = content.document.getElementById("newtab-search-text");
|
||||
is(input, content.document.activeElement, "Search input should be focused");
|
||||
});
|
||||
|
||||
// Reset changes made to toolbar
|
||||
CustomizableUI.reset();
|
||||
|
||||
// Test that Ctrl/Cmd + K will focus the search bar from toolbar.
|
||||
let searchBar = gWindow.document.getElementById("searchbar");
|
||||
EventUtils.synthesizeKey("k", { accelKey: true });
|
||||
is(searchBar.textbox.inputField, gWindow.document.activeElement, "Toolbar's search bar should be focused");
|
||||
let searchBar = document.getElementById("searchbar");
|
||||
is(searchBar.textbox.inputField, document.activeElement, "Toolbar's search bar should be focused");
|
||||
|
||||
// Test that Ctrl/Cmd + K will focus the search bar from a new about:home page if
|
||||
// the newtab is disabled from `NewTabUtils.allPages.enabled`.
|
||||
yield addNewTabPageTabPromise();
|
||||
let tab = yield* addNewTabPageTab();
|
||||
// Remove the search bar from toolbar
|
||||
CustomizableUI.removeWidgetFromArea("search-container");
|
||||
NewTabUtils.allPages.enabled = false;
|
||||
EventUtils.synthesizeKey("k", { accelKey: true });
|
||||
let waitEvent = "AboutHomeLoadSnippetsCompleted";
|
||||
yield promiseTabLoadEvent(gWindow.gBrowser.selectedTab, "about:home", waitEvent);
|
||||
|
||||
is(getContentDocument().documentURI.toLowerCase(), "about:home", "New tab's uri should be about:home");
|
||||
let searchInput = getContentDocument().getElementById("searchText");
|
||||
is(searchInput, getContentDocument().activeElement, "Search input must be the selected element");
|
||||
|
||||
let aboutHomeLoaded = new Promise(resolve => {
|
||||
tab.linkedBrowser.addEventListener("AboutHomeLoadSnippetsCompleted", function loadListener(event) {
|
||||
tab.linkedBrowser.removeEventListener("AboutHomeLoadSnippetsCompleted", loadListener, true);
|
||||
resolve();
|
||||
}, true, true);
|
||||
});
|
||||
|
||||
tab.linkedBrowser.loadURI("about:home");
|
||||
yield aboutHomeLoaded;
|
||||
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () {
|
||||
is(content.document.documentURI.toLowerCase(), "about:home", "New tab's uri should be about:home");
|
||||
let searchInput = content.document.getElementById("searchText");
|
||||
is(searchInput, content.document.activeElement, "Search input must be the selected element");
|
||||
});
|
||||
|
||||
NewTabUtils.allPages.enabled = true;
|
||||
CustomizableUI.reset();
|
||||
gBrowser.removeCurrentTab();
|
||||
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
|
||||
// Done. Revert the current engine and remove the new engines.
|
||||
searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
|
||||
Services.search.currentEngine = oldCurrentEngine;
|
||||
yield promiseSearchEvents(["CurrentEngine"]);
|
||||
yield searchEventsPromise;
|
||||
|
||||
let events = Array(gNewEngines.length).fill("CurrentState", 0, gNewEngines.length);
|
||||
searchEventsPromise = promiseSearchEvents(events);
|
||||
|
||||
let events = [];
|
||||
for (let engine of gNewEngines) {
|
||||
Services.search.removeEngine(engine);
|
||||
events.push("CurrentState");
|
||||
}
|
||||
yield promiseSearchEvents(events);
|
||||
yield searchEventsPromise;
|
||||
});
|
||||
|
||||
function searchEventListener(event) {
|
||||
info("Got search event " + event.detail.type);
|
||||
let nonempty = gExpectedSearchEventQueue.length > 0;
|
||||
ok(nonempty, "Expected search event queue should be nonempty");
|
||||
if (nonempty) {
|
||||
let { type, deferred } = gExpectedSearchEventQueue.shift();
|
||||
is(event.detail.type, type, "Got expected search event " + type);
|
||||
if (event.detail.type == type) {
|
||||
deferred.resolve();
|
||||
} else {
|
||||
deferred.reject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function $(idSuffix) {
|
||||
return getContentDocument().getElementById("newtab-search-" + idSuffix);
|
||||
}
|
||||
|
||||
function promiseSearchEvents(events) {
|
||||
info("Expecting search events: " + events);
|
||||
events = events.map(e => ({ type: e, deferred: Promise.defer() }));
|
||||
gExpectedSearchEventQueue.push(...events);
|
||||
return Promise.all(events.map(e => e.deferred.promise));
|
||||
return new Promise(resolve => {
|
||||
gExpectedSearchEventQueue.push(...events);
|
||||
gExpectedSearchEventResolver = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
function promiseNewSearchEngine({name: basename, numLogos}) {
|
||||
@ -235,121 +270,35 @@ function promiseNewSearchEngine({name: basename, numLogos}) {
|
||||
let eventPromise = promiseSearchEvents(expectedSearchEvents);
|
||||
|
||||
// Wait for addEngine().
|
||||
let addDeferred = Promise.defer();
|
||||
let url = getRootDirectory(gTestPath) + basename;
|
||||
Services.search.addEngine(url, null, "", false, {
|
||||
onSuccess: function (engine) {
|
||||
info("Search engine added: " + basename);
|
||||
gNewEngines.push(engine);
|
||||
addDeferred.resolve(engine);
|
||||
},
|
||||
onError: function (errCode) {
|
||||
ok(false, "addEngine failed with error code " + errCode);
|
||||
addDeferred.reject();
|
||||
},
|
||||
let addEnginePromise = new Promise((resolve, reject) => {
|
||||
let url = getRootDirectory(gTestPath) + basename;
|
||||
Services.search.addEngine(url, null, "", false, {
|
||||
onSuccess: function (engine) {
|
||||
info("Search engine added: " + basename);
|
||||
gNewEngines.push(engine);
|
||||
resolve(engine);
|
||||
},
|
||||
onError: function (errCode) {
|
||||
ok(false, "addEngine failed with error code " + errCode);
|
||||
reject();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all([addDeferred.promise, eventPromise]).then(([newEngine, _]) => {
|
||||
return Promise.all([addEnginePromise, eventPromise]).then(([newEngine, _]) => {
|
||||
return newEngine;
|
||||
});
|
||||
}
|
||||
|
||||
function objectURLToBlob(url) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("get", url, true);
|
||||
xhr.responseType = "blob";
|
||||
xhr.overrideMimeType("image/png");
|
||||
xhr.onload = function(e) {
|
||||
if (this.status == 200) {
|
||||
return resolve(this.response);
|
||||
}
|
||||
reject("Failed to get logo, xhr returned status: " + this.status);
|
||||
};
|
||||
xhr.onerror = reject;
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
function blobToBase64(blob) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
resolve(reader.result);
|
||||
}
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
var checkCurrentEngine = Task.async(function* ({name: basename, logoPrefix1x, logoPrefix2x}) {
|
||||
function* checkCurrentEngine(engineInfo)
|
||||
{
|
||||
let engine = Services.search.currentEngine;
|
||||
ok(engine.name.includes(basename),
|
||||
ok(engine.name.includes(engineInfo.name),
|
||||
"Sanity check: current engine: engine.name=" + engine.name +
|
||||
" basename=" + basename);
|
||||
" basename=" + engineInfo.name);
|
||||
|
||||
// gSearch.currentEngineName
|
||||
is(gSearch().defaultEngine.name, engine.name,
|
||||
"currentEngineName: " + engine.name);
|
||||
});
|
||||
|
||||
function promiseClick(node) {
|
||||
let deferred = Promise.defer();
|
||||
let win = getContentWindow();
|
||||
SimpleTest.waitForFocus(() => {
|
||||
EventUtils.synthesizeMouseAtCenter(node, {}, win);
|
||||
deferred.resolve();
|
||||
}, win);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function logoImg() {
|
||||
return $("logo");
|
||||
}
|
||||
|
||||
function gSearch() {
|
||||
return getContentWindow().gSearch._contentSearchController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a load (or custom) event to finish in a given tab. If provided
|
||||
* load an uri into the tab.
|
||||
*
|
||||
* @param tab
|
||||
* The tab to load into.
|
||||
* @param [optional] url
|
||||
* The url to load, or the current url.
|
||||
* @param [optional] event
|
||||
* The load event type to wait for. Defaults to "load".
|
||||
* @return {Promise} resolved when the event is handled.
|
||||
* @resolves to the received event
|
||||
* @rejects if a valid load event is not received within a meaningful interval
|
||||
*/
|
||||
function promiseTabLoadEvent(tab, url, eventType="load") {
|
||||
let deferred = Promise.defer();
|
||||
info("Wait tab event: " + eventType);
|
||||
|
||||
function handle(event) {
|
||||
if (event.originalTarget != tab.linkedBrowser.contentDocument ||
|
||||
event.target.location.href == "about:blank" ||
|
||||
(url && event.target.location.href != url)) {
|
||||
info("Skipping spurious '" + eventType + "'' event" +
|
||||
" for " + event.target.location.href);
|
||||
return;
|
||||
}
|
||||
clearTimeout(timeout);
|
||||
tab.linkedBrowser.removeEventListener(eventType, handle, true);
|
||||
info("Tab event received: " + eventType);
|
||||
deferred.resolve(event);
|
||||
}
|
||||
|
||||
let timeout = setTimeout(() => {
|
||||
tab.linkedBrowser.removeEventListener(eventType, handle, true);
|
||||
deferred.reject(new Error("Timed out while waiting for a '" + eventType + "'' event"));
|
||||
}, 20000);
|
||||
|
||||
tab.linkedBrowser.addEventListener(eventType, handle, true, true);
|
||||
if (url)
|
||||
tab.linkedBrowser.loadURI(url);
|
||||
return deferred.promise;
|
||||
let engineName = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
|
||||
return content.gSearch._contentSearchController.defaultEngine.name;
|
||||
});
|
||||
is(engineName, engine.name, "currentEngineName: " + engine.name);
|
||||
}
|
||||
|
@ -1,36 +1,47 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
yield setLinks("0");
|
||||
yield addNewTabPageTab();
|
||||
yield* addNewTabPageTab();
|
||||
|
||||
let site = getCell(0).node.querySelector(".newtab-site");
|
||||
site.setAttribute("type", "sponsored");
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
|
||||
var EventUtils = {};
|
||||
EventUtils.window = {};
|
||||
EventUtils.parent = EventUtils.window;
|
||||
EventUtils._EU_Ci = Components.interfaces;
|
||||
|
||||
// test explain text appearing upon a click
|
||||
let sponsoredButton = site.querySelector(".newtab-sponsored");
|
||||
EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, getContentWindow());
|
||||
let explain = site.querySelector(".sponsored-explain");
|
||||
isnot(explain, null, "Sponsored explanation shown");
|
||||
ok(explain.querySelector("input").classList.contains("newtab-control-block"), "sponsored tiles show blocked image");
|
||||
ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute");
|
||||
Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
|
||||
|
||||
// test dismissing sponsored explain
|
||||
EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, getContentWindow());
|
||||
is(site.querySelector(".sponsored-explain"), null, "Sponsored explanation no longer shown");
|
||||
ok(!sponsoredButton.hasAttribute("active"), "Sponsored button does not have active attribute");
|
||||
let cell = content.gGrid.cells[0];
|
||||
|
||||
// test with enhanced tile
|
||||
site.setAttribute("type", "enhanced");
|
||||
EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, getContentWindow());
|
||||
explain = site.querySelector(".sponsored-explain");
|
||||
isnot(explain, null, "Sponsored explanation shown");
|
||||
ok(explain.querySelector("input").classList.contains("newtab-customize"), "enhanced tiles show customize image");
|
||||
ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute");
|
||||
let site = cell.node.querySelector(".newtab-site");
|
||||
site.setAttribute("type", "sponsored");
|
||||
|
||||
// test dismissing enhanced explain
|
||||
EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, getContentWindow());
|
||||
is(site.querySelector(".sponsored-explain"), null, "Sponsored explanation no longer shown");
|
||||
ok(!sponsoredButton.hasAttribute("active"), "Sponsored button does not have active attribute");
|
||||
}
|
||||
// test explain text appearing upon a click
|
||||
let sponsoredButton = site.querySelector(".newtab-sponsored");
|
||||
EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
|
||||
let explain = site.querySelector(".sponsored-explain");
|
||||
isnot(explain, null, "Sponsored explanation shown");
|
||||
ok(explain.querySelector("input").classList.contains("newtab-control-block"), "sponsored tiles show blocked image");
|
||||
ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute");
|
||||
|
||||
// test dismissing sponsored explain
|
||||
EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
|
||||
is(site.querySelector(".sponsored-explain"), null, "Sponsored explanation no longer shown");
|
||||
ok(!sponsoredButton.hasAttribute("active"), "Sponsored button does not have active attribute");
|
||||
|
||||
// test with enhanced tile
|
||||
site.setAttribute("type", "enhanced");
|
||||
EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
|
||||
explain = site.querySelector(".sponsored-explain");
|
||||
isnot(explain, null, "Sponsored explanation shown");
|
||||
ok(explain.querySelector("input").classList.contains("newtab-customize"), "enhanced tiles show customize image");
|
||||
ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute");
|
||||
|
||||
// test dismissing enhanced explain
|
||||
EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
|
||||
is(site.querySelector(".sponsored-explain"), null, "Sponsored explanation no longer shown");
|
||||
ok(!sponsoredButton.hasAttribute("active"), "Sponsored button does not have active attribute");
|
||||
});
|
||||
});
|
||||
|
@ -4,46 +4,44 @@
|
||||
/*
|
||||
* These tests make sure that the undo dialog works as expected.
|
||||
*/
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
// remove unpinned sites and undo it
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks("5");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("5p,0,1,2,3,4,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("5p,0,1,2,3,4,6,7,8");
|
||||
|
||||
yield blockCell(4);
|
||||
yield blockCell(4);
|
||||
checkGrid("5p,0,1,2,6,7,8");
|
||||
yield* checkGrid("5p,0,1,2,6,7,8");
|
||||
|
||||
yield undo();
|
||||
checkGrid("5p,0,1,2,4,6,7,8");
|
||||
yield* undo();
|
||||
yield* checkGrid("5p,0,1,2,4,6,7,8");
|
||||
|
||||
// now remove a pinned site and undo it
|
||||
yield blockCell(0);
|
||||
checkGrid("0,1,2,4,6,7,8");
|
||||
yield* checkGrid("0,1,2,4,6,7,8");
|
||||
|
||||
yield undo();
|
||||
checkGrid("5p,0,1,2,4,6,7,8");
|
||||
yield* undo();
|
||||
yield* checkGrid("5p,0,1,2,4,6,7,8");
|
||||
|
||||
// remove a site and restore all
|
||||
yield blockCell(1);
|
||||
checkGrid("5p,1,2,4,6,7,8");
|
||||
yield* checkGrid("5p,1,2,4,6,7,8");
|
||||
|
||||
yield undoAll();
|
||||
checkGrid("5p,0,1,2,3,4,6,7,8");
|
||||
yield* undoAll();
|
||||
yield* checkGrid("5p,0,1,2,3,4,6,7,8");
|
||||
});
|
||||
|
||||
function* undo() {
|
||||
let updatedPromise = whenPagesUpdated();
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter("#newtab-undo-button", {}, gBrowser.selectedBrowser);
|
||||
yield updatedPromise;
|
||||
}
|
||||
|
||||
function undo() {
|
||||
let cw = getContentWindow();
|
||||
let target = cw.document.getElementById("newtab-undo-button");
|
||||
EventUtils.synthesizeMouseAtCenter(target, {}, cw);
|
||||
whenPagesUpdated();
|
||||
}
|
||||
|
||||
function undoAll() {
|
||||
let cw = getContentWindow();
|
||||
let target = cw.document.getElementById("newtab-undo-restore-button");
|
||||
EventUtils.synthesizeMouseAtCenter(target, {}, cw);
|
||||
whenPagesUpdated();
|
||||
function* undoAll() {
|
||||
let updatedPromise = whenPagesUpdated();
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter("#newtab-undo-restore-button", {}, gBrowser.selectedBrowser);
|
||||
yield updatedPromise;
|
||||
}
|
@ -5,17 +5,17 @@
|
||||
* These tests make sure that when a site gets unpinned it is either moved to
|
||||
* its actual place in the grid or removed in case it's not on the grid anymore.
|
||||
*/
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
// we have a pinned link that didn't change its position since it was pinned.
|
||||
// nothing should happend when we unpin it.
|
||||
// nothing should happen when we unpin it.
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",1");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,1p,2,3,4,5,6,7,8");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,1p,2,3,4,5,6,7,8");
|
||||
|
||||
yield unpinCell(1);
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
// we have a pinned link that is not anymore in the list of the most-visited
|
||||
// links. this should disappear, the remaining links adjust their positions
|
||||
@ -23,34 +23,34 @@ function runTests() {
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8");
|
||||
setPinnedLinks(",99");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("0,99p,1,2,3,4,5,6,7");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("0,99p,1,2,3,4,5,6,7");
|
||||
|
||||
yield unpinCell(1);
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
|
||||
// we have a pinned link that changed its position since it was pinned. it
|
||||
// should be moved to its new position after being unpinned.
|
||||
yield setLinks("0,1,2,3,4,5,6,7");
|
||||
setPinnedLinks(",1,,,,,,,0");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("2,1p,3,4,5,6,7,,0p");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("2,1p,3,4,5,6,7,,0p");
|
||||
|
||||
yield unpinCell(1);
|
||||
checkGrid("1,2,3,4,5,6,7,,0p");
|
||||
yield* checkGrid("1,2,3,4,5,6,7,,0p");
|
||||
|
||||
yield unpinCell(8);
|
||||
checkGrid("0,1,2,3,4,5,6,7,");
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,");
|
||||
|
||||
// we have pinned link that changed its position since it was pinned. the
|
||||
// link will disappear from the grid because it's now a much lower priority
|
||||
yield setLinks("0,1,2,3,4,5,6,7,8,9");
|
||||
setPinnedLinks("9");
|
||||
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("9p,0,1,2,3,4,5,6,7");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("9p,0,1,2,3,4,5,6,7");
|
||||
|
||||
yield unpinCell(0);
|
||||
checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
}
|
||||
yield* checkGrid("0,1,2,3,4,5,6,7,8");
|
||||
});
|
||||
|
@ -6,42 +6,41 @@
|
||||
/**
|
||||
* Checks that newtab is updated as its links change.
|
||||
*/
|
||||
function runTests() {
|
||||
add_task(function* () {
|
||||
// First, start with an empty page. setLinks will trigger a hidden page
|
||||
// update because it calls clearHistory. We need to wait for that update to
|
||||
// happen so that the next time we wait for a page update below, we catch the
|
||||
// right update and not the one triggered by setLinks.
|
||||
yield whenPagesUpdatedAnd(resolve => setLinks([], resolve));
|
||||
let updatedPromise = whenPagesUpdated();
|
||||
let setLinksPromise = setLinks([]);
|
||||
yield Promise.all([updatedPromise, setLinksPromise]);
|
||||
|
||||
// Strategy: Add some visits, open a new page, check the grid, repeat.
|
||||
yield fillHistoryAndWaitForPageUpdate([1]);
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("1,,,,,,,,");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("1,,,,,,,,");
|
||||
|
||||
yield fillHistoryAndWaitForPageUpdate([2]);
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("2,1,,,,,,,");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("2,1,,,,,,,");
|
||||
|
||||
yield fillHistoryAndWaitForPageUpdate([1]);
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("1,2,,,,,,,");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("1,2,,,,,,,");
|
||||
|
||||
yield fillHistoryAndWaitForPageUpdate([2, 3, 4]);
|
||||
yield addNewTabPageTab();
|
||||
checkGrid("2,1,3,4,,,,,");
|
||||
yield* addNewTabPageTab();
|
||||
yield* checkGrid("2,1,3,4,,,,,");
|
||||
|
||||
// Make sure these added links have the right type
|
||||
is(getCell(1).site.link.type, "history", "added link is history");
|
||||
}
|
||||
let type = yield performOnCell(1, cell => { return cell.site.link.type });
|
||||
is(type, "history", "added link is history");
|
||||
});
|
||||
|
||||
function fillHistoryAndWaitForPageUpdate(links) {
|
||||
return whenPagesUpdatedAnd(resolve => fillHistory(links.map(link), resolve));
|
||||
}
|
||||
|
||||
function whenPagesUpdatedAnd(promiseConstructor) {
|
||||
let promise1 = new Promise(whenPagesUpdated);
|
||||
let promise2 = new Promise(promiseConstructor);
|
||||
return Promise.all([promise1, promise2]).then(TestRunner.next);
|
||||
let updatedPromise = whenPagesUpdated;
|
||||
let fillHistoryPromise = fillHistory(links.map(link));
|
||||
return Promise.all([updatedPromise, fillHistoryPromise]);
|
||||
}
|
||||
|
||||
function link(id) {
|
||||
|
@ -7,22 +7,14 @@ const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
|
||||
Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
|
||||
|
||||
var tmp = {};
|
||||
Cu.import("resource://gre/modules/Promise.jsm", tmp);
|
||||
Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
|
||||
Cu.import("resource:///modules/DirectoryLinksProvider.jsm", tmp);
|
||||
Cu.import("resource://testing-common/PlacesTestUtils.jsm", tmp);
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://browser/content/sanitize.js", tmp);
|
||||
Cu.import("resource://gre/modules/Timer.jsm", tmp);
|
||||
var {Promise, NewTabUtils, Sanitizer, clearTimeout, setTimeout, DirectoryLinksProvider, PlacesTestUtils} = tmp;
|
||||
var {NewTabUtils, Sanitizer, DirectoryLinksProvider, PlacesTestUtils} = tmp;
|
||||
|
||||
var uri = Services.io.newURI("about:newtab", null, null);
|
||||
var principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
|
||||
|
||||
var isMac = ("nsILocalFileMac" in Ci);
|
||||
var isLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc);
|
||||
var isWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
|
||||
var gWindow = window;
|
||||
|
||||
// Default to dummy/empty directory links
|
||||
@ -98,153 +90,69 @@ registerCleanupFunction(function () {
|
||||
return watchLinksChangeOnce();
|
||||
});
|
||||
|
||||
function pushPrefs(...aPrefs) {
|
||||
return new Promise(resolve =>
|
||||
SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves promise when directory links are downloaded and written to disk
|
||||
*/
|
||||
function watchLinksChangeOnce() {
|
||||
let deferred = Promise.defer();
|
||||
let observer = {
|
||||
onManyLinksChanged: () => {
|
||||
DirectoryLinksProvider.removeObserver(observer);
|
||||
deferred.resolve();
|
||||
}
|
||||
};
|
||||
observer.onDownloadFail = observer.onManyLinksChanged;
|
||||
DirectoryLinksProvider.addObserver(observer);
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Provide the default test function to start our test runner.
|
||||
*
|
||||
* We need different code paths for tests that are still wired for
|
||||
* `TestRunner` and tests that have been ported to `add_task` as
|
||||
* we cannot have both in the same file.
|
||||
*/
|
||||
function isTestPortedToAddTask() {
|
||||
return gTestPath.endsWith("browser_newtab_bug722273.js");
|
||||
}
|
||||
if (!isTestPortedToAddTask()) {
|
||||
this.test = function() {
|
||||
waitForExplicitFinish();
|
||||
// start TestRunner.run() after directory links is downloaded and written to disk
|
||||
watchLinksChangeOnce().then(() => {
|
||||
// Wait for hidden page to update with the desired links
|
||||
whenPagesUpdated(() => TestRunner.run(), true);
|
||||
});
|
||||
|
||||
// Save the original directory source (which is set globally for tests)
|
||||
gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
|
||||
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource);
|
||||
}
|
||||
} else {
|
||||
add_task(function* setup() {
|
||||
registerCleanupFunction(function() {
|
||||
return new Promise(resolve => {
|
||||
function cleanupAndFinish() {
|
||||
PlacesTestUtils.clearHistory().then(() => {
|
||||
whenPagesUpdated(resolve);
|
||||
NewTabUtils.restore();
|
||||
});
|
||||
}
|
||||
|
||||
let callbacks = NewTabUtils.links._populateCallbacks;
|
||||
let numCallbacks = callbacks.length;
|
||||
|
||||
if (numCallbacks)
|
||||
callbacks.splice(0, numCallbacks, cleanupAndFinish);
|
||||
else
|
||||
cleanupAndFinish();
|
||||
});
|
||||
});
|
||||
|
||||
let promiseReady = Task.spawn(function*() {
|
||||
yield watchLinksChangeOnce();
|
||||
yield new Promise(resolve => whenPagesUpdated(resolve, true));
|
||||
});
|
||||
|
||||
// Save the original directory source (which is set globally for tests)
|
||||
gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
|
||||
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource);
|
||||
yield promiseReady;
|
||||
return new Promise(resolve => {
|
||||
let observer = {
|
||||
onManyLinksChanged: () => {
|
||||
DirectoryLinksProvider.removeObserver(observer);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
observer.onDownloadFail = observer.onManyLinksChanged;
|
||||
DirectoryLinksProvider.addObserver(observer);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The test runner that controls the execution flow of our tests.
|
||||
*/
|
||||
var TestRunner = {
|
||||
/**
|
||||
* Starts the test runner.
|
||||
*/
|
||||
run: function () {
|
||||
this._iter = runTests();
|
||||
this.next();
|
||||
},
|
||||
|
||||
/**
|
||||
* Runs the next available test or finishes if there's no test left.
|
||||
*/
|
||||
next: function () {
|
||||
try {
|
||||
TestRunner._iter.next();
|
||||
} catch (e if e instanceof StopIteration) {
|
||||
TestRunner.finish();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Finishes all tests and cleans up.
|
||||
*/
|
||||
finish: function () {
|
||||
function cleanupAndFinish() {
|
||||
PlacesTestUtils.clearHistory().then(() => {
|
||||
whenPagesUpdated(finish);
|
||||
NewTabUtils.restore();
|
||||
});
|
||||
}
|
||||
|
||||
let callbacks = NewTabUtils.links._populateCallbacks;
|
||||
let numCallbacks = callbacks.length;
|
||||
|
||||
if (numCallbacks)
|
||||
callbacks.splice(0, numCallbacks, cleanupAndFinish);
|
||||
else
|
||||
cleanupAndFinish();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the selected tab's content window.
|
||||
* @return The content window.
|
||||
*/
|
||||
function getContentWindow() {
|
||||
return gWindow.gBrowser.selectedBrowser.contentWindow;
|
||||
}
|
||||
add_task(function* setup() {
|
||||
registerCleanupFunction(function() {
|
||||
return new Promise(resolve => {
|
||||
function cleanupAndFinish() {
|
||||
PlacesTestUtils.clearHistory().then(() => {
|
||||
whenPagesUpdated().then(resolve);
|
||||
NewTabUtils.restore();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the selected tab's content document.
|
||||
* @return The content document.
|
||||
*/
|
||||
function getContentDocument() {
|
||||
return gWindow.gBrowser.selectedBrowser.contentDocument;
|
||||
}
|
||||
let callbacks = NewTabUtils.links._populateCallbacks;
|
||||
let numCallbacks = callbacks.length;
|
||||
|
||||
/**
|
||||
* Returns the newtab grid of the selected tab.
|
||||
* @return The newtab grid.
|
||||
*/
|
||||
function getGrid() {
|
||||
return getContentWindow().gGrid;
|
||||
}
|
||||
if (numCallbacks)
|
||||
callbacks.splice(0, numCallbacks, cleanupAndFinish);
|
||||
else
|
||||
cleanupAndFinish();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the cell at the given index of the selected tab's newtab grid.
|
||||
* @param aIndex The cell index.
|
||||
* @return The newtab cell.
|
||||
*/
|
||||
function getCell(aIndex) {
|
||||
return getGrid().cells[aIndex];
|
||||
let promiseReady = Task.spawn(function*() {
|
||||
yield watchLinksChangeOnce();
|
||||
yield whenPagesUpdated();
|
||||
});
|
||||
|
||||
// Save the original directory source (which is set globally for tests)
|
||||
gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
|
||||
Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource);
|
||||
yield promiseReady;
|
||||
});
|
||||
|
||||
/** Perform an action on a cell within the newtab page.
|
||||
* @param aIndex index of cell
|
||||
* @param aFn function to call in child process or tab.
|
||||
* @returns result of calling the function.
|
||||
*/
|
||||
function performOnCell(aIndex, aFn) {
|
||||
return ContentTask.spawn(gWindow.gBrowser.selectedBrowser,
|
||||
{ index: aIndex, fn: aFn.toString() }, function* (args) {
|
||||
let cell = content.gGrid.cells[args.index];
|
||||
return eval("(" + args.fn + ")(cell)");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -258,66 +166,70 @@ function getCell(aIndex) {
|
||||
* {url: "http://example2.com/", title: "site#2"},
|
||||
* {url: "http://example3.com/", title: "site#3"}]
|
||||
*/
|
||||
function setLinks(aLinks, aCallback = TestRunner.next) {
|
||||
let links = aLinks;
|
||||
function setLinks(aLinks) {
|
||||
return new Promise(resolve => {
|
||||
let links = aLinks;
|
||||
|
||||
if (typeof links == "string") {
|
||||
links = aLinks.split(/\s*,\s*/).map(function (id) {
|
||||
return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
|
||||
title: "site#" + id};
|
||||
});
|
||||
}
|
||||
if (typeof links == "string") {
|
||||
links = aLinks.split(/\s*,\s*/).map(function (id) {
|
||||
return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
|
||||
title: "site#" + id};
|
||||
});
|
||||
}
|
||||
|
||||
// Call populateCache() once to make sure that all link fetching that is
|
||||
// currently in progress has ended. We clear the history, fill it with the
|
||||
// given entries and call populateCache() now again to make sure the cache
|
||||
// has the desired contents.
|
||||
NewTabUtils.links.populateCache(function () {
|
||||
PlacesTestUtils.clearHistory().then(() => {
|
||||
fillHistory(links, function () {
|
||||
NewTabUtils.links.populateCache(function () {
|
||||
NewTabUtils.allPages.update();
|
||||
aCallback();
|
||||
}, true);
|
||||
// Call populateCache() once to make sure that all link fetching that is
|
||||
// currently in progress has ended. We clear the history, fill it with the
|
||||
// given entries and call populateCache() now again to make sure the cache
|
||||
// has the desired contents.
|
||||
NewTabUtils.links.populateCache(function () {
|
||||
PlacesTestUtils.clearHistory().then(() => {
|
||||
fillHistory(links).then(() => {
|
||||
NewTabUtils.links.populateCache(function () {
|
||||
NewTabUtils.allPages.update();
|
||||
resolve();
|
||||
}, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function fillHistory(aLinks, aCallback = TestRunner.next) {
|
||||
let numLinks = aLinks.length;
|
||||
if (!numLinks) {
|
||||
if (aCallback)
|
||||
executeSoon(aCallback);
|
||||
return;
|
||||
}
|
||||
function fillHistory(aLinks) {
|
||||
return new Promise(resolve => {
|
||||
let numLinks = aLinks.length;
|
||||
if (!numLinks) {
|
||||
executeSoon(resolve);
|
||||
return;
|
||||
}
|
||||
|
||||
let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK;
|
||||
let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK;
|
||||
|
||||
// Important: To avoid test failures due to clock jitter on Windows XP, call
|
||||
// Date.now() once here, not each time through the loop.
|
||||
let now = Date.now() * 1000;
|
||||
// Important: To avoid test failures due to clock jitter on Windows XP, call
|
||||
// Date.now() once here, not each time through the loop.
|
||||
let now = Date.now() * 1000;
|
||||
|
||||
for (let i = 0; i < aLinks.length; i++) {
|
||||
let link = aLinks[i];
|
||||
let place = {
|
||||
uri: makeURI(link.url),
|
||||
title: link.title,
|
||||
// Links are secondarily sorted by visit date descending, so decrease the
|
||||
// visit date as we progress through the array so that links appear in the
|
||||
// grid in the order they're present in the array.
|
||||
visits: [{visitDate: now - i, transitionType: transitionLink}]
|
||||
};
|
||||
for (let i = 0; i < aLinks.length; i++) {
|
||||
let link = aLinks[i];
|
||||
let place = {
|
||||
uri: makeURI(link.url),
|
||||
title: link.title,
|
||||
// Links are secondarily sorted by visit date descending, so decrease the
|
||||
// visit date as we progress through the array so that links appear in the
|
||||
// grid in the order they're present in the array.
|
||||
visits: [{visitDate: now - i, transitionType: transitionLink}]
|
||||
};
|
||||
|
||||
PlacesUtils.asyncHistory.updatePlaces(place, {
|
||||
handleError: () => ok(false, "couldn't add visit to history"),
|
||||
handleResult: function () {},
|
||||
handleCompletion: function () {
|
||||
if (--numLinks == 0 && aCallback)
|
||||
aCallback();
|
||||
}
|
||||
});
|
||||
}
|
||||
PlacesUtils.asyncHistory.updatePlaces(place, {
|
||||
handleError: () => ok(false, "couldn't add visit to history"),
|
||||
handleResult: function () {},
|
||||
handleCompletion: function () {
|
||||
if (--numLinks == 0) {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -355,8 +267,10 @@ function setPinnedLinks(aLinks) {
|
||||
* Restore the grid state.
|
||||
*/
|
||||
function restore() {
|
||||
whenPagesUpdated();
|
||||
NewTabUtils.restore();
|
||||
return new Promise(resolve => {
|
||||
whenPagesUpdated().then(resolve);
|
||||
NewTabUtils.restore();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -389,75 +303,60 @@ function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) {
|
||||
/**
|
||||
* Creates a new tab containing 'about:newtab'.
|
||||
*/
|
||||
function addNewTabPageTab() {
|
||||
addNewTabPageTabPromise().then(TestRunner.next);
|
||||
}
|
||||
|
||||
function addNewTabPageTabPromise() {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let tab = gWindow.gBrowser.selectedTab = gWindow.gBrowser.addTab("about:newtab");
|
||||
function* addNewTabPageTab() {
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gWindow.gBrowser, "about:newtab", false);
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
function whenNewTabLoaded() {
|
||||
// Wait for the document to become visible in case it was preloaded.
|
||||
yield waitForCondition(() => !browser.contentDocument.hidden)
|
||||
|
||||
yield new Promise(resolve => {
|
||||
if (NewTabUtils.allPages.enabled) {
|
||||
// Continue when the link cache has been populated.
|
||||
NewTabUtils.links.populateCache(function () {
|
||||
deferred.resolve(whenSearchInitDone());
|
||||
whenSearchInitDone().then(resolve);
|
||||
});
|
||||
} else {
|
||||
deferred.resolve();
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the new tab page to be loaded.
|
||||
waitForBrowserLoad(browser, function () {
|
||||
// Wait for the document to become visible in case it was preloaded.
|
||||
waitForCondition(() => !browser.contentDocument.hidden).then(whenNewTabLoaded);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function waitForBrowserLoad(browser, callback = TestRunner.next) {
|
||||
if (browser.contentDocument.readyState == "complete") {
|
||||
executeSoon(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
executeSoon(callback);
|
||||
}, true);
|
||||
return tab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the current grid arrangement with the given pattern.
|
||||
* @param the pattern (see below)
|
||||
* @param the array of sites to compare with (optional)
|
||||
*
|
||||
* Example: checkGrid("3p,2,,1p")
|
||||
* Result: We expect the first cell to contain the pinned site 'http://example3.com/'.
|
||||
* The second cell contains 'http://example2.com/'. The third cell is empty.
|
||||
* The fourth cell contains the pinned site 'http://example4.com/'.
|
||||
*/
|
||||
function checkGrid(aSitesPattern, aSites) {
|
||||
function* checkGrid(aSitesPattern) {
|
||||
let length = aSitesPattern.split(",").length;
|
||||
let sites = (aSites || getGrid().sites).slice(0, length);
|
||||
let current = sites.map(function (aSite) {
|
||||
if (!aSite)
|
||||
return "";
|
||||
|
||||
let pinned = aSite.isPinned();
|
||||
let hasPinnedAttr = aSite.node.hasAttribute("pinned");
|
||||
let foundPattern =
|
||||
yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser,
|
||||
{ length: length }, function* (args) {
|
||||
let grid = content.wrappedJSObject.gGrid;
|
||||
|
||||
if (pinned != hasPinnedAttr)
|
||||
ok(false, "invalid state (site.isPinned() != site[pinned])");
|
||||
let sites = grid.sites.slice(0, args.length);
|
||||
return sites.map(function (aSite) {
|
||||
if (!aSite)
|
||||
return "";
|
||||
|
||||
return aSite.url.replace(/^http:\/\/example(\d+)\.com\/$/, "$1") + (pinned ? "p" : "");
|
||||
let pinned = aSite.isPinned();
|
||||
let hasPinnedAttr = aSite.node.hasAttribute("pinned");
|
||||
|
||||
if (pinned != hasPinnedAttr)
|
||||
ok(false, "invalid state (site.isPinned() != site[pinned])");
|
||||
|
||||
return aSite.url.replace(/^http:\/\/example(\d+)\.com\/$/, "$1") + (pinned ? "p" : "");
|
||||
});
|
||||
});
|
||||
|
||||
is(current, aSitesPattern, "grid status = " + aSitesPattern);
|
||||
is(foundPattern, aSitesPattern, "grid status = " + aSitesPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -465,8 +364,12 @@ function checkGrid(aSitesPattern, aSites) {
|
||||
* @param aIndex The cell index.
|
||||
*/
|
||||
function blockCell(aIndex) {
|
||||
whenPagesUpdated();
|
||||
getCell(aIndex).site.block();
|
||||
return new Promise(resolve => {
|
||||
whenPagesUpdated().then(resolve);
|
||||
performOnCell(aIndex, cell => {
|
||||
return cell.site.block();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -474,8 +377,10 @@ function blockCell(aIndex) {
|
||||
* @param aIndex The cell index.
|
||||
* @param aPinIndex The index the defines where the site should be pinned.
|
||||
*/
|
||||
function pinCell(aIndex, aPinIndex) {
|
||||
getCell(aIndex).site.pin(aPinIndex);
|
||||
function pinCell(aIndex) {
|
||||
performOnCell(aIndex, cell => {
|
||||
cell.site.pin();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -483,22 +388,12 @@ function pinCell(aIndex, aPinIndex) {
|
||||
* @param aIndex The cell index.
|
||||
*/
|
||||
function unpinCell(aIndex) {
|
||||
whenPagesUpdated();
|
||||
getCell(aIndex).site.unpin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates a drag and drop operation.
|
||||
* @param aSourceIndex The cell index containing the dragged site.
|
||||
* @param aDestIndex The cell index of the drop target.
|
||||
*/
|
||||
function simulateDrop(aSourceIndex, aDestIndex) {
|
||||
let src = getCell(aSourceIndex).site.node;
|
||||
let dest = getCell(aDestIndex).node;
|
||||
|
||||
// Drop 'src' onto 'dest' and continue testing when all newtab
|
||||
// pages have been updated (i.e. the drop operation is completed).
|
||||
startAndCompleteDragOperation(src, dest, whenPagesUpdated);
|
||||
return new Promise(resolve => {
|
||||
whenPagesUpdated().then(resolve);
|
||||
performOnCell(aIndex, cell => {
|
||||
cell.site.unpin();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -507,254 +402,73 @@ function simulateDrop(aSourceIndex, aDestIndex) {
|
||||
* an external link onto the grid e.g. the text from the URL bar.
|
||||
* @param aDestIndex The cell index of the drop target.
|
||||
*/
|
||||
function simulateExternalDrop(aDestIndex) {
|
||||
let dest = getCell(aDestIndex).node;
|
||||
function* simulateExternalDrop(aDestIndex) {
|
||||
let pagesUpdatedPromise = whenPagesUpdated();
|
||||
|
||||
// Create an iframe that contains the external link we'll drag.
|
||||
createExternalDropIframe().then(iframe => {
|
||||
let link = iframe.contentDocument.getElementById("link");
|
||||
yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser, aDestIndex, function*(dropIndex) {
|
||||
return new Promise(resolve => {
|
||||
const url = "data:text/html;charset=utf-8," +
|
||||
"<a id='link' href='http://example99.com/'>link</a>";
|
||||
|
||||
let doc = content.document;
|
||||
let iframe = doc.createElement("iframe");
|
||||
|
||||
function iframeLoaded() {
|
||||
let link = iframe.contentDocument.getElementById("link");
|
||||
|
||||
let dataTransfer = new iframe.contentWindow.DataTransfer("dragstart", false);
|
||||
dataTransfer.mozSetDataAt("text/x-moz-url", "http://example99.com/", 0);
|
||||
|
||||
let event = content.document.createEvent("DragEvents");
|
||||
event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
|
||||
false, false, false, false, 0, null, dataTransfer);
|
||||
|
||||
let target = content.gGrid.cells[dropIndex].node;
|
||||
target.dispatchEvent(event);
|
||||
|
||||
// Drop 'link' onto 'dest'.
|
||||
startAndCompleteDragOperation(link, dest, () => {
|
||||
// Wait until the drop operation is complete
|
||||
// and all newtab pages have been updated.
|
||||
whenPagesUpdated(() => {
|
||||
// Clean up and remove the iframe.
|
||||
iframe.remove();
|
||||
// Continue testing.
|
||||
TestRunner.next();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts and complete a drag-and-drop operation.
|
||||
* @param aSource The node that is being dragged.
|
||||
* @param aDest The node we're dragging aSource onto.
|
||||
* @param aCallback The function that is called when we're done.
|
||||
*/
|
||||
function startAndCompleteDragOperation(aSource, aDest, aCallback) {
|
||||
// The implementation of this function varies by platform because each
|
||||
// platform has particular quirks that we need to deal with
|
||||
|
||||
if (isMac) {
|
||||
// On OS X once the drag starts, Cocoa manages the drag session and
|
||||
// gives us a limited amount of time to complete the drag operation. In
|
||||
// some cases as soon as the first mouse-move event is received (the one
|
||||
// that starts the drag session), Cocoa becomes blind to subsequent mouse
|
||||
// events and completes the drag session all by itself. Therefore it is
|
||||
// important that the first mouse-move we send is already positioned at
|
||||
// the destination.
|
||||
synthesizeNativeMouseLDown(aSource);
|
||||
synthesizeNativeMouseDrag(aDest);
|
||||
// In some tests, aSource and aDest are at the same position, so to ensure
|
||||
// a drag session is created (instead of it just turning into a click) we
|
||||
// move the mouse 10 pixels away and then back.
|
||||
synthesizeNativeMouseDrag(aDest, 10);
|
||||
synthesizeNativeMouseDrag(aDest);
|
||||
// Finally, release the drag and have it run the callback when done.
|
||||
synthesizeNativeMouseLUp(aDest).then(aCallback, Cu.reportError);
|
||||
} else if (isWindows) {
|
||||
// on Windows once the drag is initiated, Windows doesn't spin our
|
||||
// message loop at all, so with async event synthesization the async
|
||||
// messages never get processed while a drag is in progress. So if
|
||||
// we did a mousedown followed by a mousemove, we would never be able
|
||||
// to successfully dispatch the mouseup. Instead, we just skip the move
|
||||
// entirely, so and just generate the up at the destination. This way
|
||||
// Windows does the drag and also terminates it right away. Note that
|
||||
// this only works for tests where aSource and aDest are sufficiently
|
||||
// far to trigger a drag, otherwise it may just end up doing a click.
|
||||
synthesizeNativeMouseLDown(aSource);
|
||||
synthesizeNativeMouseLUp(aDest).then(aCallback, Cu.reportError);
|
||||
} else if (isLinux) {
|
||||
// Start by pressing the left mouse button.
|
||||
synthesizeNativeMouseLDown(aSource);
|
||||
|
||||
// Move the mouse in 5px steps until the drag operation starts.
|
||||
// Note that we need to do this with pauses in between otherwise the
|
||||
// synthesized events get coalesced somewhere in the guts of GTK. In order
|
||||
// to successfully initiate a drag session in the case where aSource and
|
||||
// aDest are at the same position, we synthesize a bunch of drags until
|
||||
// we know the drag session has started, and then move to the destination.
|
||||
let offset = 0;
|
||||
let interval = setInterval(() => {
|
||||
synthesizeNativeMouseDrag(aSource, offset += 5);
|
||||
}, 10);
|
||||
|
||||
// When the drag operation has started we'll move
|
||||
// the dragged element to its target position.
|
||||
aSource.addEventListener("dragstart", function onDragStart() {
|
||||
aSource.removeEventListener("dragstart", onDragStart);
|
||||
clearInterval(interval);
|
||||
|
||||
// Place the cursor above the drag target.
|
||||
synthesizeNativeMouseMove(aDest);
|
||||
});
|
||||
|
||||
// As soon as the dragged element hovers the target, we'll drop it.
|
||||
// Note that we need to actually wait for the dragenter event here, because
|
||||
// the mousemove synthesization is "more async" than the mouseup
|
||||
// synthesization - they use different gdk APIs. If we don't wait, the
|
||||
// up could get processed before the moves, dropping the item in the
|
||||
// wrong position.
|
||||
aDest.addEventListener("dragenter", function onDragEnter() {
|
||||
aDest.removeEventListener("dragenter", onDragEnter);
|
||||
|
||||
// Finish the drop operation.
|
||||
synthesizeNativeMouseLUp(aDest).then(aCallback, Cu.reportError);
|
||||
});
|
||||
} else {
|
||||
throw "Unsupported platform";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that creates a temporary iframe in the about:newtab
|
||||
* document. This will contain a link we can drag to the test the dropping
|
||||
* of links from external documents.
|
||||
*/
|
||||
function createExternalDropIframe() {
|
||||
const url = "data:text/html;charset=utf-8," +
|
||||
"<a id='link' href='http://example99.com/'>link</a>";
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let doc = getContentDocument();
|
||||
let iframe = doc.createElement("iframe");
|
||||
iframe.setAttribute("src", url);
|
||||
iframe.style.width = "50px";
|
||||
iframe.style.height = "50px";
|
||||
iframe.style.position = "absolute";
|
||||
iframe.style.zIndex = 50;
|
||||
|
||||
// the frame has to be attached to a visible element
|
||||
let margin = doc.getElementById("newtab-search-container");
|
||||
margin.appendChild(iframe);
|
||||
|
||||
iframe.addEventListener("load", function onLoad() {
|
||||
iframe.removeEventListener("load", onLoad);
|
||||
executeSoon(() => deferred.resolve(iframe));
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires a synthetic 'mousedown' event on the current about:newtab page.
|
||||
* @param aElement The element used to determine the cursor position.
|
||||
*/
|
||||
function synthesizeNativeMouseLDown(aElement) {
|
||||
if (isLinux) {
|
||||
let win = aElement.ownerDocument.defaultView;
|
||||
EventUtils.synthesizeMouseAtCenter(aElement, {type: "mousedown"}, win);
|
||||
} else {
|
||||
let msg = isWindows ? 2 : 1;
|
||||
synthesizeNativeMouseEvent(aElement, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires a synthetic 'mouseup' event on the current about:newtab page.
|
||||
* @param aElement The element used to determine the cursor position.
|
||||
*/
|
||||
function synthesizeNativeMouseLUp(aElement) {
|
||||
let msg = isWindows ? 4 : (isMac ? 2 : 7);
|
||||
return synthesizeNativeMouseEvent(aElement, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires a synthetic mouse drag event on the current about:newtab page.
|
||||
* @param aElement The element used to determine the cursor position.
|
||||
* @param aOffsetX The left offset that is added to the position.
|
||||
*/
|
||||
function synthesizeNativeMouseDrag(aElement, aOffsetX) {
|
||||
let msg = isMac ? 6 : 1;
|
||||
synthesizeNativeMouseEvent(aElement, msg, aOffsetX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires a synthetic 'mousemove' event on the current about:newtab page.
|
||||
* @param aElement The element used to determine the cursor position.
|
||||
*/
|
||||
function synthesizeNativeMouseMove(aElement) {
|
||||
let msg = isMac ? 5 : 1;
|
||||
synthesizeNativeMouseEvent(aElement, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires a synthetic mouse event on the current about:newtab page.
|
||||
* @param aElement The element used to determine the cursor position.
|
||||
* @param aOffsetX The left offset that is added to the position (optional).
|
||||
* @param aOffsetY The top offset that is added to the position (optional).
|
||||
*/
|
||||
function synthesizeNativeMouseEvent(aElement, aMsg, aOffsetX = 0, aOffsetY = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let rect = aElement.getBoundingClientRect();
|
||||
let win = aElement.ownerDocument.defaultView;
|
||||
let x = aOffsetX + win.mozInnerScreenX + rect.left + rect.width / 2;
|
||||
let y = aOffsetY + win.mozInnerScreenY + rect.top + rect.height / 2;
|
||||
|
||||
let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
|
||||
let scale = utils.screenPixelsPerCSSPixel;
|
||||
let observer = {
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
if (aTopic == "mouseevent") {
|
||||
resolve();
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
utils.sendNativeMouseEvent(x * scale, y * scale, aMsg, 0, null, observer);
|
||||
|
||||
iframe.addEventListener("load", function onLoad() {
|
||||
iframe.removeEventListener("load", onLoad);
|
||||
content.setTimeout(iframeLoaded, 0);
|
||||
});
|
||||
|
||||
iframe.setAttribute("src", url);
|
||||
iframe.style.width = "50px";
|
||||
iframe.style.height = "50px";
|
||||
iframe.style.position = "absolute";
|
||||
iframe.style.zIndex = 50;
|
||||
|
||||
// the frame has to be attached to a visible element
|
||||
let margin = doc.getElementById("newtab-search-container");
|
||||
margin.appendChild(iframe);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a custom drag event to a given DOM element.
|
||||
* @param aEventType The drag event's type.
|
||||
* @param aTarget The DOM element that the event is dispatched to.
|
||||
* @param aData The event's drag data (optional).
|
||||
*/
|
||||
function sendDragEvent(aEventType, aTarget, aData) {
|
||||
let event = createDragEvent(aEventType, aData);
|
||||
let ifaceReq = getContentWindow().QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
let windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils);
|
||||
windowUtils.dispatchDOMEventViaPresShell(aTarget, event, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a custom drag event.
|
||||
* @param aEventType The drag event's type.
|
||||
* @param aData The event's drag data (optional).
|
||||
* @return The drag event.
|
||||
*/
|
||||
function createDragEvent(aEventType, aData) {
|
||||
let dataTransfer = new (getContentWindow()).DataTransfer("dragstart", false);
|
||||
dataTransfer.mozSetDataAt("text/x-moz-url", aData, 0);
|
||||
let event = getContentDocument().createEvent("DragEvents");
|
||||
event.initDragEvent(aEventType, true, true, getContentWindow(), 0, 0, 0, 0, 0,
|
||||
false, false, false, false, 0, null, dataTransfer);
|
||||
|
||||
return event;
|
||||
yield pagesUpdatedPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes testing when all pages have been updated.
|
||||
* @param aCallback Called when done. If not specified, TestRunner.next is used.
|
||||
*/
|
||||
function whenPagesUpdated(aCallback = TestRunner.next) {
|
||||
let page = {
|
||||
observe: _ => _,
|
||||
function whenPagesUpdated() {
|
||||
return new Promise(resolve => {
|
||||
let page = {
|
||||
observe: _ => _,
|
||||
|
||||
update() {
|
||||
NewTabUtils.allPages.unregister(this);
|
||||
executeSoon(aCallback);
|
||||
}
|
||||
};
|
||||
update() {
|
||||
NewTabUtils.allPages.unregister(this);
|
||||
executeSoon(resolve);
|
||||
}
|
||||
};
|
||||
|
||||
NewTabUtils.allPages.register(page);
|
||||
registerCleanupFunction(function () {
|
||||
NewTabUtils.allPages.unregister(page);
|
||||
NewTabUtils.allPages.register(page);
|
||||
registerCleanupFunction(function () {
|
||||
NewTabUtils.allPages.unregister(page);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -762,27 +476,32 @@ function whenPagesUpdated(aCallback = TestRunner.next) {
|
||||
* Waits for the response to the page's initial search state request.
|
||||
*/
|
||||
function whenSearchInitDone() {
|
||||
let deferred = Promise.defer();
|
||||
let searchController = getContentWindow().gSearch._contentSearchController;
|
||||
if (searchController.defaultEngine) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
let eventName = "ContentSearchService";
|
||||
getContentWindow().addEventListener(eventName, function onEvent(event) {
|
||||
if (event.detail.type == "State") {
|
||||
getContentWindow().removeEventListener(eventName, onEvent);
|
||||
// Wait for the search controller to receive the event, then resolve.
|
||||
let resolver = function() {
|
||||
return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function*() {
|
||||
return new Promise(resolve => {
|
||||
if (content.gSearch) {
|
||||
let searchController = content.gSearch._contentSearchController;
|
||||
if (searchController.defaultEngine) {
|
||||
deferred.resolve();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
executeSoon(resolver);
|
||||
}
|
||||
executeSoon(resolver);
|
||||
}
|
||||
|
||||
let eventName = "ContentSearchService";
|
||||
content.addEventListener(eventName, function onEvent(event) {
|
||||
if (event.detail.type == "State") {
|
||||
content.removeEventListener(eventName, onEvent);
|
||||
let resolver = function() {
|
||||
// Wait for the search controller to receive the event, then resolve.
|
||||
if (content.gSearch._contentSearchController.defaultEngine) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
}
|
||||
content.setTimeout(resolver, 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -792,7 +511,7 @@ function whenSearchInitDone() {
|
||||
* Can be any of("blank"|"classic"|"enhanced")
|
||||
*/
|
||||
function customizeNewTabPage(aTheme) {
|
||||
let promise = ContentTask.spawn(gBrowser.selectedBrowser, aTheme, function*(aTheme) {
|
||||
return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, aTheme, function*(aTheme) {
|
||||
|
||||
let document = content.document;
|
||||
let panel = document.getElementById("newtab-customize-panel");
|
||||
@ -822,14 +541,14 @@ function customizeNewTabPage(aTheme) {
|
||||
customizeButton.click();
|
||||
yield closed;
|
||||
});
|
||||
|
||||
promise.then(TestRunner.next);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports presence of a scrollbar
|
||||
*/
|
||||
function hasScrollbar() {
|
||||
let docElement = getContentDocument().documentElement;
|
||||
return docElement.scrollHeight > docElement.clientHeight;
|
||||
return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function* () {
|
||||
let docElement = content.document.documentElement;
|
||||
return docElement.scrollHeight > docElement.clientHeight;
|
||||
});
|
||||
}
|
||||
|
@ -710,7 +710,7 @@ var MessageQueue = {
|
||||
},
|
||||
|
||||
observe(subject, topic, data) {
|
||||
if (topic == TIMEOUT_DISABLED_PREF) {
|
||||
if (topic == "nsPref:changed" && data == TIMEOUT_DISABLED_PREF) {
|
||||
this.timeoutDisabled =
|
||||
Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
|
||||
}
|
||||
|
@ -1272,7 +1272,7 @@ void DiagnosticsMatcher::RefCountedInsideLambdaChecker::run(
|
||||
const LambdaExpr *Lambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda");
|
||||
|
||||
for (const LambdaCapture Capture : Lambda->captures()) {
|
||||
if (Capture.capturesVariable()) {
|
||||
if (Capture.capturesVariable() && Capture.getCaptureKind() != LCK_ByRef) {
|
||||
QualType Pointee = Capture.getCapturedVar()->getType()->getPointeeType();
|
||||
|
||||
if (!Pointee.isNull() && isClassRefCounted(Pointee)) {
|
||||
|
@ -21,7 +21,7 @@ void foo() {
|
||||
SmartPtr<R> sp;
|
||||
take([&](R* argptr) {
|
||||
R* localptr;
|
||||
ptr->method(); // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}}
|
||||
ptr->method();
|
||||
argptr->method();
|
||||
localptr->method();
|
||||
});
|
||||
@ -33,7 +33,7 @@ void foo() {
|
||||
});
|
||||
take([&](R* argptr) {
|
||||
R* localptr;
|
||||
take(ptr); // expected-error{{Refcounted variable 'ptr' of type 'R' cannot be captured by a lambda}} expected-note{{Please consider using a smart pointer}}
|
||||
take(ptr);
|
||||
take(argptr);
|
||||
take(localptr);
|
||||
});
|
||||
@ -91,4 +91,28 @@ void foo() {
|
||||
take(argsp);
|
||||
take(localsp);
|
||||
});
|
||||
take([&ptr](R* argptr) {
|
||||
R* localptr;
|
||||
ptr->method();
|
||||
argptr->method();
|
||||
localptr->method();
|
||||
});
|
||||
take([&sp](SmartPtr<R> argsp) {
|
||||
SmartPtr<R> localsp;
|
||||
sp->method();
|
||||
argsp->method();
|
||||
localsp->method();
|
||||
});
|
||||
take([&ptr](R* argptr) {
|
||||
R* localptr;
|
||||
take(ptr);
|
||||
take(argptr);
|
||||
take(localptr);
|
||||
});
|
||||
take([&sp](SmartPtr<R> argsp) {
|
||||
SmartPtr<R> localsp;
|
||||
take(sp);
|
||||
take(argsp);
|
||||
take(localsp);
|
||||
});
|
||||
}
|
||||
|
4
config/external/nss/Makefile.in
vendored
4
config/external/nss/Makefile.in
vendored
@ -257,10 +257,6 @@ DEFAULT_GMAKE_FLAGS += MAKE_OBJDIR='$$(INSTALL) -D $$(OBJDIR)'
|
||||
# it, creating race conditions. See bug #836220
|
||||
DEFAULT_GMAKE_FLAGS += TARGETS='$$(LIBRARY) $$(SHARED_LIBRARY) $$(PROGRAM)'
|
||||
|
||||
ifeq ($(MOZ_FOLD_LIBS),1)
|
||||
MOZ_FOLD_LIBS_FLAGS += -D_NSPR_BUILD_=1
|
||||
endif
|
||||
|
||||
ifdef MOZ_FOLD_LIBS_FLAGS
|
||||
DEFAULT_GMAKE_FLAGS += XCFLAGS='$(MOZ_FOLD_LIBS_FLAGS)'
|
||||
endif
|
||||
|
5
config/external/nss/nss.symbols
vendored
5
config/external/nss/nss.symbols
vendored
@ -405,6 +405,7 @@ PK11_LoadPrivKey
|
||||
PK11_Logout
|
||||
PK11_LogoutAll
|
||||
PK11_MakeIDFromPubKey
|
||||
PK11_MapSignKeyType
|
||||
PK11_MechanismToAlgtag
|
||||
PK11_MergeTokens
|
||||
PK11_NeedLogin
|
||||
@ -429,11 +430,13 @@ PK11_SetPasswordFunc
|
||||
PK11_SetSlotPWValues
|
||||
PK11_Sign
|
||||
PK11_SignatureLen
|
||||
PK11_SignWithMechanism
|
||||
PK11_UnwrapPrivKey
|
||||
PK11_UnwrapSymKey
|
||||
PK11_UpdateSlotAttribute
|
||||
PK11_UserDisableSlot
|
||||
PK11_UserEnableSlot
|
||||
PK11_VerifyWithMechanism
|
||||
PK11_WrapPrivKey
|
||||
PK11_WrapSymKey
|
||||
PORT_Alloc
|
||||
@ -631,9 +634,11 @@ SEC_StringToOID
|
||||
SEC_UTF8StringTemplate @DATA@
|
||||
SEC_UTF8StringTemplate_Util @DATA@
|
||||
SGN_Begin
|
||||
SGN_CreateDigestInfo
|
||||
SGN_CreateDigestInfo_Util
|
||||
SGN_DecodeDigestInfo
|
||||
SGN_DestroyContext
|
||||
SGN_DestroyDigestInfo
|
||||
SGN_DestroyDigestInfo_Util
|
||||
SGN_End
|
||||
SGN_NewContext
|
||||
|
@ -10,7 +10,10 @@ support-files=
|
||||
|
||||
[test_cert.js]
|
||||
[test_encryption.js]
|
||||
# Failures on B2G emulator debug, B2G emulator-x86-kk
|
||||
# See bug 1234972 and bug 1199472
|
||||
skip-if = toolkit == 'gonk' && (debug || android_version > '15')
|
||||
[test_oob_cert_auth.js]
|
||||
# Failures on B2G emulator debug and Android opt
|
||||
# See bug 1141544, bug 1163052, and bug 1166032
|
||||
skip-if = (toolkit == 'gonk' && debug) || (toolkit == 'android' && !debug)
|
||||
# Failures on B2G emulator debug, B2G emulator-x86-kk and Android opt
|
||||
# See bug 1141544, bug 1163052, bug 1166032 and bug 1241831
|
||||
skip-if = (toolkit == 'gonk' && (debug || android_version > '15')) || (toolkit == 'android' && !debug)
|
||||
|
@ -12541,13 +12541,9 @@ nsDocShell::ShouldDiscardLayoutState(nsIHttpChannel* aChannel)
|
||||
}
|
||||
|
||||
// figure out if SH should be saving layout state
|
||||
nsCOMPtr<nsISupports> securityInfo;
|
||||
bool noStore = false, noCache = false;
|
||||
aChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
|
||||
bool noStore = false;
|
||||
aChannel->IsNoStoreResponse(&noStore);
|
||||
aChannel->IsNoCacheResponse(&noCache);
|
||||
|
||||
return (noStore || (noCache && securityInfo));
|
||||
return noStore;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -23,13 +23,13 @@ public:
|
||||
|
||||
void AddLayerRectangles(dom::Sequence<dom::ProfileTimelineLayerRect>& aRectangles)
|
||||
{
|
||||
nsIntRegionRectIterator it(mRegion);
|
||||
while (const nsIntRect* iterRect = it.Next()) {
|
||||
for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) {
|
||||
const nsIntRect& iterRect = iter.Get();
|
||||
dom::ProfileTimelineLayerRect rect;
|
||||
rect.mX = iterRect->X();
|
||||
rect.mY = iterRect->Y();
|
||||
rect.mWidth = iterRect->Width();
|
||||
rect.mHeight = iterRect->Height();
|
||||
rect.mX = iterRect.X();
|
||||
rect.mY = iterRect.Y();
|
||||
rect.mWidth = iterRect.Width();
|
||||
rect.mHeight = iterRect.Height();
|
||||
aRectangles.AppendElement(rect, fallible);
|
||||
}
|
||||
}
|
||||
|
@ -84,8 +84,9 @@
|
||||
}
|
||||
|
||||
function testsIterator() {
|
||||
// Load a secure page with a no-store header, followed by a simple page
|
||||
// On pagehide, first page should report it is not being persisted
|
||||
// Load a secure page with a no-cache header, followed by a simple page.
|
||||
// no-cache should not interfere with the bfcache in the way no-store
|
||||
// does.
|
||||
var test1DocURI = "https://example.com:443/tests/docshell/test/chrome/112564_nocache.html";
|
||||
|
||||
gExpected = [{type: "pagehide", persisted: true},
|
||||
@ -97,18 +98,16 @@
|
||||
var test2Doc = "data:text/html,<html><head><title>test2</title></head>" +
|
||||
"<body>test2</body></html>";
|
||||
|
||||
gExpected = [{type: "pagehide", title: "test1", persisted: false},
|
||||
{type: "unload", title: "test1"},
|
||||
gExpected = [{type: "pagehide", title: "test1", persisted: true},
|
||||
{type: "load", title: "test2"},
|
||||
{type: "pageshow", title: "test2", persisted: false}];
|
||||
gBrowser.loadURI(test2Doc);
|
||||
yield undefined;
|
||||
|
||||
// Now go back in history. First page should not have been cached.
|
||||
// Now go back in history. First page has been cached.
|
||||
// Check persisted property to confirm
|
||||
gExpected = [{type: "pagehide", title: "test2", persisted: true},
|
||||
{type: "load", title: "test1"},
|
||||
{type: "pageshow", title: "test1", persisted: false}];
|
||||
{type: "pageshow", title: "test1", persisted: true}];
|
||||
gBrowser.goBack();
|
||||
yield undefined;
|
||||
}
|
||||
|
@ -113,7 +113,8 @@
|
||||
// https no-cache
|
||||
testName = "[nocache]";
|
||||
|
||||
// Load a page with a no-cache header
|
||||
// Load a page with a no-cache header. This should not be
|
||||
// restricted like no-store (bug 567365)
|
||||
gBrowser.loadURI(nocacheURI);
|
||||
yield undefined;
|
||||
|
||||
@ -141,19 +142,19 @@
|
||||
yield undefined;
|
||||
|
||||
|
||||
// Now go back in history. First page should not have been cached.
|
||||
// Now go back in history to the cached page.
|
||||
gBrowser.goBack();
|
||||
yield undefined;
|
||||
|
||||
|
||||
// First uncacheable page will now be reloaded. Check scroll position
|
||||
// restored, and form contents not
|
||||
// First page will now be reloaded. Check scroll position
|
||||
// and form contents are restored
|
||||
is(gBrowser.contentWindow.scrollX, scrollX, testName +
|
||||
" horizontal axis scroll position not correctly restored");
|
||||
is(gBrowser.contentWindow.scrollY, scrollY, testName +
|
||||
" vertical axis scroll position not correctly restored");
|
||||
var formValue = gBrowser.contentDocument.getElementById("inp").value;
|
||||
isnot(formValue, text, testName + " form value incorrectly restored");
|
||||
is(formValue, text, testName + " form value not correctly restored");
|
||||
|
||||
// nextTest has to be called from here, as no events are fired in this
|
||||
// step
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "nsIStreamTransportService.h"
|
||||
|
||||
#include "mozilla/Base64.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/dom/DOMError.h"
|
||||
#include "mozilla/dom/EncodingUtils.h"
|
||||
#include "mozilla/dom/File.h"
|
||||
@ -315,11 +316,17 @@ FileReader::DoReadData(uint64_t aCount)
|
||||
NS_ASSERTION(bytesRead == aCount, "failed to read data");
|
||||
}
|
||||
else {
|
||||
CheckedInt<uint64_t> size = mDataLen;
|
||||
size += aCount;
|
||||
|
||||
//Update memory buffer to reflect the contents of the file
|
||||
if (mDataLen + aCount > UINT32_MAX) {
|
||||
// PR_Realloc doesn't support over 4GB memory size even if 64-bit OS
|
||||
if (!size.isValid() ||
|
||||
// PR_Realloc doesn't support over 4GB memory size even if 64-bit OS
|
||||
size.value() > UINT32_MAX ||
|
||||
size.value() > mTotal) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (mDataFormat != FILE_AS_ARRAYBUFFER) {
|
||||
mFileData = (char *) realloc(mFileData, mDataLen + aCount);
|
||||
NS_ENSURE_TRUE(mFileData, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
@ -343,20 +343,7 @@ FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
|
||||
|
||||
for (uint32_t i = 0; i < mFormData.Length(); ++i) {
|
||||
if (mFormData[i].value.IsBlob()) {
|
||||
RefPtr<File> file = mFormData[i].value.GetAsBlob()->ToFile();
|
||||
if (file) {
|
||||
fs.AddNameBlobPair(mFormData[i].name, file);
|
||||
continue;
|
||||
}
|
||||
|
||||
ErrorResult rv;
|
||||
file =
|
||||
mFormData[i].value.GetAsBlob()->ToFile(NS_LITERAL_STRING("blob"), rv);
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
fs.AddNameBlobPair(mFormData[i].name, file);
|
||||
fs.AddNameBlobPair(mFormData[i].name, mFormData[i].value.GetAsBlob());
|
||||
} else if (mFormData[i].value.IsUSVString()) {
|
||||
fs.AddNameValuePair(mFormData[i].name,
|
||||
mFormData[i].value.GetAsUSVString());
|
||||
|
@ -24,3 +24,5 @@ skip-if = e10s # this tests non-e10s behavior. it's not expected to work in e10s
|
||||
skip-if = true # Intermittent failures - bug 987493. Restore the skip-if above once fixed
|
||||
[browser_bug1058164.js]
|
||||
[browser_use_counters.js]
|
||||
[browser_bug1238440.js]
|
||||
skip-if = e10s
|
||||
|
76
dom/base/test/browser_bug1238440.js
Normal file
76
dom/base/test/browser_bug1238440.js
Normal file
@ -0,0 +1,76 @@
|
||||
/* 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://gre/modules/NetUtil.jsm");
|
||||
Components.utils.import("resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
const PAGE = "data:text/html,<html><body><input type=\"file\"/></body></html>";
|
||||
|
||||
function writeFile(file, text) {
|
||||
return new Promise((resolve) => {
|
||||
let converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
|
||||
let ostream = FileUtils.openSafeFileOutputStream(file);
|
||||
let istream = converter.convertToInputStream(text);
|
||||
NetUtil.asyncCopy(istream, ostream, function(status) {
|
||||
if (!Components.isSuccessCode(status)) throw 'fail';
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function runFileReader(input, status) {
|
||||
return new Promise((resolve) => {
|
||||
let fr = new FileReader();
|
||||
fr.onload = function() {
|
||||
ok(status, "FileReader called onload");
|
||||
resolve();
|
||||
}
|
||||
|
||||
fr.onerror = function(e) {
|
||||
e.preventDefault();
|
||||
ok(!status, "FileReader called onerror");
|
||||
resolve();
|
||||
}
|
||||
|
||||
fr.readAsArrayBuffer(input);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function() {
|
||||
info("Creating a temporary file...");
|
||||
let file = FileUtils.getFile("TmpD", ["bug1238440.txt"]);
|
||||
yield writeFile(file, "hello world");
|
||||
|
||||
info("Opening a tab...");
|
||||
let tab = gBrowser.addTab(PAGE);
|
||||
gBrowser.selectedTab = tab;
|
||||
let browser = tab.linkedBrowser;
|
||||
yield BrowserTestUtils.browserLoaded(browser);
|
||||
|
||||
info("Populating the form...");
|
||||
let doc = browser.contentDocument;
|
||||
let input = doc.querySelector('input');
|
||||
input.value = file.path;
|
||||
|
||||
info("Running the FileReader...");
|
||||
yield runFileReader(input.files[0], true);
|
||||
|
||||
info("Writing the temporary file again...");
|
||||
yield writeFile(file, "hello world-----------------------------");
|
||||
|
||||
info("Running the FileReader again...");
|
||||
yield runFileReader(input.files[0], false);
|
||||
|
||||
info("Closing the tab...");
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
|
||||
ok(true, "we didn't crash.");
|
||||
});
|
@ -36,7 +36,7 @@ function testFile(file, contents, test) {
|
||||
[{ name: "hello", value: "world"},
|
||||
{ name: "myfile",
|
||||
value: contents,
|
||||
fileName: file.name || "blob",
|
||||
fileName: file.name || "",
|
||||
contentType: file.type || "application/octet-stream" }]);
|
||||
testHasRun();
|
||||
}
|
||||
|
@ -193,6 +193,19 @@ KeyAlgorithmProxy::JwkAlg() const
|
||||
}
|
||||
}
|
||||
|
||||
if (mName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
nsString hashName = mRsa.mHash.mName;
|
||||
if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
|
||||
return NS_LITERAL_STRING(JWK_ALG_PS1);
|
||||
} else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
|
||||
return NS_LITERAL_STRING(JWK_ALG_PS256);
|
||||
} else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
|
||||
return NS_LITERAL_STRING(JWK_ALG_PS384);
|
||||
} else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
|
||||
return NS_LITERAL_STRING(JWK_ALG_PS512);
|
||||
}
|
||||
}
|
||||
|
||||
return nsString();
|
||||
}
|
||||
|
||||
|
@ -23,9 +23,11 @@
|
||||
#define WEBCRYPTO_ALG_SHA384 "SHA-384"
|
||||
#define WEBCRYPTO_ALG_SHA512 "SHA-512"
|
||||
#define WEBCRYPTO_ALG_HMAC "HMAC"
|
||||
#define WEBCRYPTO_ALG_HKDF "HKDF"
|
||||
#define WEBCRYPTO_ALG_PBKDF2 "PBKDF2"
|
||||
#define WEBCRYPTO_ALG_RSASSA_PKCS1 "RSASSA-PKCS1-v1_5"
|
||||
#define WEBCRYPTO_ALG_RSA_OAEP "RSA-OAEP"
|
||||
#define WEBCRYPTO_ALG_RSA_PSS "RSA-PSS"
|
||||
#define WEBCRYPTO_ALG_ECDH "ECDH"
|
||||
#define WEBCRYPTO_ALG_ECDSA "ECDSA"
|
||||
#define WEBCRYPTO_ALG_DH "DH"
|
||||
@ -86,6 +88,10 @@
|
||||
#define JWK_ALG_RSA_OAEP_256 "RSA-OAEP-256"
|
||||
#define JWK_ALG_RSA_OAEP_384 "RSA-OAEP-384"
|
||||
#define JWK_ALG_RSA_OAEP_512 "RSA-OAEP-512"
|
||||
#define JWK_ALG_PS1 "PS1" // RSA-PSS
|
||||
#define JWK_ALG_PS256 "PS256"
|
||||
#define JWK_ALG_PS384 "PS384"
|
||||
#define JWK_ALG_PS512 "PS512"
|
||||
#define JWK_ALG_ECDSA_P_256 "ES256"
|
||||
#define JWK_ALG_ECDSA_P_384 "ES384"
|
||||
#define JWK_ALG_ECDSA_P_521 "ES521"
|
||||
@ -204,6 +210,8 @@ MapAlgorithmNameToMechanism(const nsString& aName)
|
||||
mechanism = CKM_RSA_PKCS;
|
||||
} else if (aName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
|
||||
mechanism = CKM_RSA_PKCS_OAEP;
|
||||
} else if (aName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
mechanism = CKM_RSA_PKCS_PSS;
|
||||
} else if (aName.EqualsLiteral(WEBCRYPTO_ALG_ECDH)) {
|
||||
mechanism = CKM_ECDH1_DERIVE;
|
||||
} else if (aName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
|
||||
@ -238,12 +246,16 @@ NormalizeToken(const nsString& aName, nsString& aDest)
|
||||
aDest.AssignLiteral(WEBCRYPTO_ALG_SHA512);
|
||||
} else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_HMAC)) {
|
||||
aDest.AssignLiteral(WEBCRYPTO_ALG_HMAC);
|
||||
} else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_HKDF)) {
|
||||
aDest.AssignLiteral(WEBCRYPTO_ALG_HKDF);
|
||||
} else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_PBKDF2)) {
|
||||
aDest.AssignLiteral(WEBCRYPTO_ALG_PBKDF2);
|
||||
} else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_RSASSA_PKCS1)) {
|
||||
aDest.AssignLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1);
|
||||
} else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_RSA_OAEP)) {
|
||||
aDest.AssignLiteral(WEBCRYPTO_ALG_RSA_OAEP);
|
||||
} else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
aDest.AssignLiteral(WEBCRYPTO_ALG_RSA_PSS);
|
||||
} else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_ECDH)) {
|
||||
aDest.AssignLiteral(WEBCRYPTO_ALG_ECDH);
|
||||
} else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_ECDSA)) {
|
||||
|
@ -20,6 +20,20 @@
|
||||
#include "mozilla/dom/WebCryptoTask.h"
|
||||
#include "mozilla/dom/WebCryptoThreadPool.h"
|
||||
|
||||
// Template taken from security/nss/lib/util/templates.c
|
||||
// This (or SGN_EncodeDigestInfo) would ideally be exported
|
||||
// by NSS and until that happens we have to keep our own copy.
|
||||
const SEC_ASN1Template SGN_DigestInfoTemplate[] = {
|
||||
{ SEC_ASN1_SEQUENCE,
|
||||
0, NULL, sizeof(SGNDigestInfo) },
|
||||
{ SEC_ASN1_INLINE,
|
||||
offsetof(SGNDigestInfo,digestAlgorithm),
|
||||
SEC_ASN1_GET(SECOID_AlgorithmIDTemplate) },
|
||||
{ SEC_ASN1_OCTET_STRING,
|
||||
offsetof(SGNDigestInfo,digest) },
|
||||
{ 0, }
|
||||
};
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
@ -70,6 +84,7 @@ enum TelemetryAlgorithm {
|
||||
TA_ECDH = 20,
|
||||
TA_PBKDF2 = 21,
|
||||
TA_ECDSA = 22,
|
||||
TA_HKDF = 23,
|
||||
};
|
||||
|
||||
// Convenience functions for extracting / converting information
|
||||
@ -264,6 +279,41 @@ MapOIDTagToNamedCurve(SECOidTag aOIDTag, nsString& aResult)
|
||||
return true;
|
||||
}
|
||||
|
||||
inline SECOidTag
|
||||
MapHashAlgorithmNameToOID(const nsString& aName)
|
||||
{
|
||||
SECOidTag hashOID(SEC_OID_UNKNOWN);
|
||||
|
||||
if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
|
||||
hashOID = SEC_OID_SHA1;
|
||||
} else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
|
||||
hashOID = SEC_OID_SHA256;
|
||||
} else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
|
||||
hashOID = SEC_OID_SHA384;
|
||||
} else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
|
||||
hashOID = SEC_OID_SHA512;
|
||||
}
|
||||
|
||||
return hashOID;
|
||||
}
|
||||
|
||||
inline CK_MECHANISM_TYPE
|
||||
MapHashAlgorithmNameToMgfMechanism(const nsString& aName) {
|
||||
CK_MECHANISM_TYPE mech(UNKNOWN_CK_MECHANISM);
|
||||
|
||||
if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
|
||||
mech = CKG_MGF1_SHA1;
|
||||
} else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
|
||||
mech = CKG_MGF1_SHA256;
|
||||
} else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
|
||||
mech = CKG_MGF1_SHA384;
|
||||
} else if (aName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
|
||||
mech = CKG_MGF1_SHA512;
|
||||
}
|
||||
|
||||
return mech;
|
||||
}
|
||||
|
||||
// Helper function to clone data from an ArrayBuffer or ArrayBufferView object
|
||||
inline bool
|
||||
CloneData(JSContext* aCx, CryptoBuffer& aDst, JS::Handle<JSObject*> aSrc)
|
||||
@ -838,24 +888,15 @@ public:
|
||||
}
|
||||
// Otherwise mLabel remains the empty octet string, as intended
|
||||
|
||||
// Look up the MGF based on the KeyAlgorithm.
|
||||
// static_cast is safe because we only get here if the algorithm name
|
||||
// is RSA-OAEP, and that only happens if we've constructed
|
||||
// an RsaHashedKeyAlgorithm.
|
||||
mHashMechanism = KeyAlgorithmProxy::GetMechanism(aKey.Algorithm().mRsa.mHash);
|
||||
KeyAlgorithm& hashAlg = aKey.Algorithm().mRsa.mHash;
|
||||
mHashMechanism = KeyAlgorithmProxy::GetMechanism(hashAlg);
|
||||
mMgfMechanism = MapHashAlgorithmNameToMgfMechanism(hashAlg.mName);
|
||||
|
||||
switch (mHashMechanism) {
|
||||
case CKM_SHA_1:
|
||||
mMgfMechanism = CKG_MGF1_SHA1; break;
|
||||
case CKM_SHA256:
|
||||
mMgfMechanism = CKG_MGF1_SHA256; break;
|
||||
case CKM_SHA384:
|
||||
mMgfMechanism = CKG_MGF1_SHA384; break;
|
||||
case CKM_SHA512:
|
||||
mMgfMechanism = CKG_MGF1_SHA512; break;
|
||||
default:
|
||||
mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
||||
return;
|
||||
// Check we found appropriate mechanisms.
|
||||
if (mHashMechanism == UNKNOWN_CK_MECHANISM ||
|
||||
mMgfMechanism == UNKNOWN_CK_MECHANISM) {
|
||||
mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1040,11 +1081,14 @@ public:
|
||||
const CryptoOperationData& aData,
|
||||
bool aSign)
|
||||
: mOidTag(SEC_OID_UNKNOWN)
|
||||
, mHashMechanism(UNKNOWN_CK_MECHANISM)
|
||||
, mMgfMechanism(UNKNOWN_CK_MECHANISM)
|
||||
, mPrivKey(aKey.GetPrivateKey())
|
||||
, mPubKey(aKey.GetPublicKey())
|
||||
, mSaltLength(0)
|
||||
, mSign(aSign)
|
||||
, mVerified(false)
|
||||
, mEcdsa(false)
|
||||
, mAlgorithm(Algorithm::UNKNOWN)
|
||||
{
|
||||
ATTEMPT_BUFFER_INIT(mData, aData);
|
||||
if (!aSign) {
|
||||
@ -1052,34 +1096,44 @@ public:
|
||||
}
|
||||
|
||||
nsString algName;
|
||||
nsString hashAlgName;
|
||||
mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
|
||||
if (NS_FAILED(mEarlyRv)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Look up the SECOidTag
|
||||
if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
|
||||
mEcdsa = false;
|
||||
mAlgorithm = Algorithm::RSA_PKCS1;
|
||||
Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSASSA_PKCS1);
|
||||
CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSASSA_PKCS1);
|
||||
hashAlgName = aKey.Algorithm().mRsa.mHash.mName;
|
||||
} else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
mAlgorithm = Algorithm::RSA_PSS;
|
||||
Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSA_PSS);
|
||||
CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSA_PSS);
|
||||
|
||||
// For RSA, the hash name comes from the key algorithm
|
||||
nsString hashName = aKey.Algorithm().mRsa.mHash.mName;
|
||||
switch (MapAlgorithmNameToMechanism(hashName)) {
|
||||
case CKM_SHA_1:
|
||||
mOidTag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; break;
|
||||
case CKM_SHA256:
|
||||
mOidTag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; break;
|
||||
case CKM_SHA384:
|
||||
mOidTag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION; break;
|
||||
case CKM_SHA512:
|
||||
mOidTag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION; break;
|
||||
default:
|
||||
mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
||||
return;
|
||||
KeyAlgorithm& hashAlg = aKey.Algorithm().mRsa.mHash;
|
||||
hashAlgName = hashAlg.mName;
|
||||
mHashMechanism = KeyAlgorithmProxy::GetMechanism(hashAlg);
|
||||
mMgfMechanism = MapHashAlgorithmNameToMgfMechanism(hashAlgName);
|
||||
|
||||
// Check we found appropriate mechanisms.
|
||||
if (mHashMechanism == UNKNOWN_CK_MECHANISM ||
|
||||
mMgfMechanism == UNKNOWN_CK_MECHANISM) {
|
||||
mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
||||
return;
|
||||
}
|
||||
|
||||
RootedDictionary<RsaPssParams> params(aCx);
|
||||
mEarlyRv = Coerce(aCx, params, aAlgorithm);
|
||||
if (NS_FAILED(mEarlyRv)) {
|
||||
mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
|
||||
return;
|
||||
}
|
||||
|
||||
mSaltLength = params.mSaltLength;
|
||||
} else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
|
||||
mEcdsa = true;
|
||||
mAlgorithm = Algorithm::ECDSA;
|
||||
Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_ECDSA);
|
||||
CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_ECDSA);
|
||||
|
||||
@ -1091,38 +1145,27 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
nsString hashName;
|
||||
mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
|
||||
mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashAlgName);
|
||||
if (NS_FAILED(mEarlyRv)) {
|
||||
mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
|
||||
return;
|
||||
}
|
||||
|
||||
CK_MECHANISM_TYPE hashMechanism = MapAlgorithmNameToMechanism(hashName);
|
||||
if (hashMechanism == UNKNOWN_CK_MECHANISM) {
|
||||
mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (hashMechanism) {
|
||||
case CKM_SHA_1:
|
||||
mOidTag = SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE; break;
|
||||
case CKM_SHA256:
|
||||
mOidTag = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE; break;
|
||||
case CKM_SHA384:
|
||||
mOidTag = SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE; break;
|
||||
case CKM_SHA512:
|
||||
mOidTag = SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE; break;
|
||||
default:
|
||||
mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// This shouldn't happen; CreateSignVerifyTask shouldn't create
|
||||
// one of these unless it's for the above algorithms.
|
||||
MOZ_ASSERT(false);
|
||||
}
|
||||
|
||||
// Must have a valid algorithm by now.
|
||||
MOZ_ASSERT(mAlgorithm != Algorithm::UNKNOWN);
|
||||
|
||||
// Determine hash algorithm to use.
|
||||
mOidTag = MapHashAlgorithmNameToOID(hashAlgName);
|
||||
if (mOidTag == SEC_OID_UNKNOWN) {
|
||||
mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that we have the appropriate key
|
||||
if ((mSign && !mPrivKey) || (!mSign && !mPubKey)) {
|
||||
mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
|
||||
@ -1132,63 +1175,88 @@ public:
|
||||
|
||||
private:
|
||||
SECOidTag mOidTag;
|
||||
CK_MECHANISM_TYPE mHashMechanism;
|
||||
CK_MECHANISM_TYPE mMgfMechanism;
|
||||
ScopedSECKEYPrivateKey mPrivKey;
|
||||
ScopedSECKEYPublicKey mPubKey;
|
||||
CryptoBuffer mSignature;
|
||||
CryptoBuffer mData;
|
||||
uint32_t mSaltLength;
|
||||
bool mSign;
|
||||
bool mVerified;
|
||||
bool mEcdsa;
|
||||
|
||||
// The signature algorithm to use.
|
||||
enum class Algorithm: uint8_t {ECDSA, RSA_PKCS1, RSA_PSS, UNKNOWN};
|
||||
Algorithm mAlgorithm;
|
||||
|
||||
virtual nsresult DoCrypto() override
|
||||
{
|
||||
nsresult rv;
|
||||
if (mSign) {
|
||||
ScopedSECItem signature(::SECITEM_AllocItem(nullptr, nullptr, 0));
|
||||
if (!signature.get()) {
|
||||
SECStatus rv;
|
||||
ScopedSECItem hash(::SECITEM_AllocItem(nullptr, nullptr,
|
||||
HASH_ResultLenByOidTag(mOidTag)));
|
||||
if (!hash) {
|
||||
return NS_ERROR_DOM_OPERATION_ERR;
|
||||
}
|
||||
|
||||
// Compute digest over given data.
|
||||
rv = PK11_HashBuf(mOidTag, hash->data, mData.Elements(), mData.Length());
|
||||
NS_ENSURE_SUCCESS(MapSECStatus(rv), NS_ERROR_DOM_OPERATION_ERR);
|
||||
|
||||
// Wrap hash in a digest info template (RSA-PKCS1 only).
|
||||
if (mAlgorithm == Algorithm::RSA_PKCS1) {
|
||||
ScopedSGNDigestInfo di(SGN_CreateDigestInfo(mOidTag, hash->data, hash->len));
|
||||
if (!di) {
|
||||
return NS_ERROR_DOM_OPERATION_ERR;
|
||||
}
|
||||
|
||||
rv = MapSECStatus(SEC_SignData(signature, mData.Elements(),
|
||||
mData.Length(), mPrivKey, mOidTag));
|
||||
|
||||
if (mEcdsa) {
|
||||
// DER-decode the signature
|
||||
int signatureLength = PK11_SignatureLen(mPrivKey);
|
||||
ScopedSECItem rawSignature(DSAU_DecodeDerSigToLen(signature.get(),
|
||||
signatureLength));
|
||||
if (!rawSignature.get()) {
|
||||
return NS_ERROR_DOM_OPERATION_ERR;
|
||||
}
|
||||
|
||||
ATTEMPT_BUFFER_ASSIGN(mSignature, rawSignature);
|
||||
} else {
|
||||
ATTEMPT_BUFFER_ASSIGN(mSignature, signature);
|
||||
// Reuse |hash|.
|
||||
SECITEM_FreeItem(hash, false);
|
||||
if (!SEC_ASN1EncodeItem(nullptr, hash, di, SGN_DigestInfoTemplate)) {
|
||||
return NS_ERROR_DOM_OPERATION_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
SECItem* params = nullptr;
|
||||
CK_MECHANISM_TYPE mech = PK11_MapSignKeyType((mSign ? mPrivKey->keyType :
|
||||
mPubKey->keyType));
|
||||
|
||||
CK_RSA_PKCS_PSS_PARAMS rsaPssParams;
|
||||
SECItem rsaPssParamsItem = { siBuffer, };
|
||||
|
||||
// Set up parameters for RSA-PSS.
|
||||
if (mAlgorithm == Algorithm::RSA_PSS) {
|
||||
rsaPssParams.hashAlg = mHashMechanism;
|
||||
rsaPssParams.mgf = mMgfMechanism;
|
||||
rsaPssParams.sLen = mSaltLength;
|
||||
|
||||
rsaPssParamsItem.data = (unsigned char*)&rsaPssParams;
|
||||
rsaPssParamsItem.len = sizeof(rsaPssParams);
|
||||
params = &rsaPssParamsItem;
|
||||
|
||||
mech = CKM_RSA_PKCS_PSS;
|
||||
}
|
||||
|
||||
// Allocate SECItem to hold the signature.
|
||||
uint32_t len = mSign ? PK11_SignatureLen(mPrivKey) : 0;
|
||||
ScopedSECItem sig(::SECITEM_AllocItem(nullptr, nullptr, len));
|
||||
if (!sig) {
|
||||
return NS_ERROR_DOM_OPERATION_ERR;
|
||||
}
|
||||
|
||||
if (mSign) {
|
||||
// Sign the hash.
|
||||
rv = PK11_SignWithMechanism(mPrivKey, mech, params, sig, hash);
|
||||
NS_ENSURE_SUCCESS(MapSECStatus(rv), NS_ERROR_DOM_OPERATION_ERR);
|
||||
ATTEMPT_BUFFER_ASSIGN(mSignature, sig);
|
||||
} else {
|
||||
ScopedSECItem signature(::SECITEM_AllocItem(nullptr, nullptr, 0));
|
||||
if (!signature.get()) {
|
||||
return NS_ERROR_DOM_UNKNOWN_ERR;
|
||||
// Copy the given signature to the SECItem.
|
||||
if (!mSignature.ToSECItem(nullptr, sig)) {
|
||||
return NS_ERROR_DOM_OPERATION_ERR;
|
||||
}
|
||||
|
||||
if (mEcdsa) {
|
||||
// DER-encode the signature
|
||||
ScopedSECItem rawSignature(::SECITEM_AllocItem(nullptr, nullptr, 0));
|
||||
if (!rawSignature || !mSignature.ToSECItem(nullptr, rawSignature)) {
|
||||
return NS_ERROR_DOM_UNKNOWN_ERR;
|
||||
}
|
||||
|
||||
rv = MapSECStatus(DSAU_EncodeDerSigWithLen(signature, rawSignature,
|
||||
rawSignature->len));
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
|
||||
} else if (!mSignature.ToSECItem(nullptr, signature)) {
|
||||
return NS_ERROR_DOM_UNKNOWN_ERR;
|
||||
}
|
||||
|
||||
rv = MapSECStatus(VFY_VerifyData(mData.Elements(), mData.Length(),
|
||||
mPubKey, signature, mOidTag, nullptr));
|
||||
mVerified = NS_SUCCEEDED(rv);
|
||||
// Verify the signature.
|
||||
rv = PK11_VerifyWithMechanism(mPubKey, mech, params, sig, hash, nullptr);
|
||||
mVerified = NS_SUCCEEDED(MapSECStatus(rv));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
@ -1223,22 +1291,19 @@ public:
|
||||
|
||||
TelemetryAlgorithm telemetryAlg;
|
||||
if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
|
||||
mOidTag = SEC_OID_SHA1;
|
||||
telemetryAlg = TA_SHA_1;
|
||||
} else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
|
||||
mOidTag = SEC_OID_SHA256;
|
||||
telemetryAlg = TA_SHA_224;
|
||||
} else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
|
||||
mOidTag = SEC_OID_SHA384;
|
||||
telemetryAlg = TA_SHA_256;
|
||||
} else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
|
||||
mOidTag = SEC_OID_SHA512;
|
||||
telemetryAlg = TA_SHA_384;
|
||||
} else {
|
||||
mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
|
||||
return;
|
||||
}
|
||||
Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, telemetryAlg);
|
||||
mOidTag = MapHashAlgorithmNameToOID(algName);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -1443,6 +1508,13 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
// This task only supports raw and JWK format.
|
||||
if (!mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
|
||||
!mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
|
||||
mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is an HMAC key, import the hash name
|
||||
if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
|
||||
RootedDictionary<HmacImportParams> params(aCx);
|
||||
@ -1507,14 +1579,15 @@ public:
|
||||
!mJwk.mUse.Value().EqualsLiteral(JWK_USE_ENC)) {
|
||||
return NS_ERROR_DOM_DATA_ERR;
|
||||
}
|
||||
} else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2)) {
|
||||
} else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HKDF) ||
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2)) {
|
||||
if (mKey->HasUsageOtherThan(CryptoKey::DERIVEKEY | CryptoKey::DERIVEBITS)) {
|
||||
return NS_ERROR_DOM_DATA_ERR;
|
||||
}
|
||||
mKey->Algorithm().MakeAes(mAlgName, length);
|
||||
|
||||
if (mDataIsJwk && mJwk.mUse.WasPassed()) {
|
||||
// There is not a 'use' value consistent with PBKDF
|
||||
// There is not a 'use' value consistent with PBKDF or HKDF
|
||||
return NS_ERROR_DOM_DATA_ERR;
|
||||
};
|
||||
} else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
|
||||
@ -1595,7 +1668,8 @@ public:
|
||||
|
||||
// If this is RSA with a hash, cache the hash name
|
||||
if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
RootedDictionary<RsaHashedImportParams> params(aCx);
|
||||
mEarlyRv = Coerce(aCx, params, aAlgorithm);
|
||||
if (NS_FAILED(mEarlyRv)) {
|
||||
@ -1697,7 +1771,8 @@ private:
|
||||
mKey->HasUsageOtherThan(CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY))) {
|
||||
return NS_ERROR_DOM_DATA_ERR;
|
||||
}
|
||||
} else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
|
||||
} else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
if ((mKey->GetKeyType() == CryptoKey::PUBLIC &&
|
||||
mKey->HasUsageOtherThan(CryptoKey::VERIFY)) ||
|
||||
(mKey->GetKeyType() == CryptoKey::PRIVATE &&
|
||||
@ -2283,7 +2358,8 @@ GenerateAsymmetricKeyTask::GenerateAsymmetricKeyTask(
|
||||
// Construct an appropriate KeyAlorithm
|
||||
uint32_t privateAllowedUsages = 0, publicAllowedUsages = 0;
|
||||
if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
RootedDictionary<RsaHashedKeyGenParams> params(aCx);
|
||||
mEarlyRv = Coerce(aCx, params, aAlgorithm);
|
||||
if (NS_FAILED(mEarlyRv)) {
|
||||
@ -2386,6 +2462,7 @@ GenerateAsymmetricKeyTask::GenerateAsymmetricKeyTask(
|
||||
|
||||
// Set key usages.
|
||||
if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS) ||
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
|
||||
privateAllowedUsages = CryptoKey::SIGN;
|
||||
publicAllowedUsages = CryptoKey::VERIFY;
|
||||
@ -2396,6 +2473,8 @@ GenerateAsymmetricKeyTask::GenerateAsymmetricKeyTask(
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
|
||||
privateAllowedUsages = CryptoKey::DERIVEKEY | CryptoKey::DERIVEBITS;
|
||||
publicAllowedUsages = 0;
|
||||
} else {
|
||||
MOZ_ASSERT(false); // This shouldn't happen.
|
||||
}
|
||||
|
||||
mKeyPair->mPrivateKey.get()->SetExtractable(aExtractable);
|
||||
@ -2497,6 +2576,156 @@ GenerateAsymmetricKeyTask::Cleanup()
|
||||
mKeyPair = nullptr;
|
||||
}
|
||||
|
||||
class DeriveHkdfBitsTask : public ReturnArrayBufferViewTask
|
||||
{
|
||||
public:
|
||||
DeriveHkdfBitsTask(JSContext* aCx,
|
||||
const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength)
|
||||
: mSymKey(aKey.GetSymKey())
|
||||
{
|
||||
Init(aCx, aAlgorithm, aKey, aLength);
|
||||
}
|
||||
|
||||
DeriveHkdfBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
|
||||
CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm)
|
||||
: mSymKey(aKey.GetSymKey())
|
||||
{
|
||||
size_t length;
|
||||
mEarlyRv = GetKeyLengthForAlgorithm(aCx, aTargetAlgorithm, length);
|
||||
|
||||
if (NS_SUCCEEDED(mEarlyRv)) {
|
||||
Init(aCx, aAlgorithm, aKey, length);
|
||||
}
|
||||
}
|
||||
|
||||
void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey,
|
||||
uint32_t aLength)
|
||||
{
|
||||
Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_HKDF);
|
||||
CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_HKDF);
|
||||
|
||||
// Check that we have a key.
|
||||
if (mSymKey.Length() == 0) {
|
||||
mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
|
||||
return;
|
||||
}
|
||||
|
||||
RootedDictionary<HkdfParams> params(aCx);
|
||||
mEarlyRv = Coerce(aCx, params, aAlgorithm);
|
||||
if (NS_FAILED(mEarlyRv)) {
|
||||
mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
|
||||
return;
|
||||
}
|
||||
|
||||
// length must be greater than zero.
|
||||
if (aLength == 0) {
|
||||
mEarlyRv = NS_ERROR_DOM_DATA_ERR;
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract the hash algorithm.
|
||||
nsString hashName;
|
||||
mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
|
||||
if (NS_FAILED(mEarlyRv)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the given hash algorithm.
|
||||
switch (MapAlgorithmNameToMechanism(hashName)) {
|
||||
case CKM_SHA_1: mMechanism = CKM_NSS_HKDF_SHA1; break;
|
||||
case CKM_SHA256: mMechanism = CKM_NSS_HKDF_SHA256; break;
|
||||
case CKM_SHA384: mMechanism = CKM_NSS_HKDF_SHA384; break;
|
||||
case CKM_SHA512: mMechanism = CKM_NSS_HKDF_SHA512; break;
|
||||
default:
|
||||
mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
||||
return;
|
||||
}
|
||||
|
||||
ATTEMPT_BUFFER_INIT(mSalt, params.mSalt)
|
||||
ATTEMPT_BUFFER_INIT(mInfo, params.mInfo)
|
||||
mLengthInBytes = ceil((double)aLength / 8);
|
||||
mLengthInBits = aLength;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t mLengthInBits;
|
||||
size_t mLengthInBytes;
|
||||
CryptoBuffer mSalt;
|
||||
CryptoBuffer mInfo;
|
||||
CryptoBuffer mSymKey;
|
||||
CK_MECHANISM_TYPE mMechanism;
|
||||
|
||||
virtual nsresult DoCrypto() override
|
||||
{
|
||||
ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
||||
if (!arena) {
|
||||
return NS_ERROR_DOM_OPERATION_ERR;
|
||||
}
|
||||
|
||||
// Import the key
|
||||
SECItem keyItem = { siBuffer, nullptr, 0 };
|
||||
ATTEMPT_BUFFER_TO_SECITEM(arena, &keyItem, mSymKey);
|
||||
|
||||
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
|
||||
if (!slot.get()) {
|
||||
return NS_ERROR_DOM_OPERATION_ERR;
|
||||
}
|
||||
|
||||
ScopedPK11SymKey baseKey(PK11_ImportSymKey(slot, mMechanism,
|
||||
PK11_OriginUnwrap, CKA_WRAP,
|
||||
&keyItem, nullptr));
|
||||
if (!baseKey) {
|
||||
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
|
||||
}
|
||||
|
||||
SECItem salt = { siBuffer, nullptr, 0 };
|
||||
SECItem info = { siBuffer, nullptr, 0 };
|
||||
ATTEMPT_BUFFER_TO_SECITEM(arena, &salt, mSalt);
|
||||
ATTEMPT_BUFFER_TO_SECITEM(arena, &info, mInfo);
|
||||
|
||||
CK_NSS_HKDFParams hkdfParams = { true, salt.data, salt.len,
|
||||
true, info.data, info.len };
|
||||
SECItem params = { siBuffer, (unsigned char*)&hkdfParams,
|
||||
sizeof(hkdfParams) };
|
||||
|
||||
// CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
|
||||
// derived symmetric key and don't matter because we ignore them anyway.
|
||||
ScopedPK11SymKey symKey(PK11_Derive(baseKey, mMechanism, ¶ms,
|
||||
CKM_SHA512_HMAC, CKA_SIGN,
|
||||
mLengthInBytes));
|
||||
|
||||
if (!symKey.get()) {
|
||||
return NS_ERROR_DOM_OPERATION_ERR;
|
||||
}
|
||||
|
||||
nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey));
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_ERROR_DOM_OPERATION_ERR;
|
||||
}
|
||||
|
||||
// This doesn't leak, because the SECItem* returned by PK11_GetKeyData
|
||||
// just refers to a buffer managed by symKey. The assignment copies the
|
||||
// data, so mResult manages one copy, while symKey manages another.
|
||||
ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(symKey));
|
||||
|
||||
if (mLengthInBytes > mResult.Length()) {
|
||||
return NS_ERROR_DOM_DATA_ERR;
|
||||
}
|
||||
|
||||
if (!mResult.SetLength(mLengthInBytes, fallible)) {
|
||||
return NS_ERROR_DOM_UNKNOWN_ERR;
|
||||
}
|
||||
|
||||
// If the number of bits to derive is not a multiple of 8 we need to
|
||||
// zero out the remaining bits that were derived but not requested.
|
||||
if (mLengthInBits % 8) {
|
||||
mResult[mResult.Length() - 1] &= 0xff << (mLengthInBits % 8);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
class DerivePbkdfBitsTask : public ReturnArrayBufferViewTask
|
||||
{
|
||||
public:
|
||||
@ -3035,6 +3264,7 @@ WebCryptoTask::CreateSignVerifyTask(JSContext* aCx,
|
||||
if (algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
|
||||
return new HmacTask(aCx, aAlgorithm, aKey, aSignature, aData, aSign);
|
||||
} else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
|
||||
return new AsymmetricSignVerifyTask(aCx, aAlgorithm, aKey, aSignature,
|
||||
aData, aSign);
|
||||
@ -3103,11 +3333,13 @@ WebCryptoTask::CreateImportKeyTask(JSContext* aCx,
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_HKDF) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
|
||||
return new ImportSymmetricKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
|
||||
aExtractable, aKeyUsages);
|
||||
} else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
return new ImportRsaKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
|
||||
aExtractable, aKeyUsages);
|
||||
} else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
|
||||
@ -3153,6 +3385,7 @@ WebCryptoTask::CreateExportKeyTask(const nsAString& aFormat,
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
|
||||
@ -3192,6 +3425,7 @@ WebCryptoTask::CreateGenerateKeyTask(JSContext* aCx,
|
||||
return new GenerateSymmetricKeyTask(aCx, aAlgorithm, aExtractable, aKeyUsages);
|
||||
} else if (algName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
|
||||
algName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP) ||
|
||||
algName.EqualsASCII(WEBCRYPTO_ALG_RSA_PSS) ||
|
||||
algName.EqualsASCII(WEBCRYPTO_ALG_ECDH) ||
|
||||
algName.EqualsASCII(WEBCRYPTO_ALG_ECDSA) ||
|
||||
algName.EqualsASCII(WEBCRYPTO_ALG_DH)) {
|
||||
@ -3227,6 +3461,12 @@ WebCryptoTask::CreateDeriveKeyTask(JSContext* aCx,
|
||||
return new FailureTask(rv);
|
||||
}
|
||||
|
||||
if (algName.EqualsASCII(WEBCRYPTO_ALG_HKDF)) {
|
||||
return new DeriveKeyTask<DeriveHkdfBitsTask>(aCx, aAlgorithm, aBaseKey,
|
||||
aDerivedKeyType, aExtractable,
|
||||
aKeyUsages);
|
||||
}
|
||||
|
||||
if (algName.EqualsASCII(WEBCRYPTO_ALG_PBKDF2)) {
|
||||
return new DeriveKeyTask<DerivePbkdfBitsTask>(aCx, aAlgorithm, aBaseKey,
|
||||
aDerivedKeyType, aExtractable,
|
||||
@ -3273,6 +3513,10 @@ WebCryptoTask::CreateDeriveBitsTask(JSContext* aCx,
|
||||
return new DeriveDhBitsTask(aCx, aAlgorithm, aKey, aLength);
|
||||
}
|
||||
|
||||
if (algName.EqualsASCII(WEBCRYPTO_ALG_HKDF)) {
|
||||
return new DeriveHkdfBitsTask(aCx, aAlgorithm, aKey, aLength);
|
||||
}
|
||||
|
||||
return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
||||
}
|
||||
|
||||
@ -3358,12 +3602,14 @@ WebCryptoTask::CreateUnwrapKeyTask(JSContext* aCx,
|
||||
if (keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
|
||||
keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
|
||||
keyAlgName.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) ||
|
||||
keyAlgName.EqualsASCII(WEBCRYPTO_ALG_HKDF) ||
|
||||
keyAlgName.EqualsASCII(WEBCRYPTO_ALG_HMAC)) {
|
||||
importTask = new ImportSymmetricKeyTask(aCx, aFormat,
|
||||
aUnwrappedKeyAlgorithm,
|
||||
aExtractable, aKeyUsages);
|
||||
} else if (keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
|
||||
keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP)) {
|
||||
keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP) ||
|
||||
keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
importTask = new ImportRsaKeyTask(aCx, aFormat,
|
||||
aUnwrappedKeyAlgorithm,
|
||||
aExtractable, aKeyUsages);
|
||||
|
@ -14,9 +14,11 @@ skip-if = toolkit == 'android' # bug 1200570
|
||||
[test_WebCrypto_DH.html]
|
||||
[test_WebCrypto_ECDH.html]
|
||||
[test_WebCrypto_ECDSA.html]
|
||||
[test_WebCrypto_HKDF.html]
|
||||
[test_WebCrypto_JWK.html]
|
||||
[test_WebCrypto_Normalize.html]
|
||||
[test_WebCrypto_PBKDF2.html]
|
||||
[test_WebCrypto_Reject_Generating_Keys_Without_Usages.html]
|
||||
[test_WebCrypto_RSA_OAEP.html]
|
||||
[test_WebCrypto_RSA_PSS.html]
|
||||
[test_WebCrypto_Wrap_Unwrap.html]
|
||||
|
@ -418,6 +418,182 @@ tv = {
|
||||
),
|
||||
},
|
||||
|
||||
// [pss-vect.txt] Example 1.1 from
|
||||
// <ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip>
|
||||
rsapss: {
|
||||
pkcs8: util.hex2abv(
|
||||
"30820275020100300d06092a864886f70d01010105000482025f3082025b0201" +
|
||||
"0002818100a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1" +
|
||||
"e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ce" +
|
||||
"abfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e" +
|
||||
"6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb22" +
|
||||
"49bd9a2137020301000102818033a5042a90b27d4f5451ca9bbbd0b44771a101" +
|
||||
"af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca" +
|
||||
"0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574" +
|
||||
"501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c06" +
|
||||
"22ad79c6dcee883547c6a3b325024100e7e8942720a877517273a356053ea2a1" +
|
||||
"bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1" +
|
||||
"535bd9b3cc34160b3b6dcd3eda8e6443024100b69dca1cf7d4d7ec81e75b90fc" +
|
||||
"ca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542" +
|
||||
"cd20dc723e6963364a1f9425452b269a6799fd024028fa13938655be1f8a159c" +
|
||||
"baca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8d" +
|
||||
"d3ede2448328f385d81b30e8e43b2fffa02786197902401a8b38f398fa712049" +
|
||||
"898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455e" +
|
||||
"aeb6e1678255827580a8e4e8e14151d1510a82a3f2e729024027156aba4126d2" +
|
||||
"4a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a" +
|
||||
"2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d"
|
||||
),
|
||||
spki: util.hex2abv(
|
||||
"30819f300d06092a864886f70d010101050003818d0030818902818100a56e4a" +
|
||||
"0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c510" +
|
||||
"56ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd95" +
|
||||
"08096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2" +
|
||||
"d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137020301" +
|
||||
"0001"
|
||||
),
|
||||
data: util.hex2abv(
|
||||
"cdc87da223d786df3b45e0bbbc721326d1ee2af806cc315475cc6f0d9c66e1b6" +
|
||||
"2371d45ce2392e1ac92844c310102f156a0d8d52c1f4c40ba3aa65095786cb76" +
|
||||
"9757a6563ba958fed0bcc984e8b517a3d5f515b23b8a41e74aa867693f90dfb0" +
|
||||
"61a6e86dfaaee64472c00e5f20945729cbebe77f06ce78e08f4098fba41f9d61" +
|
||||
"93c0317e8b60d4b6084acb42d29e3808a3bc372d85e331170fcbf7cc72d0b71c" +
|
||||
"296648b3a4d10f416295d0807aa625cab2744fd9ea8fd223c42537029828bd16" +
|
||||
"be02546f130fd2e33b936d2676e08aed1b73318b750a0167d0"
|
||||
),
|
||||
sig: util.hex2abv(
|
||||
"9074308fb598e9701b2294388e52f971faac2b60a5145af185df5287b5ed2887" +
|
||||
"e57ce7fd44dc8634e407c8e0e4360bc226f3ec227f9d9e54638e8d31f5051215" +
|
||||
"df6ebb9c2f9579aa77598a38f914b5b9c1bd83c4e2f9f382a0d0aa3542ffee65" +
|
||||
"984a601bc69eb28deb27dca12c82c2d4c3f66cd500f1ff2b994d8a4e30cbb33c"
|
||||
),
|
||||
saltLength: 20,
|
||||
jwk_priv: {
|
||||
kty: "RSA",
|
||||
n: "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhK" +
|
||||
"oijlN_1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm" +
|
||||
"_4nRnxBazC0_DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc",
|
||||
e: "AQAB",
|
||||
d: "M6UEKpCyfU9UUcqbu9C0R3GhAa-IQ0Cu-YhfKku-kuiUpySsPFaMj5eFOtB8A" +
|
||||
"mbIxqPKCSnx6PESMYhEKfxNmuVf7olqEM5wfD7X5zTkRyejlXRQGlMmgxCcKr" +
|
||||
"rKuig8MbS9L1PD7jfjUs7jT55QO9gMBiKtecbc7og1R8ajsyU",
|
||||
p: "5-iUJyCod1Fyc6NWBT6iobwMlKpy1VxuhilrLfyWeUjApyy8zKfqyzVwbgmh3" +
|
||||
"1WhU1vZs8w0Fgs7bc0-2o5kQw",
|
||||
q: "tp3KHPfU1-yB51uQ_MqHSrzeEj_ScAGAqpBHm25I3o1n7ST58Z2FuidYdPVCz" +
|
||||
"SDccj5pYzZKH5QlRSsmmmeZ_Q",
|
||||
dp: "KPoTk4ZVvh-KFZy6ylpy6hkMMAieGc0nSlVvNsT24Z9VSzTAd3kEJ7vdjdPt" +
|
||||
"4kSDKPOF2Bsw6OQ7L_-gJ4YZeQ",
|
||||
dq: "Gos485j6cSBJiY1_t57gp3ZoeRKZzfoJ78DlB6yyHtdDAe9b_Ui-RV6utuFn" +
|
||||
"glWCdYCo5OjhQVHRUQqCo_LnKQ",
|
||||
qi: "JxVqukEm0kqB86Uoy_sn9WiG-ECp9uhuF6RLlP6TGVhLjiL93h5aLjvYqluo" +
|
||||
"2FhBlOshkKz4MrhH8To9JKefTQ",
|
||||
},
|
||||
jwk_pub: {
|
||||
kty: "RSA",
|
||||
n: "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhK" +
|
||||
"oijlN_1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm" +
|
||||
"_4nRnxBazC0_DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc",
|
||||
e: "AQAB",
|
||||
},
|
||||
},
|
||||
|
||||
// [pss-vect.txt] Example 1.4 from
|
||||
// <ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip>
|
||||
rsapss2: {
|
||||
spki: util.hex2abv(
|
||||
"30819f300d06092a864886f70d010101050003818d0030818902818100a56e4a" +
|
||||
"0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c510" +
|
||||
"56ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd95" +
|
||||
"08096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2" +
|
||||
"d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137020301" +
|
||||
"0001"
|
||||
),
|
||||
data: util.hex2abv(
|
||||
"bc656747fa9eafb3f0"
|
||||
),
|
||||
sig: util.hex2abv(
|
||||
"4609793b23e9d09362dc21bb47da0b4f3a7622649a47d464019b9aeafe53359c" +
|
||||
"178c91cd58ba6bcb78be0346a7bc637f4b873d4bab38ee661f199634c547a1ad" +
|
||||
"8442e03da015b136e543f7ab07c0c13e4225b8de8cce25d4f6eb8400f81f7e18" +
|
||||
"33b7ee6e334d370964ca79fdb872b4d75223b5eeb08101591fb532d155a6de87"
|
||||
),
|
||||
saltLength: 20
|
||||
},
|
||||
|
||||
// [SigVerPSS_186-3.rsp] from
|
||||
// <http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-2rsatestvectors.zip>
|
||||
rsapss3: {
|
||||
spki: util.hex2abv(
|
||||
"30819d300d06092a864886f70d010101050003818b0030818702818100be499b" +
|
||||
"5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b9bfeb7aa72db1264112e" +
|
||||
"b3fd431d31a2a7e50941566929494a0e891ed5613918b4b51b0d1fb97783b26a" +
|
||||
"cf7d0f384cfb35f4d2824f5dd380623a26bf180b63961c619dcdb20cae406f22" +
|
||||
"f6e276c80a37259490cfeb72c1a71a84f1846d330877ba3e3101ec9c7b020111"
|
||||
),
|
||||
data: util.hex2abv(
|
||||
"c7f5270fca725f9bd19f519a8d7cca3cc5c079024029f3bae510f9b02140fe23" +
|
||||
"8908e4f6c18f07a89c687c8684669b1f1db2baf9251a3c829faccb493084e16e" +
|
||||
"c9e28d58868074a5d6221667dd6e528d16fe2c9f3db4cfaf6c4dce8c8439af38" +
|
||||
"ceaaaa9ce2ecae7bc8f4a5a55e3bf96df9cd575c4f9cb327951b8cdfe4087168"
|
||||
),
|
||||
sig: util.hex2abv(
|
||||
"11e169f2fd40b07641b9768a2ab19965fb6c27f10fcf0323fcc6d12eb4f1c06b" +
|
||||
"330ddaa1ea504407afa29de9ebe0374fe9d1e7d0ffbd5fc1cf3a3446e4145415" +
|
||||
"d2ab24f789b3464c5c43a256bbc1d692cf7f04801dac5bb401a4a03ab7d5728a" +
|
||||
"860c19e1a4dc797ca542c8203cec2e601eb0c51f567f2eda022b0b9ebddeeefa"
|
||||
),
|
||||
saltLength: 10
|
||||
},
|
||||
|
||||
// [SigVerPSS_186-3.rsp] from
|
||||
// <http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-2rsatestvectors.zip>
|
||||
rsapss4: {
|
||||
spki: util.hex2abv(
|
||||
"30819d300d06092a864886f70d010101050003818b0030818702818100be499b" +
|
||||
"5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b9bfeb7aa72db1264112e" +
|
||||
"b3fd431d31a2a7e50941566929494a0e891ed5613918b4b51b0d1fb97783b26a" +
|
||||
"cf7d0f384cfb35f4d2824f5dd380623a26bf180b63961c619dcdb20cae406f22" +
|
||||
"f6e276c80a37259490cfeb72c1a71a84f1846d330877ba3e3101ec9c7b020111"
|
||||
),
|
||||
data: util.hex2abv(
|
||||
"c7f5270fca725f9bd19f519a8d7cca3cc5c079024029f3bae510f9b02140fe23" +
|
||||
"8908e4f6c18f07a89c687c8684669b1f1db2baf9251a3c829faccb493084e16e" +
|
||||
"c9e28d58868074a5d6221667dd6e528d16fe2c9f3db4cfaf6c4dce8c8439af38" +
|
||||
"ceaaaa9ce2ecae7bc8f4a5a55e3bf96df9cd575c4f9cb327951b8cdfe4087168"
|
||||
),
|
||||
sig: util.hex2abv(
|
||||
"b281ad934b2775c0cba5fb10aa574d2ed85c7f99b942b78e49702480069362ed" +
|
||||
"394baded55e56cfcbe7b0b8d2217a05a60e1acd725cb09060dfac585bc2132b9" +
|
||||
"9b41cdbd530c69d17cdbc84bc6b9830fc7dc8e1b2412cfe06dcf8c1a0cc3453f" +
|
||||
"93f25ebf10cb0c90334fac573f449138616e1a194c67f44efac34cc07a526267"
|
||||
),
|
||||
saltLength: 10
|
||||
},
|
||||
|
||||
// [SigVerPSS_186-3.rsp] from
|
||||
// <http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-2rsatestvectors.zip>
|
||||
rsapss5: {
|
||||
spki: util.hex2abv(
|
||||
"30819d300d06092a864886f70d010101050003818b0030818702818100be499b" +
|
||||
"5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b9bfeb7aa72db1264112e" +
|
||||
"b3fd431d31a2a7e50941566929494a0e891ed5613918b4b51b0d1fb97783b26a" +
|
||||
"cf7d0f384cfb35f4d2824f5dd380623a26bf180b63961c619dcdb20cae406f22" +
|
||||
"f6e276c80a37259490cfeb72c1a71a84f1846d330877ba3e3101ec9c7b020111"
|
||||
),
|
||||
data: util.hex2abv(
|
||||
"c7f5270fca725f9bd19f519a8d7cca3cc5c079024029f3bae510f9b02140fe23" +
|
||||
"8908e4f6c18f07a89c687c8684669b1f1db2baf9251a3c829faccb493084e16e" +
|
||||
"c9e28d58868074a5d6221667dd6e528d16fe2c9f3db4cfaf6c4dce8c8439af38" +
|
||||
"ceaaaa9ce2ecae7bc8f4a5a55e3bf96df9cd575c4f9cb327951b8cdfe4087168"
|
||||
),
|
||||
sig: util.hex2abv(
|
||||
"8ffc38f9b820ef6b080fd2ec7de5626c658d79056f3edf610a295b7b0546f73e" +
|
||||
"01ffdf4d0070ebf79c33fd86c2d608be9438b3d420d09535b97cd3d846ecaf8f" +
|
||||
"6551cdf93197e9f8fb048044473ab41a801e9f7fc983c62b324361dade9f71a6" +
|
||||
"5952bd35c59faaa4d6ff462f68a6c4ec0b428aa47336f2178aeb276136563b7d"
|
||||
),
|
||||
saltLength: 10
|
||||
},
|
||||
|
||||
key_wrap_known_answer: {
|
||||
key: util.hex2abv("0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a"),
|
||||
wrapping_key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
|
||||
@ -452,7 +628,12 @@ tv = {
|
||||
|
||||
derived: util.hex2abv(
|
||||
"3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"
|
||||
)
|
||||
),
|
||||
|
||||
jwk: {
|
||||
kty: "oct",
|
||||
k: "cGFzc3dvcmRQQVNTV09SRHBhc3N3b3Jk"
|
||||
}
|
||||
},
|
||||
|
||||
// https://stackoverflow.com/questions/5130513/pbkdf2-hmac-sha2-test-vectors
|
||||
@ -753,4 +934,109 @@ tv = {
|
||||
"ba3d9acbf9e8ac"
|
||||
)
|
||||
},
|
||||
|
||||
// Taken from appendix A of RFC 5869.
|
||||
// <https://tools.ietf.org/html/rfc5869>
|
||||
hkdf: [
|
||||
{
|
||||
prf: "SHA-256",
|
||||
key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
|
||||
salt: util.hex2abv("000102030405060708090a0b0c"),
|
||||
info: util.hex2abv("f0f1f2f3f4f5f6f7f8f9"),
|
||||
data: util.hex2abv(
|
||||
"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf" +
|
||||
"34007208d5b887185865"
|
||||
),
|
||||
jwk: {
|
||||
kty: "oct",
|
||||
k: "CwsLCwsLCwsLCwsLCwsLCwsLCwsLCw"
|
||||
}
|
||||
},
|
||||
{
|
||||
prf: "SHA-256",
|
||||
key: util.hex2abv(
|
||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
|
||||
"202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" +
|
||||
"404142434445464748494a4b4c4d4e4f"
|
||||
),
|
||||
salt: util.hex2abv(
|
||||
"606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f" +
|
||||
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f" +
|
||||
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
|
||||
),
|
||||
info: util.hex2abv(
|
||||
"b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf" +
|
||||
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef" +
|
||||
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
|
||||
),
|
||||
data: util.hex2abv(
|
||||
"b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c" +
|
||||
"59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71" +
|
||||
"cc30c58179ec3e87c14c01d5c1f3434f1d87"
|
||||
)
|
||||
},
|
||||
{
|
||||
prf: "SHA-256",
|
||||
key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
|
||||
salt: util.hex2abv(""),
|
||||
info: util.hex2abv(""),
|
||||
data: util.hex2abv(
|
||||
"8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d" +
|
||||
"9d201395faa4b61a96c8"
|
||||
)
|
||||
},
|
||||
{
|
||||
prf: "SHA-1",
|
||||
key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b"),
|
||||
salt: util.hex2abv("000102030405060708090a0b0c"),
|
||||
info: util.hex2abv("f0f1f2f3f4f5f6f7f8f9"),
|
||||
data: util.hex2abv(
|
||||
"085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2" +
|
||||
"c22e422478d305f3f896"
|
||||
)
|
||||
},
|
||||
{
|
||||
prf: "SHA-1",
|
||||
key: util.hex2abv(
|
||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
|
||||
"202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" +
|
||||
"404142434445464748494a4b4c4d4e4f"
|
||||
),
|
||||
salt: util.hex2abv(
|
||||
"606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f" +
|
||||
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f" +
|
||||
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
|
||||
),
|
||||
info: util.hex2abv(
|
||||
"b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf" +
|
||||
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef" +
|
||||
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
|
||||
),
|
||||
data: util.hex2abv(
|
||||
"0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe" +
|
||||
"8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e" +
|
||||
"927336d0441f4c4300e2cff0d0900b52d3b4"
|
||||
)
|
||||
},
|
||||
{
|
||||
prf: "SHA-1",
|
||||
key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
|
||||
salt: util.hex2abv(""),
|
||||
info: util.hex2abv(""),
|
||||
data: util.hex2abv(
|
||||
"0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0" +
|
||||
"ea00033de03984d34918"
|
||||
)
|
||||
},
|
||||
{
|
||||
prf: "SHA-1",
|
||||
key: util.hex2abv("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"),
|
||||
salt: util.hex2abv(""),
|
||||
info: util.hex2abv(""),
|
||||
data: util.hex2abv(
|
||||
"2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5" +
|
||||
"673a081d70cce7acfc48"
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
351
dom/crypto/test/test_WebCrypto_HKDF.html
Normal file
351
dom/crypto/test/test_WebCrypto_HKDF.html
Normal file
@ -0,0 +1,351 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>WebCrypto Test Suite</title>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
<link rel="stylesheet" href="./test_WebCrypto.css"/>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
|
||||
<!-- Utilities for manipulating ABVs -->
|
||||
<script src="util.js"></script>
|
||||
|
||||
<!-- A simple wrapper around IndexedDB -->
|
||||
<script src="simpledb.js"></script>
|
||||
|
||||
<!-- Test vectors drawn from the literature -->
|
||||
<script src="./test-vectors.js"></script>
|
||||
|
||||
<!-- General testing framework -->
|
||||
<script src="./test-array.js"></script>
|
||||
|
||||
<script>/*<![CDATA[*/
|
||||
"use strict";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Deriving zero bits should fail",
|
||||
function() {
|
||||
var that = this;
|
||||
var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
|
||||
|
||||
var alg = {
|
||||
name: "HKDF",
|
||||
hash: "SHA-256",
|
||||
salt: new Uint8Array(),
|
||||
info: new Uint8Array()
|
||||
};
|
||||
|
||||
crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
|
||||
.then(x => crypto.subtle.deriveBits(alg, x, 0), error(that))
|
||||
.then(error(that), complete(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Derive four bits with HKDF, no salt or info given",
|
||||
function() {
|
||||
var that = this;
|
||||
var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
|
||||
|
||||
var alg = {
|
||||
name: "HKDF",
|
||||
hash: "SHA-256",
|
||||
salt: new Uint8Array(),
|
||||
info: new Uint8Array()
|
||||
};
|
||||
|
||||
crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
|
||||
.then(x => crypto.subtle.deriveBits(alg, x, 4))
|
||||
// The last 4 bits should be zeroes (1000 1101 => 1000 0000).
|
||||
.then(memcmp_complete(that, new Uint8Array([0x80])))
|
||||
.catch(error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Deriving too many bits should fail",
|
||||
function() {
|
||||
var that = this;
|
||||
var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
|
||||
|
||||
var alg = {
|
||||
name: "HKDF",
|
||||
hash: "SHA-256",
|
||||
salt: new Uint8Array(),
|
||||
info: new Uint8Array()
|
||||
};
|
||||
|
||||
function deriveBits(x) {
|
||||
// The maximum length (in bytes) of output material for HKDF is 255 times
|
||||
// the digest length. In this case, the digest length (in bytes) of
|
||||
// SHA-256 is 32; 32*255 = 8160. deriveBits expects the length to be in
|
||||
// bits, so 8160*8=65280 and add 1 to exceed the maximum length.
|
||||
return crypto.subtle.deriveBits(alg, x, 65281);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
|
||||
.then(deriveBits, error(that))
|
||||
.then(error(that), complete(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Deriving with an unsupported PRF should fail",
|
||||
function() {
|
||||
var that = this;
|
||||
var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
|
||||
|
||||
var alg = {
|
||||
name: "HKDF",
|
||||
hash: "HMAC",
|
||||
salt: new Uint8Array(),
|
||||
info: new Uint8Array()
|
||||
};
|
||||
|
||||
function deriveBits(x) {
|
||||
return crypto.subtle.deriveBits(alg, x, 4);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
|
||||
.then(deriveBits, error(that))
|
||||
.then(error(that), complete(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Deriving with a non-HKDF key should fail",
|
||||
function() {
|
||||
var that = this;
|
||||
|
||||
var alg = {
|
||||
name: "HKDF",
|
||||
hash: "HMAC",
|
||||
salt: new Uint8Array(),
|
||||
info: new Uint8Array()
|
||||
};
|
||||
|
||||
function deriveBits(x) {
|
||||
return crypto.subtle.deriveBits(alg, x, 4);
|
||||
}
|
||||
|
||||
var ecAlg = {name: "ECDH", namedCurve: "P-256"};
|
||||
crypto.subtle.generateKey(ecAlg, false, ["deriveBits"])
|
||||
.then(deriveBits, error(that))
|
||||
.then(error(that), complete(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Derive known values from test vectors (SHA-1 and SHA-256)",
|
||||
function() {
|
||||
var that = this;
|
||||
var tests = tv.hkdf.slice();
|
||||
|
||||
function next() {
|
||||
if (!tests.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var test = tests.shift();
|
||||
var {key, data} = test;
|
||||
|
||||
return crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveBits"])
|
||||
.then(function (key) {
|
||||
return crypto.subtle.deriveBits({
|
||||
name: "HKDF",
|
||||
hash: test.prf,
|
||||
salt: test.salt,
|
||||
info: test.info
|
||||
}, key, test.data.byteLength * 8);
|
||||
})
|
||||
.then(function (data) {
|
||||
if (!util.memcmp(data, test.data)) {
|
||||
throw new Error("derived bits don't match expected value");
|
||||
}
|
||||
|
||||
// Next test vector.
|
||||
return next();
|
||||
});
|
||||
}
|
||||
|
||||
next().then(complete(that), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Derive known values from test vectors (JWK, SHA-256)",
|
||||
function() {
|
||||
var that = this;
|
||||
var test = tv.hkdf[0];
|
||||
var alg = {
|
||||
name: "HKDF",
|
||||
hash: test.prf,
|
||||
salt: test.salt,
|
||||
info: test.info
|
||||
};
|
||||
|
||||
crypto.subtle.importKey("jwk", test.jwk, "HKDF", false, ["deriveBits"])
|
||||
.then(x => crypto.subtle.deriveBits(alg, x, test.data.byteLength * 8))
|
||||
.then(memcmp_complete(that, test.data), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Test wrapping/unwrapping an HKDF key",
|
||||
function() {
|
||||
var that = this;
|
||||
var hkdfKey = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
|
||||
var alg = {name: "AES-GCM", length: 256, iv: new Uint8Array(16)};
|
||||
var wrappingKey;
|
||||
|
||||
function wrap(x) {
|
||||
wrappingKey = x;
|
||||
return crypto.subtle.encrypt(alg, wrappingKey, hkdfKey);
|
||||
}
|
||||
|
||||
function unwrap(wrappedKey) {
|
||||
return crypto.subtle.unwrapKey(
|
||||
"raw", wrappedKey, wrappingKey, alg, "HKDF", false, ["deriveBits"])
|
||||
.then(rawKey => {
|
||||
return crypto.subtle.deriveBits({
|
||||
name: "HKDF",
|
||||
hash: "SHA-256",
|
||||
salt: new Uint8Array(),
|
||||
info: new Uint8Array()
|
||||
}, rawKey, 4);
|
||||
})
|
||||
.then(derivedBits => {
|
||||
if (!util.memcmp(derivedBits, new Uint8Array([0x80]))) {
|
||||
throw new Error("deriving bits failed");
|
||||
}
|
||||
|
||||
// Forward to reuse.
|
||||
return wrappedKey;
|
||||
});
|
||||
}
|
||||
|
||||
crypto.subtle.generateKey(alg, false, ["encrypt", "unwrapKey"])
|
||||
.then(wrap)
|
||||
.then(unwrap)
|
||||
.then(complete(that), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Unwrapping an HKDF key in PKCS8 format should fail",
|
||||
function() {
|
||||
var that = this;
|
||||
var hkdfKey = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
|
||||
var alg = {name: "AES-GCM", length: 256, iv: new Uint8Array(16)};
|
||||
var wrappingKey;
|
||||
|
||||
function wrap(x) {
|
||||
wrappingKey = x;
|
||||
return crypto.subtle.encrypt(alg, wrappingKey, hkdfKey);
|
||||
}
|
||||
|
||||
function unwrap(x) {
|
||||
return crypto.subtle.unwrapKey(
|
||||
"pkcs8", x, wrappingKey, alg, "HKDF", false, ["deriveBits"]);
|
||||
}
|
||||
|
||||
crypto.subtle.generateKey(alg, false, ["encrypt", "unwrapKey"])
|
||||
.then(wrap, error(that))
|
||||
.then(unwrap, error(that))
|
||||
.then(error(that), complete(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Derive an AES key using with HKDF",
|
||||
function() {
|
||||
var that = this;
|
||||
var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
|
||||
|
||||
var alg = {
|
||||
name: "HKDF",
|
||||
hash: "SHA-256",
|
||||
salt: new Uint8Array(),
|
||||
info: new Uint8Array()
|
||||
};
|
||||
|
||||
function deriveKey(x) {
|
||||
var targetAlg = {name: "AES-GCM", length: 256};
|
||||
return crypto.subtle.deriveKey(alg, x, targetAlg, false, ["encrypt"]);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveKey"])
|
||||
.then(deriveKey)
|
||||
.then(complete(that), error(that))
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Deriving an HKDF key with HKDF should fail",
|
||||
function() {
|
||||
var that = this;
|
||||
var key = util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
|
||||
|
||||
var alg = {
|
||||
name: "HKDF",
|
||||
hash: "SHA-256",
|
||||
salt: new Uint8Array(),
|
||||
info: new Uint8Array()
|
||||
};
|
||||
|
||||
function deriveKey(x) {
|
||||
return crypto.subtle.deriveKey(alg, x, "HKDF", false, ["deriveBits"]);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("raw", key, "HKDF", false, ["deriveKey"])
|
||||
.then(deriveKey)
|
||||
.then(error(that), complete(that))
|
||||
}
|
||||
);
|
||||
|
||||
/*]]>*/</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="content">
|
||||
<div id="head">
|
||||
<b>Web</b>Crypto<br>
|
||||
</div>
|
||||
|
||||
<div id="start" onclick="start();">RUN ALL</div>
|
||||
|
||||
<div id="resultDiv" class="content">
|
||||
Summary:
|
||||
<span class="pass"><span id="passN">0</span> passed, </span>
|
||||
<span class="fail"><span id="failN">0</span> failed, </span>
|
||||
<span class="pending"><span id="pendingN">0</span> pending.</span>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<table id="results">
|
||||
<tr>
|
||||
<th>Test</th>
|
||||
<th>Result</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="foot"></div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -37,6 +37,32 @@ TestArray.addTest(
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Unwrapping a PBKDF2 key in PKCS8 format should fail",
|
||||
function() {
|
||||
var that = this;
|
||||
var pbkdf2Key = new TextEncoder("utf-8").encode("password");
|
||||
var alg = {name: "AES-GCM", length: 256, iv: new Uint8Array(16)};
|
||||
var wrappingKey;
|
||||
|
||||
function wrap(x) {
|
||||
wrappingKey = x;
|
||||
return crypto.subtle.encrypt(alg, wrappingKey, pbkdf2Key);
|
||||
}
|
||||
|
||||
function unwrap(x) {
|
||||
return crypto.subtle.unwrapKey(
|
||||
"pkcs8", x, wrappingKey, alg, "PBKDF2", false, ["deriveBits"]);
|
||||
}
|
||||
|
||||
crypto.subtle.generateKey(alg, false, ["encrypt", "unwrapKey"])
|
||||
.then(wrap, error(that))
|
||||
.then(unwrap, error(that))
|
||||
.then(error(that), complete(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Import raw PBKDF2 key and derive bits using HMAC-SHA-1",
|
||||
@ -66,6 +92,34 @@ TestArray.addTest(
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Import a PBKDF2 key in JWK format and derive bits using HMAC-SHA-1",
|
||||
function() {
|
||||
var that = this;
|
||||
var alg = "PBKDF2";
|
||||
|
||||
function doDerive(x) {
|
||||
if (!hasKeyFields(x)) {
|
||||
throw "Invalid key; missing field(s)";
|
||||
}
|
||||
|
||||
var alg = {
|
||||
name: "PBKDF2",
|
||||
hash: "SHA-1",
|
||||
salt: tv.pbkdf2_sha1.salt,
|
||||
iterations: tv.pbkdf2_sha1.iterations
|
||||
};
|
||||
return crypto.subtle.deriveBits(alg, x, tv.pbkdf2_sha1.length);
|
||||
}
|
||||
function fail(x) { console.log("failing"); error(that)(x); }
|
||||
|
||||
crypto.subtle.importKey("jwk", tv.pbkdf2_sha1.jwk, alg, false, ["deriveBits"])
|
||||
.then( doDerive, fail )
|
||||
.then( memcmp_complete(that, tv.pbkdf2_sha1.derived), fail );
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Import raw PBKDF2 key and derive a new key using HMAC-SHA-1",
|
||||
|
404
dom/crypto/test/test_WebCrypto_RSA_PSS.html
Normal file
404
dom/crypto/test/test_WebCrypto_RSA_PSS.html
Normal file
@ -0,0 +1,404 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>WebCrypto Test Suite</title>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
<link rel="stylesheet" href="./test_WebCrypto.css"/>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
|
||||
<!-- Utilities for manipulating ABVs -->
|
||||
<script src="util.js"></script>
|
||||
|
||||
<!-- A simple wrapper around IndexedDB -->
|
||||
<script src="simpledb.js"></script>
|
||||
|
||||
<!-- Test vectors drawn from the literature -->
|
||||
<script src="./test-vectors.js"></script>
|
||||
|
||||
<!-- General testing framework -->
|
||||
<script src="./test-array.js"></script>
|
||||
|
||||
<script>/*<![CDATA[*/
|
||||
"use strict";
|
||||
|
||||
// Generating 2048-bit keys takes some time.
|
||||
SimpleTest.requestLongerTimeout(2);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS key generation (SHA-1, 1024-bit)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {
|
||||
name: "RSA-PSS",
|
||||
hash: "SHA-1",
|
||||
modulusLength: 1024,
|
||||
publicExponent: new Uint8Array([0x01, 0x00, 0x01])
|
||||
};
|
||||
|
||||
crypto.subtle.generateKey(alg, false, ["sign", "verify"])
|
||||
.then(complete(that), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS key generation and sign/verify round-trip (SHA-256, 2048-bit)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {
|
||||
name: "RSA-PSS",
|
||||
hash: "SHA-256",
|
||||
modulusLength: 2048,
|
||||
publicExponent: new Uint8Array([0x01, 0x00, 0x01])
|
||||
};
|
||||
|
||||
var privKey, pubKey;
|
||||
var data = crypto.getRandomValues(new Uint8Array(128));
|
||||
function setKey(x) { pubKey = x.publicKey; privKey = x.privateKey; }
|
||||
function doSign() {
|
||||
var alg = {name: "RSA-PSS", saltLength: 32};
|
||||
return crypto.subtle.sign(alg, privKey, data);
|
||||
}
|
||||
function doVerify(x) {
|
||||
var alg = {name: "RSA-PSS", saltLength: 32};
|
||||
return crypto.subtle.verify(alg, pubKey, x, data);
|
||||
}
|
||||
|
||||
crypto.subtle.generateKey(alg, false, ["sign", "verify"])
|
||||
.then(setKey, error(that))
|
||||
.then(doSign, error(that))
|
||||
.then(doVerify, error(that))
|
||||
.then(complete(that, x => x), error(that))
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS verify known signature (SHA-1, 1024-bit)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
var vec = tv.rsapss;
|
||||
|
||||
function doVerify(x) {
|
||||
var alg = {name: "RSA-PSS", saltLength: vec.saltLength};
|
||||
return crypto.subtle.verify(alg, x, vec.sig, vec.data);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("spki", vec.spki, alg, false, ["verify"])
|
||||
.then(doVerify, error(that))
|
||||
.then(complete(that, x => x), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Test invalid RSA-PSS signatures",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
var vec = tv.rsapss;
|
||||
|
||||
function doVerify(x) {
|
||||
var alg = {name: "RSA-PSS", saltLength: vec.saltLength};
|
||||
var clone1 = new Uint8Array(vec.data);
|
||||
var clone2 = new Uint8Array(vec.data);
|
||||
clone1[clone1.byteLength - 1] ^= 1;
|
||||
clone2[0] ^= 1;
|
||||
|
||||
return Promise.all([
|
||||
crypto.subtle.verify(alg, x, vec.sig, clone1),
|
||||
crypto.subtle.verify(alg, x, vec.sig, clone2),
|
||||
crypto.subtle.verify(alg, x, vec.sig, vec.data.slice(1)),
|
||||
crypto.subtle.verify(alg, x, vec.sig, vec.data.slice(0, vec.data.byteLength - 1)),
|
||||
]);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("spki", vec.spki, alg, false, ["verify"])
|
||||
.then(doVerify, error(that))
|
||||
.then(results => results.every(x => !x))
|
||||
.then(complete(that, x => x), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS verify known signature (SHA-1, 1024-bit, JWK)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
|
||||
function doVerify(x) {
|
||||
var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
|
||||
return crypto.subtle.verify(alg, x, tv.rsapss.sig, tv.rsapss.data);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("jwk", tv.rsapss.jwk_pub, alg, false, ["verify"])
|
||||
.then(doVerify, error(that))
|
||||
.then(complete(that, x => x), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS verify known signatures (SHA-1 to SHA-512, 1024-bit)",
|
||||
function () {
|
||||
var that = this;
|
||||
|
||||
function verifyCase(hash, tv) {
|
||||
var alg = {name: "RSA-PSS", hash, saltLength: tv.saltLength};
|
||||
return crypto.subtle.importKey("spki", tv.spki, alg, false, ["verify"])
|
||||
.then(x => crypto.subtle.verify(alg, x, tv.sig, tv.data));
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
verifyCase("SHA-1", tv.rsapss2),
|
||||
verifyCase("SHA-256", tv.rsapss3),
|
||||
verifyCase("SHA-384", tv.rsapss4),
|
||||
verifyCase("SHA-512", tv.rsapss5),
|
||||
]).then(complete(that, x => x.every(y => y)), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS import SPKI/PKCS#8 keys and sign/verify (SHA-1, 1024-bit)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
|
||||
var privKey, pubKey;
|
||||
function setKeys([pub, priv]) { pubKey = pub; privKey = priv; }
|
||||
function doSign() {
|
||||
var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
|
||||
return crypto.subtle.sign(alg, privKey, tv.rsapss.data);
|
||||
}
|
||||
function doVerify(x) {
|
||||
var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
|
||||
return crypto.subtle.verify(alg, pubKey, x, tv.rsapss.data);
|
||||
}
|
||||
|
||||
var spki =
|
||||
crypto.subtle.importKey("spki", tv.rsapss.spki, alg, false, ["verify"]);
|
||||
var pkcs8 =
|
||||
crypto.subtle.importKey("pkcs8", tv.rsapss.pkcs8, alg, false, ["sign"]);
|
||||
|
||||
Promise.all([spki, pkcs8])
|
||||
.then(setKeys, error(that))
|
||||
.then(doSign, error(that))
|
||||
.then(doVerify, error(that))
|
||||
.then(complete(that, x => x), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS import JWK keys and sign/verify (SHA-1, 1024-bit)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
|
||||
var privKey, pubKey;
|
||||
function setKeys([pub, priv]) { pubKey = pub; privKey = priv; }
|
||||
function doSign() {
|
||||
var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
|
||||
return crypto.subtle.sign(alg, privKey, tv.rsapss.data);
|
||||
}
|
||||
function doVerify(x) {
|
||||
var alg = {name: "RSA-PSS", saltLength: tv.rsapss.saltLength};
|
||||
return crypto.subtle.verify(alg, pubKey, x, tv.rsapss.data);
|
||||
}
|
||||
|
||||
var spki =
|
||||
crypto.subtle.importKey("jwk", tv.rsapss.jwk_pub, alg, false, ["verify"]);
|
||||
var pkcs8 =
|
||||
crypto.subtle.importKey("jwk", tv.rsapss.jwk_priv, alg, false, ["sign"]);
|
||||
|
||||
Promise.all([spki, pkcs8])
|
||||
.then(setKeys, error(that))
|
||||
.then(doSign, error(that))
|
||||
.then(doVerify, error(that))
|
||||
.then(complete(that, x => x), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS SPKI import/export (SHA-1, 1024-bit)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
|
||||
function doExport(x) {
|
||||
return crypto.subtle.exportKey("spki", x);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("spki", tv.rsapss.spki, alg, true, ["verify"])
|
||||
.then(doExport, error(that))
|
||||
.then(memcmp_complete(that, tv.rsapss.spki), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS PKCS#8 import/export (SHA-1, 1024-bit)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
|
||||
function doExport(x) {
|
||||
return crypto.subtle.exportKey("pkcs8", x);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("pkcs8", tv.rsapss.pkcs8, alg, true, ["sign"])
|
||||
.then(doExport, error(that))
|
||||
.then(memcmp_complete(that, tv.rsapss.pkcs8), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS JWK export a public key",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
var jwk = tv.rsapss.jwk_pub;
|
||||
|
||||
function doExport(x) {
|
||||
return crypto.subtle.exportKey("jwk", x);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("jwk", jwk, alg, true, ["verify"])
|
||||
.then(doExport)
|
||||
.then(
|
||||
complete(that, function(x) {
|
||||
return hasBaseJwkFields(x) &&
|
||||
hasFields(x, ["n", "e"]) &&
|
||||
x.kty == "RSA" &&
|
||||
x.alg == "PS1" &&
|
||||
x.ext &&
|
||||
shallowArrayEquals(x.key_ops, ["verify"]) &&
|
||||
x.n == jwk.n &&
|
||||
x.e == jwk.e;
|
||||
}),
|
||||
error(that)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS JWK export a private key",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
var jwk = tv.rsapss.jwk_priv;
|
||||
|
||||
function doExport(x) {
|
||||
return crypto.subtle.exportKey("jwk", x);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("jwk", jwk, alg, true, ["sign"])
|
||||
.then(doExport)
|
||||
.then(
|
||||
complete(that, function(x) {
|
||||
return hasBaseJwkFields(x) &&
|
||||
hasFields(x, ["n", "e", "d", "p", "q", "dp", "dq", "qi"]) &&
|
||||
x.kty == "RSA" &&
|
||||
x.alg == "PS1" &&
|
||||
x.ext &&
|
||||
shallowArrayEquals(x.key_ops, ["sign"]) &&
|
||||
x.n == jwk.n &&
|
||||
x.e == jwk.e &&
|
||||
x.d == jwk.d &&
|
||||
x.p == jwk.p &&
|
||||
x.q == jwk.q &&
|
||||
x.dp == jwk.dp &&
|
||||
x.dq == jwk.dq &&
|
||||
x.qi == jwk.qi;
|
||||
}),
|
||||
error(that)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"Deterministic RSA-PSS signatures with saltLength=0 (SHA-256, 2048-bit)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {
|
||||
name: "RSA-PSS",
|
||||
hash: "SHA-256",
|
||||
modulusLength: 2048,
|
||||
publicExponent: new Uint8Array([0x01, 0x00, 0x01])
|
||||
};
|
||||
|
||||
var privKey, pubKey;
|
||||
var data = crypto.getRandomValues(new Uint8Array(128));
|
||||
function setKey(x) { pubKey = x.publicKey; privKey = x.privateKey; }
|
||||
|
||||
function doSignTwice() {
|
||||
var alg = {name: "RSA-PSS", saltLength: 0};
|
||||
return Promise.all([
|
||||
crypto.subtle.sign(alg, privKey, data),
|
||||
crypto.subtle.sign(alg, privKey, data)
|
||||
]);
|
||||
}
|
||||
|
||||
function doVerify(x) {
|
||||
var alg = {name: "RSA-PSS", saltLength: 0};
|
||||
return crypto.subtle.verify(alg, pubKey, x, data);
|
||||
}
|
||||
|
||||
crypto.subtle.generateKey(alg, false, ["sign", "verify"])
|
||||
.then(setKey, error(that))
|
||||
.then(doSignTwice, error(that))
|
||||
.then(([sig1, sig2]) => {
|
||||
if (!util.memcmp(sig1, sig2)) {
|
||||
throw new Error("sig1 must be equal to sig2");
|
||||
}
|
||||
|
||||
return sig1;
|
||||
}, error(that))
|
||||
.then(doVerify, error(that))
|
||||
.then(complete(that, x => x), error(that))
|
||||
}
|
||||
);
|
||||
/*]]>*/</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="content">
|
||||
<div id="head">
|
||||
<b>Web</b>Crypto<br>
|
||||
</div>
|
||||
|
||||
<div id="start" onclick="start();">RUN ALL</div>
|
||||
|
||||
<div id="resultDiv" class="content">
|
||||
Summary:
|
||||
<span class="pass"><span id="passN">0</span> passed, </span>
|
||||
<span class="fail"><span id="failN">0</span> failed, </span>
|
||||
<span class="pending"><span id="pendingN">0</span> pending.</span>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<table id="results">
|
||||
<tr>
|
||||
<th>Test</th>
|
||||
<th>Result</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="foot"></div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -21,7 +21,7 @@ var util = {
|
||||
|
||||
// Convert an ArrayBufferView to a hex string
|
||||
abv2hex: function util_abv2hex(abv) {
|
||||
var b = new Uint8Array(abv.buffer, abv.byteOffset, abv.byteLength);
|
||||
var b = new Uint8Array(abv);
|
||||
var hex = "";
|
||||
for (var i=0; i <b.length; ++i) {
|
||||
var zeropad = (b[i] < 0x10) ? "0" : "";
|
||||
|
@ -84,11 +84,9 @@ NotifyPaintEvent::ClientRects()
|
||||
RefPtr<DOMRectList> rectList = new DOMRectList(parent);
|
||||
|
||||
nsRegion r = GetRegion();
|
||||
nsRegionRectIterator iter(r);
|
||||
for (const nsRect* rgnRect = iter.Next(); rgnRect; rgnRect = iter.Next()) {
|
||||
for (auto iter = r.RectIter(); !iter.Done(); iter.Next()) {
|
||||
RefPtr<DOMRect> rect = new DOMRect(parent);
|
||||
|
||||
rect->SetLayoutRect(*rgnRect);
|
||||
rect->SetLayoutRect(iter.Get());
|
||||
rectList->Append(rect);
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,6 @@ function testSet() {
|
||||
|
||||
function testFilename() {
|
||||
var f = new FormData();
|
||||
// Spec says if a Blob (which is not a File) is added, the name parameter is set to "blob".
|
||||
f.append("blob", new Blob(["hi"]));
|
||||
ok(f.get("blob") instanceof Blob, "We should have a blob back.");
|
||||
|
||||
@ -163,7 +162,7 @@ function testSend(doneCb) {
|
||||
}
|
||||
|
||||
is(response[0].headers['Content-Disposition'],
|
||||
'form-data; name="empty"; filename="blob"');
|
||||
'form-data; name="empty"; filename=""');
|
||||
|
||||
is(response[1].headers['Content-Disposition'],
|
||||
'form-data; name="explicit"; filename="explicit-file-name"');
|
||||
|
@ -590,7 +590,7 @@ var expectedAugment = [
|
||||
//{ name: "aNameUndef", value: "undefined" },
|
||||
];
|
||||
|
||||
function checkMPSubmission(sub, expected, test, isFormData = false) {
|
||||
function checkMPSubmission(sub, expected, test) {
|
||||
function getPropCount(o) {
|
||||
var x, l = 0;
|
||||
for (x in o) ++l;
|
||||
@ -625,7 +625,7 @@ function checkMPSubmission(sub, expected, test, isFormData = false) {
|
||||
else {
|
||||
is(sub[i].headers["Content-Disposition"],
|
||||
"form-data; name=\"" + mpquote(expected[i].name) + "\"; filename=\"" +
|
||||
mpquote(expected[i].fileName != "" || !isFormData ? expected[i].fileName : "blob") + "\"",
|
||||
mpquote(expected[i].fileName) + "\"",
|
||||
"Correct name in " + test);
|
||||
is(sub[i].headers["Content-Type"],
|
||||
expected[i].contentType,
|
||||
@ -782,14 +782,14 @@ function runTest() {
|
||||
xhr.open("POST", "form_submit_server.sjs");
|
||||
xhr.send(new FormData(form));
|
||||
yield undefined; // Wait for XHR load
|
||||
checkMPSubmission(JSON.parse(xhr.responseText), expectedSub, "send form using XHR and FormData", true);
|
||||
checkMPSubmission(JSON.parse(xhr.responseText), expectedSub, "send form using XHR and FormData");
|
||||
|
||||
// Send disabled form using XHR and FormData
|
||||
setDisabled(document.querySelectorAll("input, select, textarea"), true);
|
||||
xhr.open("POST", "form_submit_server.sjs");
|
||||
xhr.send(new FormData(form));
|
||||
yield undefined;
|
||||
checkMPSubmission(JSON.parse(xhr.responseText), [], "send disabled form using XHR and FormData", true);
|
||||
checkMPSubmission(JSON.parse(xhr.responseText), [], "send disabled form using XHR and FormData");
|
||||
setDisabled(document.querySelectorAll("input, select, textarea"), false);
|
||||
|
||||
// Send FormData
|
||||
@ -804,7 +804,7 @@ function runTest() {
|
||||
xhr.open("POST", "form_submit_server.sjs");
|
||||
xhr.send(fd);
|
||||
yield undefined;
|
||||
checkMPSubmission(JSON.parse(xhr.responseText), expectedAugment, "send FormData", true);
|
||||
checkMPSubmission(JSON.parse(xhr.responseText), expectedAugment, "send FormData");
|
||||
|
||||
// Augment <form> using FormData
|
||||
fd = new FormData(form);
|
||||
@ -813,7 +813,7 @@ function runTest() {
|
||||
xhr.send(fd);
|
||||
yield undefined;
|
||||
checkMPSubmission(JSON.parse(xhr.responseText),
|
||||
expectedSub.concat(expectedAugment), "send augmented FormData", true);
|
||||
expectedSub.concat(expectedAugment), "send augmented FormData");
|
||||
|
||||
SimpleTest.finish();
|
||||
yield undefined;
|
||||
|
@ -650,14 +650,14 @@ nsDOMIdentity.prototype = {
|
||||
*/
|
||||
// nsIObserver
|
||||
observe: function nsDOMIdentityInternal_observe(aSubject, aTopic, aData) {
|
||||
let window = aSubject.QueryInterface(Ci.nsIDOMWindow);
|
||||
if (window != this._window) {
|
||||
let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
||||
if (wId != this._innerWindowID) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uninit();
|
||||
|
||||
Services.obs.removeObserver(this, "dom-window-destroyed");
|
||||
Services.obs.removeObserver(this, "inner-window-destroyed");
|
||||
this._initializeState();
|
||||
|
||||
// TODO: Also send message to DOMIdentity notifiying window is no longer valid
|
||||
@ -729,7 +729,7 @@ nsDOMIdentity.prototype = {
|
||||
}, this);
|
||||
|
||||
// Setup observers so we can remove message listeners.
|
||||
Services.obs.addObserver(this, "dom-window-destroyed", false);
|
||||
Services.obs.addObserver(this, "inner-window-destroyed", false);
|
||||
},
|
||||
|
||||
uninit: function DOMIdentity_uninit() {
|
||||
|
@ -1052,13 +1052,6 @@ DOMAudioNodeMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow,
|
||||
|
||||
DOMHwMediaStream::DOMHwMediaStream()
|
||||
{
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
mImageContainer = LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS);
|
||||
mOverlayImage = mImageContainer->CreateOverlayImage();
|
||||
nsAutoTArray<ImageContainer::NonOwningImage,1> images;
|
||||
images.AppendElement(ImageContainer::NonOwningImage(mOverlayImage));
|
||||
mImageContainer->SetCurrentImages(images);
|
||||
#endif
|
||||
}
|
||||
|
||||
DOMHwMediaStream::~DOMHwMediaStream()
|
||||
@ -1066,7 +1059,7 @@ DOMHwMediaStream::~DOMHwMediaStream()
|
||||
}
|
||||
|
||||
already_AddRefed<DOMHwMediaStream>
|
||||
DOMHwMediaStream::CreateHwStream(nsIDOMWindow* aWindow)
|
||||
DOMHwMediaStream::CreateHwStream(nsIDOMWindow* aWindow, OverlayImage* aImage)
|
||||
{
|
||||
RefPtr<DOMHwMediaStream> stream = new DOMHwMediaStream();
|
||||
|
||||
@ -1074,25 +1067,35 @@ DOMHwMediaStream::CreateHwStream(nsIDOMWindow* aWindow)
|
||||
MediaStreamGraph::GetInstance(MediaStreamGraph::SYSTEM_THREAD_DRIVER,
|
||||
AudioChannel::Normal);
|
||||
stream->InitSourceStream(aWindow, graph);
|
||||
stream->Init(stream->GetInputStream());
|
||||
stream->Init(stream->GetInputStream(), aImage);
|
||||
|
||||
return stream.forget();
|
||||
}
|
||||
|
||||
void
|
||||
DOMHwMediaStream::Init(MediaStream* stream)
|
||||
DOMHwMediaStream::Init(MediaStream* stream, OverlayImage* aImage)
|
||||
{
|
||||
SourceMediaStream* srcStream = stream->AsSourceStream();
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
if (aImage) {
|
||||
mOverlayImage = aImage;
|
||||
} else {
|
||||
Data imageData;
|
||||
imageData.mOverlayId = DEFAULT_IMAGE_ID;
|
||||
imageData.mSize.width = DEFAULT_IMAGE_WIDTH;
|
||||
imageData.mSize.height = DEFAULT_IMAGE_HEIGHT;
|
||||
|
||||
mOverlayImage = new OverlayImage();
|
||||
mOverlayImage->SetData(imageData);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (srcStream) {
|
||||
VideoSegment segment;
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
const StreamTime delta = STREAM_TIME_MAX; // Because MediaStreamGraph will run out frames in non-autoplay mode,
|
||||
// we must give it bigger frame length to cover this situation.
|
||||
mImageData.mOverlayId = DEFAULT_IMAGE_ID;
|
||||
mImageData.mSize.width = DEFAULT_IMAGE_WIDTH;
|
||||
mImageData.mSize.height = DEFAULT_IMAGE_HEIGHT;
|
||||
mOverlayImage->SetData(mImageData);
|
||||
|
||||
RefPtr<Image> image = static_cast<Image*>(mOverlayImage.get());
|
||||
mozilla::gfx::IntSize size = image->GetSize();
|
||||
@ -1120,11 +1123,17 @@ void
|
||||
DOMHwMediaStream::SetImageSize(uint32_t width, uint32_t height)
|
||||
{
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
OverlayImage::Data imgData;
|
||||
|
||||
imgData.mOverlayId = mOverlayImage->GetOverlayId();
|
||||
imgData.mSize = IntSize(width, height);
|
||||
mOverlayImage->SetData(imgData);
|
||||
if (mOverlayImage->GetSidebandStream().IsValid()) {
|
||||
OverlayImage::SidebandStreamData imgData;
|
||||
imgData.mStream = mOverlayImage->GetSidebandStream();
|
||||
imgData.mSize = IntSize(width, height);
|
||||
mOverlayImage->SetData(imgData);
|
||||
} else {
|
||||
OverlayImage::Data imgData;
|
||||
imgData.mOverlayId = mOverlayImage->GetOverlayId();
|
||||
imgData.mSize = IntSize(width, height);
|
||||
mOverlayImage->SetData(imgData);
|
||||
}
|
||||
#endif
|
||||
|
||||
SourceMediaStream* srcStream = GetInputStream()->AsSourceStream();
|
||||
@ -1152,6 +1161,43 @@ DOMHwMediaStream::SetImageSize(uint32_t width, uint32_t height)
|
||||
srcStream->AppendToTrack(TRACK_VIDEO_PRIMARY, &segment);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
DOMHwMediaStream::SetOverlayImage(OverlayImage* aImage)
|
||||
{
|
||||
if (!aImage) {
|
||||
return;
|
||||
}
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
mOverlayImage = aImage;
|
||||
#endif
|
||||
|
||||
SourceMediaStream* srcStream = GetInputStream()->AsSourceStream();
|
||||
StreamBuffer::Track* track = srcStream->FindTrack(TRACK_VIDEO_PRIMARY);
|
||||
|
||||
if (!track || !track->GetSegment()) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
// Clear the old segment.
|
||||
// Changing the existing content of segment is a Very BAD thing, and this way will
|
||||
// confuse consumers of MediaStreams.
|
||||
// It is only acceptable for DOMHwMediaStream
|
||||
// because DOMHwMediaStream doesn't have consumers of TV streams currently.
|
||||
track->GetSegment()->Clear();
|
||||
|
||||
// Change the image size.
|
||||
const StreamTime delta = STREAM_TIME_MAX;
|
||||
RefPtr<Image> image = static_cast<Image*>(mOverlayImage.get());
|
||||
mozilla::gfx::IntSize size = image->GetSize();
|
||||
VideoSegment segment;
|
||||
|
||||
segment.AppendFrame(image.forget(), delta, size);
|
||||
srcStream->AppendToTrack(TRACK_VIDEO_PRIMARY, &segment);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
DOMHwMediaStream::SetOverlayId(int32_t aOverlayId)
|
||||
{
|
||||
|
@ -698,34 +698,32 @@ private:
|
||||
class DOMHwMediaStream : public DOMLocalMediaStream
|
||||
{
|
||||
typedef mozilla::gfx::IntSize IntSize;
|
||||
typedef layers::ImageContainer ImageContainer;
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
typedef layers::OverlayImage OverlayImage;
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
typedef layers::OverlayImage::Data Data;
|
||||
#endif
|
||||
|
||||
public:
|
||||
DOMHwMediaStream();
|
||||
|
||||
static already_AddRefed<DOMHwMediaStream> CreateHwStream(nsIDOMWindow* aWindow);
|
||||
static already_AddRefed<DOMHwMediaStream> CreateHwStream(nsIDOMWindow* aWindow, OverlayImage* aImage = nullptr);
|
||||
virtual DOMHwMediaStream* AsDOMHwMediaStream() override { return this; }
|
||||
int32_t RequestOverlayId();
|
||||
void SetOverlayId(int32_t aOverlayId);
|
||||
void SetImageSize(uint32_t width, uint32_t height);
|
||||
void SetOverlayImage(OverlayImage* aImage);
|
||||
|
||||
protected:
|
||||
~DOMHwMediaStream();
|
||||
|
||||
private:
|
||||
void Init(MediaStream* aStream);
|
||||
void Init(MediaStream* aStream, OverlayImage* aImage);
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
RefPtr<ImageContainer> mImageContainer;
|
||||
const int DEFAULT_IMAGE_ID = 0x01;
|
||||
const int DEFAULT_IMAGE_WIDTH = 400;
|
||||
const int DEFAULT_IMAGE_HEIGHT = 300;
|
||||
RefPtr<OverlayImage> mOverlayImage;
|
||||
Data mImageData;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
@ -249,7 +249,7 @@ bool
|
||||
AndroidDecoderModule::SupportsMimeType(const nsACString& aMimeType) const
|
||||
{
|
||||
if (!AndroidBridge::Bridge() ||
|
||||
(AndroidBridge::Bridge()->GetAPIVersion() < 16)) {
|
||||
AndroidBridge::Bridge()->GetAPIVersion() < 16) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -258,12 +258,8 @@ AndroidDecoderModule::SupportsMimeType(const nsACString& aMimeType) const
|
||||
return true;
|
||||
}
|
||||
|
||||
MediaCodec::LocalRef ref = mozilla::CreateDecoder(aMimeType);
|
||||
if (!ref) {
|
||||
return false;
|
||||
}
|
||||
ref->Release();
|
||||
return true;
|
||||
return widget::HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType(
|
||||
nsCString(TranslateMimeType(aMimeType)));
|
||||
}
|
||||
|
||||
already_AddRefed<MediaDataDecoder>
|
||||
|
@ -3871,10 +3871,9 @@ PluginInstanceChild::ReadbackDifferenceRect(const nsIntRect& rect)
|
||||
// Subtract from mSurfaceDifferenceRect area which is overlapping with rect
|
||||
nsIntRegion result;
|
||||
result.Sub(mSurfaceDifferenceRect, nsIntRegion(rect));
|
||||
nsIntRegionRectIterator iter(result);
|
||||
const nsIntRect* r;
|
||||
while ((r = iter.Next()) != nullptr) {
|
||||
dt->CopySurface(source, *r, r->TopLeft());
|
||||
for (auto iter = result.RectIter(); !iter.Done(); iter.Next()) {
|
||||
const nsIntRect& r = iter.Get();
|
||||
dt->CopySurface(source, r, r.TopLeft());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -931,10 +931,10 @@ Promise::NewPromiseCapability(JSContext* aCx, nsIGlobalObject* aGlobal,
|
||||
// Step 6 and step 7.
|
||||
JS::Rooted<JS::Value> getCapabilities(aCx,
|
||||
JS::ObjectValue(*getCapabilitiesObj));
|
||||
JS::Rooted<JS::Value> promiseVal(aCx);
|
||||
JS::Rooted<JSObject*> promiseObj(aCx);
|
||||
if (!JS::Construct(aCx, aConstructor,
|
||||
JS::HandleValueArray(getCapabilities),
|
||||
&promiseVal)) {
|
||||
&promiseObj)) {
|
||||
aRv.NoteJSContextException();
|
||||
return;
|
||||
}
|
||||
@ -959,7 +959,7 @@ Promise::NewPromiseCapability(JSContext* aCx, nsIGlobalObject* aGlobal,
|
||||
aCapability.mReject = v;
|
||||
|
||||
// Step 10.
|
||||
aCapability.mPromise = promiseVal;
|
||||
aCapability.mPromise.setObject(*promiseObj);
|
||||
|
||||
// Step 11 doesn't need anything, since the PromiseCapability was passed in.
|
||||
}
|
||||
|
@ -188,6 +188,7 @@ SVGContentUtils::GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
|
||||
|
||||
const nsStyleSVG* styleSVG = styleContext->StyleSVG();
|
||||
|
||||
bool checkedDashAndStrokeIsDashed = false;
|
||||
if (aFlags != eIgnoreStrokeDashing) {
|
||||
DashState dashState =
|
||||
GetStrokeDashData(aStrokeOptions, aElement, styleSVG, aContextPaint);
|
||||
@ -201,6 +202,7 @@ SVGContentUtils::GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
|
||||
// Prevent our caller from wasting time looking at a pattern without gaps:
|
||||
aStrokeOptions->DiscardDashPattern();
|
||||
}
|
||||
checkedDashAndStrokeIsDashed = (dashState == eDashedStroke);
|
||||
}
|
||||
|
||||
aStrokeOptions->mLineWidth =
|
||||
@ -220,10 +222,12 @@ SVGContentUtils::GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
|
||||
break;
|
||||
}
|
||||
|
||||
if (ShapeTypeHasNoCorners(aElement)) {
|
||||
if (ShapeTypeHasNoCorners(aElement) && !checkedDashAndStrokeIsDashed) {
|
||||
// Note: if aFlags == eIgnoreStrokeDashing then we may be returning the
|
||||
// wrong linecap value here, since the actual linecap used on render in this
|
||||
// case depends on whether the stroke is dashed or not.
|
||||
aStrokeOptions->mLineCap = CapStyle::BUTT;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
switch (styleSVG->mStrokeLinecap) {
|
||||
case NS_STYLE_STROKE_LINECAP_BUTT:
|
||||
aStrokeOptions->mLineCap = CapStyle::BUTT;
|
||||
|
@ -163,6 +163,12 @@ public:
|
||||
eAllStrokeOptions,
|
||||
eIgnoreStrokeDashing
|
||||
};
|
||||
/**
|
||||
* Note: the linecap style returned in aStrokeOptions is not valid when
|
||||
* ShapeTypeHasNoCorners(aElement) == true && aFlags == eIgnoreStrokeDashing,
|
||||
* since when aElement has no corners the rendered linecap style depends on
|
||||
* whether or not the stroke is dashed.
|
||||
*/
|
||||
static void GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
|
||||
nsSVGElement* aElement,
|
||||
nsStyleContext* aStyleContext,
|
||||
|
@ -169,7 +169,7 @@ function testFormDataSend() {
|
||||
}
|
||||
|
||||
is(response[1].headers['Content-Disposition'],
|
||||
'form-data; name="empty"; filename="blob"');
|
||||
'form-data; name="empty"; filename=""');
|
||||
|
||||
is(response[2].headers['Content-Disposition'],
|
||||
'form-data; name="explicit"; filename="explicit-file-name"');
|
||||
|
@ -417,7 +417,7 @@ function testFormDataBodyExtraction() {
|
||||
ok(fd.has("blob"), "Has entry 'blob'.");
|
||||
var entries = fd.getAll("blob");
|
||||
is(entries.length, 1, "getAll returns all items.");
|
||||
is(entries[0].name, "blob", "Filename should be blob.");
|
||||
ok(entries[0] instanceof Blob, "getAll returns blobs.");
|
||||
});
|
||||
|
||||
var ws = "\r\n\r\n\r\n\r\n";
|
||||
@ -428,7 +428,7 @@ function testFormDataBodyExtraction() {
|
||||
ok(fd.has("blob"), "Has entry 'blob'.");
|
||||
var entries = fd.getAll("blob");
|
||||
is(entries.length, 1, "getAll returns all items.");
|
||||
is(entries[0].name, "blob", "Filename should be blob.");
|
||||
ok(entries[0] instanceof Blob, "getAll returns blobs.");
|
||||
|
||||
ok(fd.has("key"), "Has entry 'key'.");
|
||||
var f = fd.get("key");
|
||||
|
@ -19,17 +19,17 @@ dictionary Algorithm {
|
||||
};
|
||||
|
||||
dictionary AesCbcParams : Algorithm {
|
||||
required CryptoOperationData iv;
|
||||
required BufferSource iv;
|
||||
};
|
||||
|
||||
dictionary AesCtrParams : Algorithm {
|
||||
required CryptoOperationData counter;
|
||||
required BufferSource counter;
|
||||
[EnforceRange] required octet length;
|
||||
};
|
||||
|
||||
dictionary AesGcmParams : Algorithm {
|
||||
required CryptoOperationData iv;
|
||||
CryptoOperationData additionalData;
|
||||
required BufferSource iv;
|
||||
BufferSource additionalData;
|
||||
[EnforceRange] octet tagLength;
|
||||
};
|
||||
|
||||
@ -38,7 +38,7 @@ dictionary HmacImportParams : Algorithm {
|
||||
};
|
||||
|
||||
dictionary Pbkdf2Params : Algorithm {
|
||||
required CryptoOperationData salt;
|
||||
required BufferSource salt;
|
||||
[EnforceRange] required unsigned long iterations;
|
||||
required AlgorithmIdentifier hash;
|
||||
};
|
||||
@ -63,7 +63,11 @@ dictionary RsaHashedKeyGenParams : Algorithm {
|
||||
};
|
||||
|
||||
dictionary RsaOaepParams : Algorithm {
|
||||
CryptoOperationData label;
|
||||
BufferSource label;
|
||||
};
|
||||
|
||||
dictionary RsaPssParams : Algorithm {
|
||||
[EnforceRange] required unsigned long saltLength;
|
||||
};
|
||||
|
||||
dictionary DhKeyGenParams : Algorithm {
|
||||
@ -104,6 +108,12 @@ dictionary EcKeyImportParams : Algorithm {
|
||||
NamedCurve namedCurve;
|
||||
};
|
||||
|
||||
dictionary HkdfParams : Algorithm {
|
||||
required AlgorithmIdentifier hash;
|
||||
required BufferSource salt;
|
||||
required BufferSource info;
|
||||
};
|
||||
|
||||
/***** JWK *****/
|
||||
|
||||
dictionary RsaOtherPrimesInfo {
|
||||
@ -155,30 +165,29 @@ dictionary CryptoKeyPair {
|
||||
};
|
||||
|
||||
typedef DOMString KeyFormat;
|
||||
typedef (ArrayBufferView or ArrayBuffer) CryptoOperationData;
|
||||
typedef (object or DOMString) AlgorithmIdentifier;
|
||||
|
||||
interface SubtleCrypto {
|
||||
[Throws]
|
||||
Promise<any> encrypt(AlgorithmIdentifier algorithm,
|
||||
CryptoKey key,
|
||||
CryptoOperationData data);
|
||||
BufferSource data);
|
||||
[Throws]
|
||||
Promise<any> decrypt(AlgorithmIdentifier algorithm,
|
||||
CryptoKey key,
|
||||
CryptoOperationData data);
|
||||
BufferSource data);
|
||||
[Throws]
|
||||
Promise<any> sign(AlgorithmIdentifier algorithm,
|
||||
CryptoKey key,
|
||||
CryptoOperationData data);
|
||||
BufferSource data);
|
||||
[Throws]
|
||||
Promise<any> verify(AlgorithmIdentifier algorithm,
|
||||
CryptoKey key,
|
||||
CryptoOperationData signature,
|
||||
CryptoOperationData data);
|
||||
BufferSource signature,
|
||||
BufferSource data);
|
||||
[Throws]
|
||||
Promise<any> digest(AlgorithmIdentifier algorithm,
|
||||
CryptoOperationData data);
|
||||
BufferSource data);
|
||||
|
||||
[Throws]
|
||||
Promise<any> generateKey(AlgorithmIdentifier algorithm,
|
||||
@ -212,7 +221,7 @@ interface SubtleCrypto {
|
||||
|
||||
[Throws]
|
||||
Promise<any> unwrapKey(KeyFormat format,
|
||||
CryptoOperationData wrappedKey,
|
||||
BufferSource wrappedKey,
|
||||
CryptoKey unwrappingKey,
|
||||
AlgorithmIdentifier unwrapAlgorithm,
|
||||
AlgorithmIdentifier unwrappedKeyAlgorithm,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user