diff --git a/accessible/windows/msaa/Compatibility.cpp b/accessible/windows/msaa/Compatibility.cpp
index 9bdb038c8ca4..6c39c0950113 100644
--- a/accessible/windows/msaa/Compatibility.cpp
+++ b/accessible/windows/msaa/Compatibility.cpp
@@ -23,6 +23,25 @@
using namespace mozilla;
using namespace mozilla::a11y;
+/**
+ * String versions of consumer flags. See GetHumanReadableConsumersStr.
+ */
+static const wchar_t* ConsumerStringMap[CONSUMERS_ENUM_LEN+1] = {
+ L"NVDA",
+ L"JAWS",
+ L"OLDJAWS",
+ L"WE",
+ L"DOLPHIN",
+ L"SEROTEK",
+ L"COBRA",
+ L"ZOOMTEXT",
+ L"KAZAGURU",
+ L"YOUDAO",
+ L"UNKNOWN",
+ L"UIAUTOMATION",
+ L"\0"
+};
+
/**
* Return true if module version is lesser than the given version.
*/
@@ -398,3 +417,22 @@ Compatibility::GetActCtxResourceId()
#endif // defined(HAVE_64BIT_BUILD)
}
+// static
+void
+Compatibility::GetHumanReadableConsumersStr(nsAString &aResult)
+{
+ bool appened = false;
+ uint32_t index = 0;
+ for (uint32_t consumers = sConsumers; consumers; consumers = consumers >> 1) {
+ if (consumers & 0x1) {
+ if (appened) {
+ aResult.AppendLiteral(",");
+ }
+ aResult.Append(ConsumerStringMap[index]);
+ appened = true;
+ }
+ if (++index > CONSUMERS_ENUM_LEN) {
+ break;
+ }
+ }
+}
diff --git a/accessible/windows/msaa/Compatibility.h b/accessible/windows/msaa/Compatibility.h
index 6ecb8ca8cd7e..f95a88c58cc0 100644
--- a/accessible/windows/msaa/Compatibility.h
+++ b/accessible/windows/msaa/Compatibility.h
@@ -7,6 +7,7 @@
#ifndef COMPATIBILITY_MANAGER_H
#define COMPATIBILITY_MANAGER_H
+#include "nsString.h"
#include
namespace mozilla {
@@ -45,6 +46,12 @@ class Compatibility
*/
static uint16_t GetActCtxResourceId();
+ /**
+ * Return a string describing sConsumers suitable for about:support.
+ * Exposed through nsIXULRuntime.accessibilityInstantiator.
+ */
+ static void GetHumanReadableConsumersStr(nsAString &aResult);
+
private:
Compatibility();
Compatibility(const Compatibility&);
@@ -74,6 +81,7 @@ class Compatibility
UNKNOWN = 1 << 10,
UIAUTOMATION = 1 << 11
};
+ #define CONSUMERS_ENUM_LEN 12
private:
static uint32_t sConsumers;
diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml
index 605747189330..cff8e82d1918 100644
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2327,6 +2327,7 @@
+
@@ -2639,7 +2641,7 @@
b.registeredOpenURI = lazyBrowserURI;
}
} else {
- this._insertBrowser(t);
+ this._insertBrowser(t, true);
}
// Dispatch a new tab notification. We do this once we're
diff --git a/browser/components/extensions/ext-browser.js b/browser/components/extensions/ext-browser.js
index abd0e6b17c88..4f93170a16d0 100644
--- a/browser/components/extensions/ext-browser.js
+++ b/browser/components/extensions/ext-browser.js
@@ -569,6 +569,10 @@ class Tab extends TabBase {
return this.nativeTab.linkedBrowser;
}
+ get discarded() {
+ return !this.nativeTab.linkedPanel;
+ }
+
get frameLoader() {
// If we don't have a frameLoader yet, just return a dummy with no width and
// height.
diff --git a/browser/components/extensions/ext-tabs.js b/browser/components/extensions/ext-tabs.js
index f76dba03e917..1141d4261adb 100644
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -262,6 +262,9 @@ this.tabs = class extends ExtensionAPI {
needed.push("pinned");
} else if (event.type == "TabUnpinned") {
needed.push("pinned");
+ } else if (event.type == "TabBrowserInserted" &&
+ !event.detail.insertedOnTabCreation) {
+ needed.push("discarded");
}
let tab = tabManager.getWrapper(event.originalTarget);
@@ -290,12 +293,14 @@ this.tabs = class extends ExtensionAPI {
windowTracker.addListener("TabAttrModified", listener);
windowTracker.addListener("TabPinned", listener);
windowTracker.addListener("TabUnpinned", listener);
+ windowTracker.addListener("TabBrowserInserted", listener);
return () => {
windowTracker.removeListener("status", statusListener);
windowTracker.removeListener("TabAttrModified", listener);
windowTracker.removeListener("TabPinned", listener);
windowTracker.removeListener("TabUnpinned", listener);
+ windowTracker.removeListener("TabBrowserInserted", listener);
};
}).api(),
diff --git a/browser/components/extensions/schemas/tabs.json b/browser/components/extensions/schemas/tabs.json
index bf3348106670..7b01f51e5488 100644
--- a/browser/components/extensions/schemas/tabs.json
+++ b/browser/components/extensions/schemas/tabs.json
@@ -71,6 +71,7 @@
"title": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The title of the tab. This property is only present if the extension's manifest includes the \"tabs\"
permission."},
"favIconUrl": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL of the tab's favicon. This property is only present if the extension's manifest includes the \"tabs\"
permission. It may also be an empty string if the tab is loading."},
"status": {"type": "string", "optional": true, "description": "Either loading or complete."},
+ "discarded": {"type": "boolean", "optional": true, "description": "True while the tab is not loaded with content."},
"incognito": {"type": "boolean", "description": "Whether the tab is in an incognito window."},
"width": {"type": "integer", "optional": true, "description": "The width of the tab in pixels."},
"height": {"type": "integer", "optional": true, "description": "The height of the tab in pixels."},
@@ -589,6 +590,11 @@
"optional": true,
"description": "Whether the tabs have completed loading."
},
+ "discarded": {
+ "type": "boolean",
+ "optional": true,
+ "description": "True while the tabs are not loaded with content."
+ },
"title": {
"type": "string",
"optional": true,
@@ -1195,6 +1201,11 @@
"optional": true,
"description": "The status of the tab. Can be either loading or complete."
},
+ "discarded": {
+ "type": "boolean",
+ "optional": true,
+ "description": "True while the tab is not loaded with content."
+ },
"url": {
"type": "string",
"optional": true,
diff --git a/browser/components/extensions/test/browser/browser-common.ini b/browser/components/extensions/test/browser/browser-common.ini
index 8a36882ce716..2bf1b1abe552 100644
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -127,6 +127,7 @@ skip-if = debug || asan # Bug 1354681
[browser_ext_tabs_create.js]
[browser_ext_tabs_create_invalid_url.js]
[browser_ext_tabs_detectLanguage.js]
+[browser_ext_tabs_discarded.js]
[browser_ext_tabs_duplicate.js]
[browser_ext_tabs_events.js]
[browser_ext_tabs_executeScript.js]
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_discarded.js b/browser/components/extensions/test/browser/browser_ext_tabs_discarded.js
new file mode 100644
index 000000000000..ea9b08bba1b8
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_discarded.js
@@ -0,0 +1,66 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* global gBrowser SessionStore */
+"use strict";
+
+let lazyTabState = {entries: [{url: "http://example.com/", title: "Example Domain"}]};
+
+add_task(async function test_discarded() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: async function() {
+ let onCreatedTabData = [];
+ let discardedEventData = [];
+
+ async function finishTest() {
+ browser.test.assertEq(0, discardedEventData.length, "number of discarded events fired");
+
+ onCreatedTabData.sort((data1, data2) => data1.index - data2.index);
+ browser.test.assertEq(false, onCreatedTabData[0].discarded, "non-lazy tab onCreated discard property");
+ browser.test.assertEq(true, onCreatedTabData[1].discarded, "lazy tab onCreated discard property");
+
+ let tabs = await browser.tabs.query({currentWindow: true});
+ tabs.sort((tab1, tab2) => tab1.index - tab2.index);
+
+ browser.test.assertEq(false, tabs[1].discarded, "non-lazy tab query discard property");
+ browser.test.assertEq(true, tabs[2].discarded, "lazy tab query discard property");
+
+ let updatedTab = await browser.tabs.update(tabs[2].id, {active: true});
+ browser.test.assertEq(false, updatedTab.discarded, "lazy to non-lazy update discard property");
+ browser.test.assertEq(false, discardedEventData[0], "lazy to non-lazy onUpdated discard property");
+
+ browser.test.notifyPass("test-finished");
+ }
+
+ browser.tabs.onUpdated.addListener(function(tabId, updatedInfo) {
+ if ("discarded" in updatedInfo) {
+ discardedEventData.push(updatedInfo.discarded);
+ }
+ });
+
+ browser.tabs.onCreated.addListener(function(tab) {
+ onCreatedTabData.push({discarded: tab.discarded, index: tab.index});
+ if (onCreatedTabData.length == 2) {
+ finishTest();
+ }
+ });
+ },
+ });
+
+ await extension.startup();
+
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com");
+
+ let tab2 = BrowserTestUtils.addTab(gBrowser, "about:blank", {createLazyBrowser: true});
+ SessionStore.setTabState(tab2, JSON.stringify(lazyTabState));
+
+ await extension.awaitFinish("test-finished");
+ await extension.unload();
+
+ await BrowserTestUtils.removeTab(tab1);
+ await BrowserTestUtils.removeTab(tab2);
+});
+
diff --git a/dom/base/Attr.cpp b/dom/base/Attr.cpp
index 4ada5b6de669..58d8fdf3d1c0 100644
--- a/dom/base/Attr.cpp
+++ b/dom/base/Attr.cpp
@@ -319,13 +319,6 @@ Attr::GetChildAt(uint32_t aIndex) const
return nullptr;
}
-nsIContent * const *
-Attr::GetChildArray(uint32_t* aChildCount) const
-{
- *aChildCount = 0;
- return nullptr;
-}
-
int32_t
Attr::IndexOf(const nsINode* aPossibleChild) const
{
diff --git a/dom/base/Attr.h b/dom/base/Attr.h
index 5b4210c3d4c7..017aa6f80a1a 100644
--- a/dom/base/Attr.h
+++ b/dom/base/Attr.h
@@ -65,7 +65,6 @@ class Attr final : public nsIAttribute,
virtual bool IsNodeOfType(uint32_t aFlags) const override;
virtual uint32_t GetChildCount() const override;
virtual nsIContent *GetChildAt(uint32_t aIndex) const override;
- virtual nsIContent * const * GetChildArray(uint32_t* aChildCount) const override;
virtual int32_t IndexOf(const nsINode* aPossibleChild) const override;
virtual nsresult InsertChildAt(nsIContent* aKid, uint32_t aIndex,
bool aNotify) override;
diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp
index e9dbdd315ce3..dac3af6f1107 100644
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3044,9 +3044,15 @@ Element::List(FILE* out, int32_t aIndent,
static_cast(State().GetInternalValue()));
fprintf(out, " flags=[%08x]", static_cast(GetFlags()));
if (IsCommonAncestorForRangeInSelection()) {
- const nsTHashtable>* ranges =
- GetExistingCommonAncestorRanges();
- fprintf(out, " ranges:%d", ranges ? ranges->Count() : 0);
+ const LinkedList* ranges = GetExistingCommonAncestorRanges();
+ int32_t count = 0;
+ if (ranges) {
+ // Can't use range-based iteration on a const LinkedList, unfortunately.
+ for (const nsRange* r = ranges->getFirst(); r; r = r->getNext()) {
+ ++count;
+ }
+ }
+ fprintf(out, " ranges:%d", count);
}
fprintf(out, " primaryframe=%p", static_cast(GetPrimaryFrame()));
fprintf(out, " refcount=%" PRIuPTR "<", mRefCnt.get());
diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp
index 8e33f6598aa5..8003f1418eec 100644
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -2327,12 +2327,6 @@ FragmentOrElement::GetChildAt(uint32_t aIndex) const
return mAttrsAndChildren.GetSafeChildAt(aIndex);
}
-nsIContent * const *
-FragmentOrElement::GetChildArray(uint32_t* aChildCount) const
-{
- return mAttrsAndChildren.GetChildArray(aChildCount);
-}
-
int32_t
FragmentOrElement::IndexOf(const nsINode* aPossibleChild) const
{
diff --git a/dom/base/FragmentOrElement.h b/dom/base/FragmentOrElement.h
index 575e894a31e9..0bf09a6ba2db 100644
--- a/dom/base/FragmentOrElement.h
+++ b/dom/base/FragmentOrElement.h
@@ -120,7 +120,6 @@ class FragmentOrElement : public nsIContent
// nsINode interface methods
virtual uint32_t GetChildCount() const override;
virtual nsIContent *GetChildAt(uint32_t aIndex) const override;
- virtual nsIContent * const * GetChildArray(uint32_t* aChildCount) const override;
virtual int32_t IndexOf(const nsINode* aPossibleChild) const override;
virtual nsresult InsertChildAt(nsIContent* aKid, uint32_t aIndex,
bool aNotify) override;
diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp
index 2110d7cd11bc..acfde3bc0210 100644
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -4354,13 +4354,6 @@ nsDocument::GetChildCount() const
return mChildren.ChildCount();
}
-nsIContent * const *
-nsDocument::GetChildArray(uint32_t* aChildCount) const
-{
- return mChildren.GetChildArray(aChildCount);
-}
-
-
nsresult
nsDocument::InsertChildAt(nsIContent* aKid, uint32_t aIndex,
bool aNotify)
diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h
index 9cdf97bffd91..be3d59698b56 100644
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -598,7 +598,6 @@ class nsDocument : public nsIDocument,
// nsINode
virtual bool IsNodeOfType(uint32_t aFlags) const override;
virtual nsIContent *GetChildAt(uint32_t aIndex) const override;
- virtual nsIContent * const * GetChildArray(uint32_t* aChildCount) const override;
virtual int32_t IndexOf(const nsINode* aPossibleChild) const override;
virtual uint32_t GetChildCount() const override;
virtual nsresult InsertChildAt(nsIContent* aKid, uint32_t aIndex,
diff --git a/dom/base/nsGenericDOMDataNode.cpp b/dom/base/nsGenericDOMDataNode.cpp
index 7b1b4a44f7db..fe88292a7e8c 100644
--- a/dom/base/nsGenericDOMDataNode.cpp
+++ b/dom/base/nsGenericDOMDataNode.cpp
@@ -681,13 +681,6 @@ nsGenericDOMDataNode::GetChildAt(uint32_t aIndex) const
return nullptr;
}
-nsIContent * const *
-nsGenericDOMDataNode::GetChildArray(uint32_t* aChildCount) const
-{
- *aChildCount = 0;
- return nullptr;
-}
-
int32_t
nsGenericDOMDataNode::IndexOf(const nsINode* aPossibleChild) const
{
diff --git a/dom/base/nsGenericDOMDataNode.h b/dom/base/nsGenericDOMDataNode.h
index f79cc22acded..1f4b3a57603c 100644
--- a/dom/base/nsGenericDOMDataNode.h
+++ b/dom/base/nsGenericDOMDataNode.h
@@ -100,7 +100,6 @@ class nsGenericDOMDataNode : public nsIContent
// nsINode methods
virtual uint32_t GetChildCount() const override;
virtual nsIContent *GetChildAt(uint32_t aIndex) const override;
- virtual nsIContent * const * GetChildArray(uint32_t* aChildCount) const override;
virtual int32_t IndexOf(const nsINode* aPossibleChild) const override;
virtual nsresult InsertChildAt(nsIContent* aKid, uint32_t aIndex,
bool aNotify) override;
diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h
index 67cd5d6da069..6e64ef6464ca 100644
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -19,6 +19,7 @@
#include "nsTObserverArray.h" // for member
#include "nsWindowSizes.h" // for nsStyleSizes
#include "mozilla/ErrorResult.h"
+#include "mozilla/LinkedList.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/dom/EventTarget.h" // for base class
#include "js/TypeDecls.h" // for Handle, Value, JSObject, JSContext
@@ -500,17 +501,6 @@ class nsINode : public mozilla::dom::EventTarget
*/
virtual nsIContent* GetChildAt(uint32_t aIndex) const = 0;
- /**
- * Get a raw pointer to the child array. This should only be used if you
- * plan to walk a bunch of the kids, promise to make sure that nothing ever
- * mutates (no attribute changes, not DOM tree changes, no script execution,
- * NOTHING), and will never ever peform an out-of-bounds access here. This
- * method may return null if there are no children, or it may return a
- * garbage pointer. In all cases the out param will be set to the number of
- * children.
- */
- virtual nsIContent * const * GetChildArray(uint32_t* aChildCount) const = 0;
-
/**
* Get the index of a child within this content
* @param aPossibleChild the child to get the index of.
@@ -1132,10 +1122,12 @@ class nsINode : public mozilla::dom::EventTarget
nsNodeWeakReference* MOZ_NON_OWNING_REF mWeakReference;
/**
- * A set of ranges in the common ancestor for the selection to which
- * this node belongs to.
+ * A set of ranges which are in the selection and which have this node as
+ * their endpoints' common ancestor. This is a UniquePtr instead of just a
+ * LinkedList, because that prevents us from pushing DOMSlots up to the next
+ * allocation bucket size, at the cost of some complexity.
*/
- mozilla::UniquePtr>> mCommonAncestorRanges;
+ mozilla::UniquePtr> mCommonAncestorRanges;
/**
* Number of descendant nodes in the uncomposed document that have been
@@ -1288,10 +1280,9 @@ class nsINode : public mozilla::dom::EventTarget
nsIContent* GetFirstChild() const { return mFirstChild; }
nsIContent* GetLastChild() const
{
- uint32_t count;
- nsIContent* const* children = GetChildArray(&count);
+ uint32_t count = GetChildCount();
- return count > 0 ? children[count - 1] : nullptr;
+ return count > 0 ? GetChildAt(count - 1) : nullptr;
}
/**
@@ -1943,23 +1934,23 @@ class nsINode : public mozilla::dom::EventTarget
CallerType aCallerType,
ErrorResult& aRv);
- const nsTHashtable>* GetExistingCommonAncestorRanges() const
+ const mozilla::LinkedList* GetExistingCommonAncestorRanges() const
{
if (!HasSlots()) {
return nullptr;
}
- mozilla::UniquePtr>>& ranges =
- GetExistingSlots()->mCommonAncestorRanges;
- return ranges.get();
+ return GetExistingSlots()->mCommonAncestorRanges.get();
}
- nsTHashtable>* GetExistingCommonAncestorRanges()
+ mozilla::LinkedList* GetExistingCommonAncestorRanges()
{
- nsINode::nsSlots* slots = GetExistingSlots();
- return slots ? slots->mCommonAncestorRanges.get() : nullptr;
+ if (!HasSlots()) {
+ return nullptr;
+ }
+ return GetExistingSlots()->mCommonAncestorRanges.get();
}
- mozilla::UniquePtr>>& GetCommonAncestorRangesPtr()
+ mozilla::UniquePtr>& GetCommonAncestorRangesPtr()
{
return Slots()->mCommonAncestorRanges;
}
diff --git a/dom/base/nsRange.cpp b/dom/base/nsRange.cpp
index 678428d2a99e..d495215a2a8e 100644
--- a/dom/base/nsRange.cpp
+++ b/dom/base/nsRange.cpp
@@ -205,14 +205,14 @@ nsRange::IsNodeSelected(nsINode* aNode, uint32_t aStartOffset,
nsTHashtable> ancestorSelections;
uint32_t maxRangeCount = 0;
for (; n; n = GetNextRangeCommonAncestor(n->GetParentNode())) {
- nsTHashtable>* ranges =
- n->GetExistingCommonAncestorRanges();
+ LinkedList* ranges = n->GetExistingCommonAncestorRanges();
if (!ranges) {
continue;
}
- for (auto iter = ranges->ConstIter(); !iter.Done(); iter.Next()) {
- nsRange* range = iter.Get()->GetKey();
- if (range->IsInSelection() && !range->Collapsed()) {
+ for (nsRange* range : *ranges) {
+ MOZ_ASSERT(range->IsInSelection(),
+ "Why is this range registeed with a node?");
+ if (!range->Collapsed()) {
ancestorSelectionRanges.PutEntry(range);
Selection* selection = range->mSelection;
ancestorSelections.PutEntry(selection);
@@ -337,6 +337,14 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(nsRange)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsRange)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner);
+
+ // We _could_ just rely on Reset() to UnregisterCommonAncestor(),
+ // but it wouldn't know we're calling it from Unlink and so would do
+ // more work than it really needs to.
+ if (tmp->mRegisteredCommonAncestor) {
+ tmp->UnregisterCommonAncestor(tmp->mRegisteredCommonAncestor, true);
+ }
+
tmp->Reset();
// This needs to be unlinked after Reset() is called, as it controls
@@ -410,46 +418,45 @@ nsRange::RegisterCommonAncestor(nsINode* aNode)
MarkDescendants(aNode);
- UniquePtr>>& ranges =
- aNode->GetCommonAncestorRangesPtr();
+ UniquePtr>& ranges = aNode->GetCommonAncestorRangesPtr();
if (!ranges) {
- ranges = MakeUnique();
+ ranges = MakeUnique>();
}
- ranges->PutEntry(this);
+ ranges->insertBack(this);
aNode->SetCommonAncestorForRangeInSelection();
}
void
-nsRange::UnregisterCommonAncestor(nsINode* aNode)
+nsRange::UnregisterCommonAncestor(nsINode* aNode, bool aIsUnlinking)
{
NS_PRECONDITION(aNode, "bad arg");
NS_ASSERTION(aNode->IsCommonAncestorForRangeInSelection(), "wrong node");
MOZ_DIAGNOSTIC_ASSERT(aNode == mRegisteredCommonAncestor, "wrong node");
- nsTHashtable>* ranges =
- aNode->GetExistingCommonAncestorRanges();
+ LinkedList* ranges = aNode->GetExistingCommonAncestorRanges();
MOZ_ASSERT(ranges);
- NS_ASSERTION(ranges->GetEntry(this), "unknown range");
mRegisteredCommonAncestor = nullptr;
- bool isNativeAnon = aNode->IsInNativeAnonymousSubtree();
- bool removed = false;
+#ifdef DEBUG
+ bool found = false;
+ for (nsRange* range : *ranges) {
+ if (range == this) {
+ found = true;
+ break;
+ }
+ }
+ MOZ_ASSERT(found,
+ "We should be in the list on our registered common ancestor");
+#endif // DEBUG
+
+ remove();
- if (ranges->Count() == 1) {
+ // We don't want to waste time unmarking flags on nodes that are
+ // being unlinked anyway.
+ if (!aIsUnlinking && ranges->isEmpty()) {
aNode->ClearCommonAncestorForRangeInSelection();
- if (!isNativeAnon) {
- // For nodes which are in native anonymous subtrees, we optimize away the
- // cost of deallocating the hashtable here because we may need to create
- // it again shortly afterward. We don't do this for all nodes in order
- // to avoid the additional memory usage unconditionally.
- aNode->GetCommonAncestorRangesPtr().reset();
- removed = true;
- }
UnmarkDescendants(aNode);
}
- if (!removed) {
- ranges->RemoveEntry(this);
- }
}
/******************************************************
@@ -512,7 +519,7 @@ nsRange::CharacterDataChanged(nsIDocument* aDocument,
bool isCommonAncestor =
IsInSelection() && mStart.Container() == mEnd.Container();
if (isCommonAncestor) {
- UnregisterCommonAncestor(mStart.Container());
+ UnregisterCommonAncestor(mStart.Container(), false);
RegisterCommonAncestor(newStart.Container());
}
if (mStart.Container()->IsDescendantOfCommonAncestorForRangeInSelection()) {
@@ -546,7 +553,7 @@ nsRange::CharacterDataChanged(nsIDocument* aDocument,
IsInSelection() && mStart.Container() == mEnd.Container();
if (isCommonAncestor && !newStart.Container()) {
// The split occurs inside the range.
- UnregisterCommonAncestor(mStart.Container());
+ UnregisterCommonAncestor(mStart.Container(), false);
RegisterCommonAncestor(mStart.Container()->GetParentNode());
newEnd.Container()->SetDescendantOfCommonAncestorForRangeInSelection();
} else if (mEnd.Container()->
@@ -974,15 +981,19 @@ nsRange::DoSetRange(const RawRangeBoundary& aStart,
bool checkCommonAncestor =
(mStart.Container() != aStart.Container() || mEnd.Container() != aEnd.Container()) &&
IsInSelection() && !aNotInsertedYet;
- nsINode* oldCommonAncestor = checkCommonAncestor ? GetCommonAncestor() : nullptr;
+
+ // GetCommonAncestor is unreliable while we're unlinking (could
+ // return null if our start/end have already been unlinked), so make
+ // sure to not use it here to determine our "old" current ancestor.
mStart = aStart;
mEnd = aEnd;
mIsPositioned = !!mStart.Container();
if (checkCommonAncestor) {
+ nsINode* oldCommonAncestor = mRegisteredCommonAncestor;
nsINode* newCommonAncestor = GetCommonAncestor();
if (newCommonAncestor != oldCommonAncestor) {
if (oldCommonAncestor) {
- UnregisterCommonAncestor(oldCommonAncestor);
+ UnregisterCommonAncestor(oldCommonAncestor, false);
}
if (newCommonAncestor) {
RegisterCommonAncestor(newCommonAncestor);
@@ -1034,12 +1045,12 @@ nsRange::SetSelection(mozilla::dom::Selection* aSelection)
MOZ_ASSERT(!aSelection || !mSelection);
mSelection = aSelection;
- nsINode* commonAncestor = GetCommonAncestor();
- NS_ASSERTION(commonAncestor, "unexpected disconnected nodes");
if (mSelection) {
+ nsINode* commonAncestor = GetCommonAncestor();
+ NS_ASSERTION(commonAncestor, "unexpected disconnected nodes");
RegisterCommonAncestor(commonAncestor);
} else {
- UnregisterCommonAncestor(commonAncestor);
+ UnregisterCommonAncestor(mRegisteredCommonAncestor, false);
}
}
diff --git a/dom/base/nsRange.h b/dom/base/nsRange.h
index deb5b204c0fd..f4c89ef1b72e 100644
--- a/dom/base/nsRange.h
+++ b/dom/base/nsRange.h
@@ -22,6 +22,7 @@
#include "nsWrapperCache.h"
#include "mozilla/Attributes.h"
#include "mozilla/GuardObjects.h"
+#include "mozilla/LinkedList.h"
namespace mozilla {
class ErrorResult;
@@ -36,7 +37,9 @@ class Selection;
class nsRange final : public nsIDOMRange,
public nsStubMutationObserver,
- public nsWrapperCache
+ public nsWrapperCache,
+ // For linking together selection-associated ranges.
+ public mozilla::LinkedListElement
{
typedef mozilla::ErrorResult ErrorResult;
typedef mozilla::dom::DOMRect DOMRect;
@@ -590,7 +593,7 @@ class nsRange final : public nsIDOMRange,
typedef RangeBoundaryBase RawRangeBoundary;
void RegisterCommonAncestor(nsINode* aNode);
- void UnregisterCommonAncestor(nsINode* aNode);
+ void UnregisterCommonAncestor(nsINode* aNode, bool aIsUnlinking);
nsINode* IsValidBoundary(nsINode* aNode) const
{
return ComputeRootNode(aNode, mMaySpanAnonymousSubtrees);
diff --git a/dom/base/nsTextNode.cpp b/dom/base/nsTextNode.cpp
index 0f5e6ac9cc26..31464d38619b 100644
--- a/dom/base/nsTextNode.cpp
+++ b/dom/base/nsTextNode.cpp
@@ -165,9 +165,15 @@ nsTextNode::List(FILE* out, int32_t aIndent) const
fprintf(out, "Text@%p", static_cast(this));
fprintf(out, " flags=[%08x]", static_cast(GetFlags()));
if (IsCommonAncestorForRangeInSelection()) {
- const nsTHashtable>* ranges =
- GetExistingCommonAncestorRanges();
- fprintf(out, " ranges:%d", ranges ? ranges->Count() : 0);
+ const LinkedList* ranges = GetExistingCommonAncestorRanges();
+ int32_t count = 0;
+ if (ranges) {
+ // Can't use range-based iteration on a const LinkedList, unfortunately.
+ for (const nsRange* r = ranges->getFirst(); r; r = r->getNext()) {
+ ++count;
+ }
+ }
+ fprintf(out, " ranges:%d", count);
}
fprintf(out, " primaryframe=%p", static_cast(GetPrimaryFrame()));
fprintf(out, " refcount=%" PRIuPTR "<", mRefCnt.get());
diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h
index 98df327d3704..93186fcbad5b 100644
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -36,8 +36,6 @@
#define CHILD_PROCESS_SHUTDOWN_MESSAGE NS_LITERAL_STRING("child-process-shutdown")
-#define NO_REMOTE_TYPE ""
-
// These must match the similar ones in E10SUtils.jsm.
// Process names as reported by about:memory are defined in
// ContentChild:RecvRemoteType. Add your value there too or it will be called
@@ -169,7 +167,7 @@ class ContentParent final : public PContentParent
* 3. normal iframe
*/
static already_AddRefed
- GetNewOrUsedBrowserProcess(const nsAString& aRemoteType = NS_LITERAL_STRING(NO_REMOTE_TYPE),
+ GetNewOrUsedBrowserProcess(const nsAString& aRemoteType,
hal::ProcessPriority aPriority =
hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND,
ContentParent* aOpener = nullptr,
diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp
index 3dc9e14b7cdf..49e39a9ef703 100644
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -112,6 +112,9 @@ static GetSaveFileNameWPtr sGetSaveFileNameWPtrStub = nullptr;
typedef BOOL (WINAPI *SetCursorPosPtr)(int x, int y);
static SetCursorPosPtr sSetCursorPosPtrStub = nullptr;
+typedef BOOL (WINAPI *PrintDlgWPtr)(LPPRINTDLGW aDlg);
+static PrintDlgWPtr sPrintDlgWPtrStub = nullptr;
+
#endif
/* static */
@@ -2187,6 +2190,19 @@ PMCGetOpenFileNameW(LPOPENFILENAMEW aLpofn)
return PMCGetFileNameW(OPEN_FUNC, aLpofn);
}
+//static
+BOOL WINAPI
+PMCPrintDlgW(LPPRINTDLGW aDlg)
+{
+ // Zero out the HWND supplied by the plugin. We are sacrificing window
+ // parentage for the ability to run in the NPAPI sandbox.
+ HWND hwnd = aDlg->hwndOwner;
+ aDlg->hwndOwner = 0;
+ BOOL ret = sPrintDlgWPtrStub(aDlg);
+ aDlg->hwndOwner = hwnd;
+ return ret;
+}
+
BOOL WINAPI PMCSetCursorPos(int x, int y);
class SetCursorPosTaskData : public PluginThreadTaskData
@@ -2265,6 +2281,12 @@ PluginModuleChild::AllocPPluginInstanceChild(const nsCString& aMimeType,
sComDlg32Intercept.AddHook("GetOpenFileNameW", reinterpret_cast(PMCGetOpenFileNameW),
(void**) &sGetOpenFileNameWPtrStub);
}
+
+ if ((mQuirks & QUIRK_FLASH_HOOK_PRINTDLGW) &&
+ !sPrintDlgWPtrStub) {
+ sComDlg32Intercept.AddHook("PrintDlgW", reinterpret_cast(PMCPrintDlgW),
+ (void**) &sPrintDlgWPtrStub);
+ }
#endif
return new PluginInstanceChild(&mFunctions, aMimeType, aNames,
diff --git a/dom/plugins/ipc/PluginQuirks.cpp b/dom/plugins/ipc/PluginQuirks.cpp
index 7b79665c92ca..600e6472dfd1 100644
--- a/dom/plugins/ipc/PluginQuirks.cpp
+++ b/dom/plugins/ipc/PluginQuirks.cpp
@@ -29,6 +29,7 @@ int GetQuirksFromMimeTypeAndFilename(const nsCString& aMimeType,
quirks |= QUIRK_WINLESS_HOOK_IME;
#if defined(_M_X64) || defined(__x86_64__)
quirks |= QUIRK_FLASH_HOOK_GETKEYSTATE;
+ quirks |= QUIRK_FLASH_HOOK_PRINTDLGW;
#endif
#endif
}
diff --git a/dom/plugins/ipc/PluginQuirks.h b/dom/plugins/ipc/PluginQuirks.h
index 45c1afeee00b..947da591e6a3 100644
--- a/dom/plugins/ipc/PluginQuirks.h
+++ b/dom/plugins/ipc/PluginQuirks.h
@@ -45,6 +45,8 @@ enum PluginQuirks {
QUIRK_WINLESS_HOOK_IME = 1 << 12,
// Win: Hook GetKeyState to get keyboard state on sandbox process
QUIRK_FLASH_HOOK_GETKEYSTATE = 1 << 13,
+ // Win: Hook PrintDlgW to show print settings dialog on sandbox process
+ QUIRK_FLASH_HOOK_PRINTDLGW = 1 << 14,
};
int GetQuirksFromMimeTypeAndFilename(const nsCString& aMimeType,
diff --git a/dom/push/PushNotifier.cpp b/dom/push/PushNotifier.cpp
index 4f6c8d01e66d..88f7d365defa 100644
--- a/dom/push/PushNotifier.cpp
+++ b/dom/push/PushNotifier.cpp
@@ -108,6 +108,13 @@ PushNotifier::Dispatch(PushDispatcher& aDispatcher)
// At least one content process is active, so e10s must be enabled.
// Broadcast a message to notify observers and service workers.
for (uint32_t i = 0; i < contentActors.Length(); ++i) {
+ // We need to filter based on process type, only "web" AKA the default
+ // remote type is acceptable.
+ if (!contentActors[i]->GetRemoteType().EqualsLiteral(
+ DEFAULT_REMOTE_TYPE)) {
+ continue;
+ }
+
// Ensure that the content actor has the permissions avaliable for the
// principal the push is being sent for before sending the push message
// down.
diff --git a/dom/storage/StorageDBThread.cpp b/dom/storage/StorageDBThread.cpp
index da1be9b9d7bc..dfd6ff3c0e4e 100644
--- a/dom/storage/StorageDBThread.cpp
+++ b/dom/storage/StorageDBThread.cpp
@@ -1091,8 +1091,10 @@ StorageDBThread::DBOperation::Perform(StorageDBThread* aThread)
break;
}
}
-
- mCache->LoadDone(NS_OK);
+ // The loop condition's call to ExecuteStep() may have terminated because
+ // !NS_SUCCEEDED(), we need an early return to cover that case. This also
+ // covers success cases as well, but that's inductively safe.
+ NS_ENSURE_SUCCESS(rv, rv);
break;
}
diff --git a/dom/storage/StorageIPC.cpp b/dom/storage/StorageIPC.cpp
index b526c5d242e2..04db8a2eeabe 100644
--- a/dom/storage/StorageIPC.cpp
+++ b/dom/storage/StorageIPC.cpp
@@ -669,6 +669,7 @@ class SyncLoadCacheHelper : public LocalStorageCacheBridge
virtual bool LoadItem(const nsAString& aKey, const nsString& aValue)
{
// Called on the aCache background thread
+ MOZ_ASSERT(!mLoaded);
if (mLoaded) {
return false;
}
@@ -683,8 +684,12 @@ class SyncLoadCacheHelper : public LocalStorageCacheBridge
{
// Called on the aCache background thread
MonitorAutoLock monitor(mMonitor);
+ MOZ_ASSERT(!mLoaded && mRv);
mLoaded = true;
- *mRv = aRv;
+ if (mRv) {
+ *mRv = aRv;
+ mRv = nullptr;
+ }
monitor.Notify();
}
diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp
index f5d70e82e390..859411b539ff 100644
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -553,11 +553,8 @@ GLContext::InitWithPrefixImpl(const char* prefix, bool trygl)
MOZ_ASSERT(majorVer < 10);
MOZ_ASSERT(minorVer < 10);
mVersion = majorVer*100 + minorVer*10;
- if (mVersion < 200) {
- // Mac OSX 10.6/10.7 machines with Intel GPUs claim only OpenGL 1.4 but
- // have all the GL2+ extensions that we need.
- mVersion = 200;
- }
+ if (mVersion < 200)
+ return false;
////
diff --git a/intl/strres/nsStringBundle.cpp b/intl/strres/nsStringBundle.cpp
index c00bc89b54c2..a0bf6b928106 100644
--- a/intl/strres/nsStringBundle.cpp
+++ b/intl/strres/nsStringBundle.cpp
@@ -25,6 +25,9 @@
#include "nsIErrorService.h"
#include "nsICategoryManager.h"
#include "nsContentUtils.h"
+#include "nsStringStream.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/URLPreloader.h"
// for async loading
#ifdef ASYNC_LOADING
@@ -93,21 +96,27 @@ nsStringBundle::LoadProperties()
return NS_ERROR_ABORT;
}
- nsCOMPtr channel;
- rv = NS_NewChannel(getter_AddRefs(channel),
- uri,
- nsContentUtils::GetSystemPrincipal(),
- nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
- nsIContentPolicy::TYPE_OTHER);
+ nsCOMPtr in;
- if (NS_FAILED(rv)) return rv;
+ auto result = URLPreloader::ReadURI(uri);
+ if (result.isOk()) {
+ MOZ_TRY(NS_NewCStringInputStream(getter_AddRefs(in), result.unwrap()));
+ } else {
+ nsCOMPtr channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
- // It's a string bundle. We expect a text/plain type, so set that as hint
- channel->SetContentType(NS_LITERAL_CSTRING("text/plain"));
+ if (NS_FAILED(rv)) return rv;
- nsCOMPtr in;
- rv = channel->Open2(getter_AddRefs(in));
- if (NS_FAILED(rv)) return rv;
+ // It's a string bundle. We expect a text/plain type, so set that as hint
+ channel->SetContentType(NS_LITERAL_CSTRING("text/plain"));
+
+ rv = channel->Open2(getter_AddRefs(in));
+ if (NS_FAILED(rv)) return rv;
+ }
NS_ASSERTION(NS_SUCCEEDED(rv) && in, "Error in OpenBlockingStream");
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && in, NS_ERROR_FAILURE);
diff --git a/ipc/mscom/Interceptor.cpp b/ipc/mscom/Interceptor.cpp
index 88d1d7efadaf..bf216fabba31 100644
--- a/ipc/mscom/Interceptor.cpp
+++ b/ipc/mscom/Interceptor.cpp
@@ -305,6 +305,7 @@ Interceptor::MapEntry*
Interceptor::Lookup(REFIID aIid)
{
mMutex.AssertCurrentThreadOwns();
+
for (uint32_t index = 0, len = mInterceptorMap.Length(); index < len; ++index) {
if (mInterceptorMap[index].mIID == aIid) {
return &mInterceptorMap[index];
@@ -367,15 +368,57 @@ Interceptor::CreateInterceptor(REFIID aIid, IUnknown* aOuter, IUnknown** aOutput
}
HRESULT
-Interceptor::GetInitialInterceptorForIID(detail::LiveSetAutoLock& aLock,
+Interceptor::PublishTarget(detail::LiveSetAutoLock& aLiveSetLock,
+ RefPtr aInterceptor,
+ REFIID aTargetIid,
+ STAUniquePtr aTarget)
+{
+ RefPtr weakRef;
+ HRESULT hr = GetWeakReference(getter_AddRefs(weakRef));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ // mTarget is a weak reference to aTarget. This is safe because we transfer
+ // ownership of aTarget into mInterceptorMap which remains live for the
+ // lifetime of this Interceptor.
+ mTarget = ToInterceptorTargetPtr(aTarget);
+ GetLiveSet().Put(mTarget.get(), weakRef.forget());
+
+ // Now we transfer aTarget's ownership into mInterceptorMap.
+ mInterceptorMap.AppendElement(MapEntry(aTargetIid,
+ aInterceptor,
+ aTarget.release()));
+
+ // Release the live set lock because subsequent operations may post work to
+ // the main thread, creating potential for deadlocks.
+ aLiveSetLock.Unlock();
+ return S_OK;
+}
+
+HRESULT
+Interceptor::GetInitialInterceptorForIID(detail::LiveSetAutoLock& aLiveSetLock,
REFIID aTargetIid,
STAUniquePtr aTarget,
void** aOutInterceptor)
{
MOZ_ASSERT(aOutInterceptor);
- MOZ_ASSERT(aTargetIid != IID_IUnknown && aTargetIid != IID_IMarshal);
+ MOZ_ASSERT(aTargetIid != IID_IMarshal);
MOZ_ASSERT(!IsProxy(aTarget.get()));
+ if (aTargetIid == IID_IUnknown) {
+ // We must lock ourselves so that nothing can race with us once we have been
+ // published to the live set.
+ AutoLock lock(*this);
+
+ HRESULT hr = PublishTarget(aLiveSetLock, nullptr, aTargetIid, Move(aTarget));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ return QueryInterface(aTargetIid, aOutInterceptor);
+ }
+
// Raise the refcount for stabilization purposes during aggregation
RefPtr kungFuDeathGrip(static_cast(
static_cast(this)));
@@ -399,27 +442,15 @@ Interceptor::GetInitialInterceptorForIID(detail::LiveSetAutoLock& aLock,
return hr;
}
- RefPtr weakRef;
- hr = GetWeakReference(getter_AddRefs(weakRef));
+ // We must lock ourselves so that nothing can race with us once we have been
+ // published to the live set.
+ AutoLock lock(*this);
+
+ hr = PublishTarget(aLiveSetLock, unkInterceptor, aTargetIid, Move(aTarget));
if (FAILED(hr)) {
return hr;
}
- // mTarget is a weak reference to aTarget. This is safe because we transfer
- // ownership of aTarget into mInterceptorMap which remains live for the
- // lifetime of this Interceptor.
- mTarget = ToInterceptorTargetPtr(aTarget);
- GetLiveSet().Put(mTarget.get(), weakRef.forget());
-
- // Release the live set lock because GetInterceptorForIID will post work to
- // the main thread, creating potential for deadlocks.
- aLock.Unlock();
-
- // Now we transfer aTarget's ownership into mInterceptorMap.
- mInterceptorMap.AppendElement(MapEntry(aTargetIid,
- unkInterceptor,
- aTarget.release()));
-
if (mEventSink->MarshalAs(aTargetIid) == aTargetIid) {
return unkInterceptor->QueryInterface(aTargetIid, aOutInterceptor);
}
diff --git a/ipc/mscom/Interceptor.h b/ipc/mscom/Interceptor.h
index 7e09528f73d1..7214a0e6592d 100644
--- a/ipc/mscom/Interceptor.h
+++ b/ipc/mscom/Interceptor.h
@@ -120,7 +120,7 @@ class Interceptor final : public WeakReferenceSupport
private:
explicit Interceptor(IInterceptorSink* aSink);
~Interceptor();
- HRESULT GetInitialInterceptorForIID(detail::LiveSetAutoLock& aLock,
+ HRESULT GetInitialInterceptorForIID(detail::LiveSetAutoLock& aLiveSetLock,
REFIID aTargetIid,
STAUniquePtr aTarget,
void** aOutInterface);
@@ -129,6 +129,10 @@ class Interceptor final : public WeakReferenceSupport
HRESULT ThreadSafeQueryInterface(REFIID aIid,
IUnknown** aOutInterface) override;
HRESULT CreateInterceptor(REFIID aIid, IUnknown* aOuter, IUnknown** aOutput);
+ HRESULT PublishTarget(detail::LiveSetAutoLock& aLiveSetLock,
+ RefPtr aInterceptor,
+ REFIID aTargetIid,
+ STAUniquePtr aTarget);
private:
InterceptorTargetPtr mTarget;
diff --git a/ipc/mscom/WeakRef.cpp b/ipc/mscom/WeakRef.cpp
index cf71d2faf1d2..ffdb89be4dfa 100644
--- a/ipc/mscom/WeakRef.cpp
+++ b/ipc/mscom/WeakRef.cpp
@@ -109,6 +109,18 @@ WeakReferenceSupport::~WeakReferenceSupport()
::DeleteCriticalSection(&mCSForQI);
}
+void
+WeakReferenceSupport::Lock()
+{
+ ::EnterCriticalSection(&mCSForQI);
+}
+
+void
+WeakReferenceSupport::Unlock()
+{
+ ::LeaveCriticalSection(&mCSForQI);
+}
+
HRESULT
WeakReferenceSupport::QueryInterface(REFIID riid, void** ppv)
{
diff --git a/ipc/mscom/WeakRef.h b/ipc/mscom/WeakRef.h
index 99c0886b9e82..12b24226948c 100644
--- a/ipc/mscom/WeakRef.h
+++ b/ipc/mscom/WeakRef.h
@@ -12,6 +12,7 @@
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
#include "mozilla/RefPtr.h"
#include "nsISupportsImpl.h"
@@ -101,6 +102,12 @@ class WeakReferenceSupport : public IWeakReferenceSource
virtual HRESULT ThreadSafeQueryInterface(REFIID aIid,
IUnknown** aOutInterface) = 0;
+ void Lock();
+ void Unlock();
+
+ typedef BaseAutoLock AutoLock;
+ friend class BaseAutoLock;
+
private:
RefPtr mSharedRef;
ULONG mRefCnt;
diff --git a/js/src/build/Makefile.in b/js/src/build/Makefile.in
index 2ab1b3298bf1..2f2e4c1eb149 100644
--- a/js/src/build/Makefile.in
+++ b/js/src/build/Makefile.in
@@ -81,6 +81,8 @@ install:: js-config
cp $^ js$(MOZJS_MAJOR_VERSION)-config
$(SYSINSTALL) js$(MOZJS_MAJOR_VERSION)-config $(DESTDIR)$(bindir)
+# Use install_name_tool to set the install_name properly for standalone
+# installed libraries on macOS
install:: $(REAL_LIBRARY) $(SHARED_LIBRARY) $(IMPORT_LIBRARY)
ifneq (,$(REAL_LIBRARY))
$(SYSINSTALL) $(REAL_LIBRARY) $(DESTDIR)$(libdir)
@@ -88,7 +90,12 @@ ifneq (,$(REAL_LIBRARY))
endif
ifneq (,$(SHARED_LIBRARY))
$(SYSINSTALL) $(SHARED_LIBRARY) $(DESTDIR)$(libdir)
+ifeq ($(OS_ARCH),Darwin)
+ install_name_tool -id $(abspath $(libdir)/$(SHARED_LIBRARY)) $(DESTDIR)$(libdir)/$(SHARED_LIBRARY)
+endif
endif
ifneq (,$(IMPORT_LIBRARY))
+ifneq ($(IMPORT_LIBRARY),$(SHARED_LIBRARY))
$(SYSINSTALL) $(IMPORT_LIBRARY) $(DESTDIR)$(libdir)
endif
+endif
diff --git a/js/xpconnect/idl/xpccomponents.idl b/js/xpconnect/idl/xpccomponents.idl
index 19199b18ec8f..4cace8db96a0 100644
--- a/js/xpconnect/idl/xpccomponents.idl
+++ b/js/xpconnect/idl/xpccomponents.idl
@@ -14,6 +14,7 @@ interface nsIAddonInterposition;
interface nsIClassInfo;
interface nsIComponentManager;
interface nsICycleCollectorListener;
+interface nsIFile;
interface nsIJSCID;
interface nsIJSIID;
interface nsIPrincipal;
@@ -693,6 +694,13 @@ interface nsIXPCComponents_Utils : nsISupports
* startup, measured with a monotonic clock.
*/
double now();
+
+ /*
+ * Reads the given file and returns its contents. If called during early
+ * startup, the file will be pre-read on a background thread during profile
+ * startup so its contents will be available the next time they're read.
+ */
+ ACString readFile(in nsIFile file);
};
/**
diff --git a/js/xpconnect/loader/ScriptPreloader-inl.h b/js/xpconnect/loader/ScriptPreloader-inl.h
index 193e5e8813f6..02bf481f5b91 100644
--- a/js/xpconnect/loader/ScriptPreloader-inl.h
+++ b/js/xpconnect/loader/ScriptPreloader-inl.h
@@ -25,6 +25,15 @@ namespace loader {
using mozilla::dom::AutoJSAPI;
+static inline Result
+Write(PRFileDesc* fd, const void* data, int32_t len)
+{
+ if (PR_Write(fd, data, len) != len) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ return Ok();
+}
+
struct MOZ_RAII AutoSafeJSAPI : public AutoJSAPI
{
AutoSafeJSAPI() { Init(); }
@@ -259,8 +268,15 @@ class HashElemIter
return iter().Data();
}
+ const ElemType get() const
+ {
+ return const_cast(this)->get();
+ }
+
ElemType operator->() { return get(); }
+ const ElemType operator->() const { return get(); }
+
operator ElemType() { return get(); }
void Remove() { iter().Remove(); }
diff --git a/js/xpconnect/loader/ScriptPreloader.cpp b/js/xpconnect/loader/ScriptPreloader.cpp
index f1c537950ba8..b22ed3161ce6 100644
--- a/js/xpconnect/loader/ScriptPreloader.cpp
+++ b/js/xpconnect/loader/ScriptPreloader.cpp
@@ -8,6 +8,8 @@
#include "mozilla/ScriptPreloader.h"
#include "mozilla/loader/ScriptCacheActors.h"
+#include "mozilla/URLPreloader.h"
+
#include "mozilla/ArrayUtils.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/FileUtils.h"
@@ -413,6 +415,10 @@ ScriptPreloader::InitCache(const nsAString& basePath)
return Ok();
}
+ // Note: Code on the main thread *must not access Omnijar in any way* until
+ // this AutoBeginReading guard is destroyed.
+ URLPreloader::AutoBeginReading abr;
+
MOZ_TRY(OpenCache());
return InitCacheInternal();
@@ -517,15 +523,6 @@ ScriptPreloader::InitCacheInternal()
return Ok();
}
-static inline Result
-Write(PRFileDesc* fd, const void* data, int32_t len)
-{
- if (PR_Write(fd, data, len) != len) {
- return Err(NS_ERROR_FAILURE);
- }
- return Ok();
-}
-
void
ScriptPreloader::PrepareCacheWriteInternal()
{
@@ -603,6 +600,8 @@ ScriptPreloader::WriteCache()
{
MOZ_ASSERT(!NS_IsMainThread());
+ Unused << URLPreloader::GetSingleton().WriteCache();
+
if (!mDataPrepared && !mSaveComplete) {
MOZ_ASSERT(!mBlockedOnSyncDispatch);
mBlockedOnSyncDispatch = true;
diff --git a/js/xpconnect/loader/URLPreloader.cpp b/js/xpconnect/loader/URLPreloader.cpp
new file mode 100644
index 000000000000..10dba23b1b6b
--- /dev/null
+++ b/js/xpconnect/loader/URLPreloader.cpp
@@ -0,0 +1,691 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScriptPreloader-inl.h"
+#include "mozilla/URLPreloader.h"
+#include "mozilla/loader/AutoMemMap.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Vector.h"
+
+#include "MainThreadUtils.h"
+#include "nsPrintfCString.h"
+#include "nsDebug.h"
+#include "nsIFile.h"
+#include "nsIFileURL.h"
+#include "nsIObserverService.h"
+#include "nsNetUtil.h"
+#include "nsPromiseFlatString.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "nsZipArchive.h"
+#include "xpcpublic.h"
+
+#include "mozilla/dom/ContentChild.h"
+
+#undef DELAYED_STARTUP_TOPIC
+#define DELAYED_STARTUP_TOPIC "sessionstore-windows-restored"
+
+namespace mozilla {
+namespace {
+static LazyLogModule gURLLog("URLPreloader");
+
+#define LOG(level, ...) MOZ_LOG(gURLLog, LogLevel::level, (__VA_ARGS__))
+
+template
+bool
+StartsWith(const T& haystack, const T& needle)
+{
+ return StringHead(haystack, needle.Length()) == needle;
+}
+} // anonymous namespace
+
+using namespace mozilla::loader;
+
+nsresult
+URLPreloader::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize)
+{
+ MOZ_COLLECT_REPORT(
+ "explicit/url-preloader/other", KIND_HEAP, UNITS_BYTES,
+ ShallowSizeOfIncludingThis(MallocSizeOf),
+ "Memory used by the URL preloader service itself.");
+
+ for (const auto& elem : IterHash(mCachedURLs)) {
+ nsAutoCString pathName;
+ pathName.Append(elem->mPath);
+ // The backslashes will automatically be replaced with slashes in
+ // about:memory, without splitting each path component into a separate
+ // branch in the memory report tree.
+ pathName.ReplaceChar('/', '\\');
+
+ nsPrintfCString path("explicit/url-preloader/cached-urls/%s/[%s]",
+ elem->TypeString(), pathName.get());
+
+ aHandleReport->Callback(
+ EmptyCString(), path, KIND_HEAP, UNITS_BYTES,
+ elem->SizeOfIncludingThis(MallocSizeOf),
+ NS_LITERAL_CSTRING("Memory used to hold cache data for files which "
+ "have been read or pre-loaded during this session."),
+ aData);
+ }
+
+ return NS_OK;
+}
+
+
+URLPreloader&
+URLPreloader::GetSingleton()
+{
+ static RefPtr singleton;
+
+ if (!singleton) {
+ singleton = new URLPreloader();
+ ClearOnShutdown(&singleton);
+ }
+
+ return *singleton;
+}
+
+
+bool URLPreloader::sInitialized = false;
+
+URLPreloader::URLPreloader()
+{
+ if (InitInternal().isOk()) {
+ sInitialized = true;
+ RegisterWeakMemoryReporter(this);
+ }
+}
+
+URLPreloader::~URLPreloader()
+{
+ if (sInitialized) {
+ UnregisterWeakMemoryReporter(this);
+ }
+}
+
+Result
+URLPreloader::InitInternal()
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ if (Omnijar::HasOmnijar(Omnijar::GRE)) {
+ MOZ_TRY(Omnijar::GetURIString(Omnijar::GRE, mGREPrefix));
+ }
+ if (Omnijar::HasOmnijar(Omnijar::APP)) {
+ MOZ_TRY(Omnijar::GetURIString(Omnijar::APP, mAppPrefix));
+ }
+
+ nsresult rv;
+ nsCOMPtr ios = do_GetIOService(&rv);
+ MOZ_TRY(rv);
+
+ nsCOMPtr ph;
+ MOZ_TRY(ios->GetProtocolHandler("resource", getter_AddRefs(ph)));
+
+ mResProto = do_QueryInterface(ph, &rv);
+ MOZ_TRY(rv);
+
+ mChromeReg = services::GetChromeRegistryService();
+ if (!mChromeReg) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr obs = services::GetObserverService();
+
+ obs->AddObserver(this, DELAYED_STARTUP_TOPIC, false);
+
+ MOZ_TRY(NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(mProfD)));
+ } else {
+ mStartupFinished = true;
+ mReaderInitialized = true;
+ }
+
+ return Ok();
+}
+
+Result, nsresult>
+URLPreloader::GetCacheFile(const nsAString& suffix)
+{
+ if (!mProfD) {
+ return Err(NS_ERROR_NOT_INITIALIZED);
+ }
+
+ nsCOMPtr cacheFile;
+ MOZ_TRY(mProfD->Clone(getter_AddRefs(cacheFile)));
+
+ MOZ_TRY(cacheFile->AppendNative(NS_LITERAL_CSTRING("startupCache")));
+ Unused << cacheFile->Create(nsIFile::DIRECTORY_TYPE, 0777);
+
+ MOZ_TRY(cacheFile->Append(NS_LITERAL_STRING("urlCache") + suffix));
+
+ return Move(cacheFile);
+}
+
+static const uint8_t URL_MAGIC[] = "mozURLcachev001";
+
+Result, nsresult>
+URLPreloader::FindCacheFile()
+{
+ nsCOMPtr cacheFile;
+ MOZ_TRY_VAR(cacheFile, GetCacheFile(NS_LITERAL_STRING(".bin")));
+
+ bool exists;
+ MOZ_TRY(cacheFile->Exists(&exists));
+ if (exists) {
+ MOZ_TRY(cacheFile->MoveTo(nullptr, NS_LITERAL_STRING("urlCache-current.bin")));
+ } else {
+ MOZ_TRY(cacheFile->SetLeafName(NS_LITERAL_STRING("urlCache-current.bin")));
+ MOZ_TRY(cacheFile->Exists(&exists));
+ if (!exists) {
+ return Err(NS_ERROR_FILE_NOT_FOUND);
+ }
+ }
+
+ return Move(cacheFile);
+}
+
+Result
+URLPreloader::WriteCache()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr cacheFile;
+ MOZ_TRY_VAR(cacheFile, GetCacheFile(NS_LITERAL_STRING("-new.bin")));
+
+ bool exists;
+ MOZ_TRY(cacheFile->Exists(&exists));
+ if (exists) {
+ MOZ_TRY(cacheFile->Remove(false));
+ }
+
+ {
+ AutoFDClose fd;
+ MOZ_TRY(cacheFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, 0644, &fd.rwget()));
+
+ nsTArray entries;
+ for (auto& entry : IterHash(mCachedURLs)) {
+ if (entry->mReadTime) {
+ entries.AppendElement(entry);
+ }
+ }
+
+ entries.Sort(URLEntry::Comparator());
+
+ OutputBuffer buf;
+ for (auto entry : entries) {
+ entry->Code(buf);
+ }
+
+ uint8_t headerSize[4];
+ LittleEndian::writeUint32(headerSize, buf.cursor());
+
+ MOZ_TRY(Write(fd, URL_MAGIC, sizeof(URL_MAGIC)));
+ MOZ_TRY(Write(fd, headerSize, sizeof(headerSize)));
+ MOZ_TRY(Write(fd, buf.Get(), buf.cursor()));
+ }
+
+ MOZ_TRY(cacheFile->MoveTo(nullptr, NS_LITERAL_STRING("urlCache.bin")));
+
+ NS_DispatchToMainThread(
+ NewRunnableMethod("URLPreloader::Cleanup",
+ this,
+ &URLPreloader::Cleanup));
+
+ return Ok();
+}
+
+void
+URLPreloader::Cleanup()
+{
+ mCachedURLs.Clear();
+}
+
+Result
+URLPreloader::ReadCache(LinkedList& pendingURLs)
+{
+ nsCOMPtr cacheFile;
+ MOZ_TRY_VAR(cacheFile, FindCacheFile());
+
+ AutoMemMap cache;
+ MOZ_TRY(cache.init(cacheFile));
+
+ auto size = cache.size();
+
+ uint32_t headerSize;
+ if (size < sizeof(URL_MAGIC) + sizeof(headerSize)) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ auto data = cache.get();
+ auto end = data + size;
+
+ if (memcmp(URL_MAGIC, data.get(), sizeof(URL_MAGIC))) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+ data += sizeof(URL_MAGIC);
+
+ headerSize = LittleEndian::readUint32(data.get());
+ data += sizeof(headerSize);
+
+ if (data + headerSize > end) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ {
+ mMonitor.AssertCurrentThreadOwns();
+
+ auto cleanup = MakeScopeExit([&] () {
+ while (auto* elem = pendingURLs.getFirst()) {
+ elem->remove();
+ }
+ mCachedURLs.Clear();
+ });
+
+ Range header(data, data + headerSize);
+ data += headerSize;
+
+ InputBuffer buf(header);
+ while (!buf.finished()) {
+ CacheKey key(buf);
+
+ auto entry = mCachedURLs.LookupOrAdd(key, key);
+ entry->mResultCode = NS_ERROR_NOT_INITIALIZED;
+
+ pendingURLs.insertBack(entry);
+ }
+
+ if (buf.error()) {
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+
+ cleanup.release();
+ }
+
+ return Ok();
+}
+
+void
+URLPreloader::BackgroundReadFiles()
+{
+ Vector cursors;
+ LinkedList pendingURLs;
+
+ {
+ MonitorAutoLock mal(mMonitor);
+
+ if (ReadCache(pendingURLs).isErr()) {
+ mReaderInitialized = true;
+ mal.NotifyAll();
+ return;
+ }
+
+ int numZipEntries = 0;
+ for (auto entry : pendingURLs) {
+ if (entry->mType != entry->TypeFile) {
+ numZipEntries++;
+ }
+ }
+ MOZ_RELEASE_ASSERT(cursors.reserve(numZipEntries));
+
+ // Initialize the zip cursors for all files in Omnijar while the monitor
+ // is locked. Omnijar is not threadsafe, so the caller of
+ // AutoBeginReading guard must ensure that no code accesses Omnijar
+ // until this segment is done. Once the cursors have been initialized,
+ // the actual reading and decompression can safely be done off-thread,
+ // as is the case for thread-retargeted jar: channels.
+ for (auto entry : pendingURLs) {
+ if (entry->mType == entry->TypeFile) {
+ continue;
+ }
+
+ RefPtr zip = entry->Archive();
+
+ auto item = zip->GetItem(entry->mPath.get());
+ if (!item) {
+ entry->mResultCode = NS_ERROR_FILE_NOT_FOUND;
+ continue;
+ }
+
+ size_t size = item->RealSize();
+
+ entry->mData.SetLength(size);
+ auto data = entry->mData.BeginWriting();
+
+ cursors.infallibleEmplaceBack(item, zip, reinterpret_cast(data),
+ size, true);
+ }
+
+ mReaderInitialized = true;
+ mal.NotifyAll();
+ }
+
+ // Loop over the entries, read the file's contents, store them in the
+ // entry's mData pointer, and notify any waiting threads to check for
+ // completion.
+ uint32_t i = 0;
+ for (auto entry : pendingURLs) {
+ // If there is any other error code, the entry has already failed at
+ // this point, so don't bother trying to read it again.
+ if (entry->mResultCode != NS_ERROR_NOT_INITIALIZED) {
+ continue;
+ }
+
+ nsresult rv = NS_OK;
+
+ if (entry->mType == entry->TypeFile) {
+ auto result = entry->Read();
+ if (result.isErr()) {
+ rv = result.unwrapErr();
+ }
+ } else {
+ auto& cursor = cursors[i++];
+
+ uint32_t len;
+ cursor.Copy(&len);
+ if (len != entry->mData.Length()) {
+ entry->mData.Truncate();
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+
+ entry->mResultCode = rv;
+ mMonitor.NotifyAll();
+ }
+
+ // We're done reading pending entries, so clear the list.
+ pendingURLs.clear();
+
+ NS_DispatchToMainThread(
+ NewRunnableMethod("nsIThread::Shutdown",
+ mReaderThread, &nsIThread::Shutdown));
+ mReaderThread = nullptr;
+}
+
+void
+URLPreloader::BeginBackgroundRead()
+{
+ if (!mReaderThread && !mReaderInitialized && sInitialized) {
+ nsCOMPtr runnable =
+ NewRunnableMethod("URLPreloader::BackgroundReadFiles",
+ this,
+ &URLPreloader::BackgroundReadFiles);
+
+ Unused << NS_NewNamedThread(
+ "BGReadURLs", getter_AddRefs(mReaderThread), runnable);
+ }
+}
+
+
+Result
+URLPreloader::ReadInternal(const CacheKey& key, ReadType readType)
+{
+ if (mStartupFinished) {
+ URLEntry entry(key);
+
+ return entry.Read();
+ }
+
+ auto entry = mCachedURLs.LookupOrAdd(key, key);
+
+ entry->UpdateUsedTime();
+
+ return entry->ReadOrWait(readType);
+}
+
+Result
+URLPreloader::ReadURIInternal(nsIURI* uri, ReadType readType)
+{
+ CacheKey key;
+ MOZ_TRY_VAR(key, ResolveURI(uri));
+
+ return ReadInternal(key, readType);
+}
+
+/* static */ Result
+URLPreloader::Read(const CacheKey& key, ReadType readType)
+{
+ // If we're being called before the preloader has been initialized (i.e.,
+ // before the profile has been initialized), just fall back to a synchronous
+ // read. This happens when we're reading .ini and preference files that are
+ // needed to locate and initialize the profile.
+ if (!sInitialized) {
+ return URLEntry(key).Read();
+ }
+
+ return GetSingleton().ReadInternal(key, readType);
+}
+
+/* static */ Result
+URLPreloader::ReadURI(nsIURI* uri, ReadType readType)
+{
+ if (!sInitialized) {
+ return Err(NS_ERROR_NOT_INITIALIZED);
+ }
+
+ return GetSingleton().ReadURIInternal(uri, readType);
+}
+
+/* static */ Result
+URLPreloader::ReadFile(nsIFile* file, ReadType readType)
+{
+ return Read(CacheKey(file), readType);
+}
+
+/* static */ Result
+URLPreloader::ReadFile(const nsACString& path, ReadType readType)
+{
+ CacheKey key(CacheKey::TypeFile, path);
+ return Read(key, readType);
+}
+
+/* static */ Result
+URLPreloader::Read(FileLocation& location, ReadType readType)
+{
+ if (location.IsZip()) {
+ if (location.GetBaseZip()) {
+ nsCString path;
+ location.GetPath(path);
+ return ReadZip(location.GetBaseZip(), path);
+ }
+ return URLEntry::ReadLocation(location);
+ }
+
+ nsCOMPtr file = location.GetBaseFile();
+ return ReadFile(file, readType);
+}
+
+/* static */ Result
+URLPreloader::ReadZip(nsZipArchive* zip, const nsACString& path, ReadType readType)
+{
+ // If the zip archive belongs to an Omnijar location, map it to a cache
+ // entry, and cache it as normal. Otherwise, simply read the entry
+ // synchronously, since other JAR archives are currently unsupported by the
+ // cache.
+ RefPtr reader = Omnijar::GetReader(Omnijar::GRE);
+ if (zip == reader) {
+ CacheKey key(CacheKey::TypeGREJar, path);
+ return Read(key, readType);
+ }
+
+ reader = Omnijar::GetReader(Omnijar::APP);
+ if (zip == reader) {
+ CacheKey key(CacheKey::TypeAppJar, path);
+ return Read(key, readType);
+ }
+
+ // Not an Omnijar archive, so just read it directly.
+ FileLocation location(zip, PromiseFlatCString(path).BeginReading());
+ return URLEntry::ReadLocation(location);
+}
+
+Result
+URLPreloader::ResolveURI(nsIURI* uri)
+{
+ nsCString spec;
+ nsCString scheme;
+ MOZ_TRY(uri->GetSpec(spec));
+ MOZ_TRY(uri->GetScheme(scheme));
+
+ nsCOMPtr resolved;
+
+ // If the URI is a resource: or chrome: URI, first resolve it to the
+ // underlying URI that it wraps.
+ if (scheme.EqualsLiteral("resource")) {
+ MOZ_TRY(mResProto->ResolveURI(uri, spec));
+ MOZ_TRY(NS_NewURI(getter_AddRefs(resolved), spec));
+ } else if (scheme.EqualsLiteral("chrome")) {
+ MOZ_TRY(mChromeReg->ConvertChromeURL(uri, getter_AddRefs(resolved)));
+ MOZ_TRY(resolved->GetSpec(spec));
+ } else {
+ resolved = uri;
+ }
+ MOZ_TRY(resolved->GetScheme(scheme));
+
+ // Try the GRE and App Omnijar prefixes.
+ if (mGREPrefix.Length() && StartsWith(spec, mGREPrefix)) {
+ return CacheKey(CacheKey::TypeGREJar,
+ Substring(spec, mGREPrefix.Length()));
+ }
+
+ if (mAppPrefix.Length() && StartsWith(spec, mAppPrefix)) {
+ return CacheKey(CacheKey::TypeAppJar,
+ Substring(spec, mAppPrefix.Length()));
+ }
+
+ // Try for a file URI.
+ if (scheme.EqualsLiteral("file")) {
+ nsCOMPtr fileURL = do_QueryInterface(resolved);
+ MOZ_ASSERT(fileURL);
+
+ nsCOMPtr file;
+ MOZ_TRY(fileURL->GetFile(getter_AddRefs(file)));
+
+ nsCString path;
+ MOZ_TRY(file->GetNativePath(path));
+
+ return CacheKey(CacheKey::TypeFile, path);
+ }
+
+ // Not a file or Omnijar URI, so currently unsupported.
+ return Err(NS_ERROR_INVALID_ARG);
+}
+
+size_t
+URLPreloader::ShallowSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
+{
+ return (mallocSizeOf(this) +
+ mAppPrefix.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
+ mGREPrefix.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
+ mCachedURLs.ShallowSizeOfExcludingThis(mallocSizeOf));
+}
+
+Result
+URLPreloader::CacheKey::ToFileLocation()
+{
+ if (mType == TypeFile) {
+ nsCOMPtr file;
+ MOZ_TRY(NS_NewNativeLocalFile(mPath, false, getter_AddRefs(file)));
+ return Move(FileLocation(file));
+ }
+
+ RefPtr zip = Archive();
+ return Move(FileLocation(zip, mPath.get()));
+}
+
+Result
+URLPreloader::URLEntry::Read()
+{
+ FileLocation location;
+ MOZ_TRY_VAR(location, ToFileLocation());
+
+ MOZ_TRY_VAR(mData, ReadLocation(location));
+ return mData;
+}
+
+/* static */ Result
+URLPreloader::URLEntry::ReadLocation(FileLocation& location)
+{
+ FileLocation::Data data;
+ MOZ_TRY(location.GetData(data));
+
+ uint32_t size;
+ MOZ_TRY(data.GetSize(&size));
+
+ nsCString result;
+ result.SetLength(size);
+ MOZ_TRY(data.Copy(result.BeginWriting(), size));
+
+ return Move(result);
+}
+
+Result
+URLPreloader::URLEntry::ReadOrWait(ReadType readType)
+{
+ auto now = TimeStamp::Now();
+ LOG(Info, "Reading %s\n", mPath.get());
+ auto cleanup = MakeScopeExit([&] () {
+ LOG(Info, "Read in %fms\n", (TimeStamp::Now() - now).ToMilliseconds());
+ });
+
+ if (mResultCode == NS_ERROR_NOT_INITIALIZED) {
+ MonitorAutoLock mal(GetSingleton().mMonitor);
+
+ while (mResultCode == NS_ERROR_NOT_INITIALIZED) {
+ mal.Wait();
+ }
+ }
+
+ if (mResultCode == NS_OK && mData.IsVoid()) {
+ LOG(Info, "Reading synchronously...\n");
+ return Read();
+ }
+
+ if (NS_FAILED(mResultCode)) {
+ return Err(mResultCode);
+ }
+
+ nsCString res = mData;
+
+ if (readType == Forget) {
+ mData.SetIsVoid(true);
+ }
+ return res;
+}
+
+inline
+URLPreloader::CacheKey::CacheKey(InputBuffer& buffer)
+{
+ Code(buffer);
+}
+
+nsresult
+URLPreloader::Observe(nsISupports* subject, const char* topic, const char16_t* data)
+{
+ nsCOMPtr obs = services::GetObserverService();
+ if (!strcmp(topic, DELAYED_STARTUP_TOPIC)) {
+ obs->RemoveObserver(this, DELAYED_STARTUP_TOPIC);
+ mStartupFinished = true;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMPL_ISUPPORTS(URLPreloader, nsIObserver, nsIMemoryReporter)
+
+#undef LOG
+
+} // namespace mozilla
diff --git a/js/xpconnect/loader/URLPreloader.h b/js/xpconnect/loader/URLPreloader.h
new file mode 100644
index 000000000000..0dfef7284739
--- /dev/null
+++ b/js/xpconnect/loader/URLPreloader.h
@@ -0,0 +1,325 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef URLPreloader_h
+#define URLPreloader_h
+
+#include "mozilla/FileLocation.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Omnijar.h"
+#include "mozilla/Range.h"
+#include "mozilla/Vector.h"
+#include "mozilla/Result.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsIChromeRegistry.h"
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "nsIResProtocolHandler.h"
+#include "nsIThread.h"
+#include "nsReadableUtils.h"
+
+class nsZipArchive;
+
+namespace mozilla {
+namespace loader {
+ class InputBuffer;
+}
+
+using namespace mozilla::loader;
+
+class ScriptPreloader;
+
+/**
+ * A singleton class to manage loading local URLs during startup, recording
+ * them, and pre-loading them during early startup in the next session. URLs
+ * that are not already loaded (or already being pre-loaded) when required are
+ * read synchronously from disk, and (if startup is not already complete)
+ * added to the pre-load list for the next session.
+ */
+class URLPreloader final : public nsIObserver
+ , public nsIMemoryReporter
+{
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ URLPreloader();
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIMEMORYREPORTER
+
+ static URLPreloader& GetSingleton();
+
+ // The type of read operation to perform.
+ enum ReadType
+ {
+ // Read the file and then immediately forget its data.
+ Forget,
+ // Read the file and retain its data for the next caller.
+ Retain,
+ };
+
+ // Helpers to read the contents of files or JAR archive entries with various
+ // representations. If the preloader has not yet been initialized, or the
+ // given location is not supported by the cache, the entries will be read
+ // synchronously, and not stored in the cache.
+ static Result Read(FileLocation& location, ReadType readType = Forget);
+
+ static Result ReadURI(nsIURI* uri, ReadType readType = Forget);
+
+ static Result ReadFile(nsIFile* file, ReadType readType = Forget);
+
+ static Result ReadFile(const nsACString& path, ReadType readType = Forget);
+
+ static Result ReadZip(nsZipArchive* archive,
+ const nsACString& path,
+ ReadType readType = Forget);
+
+private:
+ struct CacheKey;
+
+ Result ReadInternal(const CacheKey& key, ReadType readType);
+
+ Result ReadURIInternal(nsIURI* uri, ReadType readType);
+
+ Result ReadFileInternal(nsIFile* file, ReadType readType);
+
+ static Result Read(const CacheKey& key, ReadType readType);
+
+ static bool sInitialized;
+
+protected:
+ friend class ScriptPreloader;
+
+ virtual ~URLPreloader();
+
+ Result WriteCache();
+
+ // Clear leftover entries after the cache has been written.
+ void Cleanup();
+
+ // Begins reading files off-thread, and ensures that initialization has
+ // completed before leaving the current scope. The caller *must* ensure that
+ // no code on the main thread access Omnijar, either directly or indirectly,
+ // for the lifetime of this guard object.
+ struct MOZ_RAII AutoBeginReading final
+ {
+ AutoBeginReading()
+ {
+ GetSingleton().BeginBackgroundRead();
+ }
+
+ ~AutoBeginReading()
+ {
+ auto& reader = GetSingleton();
+
+ MonitorAutoLock mal(reader.mMonitor);
+
+ while (!reader.mReaderInitialized && reader.sInitialized) {
+ mal.Wait();
+ }
+ }
+ };
+
+private:
+ // Represents a key for an entry in the URI cache, based on its file or JAR
+ // location.
+ struct CacheKey
+ {
+ // The type of the entry. TypeAppJar and TypeGREJar entries are in the
+ // app-specific or toolkit Omnijar files, and are handled specially.
+ // TypeFile entries are plain files in the filesystem.
+ enum EntryType : uint8_t
+ {
+ TypeAppJar,
+ TypeGREJar,
+ TypeFile,
+ };
+
+ CacheKey() = default;
+ CacheKey(const CacheKey& other) = default;
+
+ CacheKey(EntryType type, const nsACString& path)
+ : mType(type), mPath(path)
+ {}
+
+ explicit CacheKey(nsIFile* file)
+ : mType(TypeFile)
+ {
+ MOZ_ALWAYS_SUCCEEDS(file->GetNativePath(mPath));
+ }
+
+ explicit inline CacheKey(InputBuffer& buffer);
+
+ // Encodes or decodes the cache key for storage in a session cache file.
+ template
+ void Code(Buffer& buffer)
+ {
+ buffer.codeUint8(*reinterpret_cast(&mType));
+ buffer.codeString(mPath);
+ }
+
+ uint32_t Hash() const
+ {
+ return HashGeneric(mType, HashString(mPath));
+ }
+
+ bool operator==(const CacheKey& other) const
+ {
+ return mType == other.mType && mPath == other.mPath;
+ }
+
+ // Returns the Omnijar type for this entry. This may *only* be called
+ // for Omnijar entries.
+ Omnijar::Type OmnijarType()
+ {
+ switch (mType) {
+ case TypeAppJar:
+ return Omnijar::APP;
+ case TypeGREJar:
+ return Omnijar::GRE;
+ default:
+ MOZ_CRASH("Unexpected entry type");
+ return Omnijar::GRE;
+ }
+ }
+
+ const char* TypeString()
+ {
+ switch (mType) {
+ case TypeAppJar: return "AppJar";
+ case TypeGREJar: return "GREJar";
+ case TypeFile: return "File";
+ }
+ MOZ_ASSERT_UNREACHABLE("no such type");
+ return "";
+ }
+
+ already_AddRefed Archive()
+ {
+ return Omnijar::GetReader(OmnijarType());
+ }
+
+ Result ToFileLocation();
+
+ EntryType mType = TypeFile;
+
+ // The path of the entry. For Type*Jar entries, this is the path within
+ // the Omnijar archive. For TypeFile entries, this is the full path to
+ // the file.
+ nsCString mPath{};
+ };
+
+ // Represents an entry in the URI cache.
+ struct URLEntry final : public CacheKey
+ , public LinkedListElement
+ {
+ MOZ_IMPLICIT URLEntry(const CacheKey& key)
+ : CacheKey(key)
+ , mData(NullCString())
+ {}
+
+ explicit URLEntry(nsIFile* file)
+ : CacheKey(file)
+ {}
+
+ // For use with nsTArray::Sort.
+ //
+ // Sorts entries by the time they were initially read during this
+ // session.
+ struct Comparator final
+ {
+ bool Equals(const URLEntry* a, const URLEntry* b) const
+ {
+ return a->mReadTime == b->mReadTime;
+ }
+
+ bool LessThan(const URLEntry* a, const URLEntry* b) const
+ {
+ return a->mReadTime < b->mReadTime;
+ }
+ };
+
+ // Sets the first-used time of this file to the earlier of its current
+ // first-use time or the given timestamp.
+ void UpdateUsedTime(const TimeStamp& time = TimeStamp::Now())
+ {
+ if (!mReadTime || time < mReadTime) {
+ mReadTime = time;
+ }
+ }
+
+ Result Read();
+ static Result ReadLocation(FileLocation& location);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
+ {
+ return (mallocSizeOf(this) +
+ mPath.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
+ mData.SizeOfExcludingThisEvenIfShared(mallocSizeOf));
+ }
+
+ // Reads the contents of the file referenced by this entry, or wait for
+ // an off-thread read operation to finish if it is currently pending,
+ // and return the file's contents.
+ Result ReadOrWait(ReadType readType);
+
+ nsCString mData;
+
+ TimeStamp mReadTime{};
+
+ nsresult mResultCode = NS_OK;
+ };
+
+ // Resolves the given URI to a CacheKey, if the URI is cacheable.
+ Result ResolveURI(nsIURI* uri);
+
+ Result InitInternal();
+
+ // Returns a file pointer to the (possibly nonexistent) cache file with the
+ // given suffix.
+ Result, nsresult> GetCacheFile(const nsAString& suffix);
+ // Finds the correct cache file to use for this session.
+ Result, nsresult> FindCacheFile();
+
+ Result ReadCache(LinkedList& pendingURLs);
+
+ void BackgroundReadFiles();
+ void BeginBackgroundRead();
+
+ using HashType = nsClassHashtable, URLEntry>;
+
+ size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
+
+ bool mStartupFinished = false;
+ bool mReaderInitialized = false;
+
+ // The prefix URLs for files in the GRE and App omni jar archives.
+ nsCString mGREPrefix;
+ nsCString mAppPrefix;
+
+ nsCOMPtr mResProto;
+ nsCOMPtr mChromeReg;
+ nsCOMPtr mProfD;
+
+ nsCOMPtr mReaderThread;
+
+ // A map of URL entries which have were either read this session, or read
+ // from the last session's cache file.
+ HashType mCachedURLs;
+
+ Monitor mMonitor{"[URLPreloader::mMutex]"};
+};
+
+} // namespace mozilla
+
+#endif // URLPreloader_h
diff --git a/js/xpconnect/loader/moz.build b/js/xpconnect/loader/moz.build
index f91242b08b03..6e718c8430eb 100644
--- a/js/xpconnect/loader/moz.build
+++ b/js/xpconnect/loader/moz.build
@@ -11,6 +11,7 @@ UNIFIED_SOURCES += [
'mozJSSubScriptLoader.cpp',
'ScriptCacheActors.cpp',
'ScriptPreloader.cpp',
+ 'URLPreloader.cpp',
]
# mozJSComponentLoader.cpp cannot be built in unified mode because it uses
@@ -25,6 +26,7 @@ IPDL_SOURCES += [
EXPORTS.mozilla += [
'ScriptPreloader.h',
+ 'URLPreloader.h',
]
EXPORTS.mozilla.dom += [
diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp
index e524ca093115..09b4973c83d2 100644
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -21,6 +21,8 @@
#include "mozilla/Preferences.h"
#include "nsJSEnvironment.h"
#include "mozilla/TimeStamp.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/URLPreloader.h"
#include "mozilla/XPTInterfaceInfoManager.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/DOMExceptionBinding.h"
@@ -3379,6 +3381,15 @@ nsXPCComponents_Utils::AllowCPOWsInAddon(const nsACString& addonIdStr,
return NS_OK;
}
+NS_IMETHODIMP
+nsXPCComponents_Utils::ReadFile(nsIFile* aFile, nsACString& aResult)
+{
+ NS_ENSURE_TRUE(aFile, NS_ERROR_INVALID_ARG);
+
+ MOZ_TRY_VAR(aResult, URLPreloader::ReadFile(aFile));
+ return NS_OK;
+}
+
NS_IMETHODIMP
nsXPCComponents_Utils::Now(double* aRetval)
{
diff --git a/js/xpconnect/wrappers/WrapperFactory.cpp b/js/xpconnect/wrappers/WrapperFactory.cpp
index 648b758364d3..6296f69fbea4 100644
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -565,9 +565,11 @@ WrapperFactory::Rewrap(JSContext* cx, HandleObject existing, HandleObject obj)
wrapper = SelectAddonWrapper(cx, obj, wrapper);
}
- if (!targetSubsumesOrigin) {
+ if (!targetSubsumesOrigin &&
+ !originCompartmentPrivate->forcePermissiveCOWs) {
// Do a belt-and-suspenders check against exposing eval()/Function() to
- // non-subsuming content.
+ // non-subsuming content. But don't worry about doing it in the
+ // SpecialPowers case.
if (JSFunction* fun = JS_GetObjectFunction(obj)) {
if (JS_IsBuiltinEvalFunction(fun) || JS_IsBuiltinFunctionConstructor(fun)) {
NS_WARNING("Trying to expose eval or Function to non-subsuming content!");
diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp
index a3adbb4c079c..8aac8e2af855 100644
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -4293,11 +4293,9 @@ SetFlagsOnSubtree(nsIContent *aNode, uintptr_t aFlagsToSet)
aNode->SetFlags(aFlagsToSet);
// Set the flag on all of its children recursively
- uint32_t count;
- nsIContent * const *children = aNode->GetChildArray(&count);
-
- for (uint32_t index = 0; index < count; ++index) {
- SetFlagsOnSubtree(children[index], aFlagsToSet);
+ for (nsIContent* child = aNode->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ SetFlagsOnSubtree(child, aFlagsToSet);
}
}
diff --git a/layout/generic/nsPlaceholderFrame.cpp b/layout/generic/nsPlaceholderFrame.cpp
index 487af4eda75c..052c532cc810 100644
--- a/layout/generic/nsPlaceholderFrame.cpp
+++ b/layout/generic/nsPlaceholderFrame.cpp
@@ -209,25 +209,10 @@ nsPlaceholderFrame::GetParentStyleContextForOutOfFlow(nsIFrame** aProviderFrame)
nsStyleContext*
nsPlaceholderFrame::GetLayoutParentStyleForOutOfFlow(nsIFrame** aProviderFrame) const
{
- nsIFrame* parentFrame = GetParent();
- // Placeholder of backdrop frame is a child of the corresponding top
- // layer frame, and its style context inherits from that frame. In
- // case of table, the top layer frame is the table wrapper frame.
- // However, it will be skipped in CorrectStyleParentFrame below, so
- // we need to handle it specially here.
- if ((GetStateBits() & PLACEHOLDER_FOR_TOPLAYER) &&
- parentFrame->IsTableWrapperFrame()) {
- MOZ_ASSERT(mOutOfFlowFrame->IsBackdropFrame(),
- "Only placeholder of backdrop frame can be put inside "
- "a table wrapper frame");
- *aProviderFrame = parentFrame;
- return parentFrame->StyleContext();
- }
-
// Lie about our pseudo so we can step out of all anon boxes and
// pseudo-elements. The other option would be to reimplement the
// {ib} split gunk here.
- *aProviderFrame = CorrectStyleParentFrame(parentFrame,
+ *aProviderFrame = CorrectStyleParentFrame(GetParent(),
nsGkAtoms::placeholderFrame);
return *aProviderFrame ? (*aProviderFrame)->StyleContext() : nullptr;
}
diff --git a/layout/style/Loader.cpp b/layout/style/Loader.cpp
index 2953dea87932..2756e24550fe 100644
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -16,6 +16,8 @@
#include "mozilla/MemoryReporting.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/SystemGroup.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/URLPreloader.h"
#include "nsIRunnable.h"
#include "nsIUnicharStreamLoader.h"
#include "nsSyncLoadService.h"
@@ -44,6 +46,7 @@
#include "nsGkAtoms.h"
#include "nsIThreadInternal.h"
#include "nsINetworkPredictor.h"
+#include "nsStringStream.h"
#include "mozilla/dom/MediaList.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/URL.h"
@@ -1356,11 +1359,24 @@ Loader::LoadSheet(SheetLoadData* aLoadData,
// we should always have a requestingNode, or we are loading something
// outside a document, in which case the loadingPrincipal and the
// triggeringPrincipal should always be the systemPrincipal.
- rv = NS_NewChannel(getter_AddRefs(channel),
- aLoadData->mURI,
- nsContentUtils::GetSystemPrincipal(),
- securityFlags,
- contentPolicyType);
+ auto result = URLPreloader::ReadURI(aLoadData->mURI);
+ if (result.isOk()) {
+ nsCOMPtr stream;
+ MOZ_TRY(NS_NewCStringInputStream(getter_AddRefs(stream), result.unwrap()));
+
+ rv = NS_NewInputStreamChannel(getter_AddRefs(channel),
+ aLoadData->mURI,
+ stream,
+ nsContentUtils::GetSystemPrincipal(),
+ securityFlags,
+ contentPolicyType);
+ } else {
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ aLoadData->mURI,
+ nsContentUtils::GetSystemPrincipal(),
+ securityFlags,
+ contentPolicyType);
+ }
}
if (NS_FAILED(rv)) {
LOG_ERROR((" Failed to create channel"));
diff --git a/layout/tables/nsTableColGroupFrame.cpp b/layout/tables/nsTableColGroupFrame.cpp
index 1f646a8bc7fc..7ff3aef519a6 100644
--- a/layout/tables/nsTableColGroupFrame.cpp
+++ b/layout/tables/nsTableColGroupFrame.cpp
@@ -17,24 +17,17 @@
using namespace mozilla;
-#define COL_GROUP_TYPE_BITS (NS_FRAME_STATE_BIT(30) | \
- NS_FRAME_STATE_BIT(31))
-#define COL_GROUP_TYPE_OFFSET 30
+#define COLGROUP_SYNTHETIC_BIT NS_FRAME_STATE_BIT(30)
-nsTableColGroupType
-nsTableColGroupFrame::GetColType() const
+bool
+nsTableColGroupFrame::IsSynthetic() const
{
- return (nsTableColGroupType)((mState & COL_GROUP_TYPE_BITS) >> COL_GROUP_TYPE_OFFSET);
+ return HasAnyStateBits(COLGROUP_SYNTHETIC_BIT);
}
-void nsTableColGroupFrame::SetColType(nsTableColGroupType aType)
+void nsTableColGroupFrame::SetIsSynthetic()
{
- NS_ASSERTION(GetColType() == eColGroupContent,
- "should only call nsTableColGroupFrame::SetColType with aType "
- "!= eColGroupContent once");
- uint32_t type = aType - eColGroupContent;
- RemoveStateBits(COL_GROUP_TYPE_BITS);
- AddStateBits(nsFrameState(type << COL_GROUP_TYPE_OFFSET));
+ AddStateBits(COLGROUP_SYNTHETIC_BIT);
}
void nsTableColGroupFrame::ResetColIndices(nsIFrame* aFirstColGroup,
@@ -114,23 +107,16 @@ nsTableColGroupFrame::GetLastRealColGroup(nsTableFrame* aTableFrame)
{
nsFrameList colGroups = aTableFrame->GetColGroups();
- nsIFrame* nextToLastColGroup = nullptr;
- nsFrameList::FrameLinkEnumerator link(colGroups);
- for ( ; !link.AtEnd(); link.Next()) {
- nextToLastColGroup = link.PrevFrame();
+ auto lastColGroup = static_cast(colGroups.LastChild());
+ if (!lastColGroup) {
+ return nullptr;
}
- if (!link.PrevFrame()) {
- return nullptr; // there are no col group frames
+ if (!lastColGroup->IsSynthetic()) {
+ return lastColGroup;
}
- nsTableColGroupType lastColGroupType =
- static_cast(link.PrevFrame())->GetColType();
- if (eColGroupAnonymousCell == lastColGroupType) {
- return static_cast(nextToLastColGroup);
- }
-
- return static_cast(link.PrevFrame());
+ return static_cast(lastColGroup->GetPrevSibling());
}
// don't set mColCount here, it is done in AddColsToTable
@@ -190,9 +176,8 @@ nsTableColGroupFrame::AppendFrames(ChildListID aListID,
// Our next colframe should be an eColContent. We've removed all the
// eColAnonymousColGroup colframes, eColAnonymousCol colframes always follow
- // eColContent ones, and eColAnonymousCell colframes only appear in an
- // eColGroupAnonymousCell colgroup, which never gets AppendFrames() called on
- // it.
+ // eColContent ones, and eColAnonymousCell colframes only appear in a
+ // synthetic colgroup, which never gets AppendFrames() called on it.
MOZ_ASSERT(!col || col->GetColType() == eColContent,
"What's going on with our columns?");
@@ -231,9 +216,8 @@ nsTableColGroupFrame::InsertFrames(ChildListID aListID,
// Our next colframe should be an eColContent. We've removed all the
// eColAnonymousColGroup colframes, eColAnonymousCol colframes always follow
- // eColContent ones, and eColAnonymousCell colframes only appear in an
- // eColGroupAnonymousCell colgroup, which never gets InsertFrames() called on
- // it.
+ // eColContent ones, and eColAnonymousCell colframes only appear in a
+ // synthetic colgroup, which never gets InsertFrames() called on it.
MOZ_ASSERT(!col || col->GetColType() == eColContent,
"What's going on with our columns?");
@@ -338,8 +322,7 @@ nsTableColGroupFrame::RemoveFrame(ChildListID aListID,
nsTableFrame* tableFrame = GetTableFrame();
tableFrame->RemoveCol(this, colIndex, true, true);
- if (mFrames.IsEmpty() && contentRemoval &&
- GetColType() == eColGroupContent) {
+ if (mFrames.IsEmpty() && contentRemoval && !IsSynthetic()) {
tableFrame->AppendAnonymousColFrames(this, GetSpan(),
eColAnonymousColGroup, true);
}
@@ -515,20 +498,10 @@ void nsTableColGroupFrame::Dump(int32_t aIndent)
}
indent[aIndent] = 0;
- printf("%s**START COLGROUP DUMP**\n%s startcolIndex=%d colcount=%d span=%d coltype=",
- indent, indent, GetStartColumnIndex(), GetColCount(), GetSpan());
- nsTableColGroupType colType = GetColType();
- switch (colType) {
- case eColGroupContent:
- printf(" content ");
- break;
- case eColGroupAnonymousCol:
- printf(" anonymous-column ");
- break;
- case eColGroupAnonymousCell:
- printf(" anonymous-cell ");
- break;
- }
+ printf("%s**START COLGROUP DUMP**\n%s startcolIndex=%d colcount=%d span=%d isSynthetic=%s",
+ indent, indent, GetStartColumnIndex(), GetColCount(), GetSpan(),
+ IsSynthetic() ? "true" : "false");
+
// verify the colindices
int32_t j = GetStartColumnIndex();
nsTableColFrame* col = GetFirstColumn();
diff --git a/layout/tables/nsTableColGroupFrame.h b/layout/tables/nsTableColGroupFrame.h
index 46af756866c5..e740df500e79 100644
--- a/layout/tables/nsTableColGroupFrame.h
+++ b/layout/tables/nsTableColGroupFrame.h
@@ -54,19 +54,20 @@ class nsTableColGroupFrame final : public nsContainerFrame
const nsDisplayListSet& aLists) override;
/** A colgroup can be caused by three things:
- * 1) An element with table-column-group display
- * 2) An element with a table-column display without a
- * table-column-group parent
- * 3) Cells that are not in a column (and hence get an anonymous
- * column and colgroup).
- * @return colgroup type
- */
- nsTableColGroupType GetColType() const;
-
- /** Set the colgroup type based on the creation cause
- * @param aType - the reason why this colgroup is needed
+ * 1) An element with table-column-group display
+ * 2) An element with a table-column display without a
+ * table-column-group parent
+ * 3) Cells that are not in a column (and hence get an anonymous
+ * column and colgroup).
+ *
+ * In practice, we don't need to differentiate between cases (1) and (2),
+ * because they both correspond to table-column-group boxes in the spec and
+ * hence have observably identical behavior. Case three is flagged as a
+ * synthetic colgroup, because it may need to have different behavior in some
+ * cases.
*/
- void SetColType(nsTableColGroupType aType);
+ bool IsSynthetic() const;
+ void SetIsSynthetic();
/** Real in this context are colgroups that come from an element
* with table-column-group display or wrap around columns that
@@ -224,7 +225,6 @@ inline nsTableColGroupFrame::nsTableColGroupFrame(nsStyleContext* aContext)
, mColCount(0)
, mStartColIndex(0)
{
- SetColType(eColGroupContent);
}
inline int32_t nsTableColGroupFrame::GetStartColumnIndex()
diff --git a/layout/tables/nsTableFrame.cpp b/layout/tables/nsTableFrame.cpp
index 64487781027b..416873bd06e2 100644
--- a/layout/tables/nsTableFrame.cpp
+++ b/layout/tables/nsTableFrame.cpp
@@ -604,9 +604,10 @@ nsTableFrame::InsertCol(nsTableColFrame& aColFrame,
if (eColAnonymousCell == lastColType) {
// remove the col from the cache
mColFrames.RemoveElementAt(numCacheCols - 1);
- // remove the col from the eColGroupAnonymousCell col group
+ // remove the col from the synthetic col group
nsTableColGroupFrame* lastColGroup = (nsTableColGroupFrame *)mColGroups.LastChild();
if (lastColGroup) {
+ MOZ_ASSERT(lastColGroup->IsSynthetic());
lastColGroup->RemoveChild(*lastCol, false);
// remove the col group if it is empty
@@ -678,9 +679,8 @@ nsTableFrame::GetCellMap() const
return static_cast(FirstInFlow())->mCellMap;
}
-// XXX this needs to be moved to nsCSSFrameConstructor
nsTableColGroupFrame*
-nsTableFrame::CreateAnonymousColGroupFrame(nsTableColGroupType aColGroupType)
+nsTableFrame::CreateSyntheticColGroupFrame()
{
nsIContent* colGroupContent = GetContent();
nsPresContext* presContext = PresContext();
@@ -690,10 +690,11 @@ nsTableFrame::CreateAnonymousColGroupFrame(nsTableColGroupType aColGroupType)
colGroupStyle = shell->StyleSet()->
ResolveNonInheritingAnonymousBoxStyle(nsCSSAnonBoxes::tableColGroup);
// Create a col group frame
- nsIFrame* newFrame = NS_NewTableColGroupFrame(shell, colGroupStyle);
- ((nsTableColGroupFrame *)newFrame)->SetColType(aColGroupType);
+ nsTableColGroupFrame* newFrame =
+ NS_NewTableColGroupFrame(shell, colGroupStyle);
+ newFrame->SetIsSynthetic();
newFrame->Init(colGroupContent, this, nullptr);
- return (nsTableColGroupFrame *)newFrame;
+ return newFrame;
}
void
@@ -704,12 +705,11 @@ nsTableFrame::AppendAnonymousColFrames(int32_t aNumColsToAdd)
nsTableColGroupFrame* colGroupFrame =
static_cast(mColGroups.LastChild());
- if (!colGroupFrame ||
- (colGroupFrame->GetColType() != eColGroupAnonymousCell)) {
+ if (!colGroupFrame || !colGroupFrame->IsSynthetic()) {
int32_t colIndex = (colGroupFrame) ?
colGroupFrame->GetStartColumnIndex() +
colGroupFrame->GetColCount() : 0;
- colGroupFrame = CreateAnonymousColGroupFrame(eColGroupAnonymousCell);
+ colGroupFrame = CreateSyntheticColGroupFrame();
if (!colGroupFrame) {
return;
}
diff --git a/layout/tables/nsTableFrame.h b/layout/tables/nsTableFrame.h
index f288245412bb..f31e757ec861 100644
--- a/layout/tables/nsTableFrame.h
+++ b/layout/tables/nsTableFrame.h
@@ -105,12 +105,6 @@ class nsAutoPushCurrentTableItem
/* ============================================================================ */
-enum nsTableColGroupType {
- eColGroupContent = 0, // there is real col group content associated
- eColGroupAnonymousCol = 1, // the result of a col
- eColGroupAnonymousCell = 2 // the result of a cell alone
-};
-
enum nsTableColType {
eColContent = 0, // there is real col content associated
eColAnonymousCol = 1, // the result of a span on a col
@@ -504,13 +498,12 @@ class nsTableFrame : public nsContainerFrame
void InsertCol(nsTableColFrame& aColFrame,
int32_t aColIndex);
- nsTableColGroupFrame* CreateAnonymousColGroupFrame(nsTableColGroupType aType);
+ nsTableColGroupFrame* CreateSyntheticColGroupFrame();
int32_t DestroyAnonymousColFrames(int32_t aNumFrames);
// Append aNumColsToAdd anonymous col frames of type eColAnonymousCell to our
- // last eColGroupAnonymousCell colgroup. If we have no such colgroup, then
- // create one.
+ // last synthetic colgroup. If we have no such colgroup, then create one.
void AppendAnonymousColFrames(int32_t aNumColsToAdd);
// Append aNumColsToAdd anonymous col frames of type aColType to
diff --git a/mobile/android/components/extensions/ext-utils.js b/mobile/android/components/extensions/ext-utils.js
index ba26b35733c3..0e985dc20b93 100644
--- a/mobile/android/components/extensions/ext-utils.js
+++ b/mobile/android/components/extensions/ext-utils.js
@@ -482,6 +482,10 @@ class Tab extends TabBase {
return this.nativeTab.browser;
}
+ get discarded() {
+ return this.browser.getAttribute("pending") === "true";
+ }
+
get cookieStoreId() {
return getCookieStoreIdForTab(this, this.nativeTab);
}
diff --git a/mobile/android/components/extensions/schemas/tabs.json b/mobile/android/components/extensions/schemas/tabs.json
index 423263e7550a..5f9b1ca0a773 100644
--- a/mobile/android/components/extensions/schemas/tabs.json
+++ b/mobile/android/components/extensions/schemas/tabs.json
@@ -71,6 +71,7 @@
"title": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The title of the tab. This property is only present if the extension's manifest includes the \"tabs\"
permission."},
"favIconUrl": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL of the tab's favicon. This property is only present if the extension's manifest includes the \"tabs\"
permission. It may also be an empty string if the tab is loading."},
"status": {"type": "string", "optional": true, "description": "Either loading or complete."},
+ "discarded": {"type": "boolean", "optional": true, "description": "True while the tab is not loaded with content."},
"incognito": {"type": "boolean", "description": "Whether the tab is in an incognito window."},
"width": {"type": "integer", "optional": true, "description": "The width of the tab in pixels."},
"height": {"type": "integer", "optional": true, "description": "The height of the tab in pixels."},
@@ -494,6 +495,11 @@
"optional": true,
"description": "Whether the tabs have completed loading."
},
+ "discarded": {
+ "type": "boolean",
+ "optional": true,
+ "description": "True while the tabs are not loaded with content."
+ },
"title": {
"type": "string",
"optional": true,
@@ -1056,6 +1062,11 @@
"optional": true,
"description": "The status of the tab. Can be either loading or complete."
},
+ "discarded": {
+ "type": "boolean",
+ "optional": true,
+ "description": "True while the tab is not loaded with content."
+ },
"url": {
"type": "string",
"optional": true,
diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp
index 6ef130cb5146..cb7223d9de5c 100644
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -10,9 +10,12 @@
#include "mozilla/ArrayUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/HashFunctions.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/ScopeExit.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/Telemetry.h"
+#include "mozilla/URLPreloader.h"
#include "mozilla/UniquePtrExtensions.h"
#include "nsXULAppAPI.h"
@@ -1265,46 +1268,20 @@ Preferences::WritePrefFile(nsIFile* aFile, SaveMethod aSaveMethod)
static nsresult openPrefFile(nsIFile* aFile)
{
- nsCOMPtr inStr;
-
- nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), aFile);
- if (NS_FAILED(rv))
- return rv;
-
- int64_t fileSize64;
- rv = aFile->GetFileSize(&fileSize64);
- if (NS_FAILED(rv))
- return rv;
- NS_ENSURE_TRUE(fileSize64 <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
-
- uint32_t fileSize = (uint32_t)fileSize64;
- auto fileBuffer = MakeUniqueFallible(fileSize);
- if (fileBuffer == nullptr)
- return NS_ERROR_OUT_OF_MEMORY;
-
PrefParseState ps;
PREF_InitParseState(&ps, PREF_ReaderCallback, ReportToConsole, nullptr);
+ auto cleanup = MakeScopeExit([&] () {
+ PREF_FinalizeParseState(&ps);
+ });
- // Read is not guaranteed to return a buf the size of fileSize,
- // but usually will.
- nsresult rv2 = NS_OK;
- uint32_t offset = 0;
- for (;;) {
- uint32_t amtRead = 0;
- rv = inStr->Read(fileBuffer.get(), fileSize, &amtRead);
- if (NS_FAILED(rv) || amtRead == 0)
- break;
- if (!PREF_ParseBuf(&ps, fileBuffer.get(), amtRead))
- rv2 = NS_ERROR_FILE_CORRUPTED;
- offset += amtRead;
- if (offset == fileSize) {
- break;
- }
+ nsCString data;
+ MOZ_TRY_VAR(data, URLPreloader::ReadFile(aFile));
+ if (!PREF_ParseBuf(&ps, data.get(), data.Length())) {
+ return NS_ERROR_FILE_CORRUPTED;
}
- PREF_FinalizeParseState(&ps);
+ return NS_OK;
- return NS_FAILED(rv) ? rv : rv2;
}
/*
@@ -1461,12 +1438,12 @@ static nsresult pref_LoadPrefsInDirList(const char *listId)
static nsresult pref_ReadPrefFromJar(nsZipArchive* jarReader, const char *name)
{
- nsZipItemPtr manifest(jarReader, name, true);
- NS_ENSURE_TRUE(manifest.Buffer(), NS_ERROR_NOT_AVAILABLE);
+ nsCString manifest;
+ MOZ_TRY_VAR(manifest, URLPreloader::ReadZip(jarReader, nsDependentCString(name)));
PrefParseState ps;
PREF_InitParseState(&ps, PREF_ReaderCallback, ReportToConsole, nullptr);
- PREF_ParseBuf(&ps, manifest, manifest.Length());
+ PREF_ParseBuf(&ps, manifest.get(), manifest.Length());
PREF_FinalizeParseState(&ps);
return NS_OK;
diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp
index 6cf76cf018fd..a94463f5d24c 100644
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -2304,6 +2304,18 @@ HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
LOG(("HttpChannelChild::AsyncOpen [this=%p uri=%s]\n", this, mSpec.get()));
+ if (LOG4_ENABLED()) {
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ if (cx) {
+ nsAutoCString fileNameString;
+ uint32_t line = 0, col = 0;
+ if (nsJSUtils::GetCallingLocation(cx, fileNameString, &line, &col)) {
+ LOG(("HttpChannelChild %p source script=%s:%u:%u",
+ this, fileNameString.get(), line, col));
+ }
+ }
+ }
+
#ifdef DEBUG
AssertPrivateBrowsingId();
#endif
diff --git a/toolkit/components/extensions/ext-tabs-base.js b/toolkit/components/extensions/ext-tabs-base.js
index 5be339dd8119..e6be9efc46bc 100644
--- a/toolkit/components/extensions/ext-tabs-base.js
+++ b/toolkit/components/extensions/ext-tabs-base.js
@@ -491,6 +491,7 @@ class TabBase {
active: this.selected,
pinned: this.pinned,
status: this.status,
+ discarded: this.discarded,
incognito: this.incognito,
width: this.width,
height: this.height,
diff --git a/toolkit/components/passwordmgr/LoginManagerContent.jsm b/toolkit/components/passwordmgr/LoginManagerContent.jsm
index 13726cf17c7e..abf36cbc8912 100644
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -37,6 +37,10 @@ XPCOMUtils.defineLazyGetter(this, "log", () => {
return logger.log.bind(logger);
});
+Services.cpmm.addMessageListener("clearRecipeCache", () => {
+ LoginRecipesContent._clearRecipeCache();
+});
+
// These mirror signon.* prefs.
var gEnabled, gAutofillForms, gStoreWhenAutocompleteOff;
var gLastRightClickTimeStamp = Number.NEGATIVE_INFINITY;
@@ -560,6 +564,9 @@ var LoginManagerContent = {
let doc = form.ownerDocument;
let autofillForm = gAutofillForms && !PrivateBrowsingUtils.isContentWindowPrivate(doc.defaultView);
+ let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI);
+ LoginRecipesContent.cacheRecipes(formOrigin, doc.defaultView, recipes);
+
this._fillForm(form, loginsFound, recipes, {autofillForm});
},
@@ -631,10 +638,8 @@ var LoginManagerContent = {
log("onUsernameInput from", event.type);
let doc = acForm.ownerDocument;
- let messageManager = messageManagerFromWindow(doc.defaultView);
- let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
- formOrigin: LoginUtils._getPasswordOrigin(doc.documentURI),
- })[0];
+ let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI);
+ let recipes = LoginRecipesContent.getRecipes(formOrigin, doc.defaultView);
// Make sure the username field fillForm will use is the
// same field as the autocomplete was activated on.
@@ -928,9 +933,7 @@ var LoginManagerContent = {
let formSubmitURL = LoginUtils._getActionOrigin(form);
let messageManager = messageManagerFromWindow(win);
- let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
- formOrigin: hostname,
- })[0];
+ let recipes = LoginRecipesContent.getRecipes(hostname, win);
// Get the appropriate fields from the form.
var [usernameField, newPasswordField, oldPasswordField] =
@@ -1345,10 +1348,8 @@ var LoginManagerContent = {
let form = LoginFormFactory.createFromField(aField);
let doc = aField.ownerDocument;
- let messageManager = messageManagerFromWindow(doc.defaultView);
- let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
- formOrigin: LoginUtils._getPasswordOrigin(doc.documentURI),
- })[0];
+ let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI);
+ let recipes = LoginRecipesContent.getRecipes(formOrigin, doc.defaultView);
let [usernameField, newPasswordField] =
this._getFormFields(form, false, recipes);
diff --git a/toolkit/components/passwordmgr/LoginRecipes.jsm b/toolkit/components/passwordmgr/LoginRecipes.jsm
index 28b27ca26d49..926492b457ca 100644
--- a/toolkit/components/passwordmgr/LoginRecipes.jsm
+++ b/toolkit/components/passwordmgr/LoginRecipes.jsm
@@ -113,6 +113,7 @@ LoginRecipesParent.prototype = {
resolve(JSON.parse(data));
});
}).then(recipes => {
+ Services.ppmm.broadcastAsyncMessage("clearRecipeCache");
return this.load(recipes);
}).then(resolve => {
return this;
@@ -189,6 +190,62 @@ LoginRecipesParent.prototype = {
var LoginRecipesContent = {
+ _recipeCache: new WeakMap(),
+
+ _clearRecipeCache() {
+ this._recipeCache = new WeakMap();
+ },
+
+ /**
+ * Locally caches recipes for a given host.
+ *
+ * @param {String} aHost (e.g. example.com:8080 [non-default port] or sub.example.com)
+ * @param {Object} win - the window of the host
+ * @param {Set} recipes - recipes that apply to the host
+ */
+ cacheRecipes(aHost, win, recipes) {
+ let recipeMap = this._recipeCache.get(win);
+
+ if (!recipeMap) {
+ recipeMap = new Map();
+ this._recipeCache.set(win, recipeMap);
+ }
+
+ recipeMap.set(aHost, recipes);
+ },
+
+ /**
+ * Tries to fetch recipes for a given host, using a local cache if possible.
+ * Otherwise, the recipes are cached for later use.
+ *
+ * @param {String} aHost (e.g. example.com:8080 [non-default port] or sub.example.com)
+ * @param {Object} win - the window of the host
+ * @return {Set} of recipes that apply to the host
+ */
+ getRecipes(aHost, win) {
+ let recipes;
+ let recipeMap = this._recipeCache.get(win);
+
+ if (recipeMap) {
+ recipes = recipeMap.get(aHost);
+
+ if (recipes) {
+ return recipes;
+ }
+ }
+
+ let mm = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+
+ recipes = mm.sendSyncMessage("RemoteLogins:findRecipes", { formOrigin: aHost })[0];
+ this.cacheRecipes(aHost, win, recipes);
+
+ return recipes;
+ },
+
/**
* @param {Set} aRecipes - Possible recipes that could apply to the form
* @param {FormLike} aForm - We use a form instead of just a URL so we can later apply
diff --git a/toolkit/components/passwordmgr/test/mochitest/mochitest.ini b/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
index a1d8ecad342a..18f44b6f5f0f 100644
--- a/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
+++ b/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
@@ -52,6 +52,7 @@ skip-if = toolkit == 'android' # autocomplete
[test_input_events.html]
[test_input_events_for_identical_values.html]
[test_maxlength.html]
+[test_onsubmit_value_change.html]
[test_passwords_in_type_password.html]
[test_prompt.html]
skip-if = os == "linux" || toolkit == 'android' # Tests desktop prompts
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
index 2fe7033f6d56..2a2a36a3ee69 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
@@ -162,19 +162,19 @@
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
@@ -771,49 +771,10 @@
is(popupState.open, false, "Check popup stays closed due to cached empty result");
});
-add_task(async function test_form11_recipes() {
- await loadRecipes({
- siteRecipes: [{
- "hosts": ["mochi.test:8888"],
- "usernameSelector": "input[name='1']",
- "passwordSelector": "input[name='2']"
- }],
- });
- uname = $_(11, "1");
- pword = $_(11, "2");
-
- // First test DOMAutocomplete
- // Switch the password field to type=password so _fillForm marks the username
- // field for autocomplete.
- pword.type = "password";
- await promiseFormsProcessed();
- restoreForm();
- checkACForm("", "");
- let shownPromise = promiseACShown();
- doKey("down"); // open
- await shownPromise;
-
- doKey("down");
- checkACForm("", ""); // value shouldn't update
- doKey("return"); // not "enter"!
- await promiseFormsProcessed();
- checkACForm("testuser10", "testpass10");
-
- // Now test recipes with blur on the username field.
- restoreForm();
- checkACForm("", "");
- uname.value = "testuser10";
- checkACForm("testuser10", "");
- doKey("tab");
- await promiseFormsProcessed();
- checkACForm("testuser10", "testpass10");
- await resetRecipes();
-});
-
-add_task(async function test_form12_formless() {
+add_task(async function test_form11_formless() {
// Test form-less autocomplete
- uname = $_(12, "uname");
- pword = $_(12, "pword");
+ uname = $_(11, "uname");
+ pword = $_(11, "pword");
restoreForm();
checkACForm("", "");
let shownPromise = promiseACShown();
@@ -829,9 +790,9 @@
checkACForm("testuser", "testpass");
});
-add_task(async function test_form12_open_on_trusted_focus() {
- uname = $_(12, "uname");
- pword = $_(12, "pword");
+add_task(async function test_form11_open_on_trusted_focus() {
+ uname = $_(11, "uname");
+ pword = $_(11, "pword");
uname.value = "";
pword.value = "";
@@ -855,6 +816,45 @@
await processedPromise;
checkACForm("testuser", "testpass");
});
+
+add_task(async function test_form12_recipes() {
+ await loadRecipes({
+ siteRecipes: [{
+ "hosts": ["mochi.test:8888"],
+ "usernameSelector": "input[name='1']",
+ "passwordSelector": "input[name='2']"
+ }],
+ });
+ uname = $_(12, "1");
+ pword = $_(12, "2");
+
+ // First test DOMAutocomplete
+ // Switch the password field to type=password so _fillForm marks the username
+ // field for autocomplete.
+ pword.type = "password";
+ await promiseFormsProcessed();
+ restoreForm();
+ checkACForm("", "");
+ let shownPromise = promiseACShown();
+ doKey("down"); // open
+ await shownPromise;
+
+ doKey("down");
+ checkACForm("", ""); // value shouldn't update
+ doKey("return"); // not "enter"!
+ await promiseFormsProcessed();
+ checkACForm("testuser10", "testpass10");
+
+ // Now test recipes with blur on the username field.
+ restoreForm();
+ checkACForm("", "");
+ uname.value = "testuser10";
+ checkACForm("testuser10", "");
+ doKey("tab");
+ await promiseFormsProcessed();
+ checkACForm("testuser10", "testpass10");
+ await resetRecipes();
+});
+Login Manager test: input value change right after onsubmit event
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_onsubmit_value_change.html b/toolkit/components/passwordmgr/test/mochitest/test_onsubmit_value_change.html
new file mode 100644
index 000000000000..bdf1dfd61bdd
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/mochitest/test_onsubmit_value_change.html
@@ -0,0 +1,67 @@
+
+
+