mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:30:27 +00:00
Merge remote-tracking branch 'origin/master' into media-works
This commit is contained in:
@@ -347,7 +347,7 @@ getCharacterExtentsCB(AtkText *aText, gint aOffset,
|
||||
if (aCoords == ATK_XY_SCREEN) {
|
||||
geckoCoordType = nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE;
|
||||
} else {
|
||||
giabbaCoordType = nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE;
|
||||
geckoCoordType = nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE;
|
||||
}
|
||||
|
||||
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
|
||||
|
||||
@@ -204,7 +204,88 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
|
||||
}
|
||||
|
||||
// Process rendered text change notifications.
|
||||
mTextHash.EnumerateEntries(TextEnumerator, mDocument);
|
||||
for (auto iter = mTextHash.Iter(); !iter.Done(); iter.Next()) {
|
||||
nsCOMPtrHashKey<nsIContent>* entry = iter.Get();
|
||||
nsIContent* textNode = entry->GetKey();
|
||||
Accessible* textAcc = mDocument->GetAccessible(textNode);
|
||||
|
||||
// If the text node is not in tree or doesn't have frame then this case should
|
||||
// have been handled already by content removal notifications.
|
||||
nsINode* containerNode = textNode->GetParentNode();
|
||||
if (!containerNode) {
|
||||
NS_ASSERTION(!textAcc,
|
||||
"Text node was removed but accessible is kept alive!");
|
||||
continue;
|
||||
}
|
||||
|
||||
nsIFrame* textFrame = textNode->GetPrimaryFrame();
|
||||
if (!textFrame) {
|
||||
NS_ASSERTION(!textAcc,
|
||||
"Text node isn't rendered but accessible is kept alive!");
|
||||
continue;
|
||||
}
|
||||
|
||||
nsIContent* containerElm = containerNode->IsElement() ?
|
||||
containerNode->AsElement() : nullptr;
|
||||
|
||||
nsIFrame::RenderedText text = textFrame->GetRenderedText();
|
||||
|
||||
// Remove text accessible if rendered text is empty.
|
||||
if (textAcc) {
|
||||
if (text.mString.IsEmpty()) {
|
||||
#ifdef A11Y_LOG
|
||||
if (logging::IsEnabled(logging::eTree | logging::eText)) {
|
||||
logging::MsgBegin("TREE", "text node lost its content");
|
||||
logging::Node("container", containerElm);
|
||||
logging::Node("content", textNode);
|
||||
logging::MsgEnd();
|
||||
}
|
||||
#endif
|
||||
|
||||
mDocument->ContentRemoved(containerElm, textNode);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update text of the accessible and fire text change events.
|
||||
#ifdef A11Y_LOG
|
||||
if (logging::IsEnabled(logging::eText)) {
|
||||
logging::MsgBegin("TEXT", "text may be changed");
|
||||
logging::Node("container", containerElm);
|
||||
logging::Node("content", textNode);
|
||||
logging::MsgEntry("old text '%s'",
|
||||
NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get());
|
||||
logging::MsgEntry("new text: '%s'",
|
||||
NS_ConvertUTF16toUTF8(text.mString).get());
|
||||
logging::MsgEnd();
|
||||
}
|
||||
#endif
|
||||
|
||||
TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text.mString);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Append an accessible if rendered text is not empty.
|
||||
if (!text.mString.IsEmpty()) {
|
||||
#ifdef A11Y_LOG
|
||||
if (logging::IsEnabled(logging::eTree | logging::eText)) {
|
||||
logging::MsgBegin("TREE", "text node gains new content");
|
||||
logging::Node("container", containerElm);
|
||||
logging::Node("content", textNode);
|
||||
logging::MsgEnd();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Make sure the text node is in accessible document still.
|
||||
Accessible* container = mDocument->GetAccessibleOrContainer(containerNode);
|
||||
NS_ASSERTION(container,
|
||||
"Text node having rendered text hasn't accessible document!");
|
||||
if (container) {
|
||||
nsTArray<nsCOMPtr<nsIContent> > insertedContents;
|
||||
insertedContents.AppendElement(textNode);
|
||||
mDocument->ProcessContentInserted(container, &insertedContents);
|
||||
}
|
||||
}
|
||||
}
|
||||
mTextHash.Clear();
|
||||
|
||||
// Bind hanging child documents.
|
||||
@@ -314,98 +395,6 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Notification controller: text leaf accessible text update
|
||||
|
||||
PLDHashOperator
|
||||
NotificationController::TextEnumerator(nsCOMPtrHashKey<nsIContent>* aEntry,
|
||||
void* aUserArg)
|
||||
{
|
||||
DocAccessible* document = static_cast<DocAccessible*>(aUserArg);
|
||||
nsIContent* textNode = aEntry->GetKey();
|
||||
Accessible* textAcc = document->GetAccessible(textNode);
|
||||
|
||||
// If the text node is not in tree or doesn't have frame then this case should
|
||||
// have been handled already by content removal notifications.
|
||||
nsINode* containerNode = textNode->GetParentNode();
|
||||
if (!containerNode) {
|
||||
NS_ASSERTION(!textAcc,
|
||||
"Text node was removed but accessible is kept alive!");
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
nsIFrame* textFrame = textNode->GetPrimaryFrame();
|
||||
if (!textFrame) {
|
||||
NS_ASSERTION(!textAcc,
|
||||
"Text node isn't rendered but accessible is kept alive!");
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
nsIContent* containerElm = containerNode->IsElement() ?
|
||||
containerNode->AsElement() : nullptr;
|
||||
|
||||
nsIFrame::RenderedText text = textFrame->GetRenderedText();
|
||||
|
||||
// Remove text accessible if rendered text is empty.
|
||||
if (textAcc) {
|
||||
if (text.mString.IsEmpty()) {
|
||||
#ifdef A11Y_LOG
|
||||
if (logging::IsEnabled(logging::eTree | logging::eText)) {
|
||||
logging::MsgBegin("TREE", "text node lost its content");
|
||||
logging::Node("container", containerElm);
|
||||
logging::Node("content", textNode);
|
||||
logging::MsgEnd();
|
||||
}
|
||||
#endif
|
||||
|
||||
document->ContentRemoved(containerElm, textNode);
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
// Update text of the accessible and fire text change events.
|
||||
#ifdef A11Y_LOG
|
||||
if (logging::IsEnabled(logging::eText)) {
|
||||
logging::MsgBegin("TEXT", "text may be changed");
|
||||
logging::Node("container", containerElm);
|
||||
logging::Node("content", textNode);
|
||||
logging::MsgEntry("old text '%s'",
|
||||
NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get());
|
||||
logging::MsgEntry("new text: '%s'",
|
||||
NS_ConvertUTF16toUTF8(text.mString).get());
|
||||
logging::MsgEnd();
|
||||
}
|
||||
#endif
|
||||
|
||||
TextUpdater::Run(document, textAcc->AsTextLeaf(), text.mString);
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
// Append an accessible if rendered text is not empty.
|
||||
if (!text.mString.IsEmpty()) {
|
||||
#ifdef A11Y_LOG
|
||||
if (logging::IsEnabled(logging::eTree | logging::eText)) {
|
||||
logging::MsgBegin("TREE", "text node gains new content");
|
||||
logging::Node("container", containerElm);
|
||||
logging::Node("content", textNode);
|
||||
logging::MsgEnd();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Make sure the text node is in accessible document still.
|
||||
Accessible* container = document->GetAccessibleOrContainer(containerNode);
|
||||
NS_ASSERTION(container,
|
||||
"Text node having rendered text hasn't accessible document!");
|
||||
if (container) {
|
||||
nsTArray<nsCOMPtr<nsIContent> > insertedContents;
|
||||
insertedContents.AppendElement(textNode);
|
||||
document->ProcessContentInserted(container, &insertedContents);
|
||||
}
|
||||
}
|
||||
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// NotificationController: content inserted notification
|
||||
|
||||
|
||||
@@ -297,12 +297,6 @@ private:
|
||||
*/
|
||||
nsTHashtable<nsCOMPtrHashKey<nsIContent> > mTextHash;
|
||||
|
||||
/**
|
||||
* Update the accessible tree for pending rendered text change notifications.
|
||||
*/
|
||||
static PLDHashOperator TextEnumerator(nsCOMPtrHashKey<nsIContent>* aEntry,
|
||||
void* aUserArg);
|
||||
|
||||
/**
|
||||
* Other notifications like DOM events. Don't make this an nsAutoTArray; we
|
||||
* use SwapElements() on it.
|
||||
|
||||
@@ -458,7 +458,7 @@ DocAccessible::Shutdown()
|
||||
|
||||
// XXX thinking about ordering?
|
||||
if (IPCAccessibilityActive()) {
|
||||
DocAccessibleChild::Send__delete__(mIPCDoc);
|
||||
mIPCDoc->Shutdown();
|
||||
MOZ_ASSERT(!mIPCDoc);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,12 @@ SerializeTree(Accessible* aRoot, nsTArray<AccessibleData>& aTree)
|
||||
Accessible*
|
||||
DocAccessibleChild::IdToAccessible(const uint64_t& aID) const
|
||||
{
|
||||
if (!aID)
|
||||
return mDoc;
|
||||
|
||||
if (!mDoc)
|
||||
return nullptr;
|
||||
|
||||
return mDoc->GetAccessibleByUniqueID(reinterpret_cast<void*>(aID));
|
||||
}
|
||||
|
||||
|
||||
@@ -32,10 +32,21 @@ public:
|
||||
{ MOZ_COUNT_CTOR(DocAccessibleChild); }
|
||||
~DocAccessibleChild()
|
||||
{
|
||||
mDoc->SetIPCDoc(nullptr);
|
||||
// Shutdown() should have been called, but maybe it isn't if the process is
|
||||
// killed?
|
||||
MOZ_ASSERT(!mDoc);
|
||||
if (mDoc)
|
||||
mDoc->SetIPCDoc(nullptr);
|
||||
MOZ_COUNT_DTOR(DocAccessibleChild);
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
{
|
||||
mDoc->SetIPCDoc(nullptr);
|
||||
mDoc = nullptr;
|
||||
SendShutdown();
|
||||
}
|
||||
|
||||
void ShowEvent(AccShowEvent* aShowEvent);
|
||||
|
||||
/*
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "nsAutoPtr.h"
|
||||
#include "mozilla/a11y/Platform.h"
|
||||
#include "ProxyAccessible.h"
|
||||
#include "mozilla/dom/TabParent.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace a11y {
|
||||
@@ -101,6 +102,8 @@ DocAccessibleParent::RecvHideEvent(const uint64_t& aRootID)
|
||||
if (mShutdown)
|
||||
return true;
|
||||
|
||||
CheckDocTree();
|
||||
|
||||
ProxyEntry* rootEntry = mAccessibles.GetEntry(aRootID);
|
||||
if (!rootEntry) {
|
||||
NS_ERROR("invalid root being removed!");
|
||||
@@ -167,11 +170,16 @@ DocAccessibleParent::AddChildDoc(DocAccessibleParent* aChildDoc,
|
||||
return true;
|
||||
}
|
||||
|
||||
PLDHashOperator
|
||||
DocAccessibleParent::ShutdownAccessibles(ProxyEntry* entry, void*)
|
||||
bool
|
||||
DocAccessibleParent::RecvShutdown()
|
||||
{
|
||||
ProxyDestroyed(entry->mProxy);
|
||||
return PL_DHASH_REMOVE;
|
||||
Destroy();
|
||||
|
||||
if (!static_cast<dom::TabParent*>(Manager())->IsDestroyed()) {
|
||||
return PDocAccessibleParent::Send__delete__(this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -186,10 +194,28 @@ DocAccessibleParent::Destroy()
|
||||
for (uint32_t i = childDocCount - 1; i < childDocCount; i--)
|
||||
mChildDocs[i]->Destroy();
|
||||
|
||||
mAccessibles.EnumerateEntries(ShutdownAccessibles, nullptr);
|
||||
for (auto iter = mAccessibles.Iter(); !iter.Done(); iter.Next()) {
|
||||
ProxyDestroyed(iter.Get()->mProxy);
|
||||
iter.Remove();
|
||||
}
|
||||
ProxyDestroyed(this);
|
||||
mParentDoc ? mParentDoc->RemoveChildDoc(this)
|
||||
: GetAccService()->RemoteDocShutdown(this);
|
||||
}
|
||||
if (mParentDoc)
|
||||
mParentDoc->RemoveChildDoc(this);
|
||||
else if (IsTopLevel())
|
||||
GetAccService()->RemoteDocShutdown(this);
|
||||
}
|
||||
|
||||
void
|
||||
DocAccessibleParent::CheckDocTree() const
|
||||
{
|
||||
size_t childDocs = mChildDocs.Length();
|
||||
for (size_t i = 0; i < childDocs; i++) {
|
||||
if (!mChildDocs[i] || mChildDocs[i]->mParentDoc != this)
|
||||
MOZ_CRASH("document tree is broken!");
|
||||
|
||||
mChildDocs[i]->CheckDocTree();
|
||||
}
|
||||
}
|
||||
|
||||
} // a11y
|
||||
} // mozilla
|
||||
|
||||
@@ -26,15 +26,19 @@ class DocAccessibleParent : public ProxyAccessible,
|
||||
{
|
||||
public:
|
||||
DocAccessibleParent() :
|
||||
ProxyAccessible(this), mParentDoc(nullptr), mShutdown(false)
|
||||
ProxyAccessible(this), mParentDoc(nullptr),
|
||||
mTopLevel(false), mShutdown(false)
|
||||
{ MOZ_COUNT_CTOR_INHERITED(DocAccessibleParent, ProxyAccessible); }
|
||||
~DocAccessibleParent()
|
||||
{
|
||||
MOZ_COUNT_DTOR_INHERITED(DocAccessibleParent, ProxyAccessible);
|
||||
MOZ_ASSERT(mChildDocs.Length() == 0);
|
||||
MOZ_ASSERT(!mParentDoc);
|
||||
MOZ_ASSERT(!ParentDoc());
|
||||
}
|
||||
|
||||
void SetTopLevel() { mTopLevel = true; }
|
||||
bool IsTopLevel() const { return mTopLevel; }
|
||||
|
||||
/*
|
||||
* Called when a message from a document in a child process notifies the main
|
||||
* process it is firing an event.
|
||||
@@ -49,10 +53,11 @@ public:
|
||||
void Unbind()
|
||||
{
|
||||
mParent = nullptr;
|
||||
mParentDoc->mChildDocs.RemoveElement(this);
|
||||
ParentDoc()->mChildDocs.RemoveElement(this);
|
||||
mParentDoc = nullptr;
|
||||
}
|
||||
|
||||
virtual bool RecvShutdown() override;
|
||||
void Destroy();
|
||||
virtual void ActorDestroy(ActorDestroyReason aWhy) override
|
||||
{
|
||||
@@ -64,7 +69,7 @@ public:
|
||||
* Return the main processes representation of the parent document (if any)
|
||||
* of the document this object represents.
|
||||
*/
|
||||
DocAccessibleParent* Parent() const { return mParentDoc; }
|
||||
DocAccessibleParent* ParentDoc() const { return mParentDoc; }
|
||||
|
||||
/*
|
||||
* Called when a document in a content process notifies the main process of a
|
||||
@@ -128,7 +133,8 @@ private:
|
||||
uint32_t AddSubtree(ProxyAccessible* aParent,
|
||||
const nsTArray<AccessibleData>& aNewTree, uint32_t aIdx,
|
||||
uint32_t aIdxInParent);
|
||||
static PLDHashOperator ShutdownAccessibles(ProxyEntry* entry, void* unused);
|
||||
|
||||
void CheckDocTree() const;
|
||||
|
||||
nsTArray<DocAccessibleParent*> mChildDocs;
|
||||
DocAccessibleParent* mParentDoc;
|
||||
@@ -138,6 +144,7 @@ private:
|
||||
* proxy object so we can't use a real map.
|
||||
*/
|
||||
nsTHashtable<ProxyEntry> mAccessibles;
|
||||
bool mTopLevel;
|
||||
bool mShutdown;
|
||||
};
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ prio(normal upto high) sync protocol PDocAccessible
|
||||
manager PBrowser;
|
||||
|
||||
parent:
|
||||
__delete__();
|
||||
Shutdown();
|
||||
|
||||
/*
|
||||
* Notify the parent process the document in the child process is firing an
|
||||
@@ -65,6 +65,8 @@ parent:
|
||||
BindChildDoc(PDocAccessible aChildDoc, uint64_t aID);
|
||||
|
||||
child:
|
||||
__delete__();
|
||||
|
||||
// Accessible
|
||||
prio(high) sync State(uint64_t aID) returns(uint64_t states);
|
||||
prio(high) sync Name(uint64_t aID) returns(nsString name);
|
||||
|
||||
@@ -262,6 +262,9 @@ pref("browser.urlbar.clickSelectsAll", true);
|
||||
pref("browser.urlbar.doubleClickSelectsAll", false);
|
||||
pref("browser.urlbar.autoFill", true);
|
||||
pref("browser.urlbar.autoFill.typed", true);
|
||||
|
||||
pref("browser.urlbar.unifiedcomplete", false);
|
||||
|
||||
// 0: Match anywhere (e.g., middle of words)
|
||||
// 1: Match on word boundaries and then try matching anywhere
|
||||
// 2: Match only on word boundaries (e.g., after / or .)
|
||||
|
||||
+44
-26
@@ -2578,33 +2578,51 @@ ContentParent::RecvSetClipboard(const IPCDataTransfer& aDataTransfer,
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, true);
|
||||
} else if (item.data().type() == IPCDataTransferData::TnsCString) {
|
||||
const IPCDataTransferImage& imageDetails = item.imageDetails();
|
||||
const gfxIntSize size(imageDetails.width(), imageDetails.height());
|
||||
if (!size.width || !size.height) {
|
||||
return true;
|
||||
if (item.flavor().EqualsLiteral(kJPEGImageMime) ||
|
||||
item.flavor().EqualsLiteral(kJPGImageMime) ||
|
||||
item.flavor().EqualsLiteral(kPNGImageMime) ||
|
||||
item.flavor().EqualsLiteral(kGIFImageMime)) {
|
||||
const IPCDataTransferImage& imageDetails = item.imageDetails();
|
||||
const gfxIntSize size(imageDetails.width(), imageDetails.height());
|
||||
if (!size.width || !size.height) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsCString text = item.data().get_nsCString();
|
||||
mozilla::RefPtr<gfx::DataSourceSurface> image =
|
||||
new mozilla::gfx::SourceSurfaceRawData();
|
||||
mozilla::gfx::SourceSurfaceRawData* raw =
|
||||
static_cast<mozilla::gfx::SourceSurfaceRawData*>(image.get());
|
||||
raw->InitWrappingData(
|
||||
reinterpret_cast<uint8_t*>(const_cast<nsCString&>(text).BeginWriting()),
|
||||
size, imageDetails.stride(),
|
||||
static_cast<mozilla::gfx::SurfaceFormat>(imageDetails.format()), false);
|
||||
raw->GuaranteePersistance();
|
||||
|
||||
nsRefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(image, size);
|
||||
nsCOMPtr<imgIContainer> imageContainer(image::ImageOps::CreateFromDrawable(drawable));
|
||||
|
||||
nsCOMPtr<nsISupportsInterfacePointer>
|
||||
imgPtr(do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv));
|
||||
|
||||
rv = imgPtr->SetData(imageContainer);
|
||||
NS_ENSURE_SUCCESS(rv, true);
|
||||
|
||||
trans->SetTransferData(item.flavor().get(), imgPtr, sizeof(nsISupports*));
|
||||
} else {
|
||||
nsCOMPtr<nsISupportsCString> dataWrapper =
|
||||
do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, true);
|
||||
|
||||
const nsCString& text = item.data().get_nsCString();
|
||||
rv = dataWrapper->SetData(text);
|
||||
NS_ENSURE_SUCCESS(rv, true);
|
||||
|
||||
rv = trans->SetTransferData(item.flavor().get(), dataWrapper,
|
||||
text.Length());
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, true);
|
||||
}
|
||||
|
||||
nsCString text = item.data().get_nsCString();
|
||||
mozilla::RefPtr<gfx::DataSourceSurface> image =
|
||||
new mozilla::gfx::SourceSurfaceRawData();
|
||||
mozilla::gfx::SourceSurfaceRawData* raw =
|
||||
static_cast<mozilla::gfx::SourceSurfaceRawData*>(image.get());
|
||||
raw->InitWrappingData(
|
||||
reinterpret_cast<uint8_t*>(const_cast<nsCString&>(text).BeginWriting()),
|
||||
size, imageDetails.stride(),
|
||||
static_cast<mozilla::gfx::SurfaceFormat>(imageDetails.format()), false);
|
||||
raw->GuaranteePersistance();
|
||||
|
||||
nsRefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(image, size);
|
||||
nsCOMPtr<imgIContainer> imageContainer(image::ImageOps::CreateFromDrawable(drawable));
|
||||
|
||||
nsCOMPtr<nsISupportsInterfacePointer>
|
||||
imgPtr(do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv));
|
||||
|
||||
rv = imgPtr->SetData(imageContainer);
|
||||
NS_ENSURE_SUCCESS(rv, true);
|
||||
|
||||
trans->SetTransferData(item.flavor().get(), imgPtr, sizeof(nsISupports*));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1183,6 +1183,7 @@ TabParent::RecvPDocAccessibleConstructor(PDocAccessibleParent* aDoc,
|
||||
return parentDoc->AddChildDoc(doc, aParentID);
|
||||
} else {
|
||||
MOZ_ASSERT(!aParentID);
|
||||
doc->SetTopLevel();
|
||||
a11y::DocManager::RemoteDocAdded(doc);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -504,7 +504,7 @@ private:
|
||||
void DoInjection(const nsAutoHandle& aSnapshot);
|
||||
static DWORD WINAPI GetToolhelpSnapshot(LPVOID aContext);
|
||||
|
||||
void OnCrash(DWORD processID) MOZ_OVERRIDE;
|
||||
void OnCrash(DWORD processID) override;
|
||||
|
||||
DWORD mFlashProcess1;
|
||||
DWORD mFlashProcess2;
|
||||
|
||||
@@ -1000,11 +1000,13 @@ nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal,
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPermissionManager::Remove(const nsACString &aHost,
|
||||
const char *aType)
|
||||
nsPermissionManager::Remove(nsIURI* aURI,
|
||||
const char* aType)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aURI);
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
nsresult rv = GetPrincipal(aHost, getter_AddRefs(principal));
|
||||
nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return RemoveFromPrincipal(principal, aType);
|
||||
@@ -1040,6 +1042,26 @@ nsPermissionManager::RemoveFromPrincipal(nsIPrincipal* aPrincipal,
|
||||
eWriteToDB);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPermissionManager::RemovePermission(nsIPermission* aPerm)
|
||||
{
|
||||
nsAutoCString host;
|
||||
nsresult rv = aPerm->GetHost(host);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
rv = GetPrincipal(host, getter_AddRefs(principal));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsAutoCString type;
|
||||
rv = aPerm->GetType(type);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Permissions are uniquely identified by their principal and type.
|
||||
// We remove the permission using these two pieces of data.
|
||||
return RemoveFromPrincipal(principal, type.get());
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPermissionManager::RemoveAll()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function run_test() {
|
||||
// initialize the permission manager service
|
||||
let pm = Cc["@mozilla.org/permissionmanager;1"].
|
||||
getService(Ci.nsIPermissionManager);
|
||||
|
||||
do_check_eq(perm_count(), 0);
|
||||
|
||||
// add some permissions
|
||||
let uri = NetUtil.newURI("http://amazon.com:8080/foobarbaz", null, null);
|
||||
let uri2 = NetUtil.newURI("http://google.com:2048/quxx", null, null);
|
||||
|
||||
pm.add(uri, "apple", 0);
|
||||
pm.add(uri, "apple", 3);
|
||||
pm.add(uri, "pear", 3);
|
||||
pm.add(uri, "pear", 1);
|
||||
pm.add(uri, "cucumber", 1);
|
||||
pm.add(uri, "cucumber", 1);
|
||||
pm.add(uri, "cucumber", 1);
|
||||
|
||||
pm.add(uri2, "apple", 2);
|
||||
pm.add(uri2, "pear", 0);
|
||||
pm.add(uri2, "pear", 2);
|
||||
|
||||
// Make sure that removePermission doesn't remove more than one permission each time
|
||||
do_check_eq(perm_count(), 5);
|
||||
|
||||
remove_one_by_type("apple");
|
||||
do_check_eq(perm_count(), 4);
|
||||
|
||||
remove_one_by_type("apple");
|
||||
do_check_eq(perm_count(), 3);
|
||||
|
||||
remove_one_by_type("pear");
|
||||
do_check_eq(perm_count(), 2);
|
||||
|
||||
remove_one_by_type("cucumber");
|
||||
do_check_eq(perm_count(), 1);
|
||||
|
||||
remove_one_by_type("pear");
|
||||
do_check_eq(perm_count(), 0);
|
||||
|
||||
|
||||
function perm_count() {
|
||||
let enumerator = pm.enumerator;
|
||||
let count = 0;
|
||||
while (enumerator.hasMoreElements()) {
|
||||
count++;
|
||||
enumerator.getNext();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
function remove_one_by_type(type) {
|
||||
let enumerator = pm.enumerator;
|
||||
while (enumerator.hasMoreElements()) {
|
||||
let it = enumerator.getNext().QueryInterface(Ci.nsIPermission);
|
||||
if (it.type == type) {
|
||||
pm.removePermission(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,3 +34,4 @@ skip-if = debug == true
|
||||
[test_permmanager_cleardata.js]
|
||||
[test_schema_2_migration.js]
|
||||
[test_schema_3_migration.js]
|
||||
[test_permmanager_removepermission.js]
|
||||
|
||||
@@ -429,11 +429,11 @@ DrawTargetSkia::DrawSurfaceWithShadow(SourceSurface *aSurface,
|
||||
|
||||
SkPaint paint;
|
||||
|
||||
SkImageFilter* filter = SkDropShadowImageFilter::Create(aOffset.x, aOffset.y,
|
||||
aSigma, aSigma,
|
||||
ColorToSkColor(aColor, 1.0));
|
||||
SkAutoTUnref<SkImageFilter> filter(SkDropShadowImageFilter::Create(aOffset.x, aOffset.y,
|
||||
aSigma, aSigma,
|
||||
ColorToSkColor(aColor, 1.0)));
|
||||
|
||||
paint.setImageFilter(filter);
|
||||
paint.setImageFilter(filter.get());
|
||||
paint.setXfermodeMode(GfxOpToSkiaOp(aOperator));
|
||||
|
||||
mCanvas->drawBitmap(bitmap.mBitmap, aDest.x, aDest.y, &paint);
|
||||
@@ -525,6 +525,26 @@ DrawTargetSkia::Fill(const Path *aPath,
|
||||
mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint);
|
||||
}
|
||||
|
||||
bool
|
||||
DrawTargetSkia::ShouldLCDRenderText(FontType aFontType, AntialiasMode aAntialiasMode)
|
||||
{
|
||||
// For non-opaque surfaces, only allow subpixel AA if explicitly permitted.
|
||||
if (!IsOpaque(mFormat) && !mPermitSubpixelAA) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aAntialiasMode == AntialiasMode::DEFAULT) {
|
||||
switch (aFontType) {
|
||||
case FontType::MAC:
|
||||
return true;
|
||||
default:
|
||||
// TODO: Figure out what to do for the other platforms.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return (aAntialiasMode == AntialiasMode::SUBPIXEL);
|
||||
}
|
||||
|
||||
void
|
||||
DrawTargetSkia::FillGlyphs(ScaledFont *aFont,
|
||||
const GlyphBuffer &aBuffer,
|
||||
@@ -547,6 +567,9 @@ DrawTargetSkia::FillGlyphs(ScaledFont *aFont,
|
||||
paint.mPaint.setTextSize(SkFloatToScalar(skiaFont->mSize));
|
||||
paint.mPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
|
||||
|
||||
bool shouldLCDRenderText = ShouldLCDRenderText(aFont->GetType(), aOptions.mAntialiasMode);
|
||||
paint.mPaint.setLCDRenderText(shouldLCDRenderText);
|
||||
|
||||
if (aRenderingOptions && aRenderingOptions->GetType() == FontType::CAIRO) {
|
||||
switch (static_cast<const GlyphRenderingOptionsCairo*>(aRenderingOptions)->GetHinting()) {
|
||||
case FontHinting::NONE:
|
||||
@@ -566,6 +589,9 @@ DrawTargetSkia::FillGlyphs(ScaledFont *aFont,
|
||||
if (static_cast<const GlyphRenderingOptionsCairo*>(aRenderingOptions)->GetAutoHinting()) {
|
||||
paint.mPaint.setAutohinted(true);
|
||||
}
|
||||
} else if (aFont->GetType() == FontType::MAC && shouldLCDRenderText) {
|
||||
// SkFontHost_mac only supports subpixel antialiasing when hinting is turned off.
|
||||
paint.mPaint.setHinting(SkPaint::kNo_Hinting);
|
||||
} else {
|
||||
paint.mPaint.setHinting(SkPaint::kNormal_Hinting);
|
||||
}
|
||||
|
||||
@@ -129,6 +129,8 @@ private:
|
||||
|
||||
void MarkChanged();
|
||||
|
||||
bool ShouldLCDRenderText(FontType aFontType, AntialiasMode aAntialiasMode);
|
||||
|
||||
SkRect SkRectCoveringWholeSurface() const;
|
||||
|
||||
bool UsingSkiaGPU() const;
|
||||
|
||||
@@ -105,7 +105,7 @@ SourceSurfaceSkia::InitFromTexture(DrawTargetSkia* aOwner,
|
||||
mSize.width = skiaTexGlue.fWidth = aSize.width;
|
||||
mSize.height = skiaTexGlue.fHeight = aSize.height;
|
||||
skiaTexGlue.fFlags = kNone_GrBackendTextureFlag;
|
||||
skiaTexGlue.fOrigin = kBottomLeft_GrSurfaceOrigin;
|
||||
skiaTexGlue.fOrigin = kTopLeft_GrSurfaceOrigin;
|
||||
skiaTexGlue.fConfig = GfxFormatToGrConfig(aFormat);
|
||||
skiaTexGlue.fSampleCnt = 0;
|
||||
skiaTexGlue.fTextureHandle = aTexture;
|
||||
|
||||
@@ -23,6 +23,8 @@ enum class CreateContextFlags : int8_t {
|
||||
REQUIRE_COMPAT_PROFILE = 1 << 0,
|
||||
// Force the use of hardware backed GL, don't allow software implementations.
|
||||
FORCE_ENABLE_HARDWARE = 1 << 1,
|
||||
/* Don't force discrete GPU to be used (if applicable) */
|
||||
ALLOW_OFFLINE_RENDERER = 1 << 2,
|
||||
};
|
||||
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CreateContextFlags)
|
||||
|
||||
|
||||
@@ -187,6 +187,11 @@ static const NSOpenGLPixelFormatAttribute kAttribs_offscreen[] = {
|
||||
0
|
||||
};
|
||||
|
||||
static const NSOpenGLPixelFormatAttribute kAttribs_offscreen_allow_offline[] = {
|
||||
NSOpenGLPFAAllowOfflineRenderers,
|
||||
0
|
||||
};
|
||||
|
||||
static const NSOpenGLPixelFormatAttribute kAttribs_offscreen_accel[] = {
|
||||
NSOpenGLPFAAccelerated,
|
||||
0
|
||||
@@ -271,10 +276,18 @@ CreateOffscreenFBOContext(CreateContextFlags flags)
|
||||
if (!context) {
|
||||
profile = ContextProfile::OpenGLCompatibility;
|
||||
|
||||
if (gfxPrefs::RequireHardwareGL())
|
||||
context = CreateWithFormat(kAttribs_offscreen_accel);
|
||||
else
|
||||
context = CreateWithFormat(kAttribs_offscreen);
|
||||
if (flags & CreateContextFlags::ALLOW_OFFLINE_RENDERER) {
|
||||
if (gfxPrefs::RequireHardwareGL())
|
||||
context = CreateWithFormat(kAttribs_singleBuffered);
|
||||
else
|
||||
context = CreateWithFormat(kAttribs_offscreen_allow_offline);
|
||||
|
||||
} else {
|
||||
if (gfxPrefs::RequireHardwareGL())
|
||||
context = CreateWithFormat(kAttribs_offscreen_accel);
|
||||
else
|
||||
context = CreateWithFormat(kAttribs_offscreen);
|
||||
}
|
||||
}
|
||||
if (!context) {
|
||||
NS_WARNING("Failed to create NSOpenGLContext.");
|
||||
|
||||
@@ -1147,7 +1147,8 @@ gfxPlatform::GetSkiaGLGlue()
|
||||
* stands, this only works on the main thread.
|
||||
*/
|
||||
nsRefPtr<GLContext> glContext;
|
||||
glContext = GLContextProvider::CreateHeadless(CreateContextFlags::REQUIRE_COMPAT_PROFILE);
|
||||
glContext = GLContextProvider::CreateHeadless(CreateContextFlags::REQUIRE_COMPAT_PROFILE |
|
||||
CreateContextFlags::ALLOW_OFFLINE_RENDERER);
|
||||
if (!glContext) {
|
||||
printf_stderr("Failed to create GLContext for SkiaGL!\n");
|
||||
return nullptr;
|
||||
|
||||
@@ -653,7 +653,8 @@ RasterImage::CopyFrame(uint32_t aWhichFrame, uint32_t aFlags)
|
||||
}
|
||||
|
||||
DataSourceSurface::MappedSurface mapping;
|
||||
if (NS_WARN_IF(!surf->Map(DataSourceSurface::MapType::WRITE, &mapping))) {
|
||||
if (!surf->Map(DataSourceSurface::MapType::WRITE, &mapping)) {
|
||||
gfxCriticalError() << "RasterImage::CopyFrame failed to map surface";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "base/process.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
|
||||
#if defined(OS_LINUX) || defined(OS_MACOSX)
|
||||
#if defined(OS_LINUX) || defined(XP_DARWIN)
|
||||
#include <pthread.h>
|
||||
#include "SharedMemoryBasic.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
%/SharedMemoryBasic_mach.cpp: ;
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#ifdef ANDROID
|
||||
# include "mozilla/ipc/SharedMemoryBasic_android.h"
|
||||
#elif defined(XP_MACOSX)
|
||||
#elif defined(XP_DARWIN)
|
||||
# include "mozilla/ipc/SharedMemoryBasic_mach.h"
|
||||
#else
|
||||
# include "mozilla/ipc/SharedMemoryBasic_chromium.h"
|
||||
|
||||
@@ -9,7 +9,20 @@
|
||||
|
||||
#include <mach/vm_map.h>
|
||||
#include <mach/mach_port.h>
|
||||
#if defined(XP_IOS)
|
||||
#include <mach/vm_map.h>
|
||||
#define mach_vm_address_t vm_address_t
|
||||
#define mach_vm_allocate vm_allocate
|
||||
#define mach_vm_deallocate vm_deallocate
|
||||
#define mach_vm_map vm_map
|
||||
#define mach_vm_read vm_read
|
||||
#define mach_vm_region_recurse vm_region_recurse_64
|
||||
#define mach_vm_size_t vm_size_t
|
||||
#else
|
||||
#include <mach/mach_vm.h>
|
||||
#endif
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include "SharedMemoryBasic.h"
|
||||
#include "chrome/common/mach_ipc_mac.h"
|
||||
|
||||
@@ -94,6 +107,7 @@ enum {
|
||||
};
|
||||
|
||||
const int kTimeout = 1000;
|
||||
const int kLongTimeout = 60 * kTimeout;
|
||||
|
||||
pid_t gParentPid = 0;
|
||||
|
||||
@@ -114,6 +128,11 @@ struct ListeningThread {
|
||||
: mThread(thread), mPorts(ports) {}
|
||||
};
|
||||
|
||||
struct SharePortsReply {
|
||||
uint64_t serial;
|
||||
mach_port_t port;
|
||||
};
|
||||
|
||||
std::map<pid_t, ListeningThread> gThreads;
|
||||
|
||||
static void *
|
||||
@@ -264,8 +283,13 @@ void
|
||||
HandleSharePortsMessage(MachReceiveMessage* rmsg, MemoryPorts* ports)
|
||||
{
|
||||
mach_port_t port = rmsg->GetTranslatedPort(0);
|
||||
uint64_t* serial = reinterpret_cast<uint64_t*>(rmsg->GetData());
|
||||
MachSendMessage msg(kReturnIdMsg);
|
||||
msg.SetData(&port, sizeof(port));
|
||||
// Construct the reply message, echoing the serial, and adding the port
|
||||
SharePortsReply replydata;
|
||||
replydata.port = port;
|
||||
replydata.serial = *serial;
|
||||
msg.SetData(&replydata, sizeof(SharePortsReply));
|
||||
kern_return_t err = ports->mSender->SendMessage(msg, kTimeout);
|
||||
if (KERN_SUCCESS != err) {
|
||||
LOG_ERROR("SendMessage failed 0x%x %s\n", err, mach_error_string(err));
|
||||
@@ -565,8 +589,18 @@ bool
|
||||
SharedMemoryBasic::ShareToProcess(base::ProcessId pid,
|
||||
Handle* aNewHandle)
|
||||
{
|
||||
if (pid == getpid()) {
|
||||
*aNewHandle = mPort;
|
||||
return mach_port_mod_refs(mach_task_self(), *aNewHandle, MACH_PORT_RIGHT_SEND, 1) == KERN_SUCCESS;
|
||||
}
|
||||
StaticMutexAutoLock smal(gMutex);
|
||||
|
||||
// Serially number the messages, to check whether
|
||||
// the reply we get was meant for us.
|
||||
static uint64_t serial = 0;
|
||||
uint64_t my_serial = serial;
|
||||
serial++;
|
||||
|
||||
MemoryPorts* ports = GetMemoryPortsForPid(pid);
|
||||
if (!ports) {
|
||||
LOG_ERROR("Unable to get ports for process.\n");
|
||||
@@ -574,6 +608,7 @@ SharedMemoryBasic::ShareToProcess(base::ProcessId pid,
|
||||
}
|
||||
MachSendMessage smsg(kSharePortsMsg);
|
||||
smsg.AddDescriptor(MachMsgPortDescriptor(mPort, MACH_MSG_TYPE_COPY_SEND));
|
||||
smsg.SetData(&my_serial, sizeof(uint64_t));
|
||||
kern_return_t err = ports->mSender->SendMessage(smsg, kTimeout);
|
||||
if (err != KERN_SUCCESS) {
|
||||
LOG_ERROR("sending port failed %s %x\n", mach_error_string(err), err);
|
||||
@@ -582,15 +617,27 @@ SharedMemoryBasic::ShareToProcess(base::ProcessId pid,
|
||||
MachReceiveMessage msg;
|
||||
err = ports->mReceiver->WaitForMessage(&msg, kTimeout);
|
||||
if (err != KERN_SUCCESS) {
|
||||
LOG_ERROR("didn't get an id %s %x\n", mach_error_string(err), err);
|
||||
return false;
|
||||
LOG_ERROR("short timeout didn't get an id %s %x\n", mach_error_string(err), err);
|
||||
MOZ_ASSERT(false, "Receiver message short time out");
|
||||
err = ports->mReceiver->WaitForMessage(&msg, kLongTimeout);
|
||||
|
||||
if (err != KERN_SUCCESS) {
|
||||
LOG_ERROR("long timeout didn't get an id %s %x\n", mach_error_string(err), err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (msg.GetDataLength() != sizeof(mach_port_t)) {
|
||||
if (msg.GetDataLength() != sizeof(SharePortsReply)) {
|
||||
LOG_ERROR("Improperly formatted reply\n");
|
||||
return false;
|
||||
}
|
||||
mach_port_t *id = reinterpret_cast<mach_port_t*>(msg.GetData());
|
||||
*aNewHandle = *id;
|
||||
SharePortsReply* msg_data = reinterpret_cast<SharePortsReply*>(msg.GetData());
|
||||
mach_port_t id = msg_data->port;
|
||||
uint64_t serial_check = msg_data->serial;
|
||||
if (serial_check != my_serial) {
|
||||
LOG_ERROR("Serials do not match up: %d vs %d", serial_check, my_serial);
|
||||
return false;
|
||||
}
|
||||
*aNewHandle = id;
|
||||
return true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ if CONFIG['OS_TARGET'] == 'Android':
|
||||
elif CONFIG['OS_ARCH'] == 'Darwin':
|
||||
EXPORTS.mozilla.ipc += ['SharedMemoryBasic_mach.h']
|
||||
SOURCES += [
|
||||
'SharedMemoryBasic_mach.cpp',
|
||||
'SharedMemoryBasic_mach.mm',
|
||||
]
|
||||
else:
|
||||
EXPORTS.mozilla.ipc += ['SharedMemoryBasic_chromium.h']
|
||||
|
||||
@@ -74,7 +74,7 @@ protected:
|
||||
|
||||
virtual PTestBridgeMainSubChild*
|
||||
AllocPTestBridgeMainSubChild(Transport* transport,
|
||||
ProcessId otherProcess) MOZ_OVERRIDE
|
||||
ProcessId otherProcess) override
|
||||
{
|
||||
// This shouldn't be called. It's just a byproduct of testing that
|
||||
// the right code is generated for a bridged protocol that's also
|
||||
|
||||
@@ -8,10 +8,17 @@ loadRelativeToScript('CFG.js');
|
||||
|
||||
var sourceRoot = (os.getenv('SOURCE') || '') + '/'
|
||||
|
||||
var functionName;
|
||||
var functionBodies;
|
||||
|
||||
if (typeof scriptArgs[0] != 'string' || typeof scriptArgs[1] != 'string')
|
||||
throw "Usage: analyzeRoots.js <gcFunctions.lst> <gcEdges.txt> <suppressedFunctions.lst> <gcTypes.txt> [start end [tmpfile]]";
|
||||
throw "Usage: analyzeRoots.js [-f function_name] <gcFunctions.lst> <gcEdges.txt> <suppressedFunctions.lst> <gcTypes.txt> [start end [tmpfile]]";
|
||||
|
||||
var theFunctionNameToFind;
|
||||
if (scriptArgs[0] == '--function') {
|
||||
theFunctionNameToFind = scriptArgs[1];
|
||||
scriptArgs = scriptArgs.slice(2);
|
||||
}
|
||||
|
||||
var gcFunctionsFile = scriptArgs[0];
|
||||
var gcEdgesFile = scriptArgs[1];
|
||||
@@ -85,6 +92,21 @@ function expressionUsesVariable(exp, variable)
|
||||
return false;
|
||||
}
|
||||
|
||||
function expressionUsesVariableContents(exp, variable)
|
||||
{
|
||||
if (!("Exp" in exp))
|
||||
return false;
|
||||
for (var childExp of exp.Exp) {
|
||||
if (childExp.Kind == 'Drf') {
|
||||
if (expressionUsesVariable(childExp, variable))
|
||||
return true;
|
||||
} else if (expressionUsesVariableContents(childExp, variable)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect simple |return nullptr;| statements.
|
||||
function isReturningImmobileValue(edge, variable)
|
||||
{
|
||||
@@ -128,7 +150,7 @@ function edgeUsesVariable(edge, variable, body)
|
||||
return expressionUsesVariable(edge.Exp[1], variable) ? src : 0;
|
||||
|
||||
case "Assume":
|
||||
return expressionUsesVariable(edge.Exp[0], variable) ? src : 0;
|
||||
return expressionUsesVariableContents(edge.Exp[0], variable) ? src : 0;
|
||||
|
||||
case "Call":
|
||||
if (expressionUsesVariable(edge.Exp[0], variable))
|
||||
@@ -186,7 +208,7 @@ function edgeTakesVariableAddress(edge, variable)
|
||||
|
||||
function edgeKillsVariable(edge, variable)
|
||||
{
|
||||
// Direct assignments kill their lhs.
|
||||
// Direct assignments kill their lhs: var = value
|
||||
if (edge.Kind == "Assign") {
|
||||
var lhs = edge.Exp[0];
|
||||
if (lhs.Kind == "Var" && sameVariable(lhs.Variable, variable))
|
||||
@@ -250,49 +272,78 @@ function edgeCanGC(edge)
|
||||
{
|
||||
if (edge.Kind != "Call")
|
||||
return false;
|
||||
|
||||
var callee = edge.Exp[0];
|
||||
|
||||
while (callee.Kind == "Drf")
|
||||
callee = callee.Exp[0];
|
||||
|
||||
if (callee.Kind == "Var") {
|
||||
var variable = callee.Variable;
|
||||
assert(variable.Kind == "Func");
|
||||
var callee = mangled(variable.Name[0]);
|
||||
if ((callee in gcFunctions) || ((callee + internalMarker) in gcFunctions))
|
||||
return "'" + variable.Name[0] + "'";
|
||||
return null;
|
||||
|
||||
if (variable.Kind == "Func") {
|
||||
var callee = mangled(variable.Name[0]);
|
||||
if ((callee in gcFunctions) || ((callee + internalMarker) in gcFunctions))
|
||||
return "'" + variable.Name[0] + "'";
|
||||
return null;
|
||||
}
|
||||
|
||||
var varName = variable.Name[0];
|
||||
return indirectCallCannotGC(functionName, varName) ? null : "*" + varName;
|
||||
}
|
||||
assert(callee.Kind == "Drf");
|
||||
if (callee.Exp[0].Kind == "Fld") {
|
||||
var field = callee.Exp[0].Field;
|
||||
|
||||
if (callee.Kind == "Fld") {
|
||||
var field = callee.Field;
|
||||
var csuName = field.FieldCSU.Type.Name;
|
||||
var fullFieldName = csuName + "." + field.Name[0];
|
||||
if (fieldCallCannotGC(csuName, fullFieldName))
|
||||
return null;
|
||||
return (fullFieldName in suppressedFunctions) ? null : fullFieldName;
|
||||
}
|
||||
assert(callee.Exp[0].Kind == "Var");
|
||||
var varName = callee.Exp[0].Variable.Name[0];
|
||||
return indirectCallCannotGC(functionName, varName) ? null : "*" + varName;
|
||||
}
|
||||
|
||||
function variableUsePrecedesGC(suppressed, variable, worklist)
|
||||
// Search recursively through predecessors from a variable use, returning
|
||||
// whether a GC call is reachable (in the reverse direction; this means that
|
||||
// the variable use is reachable from the GC call, and therefore the variable
|
||||
// is live after the GC call), along with some additional information. What
|
||||
// info we want depends on whether the variable turns out to be live across any
|
||||
// GC call. We are looking for both hazards (unrooted variables live across GC
|
||||
// calls) and unnecessary roots (rooted variables that have no GC calls in
|
||||
// their live ranges.)
|
||||
//
|
||||
// If not:
|
||||
//
|
||||
// - 'minimumUse': the earliest point in each body that uses the variable, for
|
||||
// reporting on unnecessary roots.
|
||||
//
|
||||
// If so:
|
||||
//
|
||||
// - 'why': a path from the GC call to a use of the variable after the GC
|
||||
// call, chained through a 'why' field in the returned edge descriptor
|
||||
//
|
||||
// - 'gcInfo': a direct pointer to the GC call edge
|
||||
//
|
||||
function findGCBeforeVariableUse(suppressed, variable, worklist)
|
||||
{
|
||||
// Scan through all edges preceding an unrooted variable use, using an
|
||||
// explicit worklist. A worklist contains an incoming edge together with a
|
||||
// description of where it or one of its successors GC'd (if any).
|
||||
// explicit worklist, looking for a GC call. A worklist contains an
|
||||
// incoming edge together with a description of where it or one of its
|
||||
// successors GC'd (if any).
|
||||
|
||||
while (worklist.length) {
|
||||
var entry = worklist.pop();
|
||||
var body = entry.body, ppoint = entry.ppoint;
|
||||
var { body, ppoint, gcInfo } = entry;
|
||||
|
||||
if (body.seen) {
|
||||
if (ppoint in body.seen) {
|
||||
var seenEntry = body.seen[ppoint];
|
||||
if (!entry.gcInfo || seenEntry.gcInfo)
|
||||
if (!gcInfo || seenEntry.gcInfo)
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
body.seen = [];
|
||||
}
|
||||
body.seen[ppoint] = {body:body, gcInfo:entry.gcInfo};
|
||||
body.seen[ppoint] = {body: body, gcInfo: gcInfo};
|
||||
|
||||
if (ppoint == body.Index[0]) {
|
||||
if (body.BlockId.Kind == "Loop") {
|
||||
@@ -304,17 +355,17 @@ function variableUsePrecedesGC(suppressed, variable, worklist)
|
||||
if (sameBlockId(xbody.BlockId, parent.BlockId)) {
|
||||
assert(!found);
|
||||
found = true;
|
||||
worklist.push({body:xbody, ppoint:parent.Index,
|
||||
gcInfo:entry.gcInfo, why:entry});
|
||||
worklist.push({body: xbody, ppoint: parent.Index,
|
||||
gcInfo: gcInfo, why: entry});
|
||||
}
|
||||
}
|
||||
assert(found);
|
||||
}
|
||||
}
|
||||
} else if (variable.Kind == "Arg" && entry.gcInfo) {
|
||||
} else if (variable.Kind == "Arg" && gcInfo) {
|
||||
// The scope of arguments starts at the beginning of the
|
||||
// function
|
||||
return {gcInfo:entry.gcInfo, why:entry};
|
||||
return {gcInfo: gcInfo, why: entry};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,31 +376,45 @@ function variableUsePrecedesGC(suppressed, variable, worklist)
|
||||
for (var edge of predecessors[ppoint]) {
|
||||
var source = edge.Index[0];
|
||||
|
||||
if (edgeKillsVariable(edge, variable)) {
|
||||
if (entry.gcInfo)
|
||||
return {gcInfo: entry.gcInfo, why: {body:body, ppoint:source, gcInfo:entry.gcInfo, why:entry } }
|
||||
var edge_kills = edgeKillsVariable(edge, variable);
|
||||
var edge_uses = edgeUsesVariable(edge, variable, body);
|
||||
|
||||
if (edge_kills || edge_uses) {
|
||||
if (!body.minimumUse || source < body.minimumUse)
|
||||
body.minimumUse = source;
|
||||
}
|
||||
|
||||
if (edge_kills) {
|
||||
// This is a beginning of the variable's live range. If we can
|
||||
// reach a GC call from here, then we're done -- we have a path
|
||||
// from the beginning of the live range, through the GC call,
|
||||
// to a use after the GC call that proves its live range
|
||||
// extends at least that far.
|
||||
if (gcInfo)
|
||||
return {gcInfo: gcInfo, why: {body: body, ppoint: source, gcInfo: gcInfo, why: entry } }
|
||||
|
||||
// Otherwise, we want to continue searching for the true
|
||||
// minimumUse, for use in reporting unnecessary rooting, but we
|
||||
// truncate this particular branch of the search at this edge.
|
||||
continue;
|
||||
}
|
||||
|
||||
var gcInfo = entry.gcInfo;
|
||||
if (!gcInfo && !(source in body.suppressed) && !suppressed) {
|
||||
var gcName = edgeCanGC(edge, body);
|
||||
if (gcName)
|
||||
gcInfo = {name:gcName, body:body, ppoint:source};
|
||||
}
|
||||
|
||||
if (edgeUsesVariable(edge, variable, body)) {
|
||||
if (edge_uses) {
|
||||
// The live range starts at least this far back, so we're done
|
||||
// for the same reason as with edge_kills.
|
||||
if (gcInfo)
|
||||
return {gcInfo:gcInfo, why:entry};
|
||||
if (!body.minimumUse || source < body.minimumUse)
|
||||
body.minimumUse = source;
|
||||
}
|
||||
|
||||
if (edge.Kind == "Loop") {
|
||||
// propagate to exit points of the loop body, in addition to the
|
||||
// predecessor of the loop edge itself.
|
||||
// Additionally propagate the search into a loop body, starting
|
||||
// with the exit point.
|
||||
var found = false;
|
||||
for (var xbody of functionBodies) {
|
||||
if (sameBlockId(xbody.BlockId, edge.BlockId)) {
|
||||
@@ -362,6 +427,8 @@ function variableUsePrecedesGC(suppressed, variable, worklist)
|
||||
assert(found);
|
||||
break;
|
||||
}
|
||||
|
||||
// Propagate the search to the predecessors of this edge.
|
||||
worklist.push({body:body, ppoint:source, gcInfo:gcInfo, why:entry});
|
||||
}
|
||||
}
|
||||
@@ -384,14 +451,21 @@ function variableLiveAcrossGC(suppressed, variable)
|
||||
continue;
|
||||
for (var edge of body.PEdge) {
|
||||
var usePoint = edgeUsesVariable(edge, variable, body);
|
||||
// Example for !edgeKillsVariable:
|
||||
//
|
||||
// JSObject* obj = NewObject();
|
||||
// cangc();
|
||||
// obj = NewObject(); <-- uses 'obj', but kills previous value
|
||||
//
|
||||
if (usePoint && !edgeKillsVariable(edge, variable)) {
|
||||
// Found a use, possibly after a GC.
|
||||
var worklist = [{body:body, ppoint:usePoint, gcInfo:null, why:null}];
|
||||
var call = variableUsePrecedesGC(suppressed, variable, worklist);
|
||||
if (call) {
|
||||
call.afterGCUse = usePoint;
|
||||
return call;
|
||||
}
|
||||
var call = findGCBeforeVariableUse(suppressed, variable, worklist);
|
||||
if (!call)
|
||||
continue;
|
||||
|
||||
call.afterGCUse = usePoint;
|
||||
return call;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -612,24 +686,10 @@ var end = Math.min(minStream + each * batch - 1, maxStream);
|
||||
var theFunctionNameToFind;
|
||||
// var start = end = 12345;
|
||||
|
||||
for (var nameIndex = start; nameIndex <= end; nameIndex++) {
|
||||
var name = xdb.read_key(nameIndex);
|
||||
var functionName = name.readString();
|
||||
var data = xdb.read_entry(name);
|
||||
xdb.free_string(name);
|
||||
var json = data.readString();
|
||||
xdb.free_string(data);
|
||||
function process(name, json) {
|
||||
functionName = name;
|
||||
functionBodies = JSON.parse(json);
|
||||
|
||||
if (theFunctionNameToFind) {
|
||||
if (functionName == theFunctionNameToFind) {
|
||||
printErr("nameIndex = " + nameIndex);
|
||||
quit(1);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (var body of functionBodies)
|
||||
body.suppressed = [];
|
||||
for (var body of functionBodies) {
|
||||
@@ -638,3 +698,21 @@ for (var nameIndex = start; nameIndex <= end; nameIndex++) {
|
||||
}
|
||||
processBodies(functionName);
|
||||
}
|
||||
|
||||
if (theFunctionNameToFind) {
|
||||
var data = xdb.read_entry(theFunctionNameToFind);
|
||||
var json = data.readString();
|
||||
process(theFunctionNameToFind, json);
|
||||
xdb.free_string(data);
|
||||
quit(0);
|
||||
}
|
||||
|
||||
for (var nameIndex = start; nameIndex <= end; nameIndex++) {
|
||||
var name = xdb.read_key(nameIndex);
|
||||
var functionName = name.readString();
|
||||
var data = xdb.read_entry(name);
|
||||
xdb.free_string(name);
|
||||
var json = data.readString();
|
||||
process(functionName, json);
|
||||
xdb.free_string(data);
|
||||
}
|
||||
|
||||
@@ -244,21 +244,6 @@ function ignoreGCFunction(mangled)
|
||||
return false;
|
||||
}
|
||||
|
||||
function isRootedTypeName(name)
|
||||
{
|
||||
if (name == "mozilla::ErrorResult" ||
|
||||
name == "JSErrorResult" ||
|
||||
name == "WrappableJSErrorResult" ||
|
||||
name == "js::frontend::TokenStream" ||
|
||||
name == "js::frontend::TokenStream::Position" ||
|
||||
name == "ModuleCompiler" ||
|
||||
name == "JSAddonId")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function stripUCSAndNamespace(name)
|
||||
{
|
||||
if (name.startsWith('struct '))
|
||||
@@ -281,16 +266,36 @@ function stripUCSAndNamespace(name)
|
||||
return name;
|
||||
}
|
||||
|
||||
function isRootedPointerTypeName(name)
|
||||
function isRootedGCTypeName(name)
|
||||
{
|
||||
return (name == "JSAddonId");
|
||||
}
|
||||
|
||||
function isRootedGCPointerTypeName(name)
|
||||
{
|
||||
name = stripUCSAndNamespace(name);
|
||||
|
||||
if (name.startsWith('MaybeRooted<'))
|
||||
return /\(js::AllowGC\)1u>::RootType/.test(name);
|
||||
|
||||
if (name == "ErrorResult" ||
|
||||
name == "JSErrorResult" ||
|
||||
name == "WrappableJSErrorResult" ||
|
||||
name == "frontend::TokenStream" ||
|
||||
name == "frontend::TokenStream::Position" ||
|
||||
name == "ModuleCompiler")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return name.startsWith('Rooted') || name.startsWith('PersistentRooted');
|
||||
}
|
||||
|
||||
function isRootedTypeName(name)
|
||||
{
|
||||
return isRootedGCTypeName(name) || isRootedGCPointerTypeName(name);
|
||||
}
|
||||
|
||||
function isUnsafeStorage(typeName)
|
||||
{
|
||||
typeName = stripUCSAndNamespace(typeName);
|
||||
@@ -360,6 +365,10 @@ function listGCPointers() {
|
||||
'JS::Value',
|
||||
'jsid',
|
||||
|
||||
'js::TypeSet',
|
||||
'js::TypeSet::ObjectKey',
|
||||
'js::TypeSet::Type',
|
||||
|
||||
// AutoCheckCannotGC should also not be held live across a GC function.
|
||||
'JS::AutoCheckCannotGC',
|
||||
];
|
||||
|
||||
@@ -69,44 +69,60 @@ var gcTypes = {}; // map from parent struct => Set of GC typed children
|
||||
var gcPointers = {}; // map from parent struct => Set of GC typed children
|
||||
var nonGCTypes = {}; // set of types that would ordinarily be GC types but we are suppressing
|
||||
var nonGCPointers = {}; // set of types that would ordinarily be GC pointers but we are suppressing
|
||||
var gcFields = {};
|
||||
var gcFields = new Map;
|
||||
|
||||
// "typeName is a (pointer to a)*'depth' GC type because it contains a field
|
||||
// named 'child' of type 'why' (or pointer to 'why' if ptrdness == 1), which
|
||||
function stars(n) { return n ? '*' + stars(n-1) : '' };
|
||||
|
||||
// "typeName is a (pointer to a)^'typePtrLevel' GC type because it contains a field
|
||||
// named 'child' of type 'why' (or pointer to 'why' if fieldPtrLevel == 1), which is
|
||||
// itself a GCThing or GCPointer."
|
||||
function markGCType(typeName, child, why, depth, ptrdness)
|
||||
function markGCType(typeName, child, why, typePtrLevel, fieldPtrLevel, indent)
|
||||
{
|
||||
//printErr(`${indent}${typeName}${stars(typePtrLevel)} may be a gctype/ptr because of its child '${child}' of type ${why}${stars(fieldPtrLevel)}`);
|
||||
|
||||
// Some types, like UniquePtr, do not mark/trace/relocate their contained
|
||||
// pointers and so should not hold them live across a GC. UniquePtr in
|
||||
// particular should be the only thing pointing to a structure containing a
|
||||
// GCPointer, so nothing else can be tracing it and it'll die when the
|
||||
// UniquePtr goes out of scope. So we say that a UniquePtr's memory is just
|
||||
// as unsafe as the stack for storing GC pointers.
|
||||
if (!ptrdness && isUnsafeStorage(typeName)) {
|
||||
// GCPointer, so nothing else can possibly trace it and it'll die when the
|
||||
// UniquePtr goes out of scope. So we say that memory pointed to by a
|
||||
// UniquePtr is just as unsafe as the stack for storing GC pointers.
|
||||
if (!fieldPtrLevel && isUnsafeStorage(typeName)) {
|
||||
// The UniquePtr itself is on the stack but when you dereference the
|
||||
// contained pointer, you get to the unsafe memory that we are treating
|
||||
// as if it were the stack (aka depth 0). Note that
|
||||
// as if it were the stack (aka ptrLevel 0). Note that
|
||||
// UniquePtr<UniquePtr<JSObject*>> is fine, so we don't want to just
|
||||
// hardcode the depth.
|
||||
ptrdness = -1;
|
||||
// hardcode the ptrLevel.
|
||||
fieldPtrLevel = -1;
|
||||
}
|
||||
|
||||
depth += ptrdness;
|
||||
if (depth > 2)
|
||||
// Example: with:
|
||||
// struct Pair { JSObject* foo; int bar; };
|
||||
// struct { Pair** info }***
|
||||
// make a call to:
|
||||
// child='info' typePtrLevel=3 fieldPtrLevel=2
|
||||
// for a final ptrLevel of 5, used to later call:
|
||||
// child='foo' typePtrLevel=5 fieldPtrLevel=1
|
||||
//
|
||||
var ptrLevel = typePtrLevel + fieldPtrLevel;
|
||||
|
||||
// ...except when > 2 levels of pointers away from an actual GC thing, stop
|
||||
// searching the graph. (This would just be > 1, except that a UniquePtr
|
||||
// field might still have a GC pointer.)
|
||||
if (ptrLevel > 2)
|
||||
return;
|
||||
|
||||
if (depth == 0 && isRootedTypeName(typeName))
|
||||
if (ptrLevel == 0 && isRootedGCTypeName(typeName))
|
||||
return;
|
||||
if (depth == 1 && isRootedPointerTypeName(typeName))
|
||||
if (ptrLevel == 1 && isRootedGCPointerTypeName(typeName))
|
||||
return;
|
||||
|
||||
if (depth == 0) {
|
||||
if (ptrLevel == 0) {
|
||||
if (typeName in nonGCTypes)
|
||||
return;
|
||||
if (!(typeName in gcTypes))
|
||||
gcTypes[typeName] = new Set();
|
||||
gcTypes[typeName].add(why);
|
||||
} else if (depth == 1) {
|
||||
} else if (ptrLevel == 1) {
|
||||
if (typeName in nonGCPointers)
|
||||
return;
|
||||
if (!(typeName in gcPointers))
|
||||
@@ -114,32 +130,34 @@ function markGCType(typeName, child, why, depth, ptrdness)
|
||||
gcPointers[typeName].add(why);
|
||||
}
|
||||
|
||||
if (!(typeName in gcFields))
|
||||
gcFields[typeName] = new Map();
|
||||
gcFields[typeName].set(why, [ child, ptrdness ]);
|
||||
if (ptrLevel < 2) {
|
||||
if (!gcFields.has(typeName))
|
||||
gcFields.set(typeName, new Map());
|
||||
gcFields.get(typeName).set(child, [ why, fieldPtrLevel ]);
|
||||
}
|
||||
|
||||
if (typeName in structureParents) {
|
||||
for (var field of structureParents[typeName]) {
|
||||
var [ holderType, fieldName ] = field;
|
||||
markGCType(holderType, typeName, fieldName, depth, 0);
|
||||
markGCType(holderType, fieldName, typeName, ptrLevel, 0, indent + " ");
|
||||
}
|
||||
}
|
||||
if (typeName in pointerParents) {
|
||||
for (var field of pointerParents[typeName]) {
|
||||
var [ holderType, fieldName ] = field;
|
||||
markGCType(holderType, typeName, fieldName, depth, 1);
|
||||
markGCType(holderType, fieldName, typeName, ptrLevel, 1, indent + " ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addGCType(typeName, child, why, depth, ptrdness)
|
||||
function addGCType(typeName, child, why, depth, fieldPtrLevel)
|
||||
{
|
||||
markGCType(typeName, 'annotation', '<annotation>', 0, 0);
|
||||
markGCType(typeName, '<annotation>', '(annotation)', 0, 0, "");
|
||||
}
|
||||
|
||||
function addGCPointer(typeName)
|
||||
{
|
||||
markGCType(typeName, 'annotation', '<pointer-annotation>', 1, 0);
|
||||
markGCType(typeName, '<pointer-annotation>', '(annotation)', 1, 0, "");
|
||||
}
|
||||
|
||||
for (var type of listNonGCTypes())
|
||||
@@ -158,17 +176,19 @@ function explain(csu, indent, seen) {
|
||||
if (!seen)
|
||||
seen = new Set();
|
||||
seen.add(csu);
|
||||
if (!(csu in gcFields))
|
||||
if (!gcFields.has(csu))
|
||||
return;
|
||||
if (gcFields[csu].has('<annotation>')) {
|
||||
var fields = gcFields.get(csu);
|
||||
|
||||
if (fields.has('<annotation>')) {
|
||||
print(indent + "which is a GCThing because I said so");
|
||||
return;
|
||||
}
|
||||
if (gcFields[csu].has('<pointer-annotation>')) {
|
||||
if (fields.has('<pointer-annotation>')) {
|
||||
print(indent + "which is a GCPointer because I said so");
|
||||
return;
|
||||
}
|
||||
for (var [ field, [ child, ptrdness ] ] of gcFields[csu]) {
|
||||
for (var [ field, [ child, ptrdness ] ] of fields) {
|
||||
var inherit = "";
|
||||
if (field == "field:0")
|
||||
inherit = " (probably via inheritance)";
|
||||
|
||||
@@ -7525,8 +7525,6 @@ BytecodeEmitter::emitTree(ParseNode* pn)
|
||||
ParseNode* rest = nullptr;
|
||||
bool restIsDefn = false;
|
||||
if (fun->hasRest() && hasDefaults) {
|
||||
MOZ_ASSERT(!sc->asFunctionBox()->argumentsHasLocalBinding());
|
||||
|
||||
// Defaults with a rest parameter need special handling. The
|
||||
// rest parameter needs to be undefined while defaults are being
|
||||
// processed. To do this, we create the rest argument and let it
|
||||
|
||||
+1416
-663
File diff suppressed because it is too large
Load Diff
@@ -408,6 +408,9 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
|
||||
/* State specific to the kind of parse being performed. */
|
||||
ParseHandler handler;
|
||||
|
||||
void prepareNodeForMutation(Node node) { handler.prepareNodeForMutation(node); }
|
||||
void freeTree(Node node) { handler.freeTree(node); }
|
||||
|
||||
private:
|
||||
bool reportHelper(ParseReportKind kind, bool strict, uint32_t offset,
|
||||
unsigned errorNumber, va_list args);
|
||||
@@ -732,6 +735,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
|
||||
ParseNodeKind headKind);
|
||||
bool checkForHeadConstInitializers(Node pn1);
|
||||
|
||||
public:
|
||||
enum FunctionCallBehavior {
|
||||
PermitAssignmentToFunctionCalls,
|
||||
ForbidAssignmentToFunctionCalls
|
||||
@@ -740,6 +744,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
|
||||
bool isValidSimpleAssignmentTarget(Node node,
|
||||
FunctionCallBehavior behavior = ForbidAssignmentToFunctionCalls);
|
||||
|
||||
private:
|
||||
bool reportIfArgumentsEvalTarget(Node nameNode);
|
||||
bool reportIfNotValidSimpleAssignmentTarget(Node target, AssignmentFlavor flavor);
|
||||
|
||||
|
||||
@@ -172,6 +172,9 @@ class SyntaxParseHandler
|
||||
|
||||
static Node null() { return NodeFailure; }
|
||||
|
||||
void prepareNodeForMutation(Node node) {}
|
||||
void freeTree(Node node) {}
|
||||
|
||||
void trace(JSTracer* trc) {}
|
||||
|
||||
Node newName(PropertyName* name, uint32_t blockid, const TokenPos& pos, ExclusiveContext* cx) {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
// |jit-test| --no-ggc; allow-unhandlable-oom
|
||||
load(libdir + 'oomTest.js');
|
||||
oomTest((function(x) { assertEq(x + y + ex, 25); }));
|
||||
+14
-9
@@ -221,19 +221,24 @@ jit::ExceptionHandlerBailout(JSContext* cx, const InlineFrameIterator& frame,
|
||||
rfe->target = cx->runtime()->jitRuntime()->getBailoutTail()->raw();
|
||||
rfe->bailoutInfo = bailoutInfo;
|
||||
} else {
|
||||
// Bailout failed. If there was a fatal error, clear the
|
||||
// exception to turn this into an uncatchable error. If the
|
||||
// overrecursion check failed, continue popping all inline
|
||||
// frames and have the caller report an overrecursion error.
|
||||
// Bailout failed. If the overrecursion check failed, clear the
|
||||
// exception to turn this into an uncatchable error, continue popping
|
||||
// all inline frames and have the caller report the error.
|
||||
MOZ_ASSERT(!bailoutInfo);
|
||||
|
||||
if (!excInfo.propagatingIonExceptionForDebugMode())
|
||||
cx->clearPendingException();
|
||||
|
||||
if (retval == BAILOUT_RETURN_OVERRECURSED)
|
||||
if (retval == BAILOUT_RETURN_OVERRECURSED) {
|
||||
*overrecursed = true;
|
||||
else
|
||||
if (!excInfo.propagatingIonExceptionForDebugMode())
|
||||
cx->clearPendingException();
|
||||
} else {
|
||||
MOZ_ASSERT(retval == BAILOUT_RETURN_FATAL_ERROR);
|
||||
|
||||
// Crash for now so as not to complicate the exception handling code
|
||||
// further.
|
||||
if (cx->isThrowingOutOfMemory())
|
||||
CrashAtUnhandlableOOM("ExceptionHandlerBailout");
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
// Make the frame being bailed out the top profiled frame.
|
||||
|
||||
@@ -1471,8 +1471,10 @@ jit::BailoutIonToBaseline(JSContext* cx, JitActivation* activation, JitFrameIter
|
||||
|
||||
// Allocate buffer to hold stack replacement data.
|
||||
BaselineStackBuilder builder(iter, 1024);
|
||||
if (!builder.init())
|
||||
if (!builder.init()) {
|
||||
ReportOutOfMemory(cx);
|
||||
return BAILOUT_RETURN_FATAL_ERROR;
|
||||
}
|
||||
JitSpew(JitSpew_BaselineBailouts, " Incoming frame ptr = %p", builder.startFrame());
|
||||
|
||||
SnapshotIteratorForBailout snapIter(activation, iter);
|
||||
|
||||
@@ -460,6 +460,10 @@ struct AssemblerBufferWithConstantPools : public AssemblerBuffer<SliceSize, Inst
|
||||
// MacroAssembler before allocating any space.
|
||||
void initWithAllocator() {
|
||||
poolInfo_ = this->lifoAlloc_.template newArrayUninitialized<PoolInfo>(poolInfoSize_);
|
||||
if (!poolInfo_) {
|
||||
this->fail_oom();
|
||||
return;
|
||||
}
|
||||
|
||||
new (&pool_) Pool (poolMaxOffset_, pcBias_, this->lifoAlloc_);
|
||||
if (pool_.poolData() == nullptr)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Any copyright is dedicated to the Public Domain.
|
||||
// http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
var BUGNUMBER = 1183400;
|
||||
var summary =
|
||||
"Deletion of a && or || expression that constant-folds to a name must not " +
|
||||
"attempt to delete the name";
|
||||
|
||||
print(BUGNUMBER + ": " + summary);
|
||||
|
||||
/**************
|
||||
* BEGIN TEST *
|
||||
**************/
|
||||
|
||||
Object.defineProperty(this, "nonconfigurable", { value: 42 });
|
||||
assertEq(nonconfigurable, 42);
|
||||
|
||||
assertEq(delete nonconfigurable, false);
|
||||
assertEq(delete (true && nonconfigurable), true);
|
||||
|
||||
function nested()
|
||||
{
|
||||
assertEq(delete nonconfigurable, false);
|
||||
assertEq(delete (true && nonconfigurable), true);
|
||||
}
|
||||
nested();
|
||||
|
||||
function nestedStrict()
|
||||
{
|
||||
"use strict";
|
||||
assertEq(delete (true && nonconfigurable), true);
|
||||
}
|
||||
nestedStrict();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
|
||||
print("Tests complete");
|
||||
@@ -0,0 +1,28 @@
|
||||
// Any copyright is dedicated to the Public Domain.
|
||||
// http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
var BUGNUMBER = 1182373;
|
||||
var summary =
|
||||
"Don't let constant-folding in the MemberExpression part of a tagged " +
|
||||
"template cause an incorrect |this| be passed to the callee";
|
||||
|
||||
print(BUGNUMBER + ": " + summary);
|
||||
|
||||
/**************
|
||||
* BEGIN TEST *
|
||||
**************/
|
||||
|
||||
var prop = "global";
|
||||
|
||||
var obj = { prop: "obj", f: function() { return this.prop; } };
|
||||
|
||||
assertEq(obj.f``, "obj");
|
||||
assertEq((true ? obj.f : null)``, "global");
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
|
||||
print("Tests complete");
|
||||
@@ -0,0 +1,35 @@
|
||||
// Any copyright is dedicated to the Public Domain.
|
||||
// http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
var gTestfile = "if-constant-folding.js";
|
||||
var BUGNUMBER = 1183400;
|
||||
var summary =
|
||||
"Don't crash constant-folding an |if| governed by a truthy constant, whose " +
|
||||
"alternative statement is another |if|";
|
||||
|
||||
print(BUGNUMBER + ": " + summary);
|
||||
|
||||
/**************
|
||||
* BEGIN TEST *
|
||||
**************/
|
||||
|
||||
// Perform |if| constant folding correctly when the condition is constantly
|
||||
// truthy and the alternative statement is another |if|.
|
||||
if (true)
|
||||
{
|
||||
assertEq(true, true, "sanity");
|
||||
}
|
||||
else if (42)
|
||||
{
|
||||
assertEq(false, true, "not reached");
|
||||
assertEq(true, false, "also not reached");
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
|
||||
print("Tests complete");
|
||||
@@ -505,7 +505,7 @@ void nsBidi::GetDirProps(const char16_t *aText)
|
||||
} else if (state == SEEKING_STRONG_FOR_FSI) {
|
||||
if (stackLast <= NSBIDI_MAX_EXPLICIT_LEVEL) {
|
||||
dirProps[isolateStartStack[stackLast]] = LRI;
|
||||
flags |= LRI;
|
||||
flags |= DIRPROP_FLAG(LRI);
|
||||
}
|
||||
state = LOOKING_FOR_PDI;
|
||||
}
|
||||
@@ -518,7 +518,7 @@ void nsBidi::GetDirProps(const char16_t *aText)
|
||||
} else if (state == SEEKING_STRONG_FOR_FSI) {
|
||||
if (stackLast <= NSBIDI_MAX_EXPLICIT_LEVEL) {
|
||||
dirProps[isolateStartStack[stackLast]] = RLI;
|
||||
flags |= RLI;
|
||||
flags |= DIRPROP_FLAG(RLI);
|
||||
}
|
||||
state = LOOKING_FOR_PDI;
|
||||
}
|
||||
|
||||
+19
-25
@@ -4134,39 +4134,33 @@ IntrinsicSizeOffsets(nsIFrame* aFrame, bool aForISize)
|
||||
nsIFrame::IntrinsicISizeOffsetData result;
|
||||
WritingMode wm = aFrame->GetWritingMode();
|
||||
const nsStyleMargin* styleMargin = aFrame->StyleMargin();
|
||||
bool orthogonal = aForISize == wm.IsVertical();
|
||||
AddCoord(orthogonal ? styleMargin->mMargin.GetTop()
|
||||
: styleMargin->mMargin.GetLeft(),
|
||||
bool verticalAxis = aForISize == wm.IsVertical();
|
||||
AddCoord(verticalAxis ? styleMargin->mMargin.GetTop()
|
||||
: styleMargin->mMargin.GetLeft(),
|
||||
aFrame, &result.hMargin, &result.hPctMargin,
|
||||
false);
|
||||
AddCoord(orthogonal ? styleMargin->mMargin.GetBottom()
|
||||
: styleMargin->mMargin.GetRight(),
|
||||
AddCoord(verticalAxis ? styleMargin->mMargin.GetBottom()
|
||||
: styleMargin->mMargin.GetRight(),
|
||||
aFrame, &result.hMargin, &result.hPctMargin,
|
||||
false);
|
||||
|
||||
const nsStylePadding* stylePadding = aFrame->StylePadding();
|
||||
AddCoord(orthogonal ? stylePadding->mPadding.GetTop()
|
||||
: stylePadding->mPadding.GetLeft(),
|
||||
AddCoord(verticalAxis ? stylePadding->mPadding.GetTop()
|
||||
: stylePadding->mPadding.GetLeft(),
|
||||
aFrame, &result.hPadding, &result.hPctPadding,
|
||||
true);
|
||||
AddCoord(orthogonal ? stylePadding->mPadding.GetBottom()
|
||||
: stylePadding->mPadding.GetRight(),
|
||||
AddCoord(verticalAxis ? stylePadding->mPadding.GetBottom()
|
||||
: stylePadding->mPadding.GetRight(),
|
||||
aFrame, &result.hPadding, &result.hPctPadding,
|
||||
true);
|
||||
|
||||
if (aForISize) {
|
||||
const nsStyleBorder* styleBorder = aFrame->StyleBorder();
|
||||
result.hBorder += styleBorder->GetComputedBorderWidth(
|
||||
wm.PhysicalSideForInlineAxis(eLogicalEdgeStart));
|
||||
result.hBorder += styleBorder->GetComputedBorderWidth(
|
||||
wm.PhysicalSideForInlineAxis(eLogicalEdgeEnd));
|
||||
const nsStyleBorder* styleBorder = aFrame->StyleBorder();
|
||||
if (verticalAxis) {
|
||||
result.hBorder += styleBorder->GetComputedBorderWidth(NS_SIDE_TOP);
|
||||
result.hBorder += styleBorder->GetComputedBorderWidth(NS_SIDE_BOTTOM);
|
||||
} else {
|
||||
auto wm = aFrame->StyleVisibility()->mWritingMode;
|
||||
const nsStyleBorder* styleBorder = aFrame->StyleBorder();
|
||||
result.hBorder += styleBorder->GetComputedBorderWidth(
|
||||
WritingMode::PhysicalSideForBlockAxis(wm, eLogicalEdgeStart));
|
||||
result.hBorder += styleBorder->GetComputedBorderWidth(
|
||||
WritingMode::PhysicalSideForBlockAxis(wm, eLogicalEdgeEnd));
|
||||
result.hBorder += styleBorder->GetComputedBorderWidth(NS_SIDE_LEFT);
|
||||
result.hBorder += styleBorder->GetComputedBorderWidth(NS_SIDE_RIGHT);
|
||||
}
|
||||
|
||||
const nsStyleDisplay* disp = aFrame->StyleDisplay();
|
||||
@@ -4178,16 +4172,16 @@ IntrinsicSizeOffsets(nsIFrame* aFrame, bool aForISize)
|
||||
aFrame, disp->mAppearance,
|
||||
&border);
|
||||
result.hBorder =
|
||||
presContext->DevPixelsToAppUnits(orthogonal ? border.TopBottom()
|
||||
: border.LeftRight());
|
||||
presContext->DevPixelsToAppUnits(verticalAxis ? border.TopBottom()
|
||||
: border.LeftRight());
|
||||
|
||||
nsIntMargin padding;
|
||||
if (presContext->GetTheme()->GetWidgetPadding(presContext->DeviceContext(),
|
||||
aFrame, disp->mAppearance,
|
||||
&padding)) {
|
||||
result.hPadding =
|
||||
presContext->DevPixelsToAppUnits(orthogonal ? padding.TopBottom()
|
||||
: padding.LeftRight());
|
||||
presContext->DevPixelsToAppUnits(verticalAxis ? padding.TopBottom()
|
||||
: padding.LeftRight());
|
||||
result.hPctPadding = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ interface nsIDOMWindow;
|
||||
interface nsIPermission;
|
||||
interface nsISimpleEnumerator;
|
||||
|
||||
[scriptable, uuid(93a156f8-bcc8-4568-a214-389b073332dd)]
|
||||
[scriptable, uuid(0d1b8c65-0359-4a8c-b94d-4d3643b23e61)]
|
||||
interface nsIPermissionManager : nsISupports
|
||||
{
|
||||
/**
|
||||
@@ -106,17 +106,16 @@ interface nsIPermissionManager : nsISupports
|
||||
[optional] in int64_t expireTime);
|
||||
|
||||
/**
|
||||
* Remove permission information for a given host string and permission type.
|
||||
* The host string represents the exact entry in the permission list (such as
|
||||
* obtained from the enumerator), not a URI which that permission might apply
|
||||
* to.
|
||||
* Remove permission information for a given URI and permission type. This will
|
||||
* remove the permission for the entire host described by the uri, acting as the
|
||||
* opposite operation to the add() method.
|
||||
*
|
||||
* @param host the host to remove the permission for
|
||||
* @param uri the uri to remove the permission for
|
||||
* @param type a case-sensitive ASCII string, identifying the consumer.
|
||||
* The type must have been previously registered using the
|
||||
* add() method.
|
||||
*/
|
||||
void remove(in AUTF8String host,
|
||||
void remove(in nsIURI uri,
|
||||
in string type);
|
||||
|
||||
/**
|
||||
@@ -127,6 +126,13 @@ interface nsIPermissionManager : nsISupports
|
||||
*/
|
||||
void removeFromPrincipal(in nsIPrincipal principal, in string type);
|
||||
|
||||
/**
|
||||
* Remove the given permission from the permission manager.
|
||||
*
|
||||
* @param perm a permission obtained from the permission manager.
|
||||
*/
|
||||
void removePermission(in nsIPermission perm);
|
||||
|
||||
/**
|
||||
* Clear permission information for all websites.
|
||||
*/
|
||||
|
||||
@@ -48,7 +48,7 @@ this.LoginHelper = {
|
||||
aHostname.indexOf("\r") != -1 ||
|
||||
aHostname.indexOf("\n") != -1 ||
|
||||
aHostname.indexOf("\0") != -1) {
|
||||
throw "Invalid hostname";
|
||||
throw new Error("Invalid hostname");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -73,7 +73,7 @@ this.LoginHelper = {
|
||||
// Nulls are invalid, as they don't round-trip well.
|
||||
// Mostly not a formatting problem, although ".\0" can be quirky.
|
||||
if (badCharacterPresent(aLogin, "\0")) {
|
||||
throw "login values can't contain nulls";
|
||||
throw new Error("login values can't contain nulls");
|
||||
}
|
||||
|
||||
// In theory these nulls should just be rolled up into the encrypted
|
||||
@@ -82,26 +82,26 @@ this.LoginHelper = {
|
||||
// unexpected round-trip surprises.
|
||||
if (aLogin.username.indexOf("\0") != -1 ||
|
||||
aLogin.password.indexOf("\0") != -1) {
|
||||
throw "login values can't contain nulls";
|
||||
throw new Error("login values can't contain nulls");
|
||||
}
|
||||
|
||||
// Newlines are invalid for any field stored as plaintext.
|
||||
if (badCharacterPresent(aLogin, "\r") ||
|
||||
badCharacterPresent(aLogin, "\n")) {
|
||||
throw "login values can't contain newlines";
|
||||
throw new Error("login values can't contain newlines");
|
||||
}
|
||||
|
||||
// A line with just a "." can have special meaning.
|
||||
if (aLogin.usernameField == "." ||
|
||||
aLogin.formSubmitURL == ".") {
|
||||
throw "login values can't be periods";
|
||||
throw new Error("login values can't be periods");
|
||||
}
|
||||
|
||||
// A hostname with "\ \(" won't roundtrip.
|
||||
// eg host="foo (", realm="bar" --> "foo ( (bar)"
|
||||
// vs host="foo", realm=" (bar" --> "foo ( (bar)"
|
||||
if (aLogin.hostname.indexOf(" (") != -1) {
|
||||
throw "bad parens in hostname";
|
||||
throw new Error("bad parens in hostname");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -189,40 +189,40 @@ this.LoginHelper = {
|
||||
|
||||
// Fail if caller requests setting an unknown property.
|
||||
default:
|
||||
throw "Unexpected propertybag item: " + prop.name;
|
||||
throw new Error("Unexpected propertybag item: " + prop.name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw "newLoginData needs an expected interface!";
|
||||
throw new Error("newLoginData needs an expected interface!");
|
||||
}
|
||||
|
||||
// Sanity check the login
|
||||
if (newLogin.hostname == null || newLogin.hostname.length == 0) {
|
||||
throw "Can't add a login with a null or empty hostname.";
|
||||
throw new Error("Can't add a login with a null or empty hostname.");
|
||||
}
|
||||
|
||||
// For logins w/o a username, set to "", not null.
|
||||
if (newLogin.username == null) {
|
||||
throw "Can't add a login with a null username.";
|
||||
throw new Error("Can't add a login with a null username.");
|
||||
}
|
||||
|
||||
if (newLogin.password == null || newLogin.password.length == 0) {
|
||||
throw "Can't add a login with a null or empty password.";
|
||||
throw new Error("Can't add a login with a null or empty password.");
|
||||
}
|
||||
|
||||
if (newLogin.formSubmitURL || newLogin.formSubmitURL == "") {
|
||||
// We have a form submit URL. Can't have a HTTP realm.
|
||||
if (newLogin.httpRealm != null) {
|
||||
throw "Can't add a login with both a httpRealm and formSubmitURL.";
|
||||
throw new Error("Can't add a login with both a httpRealm and formSubmitURL.");
|
||||
}
|
||||
} else if (newLogin.httpRealm) {
|
||||
// We have a HTTP realm. Can't have a form submit URL.
|
||||
if (newLogin.formSubmitURL != null) {
|
||||
throw "Can't add a login with both a httpRealm and formSubmitURL.";
|
||||
throw new Error("Can't add a login with both a httpRealm and formSubmitURL.");
|
||||
}
|
||||
} else {
|
||||
// Need one or the other!
|
||||
throw "Can't add a login without a httpRealm or formSubmitURL.";
|
||||
throw new Error("Can't add a login without a httpRealm or formSubmitURL.");
|
||||
}
|
||||
|
||||
// Throws if there are bogus values.
|
||||
|
||||
@@ -8,16 +8,16 @@
|
||||
this.EXPORTED_SYMBOLS = [ "LoginManagerContent",
|
||||
"UserAutoCompleteResult" ];
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cc = Components.classes;
|
||||
const Cu = Components.utils;
|
||||
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
|
||||
"resource://gre/modules/LoginRecipes.jsm");
|
||||
|
||||
// These mirror signon.* prefs.
|
||||
var gEnabled, gDebug, gAutofillForms, gStoreWhenAutocompleteOff;
|
||||
|
||||
@@ -165,22 +165,48 @@ var LoginManagerContent = {
|
||||
},
|
||||
|
||||
receiveMessage: function (msg) {
|
||||
// Convert an array of logins in simple JS-object form to an array of
|
||||
// nsILoginInfo objects.
|
||||
function jsLoginsToXPCOM(logins) {
|
||||
return logins.map(login => {
|
||||
var formLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
|
||||
createInstance(Ci.nsILoginInfo);
|
||||
formLogin.init(login.hostname, login.formSubmitURL,
|
||||
login.httpRealm, login.username,
|
||||
login.password, login.usernameField,
|
||||
login.passwordField);
|
||||
return formLogin;
|
||||
});
|
||||
}
|
||||
|
||||
let request = this._takeRequest(msg);
|
||||
switch (msg.name) {
|
||||
case "RemoteLogins:loginsFound": {
|
||||
request.promise.resolve({ form: request.form,
|
||||
loginsFound: msg.data.logins });
|
||||
let loginsFound = jsLoginsToXPCOM(msg.data.logins);
|
||||
request.promise.resolve({
|
||||
form: request.form,
|
||||
loginsFound: loginsFound,
|
||||
recipes: msg.data.recipes,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "RemoteLogins:loginsAutoCompleted": {
|
||||
request.promise.resolve(msg.data.logins);
|
||||
let loginsFound = jsLoginsToXPCOM(msg.data.logins);
|
||||
request.promise.resolve(loginsFound);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_asyncFindLogins: function(form, options) {
|
||||
/**
|
||||
* Get relevant logins and recipes from the parent
|
||||
*
|
||||
* @param {HTMLFormElement} form - form to get login data for
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.showMasterPassword - whether to show a master password prompt
|
||||
*/
|
||||
_getLoginDataFromParent: function(form, options) {
|
||||
let doc = form.ownerDocument;
|
||||
let win = doc.defaultView;
|
||||
|
||||
@@ -246,16 +272,16 @@ var LoginManagerContent = {
|
||||
return;
|
||||
|
||||
log("onFormPassword for", form.ownerDocument.documentURI);
|
||||
this._asyncFindLogins(form, { showMasterPassword: true })
|
||||
this._getLoginDataFromParent(form, { showMasterPassword: true })
|
||||
.then(this.loginsFound.bind(this))
|
||||
.then(null, Cu.reportError);
|
||||
},
|
||||
|
||||
loginsFound: function({ form, loginsFound }) {
|
||||
loginsFound: function({ form, loginsFound, recipes }) {
|
||||
let doc = form.ownerDocument;
|
||||
let autofillForm = gAutofillForms && !PrivateBrowsingUtils.isContentWindowPrivate(doc.defaultView);
|
||||
|
||||
this._fillForm(form, autofillForm, false, false, loginsFound);
|
||||
this._fillForm(form, autofillForm, false, false, loginsFound, recipes);
|
||||
},
|
||||
|
||||
/*
|
||||
@@ -296,9 +322,9 @@ var LoginManagerContent = {
|
||||
var [usernameField, passwordField, ignored] =
|
||||
this._getFormFields(acForm, false);
|
||||
if (usernameField == acInputField && passwordField) {
|
||||
this._asyncFindLogins(acForm, { showMasterPassword: false })
|
||||
.then(({ form, loginsFound }) => {
|
||||
this._fillForm(form, true, true, true, loginsFound);
|
||||
this._getLoginDataFromParent(acForm, { showMasterPassword: false })
|
||||
.then(({ form, loginsFound, recipes }) => {
|
||||
this._fillForm(form, true, true, true, loginsFound, recipes);
|
||||
})
|
||||
.then(null, Cu.reportError);
|
||||
} else {
|
||||
@@ -366,14 +392,15 @@ var LoginManagerContent = {
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _getFormFields
|
||||
*
|
||||
/**
|
||||
* Returns the username and password fields found in the form.
|
||||
* Can handle complex forms by trying to figure out what the
|
||||
* relevant fields are.
|
||||
*
|
||||
* Returns: [usernameField, newPasswordField, oldPasswordField]
|
||||
* @param {HTMLFormElement} form
|
||||
* @param {bool} isSubmission
|
||||
* @param {Set} recipes
|
||||
* @return {Array} [usernameField, newPasswordField, oldPasswordField]
|
||||
*
|
||||
* usernameField may be null.
|
||||
* newPasswordField will always be non-null.
|
||||
@@ -382,37 +409,69 @@ var LoginManagerContent = {
|
||||
* change-password field, with oldPasswordField containing the password
|
||||
* that is being changed.
|
||||
*/
|
||||
_getFormFields : function (form, isSubmission) {
|
||||
_getFormFields : function (form, isSubmission, recipes) {
|
||||
var usernameField = null;
|
||||
var pwFields = null;
|
||||
var fieldOverrideRecipe = LoginRecipesContent.getFieldOverrides(recipes, form);
|
||||
if (fieldOverrideRecipe) {
|
||||
var pwOverrideField = LoginRecipesContent.queryLoginField(
|
||||
form,
|
||||
fieldOverrideRecipe.passwordSelector
|
||||
);
|
||||
if (pwOverrideField) {
|
||||
pwFields = [{
|
||||
index : [...pwOverrideField.form.elements].indexOf(pwOverrideField),
|
||||
element : pwOverrideField,
|
||||
}];
|
||||
}
|
||||
|
||||
// Locate the password field(s) in the form. Up to 3 supported.
|
||||
// If there's no password field, there's nothing for us to do.
|
||||
var pwFields = this._getPasswordFields(form, isSubmission);
|
||||
if (!pwFields)
|
||||
var usernameOverrideField = LoginRecipesContent.queryLoginField(
|
||||
form,
|
||||
fieldOverrideRecipe.usernameSelector
|
||||
);
|
||||
if (usernameOverrideField) {
|
||||
usernameField = usernameOverrideField;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pwFields) {
|
||||
// Locate the password field(s) in the form. Up to 3 supported.
|
||||
// If there's no password field, there's nothing for us to do.
|
||||
pwFields = this._getPasswordFields(form, isSubmission);
|
||||
}
|
||||
|
||||
if (!pwFields) {
|
||||
return [null, null, null];
|
||||
}
|
||||
|
||||
|
||||
// Locate the username field in the form by searching backwards
|
||||
// from the first passwordfield, assume the first text field is the
|
||||
// username. We might not find a username field if the user is
|
||||
// already logged in to the site.
|
||||
for (var i = pwFields[0].index - 1; i >= 0; i--) {
|
||||
var element = form.elements[i];
|
||||
if (this._isUsernameFieldType(element)) {
|
||||
usernameField = element;
|
||||
break;
|
||||
if (!usernameField) {
|
||||
// Locate the username field in the form by searching backwards
|
||||
// from the first passwordfield, assume the first text field is the
|
||||
// username. We might not find a username field if the user is
|
||||
// already logged in to the site.
|
||||
for (var i = pwFields[0].index - 1; i >= 0; i--) {
|
||||
var element = form.elements[i];
|
||||
if (this._isUsernameFieldType(element)) {
|
||||
usernameField = element;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!usernameField)
|
||||
log("(form -- no username field found)");
|
||||
|
||||
else
|
||||
log("Username field id/name/value is: ", usernameField.id, " / ",
|
||||
usernameField.name, " / ", usernameField.value);
|
||||
|
||||
// If we're not submitting a form (it's a page load), there are no
|
||||
// password field values for us to use for identifying fields. So,
|
||||
// just assume the first password field is the one to be filled in.
|
||||
if (!isSubmission || pwFields.length == 1)
|
||||
return [usernameField, pwFields[0].element, null];
|
||||
if (!isSubmission || pwFields.length == 1) {
|
||||
var passwordField = pwFields[0].element;
|
||||
log("Password field id/name is: ", passwordField.id, " / ", passwordField.name);
|
||||
return [usernameField, passwordField, null];
|
||||
}
|
||||
|
||||
|
||||
// Try to figure out WTF is in the form based on the password values.
|
||||
@@ -455,6 +514,8 @@ var LoginManagerContent = {
|
||||
}
|
||||
}
|
||||
|
||||
log("Password field (new) id/name is: ", newPasswordField.id, " / ", newPasswordField.name);
|
||||
log("Password field (old) id/name is: ", oldPasswordField.id, " / ", oldPasswordField.name);
|
||||
return [usernameField, newPasswordField, oldPasswordField];
|
||||
},
|
||||
|
||||
@@ -559,21 +620,21 @@ var LoginManagerContent = {
|
||||
{ openerWin: opener });
|
||||
},
|
||||
|
||||
/*
|
||||
* _fillform
|
||||
/**
|
||||
* Attempt to find the username and password fields in a form, and fill them
|
||||
* in using the provided logins and recipes.
|
||||
*
|
||||
* Fill the form with the provided login information.
|
||||
* The logins are returned so they can be reused for
|
||||
* optimization. Success of action is also returned in format
|
||||
* [success, foundLogins].
|
||||
*
|
||||
* - autofillForm denotes if we should fill the form in automatically
|
||||
* - userTriggered is an indication of whether this filling was triggered by
|
||||
* the user
|
||||
* - foundLogins is an array of nsILoginInfo
|
||||
* @param {HTMLFormElement} form
|
||||
* @param {bool} autofillForm denotes if we should fill the form in automatically
|
||||
* @param {bool} clobberPassword controls if an existing password value can be
|
||||
* overwritten
|
||||
* @param {bool} userTriggered is an indication of whether this filling was triggered by
|
||||
* the user
|
||||
* @param {nsILoginInfo[]} foundLogins is an array of nsILoginInfo that could be used for the form
|
||||
* @param {Set} recipes that could be used to affect how the form is filled
|
||||
*/
|
||||
_fillForm : function (form, autofillForm, clobberPassword,
|
||||
userTriggered, foundLogins) {
|
||||
userTriggered, foundLogins, recipes) {
|
||||
let ignoreAutocomplete = true;
|
||||
const AUTOFILL_RESULT = {
|
||||
FILLED: 0,
|
||||
@@ -586,7 +647,6 @@ var LoginManagerContent = {
|
||||
MULTIPLE_LOGINS: 7,
|
||||
NO_AUTOFILL_FORMS: 8,
|
||||
AUTOCOMPLETE_OFF: 9,
|
||||
UNKNOWN_FAILURE: 10,
|
||||
};
|
||||
|
||||
function recordAutofillResult(result) {
|
||||
@@ -598,118 +658,98 @@ var LoginManagerContent = {
|
||||
autofillResultHist.add(result);
|
||||
}
|
||||
|
||||
// Nothing to do if we have no matching logins available.
|
||||
if (foundLogins.length == 0) {
|
||||
// We don't log() here since this is a very common case.
|
||||
recordAutofillResult(AUTOFILL_RESULT.NO_SAVED_LOGINS);
|
||||
return [false, foundLogins];
|
||||
}
|
||||
try {
|
||||
// Nothing to do if we have no matching logins available.
|
||||
if (foundLogins.length == 0) {
|
||||
// We don't log() here since this is a very common case.
|
||||
recordAutofillResult(AUTOFILL_RESULT.NO_SAVED_LOGINS);
|
||||
return;
|
||||
}
|
||||
|
||||
// Heuristically determine what the user/pass fields are
|
||||
// We do this before checking to see if logins are stored,
|
||||
// so that the user isn't prompted for a master password
|
||||
// without need.
|
||||
var [usernameField, passwordField, ignored] =
|
||||
this._getFormFields(form, false);
|
||||
// Heuristically determine what the user/pass fields are
|
||||
// We do this before checking to see if logins are stored,
|
||||
// so that the user isn't prompted for a master password
|
||||
// without need.
|
||||
var [usernameField, passwordField, ignored] =
|
||||
this._getFormFields(form, false);
|
||||
|
||||
// Need a valid password field to do anything.
|
||||
if (passwordField == null) {
|
||||
log("not filling form, no password field found");
|
||||
recordAutofillResult(AUTOFILL_RESULT.NO_PASSWORD_FIELD);
|
||||
return [false, foundLogins];
|
||||
}
|
||||
// Need a valid password field to do anything.
|
||||
if (passwordField == null) {
|
||||
log("not filling form, no password field found");
|
||||
recordAutofillResult(AUTOFILL_RESULT.NO_PASSWORD_FIELD);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the password field is disabled or read-only, there's nothing to do.
|
||||
if (passwordField.disabled || passwordField.readOnly) {
|
||||
log("not filling form, password field disabled or read-only");
|
||||
recordAutofillResult(AUTOFILL_RESULT.PASSWORD_DISABLED_READONLY);
|
||||
return [false, foundLogins];
|
||||
}
|
||||
// If the password field is disabled or read-only, there's nothing to do.
|
||||
if (passwordField.disabled || passwordField.readOnly) {
|
||||
log("not filling form, password field disabled or read-only");
|
||||
recordAutofillResult(AUTOFILL_RESULT.PASSWORD_DISABLED_READONLY);
|
||||
return;
|
||||
}
|
||||
|
||||
// Discard logins which have username/password values that don't
|
||||
// fit into the fields (as specified by the maxlength attribute).
|
||||
// The user couldn't enter these values anyway, and it helps
|
||||
// with sites that have an extra PIN to be entered (bug 391514)
|
||||
var maxUsernameLen = Number.MAX_VALUE;
|
||||
var maxPasswordLen = Number.MAX_VALUE;
|
||||
var isAutocompleteOff = false;
|
||||
if (this._isAutocompleteDisabled(form) ||
|
||||
this._isAutocompleteDisabled(usernameField) ||
|
||||
this._isAutocompleteDisabled(passwordField)) {
|
||||
isAutocompleteOff = true;
|
||||
}
|
||||
|
||||
// If attribute wasn't set, default is -1.
|
||||
if (usernameField && usernameField.maxLength >= 0)
|
||||
maxUsernameLen = usernameField.maxLength;
|
||||
if (passwordField.maxLength >= 0)
|
||||
maxPasswordLen = passwordField.maxLength;
|
||||
// Discard logins which have username/password values that don't
|
||||
// fit into the fields (as specified by the maxlength attribute).
|
||||
// The user couldn't enter these values anyway, and it helps
|
||||
// with sites that have an extra PIN to be entered (bug 391514)
|
||||
var maxUsernameLen = Number.MAX_VALUE;
|
||||
var maxPasswordLen = Number.MAX_VALUE;
|
||||
|
||||
foundLogins = foundLogins.map(login => {
|
||||
var formLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
|
||||
createInstance(Ci.nsILoginInfo);
|
||||
formLogin.init(login.hostname, login.formSubmitURL,
|
||||
login.httpRealm, login.username,
|
||||
login.password, login.usernameField,
|
||||
login.passwordField);
|
||||
return formLogin;
|
||||
});
|
||||
var logins = foundLogins.filter(function (l) {
|
||||
var fit = (l.username.length <= maxUsernameLen &&
|
||||
l.password.length <= maxPasswordLen);
|
||||
if (!fit)
|
||||
log("Ignored", l.username, "login: won't fit");
|
||||
// If attribute wasn't set, default is -1.
|
||||
if (usernameField && usernameField.maxLength >= 0)
|
||||
maxUsernameLen = usernameField.maxLength;
|
||||
if (passwordField.maxLength >= 0)
|
||||
maxPasswordLen = passwordField.maxLength;
|
||||
|
||||
return fit;
|
||||
}, this);
|
||||
var logins = foundLogins.filter(function (l) {
|
||||
var fit = (l.username.length <= maxUsernameLen &&
|
||||
l.password.length <= maxPasswordLen);
|
||||
if (!fit)
|
||||
log("Ignored", l.username, "login: won't fit");
|
||||
|
||||
if (logins.length == 0) {
|
||||
log("form not filled, none of the logins fit in the field");
|
||||
recordAutofillResult(AUTOFILL_RESULT.NO_LOGINS_FIT);
|
||||
return [false, foundLogins];
|
||||
}
|
||||
return fit;
|
||||
}, this);
|
||||
|
||||
// The reason we didn't end up filling the form, if any. We include
|
||||
// this in the formInfo object we send with the passwordmgr-found-logins
|
||||
// notification. See the _notifyFoundLogins docs for possible values.
|
||||
var didntFillReason = null;
|
||||
if (logins.length == 0) {
|
||||
log("form not filled, none of the logins fit in the field");
|
||||
recordAutofillResult(AUTOFILL_RESULT.NO_LOGINS_FIT);
|
||||
return;
|
||||
}
|
||||
|
||||
// Attach autocomplete stuff to the username field, if we have
|
||||
// one. This is normally used to select from multiple accounts,
|
||||
// but even with one account we should refill if the user edits.
|
||||
if (usernameField)
|
||||
this._formFillService.markAsLoginManagerField(usernameField);
|
||||
// Attach autocomplete stuff to the username field, if we have
|
||||
// one. This is normally used to select from multiple accounts,
|
||||
// but even with one account we should refill if the user edits.
|
||||
if (usernameField)
|
||||
this._formFillService.markAsLoginManagerField(usernameField);
|
||||
|
||||
// Don't clobber an existing password.
|
||||
if (passwordField.value && !clobberPassword) {
|
||||
didntFillReason = "existingPassword";
|
||||
this._notifyFoundLogins(didntFillReason, usernameField,
|
||||
passwordField, foundLogins, null);
|
||||
log("form not filled, the password field was already filled");
|
||||
recordAutofillResult(AUTOFILL_RESULT.EXISTING_PASSWORD);
|
||||
return [false, foundLogins];
|
||||
}
|
||||
// Don't clobber an existing password.
|
||||
if (passwordField.value && !clobberPassword) {
|
||||
log("form not filled, the password field was already filled");
|
||||
recordAutofillResult(AUTOFILL_RESULT.EXISTING_PASSWORD);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the form has an autocomplete=off attribute in play, don't
|
||||
// fill in the login automatically. We check this after attaching
|
||||
// the autocomplete stuff to the username field, so the user can
|
||||
// still manually select a login to be filled in.
|
||||
var isFormDisabled = false;
|
||||
if (!ignoreAutocomplete &&
|
||||
(this._isAutocompleteDisabled(form) ||
|
||||
this._isAutocompleteDisabled(usernameField) ||
|
||||
this._isAutocompleteDisabled(passwordField))) {
|
||||
// Select a login to use for filling in the form.
|
||||
var selectedLogin;
|
||||
if (usernameField && (usernameField.value || usernameField.disabled || usernameField.readOnly)) {
|
||||
// If username was specified in the field, it's disabled or it's readOnly, only fill in the
|
||||
// password if we find a matching login.
|
||||
var username = usernameField.value.toLowerCase();
|
||||
|
||||
isFormDisabled = true;
|
||||
log("form not filled, has autocomplete=off");
|
||||
}
|
||||
let matchingLogins = logins.filter(function(l)
|
||||
l.username.toLowerCase() == username);
|
||||
if (matchingLogins.length == 0) {
|
||||
log("Password not filled. None of the stored logins match the username already present.");
|
||||
recordAutofillResult(AUTOFILL_RESULT.EXISTING_USERNAME);
|
||||
return;
|
||||
}
|
||||
|
||||
// Variable such that we reduce code duplication and can be sure we
|
||||
// should be firing notifications if and only if we can fill the form.
|
||||
var selectedLogin = null;
|
||||
|
||||
if (usernameField && (usernameField.value || usernameField.disabled || usernameField.readOnly)) {
|
||||
// If username was specified in the field, it's disabled or it's readOnly, only fill in the
|
||||
// password if we find a matching login.
|
||||
var username = usernameField.value.toLowerCase();
|
||||
|
||||
let matchingLogins = logins.filter(function(l)
|
||||
l.username.toLowerCase() == username);
|
||||
if (matchingLogins.length) {
|
||||
// If there are multiple, and one matches case, use it
|
||||
for (let l of matchingLogins) {
|
||||
if (l.username == usernameField.value) {
|
||||
@@ -720,32 +760,42 @@ var LoginManagerContent = {
|
||||
if (!selectedLogin) {
|
||||
selectedLogin = matchingLogins[0];
|
||||
}
|
||||
} else if (logins.length == 1) {
|
||||
selectedLogin = logins[0];
|
||||
} else {
|
||||
didntFillReason = "existingUsername";
|
||||
log("Password not filled. None of the stored logins match the username already present.");
|
||||
}
|
||||
} else if (logins.length == 1) {
|
||||
selectedLogin = logins[0];
|
||||
} else {
|
||||
// We have multiple logins. Handle a special case here, for sites
|
||||
// which have a normal user+pass login *and* a password-only login
|
||||
// (eg, a PIN). Prefer the login that matches the type of the form
|
||||
// (user+pass or pass-only) when there's exactly one that matches.
|
||||
let matchingLogins;
|
||||
if (usernameField)
|
||||
matchingLogins = logins.filter(function(l) l.username);
|
||||
else
|
||||
matchingLogins = logins.filter(function(l) !l.username);
|
||||
if (matchingLogins.length == 1) {
|
||||
selectedLogin = matchingLogins[0];
|
||||
} else {
|
||||
didntFillReason = "multipleLogins";
|
||||
log("Multiple logins for form, so not filling any.");
|
||||
}
|
||||
}
|
||||
// We have multiple logins. Handle a special case here, for sites
|
||||
// which have a normal user+pass login *and* a password-only login
|
||||
// (eg, a PIN). Prefer the login that matches the type of the form
|
||||
// (user+pass or pass-only) when there's exactly one that matches.
|
||||
let matchingLogins;
|
||||
if (usernameField)
|
||||
matchingLogins = logins.filter(function(l) l.username);
|
||||
else
|
||||
matchingLogins = logins.filter(function(l) !l.username);
|
||||
|
||||
if (matchingLogins.length != 1) {
|
||||
log("Multiple logins for form, so not filling any.");
|
||||
recordAutofillResult(AUTOFILL_RESULT.MULTIPLE_LOGINS);
|
||||
return;
|
||||
}
|
||||
|
||||
selectedLogin = matchingLogins[0];
|
||||
}
|
||||
|
||||
// We will always have a selectedLogin at this point.
|
||||
|
||||
if (!autofillForm) {
|
||||
log("autofillForms=false but form can be filled");
|
||||
recordAutofillResult(AUTOFILL_RESULT.NO_AUTOFILL_FORMS);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAutocompleteOff && !ignoreAutocomplete) {
|
||||
log("Not filling the login because we're respecting autocomplete=off");
|
||||
recordAutofillResult(AUTOFILL_RESULT.AUTOCOMPLETE_OFF);
|
||||
return;
|
||||
}
|
||||
|
||||
var didFillForm = false;
|
||||
if (selectedLogin && autofillForm && !isFormDisabled) {
|
||||
// Fill the form
|
||||
|
||||
if (usernameField) {
|
||||
@@ -766,106 +816,15 @@ var LoginManagerContent = {
|
||||
if (passwordField.value != selectedLogin.password) {
|
||||
passwordField.setUserInput(selectedLogin.password);
|
||||
}
|
||||
didFillForm = true;
|
||||
} else if (selectedLogin && !autofillForm) {
|
||||
// For when autofillForm is false, but we still have the information
|
||||
// to fill a form, we notify observers.
|
||||
didntFillReason = "noAutofillForms";
|
||||
Services.obs.notifyObservers(form, "passwordmgr-found-form", didntFillReason);
|
||||
log("autofillForms=false but form can be filled; notified observers");
|
||||
} else if (selectedLogin && isFormDisabled) {
|
||||
// For when autocomplete is off, but we still have the information
|
||||
// to fill a form, we notify observers.
|
||||
didntFillReason = "autocompleteOff";
|
||||
Services.obs.notifyObservers(form, "passwordmgr-found-form", didntFillReason);
|
||||
log("autocomplete=off but form can be filled; notified observers");
|
||||
}
|
||||
|
||||
this._notifyFoundLogins(didntFillReason, usernameField, passwordField,
|
||||
foundLogins, selectedLogin);
|
||||
|
||||
if (didFillForm) {
|
||||
recordAutofillResult(AUTOFILL_RESULT.FILLED);
|
||||
let doc = form.ownerDocument;
|
||||
let win = doc.defaultView;
|
||||
let messageManager = messageManagerFromWindow(win);
|
||||
messageManager.sendAsyncMessage("LoginStats:LoginFillSuccessful");
|
||||
} else {
|
||||
let autofillResult = AUTOFILL_RESULT.UNKNOWN_FAILURE;
|
||||
switch (didntFillReason) {
|
||||
// existingPassword is already handled above
|
||||
case "existingUsername":
|
||||
autofillResult = AUTOFILL_RESULT.EXISTING_USERNAME;
|
||||
break;
|
||||
case "multipleLogins":
|
||||
autofillResult = AUTOFILL_RESULT.MULTIPLE_LOGINS;
|
||||
break;
|
||||
case "noAutofillForms":
|
||||
autofillResult = AUTOFILL_RESULT.NO_AUTOFILL_FORMS;
|
||||
break;
|
||||
case "autocompleteOff":
|
||||
autofillResult = AUTOFILL_RESULT.AUTOCOMPLETE_OFF;
|
||||
break;
|
||||
}
|
||||
recordAutofillResult(autofillResult);
|
||||
} finally {
|
||||
Services.obs.notifyObservers(form, "passwordmgr-processed-form", null);
|
||||
}
|
||||
|
||||
return [didFillForm, foundLogins];
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Notify observers about an attempt to fill a form that resulted in some
|
||||
* saved logins being found for the form.
|
||||
*
|
||||
* This does not get called if the login manager attempts to fill a form
|
||||
* but does not find any saved logins. It does, however, get called when
|
||||
* the login manager does find saved logins whether or not it actually
|
||||
* fills the form with one of them.
|
||||
*
|
||||
* @param didntFillReason {String}
|
||||
* the reason the login manager didn't fill the form, if any;
|
||||
* if the value of this parameter is null, then the form was filled;
|
||||
* otherwise, this parameter will be one of these values:
|
||||
* existingUsername: the username field already contains a username
|
||||
* that doesn't match any stored usernames
|
||||
* existingPassword: the password field already contains a password
|
||||
* autocompleteOff: autocomplete has been disabled for the form
|
||||
* or its username or password fields
|
||||
* multipleLogins: we have multiple logins for the form
|
||||
* noAutofillForms: the autofillForms pref is set to false
|
||||
*
|
||||
* @param usernameField {HTMLInputElement}
|
||||
* the username field detected by the login manager, if any;
|
||||
* otherwise null
|
||||
*
|
||||
* @param passwordField {HTMLInputElement}
|
||||
* the password field detected by the login manager
|
||||
*
|
||||
* @param foundLogins {Array}
|
||||
* an array of nsILoginInfos that can be used to fill the form
|
||||
*
|
||||
* @param selectedLogin {nsILoginInfo}
|
||||
* the nsILoginInfo that was/would be used to fill the form, if any;
|
||||
* otherwise null; whether or not it was actually used depends on
|
||||
* the value of the didntFillReason parameter
|
||||
*/
|
||||
_notifyFoundLogins : function (didntFillReason, usernameField,
|
||||
passwordField, foundLogins, selectedLogin) {
|
||||
// We need .setProperty(), which is a method on the original
|
||||
// nsIWritablePropertyBag. Strangley enough, nsIWritablePropertyBag2
|
||||
// doesn't inherit from that, so the additional QI is needed.
|
||||
let formInfo = Cc["@mozilla.org/hash-property-bag;1"].
|
||||
createInstance(Ci.nsIWritablePropertyBag2).
|
||||
QueryInterface(Ci.nsIWritablePropertyBag);
|
||||
|
||||
formInfo.setPropertyAsACString("didntFillReason", didntFillReason);
|
||||
formInfo.setPropertyAsInterface("usernameField", usernameField);
|
||||
formInfo.setPropertyAsInterface("passwordField", passwordField);
|
||||
formInfo.setProperty("foundLogins", foundLogins.concat());
|
||||
formInfo.setPropertyAsInterface("selectedLogin", selectedLogin);
|
||||
|
||||
Services.obs.notifyObservers(formInfo, "passwordmgr-found-logins", null);
|
||||
},
|
||||
|
||||
};
|
||||
@@ -964,7 +923,7 @@ UserAutoCompleteResult.prototype = {
|
||||
|
||||
getValueAt : function (index) {
|
||||
if (index < 0 || index >= this.logins.length)
|
||||
throw "Index out of range.";
|
||||
throw new Error("Index out of range.");
|
||||
|
||||
return this.logins[index].username;
|
||||
},
|
||||
@@ -991,7 +950,7 @@ UserAutoCompleteResult.prototype = {
|
||||
|
||||
removeValueAt : function (index, removeFromDB) {
|
||||
if (index < 0 || index >= this.logins.length)
|
||||
throw "Index out of range.";
|
||||
throw new Error("Index out of range.");
|
||||
|
||||
var [removedLogin] = this.logins.splice(index, 1);
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
|
||||
Cu.importGlobalProperties(["URL"]);
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UserAutoCompleteResult",
|
||||
"resource://gre/modules/LoginManagerContent.jsm");
|
||||
@@ -21,36 +21,6 @@ this.EXPORTED_SYMBOLS = [ "LoginManagerParent", "PasswordsMetricsProvider" ];
|
||||
|
||||
var gDebug;
|
||||
|
||||
function log(...pieces) {
|
||||
function generateLogMessage(args) {
|
||||
let strings = ['Login Manager (parent):'];
|
||||
|
||||
args.forEach(function(arg) {
|
||||
if (typeof arg === 'string') {
|
||||
strings.push(arg);
|
||||
} else if (typeof arg === 'undefined') {
|
||||
strings.push('undefined');
|
||||
} else if (arg === null) {
|
||||
strings.push('null');
|
||||
} else {
|
||||
try {
|
||||
strings.push(JSON.stringify(arg, null, 2));
|
||||
} catch(err) {
|
||||
strings.push("<<something>>");
|
||||
}
|
||||
}
|
||||
});
|
||||
return strings.join(' ');
|
||||
}
|
||||
|
||||
if (!gDebug)
|
||||
return;
|
||||
|
||||
let message = generateLogMessage(pieces);
|
||||
dump(message + "\n");
|
||||
Services.console.logStringMessage(message);
|
||||
}
|
||||
|
||||
#ifndef ANDROID
|
||||
#ifdef MOZ_SERVICES_HEALTHREPORT
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Metrics",
|
||||
@@ -152,6 +122,37 @@ PasswordsMeasurement2.prototype = Object.freeze({
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
function log(...pieces) {
|
||||
function generateLogMessage(args) {
|
||||
let strings = ['Login Manager (parent):'];
|
||||
|
||||
args.forEach(function(arg) {
|
||||
if (typeof arg === 'string') {
|
||||
strings.push(arg);
|
||||
} else if (typeof arg === 'undefined') {
|
||||
strings.push('undefined');
|
||||
} else if (arg === null) {
|
||||
strings.push('null');
|
||||
} else {
|
||||
try {
|
||||
strings.push(JSON.stringify(arg, null, 2));
|
||||
} catch(err) {
|
||||
strings.push("<<something>>");
|
||||
}
|
||||
}
|
||||
});
|
||||
return strings.join(' ');
|
||||
}
|
||||
|
||||
if (!gDebug)
|
||||
return;
|
||||
|
||||
let message = generateLogMessage(pieces);
|
||||
dump(message + "\n");
|
||||
Services.console.logStringMessage(message);
|
||||
}
|
||||
|
||||
function prefChanged() {
|
||||
gDebug = Services.prefs.getBoolPref("signon.debug");
|
||||
}
|
||||
@@ -169,6 +170,12 @@ var LoginManagerParent = {
|
||||
mm.addMessageListener("LoginStats:LoginEncountered", this);
|
||||
mm.addMessageListener("LoginStats:LoginFillSuccessful", this);
|
||||
Services.obs.addObserver(this, "LoginStats:NewSavedPassword", false);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "recipeParentPromise", () => {
|
||||
const { LoginRecipesParent } = Cu.import("resource://gre/modules/LoginRecipes.jsm", {});
|
||||
let parent = new LoginRecipesParent();
|
||||
return parent.initializationPromise;
|
||||
});
|
||||
},
|
||||
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
@@ -187,11 +194,11 @@ var LoginManagerParent = {
|
||||
switch (msg.name) {
|
||||
case "RemoteLogins:findLogins": {
|
||||
// TODO Verify msg.target's principals against the formOrigin?
|
||||
this.findLogins(data.options.showMasterPassword,
|
||||
data.formOrigin,
|
||||
data.actionOrigin,
|
||||
data.requestId,
|
||||
msg.target.messageManager);
|
||||
this.sendLoginDataToChild(data.options.showMasterPassword,
|
||||
data.formOrigin,
|
||||
data.actionOrigin,
|
||||
data.requestId,
|
||||
msg.target.messageManager);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -232,19 +239,35 @@ var LoginManagerParent = {
|
||||
}
|
||||
},
|
||||
|
||||
findLogins: function(showMasterPassword, formOrigin, actionOrigin,
|
||||
requestId, target) {
|
||||
/**
|
||||
* Send relevant data (e.g. logins and recipes) to the child process (LoginManagerContent).
|
||||
*/
|
||||
sendLoginDataToChild: Task.async(function*(showMasterPassword, formOrigin, actionOrigin,
|
||||
requestId, target) {
|
||||
let recipes = [];
|
||||
if (formOrigin) {
|
||||
let formHost = (new URL(formOrigin)).host;
|
||||
let recipeManager = yield this.recipeParentPromise;
|
||||
recipes = recipeManager.getRecipesForHost(formHost);
|
||||
}
|
||||
|
||||
if (!showMasterPassword && !Services.logins.isLoggedIn) {
|
||||
target.sendAsyncMessage("RemoteLogins:loginsFound",
|
||||
{ requestId: requestId, logins: [] });
|
||||
target.sendAsyncMessage("RemoteLogins:loginsFound", {
|
||||
requestId: requestId,
|
||||
logins: [],
|
||||
recipes,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let allLoginsCount = Services.logins.countLogins(formOrigin, "", null);
|
||||
// If there are no logins for this site, bail out now.
|
||||
if (!allLoginsCount) {
|
||||
target.sendAsyncMessage("RemoteLogins:loginsFound",
|
||||
{ requestId: requestId, logins: [] });
|
||||
target.sendAsyncMessage("RemoteLogins:loginsFound", {
|
||||
requestId: requestId,
|
||||
logins: [],
|
||||
recipes,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -263,13 +286,16 @@ var LoginManagerParent = {
|
||||
Services.obs.removeObserver(this, "passwordmgr-crypto-login");
|
||||
Services.obs.removeObserver(this, "passwordmgr-crypto-loginCanceled");
|
||||
if (topic == "passwordmgr-crypto-loginCanceled") {
|
||||
target.sendAsyncMessage("RemoteLogins:loginsFound",
|
||||
{ requestId: requestId, logins: [] });
|
||||
target.sendAsyncMessage("RemoteLogins:loginsFound", {
|
||||
requestId: requestId,
|
||||
logins: [],
|
||||
recipes,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
self.findLogins(showMasterPassword, formOrigin, actionOrigin,
|
||||
requestId, target);
|
||||
self.sendLoginDataToChild(showMasterPassword, formOrigin, actionOrigin,
|
||||
requestId, target);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -284,8 +310,14 @@ var LoginManagerParent = {
|
||||
}
|
||||
|
||||
var logins = Services.logins.findLogins({}, formOrigin, actionOrigin, null);
|
||||
target.sendAsyncMessage("RemoteLogins:loginsFound",
|
||||
{ requestId: requestId, logins: logins });
|
||||
// Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
|
||||
// doesn't support structured cloning.
|
||||
var jsLogins = JSON.parse(JSON.stringify(logins));
|
||||
target.sendAsyncMessage("RemoteLogins:loginsFound", {
|
||||
requestId: requestId,
|
||||
logins: jsLogins,
|
||||
recipes,
|
||||
});
|
||||
|
||||
const PWMGR_FORM_ACTION_EFFECT = Services.telemetry.getHistogramById("PWMGR_FORM_ACTION_EFFECT");
|
||||
if (logins.length == 0) {
|
||||
@@ -296,7 +328,7 @@ var LoginManagerParent = {
|
||||
// logins.length < allLoginsCount
|
||||
PWMGR_FORM_ACTION_EFFECT.add(1);
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
doAutocompleteSearch: function({ formOrigin, actionOrigin,
|
||||
searchString, previousResult,
|
||||
@@ -339,9 +371,13 @@ var LoginManagerParent = {
|
||||
AutoCompleteE10S.showPopupWithResults(target.ownerDocument.defaultView, rect, result);
|
||||
}
|
||||
|
||||
target.messageManager.sendAsyncMessage("RemoteLogins:loginsAutoCompleted",
|
||||
{ requestId: requestId,
|
||||
logins: matchingLogins });
|
||||
// Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
|
||||
// doesn't support structured cloning.
|
||||
var jsLogins = JSON.parse(JSON.stringify(matchingLogins));
|
||||
target.messageManager.sendAsyncMessage("RemoteLogins:loginsAutoCompleted", {
|
||||
requestId: requestId,
|
||||
logins: jsLogins,
|
||||
});
|
||||
},
|
||||
|
||||
onFormSubmit: function(hostname, formSubmitURL,
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["LoginRecipesContent", "LoginRecipesParent"];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
const REQUIRED_KEYS = ["hosts"];
|
||||
const OPTIONAL_KEYS = ["description", "passwordSelector", "pathRegex", "usernameSelector"];
|
||||
const SUPPORTED_KEYS = REQUIRED_KEYS.concat(OPTIONAL_KEYS);
|
||||
|
||||
Cu.importGlobalProperties(["URL"]);
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
|
||||
"resource://gre/modules/LoginHelper.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "log", () => LoginHelper.createLogger("LoginRecipes"));
|
||||
|
||||
/**
|
||||
* Create an instance of the object to manage recipes in the parent process.
|
||||
* Consumers should wait until {@link initializationPromise} resolves before
|
||||
* calling methods on the object.
|
||||
*
|
||||
* @constructor
|
||||
* @param {boolean} [aOptions.defaults=true] whether to load default application recipes.
|
||||
*/
|
||||
function LoginRecipesParent(aOptions = { defaults: true }) {
|
||||
if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
|
||||
throw new Error("LoginRecipesParent should only be used from the main process");
|
||||
}
|
||||
|
||||
this._recipesByHost = new Map();
|
||||
|
||||
if (aOptions.defaults) {
|
||||
// XXX: Bug 1134850 will handle reading recipes from a file.
|
||||
this.initializationPromise = this.load(DEFAULT_RECIPES).then(resolve => {
|
||||
return this;
|
||||
});
|
||||
} else {
|
||||
this.initializationPromise = Promise.resolve(this);
|
||||
}
|
||||
}
|
||||
|
||||
LoginRecipesParent.prototype = {
|
||||
/**
|
||||
* Promise resolved with an instance of itself when the module is ready.
|
||||
*
|
||||
* @type {Promise}
|
||||
*/
|
||||
initializationPromise: null,
|
||||
|
||||
/**
|
||||
* @type {Map} Map of hosts (including non-default port numbers) to Sets of recipes.
|
||||
* e.g. "example.com:8080" => Set({...})
|
||||
*/
|
||||
_recipesByHost: null,
|
||||
|
||||
/**
|
||||
* @param {Object} aRecipes an object containing recipes to load for use. The object
|
||||
* should be compatible with JSON (e.g. no RegExp).
|
||||
* @return {Promise} resolving when the recipes are loaded
|
||||
*/
|
||||
load(aRecipes) {
|
||||
let recipeErrors = 0;
|
||||
for (let rawRecipe of aRecipes.siteRecipes) {
|
||||
try {
|
||||
rawRecipe.pathRegex = rawRecipe.pathRegex ? new RegExp(rawRecipe.pathRegex) : undefined;
|
||||
this.add(rawRecipe);
|
||||
} catch (ex) {
|
||||
recipeErrors++;
|
||||
log.error("Error loading recipe", rawRecipe, ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (recipeErrors) {
|
||||
return Promise.reject(`There were ${recipeErrors} recipe error(s)`);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate the recipe is sane and then add it to the set of recipes.
|
||||
*
|
||||
* @param {Object} recipe
|
||||
*/
|
||||
add(recipe) {
|
||||
log.debug("Adding recipe:", recipe);
|
||||
let recipeKeys = Object.keys(recipe);
|
||||
let unknownKeys = recipeKeys.filter(key => SUPPORTED_KEYS.indexOf(key) == -1);
|
||||
if (unknownKeys.length > 0) {
|
||||
throw new Error("The following recipe keys aren't supported: " + unknownKeys.join(", "));
|
||||
}
|
||||
|
||||
let missingRequiredKeys = REQUIRED_KEYS.filter(key => recipeKeys.indexOf(key) == -1);
|
||||
if (missingRequiredKeys.length > 0) {
|
||||
throw new Error("The following required recipe keys are missing: " + missingRequiredKeys.join(", "));
|
||||
}
|
||||
|
||||
if (!Array.isArray(recipe.hosts)) {
|
||||
throw new Error("'hosts' must be a array");
|
||||
}
|
||||
|
||||
if (!recipe.hosts.length) {
|
||||
throw new Error("'hosts' must be a non-empty array");
|
||||
}
|
||||
|
||||
if (recipe.pathRegex && recipe.pathRegex.constructor.name != "RegExp") {
|
||||
throw new Error("'pathRegex' must be a regular expression");
|
||||
}
|
||||
|
||||
const OPTIONAL_STRING_PROPS = ["description", "passwordSelector", "usernameSelector"];
|
||||
for (let prop of OPTIONAL_STRING_PROPS) {
|
||||
if (recipe[prop] && typeof(recipe[prop]) != "string") {
|
||||
throw new Error(`'${prop}' must be a string`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the recipe to the map for each host
|
||||
for (let host of recipe.hosts) {
|
||||
if (!this._recipesByHost.has(host)) {
|
||||
this._recipesByHost.set(host, new Set());
|
||||
}
|
||||
this._recipesByHost.get(host).add(recipe);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Currently only exact host matches are returned but this will eventually handle parent domains.
|
||||
*
|
||||
* @param {String} aHost (e.g. example.com:8080 [non-default port] or sub.example.com)
|
||||
* @return {Set} of recipes that apply to the host ordered by host priority
|
||||
*/
|
||||
getRecipesForHost(aHost) {
|
||||
let hostRecipes = this._recipesByHost.get(aHost);
|
||||
if (!hostRecipes) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return hostRecipes;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
let LoginRecipesContent = {
|
||||
/**
|
||||
* @param {Set} aRecipes - Possible recipes that could apply to the form
|
||||
* @param {HTMLFormElement} aForm - We use a form instead of just a URL so we can later apply
|
||||
* tests to the page contents.
|
||||
* @return {Set} a subset of recipes that apply to the form with the order preserved
|
||||
*/
|
||||
_filterRecipesForForm(aRecipes, aForm) {
|
||||
let formDocURL = aForm.ownerDocument.location;
|
||||
let host = formDocURL.host;
|
||||
let hostRecipes = aRecipes;
|
||||
let recipes = new Set();
|
||||
log.debug("_filterRecipesForForm", aRecipes);
|
||||
if (!hostRecipes) {
|
||||
return recipes;
|
||||
}
|
||||
|
||||
for (let hostRecipe of hostRecipes) {
|
||||
if (hostRecipe.pathRegex && !hostRecipe.pathRegex.test(formDocURL.pathname)) {
|
||||
continue;
|
||||
}
|
||||
recipes.add(hostRecipe);
|
||||
}
|
||||
|
||||
return recipes;
|
||||
},
|
||||
|
||||
/**
|
||||
* Given a set of recipes that apply to the host, choose the one most applicable for
|
||||
* overriding login fields in the form.
|
||||
*
|
||||
* @param {Set} aRecipes The set of recipes to consider for the form
|
||||
* @param {HTMLFormElement} aForm The form where login fields exist.
|
||||
* @return {Object} The recipe that is most applicable for the form.
|
||||
*/
|
||||
getFieldOverrides(aRecipes, aForm) {
|
||||
let recipes = this._filterRecipesForForm(aRecipes, aForm);
|
||||
log.debug("getFieldOverrides: filtered recipes:", recipes);
|
||||
if (!recipes.size) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let chosenRecipe = null;
|
||||
// Find the first (most-specific recipe that involves field overrides).
|
||||
for (let recipe of recipes) {
|
||||
if (!recipe.usernameSelector && !recipe.passwordSelector) {
|
||||
continue;
|
||||
}
|
||||
|
||||
chosenRecipe = recipe;
|
||||
break;
|
||||
}
|
||||
|
||||
return chosenRecipe;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} aParent the element to query for the selector from.
|
||||
* @param {CSSSelector} aSelector the CSS selector to query for the login field.
|
||||
* @return {HTMLElement|null}
|
||||
*/
|
||||
queryLoginField(aParent, aSelector) {
|
||||
if (!aSelector) {
|
||||
return null;
|
||||
}
|
||||
let field = aParent.ownerDocument.querySelector(aSelector);
|
||||
if (!field) {
|
||||
log.warn("Login field selector wasn't matched:", aSelector);
|
||||
return null;
|
||||
}
|
||||
if (!(field instanceof aParent.ownerDocument.defaultView.HTMLInputElement)) {
|
||||
log.warn("Login field isn't an <input> so ignoring it:", aSelector);
|
||||
return null;
|
||||
}
|
||||
return field;
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_RECIPES = {
|
||||
"siteRecipes": [
|
||||
{
|
||||
"description": "okta uses a hidden password field to disable filling",
|
||||
"hosts": ["mozilla.okta.com"],
|
||||
"passwordSelector": "#pass-signin"
|
||||
},
|
||||
{
|
||||
"description": "anthem uses a hidden password and username field to disable filling",
|
||||
"hosts": ["www.anthem.com"],
|
||||
"passwordSelector": "#LoginContent_txtLoginPass"
|
||||
},
|
||||
{
|
||||
"description": "An ephemeral password-shim field is incorrectly selected as the username field.",
|
||||
"hosts": ["www.discover.com"],
|
||||
"usernameSelector": "#login-account"
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -39,6 +39,7 @@ EXTRA_JS_MODULES += [
|
||||
'InsecurePasswordUtils.jsm',
|
||||
'LoginHelper.jsm',
|
||||
'LoginManagerContent.jsm',
|
||||
'LoginRecipes.jsm',
|
||||
'OSCrypto.jsm',
|
||||
]
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ interface nsIDOMHTMLInputElement;
|
||||
interface nsIDOMHTMLFormElement;
|
||||
interface nsIPropertyBag;
|
||||
|
||||
[scriptable, uuid(f0c5ca21-db71-4b32-993e-ab63054cc6f5)]
|
||||
[scriptable, uuid(38c7f6af-7df9-49c7-b558-2776b24e6cc1)]
|
||||
interface nsILoginManager : nsISupports {
|
||||
/**
|
||||
* This promise is resolved when initialization is complete, and is rejected
|
||||
@@ -215,16 +215,6 @@ interface nsILoginManager : nsISupports {
|
||||
in nsIDOMHTMLInputElement aElement,
|
||||
in nsIFormAutoCompleteObserver aListener);
|
||||
|
||||
/**
|
||||
* Fill a form with login information if we have it. This method will fill
|
||||
* aForm regardless of the signon.autofillForms preference.
|
||||
*
|
||||
* @param aForm
|
||||
* The form to fill
|
||||
* @return Promise that is resolved with whether or not the form was filled.
|
||||
*/
|
||||
jsval fillForm(in nsIDOMHTMLFormElement aForm);
|
||||
|
||||
/**
|
||||
* Search for logins in the login manager. An array is always returned;
|
||||
* if there are no logins the array is empty.
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
@@ -74,7 +72,7 @@ LoginManager.prototype = {
|
||||
return this;
|
||||
}
|
||||
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
throw new Components.Exception("Interface not available", Cr.NS_ERROR_NO_INTERFACE);
|
||||
},
|
||||
|
||||
|
||||
@@ -301,26 +299,26 @@ LoginManager.prototype = {
|
||||
addLogin : function (login) {
|
||||
// Sanity check the login
|
||||
if (login.hostname == null || login.hostname.length == 0)
|
||||
throw "Can't add a login with a null or empty hostname.";
|
||||
throw new Error("Can't add a login with a null or empty hostname.");
|
||||
|
||||
// For logins w/o a username, set to "", not null.
|
||||
if (login.username == null)
|
||||
throw "Can't add a login with a null username.";
|
||||
throw new Error("Can't add a login with a null username.");
|
||||
|
||||
if (login.password == null || login.password.length == 0)
|
||||
throw "Can't add a login with a null or empty password.";
|
||||
throw new Error("Can't add a login with a null or empty password.");
|
||||
|
||||
if (login.formSubmitURL || login.formSubmitURL == "") {
|
||||
// We have a form submit URL. Can't have a HTTP realm.
|
||||
if (login.httpRealm != null)
|
||||
throw "Can't add a login with both a httpRealm and formSubmitURL.";
|
||||
throw new Error("Can't add a login with both a httpRealm and formSubmitURL.");
|
||||
} else if (login.httpRealm) {
|
||||
// We have a HTTP realm. Can't have a form submit URL.
|
||||
if (login.formSubmitURL != null)
|
||||
throw "Can't add a login with both a httpRealm and formSubmitURL.";
|
||||
throw new Error("Can't add a login with both a httpRealm and formSubmitURL.");
|
||||
} else {
|
||||
// Need one or the other!
|
||||
throw "Can't add a login without a httpRealm or formSubmitURL.";
|
||||
throw new Error("Can't add a login without a httpRealm or formSubmitURL.");
|
||||
}
|
||||
|
||||
|
||||
@@ -329,7 +327,7 @@ LoginManager.prototype = {
|
||||
login.httpRealm);
|
||||
|
||||
if (logins.some(function(l) login.matches(l, true)))
|
||||
throw "This login already exists.";
|
||||
throw new Error("This login already exists.");
|
||||
|
||||
log("Adding login");
|
||||
return this._storage.addLogin(login);
|
||||
@@ -480,7 +478,7 @@ LoginManager.prototype = {
|
||||
setLoginSavingEnabled : function (hostname, enabled) {
|
||||
// Nulls won't round-trip with getAllDisabledHosts().
|
||||
if (hostname.indexOf("\0") != -1)
|
||||
throw "Invalid hostname";
|
||||
throw new Error("Invalid hostname");
|
||||
|
||||
log("Login saving for", hostname, "now enabled?", enabled);
|
||||
return this._storage.setLoginSavingEnabled(hostname, enabled);
|
||||
|
||||
@@ -3,15 +3,12 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/SharedPromptUtils.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
Cu.import("resource://gre/modules/SharedPromptUtils.jsm");
|
||||
|
||||
/* Constants for password prompt telemetry.
|
||||
* Mirrored in mobile/android/components/LoginManagerPrompter.js */
|
||||
@@ -136,7 +133,7 @@ LoginManagerPromptFactory.prototype = {
|
||||
}
|
||||
self._doAsyncPrompt();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
this.log("_doAsyncPrompt:run dispatched");
|
||||
@@ -241,7 +238,7 @@ LoginManagerPrompter.prototype = {
|
||||
this.__strBundle = bunService.createBundle(
|
||||
"chrome://passwordmgr/locale/passwordmgr.properties");
|
||||
if (!this.__strBundle)
|
||||
throw "String bundle for Login Manager not present!";
|
||||
throw new Error("String bundle for Login Manager not present!");
|
||||
}
|
||||
|
||||
return this.__strBundle;
|
||||
@@ -304,7 +301,8 @@ LoginManagerPrompter.prototype = {
|
||||
prompt : function (aDialogTitle, aText, aPasswordRealm,
|
||||
aSavePassword, aDefaultText, aResult) {
|
||||
if (aSavePassword != Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER)
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
throw new Components.Exception("prompt only supports SAVE_PASSWORD_NEVER",
|
||||
Cr.NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
this.log("===== prompt() called =====");
|
||||
|
||||
@@ -328,7 +326,8 @@ LoginManagerPrompter.prototype = {
|
||||
this.log("===== promptUsernameAndPassword() called =====");
|
||||
|
||||
if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION)
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
throw new Components.Exception("promptUsernameAndPassword doesn't support SAVE_PASSWORD_FOR_SESSION",
|
||||
Cr.NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
var selectedLogin = null;
|
||||
var checkBox = { value : false };
|
||||
@@ -430,7 +429,8 @@ LoginManagerPrompter.prototype = {
|
||||
this.log("===== promptPassword called() =====");
|
||||
|
||||
if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION)
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
throw new Components.Exception("promptPassword doesn't support SAVE_PASSWORD_FOR_SESSION",
|
||||
Cr.NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
var checkBox = { value : false };
|
||||
var checkBoxLabel = null;
|
||||
@@ -689,7 +689,7 @@ LoginManagerPrompter.prototype = {
|
||||
level: aLevel,
|
||||
inProgress : false,
|
||||
prompter: this
|
||||
}
|
||||
};
|
||||
|
||||
this._factory._asyncPrompts[hashKey] = asyncPrompt;
|
||||
this._factory._doAsyncPrompt();
|
||||
@@ -1451,11 +1451,11 @@ LoginManagerPrompter.prototype = {
|
||||
if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
|
||||
this.log("getAuthTarget is for proxy auth");
|
||||
if (!(aChannel instanceof Ci.nsIProxiedChannel))
|
||||
throw "proxy auth needs nsIProxiedChannel";
|
||||
throw new Error("proxy auth needs nsIProxiedChannel");
|
||||
|
||||
var info = aChannel.proxyInfo;
|
||||
if (!info)
|
||||
throw "proxy auth needs nsIProxyInfo";
|
||||
throw new Error("proxy auth needs nsIProxyInfo");
|
||||
|
||||
// Proxies don't have a scheme, but we'll use "moz-proxy://"
|
||||
// so that it's more obvious what the login is for.
|
||||
@@ -1538,7 +1538,7 @@ LoginManagerPrompter.prototype = {
|
||||
this.callback = null;
|
||||
this.context = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}; // end of LoginManagerPrompter implementation
|
||||
|
||||
@@ -115,7 +115,7 @@ this.LoginManagerStorage_json.prototype = {
|
||||
}.bind(this)).catch(Cu.reportError);
|
||||
} catch (e) {
|
||||
this.log("Initialization failed: " + e);
|
||||
throw "Initialization failed";
|
||||
throw new Error("Initialization failed");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -151,7 +151,7 @@ this.LoginManagerStorage_json.prototype = {
|
||||
loginClone.QueryInterface(Ci.nsILoginMetaInfo);
|
||||
if (loginClone.guid) {
|
||||
if (!this._isGuidUnique(loginClone.guid))
|
||||
throw "specified GUID already exists";
|
||||
throw new Error("specified GUID already exists");
|
||||
} else {
|
||||
loginClone.guid = gUUIDGenerator.generateUUID().toString();
|
||||
}
|
||||
@@ -199,7 +199,7 @@ this.LoginManagerStorage_json.prototype = {
|
||||
|
||||
let [idToDelete, storedLogin] = this._getIdForLogin(login);
|
||||
if (!idToDelete)
|
||||
throw "No matching logins";
|
||||
throw new Error("No matching logins");
|
||||
|
||||
let foundIndex = this._store.data.logins.findIndex(l => l.id == idToDelete);
|
||||
if (foundIndex != -1) {
|
||||
@@ -220,7 +220,7 @@ this.LoginManagerStorage_json.prototype = {
|
||||
|
||||
let [idToModify, oldStoredLogin] = this._getIdForLogin(oldLogin);
|
||||
if (!idToModify)
|
||||
throw "No matching logins";
|
||||
throw new Error("No matching logins");
|
||||
|
||||
let newLogin = LoginHelper.buildModifiedLogin(oldStoredLogin, newLoginData);
|
||||
|
||||
@@ -228,7 +228,7 @@ this.LoginManagerStorage_json.prototype = {
|
||||
if (newLogin.guid != oldStoredLogin.guid &&
|
||||
!this._isGuidUnique(newLogin.guid))
|
||||
{
|
||||
throw "specified GUID already exists";
|
||||
throw new Error("specified GUID already exists");
|
||||
}
|
||||
|
||||
// Look for an existing entry in case key properties changed.
|
||||
@@ -238,7 +238,7 @@ this.LoginManagerStorage_json.prototype = {
|
||||
newLogin.httpRealm);
|
||||
|
||||
if (logins.some(login => newLogin.matches(login, true)))
|
||||
throw "This login already exists.";
|
||||
throw new Error("This login already exists.");
|
||||
}
|
||||
|
||||
// Get the encrypted value of the username and password.
|
||||
@@ -362,7 +362,7 @@ this.LoginManagerStorage_json.prototype = {
|
||||
break;
|
||||
// Fail if caller requests an unknown property.
|
||||
default:
|
||||
throw "Unexpected field: " + field;
|
||||
throw new Error("Unexpected field: " + field);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -66,7 +66,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
return this._dbConnection;
|
||||
}
|
||||
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
throw new Components.Exception("Interface not available", Cr.NS_ERROR_NO_INTERFACE);
|
||||
},
|
||||
|
||||
__crypto : null, // nsILoginManagerCrypto service
|
||||
@@ -220,7 +220,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
// If the import fails on first run, we want to delete the db
|
||||
if (isFirstRun && e == "Import failed")
|
||||
this._dbCleanup(false);
|
||||
throw "Initialization failed";
|
||||
throw new Error("Initialization failed");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -253,7 +253,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
loginClone.QueryInterface(Ci.nsILoginMetaInfo);
|
||||
if (loginClone.guid) {
|
||||
if (!this._isGuidUnique(loginClone.guid))
|
||||
throw "specified GUID already exists";
|
||||
throw new Error("specified GUID already exists");
|
||||
} else {
|
||||
loginClone.guid = this._uuidService.generateUUID().toString();
|
||||
}
|
||||
@@ -302,7 +302,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
stmt.execute();
|
||||
} catch (e) {
|
||||
this.log("addLogin failed: " + e.name + " : " + e.message);
|
||||
throw "Couldn't write to database, login not added.";
|
||||
throw new Error("Couldn't write to database, login not added.");
|
||||
} finally {
|
||||
if (stmt) {
|
||||
stmt.reset();
|
||||
@@ -321,7 +321,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
removeLogin : function (login) {
|
||||
let [idToDelete, storedLogin] = this._getIdForLogin(login);
|
||||
if (!idToDelete)
|
||||
throw "No matching logins";
|
||||
throw new Error("No matching logins");
|
||||
|
||||
// Execute the statement & remove from DB
|
||||
let query = "DELETE FROM moz_logins WHERE id = :id";
|
||||
@@ -335,7 +335,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
transaction.commit();
|
||||
} catch (e) {
|
||||
this.log("_removeLogin failed: " + e.name + " : " + e.message);
|
||||
throw "Couldn't write to database, login not removed.";
|
||||
throw new Error("Couldn't write to database, login not removed.");
|
||||
transaction.rollback();
|
||||
} finally {
|
||||
if (stmt) {
|
||||
@@ -353,7 +353,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
modifyLogin : function (oldLogin, newLoginData) {
|
||||
let [idToModify, oldStoredLogin] = this._getIdForLogin(oldLogin);
|
||||
if (!idToModify)
|
||||
throw "No matching logins";
|
||||
throw new Error("No matching logins");
|
||||
|
||||
let newLogin = LoginHelper.buildModifiedLogin(oldStoredLogin, newLoginData);
|
||||
|
||||
@@ -361,7 +361,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
if (newLogin.guid != oldStoredLogin.guid &&
|
||||
!this._isGuidUnique(newLogin.guid))
|
||||
{
|
||||
throw "specified GUID already exists";
|
||||
throw new Error("specified GUID already exists");
|
||||
}
|
||||
|
||||
// Look for an existing entry in case key properties changed.
|
||||
@@ -371,7 +371,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
newLogin.httpRealm);
|
||||
|
||||
if (logins.some(login => newLogin.matches(login, true)))
|
||||
throw "This login already exists.";
|
||||
throw new Error("This login already exists.");
|
||||
}
|
||||
|
||||
// Get the encrypted value of the username and password.
|
||||
@@ -417,7 +417,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
stmt.execute();
|
||||
} catch (e) {
|
||||
this.log("modifyLogin failed: " + e.name + " : " + e.message);
|
||||
throw "Couldn't write to database, login not modified.";
|
||||
throw new Error("Couldn't write to database, login not modified.");
|
||||
} finally {
|
||||
if (stmt) {
|
||||
stmt.reset();
|
||||
@@ -519,7 +519,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
break;
|
||||
// Fail if caller requests an unknown property.
|
||||
default:
|
||||
throw "Unexpected field: " + field;
|
||||
throw new Error("Unexpected field: " + field);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,7 +610,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
} catch (e) {
|
||||
this.log("_removeAllLogins failed: " + e.name + " : " + e.message);
|
||||
transaction.rollback();
|
||||
throw "Couldn't write to database";
|
||||
throw new Error("Couldn't write to database");
|
||||
} finally {
|
||||
if (stmt) {
|
||||
stmt.reset();
|
||||
@@ -669,7 +669,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
stmt.execute();
|
||||
} catch (e) {
|
||||
this.log("setLoginSavingEnabled failed: " + e.name + " : " + e.message);
|
||||
throw "Couldn't write to database"
|
||||
throw new Error("Couldn't write to database");
|
||||
} finally {
|
||||
if (stmt) {
|
||||
stmt.reset();
|
||||
|
||||
@@ -86,12 +86,12 @@ function reallyHandleRequest(request, response) {
|
||||
authHeader = request.getHeader("Authorization");
|
||||
match = /Basic (.+)/.exec(authHeader);
|
||||
if (match.length != 2)
|
||||
throw "Couldn't parse auth header: " + authHeader;
|
||||
throw new Error("Couldn't parse auth header: " + authHeader);
|
||||
|
||||
var userpass = base64ToString(match[1]); // no atob() :-(
|
||||
match = /(.*):(.*)/.exec(userpass);
|
||||
if (match.length != 3)
|
||||
throw "Couldn't decode auth header: " + userpass;
|
||||
throw new Error("Couldn't decode auth header: " + userpass);
|
||||
actual_user = match[1];
|
||||
actual_pass = match[2];
|
||||
}
|
||||
@@ -101,12 +101,12 @@ function reallyHandleRequest(request, response) {
|
||||
authHeader = request.getHeader("Proxy-Authorization");
|
||||
match = /Basic (.+)/.exec(authHeader);
|
||||
if (match.length != 2)
|
||||
throw "Couldn't parse auth header: " + authHeader;
|
||||
throw new Error("Couldn't parse auth header: " + authHeader);
|
||||
|
||||
var userpass = base64ToString(match[1]); // no atob() :-(
|
||||
match = /(.*):(.*)/.exec(userpass);
|
||||
if (match.length != 3)
|
||||
throw "Couldn't decode auth header: " + userpass;
|
||||
throw new Error("Couldn't decode auth header: " + userpass);
|
||||
proxy_actual_user = match[1];
|
||||
proxy_actual_pass = match[2];
|
||||
}
|
||||
|
||||
@@ -91,12 +91,12 @@ function reallyHandleRequest(request, response) {
|
||||
authHeader = request.getHeader("Authorization");
|
||||
match = /Basic (.+)/.exec(authHeader);
|
||||
if (match.length != 2)
|
||||
throw "Couldn't parse auth header: " + authHeader;
|
||||
throw new Error("Couldn't parse auth header: " + authHeader);
|
||||
|
||||
var userpass = base64ToString(match[1]); // no atob() :-(
|
||||
match = /(.*):(.*)/.exec(userpass);
|
||||
if (match.length != 3)
|
||||
throw "Couldn't decode auth header: " + userpass;
|
||||
throw new Error("Couldn't decode auth header: " + userpass);
|
||||
actual_user = match[1];
|
||||
actual_pass = match[2];
|
||||
}
|
||||
@@ -106,12 +106,12 @@ function reallyHandleRequest(request, response) {
|
||||
authHeader = request.getHeader("Proxy-Authorization");
|
||||
match = /Basic (.+)/.exec(authHeader);
|
||||
if (match.length != 2)
|
||||
throw "Couldn't parse auth header: " + authHeader;
|
||||
throw new Error("Couldn't parse auth header: " + authHeader);
|
||||
|
||||
var userpass = base64ToString(match[1]); // no atob() :-(
|
||||
match = /(.*):(.*)/.exec(userpass);
|
||||
if (match.length != 3)
|
||||
throw "Couldn't decode auth header: " + userpass;
|
||||
throw new Error("Couldn't decode auth header: " + userpass);
|
||||
proxy_actual_user = match[1];
|
||||
proxy_actual_pass = match[2];
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ function handleRequest(request, response)
|
||||
authHeader = request.getHeader("Authorization");
|
||||
match = /Basic (.+)/.exec(authHeader);
|
||||
if (match.length != 2)
|
||||
throw "Couldn't parse auth header: " + authHeader;
|
||||
throw new Error("Couldn't parse auth header: " + authHeader);
|
||||
|
||||
var userpass = base64ToString(match[1]); // no atob() :-(
|
||||
match = /(.*):(.*)/.exec(userpass);
|
||||
if (match.length != 3)
|
||||
throw "Couldn't decode auth header: " + userpass;
|
||||
throw new Error("Couldn't decode auth header: " + userpass);
|
||||
actual_user = match[1];
|
||||
actual_pass = match[2];
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ support-files =
|
||||
[test_basic_form_0pw.html]
|
||||
[test_basic_form_1pw.html]
|
||||
[test_basic_form_1pw_2.html]
|
||||
[test_basic_form_2.html]
|
||||
[test_basic_form_2pw_1.html]
|
||||
[test_basic_form_2pw_2.html]
|
||||
[test_basic_form_3pw_1.html]
|
||||
@@ -40,8 +39,6 @@ skip-if = toolkit == 'android'
|
||||
[test_case_differences.html]
|
||||
skip-if = toolkit == 'android'
|
||||
[test_basic_form_html5.html]
|
||||
[test_basic_form_observer_autofillForms.html]
|
||||
[test_basic_form_observer_foundLogins.html]
|
||||
[test_basic_form_pwevent.html]
|
||||
[test_basic_form_pwonly.html]
|
||||
[test_bug_221634.html]
|
||||
@@ -72,6 +69,8 @@ skip-if = os == "linux" || toolkit == 'android' # bug 934057
|
||||
skip-if = os == "linux" || toolkit == 'android' #TIMED_OUT
|
||||
[test_prompt_async.html]
|
||||
skip-if = toolkit == 'android' #TIMED_OUT
|
||||
[test_recipe_login_fields.html]
|
||||
skip-if = buildapp == 'mulet' || buildapp == 'b2g'
|
||||
[test_xhr.html]
|
||||
skip-if = toolkit == 'android' #TIMED_OUT
|
||||
[test_xml_load.html]
|
||||
|
||||
@@ -182,18 +182,17 @@ function commonInit(selfFilling) {
|
||||
form.appendChild(password);
|
||||
|
||||
var observer = SpecialPowers.wrapCallback(function(subject, topic, data) {
|
||||
var bag = subject.QueryInterface(SpecialPowers.Ci.nsIPropertyBag2);
|
||||
var username = bag.get("usernameField");
|
||||
if (!username || username.form.id !== 'observerforcer')
|
||||
var form = subject.QueryInterface(SpecialPowers.Ci.nsIDOMNode);
|
||||
if (form.id !== 'observerforcer')
|
||||
return;
|
||||
SpecialPowers.removeObserver(observer, "passwordmgr-found-logins");
|
||||
SpecialPowers.removeObserver(observer, "passwordmgr-processed-form");
|
||||
form.parentNode.removeChild(form);
|
||||
SimpleTest.executeSoon(() => {
|
||||
var event = new Event("runTests");
|
||||
window.dispatchEvent(event);
|
||||
});
|
||||
});
|
||||
SpecialPowers.addObserver(observer, "passwordmgr-found-logins", false);
|
||||
SpecialPowers.addObserver(observer, "passwordmgr-processed-form", false);
|
||||
|
||||
document.body.appendChild(form);
|
||||
});
|
||||
@@ -259,3 +258,25 @@ function dumpLogin(label, login) {
|
||||
loginText += login.passwordField;
|
||||
ok(true, label + loginText);
|
||||
}
|
||||
|
||||
// Code to run when loaded as a chrome script in tests via loadChromeScript
|
||||
if (this.addMessageListener) {
|
||||
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
var SpecialPowers = { Cc, Ci, Cr, Cu, };
|
||||
var ok, is;
|
||||
// Ignore ok/is in commonInit since they aren't defined in a chrome script.
|
||||
ok = is = () => {};
|
||||
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
addMessageListener("setupParent", () => {
|
||||
commonInit(true);
|
||||
sendAsyncMessage("doneSetup");
|
||||
});
|
||||
addMessageListener("loadRecipes", Task.async(function* loadRecipes(recipes) {
|
||||
var { LoginManagerParent } = Cu.import("resource://gre/modules/LoginManagerParent.jsm", {});
|
||||
var recipeParent = yield LoginManagerParent.recipeParentPromise;
|
||||
yield recipeParent.load(recipes);
|
||||
sendAsyncMessage("loadedRecipes", recipes);
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Login Manager</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="pwmgr_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
Login Manager test: simple form fill with autofillForms disabled
|
||||
<script>
|
||||
commonInit();
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"]
|
||||
.getService(SpecialPowers.Ci.nsILoginManager);
|
||||
</script>
|
||||
|
||||
<p id="display"></p>
|
||||
|
||||
<div id="content" style="display: block">
|
||||
|
||||
<form id="form1" action="formtest.js">
|
||||
<p>This is form 1.</p>
|
||||
<input type="text" name="uname">
|
||||
<input type="password" name="pword">
|
||||
|
||||
<button type="submit">Submit</button>
|
||||
<button type="reset"> Reset </button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
/** Test for Login Manager: simple form fill with autofillForms disabled **/
|
||||
|
||||
function startTest(){
|
||||
// Ensure the form is empty at start
|
||||
is($_(1, "uname").value, "", "Checking for blank username");
|
||||
is($_(1, "pword").value, "", "Checking for blank password");
|
||||
|
||||
// Call the public method, check return value
|
||||
pwmgr.fillForm(document.getElementById("form1"))
|
||||
.then(function(result) {
|
||||
is(result, true, "Checking return value of fillForm");
|
||||
|
||||
// Check that the form was filled
|
||||
is($_(1, "uname").value, "testuser", "Checking for filled username");
|
||||
is($_(1, "pword").value, "testpass", "Checking for filled password");
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
// Assume that the pref starts out true, so set to false
|
||||
SpecialPowers.pushPrefEnv({"set":[["signon.autofillForms", false]]}, setup);
|
||||
function setup() {
|
||||
window.addEventListener("runTests", startTest);
|
||||
}
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -254,10 +254,10 @@ function* runTest() {
|
||||
|
||||
function waitForCompletion() {
|
||||
var observer = SpecialPowers.wrapCallback(function(subject, topic, data) {
|
||||
SpecialPowers.removeObserver(observer, "passwordmgr-found-logins");
|
||||
SpecialPowers.removeObserver(observer, "passwordmgr-processed-form");
|
||||
tester.next();
|
||||
});
|
||||
SpecialPowers.addObserver(observer, "passwordmgr-found-logins", false);
|
||||
SpecialPowers.addObserver(observer, "passwordmgr-processed-form", false);
|
||||
}
|
||||
|
||||
/* test 1 */
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Login Manager</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="pwmgr_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
Login Manager test: simple form with autofillForms disabled and notifying observers
|
||||
<script>
|
||||
commonInit(true);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
const Cc = SpecialPowers.Cc;
|
||||
const Ci = SpecialPowers.Ci;
|
||||
var TestObserver;
|
||||
// Assume that the pref starts out true, so set to false
|
||||
SpecialPowers.pushPrefEnv({"set":[["signon.autofillForms", false]]}, function() {
|
||||
TestObserver = {
|
||||
receivedNotificationFoundForm : false,
|
||||
receivedNotificationFoundLogins : false,
|
||||
dataFoundForm : "",
|
||||
dataFoundLogins : null,
|
||||
observe : function (subject, topic, data) {
|
||||
var pwmgr = Cc["@mozilla.org/login-manager;1"].
|
||||
getService(Ci.nsILoginManager);
|
||||
if (topic == "passwordmgr-found-form") {
|
||||
info("got passwordmgr-found-form");
|
||||
this.receivedNotificationFoundForm = true;
|
||||
this.dataFoundForm = data;
|
||||
// Now fill the form
|
||||
pwmgr.fillForm(subject)
|
||||
.then(window.startTest)
|
||||
.then(null, function(e) { alert(e); });
|
||||
} else if (topic == "passwordmgr-found-logins") {
|
||||
info("got passwordmgr-found-logins");
|
||||
|
||||
// We only care about the first notification (the second comes from our
|
||||
// own call to pwmgr.fillForm.
|
||||
if (this.receivedNotificationFoundLogins)
|
||||
return;
|
||||
this.receivedNotificationFoundLogins = true;
|
||||
this.dataFoundLogins = subject.QueryInterface(Ci.nsIPropertyBag2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add the observer
|
||||
SpecialPowers.addObserver(TestObserver, "passwordmgr-found-form", false);
|
||||
SpecialPowers.addObserver(TestObserver, "passwordmgr-found-logins", false);
|
||||
});
|
||||
</script>
|
||||
|
||||
<p id="display"></p>
|
||||
|
||||
<div id="content" style="display: block">
|
||||
|
||||
<form id="form1" action="formtest.js">
|
||||
<p>This is form 1.</p>
|
||||
<input type="text" name="uname">
|
||||
<input type="password" name="pword">
|
||||
|
||||
<button type="submit">Submit</button>
|
||||
<button type="reset"> Reset </button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
/** Test for Login Manager: simple form with autofillForms disabled and notifying observers **/
|
||||
function startTest() {
|
||||
// Test that found-form observer is notified & got correct data
|
||||
is(TestObserver.receivedNotificationFoundForm, true, "Checking found-form observer was notified");
|
||||
is(TestObserver.dataFoundForm, "noAutofillForms", "Checking found-form observer got correct data");
|
||||
|
||||
// Check that form1 was filled
|
||||
is($_(1, "uname").value, "testuser", "Checking for filled username");
|
||||
is($_(1, "pword").value, "testpass", "Checking for filled password");
|
||||
|
||||
// Test that found-logins observer is notified & got correct data
|
||||
is(TestObserver.receivedNotificationFoundLogins, true, "Checking found-logins observer was notified");
|
||||
is(TestObserver.dataFoundLogins.get("didntFillReason"), "noAutofillForms", "Checking didntFillReason is noAutofillForms");
|
||||
is(SpecialPowers.unwrap(TestObserver.dataFoundLogins.get("usernameField")), $_(1, "uname"), "Checking username field is correct");
|
||||
is(SpecialPowers.unwrap(TestObserver.dataFoundLogins.get("passwordField")), $_(1, "pword"), "Checking password field is correct");
|
||||
is(TestObserver.dataFoundLogins.get("foundLogins").constructor.name, "Array", "Checking foundLogins is array");
|
||||
is(TestObserver.dataFoundLogins.get("foundLogins").length, 1, "Checking foundLogins contains one login");
|
||||
ok(TestObserver.dataFoundLogins.get("selectedLogin").QueryInterface(Ci.nsILoginInfo), "Checking selectedLogin is nsILoginInfo");
|
||||
ok(TestObserver.dataFoundLogins.get("selectedLogin").equals(TestObserver.dataFoundLogins.get("foundLogins")[0]),
|
||||
"Checking selectedLogin is found login");
|
||||
// Remove the observer
|
||||
SpecialPowers.removeObserver(TestObserver, "passwordmgr-found-form");
|
||||
SpecialPowers.removeObserver(TestObserver, "passwordmgr-found-logins");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,161 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Login Manager</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="pwmgr_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
Login Manager test: notifying observers of passwordmgr-found-logins
|
||||
<script>
|
||||
commonInit();
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
const Cc = SpecialPowers.Cc;
|
||||
const Ci = SpecialPowers.Ci;
|
||||
|
||||
// Configure the login manager with two logins for one of the forms
|
||||
// so we can do a multiple logins test.
|
||||
var nsLoginInfo =
|
||||
new SpecialPowers.wrap(SpecialPowers.Components).Constructor("@mozilla.org/login-manager/loginInfo;1",
|
||||
SpecialPowers.Ci.nsILoginInfo);
|
||||
var login1 = new nsLoginInfo();
|
||||
login1.init("http://mochi.test:8888", "http://www.example.com", null,
|
||||
"testuser1", "testpass1", "uname", "pword");
|
||||
var login2 = new nsLoginInfo();
|
||||
login2.init("http://mochi.test:8888", "http://www.example.com", null,
|
||||
"testuser2", "testpass2", "uname", "pword");
|
||||
var pwmgr = Cc["@mozilla.org/login-manager;1"].
|
||||
getService(Ci.nsILoginManager);
|
||||
pwmgr.addLogin(login1);
|
||||
pwmgr.addLogin(login2);
|
||||
|
||||
var TestObserver = {
|
||||
results: {},
|
||||
observe : function (subject, topic, data) {
|
||||
if (topic == "passwordmgr-found-logins") {
|
||||
var formInfo = subject.QueryInterface(Ci.nsIPropertyBag2);
|
||||
var id = formInfo.get("passwordField").form.id;
|
||||
this.results[id].receivedNotification = true;
|
||||
this.results[id].data = formInfo;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize the object that stores the results of notifications.
|
||||
for (var formID of ["form1", "form2", "form3", "form5"])
|
||||
TestObserver.results[formID] = { receivedNotification: false, data: null };
|
||||
|
||||
// Add the observer
|
||||
SpecialPowers.addObserver(TestObserver, "passwordmgr-found-logins", false);
|
||||
</script>
|
||||
|
||||
<p id="display"></p>
|
||||
|
||||
<div id="content" style="display: block">
|
||||
|
||||
<form id="form1" action="formtest.js">
|
||||
<p>This is form 1.</p>
|
||||
<input type="text" name="uname">
|
||||
<input type="password" name="pword">
|
||||
|
||||
<button type="submit">Submit</button>
|
||||
<button type="reset"> Reset </button>
|
||||
</form>
|
||||
|
||||
<form id="form2" action="formtest.js">
|
||||
<p>This is form 2.</p>
|
||||
<input type="text" name="uname" value="existing">
|
||||
<input type="password" name="pword">
|
||||
|
||||
<button type="submit">Submit</button>
|
||||
<button type="reset"> Reset </button>
|
||||
</form>
|
||||
|
||||
<form id="form3" action="formtest.js">
|
||||
<p>This is form 3.</p>
|
||||
<input type="text" name="uname">
|
||||
<input type="password" name="pword" value="existing">
|
||||
|
||||
<button type="submit">Submit</button>
|
||||
<button type="reset"> Reset </button>
|
||||
</form>
|
||||
|
||||
<form id="form5" action="http://www.example.com">
|
||||
<p>This is form 5.</p>
|
||||
<input type="text" name="uname">
|
||||
<input type="password" name="pword">
|
||||
|
||||
<button type="submit">Submit</button>
|
||||
<button type="reset"> Reset </button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
/** Test for Login Manager: notifying observers of passwordmgr-found-logins **/
|
||||
|
||||
function startTest(){
|
||||
// Test notification of a form that was filled.
|
||||
is(TestObserver.results["form1"].receivedNotification, true, "Checking observer was notified");
|
||||
is(TestObserver.results["form1"].data.get("didntFillReason"), null, "Checking didntFillReason is null");
|
||||
is(SpecialPowers.unwrap(TestObserver.results["form1"].data.get("usernameField")), $_(1, "uname"), "Checking username field is correct");
|
||||
is(SpecialPowers.unwrap(TestObserver.results["form1"].data.get("passwordField")), $_(1, "pword"), "Checking password field is correct");
|
||||
is(TestObserver.results["form1"].data.get("foundLogins").constructor.name, "Array", "Checking foundLogins is array");
|
||||
is(TestObserver.results["form1"].data.get("foundLogins").length, 1, "Checking foundLogins contains one login");
|
||||
ok(TestObserver.results["form1"].data.get("selectedLogin").QueryInterface(Ci.nsILoginInfo), "Checking selectedLogin is nsILoginInfo");
|
||||
ok(TestObserver.results["form1"].data.get("selectedLogin").equals(TestObserver.results["form1"].data.get("foundLogins")[0]),
|
||||
"Checking selectedLogin is found login");
|
||||
|
||||
// Test notification of a form that wasn't filled because its username field
|
||||
// already contained a value.
|
||||
is(TestObserver.results["form2"].receivedNotification, true, "Checking observer was notified");
|
||||
is(TestObserver.results["form2"].data.get("didntFillReason"), "existingUsername", "Checking didntFillReason is existingUsername");
|
||||
is(SpecialPowers.unwrap(TestObserver.results["form2"].data.get("usernameField")), $_(2, "uname"), "Checking username field is correct");
|
||||
is(SpecialPowers.unwrap(TestObserver.results["form2"].data.get("passwordField")), $_(2, "pword"), "Checking password field is correct");
|
||||
is(TestObserver.results["form2"].data.get("foundLogins").constructor.name, "Array", "Checking foundLogins is array");
|
||||
is(TestObserver.results["form2"].data.get("foundLogins").length, 1, "Checking foundLogins contains one login");
|
||||
is(TestObserver.results["form2"].data.get("selectedLogin"), null, "Checking selectedLogin is null");
|
||||
|
||||
// Test notification of a form that wasn't filled because its password field
|
||||
// already contained a value.
|
||||
is(TestObserver.results["form3"].receivedNotification, true, "Checking observer was notified");
|
||||
is(TestObserver.results["form3"].data.get("didntFillReason"), "existingPassword", "Checking didntFillReason is existingPassword");
|
||||
is(SpecialPowers.unwrap(TestObserver.results["form3"].data.get("usernameField")), $_(3, "uname"), "Checking username field is correct");
|
||||
is(SpecialPowers.unwrap(TestObserver.results["form3"].data.get("passwordField")), $_(3, "pword"), "Checking password field is correct");
|
||||
is(TestObserver.results["form3"].data.get("foundLogins").constructor.name, "Array", "Checking foundLogins is array");
|
||||
is(TestObserver.results["form3"].data.get("foundLogins").length, 1, "Checking foundLogins contains one login");
|
||||
is(TestObserver.results["form3"].data.get("selectedLogin"), null, "Checking selectedLogin is null");
|
||||
|
||||
// Test notification of a form that wasn't filled because multiple logins
|
||||
// are available for the form.
|
||||
is(TestObserver.results["form5"].receivedNotification, true, "Checking observer was notified");
|
||||
is(TestObserver.results["form5"].data.get("didntFillReason"), "multipleLogins", "Checking didntFillReason is multipleLogins");
|
||||
is(SpecialPowers.unwrap(TestObserver.results["form5"].data.get("usernameField")), $_(5, "uname"), "Checking username field is correct");
|
||||
is(SpecialPowers.unwrap(TestObserver.results["form5"].data.get("passwordField")), $_(5, "pword"), "Checking password field is correct");
|
||||
is(TestObserver.results["form5"].data.get("foundLogins").constructor.name, "Array", "Checking foundLogins is array");
|
||||
is(TestObserver.results["form5"].data.get("foundLogins").length, 2, "Checking foundLogins contains two logins");
|
||||
is(TestObserver.results["form5"].data.get("selectedLogin"), null, "Checking selectedLogin is null");
|
||||
|
||||
// Test notification of a form that wasn't filled because autofillForms
|
||||
// was set to false (didntFillReason == noAutofillForms) is done in
|
||||
// test_basic_form_observer_autofillForms.html.
|
||||
|
||||
// Remove the observer
|
||||
SpecialPowers.removeObserver(TestObserver, "passwordmgr-found-logins");
|
||||
|
||||
// Remove the logins added for the multiple logins test.
|
||||
pwmgr.removeLogin(login1);
|
||||
pwmgr.removeLogin(login2);
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
window.addEventListener("runTests", startTest);
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -120,10 +120,10 @@ function* runTest() {
|
||||
|
||||
function waitForCompletion() {
|
||||
var observer = SpecialPowers.wrapCallback(function(subject, topic, data) {
|
||||
SpecialPowers.removeObserver(observer, "passwordmgr-found-logins");
|
||||
SpecialPowers.removeObserver(observer, "passwordmgr-processed-form");
|
||||
tester.next();
|
||||
});
|
||||
SpecialPowers.addObserver(observer, "passwordmgr-found-logins", false);
|
||||
SpecialPowers.addObserver(observer, "passwordmgr-processed-form", false);
|
||||
}
|
||||
|
||||
/* test 1 */
|
||||
|
||||
@@ -100,7 +100,7 @@ function loadNextTest() {
|
||||
observer = SpecialPowers.wrapCallback(function(subject, topic, data) {
|
||||
SimpleTest.executeSoon(() => { iframe.contentWindow.postMessage("go", "*"); });
|
||||
});
|
||||
SpecialPowers.addObserver(observer, "passwordmgr-found-logins", false);
|
||||
SpecialPowers.addObserver(observer, "passwordmgr-processed-form", false);
|
||||
}
|
||||
|
||||
ok(true, "Starting test #" + testNum);
|
||||
@@ -255,7 +255,7 @@ function handleLoad(aEvent) {
|
||||
aWin.close();
|
||||
});
|
||||
|
||||
SpecialPowers.removeObserver(observer, "passwordmgr-found-logins");
|
||||
SpecialPowers.removeObserver(observer, "passwordmgr-processed-form");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for recipes overriding login fields</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script type="application/javascript;version=1.8">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
let parentScriptURL = SimpleTest.getTestFileURL("pwmgr_common.js");
|
||||
mm = SpecialPowers.loadChromeScript(parentScriptURL);
|
||||
|
||||
// Tell the parent to setup test logins.
|
||||
mm.sendAsyncMessage("setupParent");
|
||||
|
||||
// When the setup is done, load a recipe for this test.
|
||||
mm.addMessageListener("doneSetup", function doneSetup() {
|
||||
mm.sendAsyncMessage("loadRecipes", {
|
||||
siteRecipes: [{
|
||||
hosts: ["mochi.test:8888"],
|
||||
usernameSelector: "input[name='uname1']",
|
||||
passwordSelector: "input[name='pword2']",
|
||||
}],
|
||||
});
|
||||
});
|
||||
|
||||
mm.addMessageListener("loadedRecipes", function loadedRecipes() {
|
||||
// Append the form dynamically so autofill is triggered after setup above.
|
||||
document.getElementById("content").innerHTML += `
|
||||
<!-- form with recipe for the username and password -->
|
||||
<form id="form1">
|
||||
<input type="text" name="uname1" oninput="reportFill(this, true)">
|
||||
<input type="text" name="uname2" oninput="reportFill(this, false)">
|
||||
<input type="password" name="pword1" oninput="reportFill(this, false)">
|
||||
<input type="password" name="pword2" oninput="reportFill(this, true)">
|
||||
</form>`;
|
||||
});
|
||||
|
||||
const EXPECTED_FILLS = 4;
|
||||
let fillCount = 0;
|
||||
|
||||
function reportFill(element, expected) {
|
||||
ok(expected, `${element.name} was filled`);
|
||||
if (++fillCount == EXPECTED_FILLS) {
|
||||
SimpleTest.finish();
|
||||
} else if (fillCount == 2) {
|
||||
document.getElementById("content").innerHTML = `
|
||||
<!-- Fallback to the default heuristics since the selectors don't match -->
|
||||
<form id="form2">
|
||||
<input type="text" name="uname3" oninput="reportFill(this, false)">
|
||||
<input type="text" name="uname4" oninput="reportFill(this, true)">
|
||||
<input type="password" name="pword3" oninput="reportFill(this, true)">
|
||||
<input type="password" name="pword4" oninput="reportFill(this, false)">
|
||||
</form>`;
|
||||
} else if (fillCount > EXPECTED_FILLS) {
|
||||
ok(false, "Too many fills");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<p id="display"></p>
|
||||
|
||||
<div id="content">
|
||||
// Forms are inserted dynamically
|
||||
</div>
|
||||
<pre id="test"></pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -16,6 +16,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/LoginRecipes.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
|
||||
"resource://gre/modules/DownloadPaths.jsm");
|
||||
@@ -198,6 +199,45 @@ const LoginTest = {
|
||||
}
|
||||
};
|
||||
|
||||
const RecipeHelpers = {
|
||||
initNewParent() {
|
||||
return (new LoginRecipesParent({ defaults: false })).initializationPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a document for the given URL containing the given HTML containing a
|
||||
* form and return the <form>.
|
||||
*/
|
||||
createTestForm(aDocumentURL, aHTML = "<form>") {
|
||||
let parser = Cc["@mozilla.org/xmlextras/domparser;1"].
|
||||
createInstance(Ci.nsIDOMParser);
|
||||
parser.init();
|
||||
let parsedDoc = parser.parseFromString(aHTML, "text/html");
|
||||
|
||||
// Mock the document.location object so we can unit test without a frame. We use a proxy
|
||||
// instead of just assigning to the property since it's not configurable or writable.
|
||||
let document = new Proxy(parsedDoc, {
|
||||
get(target, property, receiver) {
|
||||
// document.location is normally null when a document is outside of a "browsing context".
|
||||
// See https://html.spec.whatwg.org/#the-location-interface
|
||||
if (property == "location") {
|
||||
return new URL(aDocumentURL);
|
||||
}
|
||||
return target[property];
|
||||
},
|
||||
});
|
||||
|
||||
let form = parsedDoc.forms[0];
|
||||
|
||||
// Assign form.ownerDocument to the proxy so document.location works.
|
||||
Object.defineProperty(form, "ownerDocument", {
|
||||
value: document,
|
||||
});
|
||||
|
||||
return form;
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Predefined test data
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ Services.obs.removeObserver(TestObserver, "passwordmgr-storage-changed");
|
||||
LoginTest.clearData();
|
||||
|
||||
} catch (e) {
|
||||
throw "FAILED in test #" + testnum + " -- " + testdesc + ": " + e;
|
||||
throw new Error("FAILED in test #" + testnum + " -- " + testdesc + ": " + e);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests adding and retrieving LoginRecipes in the parent process.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function* test_init() {
|
||||
let parent = new LoginRecipesParent({ defaults: false });
|
||||
let initPromise1 = parent.initializationPromise;
|
||||
let initPromise2 = parent.initializationPromise;
|
||||
Assert.strictEqual(initPromise1, initPromise2, "Check that the same promise is returned");
|
||||
|
||||
let recipesParent = yield initPromise1;
|
||||
Assert.ok(recipesParent instanceof LoginRecipesParent, "Check init return value");
|
||||
Assert.strictEqual(recipesParent._recipesByHost.size, 0, "Initially 0 recipes");
|
||||
});
|
||||
|
||||
add_task(function* test_get_missing_host() {
|
||||
let recipesParent = yield RecipeHelpers.initNewParent();
|
||||
let exampleRecipes = recipesParent.getRecipesForHost("example.invalid");
|
||||
Assert.strictEqual(exampleRecipes.size, 0, "Check recipe count for example.invalid");
|
||||
|
||||
});
|
||||
|
||||
add_task(function* test_add_get_simple_host() {
|
||||
let recipesParent = yield RecipeHelpers.initNewParent();
|
||||
Assert.strictEqual(recipesParent._recipesByHost.size, 0, "Initially 0 recipes");
|
||||
recipesParent.add({
|
||||
hosts: ["example.com"],
|
||||
});
|
||||
Assert.strictEqual(recipesParent._recipesByHost.size, 1,
|
||||
"Check number of hosts after the addition");
|
||||
|
||||
let exampleRecipes = recipesParent.getRecipesForHost("example.com");
|
||||
Assert.strictEqual(exampleRecipes.size, 1, "Check recipe count for example.com");
|
||||
let recipe = [...exampleRecipes][0];
|
||||
Assert.strictEqual(typeof(recipe), "object", "Check recipe type");
|
||||
Assert.strictEqual(recipe.hosts.length, 1, "Check that one host is present");
|
||||
Assert.strictEqual(recipe.hosts[0], "example.com", "Check the one host");
|
||||
});
|
||||
|
||||
add_task(function* test_add_get_non_standard_port_host() {
|
||||
let recipesParent = yield RecipeHelpers.initNewParent();
|
||||
recipesParent.add({
|
||||
hosts: ["example.com:8080"],
|
||||
});
|
||||
Assert.strictEqual(recipesParent._recipesByHost.size, 1,
|
||||
"Check number of hosts after the addition");
|
||||
|
||||
let exampleRecipes = recipesParent.getRecipesForHost("example.com:8080");
|
||||
Assert.strictEqual(exampleRecipes.size, 1, "Check recipe count for example.com:8080");
|
||||
let recipe = [...exampleRecipes][0];
|
||||
Assert.strictEqual(typeof(recipe), "object", "Check recipe type");
|
||||
Assert.strictEqual(recipe.hosts.length, 1, "Check that one host is present");
|
||||
Assert.strictEqual(recipe.hosts[0], "example.com:8080", "Check the one host");
|
||||
});
|
||||
|
||||
add_task(function* test_add_multiple_hosts() {
|
||||
let recipesParent = yield RecipeHelpers.initNewParent();
|
||||
recipesParent.add({
|
||||
hosts: ["example.com", "foo.invalid"],
|
||||
});
|
||||
Assert.strictEqual(recipesParent._recipesByHost.size, 2,
|
||||
"Check number of hosts after the addition");
|
||||
|
||||
let exampleRecipes = recipesParent.getRecipesForHost("example.com");
|
||||
Assert.strictEqual(exampleRecipes.size, 1, "Check recipe count for example.com");
|
||||
let recipe = [...exampleRecipes][0];
|
||||
Assert.strictEqual(typeof(recipe), "object", "Check recipe type");
|
||||
Assert.strictEqual(recipe.hosts.length, 2, "Check that two hosts are present");
|
||||
Assert.strictEqual(recipe.hosts[0], "example.com", "Check the first host");
|
||||
Assert.strictEqual(recipe.hosts[1], "foo.invalid", "Check the second host");
|
||||
|
||||
let fooRecipes = recipesParent.getRecipesForHost("foo.invalid");
|
||||
Assert.strictEqual(fooRecipes.size, 1, "Check recipe count for foo.invalid");
|
||||
let fooRecipe = [...fooRecipes][0];
|
||||
Assert.strictEqual(fooRecipe, recipe, "Check that the recipe is shared");
|
||||
Assert.strictEqual(typeof(fooRecipe), "object", "Check recipe type");
|
||||
Assert.strictEqual(fooRecipe.hosts.length, 2, "Check that two hosts are present");
|
||||
Assert.strictEqual(fooRecipe.hosts[0], "example.com", "Check the first host");
|
||||
Assert.strictEqual(fooRecipe.hosts[1], "foo.invalid", "Check the second host");
|
||||
});
|
||||
|
||||
add_task(function* test_add_pathRegex() {
|
||||
let recipesParent = yield RecipeHelpers.initNewParent();
|
||||
recipesParent.add({
|
||||
hosts: ["example.com"],
|
||||
pathRegex: /^\/mypath\//,
|
||||
});
|
||||
Assert.strictEqual(recipesParent._recipesByHost.size, 1,
|
||||
"Check number of hosts after the addition");
|
||||
|
||||
let exampleRecipes = recipesParent.getRecipesForHost("example.com");
|
||||
Assert.strictEqual(exampleRecipes.size, 1, "Check recipe count for example.com");
|
||||
let recipe = [...exampleRecipes][0];
|
||||
Assert.strictEqual(typeof(recipe), "object", "Check recipe type");
|
||||
Assert.strictEqual(recipe.hosts.length, 1, "Check that one host is present");
|
||||
Assert.strictEqual(recipe.hosts[0], "example.com", "Check the one host");
|
||||
Assert.strictEqual(recipe.pathRegex.toString(), "/^\\/mypath\\//", "Check the pathRegex");
|
||||
});
|
||||
|
||||
add_task(function* test_add_selectors() {
|
||||
let recipesParent = yield RecipeHelpers.initNewParent();
|
||||
recipesParent.add({
|
||||
hosts: ["example.com"],
|
||||
usernameSelector: "#my-username",
|
||||
passwordSelector: "#my-form > input.password",
|
||||
});
|
||||
Assert.strictEqual(recipesParent._recipesByHost.size, 1,
|
||||
"Check number of hosts after the addition");
|
||||
|
||||
let exampleRecipes = recipesParent.getRecipesForHost("example.com");
|
||||
Assert.strictEqual(exampleRecipes.size, 1, "Check recipe count for example.com");
|
||||
let recipe = [...exampleRecipes][0];
|
||||
Assert.strictEqual(typeof(recipe), "object", "Check recipe type");
|
||||
Assert.strictEqual(recipe.hosts.length, 1, "Check that one host is present");
|
||||
Assert.strictEqual(recipe.hosts[0], "example.com", "Check the one host");
|
||||
Assert.strictEqual(recipe.usernameSelector, "#my-username", "Check the usernameSelector");
|
||||
Assert.strictEqual(recipe.passwordSelector, "#my-form > input.password", "Check the passwordSelector");
|
||||
});
|
||||
|
||||
/* Begin checking errors with add */
|
||||
|
||||
add_task(function* test_add_missing_prop() {
|
||||
let recipesParent = yield RecipeHelpers.initNewParent();
|
||||
Assert.throws(() => recipesParent.add({}), /required/, "Some properties are required");
|
||||
});
|
||||
|
||||
add_task(function* test_add_unknown_prop() {
|
||||
let recipesParent = yield RecipeHelpers.initNewParent();
|
||||
Assert.throws(() => recipesParent.add({
|
||||
unknownProp: true,
|
||||
}), /supported/, "Unknown properties should cause an error to help with typos");
|
||||
});
|
||||
|
||||
add_task(function* test_add_invalid_hosts() {
|
||||
let recipesParent = yield RecipeHelpers.initNewParent();
|
||||
Assert.throws(() => recipesParent.add({
|
||||
hosts: 404,
|
||||
}), /array/, "hosts should be an array");
|
||||
});
|
||||
|
||||
add_task(function* test_add_empty_host_array() {
|
||||
let recipesParent = yield RecipeHelpers.initNewParent();
|
||||
Assert.throws(() => recipesParent.add({
|
||||
hosts: [],
|
||||
}), /array/, "hosts should be a non-empty array");
|
||||
});
|
||||
|
||||
add_task(function* test_add_pathRegex_non_regexp() {
|
||||
let recipesParent = yield RecipeHelpers.initNewParent();
|
||||
Assert.throws(() => recipesParent.add({
|
||||
hosts: ["example.com"],
|
||||
pathRegex: "foo",
|
||||
}), /regular expression/, "pathRegex should be a RegExp");
|
||||
});
|
||||
|
||||
add_task(function* test_add_usernameSelector_non_string() {
|
||||
let recipesParent = yield RecipeHelpers.initNewParent();
|
||||
Assert.throws(() => recipesParent.add({
|
||||
hosts: ["example.com"],
|
||||
usernameSelector: 404,
|
||||
}), /string/, "usernameSelector should be a string");
|
||||
});
|
||||
|
||||
add_task(function* test_add_passwordSelector_non_string() {
|
||||
let recipesParent = yield RecipeHelpers.initNewParent();
|
||||
Assert.throws(() => recipesParent.add({
|
||||
hosts: ["example.com"],
|
||||
passwordSelector: 404,
|
||||
}), /string/, "passwordSelector should be a string");
|
||||
});
|
||||
|
||||
/* End checking errors with add */
|
||||
@@ -0,0 +1,41 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test filtering recipes in LoginRecipesContent.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.importGlobalProperties(["URL"]);
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
add_task(function* test_getFieldOverrides() {
|
||||
let recipes = new Set([
|
||||
{ // path doesn't match but otherwise good
|
||||
hosts: ["example.com:8080"],
|
||||
passwordSelector: "#password",
|
||||
pathRegex: /^\/$/,
|
||||
usernameSelector: ".username",
|
||||
},
|
||||
{ // match with no field overrides
|
||||
hosts: ["example.com:8080"],
|
||||
},
|
||||
{ // best match (field selectors + path match)
|
||||
description: "best match",
|
||||
hosts: ["a.invalid", "example.com:8080", "other.invalid"],
|
||||
passwordSelector: "#password",
|
||||
pathRegex: /^\/first\/second\/$/,
|
||||
usernameSelector: ".username",
|
||||
},
|
||||
]);
|
||||
|
||||
let form = RecipeHelpers.createTestForm("http://localhost:8080/first/second/");
|
||||
let override = LoginRecipesContent.getFieldOverrides(recipes, form);
|
||||
Assert.strictEqual(override.description, "best match",
|
||||
"Check the best field override recipe was returned");
|
||||
Assert.strictEqual(override.usernameSelector, ".username", "Check usernameSelector");
|
||||
Assert.strictEqual(override.passwordSelector, "#password", "Check passwordSelector");
|
||||
});
|
||||
@@ -423,7 +423,7 @@ LoginTest.deleteFile(OS.Constants.Path.profileDir, filename + ".corrupt");
|
||||
LoginTest.deleteFile(OS.Constants.Path.profileDir, filename);
|
||||
|
||||
} catch (e) {
|
||||
throw "FAILED in test #" + testnum + " -- " + testdesc + ": " + e;
|
||||
throw new Error("FAILED in test #" + testnum + " -- " + testdesc + ": " + e);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -23,5 +23,7 @@ skip-if = os != "android"
|
||||
[test_logins_metainfo.js]
|
||||
[test_logins_search.js]
|
||||
[test_notifications.js]
|
||||
[test_recipes_add.js]
|
||||
[test_recipes_content.js]
|
||||
[test_storage.js]
|
||||
[test_telemetry.js]
|
||||
|
||||
Reference in New Issue
Block a user