summaryrefslogtreecommitdiffstats
path: root/dom/xbl
diff options
context:
space:
mode:
Diffstat (limited to 'dom/xbl')
-rw-r--r--dom/xbl/XBLChildrenElement.cpp205
-rw-r--r--dom/xbl/XBLChildrenElement.h192
-rw-r--r--dom/xbl/builtin/android/jar.mn6
-rw-r--r--dom/xbl/builtin/android/moz.build7
-rw-r--r--dom/xbl/builtin/android/platformHTMLBindings.xml162
-rw-r--r--dom/xbl/builtin/browser-base.inc14
-rw-r--r--dom/xbl/builtin/editor-base.inc19
-rw-r--r--dom/xbl/builtin/emacs/jar.mn6
-rw-r--r--dom/xbl/builtin/emacs/moz.build7
-rw-r--r--dom/xbl/builtin/emacs/platformHTMLBindings.xml237
-rw-r--r--dom/xbl/builtin/input-fields-base.inc17
-rw-r--r--dom/xbl/builtin/mac/jar.mn6
-rw-r--r--dom/xbl/builtin/mac/moz.build7
-rw-r--r--dom/xbl/builtin/mac/platformHTMLBindings.xml72
-rw-r--r--dom/xbl/builtin/moz.build17
-rw-r--r--dom/xbl/builtin/textareas-base.inc16
-rw-r--r--dom/xbl/builtin/unix/jar.mn6
-rw-r--r--dom/xbl/builtin/unix/moz.build7
-rw-r--r--dom/xbl/builtin/unix/platformHTMLBindings.xml82
-rw-r--r--dom/xbl/builtin/win/jar.mn6
-rw-r--r--dom/xbl/builtin/win/moz.build7
-rw-r--r--dom/xbl/builtin/win/platformHTMLBindings.xml164
-rw-r--r--dom/xbl/crashtests/205735-1.xhtml27
-rw-r--r--dom/xbl/crashtests/223799-1.xul27
-rw-r--r--dom/xbl/crashtests/226744-1.xhtml35
-rw-r--r--dom/xbl/crashtests/232095-1.xul44
-rw-r--r--dom/xbl/crashtests/277523-1.xhtml24
-rw-r--r--dom/xbl/crashtests/277950-1.xhtml20
-rw-r--r--dom/xbl/crashtests/336744-1-inner.html6
-rw-r--r--dom/xbl/crashtests/336744-1.html9
-rw-r--r--dom/xbl/crashtests/336960-1-inner.xhtml29
-rw-r--r--dom/xbl/crashtests/336960-1.html13
-rw-r--r--dom/xbl/crashtests/342954-1.xhtml46
-rw-r--r--dom/xbl/crashtests/342954-2-xbl.xml19
-rw-r--r--dom/xbl/crashtests/342954-2.xhtml29
-rw-r--r--dom/xbl/crashtests/368276-1.xhtml33
-rw-r--r--dom/xbl/crashtests/368641-1.xhtml22
-rw-r--r--dom/xbl/crashtests/378521-1.xhtml18
-rw-r--r--dom/xbl/crashtests/382376-1.xhtml37
-rw-r--r--dom/xbl/crashtests/382376-2.xhtml27
-rw-r--r--dom/xbl/crashtests/397596-1.xhtml29
-rw-r--r--dom/xbl/crashtests/404125-1.xhtml29
-rw-r--r--dom/xbl/crashtests/406900-1.xul33
-rw-r--r--dom/xbl/crashtests/406904-1.xhtml25
-rw-r--r--dom/xbl/crashtests/406904-2.xhtml25
-rw-r--r--dom/xbl/crashtests/415192-1.xul22
-rw-r--r--dom/xbl/crashtests/415301-1.xul34
-rw-r--r--dom/xbl/crashtests/418133-1.xhtml22
-rw-r--r--dom/xbl/crashtests/420233-1.xhtml21
-rw-r--r--dom/xbl/crashtests/421997-1.xhtml27
-rw-r--r--dom/xbl/crashtests/432813-1-xbl.xml7
-rw-r--r--dom/xbl/crashtests/432813-1.xhtml3
-rw-r--r--dom/xbl/crashtests/454820-1.html17
-rw-r--r--dom/xbl/crashtests/460665-1.xhtml28
-rw-r--r--dom/xbl/crashtests/463511-1.xhtml9
-rw-r--r--dom/xbl/crashtests/464863-1.xhtml22
-rw-r--r--dom/xbl/crashtests/472260-1.xhtml35
-rw-r--r--dom/xbl/crashtests/477878-1.html4
-rw-r--r--dom/xbl/crashtests/492978-1.xul15
-rw-r--r--dom/xbl/crashtests/493123-1.xhtml34
-rw-r--r--dom/xbl/crashtests/495354-1.xhtml8
-rw-r--r--dom/xbl/crashtests/507628-1.xhtml12
-rw-r--r--dom/xbl/crashtests/507991-1.xhtml18
-rw-r--r--dom/xbl/crashtests/830614-1.xul24
-rw-r--r--dom/xbl/crashtests/895805-1.xhtml30
-rw-r--r--dom/xbl/crashtests/crashtests.list40
-rw-r--r--dom/xbl/crashtests/set-field-bad-this.xhtml31
-rw-r--r--dom/xbl/moz.build53
-rw-r--r--dom/xbl/nsBindingManager.cpp1160
-rw-r--r--dom/xbl/nsBindingManager.h235
-rw-r--r--dom/xbl/nsXBLBinding.cpp1247
-rw-r--r--dom/xbl/nsXBLBinding.h187
-rw-r--r--dom/xbl/nsXBLContentSink.cpp937
-rw-r--r--dom/xbl/nsXBLContentSink.h155
-rw-r--r--dom/xbl/nsXBLDocumentInfo.cpp325
-rw-r--r--dom/xbl/nsXBLDocumentInfo.h73
-rw-r--r--dom/xbl/nsXBLEventHandler.cpp187
-rw-r--r--dom/xbl/nsXBLEventHandler.h121
-rw-r--r--dom/xbl/nsXBLMaybeCompiled.h172
-rw-r--r--dom/xbl/nsXBLProtoImpl.cpp535
-rw-r--r--dom/xbl/nsXBLProtoImpl.h125
-rw-r--r--dom/xbl/nsXBLProtoImplField.cpp510
-rw-r--r--dom/xbl/nsXBLProtoImplField.h63
-rw-r--r--dom/xbl/nsXBLProtoImplMember.h103
-rw-r--r--dom/xbl/nsXBLProtoImplMethod.cpp356
-rw-r--r--dom/xbl/nsXBLProtoImplMethod.h156
-rw-r--r--dom/xbl/nsXBLProtoImplProperty.cpp370
-rw-r--r--dom/xbl/nsXBLProtoImplProperty.h66
-rw-r--r--dom/xbl/nsXBLPrototypeBinding.cpp1696
-rw-r--r--dom/xbl/nsXBLPrototypeBinding.h359
-rw-r--r--dom/xbl/nsXBLPrototypeHandler.cpp1016
-rw-r--r--dom/xbl/nsXBLPrototypeHandler.h243
-rw-r--r--dom/xbl/nsXBLPrototypeResources.cpp204
-rw-r--r--dom/xbl/nsXBLPrototypeResources.h72
-rw-r--r--dom/xbl/nsXBLResourceLoader.cpp294
-rw-r--r--dom/xbl/nsXBLResourceLoader.h71
-rw-r--r--dom/xbl/nsXBLSerialize.cpp34
-rw-r--r--dom/xbl/nsXBLSerialize.h91
-rw-r--r--dom/xbl/nsXBLService.cpp1090
-rw-r--r--dom/xbl/nsXBLService.h122
-rw-r--r--dom/xbl/nsXBLWindowKeyHandler.cpp892
-rw-r--r--dom/xbl/nsXBLWindowKeyHandler.h132
-rw-r--r--dom/xbl/test/bug310107-resource.xhtml21
-rw-r--r--dom/xbl/test/chrome.ini15
-rw-r--r--dom/xbl/test/file_bug372769.xhtml181
-rw-r--r--dom/xbl/test/file_bug379959_cross.html32
-rw-r--r--dom/xbl/test/file_bug379959_data.html20
-rw-r--r--dom/xbl/test/file_bug379959_xbl.xml4
-rw-r--r--dom/xbl/test/file_bug397934.xhtml117
-rw-r--r--dom/xbl/test/file_bug481558.xbl14
-rw-r--r--dom/xbl/test/file_bug481558css.sjs17
-rw-r--r--dom/xbl/test/file_bug591198_inner.html38
-rw-r--r--dom/xbl/test/file_bug591198_xbl.xml5
-rw-r--r--dom/xbl/test/file_bug821850.xhtml299
-rw-r--r--dom/xbl/test/file_bug844783.xhtml54
-rw-r--r--dom/xbl/test/file_bug944407.html8
-rw-r--r--dom/xbl/test/file_bug944407.xml76
-rw-r--r--dom/xbl/test/file_bug946815.xhtml97
-rw-r--r--dom/xbl/test/file_bug950909.html8
-rw-r--r--dom/xbl/test/file_bug950909.xml37
-rw-r--r--dom/xbl/test/file_fieldScopeChain.xml8
-rw-r--r--dom/xbl/test/mochitest.ini43
-rw-r--r--dom/xbl/test/test_bug1086996.xhtml62
-rw-r--r--dom/xbl/test/test_bug1098628_throw_from_construct.xhtml40
-rw-r--r--dom/xbl/test/test_bug1359859.xhtml41
-rw-r--r--dom/xbl/test/test_bug310107.html53
-rw-r--r--dom/xbl/test/test_bug366770.html41
-rw-r--r--dom/xbl/test/test_bug371724.xhtml58
-rw-r--r--dom/xbl/test/test_bug372769.html34
-rw-r--r--dom/xbl/test/test_bug378518.xul83
-rw-r--r--dom/xbl/test/test_bug378866.xhtml57
-rw-r--r--dom/xbl/test/test_bug379959.html69
-rw-r--r--dom/xbl/test/test_bug389322.xhtml126
-rw-r--r--dom/xbl/test/test_bug397934.html34
-rw-r--r--dom/xbl/test/test_bug398135.xul135
-rw-r--r--dom/xbl/test/test_bug398492.xul91
-rw-r--r--dom/xbl/test/test_bug400705.xhtml48
-rw-r--r--dom/xbl/test/test_bug401907.xhtml58
-rw-r--r--dom/xbl/test/test_bug403162.xhtml57
-rw-r--r--dom/xbl/test/test_bug468210.xhtml51
-rw-r--r--dom/xbl/test/test_bug481558.html38
-rw-r--r--dom/xbl/test/test_bug526178.xhtml80
-rw-r--r--dom/xbl/test/test_bug542406.xhtml44
-rw-r--r--dom/xbl/test/test_bug591198.html46
-rw-r--r--dom/xbl/test/test_bug639338.xhtml66
-rw-r--r--dom/xbl/test/test_bug721452.xul25
-rw-r--r--dom/xbl/test/test_bug723676.xul25
-rw-r--r--dom/xbl/test/test_bug772966.xul42
-rw-r--r--dom/xbl/test/test_bug790265.xhtml55
-rw-r--r--dom/xbl/test/test_bug821850.html37
-rw-r--r--dom/xbl/test/test_bug844783.html38
-rw-r--r--dom/xbl/test/test_bug872273.xhtml53
-rw-r--r--dom/xbl/test/test_bug944407.xul44
-rw-r--r--dom/xbl/test/test_bug950909.xul36
-rw-r--r--dom/xbl/test/test_fieldScopeChain.html34
155 files changed, 18678 insertions, 0 deletions
diff --git a/dom/xbl/XBLChildrenElement.cpp b/dom/xbl/XBLChildrenElement.cpp
new file mode 100644
index 000000000..e4058a789
--- /dev/null
+++ b/dom/xbl/XBLChildrenElement.cpp
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/XBLChildrenElement.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "mozilla/dom/NodeListBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+XBLChildrenElement::~XBLChildrenElement()
+{
+}
+
+NS_IMPL_ADDREF_INHERITED(XBLChildrenElement, Element)
+NS_IMPL_RELEASE_INHERITED(XBLChildrenElement, Element)
+
+NS_INTERFACE_TABLE_HEAD(XBLChildrenElement)
+ NS_INTERFACE_TABLE_INHERITED(XBLChildrenElement, nsIDOMNode,
+ nsIDOMElement)
+ NS_ELEMENT_INTERFACE_TABLE_TO_MAP_SEGUE
+NS_INTERFACE_MAP_END_INHERITING(Element)
+
+NS_IMPL_ELEMENT_CLONE(XBLChildrenElement)
+
+nsresult
+XBLChildrenElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify)
+{
+ if (aAttribute == nsGkAtoms::includes &&
+ aNameSpaceID == kNameSpaceID_None) {
+ mIncludes.Clear();
+ }
+
+ return Element::UnsetAttr(aNameSpaceID, aAttribute, aNotify);
+}
+
+bool
+XBLChildrenElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aAttribute == nsGkAtoms::includes &&
+ aNamespaceID == kNameSpaceID_None) {
+ mIncludes.Clear();
+ nsCharSeparatedTokenizer tok(aValue, '|',
+ nsCharSeparatedTokenizer::SEPARATOR_OPTIONAL);
+ while (tok.hasMoreTokens()) {
+ mIncludes.AppendElement(NS_Atomize(tok.nextToken()));
+ }
+ }
+
+ return false;
+}
+
+} // namespace dom
+} // namespace mozilla
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsAnonymousContentList, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAnonymousContentList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAnonymousContentList)
+
+NS_INTERFACE_TABLE_HEAD(nsAnonymousContentList)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE_INHERITED(nsAnonymousContentList, nsINodeList,
+ nsIDOMNodeList)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsAnonymousContentList)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+nsAnonymousContentList::GetLength(uint32_t* aLength)
+{
+ if (!mParent) {
+ *aLength = 0;
+ return NS_OK;
+ }
+
+ uint32_t count = 0;
+ for (nsIContent* child = mParent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
+ XBLChildrenElement* point = static_cast<XBLChildrenElement*>(child);
+ if (point->HasInsertedChildren()) {
+ count += point->InsertedChildrenLength();
+ }
+ else {
+ count += point->GetChildCount();
+ }
+ }
+ else {
+ ++count;
+ }
+ }
+
+ *aLength = count;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAnonymousContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn)
+{
+ nsIContent* item = Item(aIndex);
+ if (!item) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return CallQueryInterface(item, aReturn);
+}
+
+nsIContent*
+nsAnonymousContentList::Item(uint32_t aIndex)
+{
+ if (!mParent) {
+ return nullptr;
+ }
+
+ uint32_t remIndex = aIndex;
+ for (nsIContent* child = mParent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
+ XBLChildrenElement* point = static_cast<XBLChildrenElement*>(child);
+ if (point->HasInsertedChildren()) {
+ if (remIndex < point->InsertedChildrenLength()) {
+ return point->InsertedChild(remIndex);
+ }
+ remIndex -= point->InsertedChildrenLength();
+ }
+ else {
+ if (remIndex < point->GetChildCount()) {
+ return point->GetChildAt(remIndex);
+ }
+ remIndex -= point->GetChildCount();
+ }
+ }
+ else {
+ if (remIndex == 0) {
+ return child;
+ }
+ --remIndex;
+ }
+ }
+
+ return nullptr;
+}
+
+int32_t
+nsAnonymousContentList::IndexOf(nsIContent* aContent)
+{
+ NS_ASSERTION(!aContent->NodeInfo()->Equals(nsGkAtoms::children,
+ kNameSpaceID_XBL),
+ "Looking for insertion point");
+
+ if (!mParent) {
+ return -1;
+ }
+
+ int32_t index = 0;
+ for (nsIContent* child = mParent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
+ XBLChildrenElement* point = static_cast<XBLChildrenElement*>(child);
+ if (point->HasInsertedChildren()) {
+ int32_t insIndex = point->IndexOfInsertedChild(aContent);
+ if (insIndex != -1) {
+ return index + insIndex;
+ }
+ index += point->InsertedChildrenLength();
+ }
+ else {
+ int32_t insIndex = point->IndexOf(aContent);
+ if (insIndex != -1) {
+ return index + insIndex;
+ }
+ index += point->GetChildCount();
+ }
+ }
+ else {
+ if (child == aContent) {
+ return index;
+ }
+ ++index;
+ }
+ }
+
+ return -1;
+}
+
+JSObject*
+nsAnonymousContentList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return mozilla::dom::NodeListBinding::Wrap(cx, this, aGivenProto);
+}
diff --git a/dom/xbl/XBLChildrenElement.h b/dom/xbl/XBLChildrenElement.h
new file mode 100644
index 000000000..4714da4a8
--- /dev/null
+++ b/dom/xbl/XBLChildrenElement.h
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLChildrenElement_h___
+#define nsXBLChildrenElement_h___
+
+#include "nsIDOMElement.h"
+#include "nsINodeList.h"
+#include "nsBindingManager.h"
+#include "mozilla/dom/nsXMLElement.h"
+
+class nsAnonymousContentList;
+
+namespace mozilla {
+namespace dom {
+
+class XBLChildrenElement : public nsXMLElement
+{
+public:
+ explicit XBLChildrenElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsXMLElement(aNodeInfo)
+ {
+ }
+ explicit XBLChildrenElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsXMLElement(aNodeInfo)
+ {
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsINode interface methods
+ virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
+
+ virtual nsIDOMNode* AsDOMNode() override { return this; }
+
+ // nsIContent interface methods
+ virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+ bool aNotify) override;
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+
+ void AppendInsertedChild(nsIContent* aChild)
+ {
+ mInsertedChildren.AppendElement(aChild);
+ aChild->SetXBLInsertionParent(GetParent());
+
+ // Appending an inserted child causes the inserted
+ // children to be projected instead of default content.
+ MaybeRemoveDefaultContent();
+ }
+
+ void InsertInsertedChildAt(nsIContent* aChild, uint32_t aIndex)
+ {
+ mInsertedChildren.InsertElementAt(aIndex, aChild);
+ aChild->SetXBLInsertionParent(GetParent());
+
+ // Inserting an inserted child causes the inserted
+ // children to be projected instead of default content.
+ MaybeRemoveDefaultContent();
+ }
+
+ void RemoveInsertedChild(nsIContent* aChild)
+ {
+ // Can't use this assertion as we cheat for dynamic insertions and
+ // only insert in the innermost insertion point.
+ //NS_ASSERTION(mInsertedChildren.Contains(aChild),
+ // "Removing child that's not there");
+ mInsertedChildren.RemoveElement(aChild);
+
+ // After removing the inserted child, default content
+ // may be projected into this insertion point.
+ MaybeSetupDefaultContent();
+ }
+
+ void ClearInsertedChildren()
+ {
+ for (uint32_t c = 0; c < mInsertedChildren.Length(); ++c) {
+ mInsertedChildren[c]->SetXBLInsertionParent(nullptr);
+ }
+ mInsertedChildren.Clear();
+
+ // After clearing inserted children, default content
+ // will be projected into this insertion point.
+ MaybeSetupDefaultContent();
+ }
+
+ void MaybeSetupDefaultContent()
+ {
+ if (!HasInsertedChildren()) {
+ for (nsIContent* child = static_cast<nsINode*>(this)->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ child->SetXBLInsertionParent(GetParent());
+ }
+ }
+ }
+
+ void MaybeRemoveDefaultContent()
+ {
+ if (!HasInsertedChildren()) {
+ for (nsIContent* child = static_cast<nsINode*>(this)->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ child->SetXBLInsertionParent(nullptr);
+ }
+ }
+ }
+
+ uint32_t InsertedChildrenLength()
+ {
+ return mInsertedChildren.Length();
+ }
+
+ bool HasInsertedChildren()
+ {
+ return !mInsertedChildren.IsEmpty();
+ }
+
+ int32_t IndexOfInsertedChild(nsIContent* aChild)
+ {
+ return mInsertedChildren.IndexOf(aChild);
+ }
+
+ bool Includes(nsIContent* aChild)
+ {
+ NS_ASSERTION(!mIncludes.IsEmpty(),
+ "Shouldn't check for includes on default insertion point");
+ return mIncludes.Contains(aChild->NodeInfo()->NameAtom());
+ }
+
+ bool IsDefaultInsertion()
+ {
+ return mIncludes.IsEmpty();
+ }
+
+ nsIContent* InsertedChild(uint32_t aIndex)
+ {
+ return mInsertedChildren[aIndex];
+ }
+
+protected:
+ ~XBLChildrenElement();
+
+private:
+ nsTArray<nsIContent*> mInsertedChildren; // WEAK
+ nsTArray<nsCOMPtr<nsIAtom> > mIncludes;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+class nsAnonymousContentList : public nsINodeList
+{
+public:
+ explicit nsAnonymousContentList(nsIContent* aParent)
+ : mParent(aParent)
+ {
+ MOZ_COUNT_CTOR(nsAnonymousContentList);
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsAnonymousContentList)
+ // nsIDOMNodeList interface
+ NS_DECL_NSIDOMNODELIST
+
+ // nsINodeList interface
+ virtual int32_t IndexOf(nsIContent* aContent) override;
+ virtual nsINode* GetParentObject() override { return mParent; }
+ virtual nsIContent* Item(uint32_t aIndex) override;
+
+ virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+ bool IsListFor(nsIContent* aContent) {
+ return mParent == aContent;
+ }
+
+private:
+ virtual ~nsAnonymousContentList()
+ {
+ MOZ_COUNT_DTOR(nsAnonymousContentList);
+ }
+
+ nsCOMPtr<nsIContent> mParent;
+};
+
+#endif // nsXBLChildrenElement_h___
diff --git a/dom/xbl/builtin/android/jar.mn b/dom/xbl/builtin/android/jar.mn
new file mode 100644
index 000000000..9f05c2dd6
--- /dev/null
+++ b/dom/xbl/builtin/android/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+* content/global/platformHTMLBindings.xml (platformHTMLBindings.xml)
diff --git a/dom/xbl/builtin/android/moz.build b/dom/xbl/builtin/android/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/dom/xbl/builtin/android/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/dom/xbl/builtin/android/platformHTMLBindings.xml b/dom/xbl/builtin/android/platformHTMLBindings.xml
new file mode 100644
index 000000000..03363c1b5
--- /dev/null
+++ b/dom/xbl/builtin/android/platformHTMLBindings.xml
@@ -0,0 +1,162 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<bindings id="htmlBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="inputFields" bindToUntrustedContent="true">
+ <handlers>
+#include ../input-fields-base.inc
+ <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
+
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_wordPrevious"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_wordNext"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" command="cmd_selectWordPrevious"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" command="cmd_selectWordNext"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="alt" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="alt" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift,alt" command="cmd_selectBeginLine"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,alt" command="cmd_selectEndLine"/>
+
+ <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
+ <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
+
+ <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_deleteToBeginningOfLine"/>
+ <handler event="keypress" keycode="VK_DELETE" modifiers="alt" command="cmd_deleteToEndOfLine"/>
+ </handlers>
+ </binding>
+
+ <binding id="textAreas" bindToUntrustedContent="true">
+ <handlers>
+#include ../textareas-base.inc
+ <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
+
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_wordPrevious"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_wordNext"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" command="cmd_selectWordPrevious"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" command="cmd_selectWordNext"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="alt" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="alt" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift,alt" command="cmd_selectBeginLine"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,alt" command="cmd_selectEndLine"/>
+
+ <handler event="keypress" keycode="VK_UP" modifiers="alt" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="alt" command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_UP" modifiers="shift,alt" command="cmd_selectTop"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift,alt" command="cmd_selectBottom"/>
+
+ <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="alt" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="alt" command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift,alt" command="cmd_selectTop"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift,alt" command="cmd_selectBottom"/>
+
+ <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
+ <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
+ <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop" />
+ <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom" />
+
+ <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_deleteToBeginningOfLine"/>
+ <handler event="keypress" keycode="VK_DELETE" modifiers="alt" command="cmd_deleteToEndOfLine"/>
+ </handlers>
+ </binding>
+
+ <binding id="browser">
+ <handlers>
+#include ../browser-base.inc
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectCharPrevious" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectCharNext" />
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_wordPrevious" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_wordNext" />
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control,shift" command="cmd_selectWordPrevious" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control,shift" command="cmd_selectWordNext" />
+ <handler event="keypress" keycode="VK_LEFT" modifiers="alt" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="alt" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift,alt" command="cmd_selectBeginLine"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,alt" command="cmd_selectEndLine"/>
+
+ <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectLinePrevious" />
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectLineNext" />
+ <handler event="keypress" keycode="VK_UP" modifiers="alt" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="alt" command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_UP" modifiers="shift,alt" command="cmd_selectTop"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift,alt" command="cmd_selectBottom"/>
+
+ <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="alt" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="alt" command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift,alt" command="cmd_selectTop"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift,alt" command="cmd_selectBottom"/>
+
+ <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
+ <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
+ <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop" />
+ <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom" />
+
+ <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_deleteToBeginningOfLine"/>
+ <handler event="keypress" keycode="VK_DELETE" modifiers="alt" command="cmd_deleteToEndOfLine"/>
+ </handlers>
+ </binding>
+
+ <binding id="editor">
+ <handlers>
+#include ../editor-base.inc
+ <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
+
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_wordPrevious"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_wordNext"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" command="cmd_selectWordPrevious"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" command="cmd_selectWordNext"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="alt" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="alt" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift,alt" command="cmd_selectBeginLine"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,alt" command="cmd_selectEndLine"/>
+
+ <handler event="keypress" keycode="VK_UP" modifiers="alt" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="alt" command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_UP" modifiers="shift,alt" command="cmd_selectTop"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift,alt" command="cmd_selectBottom"/>
+
+ <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="alt" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="alt" command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift,alt" command="cmd_selectTop"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift,alt" command="cmd_selectBottom"/>
+
+ <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
+ <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
+ <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop" />
+ <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom" />
+
+ <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_deleteToBeginningOfLine"/>
+ <handler event="keypress" keycode="VK_DELETE" modifiers="alt" command="cmd_deleteToEndOfLine"/>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/dom/xbl/builtin/browser-base.inc b/dom/xbl/builtin/browser-base.inc
new file mode 100644
index 000000000..03264c270
--- /dev/null
+++ b/dom/xbl/builtin/browser-base.inc
@@ -0,0 +1,14 @@
+ <handler event="keypress" key=" " modifiers="shift" command="cmd_scrollPageUp" />
+ <handler event="keypress" key=" " command="cmd_scrollPageDown" />
+
+ <handler event="keypress" keycode="VK_UP" command="cmd_moveUp" />
+ <handler event="keypress" keycode="VK_DOWN" command="cmd_moveDown" />
+ <handler event="keypress" keycode="VK_LEFT" command="cmd_moveLeft" />
+ <handler event="keypress" keycode="VK_RIGHT" command="cmd_moveRight" />
+
+ <handler event="keypress" key="x" command="cmd_cut" modifiers="accel"/>
+ <handler event="keypress" key="c" command="cmd_copy" modifiers="accel"/>
+ <handler event="keypress" key="v" command="cmd_paste" modifiers="accel"/>
+ <handler event="keypress" key="z" command="cmd_undo" modifiers="accel"/>
+ <handler event="keypress" key="z" command="cmd_redo" modifiers="accel,shift" />
+ <handler event="keypress" key="a" command="cmd_selectAll" modifiers="accel"/>
diff --git a/dom/xbl/builtin/editor-base.inc b/dom/xbl/builtin/editor-base.inc
new file mode 100644
index 000000000..1084da814
--- /dev/null
+++ b/dom/xbl/builtin/editor-base.inc
@@ -0,0 +1,19 @@
+ <handler event="keypress" key=" " modifiers="shift" command="cmd_scrollPageUp" />
+ <handler event="keypress" key=" " command="cmd_scrollPageDown" />
+
+ <handler event="keypress" keycode="VK_LEFT" command="cmd_moveLeft"/>
+ <handler event="keypress" keycode="VK_RIGHT" command="cmd_moveRight"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight"/>
+
+ <handler event="keypress" keycode="VK_UP" command="cmd_moveUp"/>
+ <handler event="keypress" keycode="VK_DOWN" command="cmd_moveDown"/>
+ <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown"/>
+
+ <handler event="keypress" key="z" command="cmd_undo" modifiers="accel"/>
+ <handler event="keypress" key="z" command="cmd_redo" modifiers="accel,shift" />
+ <handler event="keypress" key="x" command="cmd_cut" modifiers="accel"/>
+ <handler event="keypress" key="c" command="cmd_copy" modifiers="accel"/>
+ <handler event="keypress" key="v" command="cmd_paste" modifiers="accel"/>
+ <handler event="keypress" key="v" command="cmd_pasteNoFormatting" modifiers="accel,shift"/>
diff --git a/dom/xbl/builtin/emacs/jar.mn b/dom/xbl/builtin/emacs/jar.mn
new file mode 100644
index 000000000..9f05c2dd6
--- /dev/null
+++ b/dom/xbl/builtin/emacs/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+* content/global/platformHTMLBindings.xml (platformHTMLBindings.xml)
diff --git a/dom/xbl/builtin/emacs/moz.build b/dom/xbl/builtin/emacs/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/dom/xbl/builtin/emacs/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/dom/xbl/builtin/emacs/platformHTMLBindings.xml b/dom/xbl/builtin/emacs/platformHTMLBindings.xml
new file mode 100644
index 000000000..76c214ffa
--- /dev/null
+++ b/dom/xbl/builtin/emacs/platformHTMLBindings.xml
@@ -0,0 +1,237 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<bindings id="htmlBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="inputFields" bindToUntrustedContent="true">
+ <handlers>
+#include ../input-fields-base.inc
+ <!-- Emacsish single-line motion and delete keys -->
+ <handler event="keypress" key="a" modifiers="control"
+ command="cmd_beginLine"/>
+ <handler event="keypress" key="e" modifiers="control"
+ command="cmd_endLine"/>
+ <handler event="keypress" key="b" modifiers="control"
+ command="cmd_charPrevious"/>
+ <handler event="keypress" key="f" modifiers="control"
+ command="cmd_charNext"/>
+ <handler event="keypress" key="h" modifiers="control"
+ command="cmd_deleteCharBackward"/>
+ <handler event="keypress" key="d" modifiers="control"
+ command="cmd_deleteCharForward"/>
+ <handler event="keypress" key="w" modifiers="control"
+ command="cmd_deleteWordBackward"/>
+ <handler event="keypress" key="u" modifiers="control"
+ command="cmd_deleteToBeginningOfLine"/>
+ <handler event="keypress" key="k" modifiers="control"
+ command="cmd_deleteToEndOfLine"/>
+
+ <!-- Alternate Windows copy/paste/undo/redo keys -->
+ <handler event="keypress" keycode="VK_DELETE" modifiers="shift"
+ command="cmd_cutOrDelete"/>
+ <handler event="keypress" keycode="VK_DELETE" modifiers="control"
+ command="cmd_copyOrDelete"/>
+ <handler event="keypress" keycode="VK_INSERT" modifiers="control"
+ command="cmd_copy"/>
+ <handler event="keypress" keycode="VK_INSERT" modifiers="shift"
+ command="cmd_paste"/>
+
+ <!-- navigating by word keys -->
+ <handler event="keypress" keycode="VK_HOME"
+ command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_END"
+ command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift"
+ command="cmd_selectBeginLine"/>
+ <handler event="keypress" keycode="VK_END" modifiers="shift"
+ command="cmd_selectEndLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="control"
+ command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_END" modifiers="control"
+ command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="control,shift"
+ command="cmd_selectBeginLine"/>
+ <handler event="keypress" keycode="VK_END" modifiers="control,shift"
+ command="cmd_selectEndLine"/>
+ <handler event="keypress" keycode="VK_BACK" modifiers="control"
+ command="cmd_deleteWordBackward"/>
+
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control"
+ command="cmd_wordPrevious"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control"
+ command="cmd_wordNext"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control"
+ command="cmd_selectWordPrevious"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control"
+ command="cmd_selectWordNext"/>
+ <handler event="keypress" key="y" modifiers="accel"
+ command="cmd_redo"/>
+ <handler event="keypress" key="a" modifiers="alt"
+ command="cmd_selectAll"/>
+ </handlers>
+ </binding>
+
+ <binding id="textAreas" bindToUntrustedContent="true">
+ <handlers>
+#include ../textareas-base.inc
+ <!-- Emacsish single-line motion and delete keys -->
+ <handler event="keypress" key="a" modifiers="control"
+ command="cmd_beginLine"/>
+ <handler event="keypress" key="e" modifiers="control"
+ command="cmd_endLine"/>
+ <handler event="keypress" id="key_left" key="b" modifiers="control"
+ command="cmd_charPrevious"/>
+ <handler event="keypress" id="key_right" key="f" modifiers="control"
+ command="cmd_charNext"/>
+ <handler event="keypress" id="key_delback" key="h" modifiers="control"
+ command="cmd_deleteCharBackward"/>
+ <handler event="keypress" id="key_delforw" key="d" modifiers="control"
+ command="cmd_deleteCharForward"/>
+ <handler event="keypress" id="key_delwback" key="w" modifiers="control"
+ command="cmd_deleteWordBackward"/>
+ <handler event="keypress" id="key_del_bol" key="u" modifiers="control"
+ command="cmd_deleteToBeginningOfLine"/>
+ <handler event="keypress" id="key_del_eol" key="k" modifiers="control"
+ command="cmd_deleteToEndOfLine"/>
+
+ <!-- Alternate Windows copy/paste/undo/redo keys -->
+ <handler event="keypress" keycode="VK_DELETE" modifiers="shift"
+ command="cmd_cutOrDelete"/>
+ <handler event="keypress" keycode="VK_DELETE" modifiers="control"
+ command="cmd_copyOrDelete"/>
+ <handler event="keypress" keycode="VK_INSERT" modifiers="control"
+ command="cmd_copy"/>
+ <handler event="keypress" keycode="VK_INSERT" modifiers="shift"
+ command="cmd_paste"/>
+
+ <!-- Emacsish multi-line motion and delete keys -->
+ <handler event="keypress" id="key_linedown" key="n" modifiers="control"
+ command="cmd_lineNext"/>
+ <handler event="keypress" id="key_lineup" key="p" modifiers="control"
+ command="cmd_linePrevious"/>
+
+ <!-- handle home/end/arrow keys and redo -->
+ <handler event="keypress" keycode="VK_HOME"
+ command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_END"
+ command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift"
+ command="cmd_selectBeginLine"/>
+ <handler event="keypress" keycode="VK_END" modifiers="shift"
+ command="cmd_selectEndLine"/>
+
+ <handler event="keypress" keycode="VK_HOME" modifiers="control"
+ command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="control"
+ command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift,control"
+ command="cmd_selectTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="shift,control"
+ command="cmd_selectBottom"/>
+
+ <handler event="keypress" keycode="VK_PAGE_UP"
+ command="cmd_movePageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN"
+ command="cmd_movePageDown"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift"
+ command="cmd_selectPageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift"
+ command="cmd_selectPageDown"/>
+
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control"
+ command="cmd_wordPrevious"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control"
+ command="cmd_wordNext"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control"
+ command="cmd_selectWordPrevious"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control"
+ command="cmd_selectWordNext"/>
+ <handler event="keypress" keycode="VK_BACK" modifiers="control"
+ command="cmd_deleteWordBackward"/>
+ <handler event="keypress" key="y" modifiers="accel"
+ command="cmd_redo"/>
+ <handler event="keypress" key="a" modifiers="alt"
+ command="cmd_selectAll"/>
+ </handlers>
+ </binding>
+
+ <binding id="browser">
+ <handlers>
+#include ../browser-base.inc
+ <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
+
+ <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cut" />
+ <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_copy" />
+ <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy" />
+ <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop" />
+ <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom" />
+
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_wordPrevious" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_wordNext" />
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control,shift" command="cmd_selectWordPrevious" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control,shift" command="cmd_selectWordNext" />
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectCharPrevious" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectCharNext" />
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
+ <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
+ <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectLinePrevious" />
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectLineNext" />
+ <handler event="keypress" key="a" modifiers="alt" command="cmd_selectAll"/>
+ </handlers>
+ </binding>
+
+ <binding id="editor">
+ <handlers>
+#include ../editor-base.inc
+ <handler event="keypress" key="h" modifiers="control" command="cmd_deleteCharBackward"/>
+ <handler event="keypress" key="d" modifiers="control" command="cmd_deleteCharForward"/>
+ <handler event="keypress" key="k" modifiers="control" command="cmd_deleteToEndOfLine"/>
+ <handler event="keypress" key="u" modifiers="control" command="cmd_deleteToBeginningOfLine"/>
+ <handler event="keypress" key="a" modifiers="control" command="cmd_beginLine"/>
+ <handler event="keypress" key="e" modifiers="control" command="cmd_endLine"/>
+ <handler event="keypress" key="b" modifiers="control" command="cmd_charPrevious"/>
+ <handler event="keypress" key="f" modifiers="control" command="cmd_charNext"/>
+ <handler event="keypress" key="p" modifiers="control" command="cmd_linePrevious"/>
+ <handler event="keypress" key="n" modifiers="control" command="cmd_lineNext"/>
+ <handler event="keypress" key="x" modifiers="control" command="cmd_cut"/>
+ <handler event="keypress" key="c" modifiers="control" command="cmd_copy"/>
+ <handler event="keypress" key="v" modifiers="control" command="cmd_paste"/>
+ <handler event="keypress" key="z" modifiers="control" command="cmd_undo"/>
+ <handler event="keypress" key="y" modifiers="accel" command="cmd_redo"/>
+ <handler event="keypress" key="a" modifiers="alt" command="cmd_selectAll"/>
+ <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cutOrDelete"/>
+ <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_copyOrDelete"/>
+ <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy"/>
+ <handler event="keypress" keycode="VK_INSERT" modifiers="shift" command="cmd_paste"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_wordPrevious"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_wordNext"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" command="cmd_selectWordPrevious"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" command="cmd_selectWordNext"/>
+ <handler event="keypress" keycode="VK_BACK" modifiers="control" command="cmd_deleteWordBackward"/>
+ <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine"/>
+ <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/dom/xbl/builtin/input-fields-base.inc b/dom/xbl/builtin/input-fields-base.inc
new file mode 100644
index 000000000..a19686291
--- /dev/null
+++ b/dom/xbl/builtin/input-fields-base.inc
@@ -0,0 +1,17 @@
+ <handler event="keypress" keycode="VK_LEFT" command="cmd_moveLeft"/>
+ <handler event="keypress" keycode="VK_RIGHT" command="cmd_moveRight"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight"/>
+
+ <handler event="keypress" keycode="VK_UP" command="cmd_moveUp"/>
+ <handler event="keypress" keycode="VK_DOWN" command="cmd_moveDown"/>
+ <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown"/>
+
+ <!-- Cut/copy/paste/undo -->
+ <handler event="keypress" key="c" modifiers="accel" command="cmd_copy"/>
+ <handler event="keypress" key="x" modifiers="accel" command="cmd_cut"/>
+ <handler event="keypress" key="v" modifiers="accel" command="cmd_paste"/>
+ <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
+ <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo" />
+
diff --git a/dom/xbl/builtin/mac/jar.mn b/dom/xbl/builtin/mac/jar.mn
new file mode 100644
index 000000000..9f05c2dd6
--- /dev/null
+++ b/dom/xbl/builtin/mac/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+* content/global/platformHTMLBindings.xml (platformHTMLBindings.xml)
diff --git a/dom/xbl/builtin/mac/moz.build b/dom/xbl/builtin/mac/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/dom/xbl/builtin/mac/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/dom/xbl/builtin/mac/platformHTMLBindings.xml b/dom/xbl/builtin/mac/platformHTMLBindings.xml
new file mode 100644
index 000000000..b70592399
--- /dev/null
+++ b/dom/xbl/builtin/mac/platformHTMLBindings.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<bindings id="htmlBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="inputFields" bindToUntrustedContent="true">
+ <handlers>
+ <handler event="keypress" key="c" modifiers="accel" command="cmd_copy"/>
+ <handler event="keypress" key="x" modifiers="accel" command="cmd_cut"/>
+ <handler event="keypress" key="v" modifiers="accel" command="cmd_paste"/>
+ <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
+ <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo"/>
+ <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
+ </handlers>
+ </binding>
+
+ <binding id="textAreas" bindToUntrustedContent="true">
+ <handlers>
+ <handler event="keypress" key="c" modifiers="accel" command="cmd_copy"/>
+ <handler event="keypress" key="x" modifiers="accel" command="cmd_cut"/>
+ <handler event="keypress" key="v" modifiers="accel" command="cmd_paste"/>
+ <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
+ <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo"/>
+ <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
+ </handlers>
+ </binding>
+
+ <binding id="browser">
+ <handlers>
+#include ../browser-base.inc
+ <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_scrollPageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_scrollPageDown"/>
+ <handler event="keypress" keycode="VK_HOME" command="cmd_scrollTop" />
+ <handler event="keypress" keycode="VK_END" command="cmd_scrollBottom" />
+
+ <handler event="keypress" keycode="VK_LEFT" modifiers="alt" command="cmd_moveLeft2" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="alt" command="cmd_moveRight2" />
+ <handler event="keypress" keycode="VK_LEFT" modifiers="alt,shift" command="cmd_selectLeft2" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="alt,shift" command="cmd_selectRight2" />
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight" />
+ <handler event="keypress" keycode="VK_UP" modifiers="alt,shift" command="cmd_selectUp2" />
+ <handler event="keypress" keycode="VK_DOWN" modifiers="alt,shift" command="cmd_selectDown2" />
+ <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp" />
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown" />
+ <handler event="keypress" keycode="VK_UP" modifiers="accel" command="cmd_moveUp2"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="accel" command="cmd_moveDown2"/>
+ </handlers>
+ </binding>
+
+ <binding id="editor">
+ <handlers>
+ <handler event="keypress" key=" " modifiers="shift" command="cmd_scrollPageUp" />
+ <handler event="keypress" key=" " command="cmd_scrollPageDown" />
+
+ <handler event="keypress" key="z" command="cmd_undo" modifiers="accel"/>
+ <handler event="keypress" key="z" command="cmd_redo" modifiers="accel,shift" />
+ <handler event="keypress" key="x" command="cmd_cut" modifiers="accel"/>
+ <handler event="keypress" key="c" command="cmd_copy" modifiers="accel"/>
+ <handler event="keypress" key="v" command="cmd_paste" modifiers="accel"/>
+ <handler event="keypress" key="v" command="cmd_pasteNoFormatting" modifiers="accel,shift"/>
+ <handler event="keypress" key="a" command="cmd_selectAll" modifiers="accel"/>
+ <handler event="keypress" key="v" command="cmd_pasteNoFormatting" modifiers="accel,alt,shift"/>
+ </handlers>
+ </binding>
+
+</bindings>
diff --git a/dom/xbl/builtin/moz.build b/dom/xbl/builtin/moz.build
new file mode 100644
index 000000000..09574b363
--- /dev/null
+++ b/dom/xbl/builtin/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ DIRS += ['win']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ DIRS += ['mac']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+ DIRS += ['android']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
+ DIRS += ['unix']
+else:
+ DIRS += ['emacs']
+
diff --git a/dom/xbl/builtin/textareas-base.inc b/dom/xbl/builtin/textareas-base.inc
new file mode 100644
index 000000000..7c52359d7
--- /dev/null
+++ b/dom/xbl/builtin/textareas-base.inc
@@ -0,0 +1,16 @@
+ <handler event="keypress" keycode="VK_LEFT" command="cmd_moveLeft"/>
+ <handler event="keypress" keycode="VK_RIGHT" command="cmd_moveRight"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight"/>
+
+ <handler event="keypress" keycode="VK_UP" command="cmd_moveUp"/>
+ <handler event="keypress" keycode="VK_DOWN" command="cmd_moveDown"/>
+ <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown"/>
+
+ <!-- Cut/copy/paste/undo -->
+ <handler event="keypress" key="c" modifiers="accel" command="cmd_copy"/>
+ <handler event="keypress" key="x" modifiers="accel" command="cmd_cut"/>
+ <handler event="keypress" key="v" modifiers="accel" command="cmd_paste"/>
+ <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
+ <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo" />
diff --git a/dom/xbl/builtin/unix/jar.mn b/dom/xbl/builtin/unix/jar.mn
new file mode 100644
index 000000000..9f05c2dd6
--- /dev/null
+++ b/dom/xbl/builtin/unix/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+* content/global/platformHTMLBindings.xml (platformHTMLBindings.xml)
diff --git a/dom/xbl/builtin/unix/moz.build b/dom/xbl/builtin/unix/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/dom/xbl/builtin/unix/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/dom/xbl/builtin/unix/platformHTMLBindings.xml b/dom/xbl/builtin/unix/platformHTMLBindings.xml
new file mode 100644
index 000000000..75645f1f6
--- /dev/null
+++ b/dom/xbl/builtin/unix/platformHTMLBindings.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<bindings id="htmlBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="inputFields" bindToUntrustedContent="true">
+ <handlers>
+#include ../input-fields-base.inc
+ <handler event="keypress" key="a" modifiers="alt"
+ command="cmd_selectAll"/>
+ <handler event="keypress" key="y" modifiers="accel"
+ command="cmd_redo"/>
+ <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo"/>
+ <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
+ </handlers>
+ </binding>
+
+ <binding id="textAreas" bindToUntrustedContent="true">
+ <handlers>
+#include ../textareas-base.inc
+ <handler event="keypress" key="a" modifiers="alt"
+ command="cmd_selectAll"/>
+ <handler event="keypress" key="y" modifiers="accel"
+ command="cmd_redo"/>
+ <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
+ <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo"/>
+ </handlers>
+ </binding>
+
+ <binding id="browser">
+ <handlers>
+#include ../browser-base.inc
+ <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
+
+ <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cut" />
+ <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_copy" />
+ <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy" />
+ <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
+ <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
+ <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop" />
+ <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom" />
+
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight" />
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_moveLeft2" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_moveRight2" />
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control,shift" command="cmd_selectLeft2" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control,shift" command="cmd_selectRight2" />
+
+ <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp" />
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown" />
+ <handler event="keypress" keycode="VK_UP" modifiers="control" command="cmd_moveUp2" />
+ <handler event="keypress" keycode="VK_DOWN" modifiers="control" command="cmd_moveDown2" />
+ <handler event="keypress" keycode="VK_UP" modifiers="control,shift" command="cmd_selectUp2" />
+ <handler event="keypress" keycode="VK_DOWN" modifiers="control,shift" command="cmd_selectDown2" />
+
+ <handler event="keypress" key="a" modifiers="alt" command="cmd_selectAll"/>
+ </handlers>
+ </binding>
+
+ <binding id="editor">
+ <handlers>
+#include ../editor-base.inc
+ <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
+ <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo"/>
+ <handler event="keypress" key="y" modifiers="accel" command="cmd_redo"/>
+ <handler event="keypress" key="a" modifiers="alt" command="cmd_selectAll"/>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/dom/xbl/builtin/win/jar.mn b/dom/xbl/builtin/win/jar.mn
new file mode 100644
index 000000000..9f05c2dd6
--- /dev/null
+++ b/dom/xbl/builtin/win/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+* content/global/platformHTMLBindings.xml (platformHTMLBindings.xml)
diff --git a/dom/xbl/builtin/win/moz.build b/dom/xbl/builtin/win/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/dom/xbl/builtin/win/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/dom/xbl/builtin/win/platformHTMLBindings.xml b/dom/xbl/builtin/win/platformHTMLBindings.xml
new file mode 100644
index 000000000..1612138e1
--- /dev/null
+++ b/dom/xbl/builtin/win/platformHTMLBindings.xml
@@ -0,0 +1,164 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<bindings id="htmlBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="inputFields" bindToUntrustedContent="true">
+ <handlers>
+#include ../input-fields-base.inc
+ <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine"/>
+ <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
+
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_moveLeft2"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_moveRight2"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" command="cmd_selectLeft2"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" command="cmd_selectRight2"/>
+
+ <handler event="keypress" keycode="VK_UP" modifiers="control" command="cmd_moveUp2"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="control" command="cmd_moveDown2"/>
+ <handler event="keypress" keycode="VK_UP" modifiers="shift,control" command="cmd_selectUp2"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift,control" command="cmd_selectDown2"/>
+
+ <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cutOrDelete"/>
+ <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_deleteWordForward"/>
+ <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy"/>
+ <handler event="keypress" keycode="VK_INSERT" modifiers="shift" command="cmd_paste"/>
+
+ <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_undo"/>
+ <handler event="keypress" keycode="VK_BACK" modifiers="alt,shift" command="cmd_redo"/>
+ <handler event="keypress" keycode="VK_BACK" modifiers="control" command="cmd_deleteWordBackward"/>
+
+ <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
+ <handler event="keypress" key="y" modifiers="accel" command="cmd_redo"/>
+ </handlers>
+ </binding>
+
+ <binding id="textAreas" bindToUntrustedContent="true">
+ <handlers>
+#include ../textareas-base.inc
+ <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine"/>
+ <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
+
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_moveLeft2"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_moveRight2"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift,control" command="cmd_selectLeft2"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,control" command="cmd_selectRight2"/>
+
+ <handler event="keypress" keycode="VK_UP" modifiers="control" command="cmd_moveUp2"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="control" command="cmd_moveDown2"/>
+ <handler event="keypress" keycode="VK_UP" modifiers="shift,control" command="cmd_selectUp2"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift,control" command="cmd_selectDown2"/>
+
+ <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
+
+ <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cutOrDelete"/>
+ <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_deleteWordForward"/>
+ <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy"/>
+ <handler event="keypress" keycode="VK_INSERT" modifiers="shift" command="cmd_paste"/>
+
+ <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_undo"/>
+ <handler event="keypress" keycode="VK_BACK" modifiers="alt,shift" command="cmd_redo"/>
+ <handler event="keypress" keycode="VK_BACK" modifiers="control" command="cmd_deleteWordBackward"/>
+
+ <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
+ <handler event="keypress" key="y" modifiers="accel" command="cmd_redo"/>
+ </handlers>
+ </binding>
+
+ <binding id="browser">
+ <handlers>
+#include ../browser-base.inc
+ <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
+
+ <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cut"/>
+ <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_deleteWordForward"/>
+ <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy"/>
+ <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop" />
+ <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom" />
+
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control" command="cmd_moveLeft2" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control" command="cmd_moveRight2" />
+ <handler event="keypress" keycode="VK_LEFT" modifiers="control,shift" command="cmd_selectLeft2" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="control,shift" command="cmd_selectRight2" />
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight" />
+
+ <handler event="keypress" keycode="VK_UP" modifiers="control" command="cmd_moveUp2" />
+ <handler event="keypress" keycode="VK_DOWN" modifiers="control" command="cmd_moveDown2" />
+ <handler event="keypress" keycode="VK_UP" modifiers="control,shift" command="cmd_selectUp2" />
+ <handler event="keypress" keycode="VK_DOWN" modifiers="control,shift" command="cmd_selectDown2" />
+ <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp" />
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown" />
+
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift" command="cmd_selectBeginLine" />
+ <handler event="keypress" keycode="VK_END" modifiers="shift" command="cmd_selectEndLine" />
+ <handler event="keypress" key="y" modifiers="accel" command="cmd_redo"/>
+ </handlers>
+ </binding>
+
+ <binding id="editor">
+ <handlers>
+#include ../editor-base.inc
+ <handler event="keypress" key="a" command="cmd_selectAll" modifiers="accel"/>
+ <handler event="keypress" keycode="VK_DELETE" modifiers="shift" command="cmd_cutOrDelete"/>
+ <handler event="keypress" keycode="VK_DELETE" modifiers="control" command="cmd_deleteWordForward"/>
+ <handler event="keypress" keycode="VK_INSERT" modifiers="control" command="cmd_copy"/>
+ <handler event="keypress" keycode="VK_INSERT" modifiers="shift" command="cmd_paste"/>
+ <handler event="keypress" keycode="VK_BACK" modifiers="alt" command="cmd_undo"/>
+ <handler event="keypress" keycode="VK_BACK" modifiers="alt,shift" command="cmd_redo"/>
+
+ <handler event="keypress" keycode="VK_LEFT" modifiers="accel" command="cmd_moveLeft2"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="accel" command="cmd_moveRight2"/>
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift,accel" command="cmd_selectLeft2"/>
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift,accel" command="cmd_selectRight2"/>
+
+ <handler event="keypress" keycode="VK_UP" modifiers="accel" command="cmd_moveUp2"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="accel" command="cmd_moveDown2"/>
+ <handler event="keypress" keycode="VK_UP" modifiers="shift,accel" command="cmd_selectUp2"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift,accel" command="cmd_selectDown2"/>
+
+ <handler event="keypress" keycode="VK_HOME" modifiers="shift,control" command="cmd_selectTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="shift,control" command="cmd_selectBottom"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="control" command="cmd_moveTop"/>
+ <handler event="keypress" keycode="VK_END" modifiers="control" command="cmd_moveBottom"/>
+ <handler event="keypress" keycode="VK_BACK" modifiers="control" command="cmd_deleteWordBackward"/>
+
+ <handler event="keypress" keycode="VK_HOME" command="cmd_beginLine"/>
+ <handler event="keypress" keycode="VK_END" command="cmd_endLine"/>
+ <handler event="keypress" keycode="VK_HOME" command="cmd_selectBeginLine" modifiers="shift"/>
+ <handler event="keypress" keycode="VK_END" command="cmd_selectEndLine" modifiers="shift"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_movePageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_movePageDown"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="shift" command="cmd_selectPageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="shift" command="cmd_selectPageDown"/>
+ <handler event="keypress" key="y" modifiers="accel" command="cmd_redo"/>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/dom/xbl/crashtests/205735-1.xhtml b/dom/xbl/crashtests/205735-1.xhtml
new file mode 100644
index 000000000..bfc42773b
--- /dev/null
+++ b/dom/xbl/crashtests/205735-1.xhtml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Bug 205735</title>
+<xbl:bindings xmlns:xbl="http://www.mozilla.org/xbl" xmlns="http://www.mozilla.org/xbl">
+<binding id="rd">
+<xbl:content xmlns="http://www.w3.org/1999/xhtml" xmlns:xbl="http://www.mozilla.org/xbl"><div><div>binding content<xbl:children/></div></div></xbl:content>
+ <implementation>
+ <constructor>
+ <![CDATA[
+ var x=document.getAnonymousNodes(this)[0];
+ x.removeChild(x.childNodes[0]);
+ ]]>
+ </constructor>
+ </implementation>
+</binding>
+</xbl:bindings>
+<style type="text/css">
+.chapter{-moz-binding:url(#rd);}
+</style>
+</head>
+<body>
+<div class="chapter">test</div>
+</body>
+</html>
diff --git a/dom/xbl/crashtests/223799-1.xul b/dom/xbl/crashtests/223799-1.xul
new file mode 100644
index 000000000..455ac9e05
--- /dev/null
+++ b/dom/xbl/crashtests/223799-1.xul
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ >
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="foo">
+ <content><xul:box><xul:box><children/></xul:box></xul:box></content>
+ </binding>
+ <implementation>
+ <field name="box">document.getAnonymousNodes(this)[0]</field>
+ </implementation>
+ </bindings>
+ <html:style type="text/css">
+ <!-- CSS style rules for XBL bindings go here -->
+ <![CDATA[
+foo {
+ -moz-binding: url("#foo");
+}
+ ]]>
+ </html:style>
+ <!-- XUL application goes here -->
+
+ <foo><description>World</description></foo>
+</window>
diff --git a/dom/xbl/crashtests/226744-1.xhtml b/dom/xbl/crashtests/226744-1.xhtml
new file mode 100644
index 000000000..63915dbe8
--- /dev/null
+++ b/dom/xbl/crashtests/226744-1.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <xbl:bindings>
+ <xbl:binding id="rd">
+ <xbl:resources>
+<xbl:stylesheet src="data:text/html;charset=utf-8,"/>
+</xbl:resources>
+ <xbl:content>
+<xbl:children/>
+ </xbl:content>
+ </xbl:binding>
+
+ </xbl:bindings>
+
+ <head>
+ <title>testcase bug ?</title>
+
+ <style type="text/css">
+ .hoofdstuk {
+ -moz-binding: url(#rd);
+ }
+ </style>
+ </head>
+
+ <body>
+<div class="hoofdstuk">test
+</div>
+<div class="hoofdstuk">test
+</div>
+ </body>
+
+</html>
diff --git a/dom/xbl/crashtests/232095-1.xul b/dom/xbl/crashtests/232095-1.xul
new file mode 100644
index 000000000..bfd5476e6
--- /dev/null
+++ b/dom/xbl/crashtests/232095-1.xul
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ >
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="serverpost_base">
+ <implementation>
+ <method name="getSuccessfulControls">
+ <parameter name="aNode"/>
+ <body><![CDATA[
+ dump("<html:input type="file"/> has not been tested yet! This may not work!!!\n");
+ ]]></body>
+ </method>
+
+ <method name='finalizeAndSubmit'>
+ <body>
+ return true;
+ </body>
+ </method>
+ </implementation>
+ </binding>
+</bindings>
+
+ <html:style type="text/css"><![CDATA[
+ @namespace xbl url("http://www.mozilla.org/xbl");
+ @namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+ xbl|bindings {
+ display: none;
+ }
+
+ xul|serverpost {
+ -moz-binding: url("#serverpost_base");
+ }
+
+ ]]></html:style>
+
+ <serverpost/>
+ <serverpost/>
+</window>
diff --git a/dom/xbl/crashtests/277523-1.xhtml b/dom/xbl/crashtests/277523-1.xhtml
new file mode 100644
index 000000000..74dc5d808
--- /dev/null
+++ b/dom/xbl/crashtests/277523-1.xhtml
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xbl="http://www.mozilla.org/xbl">
+<head>
+<title>Testcase bug 277208 - appendChild to a hidden box from a binding crashes</title>
+
+<xbl:bindings><xbl:binding id="crash"><xbl:content>
+<span style="display:none"><xbl:children includes="span"/></span>
+</xbl:content></xbl:binding></xbl:bindings>
+
+<style type="text/css">
+#test{-moz-binding:url(#crash);}
+</style>
+</head>
+<body onload="init()">
+<span id="test"></span>
+<script>
+function init(){
+ document.getElementById('test').appendChild(document.createElement('span'));
+}
+</script>
+</body>
+</html>
diff --git a/dom/xbl/crashtests/277950-1.xhtml b/dom/xbl/crashtests/277950-1.xhtml
new file mode 100644
index 000000000..eae6f1574
--- /dev/null
+++ b/dom/xbl/crashtests/277950-1.xhtml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Testcase</title>
+<xbl:bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+ <binding id="test">
+ <implementation>
+ <property/>
+ </implementation>
+ </binding>
+</xbl:bindings>
+<style type="text/css">
+ p{ -moz-binding: url(#test); }
+</style>
+</head>
+<body>
+<p></p>
+</body>
+</html>
diff --git a/dom/xbl/crashtests/336744-1-inner.html b/dom/xbl/crashtests/336744-1-inner.html
new file mode 100644
index 000000000..4af1fbd52
--- /dev/null
+++ b/dom/xbl/crashtests/336744-1-inner.html
@@ -0,0 +1,6 @@
+<html><head>
+<title>Testcase bug 336744 - Crash when window gets destroyed during popuphiding event</title>
+</head><body>
+This should not crash Mozilla within 1 second<br>
+<iframe src="data:application/vnd.mozilla.xul+xml;charset=utf-8,%3C%3Fxml%20version%3D%221.0%22%3F%3E%0A%3C%3Fxml-stylesheet%20href%3D%22chrome%3A//global/skin%22%20type%3D%22text/css%22%3F%3E%0A%0A%3Cwindow%20xmlns%3Ahtml%3D%22http%3A//www.w3.org/1999/xhtml%22%0A%20%20%20%20%20%20%20%20xmlns%3D%22http%3A//www.mozilla.org/keymaster/gatekeeper/there.is.only.xul%22%3E%0A%3Chtml%3Ascript%3E%0Adocument.addEventListener%28%22popuphiding%22%2C%20doe%2C%20true%29%3B%0Afunction%20doe%28e%29%20%7B%0Avar%20x%3D%20parent.document.getElementsByTagName%28%27iframe%27%29%5B0%5D%3B%0Ax.parentNode.removeChild%28x%29%3B%0A%7D%0AsetTimeout%28function%28%29%20%7B%20document.getElementsByTagName%28%22menupopup%22%29%5B0%5D.showPopup%28%29%3B%20%7D%2C%20800%29%3B%0AsetTimeout%28function%28%29%20%7B%20document.getElementsByTagName%28%22menupopup%22%29%5B0%5D.hidePopup%28%29%3B%20%7D%2C%201000%29%3B%0A%3C/html%3Ascript%3E%0A%3Cscript%3E%0A%0A%3C/script%3E%0A%3Cmenupopup%3E%0A%20%20%3Cmenuitem%20label%3D%22One%22/%3E%0A%3C/menupopup%3E%0A%3C/window%3E%0A"></iframe>
+</body></html> \ No newline at end of file
diff --git a/dom/xbl/crashtests/336744-1.html b/dom/xbl/crashtests/336744-1.html
new file mode 100644
index 000000000..48d5c1701
--- /dev/null
+++ b/dom/xbl/crashtests/336744-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="336744-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/dom/xbl/crashtests/336960-1-inner.xhtml b/dom/xbl/crashtests/336960-1-inner.xhtml
new file mode 100644
index 000000000..2064f8977
--- /dev/null
+++ b/dom/xbl/crashtests/336960-1-inner.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <script>
+ var x=document.createElementNS('http://www.w3.org/1999/xhtml','style');
+ document.documentElement.appendChild(x);
+ function doe(){
+ var y=document.getElementsByTagName('style')[0];
+ }
+ setTimeout(doe,500);
+ </script>
+
+ <style>
+ style {-moz-binding:url(#randomxbl);}
+ </style>
+
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="randomxbl">
+ <implementation>
+ <constructor>
+ var x= parent.document.getElementsByTagName('iframe')[0];
+x.parentNode.removeChild(x);
+ </constructor>
+ </implementation>
+ </binding>
+ </bindings>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/xbl/crashtests/336960-1.html b/dom/xbl/crashtests/336960-1.html
new file mode 100644
index 000000000..0263d3095
--- /dev/null
+++ b/dom/xbl/crashtests/336960-1.html
@@ -0,0 +1,13 @@
+<html class="reftest-wait">
+<head>
+
+<title>Testcase bug 336960 - Crash when window gets destroyed when constructor of xbl is running</title>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+</head><body>
+This page should not crash Mozilla, you should see no iframe<br>
+<iframe src="./336960-1-inner.xhtml"></iframe>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/xbl/crashtests/342954-1.xhtml b/dom/xbl/crashtests/342954-1.xhtml
new file mode 100644
index 000000000..dbaa15384
--- /dev/null
+++ b/dom/xbl/crashtests/342954-1.xhtml
@@ -0,0 +1,46 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xbl="http://www.mozilla.org/xbl">
+
+
+<head>
+
+<script>
+<![CDATA[
+
+function boo()
+{
+ s1 = document.getElementById("s1");
+ marq = document.getElementById("marq");
+ marqAnonymousSomething = document.getAnonymousNodes(marq)[0].childNodes[0];
+
+ removeNode(marqAnonymousSomething);
+ s1.appendChild(document.createElement("div"));
+}
+
+function removeNode(q1) { q1.parentNode.removeChild(q1); }
+
+]]>
+</script>
+
+<xbl:bindings id="marqueeBindings">
+ <xbl:binding id="marquee-horizontal-12">
+ <xbl:content>
+ <div>
+ <xbl:children/>
+ </div>
+ </xbl:content>
+ </xbl:binding>
+</xbl:bindings>
+
+</head>
+
+
+<body onload="boo()">
+
+<span id="s1">Span</span>
+
+<div id="marq" style="-moz-binding: url('#marquee-horizontal-12');">Marquee</div>
+
+</body>
+
+
+</html>
diff --git a/dom/xbl/crashtests/342954-2-xbl.xml b/dom/xbl/crashtests/342954-2-xbl.xml
new file mode 100644
index 000000000..3e73f1355
--- /dev/null
+++ b/dom/xbl/crashtests/342954-2-xbl.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+
+<bindings id="marqueeBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="marquee-horizontal-10">
+
+ <content>
+ <html:div>
+ <children/>
+ </html:div>
+ </content>
+
+ </binding>
+
+</bindings>
diff --git a/dom/xbl/crashtests/342954-2.xhtml b/dom/xbl/crashtests/342954-2.xhtml
new file mode 100644
index 000000000..4a250e31c
--- /dev/null
+++ b/dom/xbl/crashtests/342954-2.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+<![CDATA[
+
+function boo()
+{
+ s1 = document.getElementById("s1");
+ marq = document.getElementById("marq");
+ marqAnonymousSomething = document.getAnonymousNodes(marq)[0].childNodes[0];
+
+ removeNode(marqAnonymousSomething);
+ s1.appendChild(document.createElement("div"));
+}
+
+function removeNode(q1) { q1.parentNode.removeChild(q1); }
+
+]]>
+</script>
+</head>
+
+<body onload="boo()">
+
+<span id="s1">Span</span>
+
+<div id="marq" style="-moz-binding: url('342954-2-xbl.xml#marquee-horizontal-10');">Marquee</div>
+
+</body>
+</html>
diff --git a/dom/xbl/crashtests/368276-1.xhtml b/dom/xbl/crashtests/368276-1.xhtml
new file mode 100644
index 000000000..162494b2c
--- /dev/null
+++ b/dom/xbl/crashtests/368276-1.xhtml
@@ -0,0 +1,33 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="reftest-wait">
+
+<head>
+<script>
+
+function boom()
+{
+ var quote = document.getElementById("quote");
+ var para = document.getElementById("para");
+ var dialog = document.getElementById("dialog");
+ var e = document.getElementById("e");
+
+ dialog.appendChild(quote);
+ e.insertBefore(quote, para);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30)">
+
+<p>He said <q id="quote">J<xul:hbox/></q></p>
+
+<xul:dialog id="dialog"/>
+
+<div id="e"><p id="para">Foopy</p></div>
+
+</body>
+</html>
diff --git a/dom/xbl/crashtests/368641-1.xhtml b/dom/xbl/crashtests/368641-1.xhtml
new file mode 100644
index 000000000..0b42ac813
--- /dev/null
+++ b/dom/xbl/crashtests/368641-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<head>
+<script>
+
+function boom()
+{
+ var yaz = document.getElementById("yaz");
+
+ document.body.appendChild(yaz);
+}
+
+</script>
+</head>
+
+<body onload="boom()">
+
+ <xul:tabs><span id="yaz"><xul:hbox><xul:popup/><xul:template/></xul:hbox></span></xul:tabs>
+</body>
+
+</html>
diff --git a/dom/xbl/crashtests/378521-1.xhtml b/dom/xbl/crashtests/378521-1.xhtml
new file mode 100644
index 000000000..3db89d49a
--- /dev/null
+++ b/dom/xbl/crashtests/378521-1.xhtml
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml">
+<head>
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="foo" extends="html:input">
+ <content>
+ Foo?
+ <children/>
+ </content>
+ </binding>
+</bindings>
+</head>
+
+<body>
+<div>Hi!</div>
+<div style="-moz-binding: url(#foo)">XXX</div>
+</body>
+
+</html> \ No newline at end of file
diff --git a/dom/xbl/crashtests/382376-1.xhtml b/dom/xbl/crashtests/382376-1.xhtml
new file mode 100644
index 000000000..55457f79e
--- /dev/null
+++ b/dom/xbl/crashtests/382376-1.xhtml
@@ -0,0 +1,37 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+
+function boom1()
+{
+ var c = document.getElementById("c");
+ c.style.MozBinding = "url('#foo')";
+
+ setTimeout(boom2, 30);
+}
+
+function boom2()
+{
+ var v = document.getElementById("v");
+ v.parentNode.removeChild(v);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo">
+ <content>x x x x x <children/></content>
+</binding></bindings>
+
+</head>
+
+<body style="font-family: monospace; width: 10ch;" onload="setTimeout(boom1, 30);">
+
+<div style="background: lightblue; border: 1px solid blue; margin-bottom: 1em;">
+ <span id="c">y y<span id="v">Z<div/></span></span>
+</div>
+
+<div style="background: lightgreen; border: 1px solid green; height: 2000px;">s</div>
+
+</body>
+</html>
diff --git a/dom/xbl/crashtests/382376-2.xhtml b/dom/xbl/crashtests/382376-2.xhtml
new file mode 100644
index 000000000..4e22b8f4e
--- /dev/null
+++ b/dom/xbl/crashtests/382376-2.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var v = document.getElementById("v");
+ v.parentNode.removeChild(v);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo">
+ <content>x x x x x <children/></content>
+</binding></bindings>
+
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+<div style="background: lightblue; border: 1px solid blue; width: 300px">
+ <span style="-moz-binding: url(#foo);" >y y<span id="v">Z<div/></span></span>
+</div>
+
+</body>
+</html>
diff --git a/dom/xbl/crashtests/397596-1.xhtml b/dom/xbl/crashtests/397596-1.xhtml
new file mode 100644
index 000000000..c7b562b52
--- /dev/null
+++ b/dom/xbl/crashtests/397596-1.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Testcase bug - 100% cpu usage with binding setting position: fixed in constructor</title>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="b" inheritstyle="false">
+<content>
+<div xmlns="http://www.w3.org/1999/xhtml" style=" -moz-binding: url(#a);"/>
+<children/>
+</content>
+</binding>
+
+<binding id="a">
+<implementation>
+<constructor>
+ this.style.position='fixed';
+</constructor>
+</implementation>
+<content>
+<children xmlns="http://www.mozilla.org/xbl"/>
+</content>
+</binding>
+</bindings>
+
+</head>
+<body>
+<span style="-moz-binding:url();"></span>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/xbl/crashtests/404125-1.xhtml b/dom/xbl/crashtests/404125-1.xhtml
new file mode 100644
index 000000000..46b0fd6f8
--- /dev/null
+++ b/dom/xbl/crashtests/404125-1.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="lc">
+ <content>
+ <xul:listcell> t <children/></xul:listcell>
+ </content>
+ </binding>
+</bindings>
+
+<script>
+function boom()
+{
+ var hbox = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "hbox");
+ document.getElementById("vv").appendChild(hbox);
+}
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<div style="-moz-binding: url(#lc);" id="vv"></div>
+
+</body>
+
+</html>
diff --git a/dom/xbl/crashtests/406900-1.xul b/dom/xbl/crashtests/406900-1.xul
new file mode 100644
index 000000000..3b3dc1f87
--- /dev/null
+++ b/dom/xbl/crashtests/406900-1.xul
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="boom();">
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="lit">
+ <content>
+ <children>
+ <xul:hbox/>
+ </children>
+ </content>
+ </binding>
+</bindings>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var x = document.getElementById("x");
+ var anon = document.getAnonymousNodes(x)[0];
+ document.documentElement.removeChild(x);
+ document.documentElement.appendChild(x);
+ var hbox = document.createElement('hbox');
+ anon.appendChild(hbox);
+}
+
+</script>
+
+<hbox id="x" style="-moz-binding: url(#lit)" />
+
+</window>
diff --git a/dom/xbl/crashtests/406904-1.xhtml b/dom/xbl/crashtests/406904-1.xhtml
new file mode 100644
index 000000000..758fb04cd
--- /dev/null
+++ b/dom/xbl/crashtests/406904-1.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xbl="http://www.mozilla.org/xbl">
+<head>
+
+<xbl:bindings xmlns="http://www.mozilla.org/xbl"><xbl:binding id="a"><xbl:content>
+<span>foo<xbl:children/></span>
+</xbl:content></xbl:binding></xbl:bindings>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var s = document.getElementById("s");
+ var anon = document.getAnonymousNodes(s)[0];
+
+ while (anon.firstChild)
+ anon.removeChild(anon.firstChild);
+
+ document.documentElement.style.MozBinding = "url('data:text/xml,%3Cbindings%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fxbl%22%3E%3Cbinding%20id%3D%22foo%22%3E%3Ccontent%3EQ%3C%2Fcontent%3E%3C%2Fbinding%3E%3C%2Fbindings%3E%0A')";
+}
+
+</script>
+</head>
+
+<body onload="boom();"><span id="s" style="-moz-binding: url('#a');"></span></body>
+</html>
diff --git a/dom/xbl/crashtests/406904-2.xhtml b/dom/xbl/crashtests/406904-2.xhtml
new file mode 100644
index 000000000..763672b3f
--- /dev/null
+++ b/dom/xbl/crashtests/406904-2.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xbl="http://www.mozilla.org/xbl">
+<head>
+
+<xbl:bindings xmlns="http://www.mozilla.org/xbl"><xbl:binding id="a"><xbl:content>
+<span><span></span><xbl:children/></span>
+</xbl:content></xbl:binding></xbl:bindings>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var s = document.getElementById("s");
+ var anon = document.getAnonymousNodes(s)[0];
+
+ while (anon.firstChild)
+ anon.removeChild(anon.firstChild);
+
+ document.documentElement.style.MozBinding = "url('data:text/xml,%3Cbindings%20xmlns%3D%22http%3A%2F%2Fwww.mozilla.org%2Fxbl%22%3E%3Cbinding%20id%3D%22foo%22%3E%3Ccontent%3EQ%3C%2Fcontent%3E%3C%2Fbinding%3E%3C%2Fbindings%3E%0A')";
+}
+
+</script>
+</head>
+
+<body onload="boom();"><span id="s" style="-moz-binding: url('#a');"></span></body>
+</html>
diff --git a/dom/xbl/crashtests/415192-1.xul b/dom/xbl/crashtests/415192-1.xul
new file mode 100644
index 000000000..3795ca220
--- /dev/null
+++ b/dom/xbl/crashtests/415192-1.xul
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();">
+<script type="text/javascript">
+
+function boom()
+{
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var wizard = document.createElementNS(XUL_NS, "wizard");
+ var space = document.createTextNode(" ");
+ var hbox = document.createElementNS(XUL_NS, "hbox");
+ hbox.setAttribute("anonid", "Buttons");
+
+ document.documentElement.appendChild(wizard);
+ wizard.appendChild(space);
+ wizard.appendChild(hbox);
+ wizard.cloneNode(true);
+}
+
+</script>
+</window>
diff --git a/dom/xbl/crashtests/415301-1.xul b/dom/xbl/crashtests/415301-1.xul
new file mode 100644
index 000000000..cee274fcd
--- /dev/null
+++ b/dom/xbl/crashtests/415301-1.xul
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="boom();">
+
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+
+<binding id="chil"><content><children/></content></binding>
+
+<binding id="ichil"><content>
+<xul:hbox style="-moz-binding: url(#chil)"><children/></xul:hbox>
+</content></binding>
+
+</bindings>
+
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("inner").removeChild(document.getElementById("lbb"));
+ document.getElementById("outer").style.MozBinding = "";
+}
+
+</script>
+
+
+<hbox id="outer" style="-moz-binding: url(#chil)"><hbox id="inner" style="-moz-binding: url(#ichil)"><listboxbody id="lbb" /></hbox></hbox>
+
+
+</window>
diff --git a/dom/xbl/crashtests/418133-1.xhtml b/dom/xbl/crashtests/418133-1.xhtml
new file mode 100644
index 000000000..ea821b76e
--- /dev/null
+++ b/dom/xbl/crashtests/418133-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ for(var p in document.getElementById("prefs")) {
+ }
+
+ for(var p in window) {
+ }
+}
+
+</script>
+</head>
+<body onload="boom();">
+
+<xul:preferences id="prefs" />
+
+</body>
+</html>
diff --git a/dom/xbl/crashtests/420233-1.xhtml b/dom/xbl/crashtests/420233-1.xhtml
new file mode 100644
index 000000000..8ec425075
--- /dev/null
+++ b/dom/xbl/crashtests/420233-1.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="ab"><content><html:span>a b<children/></html:span></content></binding>
+ <binding id="empty"><content></content></binding>
+</bindings>
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("td").style.MozBinding = "url('#empty')";
+}
+
+</script>
+
+</head>
+
+<body onload="boom();"><div style="width: 1px;"><span style="-moz-binding: url(#ab);"><td id="td"/></span></div></body>
+</html>
diff --git a/dom/xbl/crashtests/421997-1.xhtml b/dom/xbl/crashtests/421997-1.xhtml
new file mode 100644
index 000000000..4b91f00fc
--- /dev/null
+++ b/dom/xbl/crashtests/421997-1.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="g">
+ <implementation>
+ <constructor>
+ throw 3;
+ </constructor>
+ </implementation>
+ </binding>
+</bindings>
+
+<script type="text/javascript">
+
+function e()
+{
+ var listboxbody = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "listboxbody");
+ document.getElementById("s").appendChild(listboxbody);
+}
+
+</script>
+</head>
+
+<body onerror="e();"><span id="s" style="-moz-binding: url(#g)"></span></body>
+
+</html>
diff --git a/dom/xbl/crashtests/432813-1-xbl.xml b/dom/xbl/crashtests/432813-1-xbl.xml
new file mode 100644
index 000000000..b2940e638
--- /dev/null
+++ b/dom/xbl/crashtests/432813-1-xbl.xml
@@ -0,0 +1,7 @@
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="a">
+<content>
+<div xmlns="http://www.w3.org/1999/xhtml" style="-moz-binding: inherit;"/>
+</content>
+</binding>
+</bindings>
diff --git a/dom/xbl/crashtests/432813-1.xhtml b/dom/xbl/crashtests/432813-1.xhtml
new file mode 100644
index 000000000..586ac05f1
--- /dev/null
+++ b/dom/xbl/crashtests/432813-1.xhtml
@@ -0,0 +1,3 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <box style="-moz-binding:url(432813-1-xbl.xml);"/>
+</window>
diff --git a/dom/xbl/crashtests/454820-1.html b/dom/xbl/crashtests/454820-1.html
new file mode 100644
index 000000000..31a8316f6
--- /dev/null
+++ b/dom/xbl/crashtests/454820-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 454820</title>
+</head>
+<body>
+
+<p>PASS if Firefox does not crash.</p>
+<script type="text/javascript">
+var evObj = document.createEvent('UIEvents');
+evObj.initUIEvent( 'keypress', true, true, window, 1 );
+document.getElementsByTagName('p')[0].dispatchEvent(evObj);
+</script>
+
+
+</body>
+</html>
diff --git a/dom/xbl/crashtests/460665-1.xhtml b/dom/xbl/crashtests/460665-1.xhtml
new file mode 100644
index 000000000..2ea1f7212
--- /dev/null
+++ b/dom/xbl/crashtests/460665-1.xhtml
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="a">
+ <content></content>
+ <implementation>
+ <field>t</field>
+ <field/>
+ <property/>
+ <property>
+ <getter>x</getter>
+ <setter>y</setter>
+ </property>
+ <method/>
+ <method>
+ <body/>
+ </method>
+ <method>
+ <body>a</body>
+ </method>
+ </implementation>
+ </binding>
+</bindings>
+</head>
+
+<body><span id="s" style="-moz-binding: url('#a');"></span></body>
+</html>
diff --git a/dom/xbl/crashtests/463511-1.xhtml b/dom/xbl/crashtests/463511-1.xhtml
new file mode 100644
index 000000000..a9fec3b50
--- /dev/null
+++ b/dom/xbl/crashtests/463511-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="-moz-binding: url(#foo)">
+<head>
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="foo"><content><listcell xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><children xmlns="http://www.mozilla.org/xbl"/></listcell></content></binding>
+</bindings>
+</head>
+
+<body onload="document.documentElement.innerHTML = 'x';"></body>
+</html>
diff --git a/dom/xbl/crashtests/464863-1.xhtml b/dom/xbl/crashtests/464863-1.xhtml
new file mode 100644
index 000000000..a665afc9c
--- /dev/null
+++ b/dom/xbl/crashtests/464863-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content><qqq><children xmlns="http://www.mozilla.org/xbl"/></qqq></content></binding></bindings>
+
+<script type="text/javascript">
+// <![CDATA[
+
+function boom()
+{
+ var s = document.getElementById("s");
+ var o = document.getAnonymousNodes(s)[0];
+ (document.body).appendChild(s);
+ o.appendChild(document.createTextNode("F"));
+}
+
+// ]]>
+</script>
+</head>
+
+<body onload="boom();"><span id="s" style="-moz-binding: url(#foo)"></span></body>
+</html>
diff --git a/dom/xbl/crashtests/472260-1.xhtml b/dom/xbl/crashtests/472260-1.xhtml
new file mode 100644
index 000000000..49d5407d4
--- /dev/null
+++ b/dom/xbl/crashtests/472260-1.xhtml
@@ -0,0 +1,35 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="foo"><content><span xmlns="http://www.w3.org/1999/xhtml"/></content></binding>
+ <binding id="bar"><content></content></binding>
+</bindings>
+
+<script type="text/javascript">
+// <![CDATA[
+
+function boom()
+{
+ var bo = document.getElementById("bo");
+ var anon = SpecialPowers.wrap(document).getAnonymousNodes(bo)[0];
+
+ bo.style.MozBinding = "url(#bar)";
+
+ var fr = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+ fr.setAttribute("src", "javascript:void 0;");
+ anon.appendChild(fr);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+</head>
+
+<body onload="setTimeout(boom, 100);">
+
+<span style="-moz-binding: url(#foo)" id="bo"></span>
+
+</body>
+</html>
diff --git a/dom/xbl/crashtests/477878-1.html b/dom/xbl/crashtests/477878-1.html
new file mode 100644
index 000000000..17e4002b4
--- /dev/null
+++ b/dom/xbl/crashtests/477878-1.html
@@ -0,0 +1,4 @@
+<html>
+<head></head>
+<body><iframe style="display:none" src="data:text/html,<marquee>Marquee</marquee>" onload="this.style.display = '';"></iframe></body>
+</html>
diff --git a/dom/xbl/crashtests/492978-1.xul b/dom/xbl/crashtests/492978-1.xul
new file mode 100644
index 000000000..365ecfc9e
--- /dev/null
+++ b/dom/xbl/crashtests/492978-1.xul
@@ -0,0 +1,15 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="a" inheritstyle="false">
+<content>
+<style xmlns="http://www.w3.org/1999/xhtml">
+window::after, box::after, bindings::after { content:"m"; float:right;}
+</style>
+</content>
+</binding>
+</bindings>
+
+<box style="-moz-binding:url(crash1.xul);overflow: scroll;"/>
+
+</window> \ No newline at end of file
diff --git a/dom/xbl/crashtests/493123-1.xhtml b/dom/xbl/crashtests/493123-1.xhtml
new file mode 100644
index 000000000..1c568ae15
--- /dev/null
+++ b/dom/xbl/crashtests/493123-1.xhtml
@@ -0,0 +1,34 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="a"><content></content></binding>
+ <binding id="b"><content><style xmlns="http://www.w3.org/1999/xhtml" style="display: none;">* { overflow: hidden; }</style><children xmlns="http://www.mozilla.org/xbl"/></content></binding>
+</bindings>
+
+<script type="text/javascript" style="display: none">
+
+function one()
+{
+ while (document.documentElement.firstChild) document.documentElement.removeChild(document.documentElement.firstChild)
+ document.documentElement.style.MozBinding = 'url("#a")';
+ setTimeout(two, 1);
+}
+
+function two()
+{
+ document.documentElement.style.MozBinding = 'url("#b")';
+ setTimeout(three, 1);
+}
+
+function three()
+{
+ document.documentElement.style.MozBinding = 'url("#a")';
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="one();"></body>
+</html>
diff --git a/dom/xbl/crashtests/495354-1.xhtml b/dom/xbl/crashtests/495354-1.xhtml
new file mode 100644
index 000000000..2e76fb846
--- /dev/null
+++ b/dom/xbl/crashtests/495354-1.xhtml
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="x"><content><caption xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><children xmlns="http://www.mozilla.org/xbl"/></caption></content></binding></bindings>
+</head>
+<body onload="document.getElementById('x').innerHTML = '9';">
+<span id="x" style="-moz-binding: url(#x)"><div></div></span>
+</body>
+</html>
diff --git a/dom/xbl/crashtests/507628-1.xhtml b/dom/xbl/crashtests/507628-1.xhtml
new file mode 100644
index 000000000..a82871f71
--- /dev/null
+++ b/dom/xbl/crashtests/507628-1.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="b" inheritstyle="false">
+<content>
+<span xmlns="http://www.w3.org/1999/xhtml"/><span xmlns="http://www.w3.org/1999/xhtml"><embed style="display: block;" type="*"/></span>
+</content>
+</binding>
+</bindings>
+
+<div style="-moz-binding:url();"/>
+
+</html> \ No newline at end of file
diff --git a/dom/xbl/crashtests/507991-1.xhtml b/dom/xbl/crashtests/507991-1.xhtml
new file mode 100644
index 000000000..402e680ee
--- /dev/null
+++ b/dom/xbl/crashtests/507991-1.xhtml
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<title>Crash [@ nsSprocketLayout::PopulateBoxSizes] with object and position:absolute;overflow:scroll in binding</title>
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="a" inheritstyle="false">
+<content>
+<object xmlns="http://www.w3.org/1999/xhtml" style="display: table;"/>
+<div xmlns="http://www.w3.org/1999/xhtml" style="position: absolute;overflow: scroll; "/>
+</content>
+</binding>
+</bindings>
+</head>
+
+<body>
+<span style="-moz-binding:url()"></span>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/xbl/crashtests/830614-1.xul b/dom/xbl/crashtests/830614-1.xul
new file mode 100644
index 000000000..f2f9bb352
--- /dev/null
+++ b/dom/xbl/crashtests/830614-1.xul
@@ -0,0 +1,24 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="document.getElementById('trigger');">
+ <box style="display: none">
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="crash">
+ <implementation>
+ <constructor>
+ // Fetch it
+ var obj = this.getElementsByTagName("box")[0];
+ // And make it preserve its wrapper. Note that this will happen
+ // while we're wrapping our box as the parent for id="trigger",
+ // so then we'll unwind and things will be bad.
+ if (obj) obj.expando = 5;
+ </constructor>
+ </implementation>
+ </binding>
+ </bindings>
+ <box style="-moz-binding:url(#crash);">
+ <box id="trigger"/>
+ </box>
+ </box>
+ <!-- Make sure we load our XBL before we try to run our test -->
+ <box style="-moz-binding:url(#crash);"/>
+</window>
diff --git a/dom/xbl/crashtests/895805-1.xhtml b/dom/xbl/crashtests/895805-1.xhtml
new file mode 100644
index 000000000..a6598de9c
--- /dev/null
+++ b/dom/xbl/crashtests/895805-1.xhtml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+<head>
+<title>Bug 895805 - Adopting bound element to another document.</title>
+<xbl:bindings>
+ <xbl:binding id="crash">
+ <xbl:content>
+ <xbl:children />
+ Bug 895805 dummy binding
+ </xbl:content>
+ </xbl:binding>
+</xbl:bindings>
+<style type="text/css">
+#test {
+ -moz-binding:url(#crash);
+}
+</style>
+</head>
+<body onload="init()">
+<span id="test">Test</span>
+<script>
+function init() {
+ var boundElement = document.getElementById('test');
+ var otherDoc = document.implementation.createDocument('', '', null);
+ otherDoc.adoptNode(boundElement);
+}
+</script>
+</body>
+</html>
diff --git a/dom/xbl/crashtests/crashtests.list b/dom/xbl/crashtests/crashtests.list
new file mode 100644
index 000000000..26424c7d8
--- /dev/null
+++ b/dom/xbl/crashtests/crashtests.list
@@ -0,0 +1,40 @@
+load 205735-1.xhtml
+load 223799-1.xul
+load 226744-1.xhtml
+load 232095-1.xul
+load 277523-1.xhtml
+load 277950-1.xhtml
+skip-if(Android) load 336744-1.html # bug 1268050
+load 336960-1.html
+load 342954-1.xhtml
+load 342954-2.xhtml
+load 368276-1.xhtml
+load 368641-1.xhtml
+load 378521-1.xhtml
+load 382376-1.xhtml
+load 382376-2.xhtml
+load 397596-1.xhtml
+load 404125-1.xhtml
+load 406900-1.xul
+load 406904-1.xhtml
+load 406904-2.xhtml
+load 415192-1.xul
+load 415301-1.xul
+load 418133-1.xhtml
+load 420233-1.xhtml
+load 421997-1.xhtml
+load 432813-1.xhtml
+load 454820-1.html
+load 460665-1.xhtml
+load 463511-1.xhtml
+load 464863-1.xhtml
+load 472260-1.xhtml
+load 477878-1.html
+load 492978-1.xul
+load 493123-1.xhtml
+load 495354-1.xhtml
+load 507628-1.xhtml
+load 507991-1.xhtml
+load 830614-1.xul
+load 895805-1.xhtml
+load set-field-bad-this.xhtml
diff --git a/dom/xbl/crashtests/set-field-bad-this.xhtml b/dom/xbl/crashtests/set-field-bad-this.xhtml
new file mode 100644
index 000000000..7b3dea03a
--- /dev/null
+++ b/dom/xbl/crashtests/set-field-bad-this.xhtml
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+ <title>Gracefully handle setting a field on a bad |this|</title>
+
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="a">
+ <implementation>
+ <field name="f">17</field>
+ </implementation>
+ </binding>
+ </bindings>
+
+ <script type="application/javascript">
+ window.onload = function()
+ {
+ var bound = document.getElementById("bound");
+ try
+ {
+ Object.getPrototypeOf(bound).f = 42;
+ }
+ catch (e) { /* Throwing's fine, crashing isn't. */ }
+ };
+ </script>
+</head>
+
+<body>
+ <div id="bound" style="-moz-binding: url(#a)"></div>
+</body>
+
+</html>
diff --git a/dom/xbl/moz.build b/dom/xbl/moz.build
new file mode 100644
index 000000000..0950db036
--- /dev/null
+++ b/dom/xbl/moz.build
@@ -0,0 +1,53 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['builtin']
+
+EXPORTS += [
+ 'nsBindingManager.h',
+ 'nsXBLBinding.h',
+ 'nsXBLService.h',
+]
+
+EXPORTS.mozilla.dom += [
+ 'XBLChildrenElement.h',
+]
+
+UNIFIED_SOURCES += [
+ 'nsBindingManager.cpp',
+ 'nsXBLBinding.cpp',
+ 'nsXBLContentSink.cpp',
+ 'nsXBLDocumentInfo.cpp',
+ 'nsXBLEventHandler.cpp',
+ 'nsXBLProtoImpl.cpp',
+ 'nsXBLProtoImplField.cpp',
+ 'nsXBLProtoImplMethod.cpp',
+ 'nsXBLProtoImplProperty.cpp',
+ 'nsXBLPrototypeBinding.cpp',
+ 'nsXBLPrototypeHandler.cpp',
+ 'nsXBLPrototypeResources.cpp',
+ 'nsXBLResourceLoader.cpp',
+ 'nsXBLSerialize.cpp',
+ 'nsXBLService.cpp',
+ 'nsXBLWindowKeyHandler.cpp',
+ 'XBLChildrenElement.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/dom/html',
+ '/dom/xml',
+ '/dom/xul',
+ '/layout/style',
+]
+
+FINAL_LIBRARY = 'xul'
+
+MOCHITEST_MANIFESTS += ['test/mochitest.ini']
+MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/dom/xbl/nsBindingManager.cpp b/dom/xbl/nsBindingManager.cpp
new file mode 100644
index 000000000..405c7aac7
--- /dev/null
+++ b/dom/xbl/nsBindingManager.cpp
@@ -0,0 +1,1160 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsBindingManager.h"
+
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsXBLService.h"
+#include "nsIInputStream.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsXPIDLString.h"
+#include "plstr.h"
+#include "nsIContent.h"
+#include "nsIDOMElement.h"
+#include "nsIDocument.h"
+#include "nsContentUtils.h"
+#include "nsIPresShell.h"
+#include "nsIXMLContentSink.h"
+#include "nsContentCID.h"
+#include "mozilla/dom/XMLDocument.h"
+#include "nsIStreamListener.h"
+#include "ChildIterator.h"
+#include "nsITimer.h"
+
+#include "nsXBLBinding.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsXBLDocumentInfo.h"
+#include "mozilla/dom/XBLChildrenElement.h"
+
+#include "nsIStyleRuleProcessor.h"
+#include "nsRuleProcessorData.h"
+#include "nsIWeakReference.h"
+
+#include "nsWrapperCacheInlines.h"
+#include "nsIXPConnect.h"
+#include "nsDOMCID.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsTHashtable.h"
+
+#include "nsIScriptContext.h"
+#include "xpcpublic.h"
+#include "jswrapper.h"
+
+#include "nsThreadUtils.h"
+#include "mozilla/dom/NodeListBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/Unused.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// Implement our nsISupports methods
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsBindingManager)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsBindingManager)
+ tmp->mDestroyed = true;
+
+ if (tmp->mBoundContentSet)
+ tmp->mBoundContentSet->Clear();
+
+ if (tmp->mDocumentTable)
+ tmp->mDocumentTable->Clear();
+
+ if (tmp->mLoadingDocTable)
+ tmp->mLoadingDocTable->Clear();
+
+ if (tmp->mWrapperTable) {
+ tmp->mWrapperTable->Clear();
+ tmp->mWrapperTable = nullptr;
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAttachedStack)
+
+ if (tmp->mProcessAttachedQueueEvent) {
+ tmp->mProcessAttachedQueueEvent->Revoke();
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsBindingManager)
+ // The hashes keyed on nsIContent are traversed from the nsIContent itself.
+ if (tmp->mDocumentTable) {
+ for (auto iter = tmp->mDocumentTable->Iter(); !iter.Done(); iter.Next()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDocumentTable value");
+ cb.NoteXPCOMChild(iter.UserData());
+ }
+ }
+ if (tmp->mLoadingDocTable) {
+ for (auto iter = tmp->mLoadingDocTable->Iter(); !iter.Done(); iter.Next()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mLoadingDocTable value");
+ cb.NoteXPCOMChild(iter.UserData());
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAttachedStack)
+ // No need to traverse mProcessAttachedQueueEvent, since it'll just
+ // fire at some point or become revoke and drop its ref to us.
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsBindingManager)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsBindingManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsBindingManager)
+
+// Constructors/Destructors
+nsBindingManager::nsBindingManager(nsIDocument* aDocument)
+ : mProcessingAttachedStack(false),
+ mDestroyed(false),
+ mAttachedStackSizeOnOutermost(0),
+ mDocument(aDocument)
+{
+}
+
+nsBindingManager::~nsBindingManager(void)
+{
+ mDestroyed = true;
+}
+
+nsXBLBinding*
+nsBindingManager::GetBindingWithContent(const nsIContent* aContent)
+{
+ nsXBLBinding* binding = aContent ? aContent->GetXBLBinding() : nullptr;
+ return binding ? binding->GetBindingWithContent() : nullptr;
+}
+
+void
+nsBindingManager::AddBoundContent(nsIContent* aContent)
+{
+ if (!mBoundContentSet) {
+ mBoundContentSet = new nsTHashtable<nsRefPtrHashKey<nsIContent> >;
+ }
+ mBoundContentSet->PutEntry(aContent);
+}
+
+void
+nsBindingManager::RemoveBoundContent(nsIContent* aContent)
+{
+ if (mBoundContentSet) {
+ mBoundContentSet->RemoveEntry(aContent);
+ }
+
+ // The death of the bindings means the death of the JS wrapper.
+ SetWrappedJS(aContent, nullptr);
+}
+
+nsIXPConnectWrappedJS*
+nsBindingManager::GetWrappedJS(nsIContent* aContent)
+{
+ if (!mWrapperTable) {
+ return nullptr;
+ }
+
+ if (!aContent || !aContent->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+ return nullptr;
+ }
+
+ return mWrapperTable->GetWeak(aContent);
+}
+
+nsresult
+nsBindingManager::SetWrappedJS(nsIContent* aContent, nsIXPConnectWrappedJS* aWrappedJS)
+{
+ if (mDestroyed) {
+ return NS_OK;
+ }
+
+ if (aWrappedJS) {
+ // lazily create the table, but only when adding elements
+ if (!mWrapperTable) {
+ mWrapperTable = new WrapperHashtable();
+ }
+ aContent->SetFlags(NODE_MAY_BE_IN_BINDING_MNGR);
+
+ NS_ASSERTION(aContent, "key must be non-null");
+ if (!aContent) return NS_ERROR_INVALID_ARG;
+
+ mWrapperTable->Put(aContent, aWrappedJS);
+
+ return NS_OK;
+ }
+
+ // no value, so remove the key from the table
+ if (mWrapperTable) {
+ mWrapperTable->Remove(aContent);
+ }
+
+ return NS_OK;
+}
+
+void
+nsBindingManager::RemovedFromDocumentInternal(nsIContent* aContent,
+ nsIDocument* aOldDocument,
+ DestructorHandling aDestructorHandling)
+{
+ NS_PRECONDITION(aOldDocument != nullptr, "no old document");
+
+ RefPtr<nsXBLBinding> binding = aContent->GetXBLBinding();
+ if (binding) {
+ // The binding manager may have been destroyed before a runnable
+ // has had a chance to reach this point. If so, we bail out on calling
+ // BindingDetached (which may invoke a XBL destructor) and
+ // ChangeDocument, but we still want to clear out the binding
+ // and insertion parent that may hold references.
+ if (!mDestroyed && aDestructorHandling == eRunDtor) {
+ binding->PrototypeBinding()->BindingDetached(binding->GetBoundElement());
+ binding->ChangeDocument(aOldDocument, nullptr);
+ }
+
+ aContent->SetXBLBinding(nullptr, this);
+ }
+
+ // Clear out insertion parent and content lists.
+ aContent->SetXBLInsertionParent(nullptr);
+}
+
+nsIAtom*
+nsBindingManager::ResolveTag(nsIContent* aContent, int32_t* aNameSpaceID)
+{
+ nsXBLBinding *binding = aContent->GetXBLBinding();
+
+ if (binding) {
+ nsIAtom* base = binding->GetBaseTag(aNameSpaceID);
+
+ if (base) {
+ return base;
+ }
+ }
+
+ *aNameSpaceID = aContent->GetNameSpaceID();
+ return aContent->NodeInfo()->NameAtom();
+}
+
+nsresult
+nsBindingManager::GetAnonymousNodesFor(nsIContent* aContent,
+ nsIDOMNodeList** aResult)
+{
+ NS_IF_ADDREF(*aResult = GetAnonymousNodesFor(aContent));
+ return NS_OK;
+}
+
+nsINodeList*
+nsBindingManager::GetAnonymousNodesFor(nsIContent* aContent)
+{
+ nsXBLBinding* binding = GetBindingWithContent(aContent);
+ return binding ? binding->GetAnonymousNodeList() : nullptr;
+}
+
+nsresult
+nsBindingManager::ClearBinding(nsIContent* aContent)
+{
+ // Hold a ref to the binding so it won't die when we remove it from our table
+ RefPtr<nsXBLBinding> binding =
+ aContent ? aContent->GetXBLBinding() : nullptr;
+
+ if (!binding) {
+ return NS_OK;
+ }
+
+ // For now we can only handle removing a binding if it's the only one
+ NS_ENSURE_FALSE(binding->GetBaseBinding(), NS_ERROR_FAILURE);
+
+ // Hold strong ref in case removing the binding tries to close the
+ // window or something.
+ // XXXbz should that be ownerdoc? Wouldn't we need a ref to the
+ // currentdoc too? What's the one that should be passed to
+ // ChangeDocument?
+ nsCOMPtr<nsIDocument> doc = aContent->OwnerDoc();
+
+ // Finally remove the binding...
+ // XXXbz this doesn't remove the implementation! Should fix! Until
+ // then we need the explicit UnhookEventHandlers here.
+ binding->UnhookEventHandlers();
+ binding->ChangeDocument(doc, nullptr);
+ aContent->SetXBLBinding(nullptr, this);
+ binding->MarkForDeath();
+
+ // ...and recreate its frames. We need to do this since the frames may have
+ // been removed and style may have changed due to the removal of the
+ // anonymous children.
+ // XXXbz this should be using the current doc (if any), not the owner doc.
+ nsIPresShell *presShell = doc->GetShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ return presShell->RecreateFramesFor(aContent);;
+}
+
+nsresult
+nsBindingManager::LoadBindingDocument(nsIDocument* aBoundDoc,
+ nsIURI* aURL,
+ nsIPrincipal* aOriginPrincipal)
+{
+ NS_PRECONDITION(aURL, "Must have a URI to load!");
+
+ // First we need to load our binding.
+ nsXBLService* xblService = nsXBLService::GetInstance();
+ if (!xblService)
+ return NS_ERROR_FAILURE;
+
+ // Load the binding doc.
+ RefPtr<nsXBLDocumentInfo> info;
+ xblService->LoadBindingDocumentInfo(nullptr, aBoundDoc, aURL,
+ aOriginPrincipal, true,
+ getter_AddRefs(info));
+ if (!info)
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+void
+nsBindingManager::RemoveFromAttachedQueue(nsXBLBinding* aBinding)
+{
+ // Don't remove items here as that could mess up an executing
+ // ProcessAttachedQueue. Instead, null the entry in the queue.
+ size_t index = mAttachedStack.IndexOf(aBinding);
+ if (index != mAttachedStack.NoIndex) {
+ mAttachedStack[index] = nullptr;
+ }
+}
+
+nsresult
+nsBindingManager::AddToAttachedQueue(nsXBLBinding* aBinding)
+{
+ mAttachedStack.AppendElement(aBinding);
+
+ // If we're in the middle of processing our queue already, don't
+ // bother posting the event.
+ if (!mProcessingAttachedStack && !mProcessAttachedQueueEvent) {
+ PostProcessAttachedQueueEvent();
+ }
+
+ // Make sure that flushes will flush out the new items as needed.
+ mDocument->SetNeedStyleFlush();
+
+ return NS_OK;
+
+}
+
+void
+nsBindingManager::PostProcessAttachedQueueEvent()
+{
+ mProcessAttachedQueueEvent =
+ NewRunnableMethod(this, &nsBindingManager::DoProcessAttachedQueue);
+ nsresult rv = NS_DispatchToCurrentThread(mProcessAttachedQueueEvent);
+ if (NS_SUCCEEDED(rv) && mDocument) {
+ mDocument->BlockOnload();
+ }
+}
+
+// static
+void
+nsBindingManager::PostPAQEventCallback(nsITimer* aTimer, void* aClosure)
+{
+ RefPtr<nsBindingManager> mgr =
+ already_AddRefed<nsBindingManager>(static_cast<nsBindingManager*>(aClosure));
+ mgr->PostProcessAttachedQueueEvent();
+ NS_RELEASE(aTimer);
+}
+
+void
+nsBindingManager::DoProcessAttachedQueue()
+{
+ if (!mProcessingAttachedStack) {
+ ProcessAttachedQueue();
+
+ NS_ASSERTION(mAttachedStack.Length() == 0,
+ "Shouldn't have pending bindings!");
+
+ mProcessAttachedQueueEvent = nullptr;
+ } else {
+ // Someone's doing event processing from inside a constructor.
+ // They're evil, but we'll fight back! Just poll on them being
+ // done and repost the attached queue event.
+ //
+ // But don't poll in a tight loop -- otherwise we keep the Gecko
+ // event loop non-empty and trigger bug 1021240 on OS X.
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (timer) {
+ rv = timer->InitWithFuncCallback(PostPAQEventCallback, this,
+ 100, nsITimer::TYPE_ONE_SHOT);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ NS_ADDREF_THIS();
+ // We drop our reference to the timer here, since the timer callback is
+ // responsible for releasing the object.
+ Unused << timer.forget().take();
+ }
+ }
+
+ // No matter what, unblock onload for the event that's fired.
+ if (mDocument) {
+ // Hold a strong reference while calling UnblockOnload since that might
+ // run script.
+ nsCOMPtr<nsIDocument> doc = mDocument;
+ doc->UnblockOnload(true);
+ }
+}
+
+void
+nsBindingManager::ProcessAttachedQueueInternal(uint32_t aSkipSize)
+{
+ mProcessingAttachedStack = true;
+
+ // Excute constructors. Do this from high index to low
+ while (mAttachedStack.Length() > aSkipSize) {
+ uint32_t lastItem = mAttachedStack.Length() - 1;
+ RefPtr<nsXBLBinding> binding = mAttachedStack.ElementAt(lastItem);
+ mAttachedStack.RemoveElementAt(lastItem);
+ if (binding) {
+ binding->ExecuteAttachedHandler();
+ }
+ }
+
+ // If NodeWillBeDestroyed has run we don't want to clobber
+ // mProcessingAttachedStack set there.
+ if (mDocument) {
+ mProcessingAttachedStack = false;
+ }
+
+ NS_ASSERTION(mAttachedStack.Length() == aSkipSize, "How did we get here?");
+
+ mAttachedStack.Compact();
+}
+
+// Keep bindings and bound elements alive while executing detached handlers.
+void
+nsBindingManager::ExecuteDetachedHandlers()
+{
+ // Walk our hashtable of bindings.
+ if (!mBoundContentSet) {
+ return;
+ }
+
+ nsCOMArray<nsIContent> boundElements;
+ nsBindingList bindings;
+
+ for (auto iter = mBoundContentSet->Iter(); !iter.Done(); iter.Next()) {
+ nsXBLBinding* binding = iter.Get()->GetKey()->GetXBLBinding();
+ if (binding && bindings.AppendElement(binding)) {
+ if (!boundElements.AppendObject(binding->GetBoundElement())) {
+ bindings.RemoveElementAt(bindings.Length() - 1);
+ }
+ }
+ }
+
+ uint32_t i, count = bindings.Length();
+ for (i = 0; i < count; ++i) {
+ bindings[i]->ExecuteDetachedHandler();
+ }
+}
+
+nsresult
+nsBindingManager::PutXBLDocumentInfo(nsXBLDocumentInfo* aDocumentInfo)
+{
+ NS_PRECONDITION(aDocumentInfo, "Must have a non-null documentinfo!");
+
+ if (!mDocumentTable) {
+ mDocumentTable = new nsRefPtrHashtable<nsURIHashKey,nsXBLDocumentInfo>();
+ }
+
+ mDocumentTable->Put(aDocumentInfo->DocumentURI(), aDocumentInfo);
+
+ return NS_OK;
+}
+
+void
+nsBindingManager::RemoveXBLDocumentInfo(nsXBLDocumentInfo* aDocumentInfo)
+{
+ if (mDocumentTable) {
+ mDocumentTable->Remove(aDocumentInfo->DocumentURI());
+ }
+}
+
+nsXBLDocumentInfo*
+nsBindingManager::GetXBLDocumentInfo(nsIURI* aURL)
+{
+ if (!mDocumentTable)
+ return nullptr;
+
+ return mDocumentTable->GetWeak(aURL);
+}
+
+nsresult
+nsBindingManager::PutLoadingDocListener(nsIURI* aURL, nsIStreamListener* aListener)
+{
+ NS_PRECONDITION(aListener, "Must have a non-null listener!");
+
+ if (!mLoadingDocTable) {
+ mLoadingDocTable =
+ new nsInterfaceHashtable<nsURIHashKey,nsIStreamListener>();
+ }
+ mLoadingDocTable->Put(aURL, aListener);
+
+ return NS_OK;
+}
+
+nsIStreamListener*
+nsBindingManager::GetLoadingDocListener(nsIURI* aURL)
+{
+ if (!mLoadingDocTable)
+ return nullptr;
+
+ return mLoadingDocTable->GetWeak(aURL);
+}
+
+void
+nsBindingManager::RemoveLoadingDocListener(nsIURI* aURL)
+{
+ if (mLoadingDocTable) {
+ mLoadingDocTable->Remove(aURL);
+ }
+}
+
+void
+nsBindingManager::FlushSkinBindings()
+{
+ if (!mBoundContentSet) {
+ return;
+ }
+
+ for (auto iter = mBoundContentSet->Iter(); !iter.Done(); iter.Next()) {
+ nsXBLBinding* binding = iter.Get()->GetKey()->GetXBLBinding();
+
+ if (binding->MarkedForDeath()) {
+ continue;
+ }
+
+ nsAutoCString path;
+ binding->PrototypeBinding()->DocURI()->GetPath(path);
+
+ if (!strncmp(path.get(), "/skin", 5)) {
+ binding->MarkForDeath();
+ }
+ }
+}
+
+// Used below to protect from recurring in QI calls through XPConnect.
+struct AntiRecursionData {
+ nsIContent* element;
+ REFNSIID iid;
+ AntiRecursionData* next;
+
+ AntiRecursionData(nsIContent* aElement,
+ REFNSIID aIID,
+ AntiRecursionData* aNext)
+ : element(aElement), iid(aIID), next(aNext) {}
+};
+
+nsresult
+nsBindingManager::GetBindingImplementation(nsIContent* aContent, REFNSIID aIID,
+ void** aResult)
+{
+ *aResult = nullptr;
+ nsXBLBinding *binding = aContent ? aContent->GetXBLBinding() : nullptr;
+ if (binding) {
+ // The binding should not be asked for nsISupports
+ NS_ASSERTION(!aIID.Equals(NS_GET_IID(nsISupports)), "Asking a binding for nsISupports");
+ if (binding->ImplementsInterface(aIID)) {
+ nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS = GetWrappedJS(aContent);
+
+ if (wrappedJS) {
+ // Protect from recurring in QI calls through XPConnect.
+ // This can happen when a second binding is being resolved.
+ // At that point a wrappedJS exists, but it doesn't yet know about
+ // the iid we are asking for. So, without this protection,
+ // AggregatedQueryInterface would end up recurring back into itself
+ // through this code.
+ //
+ // With this protection, when we detect the recursion we return
+ // NS_NOINTERFACE in the inner call. The outer call will then fall
+ // through (see below) and build a new chained wrappedJS for the iid.
+ //
+ // We're careful to not assume that only one direct nesting can occur
+ // because there is a call into JS in the middle and we can't assume
+ // that this code won't be reached by some more complex nesting path.
+ //
+ // NOTE: We *assume* this is single threaded, so we can use a
+ // static linked list to do the check.
+
+ static AntiRecursionData* list = nullptr;
+
+ for (AntiRecursionData* p = list; p; p = p->next) {
+ if (p->element == aContent && p->iid.Equals(aIID)) {
+ *aResult = nullptr;
+ return NS_NOINTERFACE;
+ }
+ }
+
+ AntiRecursionData item(aContent, aIID, list);
+ list = &item;
+
+ nsresult rv = wrappedJS->AggregatedQueryInterface(aIID, aResult);
+
+ list = item.next;
+
+ if (*aResult)
+ return rv;
+
+ // No result was found, so this must be another XBL interface.
+ // Fall through to create a new wrapper.
+ }
+
+ // We have never made a wrapper for this implementation.
+ // Create an XPC wrapper for the script object and hand it back.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+
+ nsIXPConnect *xpConnect = nsContentUtils::XPConnect();
+
+ JS::Rooted<JSObject*> jsobj(cx, aContent->GetWrapper());
+ NS_ENSURE_TRUE(jsobj, NS_NOINTERFACE);
+
+ // If we're using an XBL scope, we need to use the Xray view to the bound
+ // content in order to view the full array of methods defined in the
+ // binding, some of which may not be exposed on the prototype of
+ // untrusted content. We don't need to consider add-on scopes here
+ // because they're chrome-only and no Xrays are involved.
+ //
+ // If there's no separate XBL scope, or if the reflector itself lives in
+ // the XBL scope, we'll end up with the global of the reflector.
+ JS::Rooted<JSObject*> xblScope(cx, xpc::GetXBLScopeOrGlobal(cx, jsobj));
+ NS_ENSURE_TRUE(xblScope, NS_ERROR_UNEXPECTED);
+ JSAutoCompartment ac(cx, xblScope);
+ bool ok = JS_WrapObject(cx, &jsobj);
+ NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
+ MOZ_ASSERT_IF(js::IsWrapper(jsobj), xpc::IsXrayWrapper(jsobj));
+
+ nsresult rv = xpConnect->WrapJSAggregatedToNative(aContent, cx,
+ jsobj, aIID, aResult);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // We successfully created a wrapper. We will own this wrapper for as long as the binding remains
+ // alive. At the time the binding is cleared out of the bindingManager, we will remove the wrapper
+ // from the bindingManager as well.
+ nsISupports* supp = static_cast<nsISupports*>(*aResult);
+ wrappedJS = do_QueryInterface(supp);
+ SetWrappedJS(aContent, wrappedJS);
+
+ return rv;
+ }
+ }
+
+ *aResult = nullptr;
+ return NS_NOINTERFACE;
+}
+
+nsresult
+nsBindingManager::WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc,
+ ElementDependentRuleProcessorData* aData,
+ bool* aCutOffInheritance)
+{
+ *aCutOffInheritance = false;
+
+ NS_ASSERTION(aData->mElement, "How did that happen?");
+
+ // Walk the binding scope chain, starting with the binding attached to our
+ // content, up till we run out of scopes or we get cut off.
+ nsIContent *content = aData->mElement;
+
+ do {
+ nsXBLBinding *binding = content->GetXBLBinding();
+ if (binding) {
+ aData->mTreeMatchContext.mScopedRoot = content;
+ binding->WalkRules(aFunc, aData);
+ // If we're not looking at our original content, allow the binding to cut
+ // off style inheritance
+ if (content != aData->mElement) {
+ if (!binding->InheritsStyle()) {
+ // Go no further; we're not inheriting style from anything above here
+ break;
+ }
+ }
+ }
+
+ if (content->IsRootOfNativeAnonymousSubtree()) {
+ break; // Deliberately cut off style inheritance here.
+ }
+
+ content = content->GetBindingParent();
+ } while (content);
+
+ // If "content" is non-null that means we cut off inheritance at some point
+ // in the loop.
+ *aCutOffInheritance = (content != nullptr);
+
+ // Null out the scoped root that we set repeatedly
+ aData->mTreeMatchContext.mScopedRoot = nullptr;
+
+ return NS_OK;
+}
+
+typedef nsTHashtable<nsPtrHashKey<nsIStyleRuleProcessor> > RuleProcessorSet;
+
+static RuleProcessorSet*
+GetContentSetRuleProcessors(nsTHashtable<nsRefPtrHashKey<nsIContent>>* aContentSet)
+{
+ RuleProcessorSet* set = nullptr;
+
+ for (auto iter = aContentSet->Iter(); !iter.Done(); iter.Next()) {
+ nsIContent* boundContent = iter.Get()->GetKey();
+ for (nsXBLBinding* binding = boundContent->GetXBLBinding(); binding;
+ binding = binding->GetBaseBinding()) {
+ nsIStyleRuleProcessor* ruleProc =
+ binding->PrototypeBinding()->GetRuleProcessor();
+ if (ruleProc) {
+ if (!set) {
+ set = new RuleProcessorSet;
+ }
+ set->PutEntry(ruleProc);
+ }
+ }
+ }
+
+ return set;
+}
+
+void
+nsBindingManager::WalkAllRules(nsIStyleRuleProcessor::EnumFunc aFunc,
+ ElementDependentRuleProcessorData* aData)
+{
+ if (!mBoundContentSet) {
+ return;
+ }
+
+ nsAutoPtr<RuleProcessorSet> set;
+ set = GetContentSetRuleProcessors(mBoundContentSet);
+ if (!set) {
+ return;
+ }
+
+ for (auto iter = set->Iter(); !iter.Done(); iter.Next()) {
+ nsIStyleRuleProcessor* ruleProcessor = iter.Get()->GetKey();
+ (*(aFunc))(ruleProcessor, aData);
+ }
+}
+
+nsresult
+nsBindingManager::MediumFeaturesChanged(nsPresContext* aPresContext,
+ bool* aRulesChanged)
+{
+ *aRulesChanged = false;
+ if (!mBoundContentSet) {
+ return NS_OK;
+ }
+
+ nsAutoPtr<RuleProcessorSet> set;
+ set = GetContentSetRuleProcessors(mBoundContentSet);
+ if (!set) {
+ return NS_OK;
+ }
+
+ for (auto iter = set->Iter(); !iter.Done(); iter.Next()) {
+ nsIStyleRuleProcessor* ruleProcessor = iter.Get()->GetKey();
+ bool thisChanged = ruleProcessor->MediumFeaturesChanged(aPresContext);
+ *aRulesChanged = *aRulesChanged || thisChanged;
+ }
+
+ return NS_OK;
+}
+
+void
+nsBindingManager::AppendAllSheets(nsTArray<StyleSheet*>& aArray)
+{
+ if (!mBoundContentSet) {
+ return;
+ }
+
+ for (auto iter = mBoundContentSet->Iter(); !iter.Done(); iter.Next()) {
+ nsIContent* boundContent = iter.Get()->GetKey();
+ for (nsXBLBinding* binding = boundContent->GetXBLBinding(); binding;
+ binding = binding->GetBaseBinding()) {
+ binding->PrototypeBinding()->AppendStyleSheetsTo(aArray);
+ }
+ }
+}
+
+static void
+InsertAppendedContent(XBLChildrenElement* aPoint,
+ nsIContent* aFirstNewContent)
+{
+ int32_t insertionIndex;
+ if (nsIContent* prevSibling = aFirstNewContent->GetPreviousSibling()) {
+ // If we have a previous sibling, then it must already be in aPoint. Find
+ // it and insert after it.
+ insertionIndex = aPoint->IndexOfInsertedChild(prevSibling);
+ MOZ_ASSERT(insertionIndex != -1);
+
+ // Our insertion index is one after our previous sibling's index.
+ ++insertionIndex;
+ } else {
+ // Otherwise, we append.
+ // TODO This is wrong for nested insertion points. In that case, we need to
+ // keep track of the right index to insert into.
+ insertionIndex = aPoint->InsertedChildrenLength();
+ }
+
+ // Do the inserting.
+ for (nsIContent* currentChild = aFirstNewContent;
+ currentChild;
+ currentChild = currentChild->GetNextSibling()) {
+ aPoint->InsertInsertedChildAt(currentChild, insertionIndex++);
+ }
+}
+
+void
+nsBindingManager::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ if (aNewIndexInContainer == -1) {
+ return;
+ }
+
+ // Try to find insertion points for all the new kids.
+ XBLChildrenElement* point = nullptr;
+ nsIContent* parent = aContainer;
+
+ // Handle appending of default content.
+ if (parent && parent->IsActiveChildrenElement()) {
+ XBLChildrenElement* childrenEl = static_cast<XBLChildrenElement*>(parent);
+ if (childrenEl->HasInsertedChildren()) {
+ // Appending default content that isn't being used. Ignore.
+ return;
+ }
+
+ childrenEl->MaybeSetupDefaultContent();
+ parent = childrenEl->GetParent();
+ }
+
+ bool first = true;
+ do {
+ nsXBLBinding* binding = GetBindingWithContent(parent);
+ if (!binding) {
+ break;
+ }
+
+ if (binding->HasFilteredInsertionPoints()) {
+ // There are filtered insertion points involved, handle each child
+ // separately.
+ // We could optimize this in the case when we've nested a few levels
+ // deep already, without hitting bindings that have filtered insertion
+ // points.
+ int32_t currentIndex = aNewIndexInContainer;
+ for (nsIContent* currentChild = aFirstNewContent; currentChild;
+ currentChild = currentChild->GetNextSibling()) {
+ HandleChildInsertion(aContainer, currentChild,
+ currentIndex++, true);
+ }
+
+ return;
+ }
+
+ point = binding->GetDefaultInsertionPoint();
+ if (!point) {
+ break;
+ }
+
+ // Even though we're in ContentAppended, nested insertion points force us
+ // to deal with this append as an insertion except in the outermost
+ // binding.
+ if (first) {
+ first = false;
+ for (nsIContent* child = aFirstNewContent; child;
+ child = child->GetNextSibling()) {
+ point->AppendInsertedChild(child);
+ }
+ } else {
+ InsertAppendedContent(point, aFirstNewContent);
+ }
+
+ nsIContent* newParent = point->GetParent();
+ if (newParent == parent) {
+ break;
+ }
+ parent = newParent;
+ } while (parent);
+}
+
+void
+nsBindingManager::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ if (aIndexInContainer == -1) {
+ return;
+ }
+
+ HandleChildInsertion(aContainer, aChild, aIndexInContainer, false);
+}
+
+void
+nsBindingManager::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ aChild->SetXBLInsertionParent(nullptr);
+
+ XBLChildrenElement* point = nullptr;
+ nsIContent* parent = aContainer;
+
+ // Handle appending of default content.
+ if (parent && parent->IsActiveChildrenElement()) {
+ XBLChildrenElement* childrenEl = static_cast<XBLChildrenElement*>(parent);
+ if (childrenEl->HasInsertedChildren()) {
+ // Removing default content that isn't being used. Ignore.
+ return;
+ }
+
+ parent = childrenEl->GetParent();
+ }
+
+ do {
+ nsXBLBinding* binding = GetBindingWithContent(parent);
+ if (!binding) {
+ // If aChild is XBL content, it might have <xbl:children> elements
+ // somewhere under it. We need to inform those elements that they're no
+ // longer in the tree so they can tell their distributed children that
+ // they're no longer distributed under them.
+ // XXX This is wrong. We need to do far more work to update the parent
+ // binding's list of insertion points and to get the new insertion parent
+ // for the newly-distributed children correct.
+ if (aChild->GetBindingParent()) {
+ ClearInsertionPointsRecursively(aChild);
+ }
+ return;
+ }
+
+ point = binding->FindInsertionPointFor(aChild);
+ if (!point) {
+ break;
+ }
+
+ point->RemoveInsertedChild(aChild);
+
+ nsIContent* newParent = point->GetParent();
+ if (newParent == parent) {
+ break;
+ }
+ parent = newParent;
+ } while (parent);
+}
+
+void
+nsBindingManager::ClearInsertionPointsRecursively(nsIContent* aContent)
+{
+ if (aContent->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
+ static_cast<XBLChildrenElement*>(aContent)->ClearInsertedChildren();
+ }
+
+ for (nsIContent* child = aContent->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ ClearInsertionPointsRecursively(child);
+ }
+}
+
+void
+nsBindingManager::DropDocumentReference()
+{
+ mDestroyed = true;
+
+ // Make sure to not run any more XBL constructors
+ mProcessingAttachedStack = true;
+ if (mProcessAttachedQueueEvent) {
+ mProcessAttachedQueueEvent->Revoke();
+ }
+
+ if (mBoundContentSet) {
+ mBoundContentSet->Clear();
+ }
+
+ mDocument = nullptr;
+}
+
+void
+nsBindingManager::Traverse(nsIContent *aContent,
+ nsCycleCollectionTraversalCallback &cb)
+{
+ if (!aContent->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) ||
+ !aContent->IsElement()) {
+ // Don't traverse if content is not in this binding manager.
+ // We also don't traverse non-elements because there should not
+ // be bindings (checking the flag alone is not sufficient because
+ // the flag is also set on children of insertion points that may be
+ // non-elements).
+ return;
+ }
+
+ if (mBoundContentSet && mBoundContentSet->Contains(aContent)) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "[via binding manager] mBoundContentSet entry");
+ cb.NoteXPCOMChild(aContent);
+ }
+
+ nsIXPConnectWrappedJS *value = GetWrappedJS(aContent);
+ if (value) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "[via binding manager] mWrapperTable key");
+ cb.NoteXPCOMChild(aContent);
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "[via binding manager] mWrapperTable value");
+ cb.NoteXPCOMChild(value);
+ }
+}
+
+void
+nsBindingManager::HandleChildInsertion(nsIContent* aContainer,
+ nsIContent* aChild,
+ uint32_t aIndexInContainer,
+ bool aAppend)
+{
+ NS_PRECONDITION(aChild, "Must have child");
+ NS_PRECONDITION(!aContainer ||
+ uint32_t(aContainer->IndexOf(aChild)) == aIndexInContainer,
+ "Child not at the right index?");
+
+ XBLChildrenElement* point = nullptr;
+ nsIContent* parent = aContainer;
+
+ // Handle insertion of default content.
+ if (parent && parent->IsActiveChildrenElement()) {
+ XBLChildrenElement* childrenEl = static_cast<XBLChildrenElement*>(parent);
+ if (childrenEl->HasInsertedChildren()) {
+ // Inserting default content that isn't being used. Ignore.
+ return;
+ }
+
+ childrenEl->MaybeSetupDefaultContent();
+ parent = childrenEl->GetParent();
+ }
+
+ while (parent) {
+ nsXBLBinding* binding = GetBindingWithContent(parent);
+ if (!binding) {
+ break;
+ }
+
+ point = binding->FindInsertionPointFor(aChild);
+ if (!point) {
+ break;
+ }
+
+ // Insert the child into the proper insertion point.
+ // TODO If there were multiple insertion points, this approximation can be
+ // wrong. We need to re-run the distribution algorithm. In the meantime,
+ // this should work well enough.
+ uint32_t index = aAppend ? point->InsertedChildrenLength() : 0;
+ for (nsIContent* currentSibling = aChild->GetPreviousSibling();
+ currentSibling;
+ currentSibling = currentSibling->GetPreviousSibling()) {
+ // If we find one of our previous siblings in the insertion point, the
+ // index following it is the correct insertion point. Otherwise, we guess
+ // based on whether we're appending or inserting.
+ int32_t pointIndex = point->IndexOfInsertedChild(currentSibling);
+ if (pointIndex != -1) {
+ index = pointIndex + 1;
+ break;
+ }
+ }
+
+ point->InsertInsertedChildAt(aChild, index);
+
+ nsIContent* newParent = point->GetParent();
+ if (newParent == parent) {
+ break;
+ }
+
+ parent = newParent;
+ }
+}
+
+
+nsIContent*
+nsBindingManager::FindNestedInsertionPoint(nsIContent* aContainer,
+ nsIContent* aChild)
+{
+ NS_PRECONDITION(aChild->GetParent() == aContainer,
+ "Wrong container");
+
+ nsIContent* parent = aContainer;
+ if (aContainer->IsActiveChildrenElement()) {
+ if (static_cast<XBLChildrenElement*>(aContainer)->
+ HasInsertedChildren()) {
+ return nullptr;
+ }
+ parent = aContainer->GetParent();
+ }
+
+ while (parent) {
+ nsXBLBinding* binding = GetBindingWithContent(parent);
+ if (!binding) {
+ break;
+ }
+
+ XBLChildrenElement* point = binding->FindInsertionPointFor(aChild);
+ if (!point) {
+ return nullptr;
+ }
+
+ nsIContent* newParent = point->GetParent();
+ if (newParent == parent) {
+ break;
+ }
+ parent = newParent;
+ }
+
+ return parent;
+}
+
+nsIContent*
+nsBindingManager::FindNestedSingleInsertionPoint(nsIContent* aContainer,
+ bool* aMulti)
+{
+ *aMulti = false;
+
+ nsIContent* parent = aContainer;
+ if (aContainer->IsActiveChildrenElement()) {
+ if (static_cast<XBLChildrenElement*>(aContainer)->
+ HasInsertedChildren()) {
+ return nullptr;
+ }
+ parent = aContainer->GetParent();
+ }
+
+ while(parent) {
+ nsXBLBinding* binding = GetBindingWithContent(parent);
+ if (!binding) {
+ break;
+ }
+
+ if (binding->HasFilteredInsertionPoints()) {
+ *aMulti = true;
+ return nullptr;
+ }
+
+ XBLChildrenElement* point = binding->GetDefaultInsertionPoint();
+ if (!point) {
+ return nullptr;
+ }
+
+ nsIContent* newParent = point->GetParent();
+ if (newParent == parent) {
+ break;
+ }
+ parent = newParent;
+ }
+
+ return parent;
+}
diff --git a/dom/xbl/nsBindingManager.h b/dom/xbl/nsBindingManager.h
new file mode 100644
index 000000000..a71ff2188
--- /dev/null
+++ b/dom/xbl/nsBindingManager.h
@@ -0,0 +1,235 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBindingManager_h_
+#define nsBindingManager_h_
+
+#include "nsAutoPtr.h"
+#include "nsIContent.h"
+#include "nsStubMutationObserver.h"
+#include "nsHashKeys.h"
+#include "nsInterfaceHashtable.h"
+#include "nsRefPtrHashtable.h"
+#include "nsURIHashKey.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsXBLBinding.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "mozilla/StyleSheet.h"
+
+struct ElementDependentRuleProcessorData;
+class nsIXPConnectWrappedJS;
+class nsIAtom;
+class nsIDOMNodeList;
+class nsIDocument;
+class nsIURI;
+class nsXBLDocumentInfo;
+class nsIStreamListener;
+class nsXBLBinding;
+typedef nsTArray<RefPtr<nsXBLBinding> > nsBindingList;
+class nsIPrincipal;
+class nsITimer;
+
+class nsBindingManager final : public nsStubMutationObserver
+{
+ ~nsBindingManager();
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ explicit nsBindingManager(nsIDocument* aDocument);
+
+ nsXBLBinding* GetBindingWithContent(const nsIContent* aContent);
+
+ void AddBoundContent(nsIContent* aContent);
+ void RemoveBoundContent(nsIContent* aContent);
+
+ /**
+ * Notify the binding manager that an element
+ * has been removed from its document,
+ * so that it can update any bindings or
+ * nsIAnonymousContentCreator-created anonymous
+ * content that may depend on the document.
+ * @param aContent the element that's being moved
+ * @param aOldDocument the old document in which the
+ * content resided.
+ * @param aDestructorHandling whether or not to run the possible XBL
+ * destructor.
+ */
+
+ enum DestructorHandling {
+ eRunDtor,
+ eDoNotRunDtor
+ };
+ void RemovedFromDocument(nsIContent* aContent, nsIDocument* aOldDocument,
+ DestructorHandling aDestructorHandling)
+ {
+ if (aContent->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+ RemovedFromDocumentInternal(aContent, aOldDocument, aDestructorHandling);
+ }
+ }
+ void RemovedFromDocumentInternal(nsIContent* aContent,
+ nsIDocument* aOldDocument,
+ DestructorHandling aDestructorHandling);
+
+ nsIAtom* ResolveTag(nsIContent* aContent, int32_t* aNameSpaceID);
+
+ /**
+ * Return the nodelist of "anonymous" kids for this node. This might
+ * actually include some of the nodes actual DOM kids, if there are
+ * <children> tags directly as kids of <content>. This will only end up
+ * returning a non-null list for nodes which have a binding attached.
+ */
+ nsresult GetAnonymousNodesFor(nsIContent* aContent, nsIDOMNodeList** aResult);
+ nsINodeList* GetAnonymousNodesFor(nsIContent* aContent);
+
+ nsresult ClearBinding(nsIContent* aContent);
+ nsresult LoadBindingDocument(nsIDocument* aBoundDoc, nsIURI* aURL,
+ nsIPrincipal* aOriginPrincipal);
+
+ nsresult AddToAttachedQueue(nsXBLBinding* aBinding);
+ void RemoveFromAttachedQueue(nsXBLBinding* aBinding);
+ void ProcessAttachedQueue(uint32_t aSkipSize = 0)
+ {
+ if (mProcessingAttachedStack || mAttachedStack.Length() <= aSkipSize) {
+ return;
+ }
+
+ ProcessAttachedQueueInternal(aSkipSize);
+ }
+private:
+ void ProcessAttachedQueueInternal(uint32_t aSkipSize);
+
+public:
+
+ void ExecuteDetachedHandlers();
+
+ nsresult PutXBLDocumentInfo(nsXBLDocumentInfo* aDocumentInfo);
+ nsXBLDocumentInfo* GetXBLDocumentInfo(nsIURI* aURI);
+ void RemoveXBLDocumentInfo(nsXBLDocumentInfo* aDocumentInfo);
+
+ nsresult PutLoadingDocListener(nsIURI* aURL, nsIStreamListener* aListener);
+ nsIStreamListener* GetLoadingDocListener(nsIURI* aURL);
+ void RemoveLoadingDocListener(nsIURI* aURL);
+
+ void FlushSkinBindings();
+
+ nsresult GetBindingImplementation(nsIContent* aContent, REFNSIID aIID, void** aResult);
+
+ // Style rule methods
+ nsresult WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc,
+ ElementDependentRuleProcessorData* aData,
+ bool* aCutOffInheritance);
+
+ void WalkAllRules(nsIStyleRuleProcessor::EnumFunc aFunc,
+ ElementDependentRuleProcessorData* aData);
+ /**
+ * Do any processing that needs to happen as a result of a change in
+ * the characteristics of the medium, and return whether this rule
+ * processor's rules have changed (e.g., because of media queries).
+ */
+ nsresult MediumFeaturesChanged(nsPresContext* aPresContext,
+ bool* aRulesChanged);
+
+ void AppendAllSheets(nsTArray<mozilla::StyleSheet*>& aArray);
+
+ void Traverse(nsIContent *aContent,
+ nsCycleCollectionTraversalCallback &cb);
+
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsBindingManager)
+
+ // Notify the binding manager when an outermost update begins and
+ // ends. The end method can execute script.
+ void BeginOutermostUpdate()
+ {
+ mAttachedStackSizeOnOutermost = mAttachedStack.Length();
+ }
+
+ void EndOutermostUpdate()
+ {
+ if (!mProcessingAttachedStack) {
+ ProcessAttachedQueue(mAttachedStackSizeOnOutermost);
+ mAttachedStackSizeOnOutermost = 0;
+ }
+ }
+
+ // When removing an insertion point or a parent of one, clear the insertion
+ // points and their insertion parents.
+ void ClearInsertionPointsRecursively(nsIContent* aContent);
+
+ // Called when the document is going away
+ void DropDocumentReference();
+
+ nsIContent* FindNestedInsertionPoint(nsIContent* aContainer,
+ nsIContent* aChild);
+
+ nsIContent* FindNestedSingleInsertionPoint(nsIContent* aContainer, bool* aMulti);
+
+protected:
+ nsIXPConnectWrappedJS* GetWrappedJS(nsIContent* aContent);
+ nsresult SetWrappedJS(nsIContent* aContent, nsIXPConnectWrappedJS* aResult);
+
+ // Called by ContentAppended and ContentInserted to handle a single child
+ // insertion. aChild must not be null. aContainer may be null.
+ // aIndexInContainer is the index of the child in the parent. aAppend is
+ // true if this child is being appended, not inserted.
+ void HandleChildInsertion(nsIContent* aContainer, nsIContent* aChild,
+ uint32_t aIndexInContainer, bool aAppend);
+
+ // Same as ProcessAttachedQueue, but also nulls out
+ // mProcessAttachedQueueEvent
+ void DoProcessAttachedQueue();
+
+ // Post an event to process the attached queue.
+ void PostProcessAttachedQueueEvent();
+
+ // Call PostProcessAttachedQueueEvent() on a timer.
+ static void PostPAQEventCallback(nsITimer* aTimer, void* aClosure);
+
+// MEMBER VARIABLES
+protected:
+ // A set of nsIContent that currently have a binding installed.
+ nsAutoPtr<nsTHashtable<nsRefPtrHashKey<nsIContent> > > mBoundContentSet;
+
+ // A mapping from nsIContent* to nsIXPWrappedJS* (an XPConnect
+ // wrapper for JS objects). For XBL bindings that implement XPIDL
+ // interfaces, and that get referred to from C++, this table caches
+ // the XPConnect wrapper for the binding. By caching it, I control
+ // its lifetime, and I prevent a re-wrap of the same script object
+ // (in the case where multiple bindings in an XBL inheritance chain
+ // both implement an XPIDL interface).
+ typedef nsInterfaceHashtable<nsISupportsHashKey, nsIXPConnectWrappedJS> WrapperHashtable;
+ nsAutoPtr<WrapperHashtable> mWrapperTable;
+
+ // A mapping from a URL (a string) to nsXBLDocumentInfo*. This table
+ // is the cache of all binding documents that have been loaded by a
+ // given bound document.
+ nsAutoPtr<nsRefPtrHashtable<nsURIHashKey,nsXBLDocumentInfo> > mDocumentTable;
+
+ // A mapping from a URL (a string) to a nsIStreamListener. This
+ // table is the currently loading binding docs. If they're in this
+ // table, they have not yet finished loading.
+ nsAutoPtr<nsInterfaceHashtable<nsURIHashKey,nsIStreamListener> > mLoadingDocTable;
+
+ // A queue of binding attached event handlers that are awaiting execution.
+ nsBindingList mAttachedStack;
+ bool mProcessingAttachedStack;
+ bool mDestroyed;
+ uint32_t mAttachedStackSizeOnOutermost;
+
+ // Our posted event to process the attached queue, if any
+ friend class nsRunnableMethod<nsBindingManager>;
+ RefPtr< nsRunnableMethod<nsBindingManager> > mProcessAttachedQueueEvent;
+
+ // Our document. This is a weak ref; the document owns us
+ nsIDocument* mDocument;
+};
+
+#endif
diff --git a/dom/xbl/nsXBLBinding.cpp b/dom/xbl/nsXBLBinding.cpp
new file mode 100644
index 000000000..6ae17c4c0
--- /dev/null
+++ b/dom/xbl/nsXBLBinding.cpp
@@ -0,0 +1,1247 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsXBLDocumentInfo.h"
+#include "nsIInputStream.h"
+#include "nsNameSpaceManager.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "plstr.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsContentUtils.h"
+#include "ChildIterator.h"
+#ifdef MOZ_XUL
+#include "nsIXULDocument.h"
+#endif
+#include "nsIXMLContentSink.h"
+#include "nsContentCID.h"
+#include "mozilla/dom/XMLDocument.h"
+#include "jsapi.h"
+#include "nsXBLService.h"
+#include "nsIXPConnect.h"
+#include "nsIScriptContext.h"
+#include "nsCRT.h"
+
+// Event listeners
+#include "mozilla/EventListenerManager.h"
+#include "nsIDOMEventListener.h"
+#include "nsAttrName.h"
+
+#include "nsGkAtoms.h"
+
+#include "nsXBLPrototypeHandler.h"
+
+#include "nsXBLPrototypeBinding.h"
+#include "nsXBLBinding.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "mozilla/dom/XBLChildrenElement.h"
+
+#include "prprf.h"
+#include "nsNodeUtils.h"
+#include "nsJSUtils.h"
+
+// Nasty hack. Maybe we could move some of the classinfo utility methods
+// (e.g. WrapNative) over to nsContentUtils?
+#include "nsDOMClassInfo.h"
+
+#include "mozilla/DeferredFinalize.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/ServoStyleSet.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// Helper classes
+
+/***********************************************************************/
+//
+// The JS class for XBLBinding
+//
+static void
+XBLFinalize(JSFreeOp *fop, JSObject *obj)
+{
+ nsXBLDocumentInfo* docInfo =
+ static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(obj));
+ DeferredFinalize(docInfo);
+}
+
+static bool
+XBLEnumerate(JSContext *cx, JS::Handle<JSObject*> obj)
+{
+ nsXBLPrototypeBinding* protoBinding =
+ static_cast<nsXBLPrototypeBinding*>(::JS_GetReservedSlot(obj, 0).toPrivate());
+ MOZ_ASSERT(protoBinding);
+
+ return protoBinding->ResolveAllFields(cx, obj);
+}
+
+static const JSClassOps gPrototypeJSClassOps = {
+ nullptr, nullptr, nullptr, nullptr,
+ XBLEnumerate, nullptr,
+ nullptr, XBLFinalize,
+ nullptr, nullptr, nullptr, nullptr
+};
+
+static const JSClass gPrototypeJSClass = {
+ "XBL prototype JSClass",
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_PRIVATE_IS_NSISUPPORTS |
+ JSCLASS_FOREGROUND_FINALIZE |
+ // Our one reserved slot holds the relevant nsXBLPrototypeBinding
+ JSCLASS_HAS_RESERVED_SLOTS(1),
+ &gPrototypeJSClassOps
+};
+
+// Implementation /////////////////////////////////////////////////////////////////
+
+// Constructors/Destructors
+nsXBLBinding::nsXBLBinding(nsXBLPrototypeBinding* aBinding)
+ : mMarkedForDeath(false)
+ , mUsingContentXBLScope(false)
+ , mIsShadowRootBinding(false)
+ , mPrototypeBinding(aBinding)
+{
+ NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!");
+ // Grab a ref to the document info so the prototype binding won't die
+ NS_ADDREF(mPrototypeBinding->XBLDocumentInfo());
+}
+
+// Constructor used by web components.
+nsXBLBinding::nsXBLBinding(ShadowRoot* aShadowRoot, nsXBLPrototypeBinding* aBinding)
+ : mMarkedForDeath(false),
+ mUsingContentXBLScope(false),
+ mIsShadowRootBinding(true),
+ mPrototypeBinding(aBinding),
+ mContent(aShadowRoot)
+{
+ NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!");
+ // Grab a ref to the document info so the prototype binding won't die
+ NS_ADDREF(mPrototypeBinding->XBLDocumentInfo());
+}
+
+nsXBLBinding::~nsXBLBinding(void)
+{
+ if (mContent && !mIsShadowRootBinding) {
+ // It is unnecessary to uninstall anonymous content in a shadow tree
+ // because the ShadowRoot itself is a DocumentFragment and does not
+ // need any additional cleanup.
+ nsXBLBinding::UninstallAnonymousContent(mContent->OwnerDoc(), mContent);
+ }
+ nsXBLDocumentInfo* info = mPrototypeBinding->XBLDocumentInfo();
+ NS_RELEASE(info);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLBinding)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLBinding)
+ // XXX Probably can't unlink mPrototypeBinding->XBLDocumentInfo(), because
+ // mPrototypeBinding is weak.
+ if (tmp->mContent && !tmp->mIsShadowRootBinding) {
+ nsXBLBinding::UninstallAnonymousContent(tmp->mContent->OwnerDoc(),
+ tmp->mContent);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNextBinding)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDefaultInsertionPoint)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mInsertionPoints)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContentList)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXBLBinding)
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mPrototypeBinding->XBLDocumentInfo()");
+ cb.NoteXPCOMChild(tmp->mPrototypeBinding->XBLDocumentInfo());
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNextBinding)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDefaultInsertionPoint)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInsertionPoints)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContentList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsXBLBinding, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsXBLBinding, Release)
+
+void
+nsXBLBinding::SetBaseBinding(nsXBLBinding* aBinding)
+{
+ if (mNextBinding) {
+ NS_ERROR("Base XBL binding is already defined!");
+ return;
+ }
+
+ mNextBinding = aBinding; // Comptr handles rel/add
+}
+
+nsXBLBinding*
+nsXBLBinding::GetBindingWithContent()
+{
+ if (mContent) {
+ return this;
+ }
+
+ return mNextBinding ? mNextBinding->GetBindingWithContent() : nullptr;
+}
+
+void
+nsXBLBinding::InstallAnonymousContent(nsIContent* aAnonParent, nsIContent* aElement,
+ bool aChromeOnlyContent)
+{
+ // We need to ensure two things.
+ // (1) The anonymous content should be fooled into thinking it's in the bound
+ // element's document, assuming that the bound element is in a document
+ // Note that we don't change the current doc of aAnonParent here, since that
+ // quite simply does not matter. aAnonParent is just a way of keeping refs
+ // to all its kids, which are anonymous content from the point of view of
+ // aElement.
+ // (2) The children's parent back pointer should not be to this synthetic root
+ // but should instead point to the enclosing parent element.
+ nsIDocument* doc = aElement->GetUncomposedDoc();
+ bool allowScripts = AllowScripts();
+
+ nsAutoScriptBlocker scriptBlocker;
+ for (nsIContent* child = aAnonParent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ child->UnbindFromTree();
+ if (aChromeOnlyContent) {
+ child->SetFlags(NODE_CHROME_ONLY_ACCESS |
+ NODE_IS_ROOT_OF_CHROME_ONLY_ACCESS);
+ }
+ nsresult rv =
+ child->BindToTree(doc, aElement, mBoundElement, allowScripts);
+ if (NS_FAILED(rv)) {
+ // Oh, well... Just give up.
+ // XXXbz This really shouldn't be a void method!
+ child->UnbindFromTree();
+ return;
+ }
+
+ child->SetFlags(NODE_IS_ANONYMOUS_ROOT);
+
+#ifdef MOZ_XUL
+ // To make XUL templates work (and other goodies that happen when
+ // an element is added to a XUL document), we need to notify the
+ // XUL document using its special API.
+ nsCOMPtr<nsIXULDocument> xuldoc(do_QueryInterface(doc));
+ if (xuldoc)
+ xuldoc->AddSubtreeToDocument(child);
+#endif
+ }
+}
+
+void
+nsXBLBinding::UninstallAnonymousContent(nsIDocument* aDocument,
+ nsIContent* aAnonParent)
+{
+ nsAutoScriptBlocker scriptBlocker;
+ // Hold a strong ref while doing this, just in case.
+ nsCOMPtr<nsIContent> anonParent = aAnonParent;
+#ifdef MOZ_XUL
+ nsCOMPtr<nsIXULDocument> xuldoc =
+ do_QueryInterface(aDocument);
+#endif
+ for (nsIContent* child = aAnonParent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ child->UnbindFromTree();
+#ifdef MOZ_XUL
+ if (xuldoc) {
+ xuldoc->RemoveSubtreeFromDocument(child);
+ }
+#endif
+ }
+}
+
+void
+nsXBLBinding::SetBoundElement(nsIContent* aElement)
+{
+ mBoundElement = aElement;
+ if (mNextBinding)
+ mNextBinding->SetBoundElement(aElement);
+
+ if (!mBoundElement) {
+ return;
+ }
+
+ // Compute whether we're using an XBL scope.
+ //
+ // We disable XBL scopes for remote XUL, where we care about compat more
+ // than security. So we need to know whether we're using an XBL scope so that
+ // we can decide what to do about untrusted events when "allowuntrusted"
+ // is not given in the handler declaration.
+ nsCOMPtr<nsIGlobalObject> go = mBoundElement->OwnerDoc()->GetScopeObject();
+ NS_ENSURE_TRUE_VOID(go && go->GetGlobalJSObject());
+ mUsingContentXBLScope = xpc::UseContentXBLScope(js::GetObjectCompartment(go->GetGlobalJSObject()));
+}
+
+bool
+nsXBLBinding::HasStyleSheets() const
+{
+ // Find out if we need to re-resolve style. We'll need to do this
+ // if we have additional stylesheets in our binding document.
+ if (mPrototypeBinding->HasStyleSheets())
+ return true;
+
+ return mNextBinding ? mNextBinding->HasStyleSheets() : false;
+}
+
+void
+nsXBLBinding::GenerateAnonymousContent()
+{
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "Someone forgot a script blocker");
+
+ // Fetch the content element for this binding.
+ nsIContent* content =
+ mPrototypeBinding->GetImmediateChild(nsGkAtoms::content);
+
+ if (!content) {
+ // We have no anonymous content.
+ if (mNextBinding)
+ mNextBinding->GenerateAnonymousContent();
+
+ return;
+ }
+
+ // Find out if we're really building kids or if we're just
+ // using the attribute-setting shorthand hack.
+ uint32_t contentCount = content->GetChildCount();
+
+ // Plan to build the content by default.
+ bool hasContent = (contentCount > 0);
+ if (hasContent) {
+ nsIDocument* doc = mBoundElement->OwnerDoc();
+
+ nsCOMPtr<nsINode> clonedNode;
+ nsCOMArray<nsINode> nodesWithProperties;
+ nsNodeUtils::Clone(content, true, doc->NodeInfoManager(),
+ nodesWithProperties, getter_AddRefs(clonedNode));
+ mContent = clonedNode->AsElement();
+
+ // Search for <xbl:children> elements in the XBL content. In the presence
+ // of multiple default insertion points, we use the last one in document
+ // order.
+ for (nsIContent* child = mContent; child; child = child->GetNextNode(mContent)) {
+ if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
+ XBLChildrenElement* point = static_cast<XBLChildrenElement*>(child);
+ if (point->IsDefaultInsertion()) {
+ mDefaultInsertionPoint = point;
+ } else {
+ mInsertionPoints.AppendElement(point);
+ }
+ }
+ }
+
+ // Do this after looking for <children> as this messes up the parent
+ // pointer which would make the GetNextNode call above fail
+ InstallAnonymousContent(mContent, mBoundElement,
+ mPrototypeBinding->ChromeOnlyContent());
+
+ // Insert explicit children into insertion points
+ if (mDefaultInsertionPoint && mInsertionPoints.IsEmpty()) {
+ ExplicitChildIterator iter(mBoundElement);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ mDefaultInsertionPoint->AppendInsertedChild(child);
+ }
+ } else {
+ // It is odd to come into this code if mInsertionPoints is not empty, but
+ // we need to make sure to do the compatibility hack below if the bound
+ // node has any non <xul:template> or <xul:observes> children.
+ ExplicitChildIterator iter(mBoundElement);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ XBLChildrenElement* point = FindInsertionPointForInternal(child);
+ if (point) {
+ point->AppendInsertedChild(child);
+ } else {
+ NodeInfo *ni = child->NodeInfo();
+ if (ni->NamespaceID() != kNameSpaceID_XUL ||
+ (!ni->Equals(nsGkAtoms::_template) &&
+ !ni->Equals(nsGkAtoms::observes))) {
+ // Compatibility hack. For some reason the original XBL
+ // implementation dropped the content of a binding if any child of
+ // the bound element didn't match any of the <children> in the
+ // binding. This became a pseudo-API that we have to maintain.
+
+ // Undo InstallAnonymousContent
+ UninstallAnonymousContent(doc, mContent);
+
+ // Clear out our children elements to avoid dangling references.
+ ClearInsertionPoints();
+
+ // Pretend as though there was no content in the binding.
+ mContent = nullptr;
+ return;
+ }
+ }
+ }
+ }
+
+ // Set binding parent on default content if need
+ if (mDefaultInsertionPoint) {
+ mDefaultInsertionPoint->MaybeSetupDefaultContent();
+ }
+ for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
+ mInsertionPoints[i]->MaybeSetupDefaultContent();
+ }
+
+ mPrototypeBinding->SetInitialAttributes(mBoundElement, mContent);
+ }
+
+ // Always check the content element for potential attributes.
+ // This shorthand hack always happens, even when we didn't
+ // build anonymous content.
+ BorrowedAttrInfo attrInfo;
+ for (uint32_t i = 0; (attrInfo = content->GetAttrInfoAt(i)); ++i) {
+ int32_t namespaceID = attrInfo.mName->NamespaceID();
+ // Hold a strong reference here so that the atom doesn't go away during
+ // UnsetAttr.
+ nsCOMPtr<nsIAtom> name = attrInfo.mName->LocalName();
+
+ if (name != nsGkAtoms::includes) {
+ if (!nsContentUtils::HasNonEmptyAttr(mBoundElement, namespaceID, name)) {
+ nsAutoString value2;
+ attrInfo.mValue->ToString(value2);
+ mBoundElement->SetAttr(namespaceID, name, attrInfo.mName->GetPrefix(),
+ value2, false);
+ }
+ }
+
+ // Conserve space by wiping the attributes off the clone.
+ if (mContent)
+ mContent->UnsetAttr(namespaceID, name, false);
+ }
+
+ // Now that we've finished shuffling the tree around, go ahead and restyle it
+ // since frame construction is about to happen.
+ nsIPresShell* presShell = mBoundElement->OwnerDoc()->GetShell();
+ ServoStyleSet* servoSet = presShell->StyleSet()->GetAsServo();
+ if (servoSet) {
+ mBoundElement->SetHasDirtyDescendantsForServo();
+ servoSet->StyleNewChildren(mBoundElement);
+ }
+}
+
+nsIURI*
+nsXBLBinding::GetSourceDocURI()
+{
+ nsIContent* targetContent =
+ mPrototypeBinding->GetImmediateChild(nsGkAtoms::content);
+ if (!targetContent) {
+ return nullptr;
+ }
+
+ return targetContent->OwnerDoc()->GetDocumentURI();
+}
+
+XBLChildrenElement*
+nsXBLBinding::FindInsertionPointFor(nsIContent* aChild)
+{
+ // XXX We should get rid of this function as it causes us to traverse the
+ // binding chain multiple times
+ if (mContent) {
+ return FindInsertionPointForInternal(aChild);
+ }
+
+ return mNextBinding ? mNextBinding->FindInsertionPointFor(aChild)
+ : nullptr;
+}
+
+XBLChildrenElement*
+nsXBLBinding::FindInsertionPointForInternal(nsIContent* aChild)
+{
+ for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
+ XBLChildrenElement* point = mInsertionPoints[i];
+ if (point->Includes(aChild)) {
+ return point;
+ }
+ }
+
+ return mDefaultInsertionPoint;
+}
+
+void
+nsXBLBinding::ClearInsertionPoints()
+{
+ if (mDefaultInsertionPoint) {
+ mDefaultInsertionPoint->ClearInsertedChildren();
+ }
+
+ for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
+ mInsertionPoints[i]->ClearInsertedChildren();
+ }
+}
+
+nsAnonymousContentList*
+nsXBLBinding::GetAnonymousNodeList()
+{
+ if (!mContent) {
+ return mNextBinding ? mNextBinding->GetAnonymousNodeList() : nullptr;
+ }
+
+ if (!mAnonymousContentList) {
+ mAnonymousContentList = new nsAnonymousContentList(mContent);
+ }
+
+ return mAnonymousContentList;
+}
+
+void
+nsXBLBinding::InstallEventHandlers()
+{
+ // Don't install handlers if scripts aren't allowed.
+ if (AllowScripts()) {
+ // Fetch the handlers prototypes for this binding.
+ nsXBLPrototypeHandler* handlerChain = mPrototypeBinding->GetPrototypeHandlers();
+
+ if (handlerChain) {
+ EventListenerManager* manager = mBoundElement->GetOrCreateListenerManager();
+ if (!manager)
+ return;
+
+ bool isChromeDoc =
+ nsContentUtils::IsChromeDoc(mBoundElement->OwnerDoc());
+ bool isChromeBinding = mPrototypeBinding->IsChrome();
+ nsXBLPrototypeHandler* curr;
+ for (curr = handlerChain; curr; curr = curr->GetNextHandler()) {
+ // Fetch the event type.
+ nsCOMPtr<nsIAtom> eventAtom = curr->GetEventName();
+ if (!eventAtom ||
+ eventAtom == nsGkAtoms::keyup ||
+ eventAtom == nsGkAtoms::keydown ||
+ eventAtom == nsGkAtoms::keypress)
+ continue;
+
+ nsXBLEventHandler* handler = curr->GetEventHandler();
+ if (handler) {
+ // Figure out if we're using capturing or not.
+ EventListenerFlags flags;
+ flags.mCapture = (curr->GetPhase() == NS_PHASE_CAPTURING);
+
+ // If this is a command, add it in the system event group
+ if ((curr->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
+ NS_HANDLER_TYPE_SYSTEM)) &&
+ (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
+ flags.mInSystemGroup = true;
+ }
+
+ bool hasAllowUntrustedAttr = curr->HasAllowUntrustedAttr();
+ if ((hasAllowUntrustedAttr && curr->AllowUntrustedEvents()) ||
+ (!hasAllowUntrustedAttr && !isChromeDoc && !mUsingContentXBLScope)) {
+ flags.mAllowUntrustedEvents = true;
+ }
+
+ manager->AddEventListenerByType(handler,
+ nsDependentAtomString(eventAtom),
+ flags);
+ }
+ }
+
+ const nsCOMArray<nsXBLKeyEventHandler>* keyHandlers =
+ mPrototypeBinding->GetKeyEventHandlers();
+ int32_t i;
+ for (i = 0; i < keyHandlers->Count(); ++i) {
+ nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i);
+ handler->SetIsBoundToChrome(isChromeDoc);
+ handler->SetUsingContentXBLScope(mUsingContentXBLScope);
+
+ nsAutoString type;
+ handler->GetEventName(type);
+
+ // If this is a command, add it in the system event group, otherwise
+ // add it to the standard event group.
+
+ // Figure out if we're using capturing or not.
+ EventListenerFlags flags;
+ flags.mCapture = (handler->GetPhase() == NS_PHASE_CAPTURING);
+
+ if ((handler->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
+ NS_HANDLER_TYPE_SYSTEM)) &&
+ (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
+ flags.mInSystemGroup = true;
+ }
+
+ // For key handlers we have to set mAllowUntrustedEvents flag.
+ // Whether the handling of the event is allowed or not is handled in
+ // nsXBLKeyEventHandler::HandleEvent
+ flags.mAllowUntrustedEvents = true;
+
+ manager->AddEventListenerByType(handler, type, flags);
+ }
+ }
+ }
+
+ if (mNextBinding)
+ mNextBinding->InstallEventHandlers();
+}
+
+nsresult
+nsXBLBinding::InstallImplementation()
+{
+ // Always install the base class properties first, so that
+ // derived classes can reference the base class properties.
+
+ if (mNextBinding) {
+ nsresult rv = mNextBinding->InstallImplementation();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // iterate through each property in the prototype's list and install the property.
+ if (AllowScripts())
+ return mPrototypeBinding->InstallImplementation(this);
+
+ return NS_OK;
+}
+
+nsIAtom*
+nsXBLBinding::GetBaseTag(int32_t* aNameSpaceID)
+{
+ nsIAtom *tag = mPrototypeBinding->GetBaseTag(aNameSpaceID);
+ if (!tag && mNextBinding)
+ return mNextBinding->GetBaseTag(aNameSpaceID);
+
+ return tag;
+}
+
+void
+nsXBLBinding::AttributeChanged(nsIAtom* aAttribute, int32_t aNameSpaceID,
+ bool aRemoveFlag, bool aNotify)
+{
+ // XXX Change if we ever allow multiple bindings in a chain to contribute anonymous content
+ if (!mContent) {
+ if (mNextBinding)
+ mNextBinding->AttributeChanged(aAttribute, aNameSpaceID,
+ aRemoveFlag, aNotify);
+ } else {
+ mPrototypeBinding->AttributeChanged(aAttribute, aNameSpaceID, aRemoveFlag,
+ mBoundElement, mContent, aNotify);
+ }
+}
+
+void
+nsXBLBinding::ExecuteAttachedHandler()
+{
+ if (mNextBinding)
+ mNextBinding->ExecuteAttachedHandler();
+
+ if (AllowScripts())
+ mPrototypeBinding->BindingAttached(mBoundElement);
+}
+
+void
+nsXBLBinding::ExecuteDetachedHandler()
+{
+ if (AllowScripts())
+ mPrototypeBinding->BindingDetached(mBoundElement);
+
+ if (mNextBinding)
+ mNextBinding->ExecuteDetachedHandler();
+}
+
+void
+nsXBLBinding::UnhookEventHandlers()
+{
+ nsXBLPrototypeHandler* handlerChain = mPrototypeBinding->GetPrototypeHandlers();
+
+ if (handlerChain) {
+ EventListenerManager* manager = mBoundElement->GetExistingListenerManager();
+ if (!manager) {
+ return;
+ }
+
+ bool isChromeBinding = mPrototypeBinding->IsChrome();
+ nsXBLPrototypeHandler* curr;
+ for (curr = handlerChain; curr; curr = curr->GetNextHandler()) {
+ nsXBLEventHandler* handler = curr->GetCachedEventHandler();
+ if (!handler) {
+ continue;
+ }
+
+ nsCOMPtr<nsIAtom> eventAtom = curr->GetEventName();
+ if (!eventAtom ||
+ eventAtom == nsGkAtoms::keyup ||
+ eventAtom == nsGkAtoms::keydown ||
+ eventAtom == nsGkAtoms::keypress)
+ continue;
+
+ // Figure out if we're using capturing or not.
+ EventListenerFlags flags;
+ flags.mCapture = (curr->GetPhase() == NS_PHASE_CAPTURING);
+
+ // If this is a command, remove it from the system event group,
+ // otherwise remove it from the standard event group.
+
+ if ((curr->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
+ NS_HANDLER_TYPE_SYSTEM)) &&
+ (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
+ flags.mInSystemGroup = true;
+ }
+
+ manager->RemoveEventListenerByType(handler,
+ nsDependentAtomString(eventAtom),
+ flags);
+ }
+
+ const nsCOMArray<nsXBLKeyEventHandler>* keyHandlers =
+ mPrototypeBinding->GetKeyEventHandlers();
+ int32_t i;
+ for (i = 0; i < keyHandlers->Count(); ++i) {
+ nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i);
+
+ nsAutoString type;
+ handler->GetEventName(type);
+
+ // Figure out if we're using capturing or not.
+ EventListenerFlags flags;
+ flags.mCapture = (handler->GetPhase() == NS_PHASE_CAPTURING);
+
+ // If this is a command, remove it from the system event group, otherwise
+ // remove it from the standard event group.
+
+ if ((handler->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND | NS_HANDLER_TYPE_SYSTEM)) &&
+ (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
+ flags.mInSystemGroup = true;
+ }
+
+ manager->RemoveEventListenerByType(handler, type, flags);
+ }
+ }
+}
+
+static void
+UpdateInsertionParent(XBLChildrenElement* aPoint,
+ nsIContent* aOldBoundElement)
+{
+ if (aPoint->IsDefaultInsertion()) {
+ return;
+ }
+
+ for (size_t i = 0; i < aPoint->InsertedChildrenLength(); ++i) {
+ nsIContent* child = aPoint->InsertedChild(i);
+
+ MOZ_ASSERT(child->GetParentNode());
+
+ // Here, we're iterating children that we inserted. There are two cases:
+ // either |child| is an explicit child of |aOldBoundElement| and is no
+ // longer inserted anywhere or it's a child of a <children> element
+ // parented to |aOldBoundElement|. In the former case, the child is no
+ // longer inserted anywhere, so we set its insertion parent to null. In the
+ // latter case, the child is now inserted into |aOldBoundElement| from some
+ // binding above us, so we set its insertion parent to aOldBoundElement.
+ if (child->GetParentNode() == aOldBoundElement) {
+ child->SetXBLInsertionParent(nullptr);
+ } else {
+ child->SetXBLInsertionParent(aOldBoundElement);
+ }
+ }
+}
+
+void
+nsXBLBinding::ChangeDocument(nsIDocument* aOldDocument, nsIDocument* aNewDocument)
+{
+ if (aOldDocument == aNewDocument)
+ return;
+
+ // Now the binding dies. Unhook our prototypes.
+ if (mPrototypeBinding->HasImplementation()) {
+ AutoJSAPI jsapi;
+ // Init might fail here if we've cycle-collected the global object, since
+ // the Unlink phase of cycle collection happens after JS GC finalization.
+ // But in that case, we don't care about fixing the prototype chain, since
+ // everything's going away immediately.
+ if (jsapi.Init(aOldDocument->GetScopeObject())) {
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSObject*> scriptObject(cx, mBoundElement->GetWrapper());
+ if (scriptObject) {
+ // XXX Stay in sync! What if a layered binding has an
+ // <interface>?!
+ // XXXbz what does that comment mean, really? It seems to date
+ // back to when there was such a thing as an <interface>, whever
+ // that was...
+
+ // Find the right prototype.
+ JSAutoCompartment ac(cx, scriptObject);
+
+ JS::Rooted<JSObject*> base(cx, scriptObject);
+ JS::Rooted<JSObject*> proto(cx);
+ for ( ; true; base = proto) { // Will break out on null proto
+ if (!JS_GetPrototype(cx, base, &proto)) {
+ return;
+ }
+ if (!proto) {
+ break;
+ }
+
+ if (JS_GetClass(proto) != &gPrototypeJSClass) {
+ // Clearly not the right class
+ continue;
+ }
+
+ RefPtr<nsXBLDocumentInfo> docInfo =
+ static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(proto));
+ if (!docInfo) {
+ // Not the proto we seek
+ continue;
+ }
+
+ JS::Value protoBinding = ::JS_GetReservedSlot(proto, 0);
+
+ if (protoBinding.toPrivate() != mPrototypeBinding) {
+ // Not the right binding
+ continue;
+ }
+
+ // Alright! This is the right prototype. Pull it out of the
+ // proto chain.
+ JS::Rooted<JSObject*> grandProto(cx);
+ if (!JS_GetPrototype(cx, proto, &grandProto)) {
+ return;
+ }
+ ::JS_SetPrototype(cx, base, grandProto);
+ break;
+ }
+
+ mPrototypeBinding->UndefineFields(cx, scriptObject);
+
+ // Don't remove the reference from the document to the
+ // wrapper here since it'll be removed by the element
+ // itself when that's taken out of the document.
+ }
+ }
+ }
+
+ // Remove our event handlers
+ UnhookEventHandlers();
+
+ {
+ nsAutoScriptBlocker scriptBlocker;
+
+ // Then do our ancestors. This reverses the construction order, so that at
+ // all times things are consistent as far as everyone is concerned.
+ if (mNextBinding) {
+ mNextBinding->ChangeDocument(aOldDocument, aNewDocument);
+ }
+
+ // Update the anonymous content.
+ // XXXbz why not only for style bindings?
+ if (mContent && !mIsShadowRootBinding) {
+ nsXBLBinding::UninstallAnonymousContent(aOldDocument, mContent);
+ }
+
+ // Now that we've unbound our anonymous content from the tree and updated
+ // its binding parent, update the insertion parent for content inserted
+ // into our <children> elements.
+ if (mDefaultInsertionPoint) {
+ UpdateInsertionParent(mDefaultInsertionPoint, mBoundElement);
+ }
+
+ for (size_t i = 0; i < mInsertionPoints.Length(); ++i) {
+ UpdateInsertionParent(mInsertionPoints[i], mBoundElement);
+ }
+
+ // Now that our inserted children no longer think they're inserted
+ // anywhere, make sure our internal state reflects that as well.
+ ClearInsertionPoints();
+ }
+}
+
+bool
+nsXBLBinding::InheritsStyle() const
+{
+ // XXX Will have to change if we ever allow multiple bindings to contribute anonymous content.
+ // Most derived binding with anonymous content determines style inheritance for now.
+
+ // XXX What about bindings with <content> but no kids, e.g., my treecell-text binding?
+ if (mContent)
+ return mPrototypeBinding->InheritsStyle();
+
+ if (mNextBinding)
+ return mNextBinding->InheritsStyle();
+
+ return true;
+}
+
+void
+nsXBLBinding::WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc, void* aData)
+{
+ if (mNextBinding)
+ mNextBinding->WalkRules(aFunc, aData);
+
+ nsIStyleRuleProcessor *rules = mPrototypeBinding->GetRuleProcessor();
+ if (rules)
+ (*aFunc)(rules, aData);
+}
+
+// Internal helper methods ////////////////////////////////////////////////////////////////
+
+// Get or create a WeakMap object on a given XBL-hosting global.
+//
+// The scheme is as follows. XBL-hosting globals (either privileged content
+// Windows or XBL scopes) get two lazily-defined WeakMap properties. Each
+// WeakMap is keyed by the grand-proto - i.e. the original prototype of the
+// content before it was bound, and the prototype of the class object that we
+// splice in. The values in the WeakMap are simple dictionary-style objects,
+// mapping from XBL class names to class objects.
+static JSObject*
+GetOrCreateClassObjectMap(JSContext *cx, JS::Handle<JSObject*> scope, const char *mapName)
+{
+ AssertSameCompartment(cx, scope);
+ MOZ_ASSERT(JS_IsGlobalObject(scope));
+ MOZ_ASSERT(scope == xpc::GetXBLScopeOrGlobal(cx, scope));
+
+ // First, see if the map is already defined.
+ JS::Rooted<JS::PropertyDescriptor> desc(cx);
+ if (!JS_GetOwnPropertyDescriptor(cx, scope, mapName, &desc)) {
+ return nullptr;
+ }
+ if (desc.object() && desc.value().isObject() &&
+ JS::IsWeakMapObject(&desc.value().toObject())) {
+ return &desc.value().toObject();
+ }
+
+ // It's not there. Create and define it.
+ JS::Rooted<JSObject*> map(cx, JS::NewWeakMapObject(cx));
+ if (!map || !JS_DefineProperty(cx, scope, mapName, map,
+ JSPROP_PERMANENT | JSPROP_READONLY,
+ JS_STUBGETTER, JS_STUBSETTER))
+ {
+ return nullptr;
+ }
+ return map;
+}
+
+static JSObject*
+GetOrCreateMapEntryForPrototype(JSContext *cx, JS::Handle<JSObject*> proto)
+{
+ AssertSameCompartment(cx, proto);
+ // We want to hang our class objects off the XBL scope. But since we also
+ // hoist anonymous content into the XBL scope, this creates the potential for
+ // tricky collisions, since we can simultaneously have a bound in-content
+ // node with grand-proto HTMLDivElement and a bound anonymous node whose
+ // grand-proto is the XBL scope's cross-compartment wrapper to HTMLDivElement.
+ // Since we have to wrap the WeakMap keys into its scope, this distinction
+ // would be lost if we don't do something about it.
+ //
+ // So we define two maps - one class objects that live in content (prototyped
+ // to content prototypes), and the other for class objects that live in the
+ // XBL scope (prototyped to cross-compartment-wrapped content prototypes).
+ const char* name = xpc::IsInContentXBLScope(proto) ? "__ContentClassObjectMap__"
+ : "__XBLClassObjectMap__";
+
+ // Now, enter the XBL scope, since that's where we need to operate, and wrap
+ // the proto accordingly. We hang the map off of the content XBL scope for
+ // content, and the Window for chrome (whether add-ons are involved or not).
+ JS::Rooted<JSObject*> scope(cx, xpc::GetXBLScopeOrGlobal(cx, proto));
+ NS_ENSURE_TRUE(scope, nullptr);
+ MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(scope) == scope);
+
+ JS::Rooted<JSObject*> wrappedProto(cx, proto);
+ JSAutoCompartment ac(cx, scope);
+ if (!JS_WrapObject(cx, &wrappedProto)) {
+ return nullptr;
+ }
+
+ // Grab the appropriate WeakMap.
+ JS::Rooted<JSObject*> map(cx, GetOrCreateClassObjectMap(cx, scope, name));
+ if (!map) {
+ return nullptr;
+ }
+
+ // See if we already have a map entry for that prototype.
+ JS::Rooted<JS::Value> val(cx);
+ if (!JS::GetWeakMapEntry(cx, map, wrappedProto, &val)) {
+ return nullptr;
+ }
+ if (val.isObject()) {
+ return &val.toObject();
+ }
+
+ // We don't have an entry. Create one and stick it in the map.
+ JS::Rooted<JSObject*> entry(cx);
+ entry = JS_NewObjectWithGivenProto(cx, nullptr, nullptr);
+ if (!entry) {
+ return nullptr;
+ }
+ JS::Rooted<JS::Value> entryVal(cx, JS::ObjectValue(*entry));
+ if (!JS::SetWeakMapEntry(cx, map, wrappedProto, entryVal)) {
+ NS_WARNING("SetWeakMapEntry failed, probably due to non-preservable WeakMap "
+ "key. XBL binding will fail for this element.");
+ return nullptr;
+ }
+ return entry;
+}
+
+static
+nsXBLPrototypeBinding*
+GetProtoBindingFromClassObject(JSObject* obj)
+{
+ MOZ_ASSERT(JS_GetClass(obj) == &gPrototypeJSClass);
+ return static_cast<nsXBLPrototypeBinding*>(::JS_GetReservedSlot(obj, 0).toPrivate());
+}
+
+
+// static
+nsresult
+nsXBLBinding::DoInitJSClass(JSContext *cx,
+ JS::Handle<JSObject*> obj,
+ const nsAFlatString& aClassName,
+ nsXBLPrototypeBinding* aProtoBinding,
+ JS::MutableHandle<JSObject*> aClassObject,
+ bool* aNew)
+{
+ MOZ_ASSERT(obj);
+
+ // Note that, now that NAC reflectors are created in the XBL scope, the
+ // reflector is not necessarily same-compartment with the document. So we'll
+ // end up creating a separate instance of the oddly-named XBL class object
+ // and defining it as a property on the XBL scope's global. This works fine,
+ // but we need to make sure never to assume that the the reflector and
+ // prototype are same-compartment with the bound document.
+ JS::Rooted<JSObject*> global(cx, js::GetGlobalForObjectCrossCompartment(obj));
+
+ // We never store class objects in add-on scopes.
+ JS::Rooted<JSObject*> xblScope(cx, xpc::GetXBLScopeOrGlobal(cx, global));
+ NS_ENSURE_TRUE(xblScope, NS_ERROR_UNEXPECTED);
+
+ JS::Rooted<JSObject*> parent_proto(cx);
+ if (!JS_GetPrototype(cx, obj, &parent_proto)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get the map entry for the parent prototype. In the one-off case that the
+ // parent prototype is null, we somewhat hackily just use the WeakMap itself
+ // as a property holder.
+ JS::Rooted<JSObject*> holder(cx);
+ if (parent_proto) {
+ holder = GetOrCreateMapEntryForPrototype(cx, parent_proto);
+ } else {
+ JSAutoCompartment innerAC(cx, xblScope);
+ holder = GetOrCreateClassObjectMap(cx, xblScope, "__ContentClassObjectMap__");
+ }
+ if (NS_WARN_IF(!holder)) {
+ return NS_ERROR_FAILURE;
+ }
+ js::AssertSameCompartment(holder, xblScope);
+ JSAutoCompartment ac(cx, holder);
+
+ // Look up the class on the property holder. The only properties on the
+ // holder should be class objects. If we don't find the class object, we need
+ // to create and define it.
+ JS::Rooted<JSObject*> proto(cx);
+ JS::Rooted<JS::PropertyDescriptor> desc(cx);
+ if (!JS_GetOwnUCPropertyDescriptor(cx, holder, aClassName.get(), &desc)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ *aNew = !desc.object();
+ if (desc.object()) {
+ proto = &desc.value().toObject();
+ DebugOnly<nsXBLPrototypeBinding*> cachedBinding =
+ GetProtoBindingFromClassObject(js::UncheckedUnwrap(proto));
+ MOZ_ASSERT(cachedBinding == aProtoBinding);
+ } else {
+
+ // We need to create the prototype. First, enter the compartment where it's
+ // going to live, and create it.
+ JSAutoCompartment ac2(cx, global);
+ proto = JS_NewObjectWithGivenProto(cx, &gPrototypeJSClass, parent_proto);
+ if (!proto) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Keep this proto binding alive while we're alive. Do this first so that
+ // we can guarantee that in XBLFinalize this will be non-null.
+ // Note that we can't just store aProtoBinding in the private and
+ // addref/release the nsXBLDocumentInfo through it, because cycle
+ // collection doesn't seem to work right if the private is not an
+ // nsISupports.
+ nsXBLDocumentInfo* docInfo = aProtoBinding->XBLDocumentInfo();
+ ::JS_SetPrivate(proto, docInfo);
+ NS_ADDREF(docInfo);
+ JS_SetReservedSlot(proto, 0, JS::PrivateValue(aProtoBinding));
+
+ // Next, enter the compartment of the property holder, wrap the proto, and
+ // stick it on.
+ JSAutoCompartment ac3(cx, holder);
+ if (!JS_WrapObject(cx, &proto) ||
+ !JS_DefineUCProperty(cx, holder, aClassName.get(), -1, proto,
+ JSPROP_READONLY | JSPROP_PERMANENT,
+ JS_STUBGETTER, JS_STUBSETTER))
+ {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ // Whew. We have the proto. Wrap it back into the compartment of |obj|,
+ // splice it in, and return it.
+ JSAutoCompartment ac4(cx, obj);
+ if (!JS_WrapObject(cx, &proto) || !JS_SetPrototype(cx, obj, proto)) {
+ return NS_ERROR_FAILURE;
+ }
+ aClassObject.set(proto);
+ return NS_OK;
+}
+
+bool
+nsXBLBinding::AllowScripts()
+{
+ return mBoundElement && mPrototypeBinding->GetAllowScripts();
+}
+
+nsXBLBinding*
+nsXBLBinding::RootBinding()
+{
+ if (mNextBinding)
+ return mNextBinding->RootBinding();
+
+ return this;
+}
+
+bool
+nsXBLBinding::ResolveAllFields(JSContext *cx, JS::Handle<JSObject*> obj) const
+{
+ if (!mPrototypeBinding->ResolveAllFields(cx, obj)) {
+ return false;
+ }
+
+ if (mNextBinding) {
+ return mNextBinding->ResolveAllFields(cx, obj);
+ }
+
+ return true;
+}
+
+bool
+nsXBLBinding::LookupMember(JSContext* aCx, JS::Handle<jsid> aId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc)
+{
+ // We should never enter this function with a pre-filled property descriptor.
+ MOZ_ASSERT(!aDesc.object());
+
+ // Get the string as an nsString before doing anything, so we can make
+ // convenient comparisons during our search.
+ if (!JSID_IS_STRING(aId)) {
+ return true;
+ }
+ nsAutoJSString name;
+ if (!name.init(aCx, JSID_TO_STRING(aId))) {
+ return false;
+ }
+
+ // We have a weak reference to our bound element, so make sure it's alive.
+ if (!mBoundElement || !mBoundElement->GetWrapper()) {
+ return false;
+ }
+
+ // Get the scope of mBoundElement and the associated XBL scope. We should only
+ // be calling into this machinery if we're running in a separate XBL scope.
+ //
+ // Note that we only end up in LookupMember for XrayWrappers from XBL scopes
+ // into content. So for NAC reflectors that live in the XBL scope, we should
+ // never get here. But on the off-chance that someone adds new callsites to
+ // LookupMember, we do a release-mode assertion as belt-and-braces.
+ // We do a release-mode assertion here to be extra safe.
+ //
+ // This code is only called for content XBL, so we don't have to worry about
+ // add-on scopes here.
+ JS::Rooted<JSObject*> boundScope(aCx,
+ js::GetGlobalForObjectCrossCompartment(mBoundElement->GetWrapper()));
+ MOZ_RELEASE_ASSERT(!xpc::IsInAddonScope(boundScope));
+ MOZ_RELEASE_ASSERT(!xpc::IsInContentXBLScope(boundScope));
+ JS::Rooted<JSObject*> xblScope(aCx, xpc::GetXBLScope(aCx, boundScope));
+ NS_ENSURE_TRUE(xblScope, false);
+ MOZ_ASSERT(boundScope != xblScope);
+
+ // Enter the xbl scope and invoke the internal version.
+ {
+ JSAutoCompartment ac(aCx, xblScope);
+ JS::Rooted<jsid> id(aCx, aId);
+ if (!LookupMemberInternal(aCx, name, id, aDesc, xblScope)) {
+ return false;
+ }
+ }
+
+ // Wrap into the caller's scope.
+ return JS_WrapPropertyDescriptor(aCx, aDesc);
+}
+
+bool
+nsXBLBinding::LookupMemberInternal(JSContext* aCx, nsString& aName,
+ JS::Handle<jsid> aNameAsId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc,
+ JS::Handle<JSObject*> aXBLScope)
+{
+ // First, see if we have an implementation. If we don't, it means that this
+ // binding doesn't have a class object, and thus doesn't have any members.
+ // Skip it.
+ if (!PrototypeBinding()->HasImplementation()) {
+ if (!mNextBinding) {
+ return true;
+ }
+ return mNextBinding->LookupMemberInternal(aCx, aName, aNameAsId,
+ aDesc, aXBLScope);
+ }
+
+ // Find our class object. It's in a protected scope and permanent just in case,
+ // so should be there no matter what.
+ JS::Rooted<JS::Value> classObject(aCx);
+ if (!JS_GetUCProperty(aCx, aXBLScope, PrototypeBinding()->ClassName().get(),
+ -1, &classObject)) {
+ return false;
+ }
+
+ // The bound element may have been adoped by a document and have a different
+ // wrapper (and different xbl scope) than when the binding was applied, in
+ // this case getting the class object will fail. Behave as if the class
+ // object did not exist.
+ if (classObject.isUndefined()) {
+ return true;
+ }
+
+ MOZ_ASSERT(classObject.isObject());
+
+ // Look for the property on this binding. If it's not there, try the next
+ // binding on the chain.
+ nsXBLProtoImpl* impl = mPrototypeBinding->GetImplementation();
+ JS::Rooted<JSObject*> object(aCx, &classObject.toObject());
+ if (impl && !impl->LookupMember(aCx, aName, aNameAsId, aDesc, object)) {
+ return false;
+ }
+ if (aDesc.object() || !mNextBinding) {
+ return true;
+ }
+
+ return mNextBinding->LookupMemberInternal(aCx, aName, aNameAsId, aDesc,
+ aXBLScope);
+}
+
+bool
+nsXBLBinding::HasField(nsString& aName)
+{
+ // See if this binding has such a field.
+ return mPrototypeBinding->FindField(aName) ||
+ (mNextBinding && mNextBinding->HasField(aName));
+}
+
+void
+nsXBLBinding::MarkForDeath()
+{
+ mMarkedForDeath = true;
+ ExecuteDetachedHandler();
+}
+
+bool
+nsXBLBinding::ImplementsInterface(REFNSIID aIID) const
+{
+ return mPrototypeBinding->ImplementsInterface(aIID) ||
+ (mNextBinding && mNextBinding->ImplementsInterface(aIID));
+}
diff --git a/dom/xbl/nsXBLBinding.h b/dom/xbl/nsXBLBinding.h
new file mode 100644
index 000000000..b977c3e87
--- /dev/null
+++ b/dom/xbl/nsXBLBinding.h
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLBinding_h_
+#define nsXBLBinding_h_
+
+#include "nsXBLService.h"
+#include "nsCOMPtr.h"
+#include "nsINodeList.h"
+#include "nsIStyleRuleProcessor.h"
+#include "nsClassHashtable.h"
+#include "nsTArray.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupportsImpl.h"
+#include "js/TypeDecls.h"
+
+class nsXBLPrototypeBinding;
+class nsIContent;
+class nsIAtom;
+class nsIDocument;
+
+namespace mozilla {
+namespace dom {
+
+class ShadowRoot;
+class XBLChildrenElement;
+
+} // namespace dom
+} // namespace mozilla
+
+class nsAnonymousContentList;
+
+// *********************************************************************/
+// The XBLBinding class
+
+class nsXBLBinding final
+{
+public:
+ explicit nsXBLBinding(nsXBLPrototypeBinding* aProtoBinding);
+ nsXBLBinding(mozilla::dom::ShadowRoot* aShadowRoot, nsXBLPrototypeBinding* aProtoBinding);
+
+ /**
+ * XBLBindings are refcounted. They are held onto in 3 ways:
+ * 1. The binding manager's binding table holds onto all bindings that are
+ * currently attached to a content node.
+ * 2. Bindings hold onto their base binding. This is important since
+ * the base binding itself may not be attached to anything.
+ * 3. The binding manager holds an additional reference to bindings
+ * which are queued to fire their constructors.
+ */
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsXBLBinding)
+
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsXBLBinding)
+
+ nsXBLPrototypeBinding* PrototypeBinding() const { return mPrototypeBinding; }
+ nsIContent* GetAnonymousContent() { return mContent.get(); }
+ nsXBLBinding* GetBindingWithContent();
+
+ nsXBLBinding* GetBaseBinding() const { return mNextBinding; }
+ void SetBaseBinding(nsXBLBinding *aBinding);
+
+ nsIContent* GetBoundElement() { return mBoundElement; }
+ void SetBoundElement(nsIContent *aElement);
+
+ /*
+ * Does a lookup for a method or attribute provided by one of the bindings'
+ * prototype implementation. If found, |desc| will be set up appropriately,
+ * and wrapped into cx->compartment.
+ *
+ * May only be called when XBL code is being run in a separate scope, because
+ * otherwise we don't have untainted data with which to do a proper lookup.
+ */
+ bool LookupMember(JSContext* aCx, JS::Handle<jsid> aId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc);
+
+ /*
+ * Determines whether the binding has a field with the given name.
+ */
+ bool HasField(nsString& aName);
+
+protected:
+
+ ~nsXBLBinding();
+
+ /*
+ * Internal version. Requires that aCx is in appropriate xbl scope.
+ */
+ bool LookupMemberInternal(JSContext* aCx, nsString& aName,
+ JS::Handle<jsid> aNameAsId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc,
+ JS::Handle<JSObject*> aXBLScope);
+
+public:
+
+ void MarkForDeath();
+ bool MarkedForDeath() const { return mMarkedForDeath; }
+
+ bool HasStyleSheets() const;
+ bool InheritsStyle() const;
+ bool ImplementsInterface(REFNSIID aIID) const;
+
+ void GenerateAnonymousContent();
+ void InstallAnonymousContent(nsIContent* aAnonParent, nsIContent* aElement,
+ bool aNativeAnon);
+ static void UninstallAnonymousContent(nsIDocument* aDocument,
+ nsIContent* aAnonParent);
+ void InstallEventHandlers();
+ nsresult InstallImplementation();
+
+ void ExecuteAttachedHandler();
+ void ExecuteDetachedHandler();
+ void UnhookEventHandlers();
+
+ nsIAtom* GetBaseTag(int32_t* aNameSpaceID);
+ nsXBLBinding* RootBinding();
+
+ // Resolve all the fields for this binding and all ancestor bindings on the
+ // object |obj|. False return means a JS exception was set.
+ bool ResolveAllFields(JSContext *cx, JS::Handle<JSObject*> obj) const;
+
+ void AttributeChanged(nsIAtom* aAttribute, int32_t aNameSpaceID,
+ bool aRemoveFlag, bool aNotify);
+
+ void ChangeDocument(nsIDocument* aOldDocument, nsIDocument* aNewDocument);
+
+ void WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc, void* aData);
+
+ static nsresult DoInitJSClass(JSContext *cx, JS::Handle<JSObject*> obj,
+ const nsAFlatString& aClassName,
+ nsXBLPrototypeBinding* aProtoBinding,
+ JS::MutableHandle<JSObject*> aClassObject,
+ bool* aNew);
+
+ bool AllowScripts();
+
+ mozilla::dom::XBLChildrenElement* FindInsertionPointFor(nsIContent* aChild);
+
+ bool HasFilteredInsertionPoints()
+ {
+ return !mInsertionPoints.IsEmpty();
+ }
+
+ mozilla::dom::XBLChildrenElement* GetDefaultInsertionPoint()
+ {
+ return mDefaultInsertionPoint;
+ }
+
+ // Removes all inserted node from <xbl:children> insertion points under us.
+ void ClearInsertionPoints();
+
+ // Returns a live node list that iterates over the anonymous nodes generated
+ // by this binding.
+ nsAnonymousContentList* GetAnonymousNodeList();
+
+ nsIURI* GetSourceDocURI();
+
+// MEMBER VARIABLES
+protected:
+
+ bool mMarkedForDeath;
+ bool mUsingContentXBLScope;
+ bool mIsShadowRootBinding;
+
+ nsXBLPrototypeBinding* mPrototypeBinding; // Weak, but we're holding a ref to the docinfo
+ nsCOMPtr<nsIContent> mContent; // Strong. Our anonymous content stays around with us.
+ RefPtr<nsXBLBinding> mNextBinding; // Strong. The derived binding owns the base class bindings.
+
+ nsIContent* mBoundElement; // [WEAK] We have a reference, but we don't own it.
+
+ // The <xbl:children> elements that we found in our <xbl:content> when we
+ // processed this binding. The default insertion point has no includes
+ // attribute and all other insertion points must have at least one includes
+ // attribute. These points must be up-to-date with respect to their parent's
+ // children, even if their parent has another binding attached to it,
+ // preventing us from rendering their contents directly.
+ RefPtr<mozilla::dom::XBLChildrenElement> mDefaultInsertionPoint;
+ nsTArray<RefPtr<mozilla::dom::XBLChildrenElement> > mInsertionPoints;
+ RefPtr<nsAnonymousContentList> mAnonymousContentList;
+
+ mozilla::dom::XBLChildrenElement* FindInsertionPointForInternal(nsIContent* aChild);
+};
+
+#endif // nsXBLBinding_h_
diff --git a/dom/xbl/nsXBLContentSink.cpp b/dom/xbl/nsXBLContentSink.cpp
new file mode 100644
index 000000000..4d5c9fb74
--- /dev/null
+++ b/dom/xbl/nsXBLContentSink.cpp
@@ -0,0 +1,937 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsXBLContentSink.h"
+#include "nsIDocument.h"
+#include "nsBindingManager.h"
+#include "nsIDOMNode.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#include "nsIURI.h"
+#include "nsTextFragment.h"
+#ifdef MOZ_XUL
+#include "nsXULElement.h"
+#endif
+#include "nsXBLProtoImplProperty.h"
+#include "nsXBLProtoImplMethod.h"
+#include "nsXBLProtoImplField.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsContentUtils.h"
+#include "nsIConsoleService.h"
+#include "nsIScriptError.h"
+#include "nsNodeInfoManager.h"
+#include "nsIPrincipal.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsresult
+NS_NewXBLContentSink(nsIXMLContentSink** aResult,
+ nsIDocument* aDoc,
+ nsIURI* aURI,
+ nsISupports* aContainer)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ RefPtr<nsXBLContentSink> it = new nsXBLContentSink();
+ nsresult rv = it->Init(aDoc, aURI, aContainer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ it.forget(aResult);
+ return NS_OK;
+}
+
+nsXBLContentSink::nsXBLContentSink()
+ : mState(eXBL_InDocument),
+ mSecondaryState(eXBL_None),
+ mDocInfo(nullptr),
+ mIsChromeOrResource(false),
+ mFoundFirstBinding(false),
+ mBinding(nullptr),
+ mHandler(nullptr),
+ mImplementation(nullptr),
+ mImplMember(nullptr),
+ mImplField(nullptr),
+ mProperty(nullptr),
+ mMethod(nullptr),
+ mField(nullptr)
+{
+ mPrettyPrintXML = false;
+}
+
+nsXBLContentSink::~nsXBLContentSink()
+{
+}
+
+nsresult
+nsXBLContentSink::Init(nsIDocument* aDoc,
+ nsIURI* aURI,
+ nsISupports* aContainer)
+{
+ nsresult rv;
+ rv = nsXMLContentSink::Init(aDoc, aURI, aContainer, nullptr);
+ return rv;
+}
+
+void
+nsXBLContentSink::MaybeStartLayout(bool aIgnorePendingSheets)
+{
+ return;
+}
+
+nsresult
+nsXBLContentSink::FlushText(bool aReleaseTextNode)
+{
+ if (mTextLength != 0) {
+ const nsASingleFragmentString& text = Substring(mText, mText+mTextLength);
+ if (mState == eXBL_InHandlers) {
+ NS_ASSERTION(mBinding, "Must have binding here");
+ // Get the text and add it to the event handler.
+ if (mSecondaryState == eXBL_InHandler)
+ mHandler->AppendHandlerText(text);
+ mTextLength = 0;
+ return NS_OK;
+ }
+ else if (mState == eXBL_InImplementation) {
+ NS_ASSERTION(mBinding, "Must have binding here");
+ if (mSecondaryState == eXBL_InConstructor ||
+ mSecondaryState == eXBL_InDestructor) {
+ // Construct a method for the constructor/destructor.
+ nsXBLProtoImplMethod* method;
+ if (mSecondaryState == eXBL_InConstructor)
+ method = mBinding->GetConstructor();
+ else
+ method = mBinding->GetDestructor();
+
+ // Get the text and add it to the constructor/destructor.
+ method->AppendBodyText(text);
+ }
+ else if (mSecondaryState == eXBL_InGetter ||
+ mSecondaryState == eXBL_InSetter) {
+ // Get the text and add it to the getter/setter
+ if (mSecondaryState == eXBL_InGetter)
+ mProperty->AppendGetterText(text);
+ else
+ mProperty->AppendSetterText(text);
+ }
+ else if (mSecondaryState == eXBL_InBody) {
+ // Get the text and add it to the method
+ if (mMethod)
+ mMethod->AppendBodyText(text);
+ }
+ else if (mSecondaryState == eXBL_InField) {
+ // Get the text and add it to the method
+ if (mField)
+ mField->AppendFieldText(text);
+ }
+ mTextLength = 0;
+ return NS_OK;
+ }
+
+ nsIContent* content = GetCurrentContent();
+ if (content &&
+ (content->NodeInfo()->NamespaceEquals(kNameSpaceID_XBL) ||
+ (content->IsXULElement() &&
+ !content->IsAnyOfXULElements(nsGkAtoms::label,
+ nsGkAtoms::description)))) {
+
+ bool isWS = true;
+ if (mTextLength > 0) {
+ const char16_t* cp = mText;
+ const char16_t* end = mText + mTextLength;
+ while (cp < end) {
+ char16_t ch = *cp++;
+ if (!dom::IsSpaceCharacter(ch)) {
+ isWS = false;
+ break;
+ }
+ }
+ }
+
+ if (isWS && mTextLength > 0) {
+ mTextLength = 0;
+ // Make sure to drop the textnode, if any
+ return nsXMLContentSink::FlushText(aReleaseTextNode);
+ }
+ }
+ }
+
+ return nsXMLContentSink::FlushText(aReleaseTextNode);
+}
+
+NS_IMETHODIMP
+nsXBLContentSink::ReportError(const char16_t* aErrorText,
+ const char16_t* aSourceText,
+ nsIScriptError *aError,
+ bool *_retval)
+{
+ NS_PRECONDITION(aError && aSourceText && aErrorText, "Check arguments!!!");
+
+ // XXX FIXME This function overrides and calls on
+ // nsXMLContentSink::ReportError, and probably should die. See bug 347826.
+
+ // XXX We should make sure the binding has no effect, but that it also
+ // gets destroyed properly without leaking. Perhaps we should even
+ // ensure that the content that was bound is displayed with no
+ // binding.
+
+#ifdef DEBUG
+ // Report the error to stderr.
+ fprintf(stderr,
+ "\n%s\n%s\n\n",
+ NS_LossyConvertUTF16toASCII(aErrorText).get(),
+ NS_LossyConvertUTF16toASCII(aSourceText).get());
+#endif
+
+ // Most of what this does won't be too useful, but whatever...
+ // nsXMLContentSink::ReportError will handle the console logging.
+ return nsXMLContentSink::ReportError(aErrorText,
+ aSourceText,
+ aError,
+ _retval);
+}
+
+nsresult
+nsXBLContentSink::ReportUnexpectedElement(nsIAtom* aElementName,
+ uint32_t aLineNumber)
+{
+ // XXX we should really somehow stop the parse and drop the binding
+ // instead of just letting the XML sink build the content model like
+ // we do...
+ mState = eXBL_Error;
+ nsAutoString elementName;
+ aElementName->ToString(elementName);
+
+ const char16_t* params[] = { elementName.get() };
+
+ return nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("XBL Content Sink"),
+ mDocument,
+ nsContentUtils::eXBL_PROPERTIES,
+ "UnexpectedElement",
+ params, ArrayLength(params),
+ nullptr,
+ EmptyString() /* source line */,
+ aLineNumber);
+}
+
+void
+nsXBLContentSink::AddMember(nsXBLProtoImplMember* aMember)
+{
+ // Add this member to our chain.
+ if (mImplMember)
+ mImplMember->SetNext(aMember); // Already have a chain. Just append to the end.
+ else
+ mImplementation->SetMemberList(aMember); // We're the first member in the chain.
+
+ mImplMember = aMember; // Adjust our pointer to point to the new last member in the chain.
+}
+
+void
+nsXBLContentSink::AddField(nsXBLProtoImplField* aField)
+{
+ // Add this field to our chain.
+ if (mImplField)
+ mImplField->SetNext(aField); // Already have a chain. Just append to the end.
+ else
+ mImplementation->SetFieldList(aField); // We're the first member in the chain.
+
+ mImplField = aField; // Adjust our pointer to point to the new last field in the chain.
+}
+
+NS_IMETHODIMP
+nsXBLContentSink::HandleStartElement(const char16_t *aName,
+ const char16_t **aAtts,
+ uint32_t aAttsCount,
+ uint32_t aLineNumber)
+{
+ nsresult rv = nsXMLContentSink::HandleStartElement(aName, aAtts, aAttsCount,
+ aLineNumber);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mState == eXBL_InBinding && !mBinding) {
+ rv = ConstructBinding(aLineNumber);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // mBinding may still be null, if the binding had no id. If so,
+ // we'll deal with that later in the sink.
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsXBLContentSink::HandleEndElement(const char16_t *aName)
+{
+ FlushText();
+
+ if (mState != eXBL_InDocument) {
+ int32_t nameSpaceID;
+ nsCOMPtr<nsIAtom> prefix, localName;
+ nsContentUtils::SplitExpatName(aName, getter_AddRefs(prefix),
+ getter_AddRefs(localName), &nameSpaceID);
+
+ if (nameSpaceID == kNameSpaceID_XBL) {
+ if (mState == eXBL_Error) {
+ // Check whether we've opened this tag before; we may not have if
+ // it was a real XBL tag before the error occurred.
+ if (!GetCurrentContent()->NodeInfo()->Equals(localName,
+ nameSpaceID)) {
+ // OK, this tag was never opened as far as the XML sink is
+ // concerned. Just drop the HandleEndElement
+ return NS_OK;
+ }
+ }
+ else if (mState == eXBL_InHandlers) {
+ if (localName == nsGkAtoms::handlers) {
+ mState = eXBL_InBinding;
+ mHandler = nullptr;
+ }
+ else if (localName == nsGkAtoms::handler)
+ mSecondaryState = eXBL_None;
+ return NS_OK;
+ }
+ else if (mState == eXBL_InResources) {
+ if (localName == nsGkAtoms::resources)
+ mState = eXBL_InBinding;
+ return NS_OK;
+ }
+ else if (mState == eXBL_InImplementation) {
+ if (localName == nsGkAtoms::implementation)
+ mState = eXBL_InBinding;
+ else if (localName == nsGkAtoms::property) {
+ mSecondaryState = eXBL_None;
+ mProperty = nullptr;
+ }
+ else if (localName == nsGkAtoms::method) {
+ mSecondaryState = eXBL_None;
+ mMethod = nullptr;
+ }
+ else if (localName == nsGkAtoms::field) {
+ mSecondaryState = eXBL_None;
+ mField = nullptr;
+ }
+ else if (localName == nsGkAtoms::constructor ||
+ localName == nsGkAtoms::destructor)
+ mSecondaryState = eXBL_None;
+ else if (localName == nsGkAtoms::getter ||
+ localName == nsGkAtoms::setter)
+ mSecondaryState = eXBL_InProperty;
+ else if (localName == nsGkAtoms::parameter ||
+ localName == nsGkAtoms::body)
+ mSecondaryState = eXBL_InMethod;
+ return NS_OK;
+ }
+ else if (mState == eXBL_InBindings &&
+ localName == nsGkAtoms::bindings) {
+ mState = eXBL_InDocument;
+ }
+
+ nsresult rv = nsXMLContentSink::HandleEndElement(aName);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mState == eXBL_InBinding && localName == nsGkAtoms::binding) {
+ mState = eXBL_InBindings;
+ if (mBinding) { // See comment in HandleStartElement()
+ mBinding->Initialize();
+ mBinding = nullptr; // Clear our current binding ref.
+ }
+ }
+
+ return NS_OK;
+ }
+ }
+
+ return nsXMLContentSink::HandleEndElement(aName);
+}
+
+NS_IMETHODIMP
+nsXBLContentSink::HandleCDataSection(const char16_t *aData,
+ uint32_t aLength)
+{
+ if (mState == eXBL_InHandlers || mState == eXBL_InImplementation)
+ return AddText(aData, aLength);
+ return nsXMLContentSink::HandleCDataSection(aData, aLength);
+}
+
+#define ENSURE_XBL_STATE(_cond) \
+ PR_BEGIN_MACRO \
+ if (!(_cond)) { ReportUnexpectedElement(aTagName, aLineNumber); return true; } \
+ PR_END_MACRO
+
+bool
+nsXBLContentSink::OnOpenContainer(const char16_t **aAtts,
+ uint32_t aAttsCount,
+ int32_t aNameSpaceID,
+ nsIAtom* aTagName,
+ uint32_t aLineNumber)
+{
+ if (mState == eXBL_Error) {
+ return true;
+ }
+
+ if (aNameSpaceID != kNameSpaceID_XBL) {
+ // Construct non-XBL nodes
+ return true;
+ }
+
+ bool ret = true;
+ if (aTagName == nsGkAtoms::bindings) {
+ ENSURE_XBL_STATE(mState == eXBL_InDocument);
+
+ NS_ASSERTION(mDocument, "Must have a document!");
+ RefPtr<nsXBLDocumentInfo> info = new nsXBLDocumentInfo(mDocument);
+
+ // We keep a weak ref. We're creating a cycle between doc/binding manager/doc info.
+ mDocInfo = info;
+
+ if (!mDocInfo) {
+ mState = eXBL_Error;
+ return true;
+ }
+
+ mDocument->BindingManager()->PutXBLDocumentInfo(mDocInfo);
+
+ nsIURI *uri = mDocument->GetDocumentURI();
+
+ bool isChrome = false;
+ bool isRes = false;
+
+ uri->SchemeIs("chrome", &isChrome);
+ uri->SchemeIs("resource", &isRes);
+ mIsChromeOrResource = isChrome || isRes;
+
+ mState = eXBL_InBindings;
+ }
+ else if (aTagName == nsGkAtoms::binding) {
+ ENSURE_XBL_STATE(mState == eXBL_InBindings);
+ mState = eXBL_InBinding;
+ }
+ else if (aTagName == nsGkAtoms::handlers) {
+ ENSURE_XBL_STATE(mState == eXBL_InBinding && mBinding);
+ mState = eXBL_InHandlers;
+ ret = false;
+ }
+ else if (aTagName == nsGkAtoms::handler) {
+ ENSURE_XBL_STATE(mState == eXBL_InHandlers);
+ mSecondaryState = eXBL_InHandler;
+ ConstructHandler(aAtts, aLineNumber);
+ ret = false;
+ }
+ else if (aTagName == nsGkAtoms::resources) {
+ ENSURE_XBL_STATE(mState == eXBL_InBinding && mBinding);
+ mState = eXBL_InResources;
+ // Note that this mState will cause us to return false, so no need
+ // to set ret to false.
+ }
+ else if (aTagName == nsGkAtoms::stylesheet || aTagName == nsGkAtoms::image) {
+ ENSURE_XBL_STATE(mState == eXBL_InResources);
+ NS_ASSERTION(mBinding, "Must have binding here");
+ ConstructResource(aAtts, aTagName);
+ }
+ else if (aTagName == nsGkAtoms::implementation) {
+ ENSURE_XBL_STATE(mState == eXBL_InBinding && mBinding);
+ mState = eXBL_InImplementation;
+ ConstructImplementation(aAtts);
+ // Note that this mState will cause us to return false, so no need
+ // to set ret to false.
+ }
+ else if (aTagName == nsGkAtoms::constructor) {
+ ENSURE_XBL_STATE(mState == eXBL_InImplementation &&
+ mSecondaryState == eXBL_None);
+ NS_ASSERTION(mBinding, "Must have binding here");
+
+ mSecondaryState = eXBL_InConstructor;
+ nsAutoString name;
+ if (!mCurrentBindingID.IsEmpty()) {
+ name.Assign(mCurrentBindingID);
+ name.AppendLiteral("_XBL_Constructor");
+ } else {
+ name.AppendLiteral("XBL_Constructor");
+ }
+ nsXBLProtoImplAnonymousMethod* newMethod =
+ new nsXBLProtoImplAnonymousMethod(name.get());
+ newMethod->SetLineNumber(aLineNumber);
+ mBinding->SetConstructor(newMethod);
+ AddMember(newMethod);
+ }
+ else if (aTagName == nsGkAtoms::destructor) {
+ ENSURE_XBL_STATE(mState == eXBL_InImplementation &&
+ mSecondaryState == eXBL_None);
+ NS_ASSERTION(mBinding, "Must have binding here");
+ mSecondaryState = eXBL_InDestructor;
+ nsAutoString name;
+ if (!mCurrentBindingID.IsEmpty()) {
+ name.Assign(mCurrentBindingID);
+ name.AppendLiteral("_XBL_Destructor");
+ } else {
+ name.AppendLiteral("XBL_Destructor");
+ }
+ nsXBLProtoImplAnonymousMethod* newMethod =
+ new nsXBLProtoImplAnonymousMethod(name.get());
+ newMethod->SetLineNumber(aLineNumber);
+ mBinding->SetDestructor(newMethod);
+ AddMember(newMethod);
+ }
+ else if (aTagName == nsGkAtoms::field) {
+ ENSURE_XBL_STATE(mState == eXBL_InImplementation &&
+ mSecondaryState == eXBL_None);
+ NS_ASSERTION(mBinding, "Must have binding here");
+ mSecondaryState = eXBL_InField;
+ ConstructField(aAtts, aLineNumber);
+ }
+ else if (aTagName == nsGkAtoms::property) {
+ ENSURE_XBL_STATE(mState == eXBL_InImplementation &&
+ mSecondaryState == eXBL_None);
+ NS_ASSERTION(mBinding, "Must have binding here");
+ mSecondaryState = eXBL_InProperty;
+ ConstructProperty(aAtts, aLineNumber);
+ }
+ else if (aTagName == nsGkAtoms::getter) {
+ ENSURE_XBL_STATE(mSecondaryState == eXBL_InProperty && mProperty);
+ NS_ASSERTION(mState == eXBL_InImplementation, "Unexpected state");
+ mProperty->SetGetterLineNumber(aLineNumber);
+ mSecondaryState = eXBL_InGetter;
+ }
+ else if (aTagName == nsGkAtoms::setter) {
+ ENSURE_XBL_STATE(mSecondaryState == eXBL_InProperty && mProperty);
+ NS_ASSERTION(mState == eXBL_InImplementation, "Unexpected state");
+ mProperty->SetSetterLineNumber(aLineNumber);
+ mSecondaryState = eXBL_InSetter;
+ }
+ else if (aTagName == nsGkAtoms::method) {
+ ENSURE_XBL_STATE(mState == eXBL_InImplementation &&
+ mSecondaryState == eXBL_None);
+ NS_ASSERTION(mBinding, "Must have binding here");
+ mSecondaryState = eXBL_InMethod;
+ ConstructMethod(aAtts);
+ }
+ else if (aTagName == nsGkAtoms::parameter) {
+ ENSURE_XBL_STATE(mSecondaryState == eXBL_InMethod && mMethod);
+ NS_ASSERTION(mState == eXBL_InImplementation, "Unexpected state");
+ ConstructParameter(aAtts);
+ }
+ else if (aTagName == nsGkAtoms::body) {
+ ENSURE_XBL_STATE(mSecondaryState == eXBL_InMethod && mMethod);
+ NS_ASSERTION(mState == eXBL_InImplementation, "Unexpected state");
+ // stash away the line number
+ mMethod->SetLineNumber(aLineNumber);
+ mSecondaryState = eXBL_InBody;
+ }
+
+ return ret && mState != eXBL_InResources && mState != eXBL_InImplementation;
+}
+
+#undef ENSURE_XBL_STATE
+
+nsresult
+nsXBLContentSink::ConstructBinding(uint32_t aLineNumber)
+{
+ nsCOMPtr<nsIContent> binding = GetCurrentContent();
+ binding->GetAttr(kNameSpaceID_None, nsGkAtoms::id, mCurrentBindingID);
+ NS_ConvertUTF16toUTF8 cid(mCurrentBindingID);
+
+ nsresult rv = NS_OK;
+
+ // Don't create a binding with no id. nsXBLPrototypeBinding::Read also
+ // performs this check.
+ if (!cid.IsEmpty()) {
+ mBinding = new nsXBLPrototypeBinding();
+
+ rv = mBinding->Init(cid, mDocInfo, binding, !mFoundFirstBinding);
+ if (NS_SUCCEEDED(rv) &&
+ NS_SUCCEEDED(mDocInfo->SetPrototypeBinding(cid, mBinding))) {
+ if (!mFoundFirstBinding) {
+ mFoundFirstBinding = true;
+ mDocInfo->SetFirstPrototypeBinding(mBinding);
+ }
+ binding->UnsetAttr(kNameSpaceID_None, nsGkAtoms::id, false);
+ } else {
+ delete mBinding;
+ mBinding = nullptr;
+ }
+ } else {
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("XBL Content Sink"), nullptr,
+ nsContentUtils::eXBL_PROPERTIES,
+ "MissingIdAttr", nullptr, 0,
+ mDocumentURI,
+ EmptyString(),
+ aLineNumber);
+ }
+
+ return rv;
+}
+
+static bool
+FindValue(const char16_t **aAtts, nsIAtom *aAtom, const char16_t **aResult)
+{
+ nsCOMPtr<nsIAtom> prefix, localName;
+ for (; *aAtts; aAtts += 2) {
+ int32_t nameSpaceID;
+ nsContentUtils::SplitExpatName(aAtts[0], getter_AddRefs(prefix),
+ getter_AddRefs(localName), &nameSpaceID);
+
+ // Is this attribute one of the ones we care about?
+ if (nameSpaceID == kNameSpaceID_None && localName == aAtom) {
+ *aResult = aAtts[1];
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+nsXBLContentSink::ConstructHandler(const char16_t **aAtts, uint32_t aLineNumber)
+{
+ const char16_t* event = nullptr;
+ const char16_t* modifiers = nullptr;
+ const char16_t* button = nullptr;
+ const char16_t* clickcount = nullptr;
+ const char16_t* keycode = nullptr;
+ const char16_t* charcode = nullptr;
+ const char16_t* phase = nullptr;
+ const char16_t* command = nullptr;
+ const char16_t* action = nullptr;
+ const char16_t* group = nullptr;
+ const char16_t* preventdefault = nullptr;
+ const char16_t* allowuntrusted = nullptr;
+
+ nsCOMPtr<nsIAtom> prefix, localName;
+ for (; *aAtts; aAtts += 2) {
+ int32_t nameSpaceID;
+ nsContentUtils::SplitExpatName(aAtts[0], getter_AddRefs(prefix),
+ getter_AddRefs(localName), &nameSpaceID);
+
+ if (nameSpaceID != kNameSpaceID_None) {
+ continue;
+ }
+
+ // Is this attribute one of the ones we care about?
+ if (localName == nsGkAtoms::event)
+ event = aAtts[1];
+ else if (localName == nsGkAtoms::modifiers)
+ modifiers = aAtts[1];
+ else if (localName == nsGkAtoms::button)
+ button = aAtts[1];
+ else if (localName == nsGkAtoms::clickcount)
+ clickcount = aAtts[1];
+ else if (localName == nsGkAtoms::keycode)
+ keycode = aAtts[1];
+ else if (localName == nsGkAtoms::key || localName == nsGkAtoms::charcode)
+ charcode = aAtts[1];
+ else if (localName == nsGkAtoms::phase)
+ phase = aAtts[1];
+ else if (localName == nsGkAtoms::command)
+ command = aAtts[1];
+ else if (localName == nsGkAtoms::action)
+ action = aAtts[1];
+ else if (localName == nsGkAtoms::group)
+ group = aAtts[1];
+ else if (localName == nsGkAtoms::preventdefault)
+ preventdefault = aAtts[1];
+ else if (localName == nsGkAtoms::allowuntrusted)
+ allowuntrusted = aAtts[1];
+ }
+
+ if (command && !mIsChromeOrResource) {
+ // Make sure the XBL doc is chrome or resource if we have a command
+ // shorthand syntax.
+ mState = eXBL_Error;
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("XBL Content Sink"),
+ mDocument,
+ nsContentUtils::eXBL_PROPERTIES,
+ "CommandNotInChrome", nullptr, 0,
+ nullptr,
+ EmptyString() /* source line */,
+ aLineNumber);
+ return; // Don't even make this handler.
+ }
+
+ // All of our pointers are now filled in. Construct our handler with all of
+ // these parameters.
+ nsXBLPrototypeHandler* newHandler;
+ newHandler = new nsXBLPrototypeHandler(event, phase, action, command,
+ keycode, charcode, modifiers, button,
+ clickcount, group, preventdefault,
+ allowuntrusted, mBinding, aLineNumber);
+
+ // Add this handler to our chain of handlers.
+ if (mHandler) {
+ // Already have a chain. Just append to the end.
+ mHandler->SetNextHandler(newHandler);
+ } else {
+ // We're the first handler in the chain.
+ mBinding->SetPrototypeHandlers(newHandler);
+ }
+ // Adjust our mHandler pointer to point to the new last handler in the
+ // chain.
+ mHandler = newHandler;
+}
+
+void
+nsXBLContentSink::ConstructResource(const char16_t **aAtts,
+ nsIAtom* aResourceType)
+{
+ if (!mBinding)
+ return;
+
+ const char16_t* src = nullptr;
+ if (FindValue(aAtts, nsGkAtoms::src, &src)) {
+ mBinding->AddResource(aResourceType, nsDependentString(src));
+ }
+}
+
+void
+nsXBLContentSink::ConstructImplementation(const char16_t **aAtts)
+{
+ mImplementation = nullptr;
+ mImplMember = nullptr;
+ mImplField = nullptr;
+
+ if (!mBinding)
+ return;
+
+ const char16_t* name = nullptr;
+
+ nsCOMPtr<nsIAtom> prefix, localName;
+ for (; *aAtts; aAtts += 2) {
+ int32_t nameSpaceID;
+ nsContentUtils::SplitExpatName(aAtts[0], getter_AddRefs(prefix),
+ getter_AddRefs(localName), &nameSpaceID);
+
+ if (nameSpaceID != kNameSpaceID_None) {
+ continue;
+ }
+
+ // Is this attribute one of the ones we care about?
+ if (localName == nsGkAtoms::name) {
+ name = aAtts[1];
+ }
+ else if (localName == nsGkAtoms::implements) {
+ // Only allow implementation of interfaces via XBL if the principal of
+ // our XBL document is the system principal.
+ if (nsContentUtils::IsSystemPrincipal(mDocument->NodePrincipal())) {
+ mBinding->ConstructInterfaceTable(nsDependentString(aAtts[1]));
+ }
+ }
+ }
+
+ NS_NewXBLProtoImpl(mBinding, name, &mImplementation);
+}
+
+void
+nsXBLContentSink::ConstructField(const char16_t **aAtts, uint32_t aLineNumber)
+{
+ const char16_t* name = nullptr;
+ const char16_t* readonly = nullptr;
+
+ nsCOMPtr<nsIAtom> prefix, localName;
+ for (; *aAtts; aAtts += 2) {
+ int32_t nameSpaceID;
+ nsContentUtils::SplitExpatName(aAtts[0], getter_AddRefs(prefix),
+ getter_AddRefs(localName), &nameSpaceID);
+
+ if (nameSpaceID != kNameSpaceID_None) {
+ continue;
+ }
+
+ // Is this attribute one of the ones we care about?
+ if (localName == nsGkAtoms::name) {
+ name = aAtts[1];
+ }
+ else if (localName == nsGkAtoms::readonly) {
+ readonly = aAtts[1];
+ }
+ }
+
+ if (name) {
+ // All of our pointers are now filled in. Construct our field with all of
+ // these parameters.
+ mField = new nsXBLProtoImplField(name, readonly);
+ mField->SetLineNumber(aLineNumber);
+ AddField(mField);
+ }
+}
+
+void
+nsXBLContentSink::ConstructProperty(const char16_t **aAtts, uint32_t aLineNumber)
+{
+ const char16_t* name = nullptr;
+ const char16_t* readonly = nullptr;
+ const char16_t* onget = nullptr;
+ const char16_t* onset = nullptr;
+ bool exposeToUntrustedContent = false;
+
+ nsCOMPtr<nsIAtom> prefix, localName;
+ for (; *aAtts; aAtts += 2) {
+ int32_t nameSpaceID;
+ nsContentUtils::SplitExpatName(aAtts[0], getter_AddRefs(prefix),
+ getter_AddRefs(localName), &nameSpaceID);
+
+ if (nameSpaceID != kNameSpaceID_None) {
+ continue;
+ }
+
+ // Is this attribute one of the ones we care about?
+ if (localName == nsGkAtoms::name) {
+ name = aAtts[1];
+ }
+ else if (localName == nsGkAtoms::readonly) {
+ readonly = aAtts[1];
+ }
+ else if (localName == nsGkAtoms::onget) {
+ onget = aAtts[1];
+ }
+ else if (localName == nsGkAtoms::onset) {
+ onset = aAtts[1];
+ }
+ else if (localName == nsGkAtoms::exposeToUntrustedContent &&
+ nsDependentString(aAtts[1]).EqualsLiteral("true"))
+ {
+ exposeToUntrustedContent = true;
+ }
+ }
+
+ if (name) {
+ // All of our pointers are now filled in. Construct our property with all of
+ // these parameters.
+ mProperty = new nsXBLProtoImplProperty(name, onget, onset, readonly, aLineNumber);
+ if (exposeToUntrustedContent) {
+ mProperty->SetExposeToUntrustedContent(true);
+ }
+ AddMember(mProperty);
+ }
+}
+
+void
+nsXBLContentSink::ConstructMethod(const char16_t **aAtts)
+{
+ mMethod = nullptr;
+
+ const char16_t* name = nullptr;
+ const char16_t* expose = nullptr;
+ if (FindValue(aAtts, nsGkAtoms::name, &name)) {
+ mMethod = new nsXBLProtoImplMethod(name);
+ if (FindValue(aAtts, nsGkAtoms::exposeToUntrustedContent, &expose) &&
+ nsDependentString(expose).EqualsLiteral("true"))
+ {
+ mMethod->SetExposeToUntrustedContent(true);
+ }
+ }
+
+ if (mMethod) {
+ AddMember(mMethod);
+ }
+}
+
+void
+nsXBLContentSink::ConstructParameter(const char16_t **aAtts)
+{
+ if (!mMethod)
+ return;
+
+ const char16_t* name = nullptr;
+ if (FindValue(aAtts, nsGkAtoms::name, &name)) {
+ mMethod->AddParameter(nsDependentString(name));
+ }
+}
+
+nsresult
+nsXBLContentSink::CreateElement(const char16_t** aAtts, uint32_t aAttsCount,
+ mozilla::dom::NodeInfo* aNodeInfo, uint32_t aLineNumber,
+ nsIContent** aResult, bool* aAppendContent,
+ FromParser aFromParser)
+{
+#ifdef MOZ_XUL
+ if (!aNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) {
+#endif
+ return nsXMLContentSink::CreateElement(aAtts, aAttsCount, aNodeInfo,
+ aLineNumber, aResult,
+ aAppendContent, aFromParser);
+#ifdef MOZ_XUL
+ }
+
+ // Note that this needs to match the code in nsXBLPrototypeBinding::ReadContentNode.
+
+ *aAppendContent = true;
+ RefPtr<nsXULPrototypeElement> prototype = new nsXULPrototypeElement();
+
+ prototype->mNodeInfo = aNodeInfo;
+
+ AddAttributesToXULPrototype(aAtts, aAttsCount, prototype);
+
+ Element* result;
+ nsresult rv = nsXULElement::Create(prototype, mDocument, false, false, &result);
+ *aResult = result;
+ return rv;
+#endif
+}
+
+nsresult
+nsXBLContentSink::AddAttributes(const char16_t** aAtts,
+ nsIContent* aContent)
+{
+ if (aContent->IsXULElement())
+ return NS_OK; // Nothing to do, since the proto already has the attrs.
+
+ return nsXMLContentSink::AddAttributes(aAtts, aContent);
+}
+
+#ifdef MOZ_XUL
+nsresult
+nsXBLContentSink::AddAttributesToXULPrototype(const char16_t **aAtts,
+ uint32_t aAttsCount,
+ nsXULPrototypeElement* aElement)
+{
+ // Add tag attributes to the element
+ nsresult rv;
+
+ // Create storage for the attributes
+ nsXULPrototypeAttribute* attrs = nullptr;
+ if (aAttsCount > 0) {
+ attrs = new nsXULPrototypeAttribute[aAttsCount];
+ }
+
+ aElement->mAttributes = attrs;
+ aElement->mNumAttributes = aAttsCount;
+
+ // Copy the attributes into the prototype
+ nsCOMPtr<nsIAtom> prefix, localName;
+
+ uint32_t i;
+ for (i = 0; i < aAttsCount; ++i) {
+ int32_t nameSpaceID;
+ nsContentUtils::SplitExpatName(aAtts[i * 2], getter_AddRefs(prefix),
+ getter_AddRefs(localName), &nameSpaceID);
+
+ if (nameSpaceID == kNameSpaceID_None) {
+ attrs[i].mName.SetTo(localName);
+ }
+ else {
+ RefPtr<NodeInfo> ni;
+ ni = mNodeInfoManager->GetNodeInfo(localName, prefix, nameSpaceID,
+ nsIDOMNode::ATTRIBUTE_NODE);
+ attrs[i].mName.SetTo(ni);
+ }
+
+ rv = aElement->SetAttrAt(i, nsDependentString(aAtts[i * 2 + 1]),
+ mDocumentURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+#endif
diff --git a/dom/xbl/nsXBLContentSink.h b/dom/xbl/nsXBLContentSink.h
new file mode 100644
index 000000000..3c220c0f3
--- /dev/null
+++ b/dom/xbl/nsXBLContentSink.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLContentSink_h__
+#define nsXBLContentSink_h__
+
+#include "mozilla/Attributes.h"
+#include "nsXMLContentSink.h"
+#include "nsXBLDocumentInfo.h"
+#include "nsXBLPrototypeHandler.h"
+#include "nsXBLProtoImpl.h"
+#include "nsLayoutCID.h"
+
+/*
+ * Enum that describes the primary state of the parsing process
+ */
+typedef enum {
+ eXBL_InDocument, /* outside any bindings */
+ eXBL_InBindings, /* Inside a <bindings> element */
+ eXBL_InBinding, /* Inside a <binding> */
+ eXBL_InResources, /* Inside a <resources> */
+ eXBL_InImplementation, /* Inside a <implementation> */
+ eXBL_InHandlers, /* Inside a <handlers> */
+ eXBL_Error /* An error has occurred. Suspend binding construction */
+} XBLPrimaryState;
+
+/*
+ * Enum that describes our substate (typically when parsing something
+ * like <handlers> or <implementation>).
+ */
+typedef enum {
+ eXBL_None,
+ eXBL_InHandler,
+ eXBL_InMethod,
+ eXBL_InProperty,
+ eXBL_InField,
+ eXBL_InBody,
+ eXBL_InGetter,
+ eXBL_InSetter,
+ eXBL_InConstructor,
+ eXBL_InDestructor
+} XBLSecondaryState;
+
+class nsXULPrototypeElement;
+class nsXBLProtoImplMember;
+class nsXBLProtoImplProperty;
+class nsXBLProtoImplMethod;
+class nsXBLProtoImplField;
+class nsXBLPrototypeBinding;
+
+// The XBL content sink overrides the XML content sink to
+// builds its own lightweight data structures for the <resources>,
+// <handlers>, <implementation>, and
+
+class nsXBLContentSink : public nsXMLContentSink {
+public:
+ nsXBLContentSink();
+ ~nsXBLContentSink();
+
+ NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
+
+ nsresult Init(nsIDocument* aDoc,
+ nsIURI* aURL,
+ nsISupports* aContainer);
+
+ // nsIContentSink overrides
+ NS_IMETHOD HandleStartElement(const char16_t *aName,
+ const char16_t **aAtts,
+ uint32_t aAttsCount,
+ uint32_t aLineNumber) override;
+
+ NS_IMETHOD HandleEndElement(const char16_t *aName) override;
+
+ NS_IMETHOD HandleCDataSection(const char16_t *aData,
+ uint32_t aLength) override;
+
+protected:
+ // nsXMLContentSink overrides
+ virtual void MaybeStartLayout(bool aIgnorePendingSheets) override;
+
+ bool OnOpenContainer(const char16_t **aAtts,
+ uint32_t aAttsCount,
+ int32_t aNameSpaceID,
+ nsIAtom* aTagName,
+ uint32_t aLineNumber) override;
+
+ bool NotifyForDocElement() override { return false; }
+
+ nsresult CreateElement(const char16_t** aAtts, uint32_t aAttsCount,
+ mozilla::dom::NodeInfo* aNodeInfo, uint32_t aLineNumber,
+ nsIContent** aResult, bool* aAppendContent,
+ mozilla::dom::FromParser aFromParser) override;
+
+ nsresult AddAttributes(const char16_t** aAtts,
+ nsIContent* aContent) override;
+
+#ifdef MOZ_XUL
+ nsresult AddAttributesToXULPrototype(const char16_t **aAtts,
+ uint32_t aAttsCount,
+ nsXULPrototypeElement* aElement);
+#endif
+
+ // Our own helpers for constructing XBL prototype objects.
+ nsresult ConstructBinding(uint32_t aLineNumber);
+ void ConstructHandler(const char16_t **aAtts, uint32_t aLineNumber);
+ void ConstructResource(const char16_t **aAtts, nsIAtom* aResourceType);
+ void ConstructImplementation(const char16_t **aAtts);
+ void ConstructProperty(const char16_t **aAtts, uint32_t aLineNumber);
+ void ConstructMethod(const char16_t **aAtts);
+ void ConstructParameter(const char16_t **aAtts);
+ void ConstructField(const char16_t **aAtts, uint32_t aLineNumber);
+
+
+ // nsXMLContentSink overrides
+ nsresult FlushText(bool aReleaseTextNode = true) override;
+
+ // nsIExpatSink overrides
+ NS_IMETHOD ReportError(const char16_t* aErrorText,
+ const char16_t* aSourceText,
+ nsIScriptError *aError,
+ bool *_retval) override;
+
+protected:
+ nsresult ReportUnexpectedElement(nsIAtom* aElementName, uint32_t aLineNumber);
+
+ void AddMember(nsXBLProtoImplMember* aMember);
+ void AddField(nsXBLProtoImplField* aField);
+
+ XBLPrimaryState mState;
+ XBLSecondaryState mSecondaryState;
+ nsXBLDocumentInfo* mDocInfo;
+ bool mIsChromeOrResource; // For bug #45989
+ bool mFoundFirstBinding;
+
+ nsString mCurrentBindingID;
+
+ nsXBLPrototypeBinding* mBinding;
+ nsXBLPrototypeHandler* mHandler; // current handler, owned by its PrototypeBinding
+ nsXBLProtoImpl* mImplementation;
+ nsXBLProtoImplMember* mImplMember;
+ nsXBLProtoImplField* mImplField;
+ nsXBLProtoImplProperty* mProperty;
+ nsXBLProtoImplMethod* mMethod;
+ nsXBLProtoImplField* mField;
+};
+
+nsresult
+NS_NewXBLContentSink(nsIXMLContentSink** aResult,
+ nsIDocument* aDoc,
+ nsIURI* aURL,
+ nsISupports* aContainer);
+#endif // nsXBLContentSink_h__
diff --git a/dom/xbl/nsXBLDocumentInfo.cpp b/dom/xbl/nsXBLDocumentInfo.cpp
new file mode 100644
index 000000000..283775dc6
--- /dev/null
+++ b/dom/xbl/nsXBLDocumentInfo.cpp
@@ -0,0 +1,325 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsXBLDocumentInfo.h"
+#include "nsIDocument.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptContext.h"
+#include "nsIDOMDocument.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "nsIURI.h"
+#include "nsIConsoleService.h"
+#include "nsIScriptError.h"
+#include "nsIChromeRegistry.h"
+#include "nsIPrincipal.h"
+#include "nsJSPrincipals.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsContentUtils.h"
+#include "nsDOMJSUtils.h"
+#include "mozilla/Services.h"
+#include "xpcpublic.h"
+#include "mozilla/scache/StartupCache.h"
+#include "mozilla/scache/StartupCacheUtils.h"
+#include "nsCCUncollectableMarker.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/URL.h"
+
+using namespace mozilla;
+using namespace mozilla::scache;
+using namespace mozilla::dom;
+
+static const char kXBLCachePrefix[] = "xblcache";
+
+/* Implementation file */
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLDocumentInfo)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLDocumentInfo)
+ if (tmp->mBindingTable) {
+ for (auto iter = tmp->mBindingTable->ConstIter();
+ !iter.Done(); iter.Next()) {
+ iter.UserData()->Unlink();
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXBLDocumentInfo)
+ if (tmp->mDocument &&
+ nsCCUncollectableMarker::InGeneration(cb, tmp->mDocument->GetMarkedCCGeneration())) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+ return NS_SUCCESS_INTERRUPTED_TRAVERSE;
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
+ if (tmp->mBindingTable) {
+ for (auto iter = tmp->mBindingTable->ConstIter();
+ !iter.Done(); iter.Next()) {
+ iter.UserData()->Traverse(cb);
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXBLDocumentInfo)
+ if (tmp->mBindingTable) {
+ for (auto iter = tmp->mBindingTable->ConstIter();
+ !iter.Done(); iter.Next()) {
+ iter.UserData()->Trace(aCallbacks, aClosure);
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+static void
+UnmarkXBLJSObject(JS::GCCellPtr aPtr, const char* aName, void* aClosure)
+{
+ JS::ExposeObjectToActiveJS(&aPtr.as<JSObject>());
+}
+
+void
+nsXBLDocumentInfo::MarkInCCGeneration(uint32_t aGeneration)
+{
+ if (mDocument) {
+ mDocument->MarkUncollectableForCCGeneration(aGeneration);
+ }
+ // Unmark any JS we hold
+ if (mBindingTable) {
+ for (auto iter = mBindingTable->Iter(); !iter.Done(); iter.Next()) {
+ iter.UserData()->Trace(TraceCallbackFunc(UnmarkXBLJSObject), nullptr);
+ }
+ }
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXBLDocumentInfo)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXBLDocumentInfo)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXBLDocumentInfo)
+
+nsXBLDocumentInfo::nsXBLDocumentInfo(nsIDocument* aDocument)
+ : mDocument(aDocument),
+ mScriptAccess(true),
+ mIsChrome(false),
+ mFirstBinding(nullptr)
+{
+ nsIURI* uri = aDocument->GetDocumentURI();
+ if (IsChromeURI(uri)) {
+ // Cache whether or not this chrome XBL can execute scripts.
+ nsCOMPtr<nsIXULChromeRegistry> reg =
+ mozilla::services::GetXULChromeRegistryService();
+ if (reg) {
+ bool allow = true;
+ reg->AllowScriptsForPackage(uri, &allow);
+ mScriptAccess = allow;
+ }
+ mIsChrome = true;
+ } else {
+ // If this binding isn't running with system principal, then it's running
+ // from a remote-XUL whitelisted domain. This is already a not-really-
+ // supported configuration (among other things, we don't use XBL scopes in
+ // that configuration for compatibility reasons). But we should still at
+ // least make an effort to prevent binding code from running if content
+ // script is disabled or if the source domain is blacklisted (since the
+ // source domain for remote XBL must always be the same as the source domain
+ // of the bound content).
+ //
+ // If we just ask the binding document if script is enabled, it will
+ // discover that it has no inner window, and return false. So instead, we
+ // short-circuit the normal compartment-managed script-disabling machinery,
+ // and query the policy for the URI directly.
+ bool allow;
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ nsresult rv = ssm->PolicyAllowsScript(uri, &allow);
+ mScriptAccess = NS_SUCCEEDED(rv) && allow;
+ }
+}
+
+nsXBLDocumentInfo::~nsXBLDocumentInfo()
+{
+ mozilla::DropJSObjects(this);
+}
+
+nsXBLPrototypeBinding*
+nsXBLDocumentInfo::GetPrototypeBinding(const nsACString& aRef)
+{
+ if (!mBindingTable)
+ return nullptr;
+
+ if (aRef.IsEmpty()) {
+ // Return our first binding
+ return mFirstBinding;
+ }
+
+ return mBindingTable->Get(aRef);
+}
+
+nsresult
+nsXBLDocumentInfo::SetPrototypeBinding(const nsACString& aRef, nsXBLPrototypeBinding* aBinding)
+{
+ if (!mBindingTable) {
+ mBindingTable = new nsClassHashtable<nsCStringHashKey, nsXBLPrototypeBinding>();
+ mozilla::HoldJSObjects(this);
+ }
+
+ NS_ENSURE_STATE(!mBindingTable->Get(aRef));
+ mBindingTable->Put(aRef, aBinding);
+
+ return NS_OK;
+}
+
+void
+nsXBLDocumentInfo::RemovePrototypeBinding(const nsACString& aRef)
+{
+ if (mBindingTable) {
+ nsAutoPtr<nsXBLPrototypeBinding> bindingToRemove;
+ mBindingTable->RemoveAndForget(aRef, bindingToRemove);
+
+ // We do not want to destroy the binding, so just forget it.
+ bindingToRemove.forget();
+ }
+}
+
+// static
+nsresult
+nsXBLDocumentInfo::ReadPrototypeBindings(nsIURI* aURI, nsXBLDocumentInfo** aDocInfo)
+{
+ *aDocInfo = nullptr;
+
+ nsAutoCString spec(kXBLCachePrefix);
+ nsresult rv = PathifyURI(aURI, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ StartupCache* startupCache = StartupCache::GetSingleton();
+ if (!startupCache) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<char[]> buf;
+ uint32_t len;
+ rv = startupCache->GetBuffer(spec.get(), &buf, &len);
+ // GetBuffer will fail if the binding is not in the cache.
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIObjectInputStream> stream;
+ rv = NewObjectInputStreamFromBuffer(Move(buf), len, getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The file compatibility.ini stores the build id. This is checked in
+ // nsAppRunner.cpp and will delete the cache if a different build is
+ // present. However, we check that the version matches here to be safe.
+ uint32_t version;
+ rv = stream->Read32(&version);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (version != XBLBinding_Serialize_Version) {
+ // The version that exists is different than expected, likely created with a
+ // different build, so invalidate the cache.
+ startupCache->InvalidateCache();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsContentUtils::GetSecurityManager()->
+ GetSystemPrincipal(getter_AddRefs(principal));
+
+ nsCOMPtr<nsIDOMDocument> domdoc;
+ rv = NS_NewXBLDocument(getter_AddRefs(domdoc), aURI, nullptr, principal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc);
+ NS_ASSERTION(doc, "Must have a document!");
+ RefPtr<nsXBLDocumentInfo> docInfo = new nsXBLDocumentInfo(doc);
+
+ while (1) {
+ uint8_t flags;
+ nsresult rv = stream->Read8(&flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (flags == XBLBinding_Serialize_NoMoreBindings)
+ break;
+
+ rv = nsXBLPrototypeBinding::ReadNewBinding(stream, docInfo, doc, flags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ docInfo.forget(aDocInfo);
+ return NS_OK;
+}
+
+nsresult
+nsXBLDocumentInfo::WritePrototypeBindings()
+{
+ // Only write out bindings with the system principal
+ if (!nsContentUtils::IsSystemPrincipal(mDocument->NodePrincipal()))
+ return NS_OK;
+
+ nsAutoCString spec(kXBLCachePrefix);
+ nsresult rv = PathifyURI(DocumentURI(), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ StartupCache* startupCache = StartupCache::GetSingleton();
+ if (!startupCache) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIObjectOutputStream> stream;
+ nsCOMPtr<nsIStorageStream> storageStream;
+ rv = NewObjectOutputWrappedStorageStream(getter_AddRefs(stream),
+ getter_AddRefs(storageStream),
+ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stream->Write32(XBLBinding_Serialize_Version);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mBindingTable) {
+ for (auto iter = mBindingTable->Iter(); !iter.Done(); iter.Next()) {
+ iter.UserData()->Write(stream);
+ }
+ }
+
+ // write a end marker at the end
+ rv = stream->Write8(XBLBinding_Serialize_NoMoreBindings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ stream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t len;
+ UniquePtr<char[]> buf;
+ rv = NewBufferFromStorageStream(storageStream, &buf, &len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return startupCache->PutBuffer(spec.get(), buf.get(), len);
+}
+
+void
+nsXBLDocumentInfo::SetFirstPrototypeBinding(nsXBLPrototypeBinding* aBinding)
+{
+ mFirstBinding = aBinding;
+}
+
+void
+nsXBLDocumentInfo::FlushSkinStylesheets()
+{
+ if (mBindingTable) {
+ for (auto iter = mBindingTable->Iter(); !iter.Done(); iter.Next()) {
+ iter.UserData()->FlushSkinSheets();
+ }
+ }
+}
+
+#ifdef DEBUG
+void
+AssertInCompilationScope()
+{
+ AutoJSContext cx;
+ MOZ_ASSERT(xpc::CompilationScope() == JS::CurrentGlobalOrNull(cx));
+}
+#endif
diff --git a/dom/xbl/nsXBLDocumentInfo.h b/dom/xbl/nsXBLDocumentInfo.h
new file mode 100644
index 000000000..79ea005e0
--- /dev/null
+++ b/dom/xbl/nsXBLDocumentInfo.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLDocumentInfo_h__
+#define nsXBLDocumentInfo_h__
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsWeakReference.h"
+#include "nsIDocument.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsXBLPrototypeBinding;
+
+class nsXBLDocumentInfo final : public nsSupportsWeakReference
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ explicit nsXBLDocumentInfo(nsIDocument* aDocument);
+
+ already_AddRefed<nsIDocument> GetDocument()
+ { nsCOMPtr<nsIDocument> copy = mDocument; return copy.forget(); }
+
+ bool GetScriptAccess() const { return mScriptAccess; }
+
+ nsIURI* DocumentURI() { return mDocument->GetDocumentURI(); }
+
+ nsXBLPrototypeBinding* GetPrototypeBinding(const nsACString& aRef);
+ nsresult SetPrototypeBinding(const nsACString& aRef,
+ nsXBLPrototypeBinding* aBinding);
+
+ // This removes the binding without deleting it
+ void RemovePrototypeBinding(const nsACString& aRef);
+
+ nsresult WritePrototypeBindings();
+
+ void SetFirstPrototypeBinding(nsXBLPrototypeBinding* aBinding);
+
+ void FlushSkinStylesheets();
+
+ bool IsChrome() { return mIsChrome; }
+
+ void MarkInCCGeneration(uint32_t aGeneration);
+
+ static nsresult ReadPrototypeBindings(nsIURI* aURI, nsXBLDocumentInfo** aDocInfo);
+
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsXBLDocumentInfo)
+
+private:
+ virtual ~nsXBLDocumentInfo();
+
+ nsCOMPtr<nsIDocument> mDocument;
+ bool mScriptAccess;
+ bool mIsChrome;
+ // the binding table owns each nsXBLPrototypeBinding
+ nsAutoPtr<nsClassHashtable<nsCStringHashKey, nsXBLPrototypeBinding>> mBindingTable;
+
+ // non-owning pointer to the first binding in the table
+ nsXBLPrototypeBinding* mFirstBinding;
+};
+
+#ifdef DEBUG
+void AssertInCompilationScope();
+#else
+inline void AssertInCompilationScope() {}
+#endif
+
+#endif
diff --git a/dom/xbl/nsXBLEventHandler.cpp b/dom/xbl/nsXBLEventHandler.cpp
new file mode 100644
index 000000000..6a873cf3b
--- /dev/null
+++ b/dom/xbl/nsXBLEventHandler.cpp
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsXBLPrototypeHandler.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/TextEvents.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsXBLEventHandler::nsXBLEventHandler(nsXBLPrototypeHandler* aHandler)
+ : mProtoHandler(aHandler)
+{
+}
+
+nsXBLEventHandler::~nsXBLEventHandler()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsXBLEventHandler, nsIDOMEventListener)
+
+NS_IMETHODIMP
+nsXBLEventHandler::HandleEvent(nsIDOMEvent* aEvent)
+{
+ if (!mProtoHandler)
+ return NS_ERROR_FAILURE;
+
+ uint8_t phase = mProtoHandler->GetPhase();
+ if (phase == NS_PHASE_TARGET) {
+ uint16_t eventPhase;
+ aEvent->GetEventPhase(&eventPhase);
+ if (eventPhase != nsIDOMEvent::AT_TARGET)
+ return NS_OK;
+ }
+
+ if (!EventMatched(aEvent))
+ return NS_OK;
+
+ mProtoHandler->ExecuteHandler(aEvent->InternalDOMEvent()->GetCurrentTarget(),
+ aEvent);
+
+ return NS_OK;
+}
+
+nsXBLMouseEventHandler::nsXBLMouseEventHandler(nsXBLPrototypeHandler* aHandler)
+ : nsXBLEventHandler(aHandler)
+{
+}
+
+nsXBLMouseEventHandler::~nsXBLMouseEventHandler()
+{
+}
+
+bool
+nsXBLMouseEventHandler::EventMatched(nsIDOMEvent* aEvent)
+{
+ nsCOMPtr<nsIDOMMouseEvent> mouse(do_QueryInterface(aEvent));
+ return mouse && mProtoHandler->MouseEventMatched(mouse);
+}
+
+nsXBLKeyEventHandler::nsXBLKeyEventHandler(nsIAtom* aEventType, uint8_t aPhase,
+ uint8_t aType)
+ : mEventType(aEventType),
+ mPhase(aPhase),
+ mType(aType),
+ mIsBoundToChrome(false),
+ mUsingContentXBLScope(false)
+{
+}
+
+nsXBLKeyEventHandler::~nsXBLKeyEventHandler()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsXBLKeyEventHandler, nsIDOMEventListener)
+
+bool
+nsXBLKeyEventHandler::ExecuteMatchedHandlers(
+ nsIDOMKeyEvent* aKeyEvent,
+ uint32_t aCharCode,
+ const IgnoreModifierState& aIgnoreModifierState)
+{
+ WidgetEvent* event = aKeyEvent->AsEvent()->WidgetEventPtr();
+ nsCOMPtr<EventTarget> target = aKeyEvent->AsEvent()->InternalDOMEvent()->GetCurrentTarget();
+
+ bool executed = false;
+ for (uint32_t i = 0; i < mProtoHandlers.Length(); ++i) {
+ nsXBLPrototypeHandler* handler = mProtoHandlers[i];
+ bool hasAllowUntrustedAttr = handler->HasAllowUntrustedAttr();
+ if ((event->IsTrusted() ||
+ (hasAllowUntrustedAttr && handler->AllowUntrustedEvents()) ||
+ (!hasAllowUntrustedAttr && !mIsBoundToChrome && !mUsingContentXBLScope)) &&
+ handler->KeyEventMatched(aKeyEvent, aCharCode, aIgnoreModifierState)) {
+ handler->ExecuteHandler(target, aKeyEvent->AsEvent());
+ executed = true;
+ }
+ }
+#ifdef XP_WIN
+ // Windows native applications ignore Windows-Logo key state when checking
+ // shortcut keys even if the key is pressed. Therefore, if there is no
+ // shortcut key which exactly matches current modifier state, we should
+ // retry to look for a shortcut key without the Windows-Logo key press.
+ if (!executed && !aIgnoreModifierState.mOS) {
+ WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
+ if (keyEvent && keyEvent->IsOS()) {
+ IgnoreModifierState ignoreModifierState(aIgnoreModifierState);
+ ignoreModifierState.mOS = true;
+ return ExecuteMatchedHandlers(aKeyEvent, aCharCode, ignoreModifierState);
+ }
+ }
+#endif
+ return executed;
+}
+
+NS_IMETHODIMP
+nsXBLKeyEventHandler::HandleEvent(nsIDOMEvent* aEvent)
+{
+ uint32_t count = mProtoHandlers.Length();
+ if (count == 0)
+ return NS_ERROR_FAILURE;
+
+ if (mPhase == NS_PHASE_TARGET) {
+ uint16_t eventPhase;
+ aEvent->GetEventPhase(&eventPhase);
+ if (eventPhase != nsIDOMEvent::AT_TARGET)
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMKeyEvent> key(do_QueryInterface(aEvent));
+ if (!key)
+ return NS_OK;
+
+ WidgetKeyboardEvent* nativeKeyboardEvent =
+ aEvent->WidgetEventPtr()->AsKeyboardEvent();
+ MOZ_ASSERT(nativeKeyboardEvent);
+ AutoShortcutKeyCandidateArray shortcutKeys;
+ nativeKeyboardEvent->GetShortcutKeyCandidates(shortcutKeys);
+
+ if (shortcutKeys.IsEmpty()) {
+ ExecuteMatchedHandlers(key, 0, IgnoreModifierState());
+ return NS_OK;
+ }
+
+ for (uint32_t i = 0; i < shortcutKeys.Length(); ++i) {
+ IgnoreModifierState ignoreModifierState;
+ ignoreModifierState.mShift = shortcutKeys[i].mIgnoreShift;
+ if (ExecuteMatchedHandlers(key, shortcutKeys[i].mCharCode,
+ ignoreModifierState)) {
+ return NS_OK;
+ }
+ }
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+
+already_AddRefed<nsXBLEventHandler>
+NS_NewXBLEventHandler(nsXBLPrototypeHandler* aHandler,
+ nsIAtom* aEventType)
+{
+ RefPtr<nsXBLEventHandler> handler;
+
+ switch (nsContentUtils::GetEventClassID(nsDependentAtomString(aEventType))) {
+ case eDragEventClass:
+ case eMouseEventClass:
+ case eMouseScrollEventClass:
+ case eWheelEventClass:
+ case eSimpleGestureEventClass:
+ handler = new nsXBLMouseEventHandler(aHandler);
+ break;
+ default:
+ handler = new nsXBLEventHandler(aHandler);
+ break;
+ }
+
+ return handler.forget();
+}
diff --git a/dom/xbl/nsXBLEventHandler.h b/dom/xbl/nsXBLEventHandler.h
new file mode 100644
index 000000000..8c74cabdb
--- /dev/null
+++ b/dom/xbl/nsXBLEventHandler.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLEventHandler_h__
+#define nsXBLEventHandler_h__
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMEventListener.h"
+#include "nsTArray.h"
+
+class nsIAtom;
+class nsIDOMKeyEvent;
+class nsXBLPrototypeHandler;
+
+namespace mozilla {
+namespace dom {
+struct IgnoreModifierState;
+} // namespace dom
+} // namespace mozilla
+
+class nsXBLEventHandler : public nsIDOMEventListener
+{
+public:
+ explicit nsXBLEventHandler(nsXBLPrototypeHandler* aHandler);
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIDOMEVENTLISTENER
+
+protected:
+ virtual ~nsXBLEventHandler();
+ nsXBLPrototypeHandler* mProtoHandler;
+
+private:
+ nsXBLEventHandler();
+ virtual bool EventMatched(nsIDOMEvent* aEvent)
+ {
+ return true;
+ }
+};
+
+class nsXBLMouseEventHandler : public nsXBLEventHandler
+{
+public:
+ explicit nsXBLMouseEventHandler(nsXBLPrototypeHandler* aHandler);
+ virtual ~nsXBLMouseEventHandler();
+
+private:
+ bool EventMatched(nsIDOMEvent* aEvent) override;
+};
+
+class nsXBLKeyEventHandler : public nsIDOMEventListener
+{
+ typedef mozilla::dom::IgnoreModifierState IgnoreModifierState;
+
+public:
+ nsXBLKeyEventHandler(nsIAtom* aEventType, uint8_t aPhase, uint8_t aType);
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ void AddProtoHandler(nsXBLPrototypeHandler* aProtoHandler)
+ {
+ mProtoHandlers.AppendElement(aProtoHandler);
+ }
+
+ bool Matches(nsIAtom* aEventType, uint8_t aPhase, uint8_t aType) const
+ {
+ return (mEventType == aEventType && mPhase == aPhase && mType == aType);
+ }
+
+ void GetEventName(nsAString& aString) const
+ {
+ mEventType->ToString(aString);
+ }
+
+ uint8_t GetPhase() const
+ {
+ return mPhase;
+ }
+
+ uint8_t GetType() const
+ {
+ return mType;
+ }
+
+ void SetIsBoundToChrome(bool aIsBoundToChrome)
+ {
+ mIsBoundToChrome = aIsBoundToChrome;
+ }
+
+ void SetUsingContentXBLScope(bool aUsingContentXBLScope)
+ {
+ mUsingContentXBLScope = aUsingContentXBLScope;
+ }
+
+private:
+ nsXBLKeyEventHandler();
+ virtual ~nsXBLKeyEventHandler();
+
+ bool ExecuteMatchedHandlers(nsIDOMKeyEvent* aEvent, uint32_t aCharCode,
+ const IgnoreModifierState& aIgnoreModifierState);
+
+ nsTArray<nsXBLPrototypeHandler*> mProtoHandlers;
+ nsCOMPtr<nsIAtom> mEventType;
+ uint8_t mPhase;
+ uint8_t mType;
+ bool mIsBoundToChrome;
+ bool mUsingContentXBLScope;
+};
+
+already_AddRefed<nsXBLEventHandler>
+NS_NewXBLEventHandler(nsXBLPrototypeHandler* aHandler,
+ nsIAtom* aEventType);
+
+#endif
diff --git a/dom/xbl/nsXBLMaybeCompiled.h b/dom/xbl/nsXBLMaybeCompiled.h
new file mode 100644
index 000000000..d4b366b0e
--- /dev/null
+++ b/dom/xbl/nsXBLMaybeCompiled.h
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLMaybeCompiled_h__
+#define nsXBLMaybeCompiled_h__
+
+#include "js/GCAPI.h"
+
+/*
+ * A union containing either a pointer representing uncompiled source or a
+ * JSObject* representing the compiled result. The class is templated on the
+ * source object type.
+ *
+ * The purpose of abstracting this as a separate class is to allow it to be
+ * wrapped in a JS::Heap<T> to correctly handle post-barriering of the JSObject
+ * pointer, when present.
+ *
+ * No implementation of rootKind() is provided, which prevents
+ * Root<nsXBLMaybeCompiled<UncompiledT>> from being used.
+ */
+template <class UncompiledT>
+class nsXBLMaybeCompiled
+{
+public:
+ nsXBLMaybeCompiled() : mUncompiled(BIT_UNCOMPILED) {}
+
+ explicit nsXBLMaybeCompiled(UncompiledT* uncompiled)
+ : mUncompiled(reinterpret_cast<uintptr_t>(uncompiled) | BIT_UNCOMPILED) {}
+
+ explicit nsXBLMaybeCompiled(JSObject* compiled) : mCompiled(compiled) {}
+
+ bool IsCompiled() const
+ {
+ return !(mUncompiled & BIT_UNCOMPILED);
+ }
+
+ UncompiledT* GetUncompiled() const
+ {
+ MOZ_ASSERT(!IsCompiled(), "Attempt to get compiled function as uncompiled");
+ uintptr_t unmasked = mUncompiled & ~BIT_UNCOMPILED;
+ return reinterpret_cast<UncompiledT*>(unmasked);
+ }
+
+ JSObject* GetJSFunction() const
+ {
+ MOZ_ASSERT(IsCompiled(), "Attempt to get uncompiled function as compiled");
+ if (mCompiled) {
+ JS::ExposeObjectToActiveJS(mCompiled);
+ }
+ return mCompiled;
+ }
+
+ // This is appropriate for use in tracing methods, etc.
+ JSObject* GetJSFunctionPreserveColor() const
+ {
+ MOZ_ASSERT(IsCompiled(), "Attempt to get uncompiled function as compiled");
+ return mCompiled;
+ }
+
+private:
+ JSObject*& UnsafeGetJSFunction()
+ {
+ MOZ_ASSERT(IsCompiled(), "Attempt to get uncompiled function as compiled");
+ return mCompiled;
+ }
+
+ enum { BIT_UNCOMPILED = 1 << 0 };
+
+ union
+ {
+ // An pointer that represents the function before being compiled, with
+ // BIT_UNCOMPILED set.
+ uintptr_t mUncompiled;
+
+ // The JS object for the compiled result.
+ JSObject* mCompiled;
+ };
+
+ friend struct js::BarrierMethods<nsXBLMaybeCompiled<UncompiledT>>;
+};
+
+/* Add support for JS::Heap<nsXBLMaybeCompiled>. */
+namespace JS {
+
+template <class UncompiledT>
+struct GCPolicy<nsXBLMaybeCompiled<UncompiledT>>
+{
+ static nsXBLMaybeCompiled<UncompiledT> initial() { return nsXBLMaybeCompiled<UncompiledT>(); }
+};
+
+} // namespace JS
+
+namespace js {
+
+template <class UncompiledT>
+struct BarrierMethods<nsXBLMaybeCompiled<UncompiledT>>
+{
+ typedef struct BarrierMethods<JSObject *> Base;
+
+ static void postBarrier(nsXBLMaybeCompiled<UncompiledT>* functionp,
+ nsXBLMaybeCompiled<UncompiledT> prev,
+ nsXBLMaybeCompiled<UncompiledT> next)
+ {
+ if (next.IsCompiled()) {
+ Base::postBarrier(&functionp->UnsafeGetJSFunction(),
+ prev.IsCompiled() ? prev.UnsafeGetJSFunction() : nullptr,
+ next.UnsafeGetJSFunction());
+ } else if (prev.IsCompiled()) {
+ Base::postBarrier(&prev.UnsafeGetJSFunction(),
+ prev.UnsafeGetJSFunction(),
+ nullptr);
+ }
+ }
+ static void exposeToJS(nsXBLMaybeCompiled<UncompiledT> fun) {
+ if (fun.IsCompiled()) {
+ JS::ExposeObjectToActiveJS(fun.UnsafeGetJSFunction());
+ }
+ }
+};
+
+template <class T>
+struct IsHeapConstructibleType<nsXBLMaybeCompiled<T>>
+{ // Yes, this is the exception to the rule. Sorry.
+ static constexpr bool value = true;
+};
+
+template <class UncompiledT>
+class HeapBase<nsXBLMaybeCompiled<UncompiledT>>
+{
+ const JS::Heap<nsXBLMaybeCompiled<UncompiledT>>& wrapper() const {
+ return *static_cast<const JS::Heap<nsXBLMaybeCompiled<UncompiledT>>*>(this);
+ }
+
+ JS::Heap<nsXBLMaybeCompiled<UncompiledT>>& wrapper() {
+ return *static_cast<JS::Heap<nsXBLMaybeCompiled<UncompiledT>>*>(this);
+ }
+
+ const nsXBLMaybeCompiled<UncompiledT>* extract() const {
+ return wrapper().address();
+ }
+
+ nsXBLMaybeCompiled<UncompiledT>* extract() {
+ return wrapper().unsafeGet();
+ }
+
+public:
+ bool IsCompiled() const { return extract()->IsCompiled(); }
+ UncompiledT* GetUncompiled() const { return extract()->GetUncompiled(); }
+ JSObject* GetJSFunction() const { return extract()->GetJSFunction(); }
+ JSObject* GetJSFunctionPreserveColor() const { return extract()->GetJSFunctionPreserveColor(); }
+
+ void SetUncompiled(UncompiledT* source) {
+ wrapper() = nsXBLMaybeCompiled<UncompiledT>(source);
+ }
+
+ void SetJSFunction(JSObject* function) {
+ wrapper() = nsXBLMaybeCompiled<UncompiledT>(function);
+ }
+
+ JS::Heap<JSObject*>& AsHeapObject()
+ {
+ MOZ_ASSERT(extract()->IsCompiled());
+ return *reinterpret_cast<JS::Heap<JSObject*>*>(this);
+ }
+};
+
+} /* namespace js */
+
+#endif // nsXBLMaybeCompiled_h__
diff --git a/dom/xbl/nsXBLProtoImpl.cpp b/dom/xbl/nsXBLProtoImpl.cpp
new file mode 100644
index 000000000..4db9cabf0
--- /dev/null
+++ b/dom/xbl/nsXBLProtoImpl.cpp
@@ -0,0 +1,535 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsXBLProtoImpl.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsContentUtils.h"
+#include "nsIXPConnect.h"
+#include "nsIServiceManager.h"
+#include "nsIDOMNode.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsXBLProtoImplProperty.h"
+#include "nsIURI.h"
+#include "mozilla/AddonPathService.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/XULElementBinding.h"
+#include "xpcpublic.h"
+#include "js/CharacterEncoding.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using js::GetGlobalForObjectCrossCompartment;
+using js::AssertSameCompartment;
+
+nsresult
+nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aPrototypeBinding,
+ nsXBLBinding* aBinding)
+{
+ // This function is called to install a concrete implementation on a bound element using
+ // this prototype implementation as a guide. The prototype implementation is compiled lazily,
+ // so for the first bound element that needs a concrete implementation, we also build the
+ // prototype implementation.
+ if (!mMembers && !mFields) // Constructor and destructor also live in mMembers
+ return NS_OK; // Nothing to do, so let's not waste time.
+
+ // If the way this gets the script context changes, fix
+ // nsXBLProtoImplAnonymousMethod::Execute
+ nsIDocument* document = aBinding->GetBoundElement()->OwnerDoc();
+
+ // This sometimes gets called when we have no outer window and if we don't
+ // catch this, we get leaks during crashtests and reftests.
+ if (NS_WARN_IF(!document->GetWindow())) {
+ return NS_OK;
+ }
+
+ // |propertyHolder| (below) can be an existing object, so in theory we might
+ // hit something that could end up running script. We never want that to
+ // happen here, so we use an AutoJSAPI instead of an AutoEntryScript.
+ dom::AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(document->GetScopeObject()))) {
+ return NS_OK;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // InitTarget objects gives us back the JS object that represents the bound element and the
+ // class object in the bound document that represents the concrete version of this implementation.
+ // This function also has the side effect of building up the prototype implementation if it has
+ // not been built already.
+ JS::Rooted<JSObject*> targetClassObject(cx, nullptr);
+ bool targetObjectIsNew = false;
+ nsresult rv = InitTargetObjects(aPrototypeBinding,
+ aBinding->GetBoundElement(),
+ &targetClassObject,
+ &targetObjectIsNew);
+ NS_ENSURE_SUCCESS(rv, rv); // kick out if we were unable to properly intialize our target objects
+ MOZ_ASSERT(targetClassObject);
+
+ // If the prototype already existed, we don't need to install anything. return early.
+ if (!targetObjectIsNew)
+ return NS_OK;
+
+ // We want to define the canonical set of members in a safe place. If we're
+ // using a separate XBL scope, we want to define them there first (so that
+ // they'll be available for Xray lookups, among other things), and then copy
+ // the properties to the content-side prototype as needed. We don't need to
+ // bother about the field accessors here, since we don't use/support those
+ // for in-content bindings.
+
+ // First, start by entering the compartment of the XBL scope. This may or may
+ // not be the same compartment as globalObject.
+ JSAddonId* addonId = MapURIToAddonID(aPrototypeBinding->BindingURI());
+ JS::Rooted<JSObject*> globalObject(cx,
+ GetGlobalForObjectCrossCompartment(targetClassObject));
+ JS::Rooted<JSObject*> scopeObject(cx, xpc::GetScopeForXBLExecution(cx, globalObject, addonId));
+ NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
+ MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(scopeObject) == scopeObject);
+ JSAutoCompartment ac(cx, scopeObject);
+
+ // Determine the appropriate property holder.
+ //
+ // Note: If |targetIsNew| is false, we'll early-return above. However, that only
+ // tells us if the content-side object is new, which may be the case even if
+ // we've already set up the binding on the XBL side. For example, if we apply
+ // a binding #foo to a <span> when we've already applied it to a <div>, we'll
+ // end up with a different content prototype, but we'll already have a property
+ // holder called |foo| in the XBL scope. Check for that to avoid wasteful and
+ // weird property holder duplication.
+ const char16_t* className = aPrototypeBinding->ClassName().get();
+ JS::Rooted<JSObject*> propertyHolder(cx);
+ JS::Rooted<JS::PropertyDescriptor> existingHolder(cx);
+ if (scopeObject != globalObject &&
+ !JS_GetOwnUCPropertyDescriptor(cx, scopeObject, className, &existingHolder)) {
+ return NS_ERROR_FAILURE;
+ }
+ bool propertyHolderIsNew = !existingHolder.object() || !existingHolder.value().isObject();
+
+ if (!propertyHolderIsNew) {
+ propertyHolder = &existingHolder.value().toObject();
+ } else if (scopeObject != globalObject) {
+
+ // This is just a property holder, so it doesn't need any special JSClass.
+ propertyHolder = JS_NewObjectWithGivenProto(cx, nullptr, nullptr);
+ NS_ENSURE_TRUE(propertyHolder, NS_ERROR_OUT_OF_MEMORY);
+
+ // Define it as a property on the scopeObject, using the same name used on
+ // the content side.
+ bool ok = JS_DefineUCProperty(cx, scopeObject, className, -1, propertyHolder,
+ JSPROP_PERMANENT | JSPROP_READONLY,
+ JS_STUBGETTER, JS_STUBSETTER);
+ NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
+ } else {
+ propertyHolder = targetClassObject;
+ }
+
+ // Walk our member list and install each one in turn on the XBL scope object.
+ if (propertyHolderIsNew) {
+ for (nsXBLProtoImplMember* curr = mMembers;
+ curr;
+ curr = curr->GetNext())
+ curr->InstallMember(cx, propertyHolder);
+ }
+
+ // Now, if we're using a separate XBL scope, enter the compartment of the
+ // bound node and copy exposable properties to the prototype there. This
+ // rewraps them appropriately, which should result in cross-compartment
+ // function wrappers.
+ if (propertyHolder != targetClassObject) {
+ AssertSameCompartment(propertyHolder, scopeObject);
+ AssertSameCompartment(targetClassObject, globalObject);
+ bool inContentXBLScope = xpc::IsInContentXBLScope(scopeObject);
+ for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) {
+ if (!inContentXBLScope || curr->ShouldExposeToUntrustedContent()) {
+ JS::Rooted<jsid> id(cx);
+ JS::TwoByteChars chars(curr->GetName(), NS_strlen(curr->GetName()));
+ bool ok = JS_CharsToId(cx, chars, &id);
+ NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
+
+ bool found;
+ ok = JS_HasPropertyById(cx, propertyHolder, id, &found);
+ NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
+ if (!found) {
+ // Some members don't install anything in InstallMember (e.g.,
+ // nsXBLProtoImplAnonymousMethod). We need to skip copying in
+ // those cases.
+ continue;
+ }
+
+ ok = JS_CopyPropertyFrom(cx, id, targetClassObject, propertyHolder);
+ NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
+ }
+ }
+ }
+
+ // From here on out, work in the scope of the bound element.
+ JSAutoCompartment ac2(cx, targetClassObject);
+
+ // Install all of our field accessors.
+ for (nsXBLProtoImplField* curr = mFields;
+ curr;
+ curr = curr->GetNext())
+ curr->InstallAccessors(cx, targetClassObject);
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImpl::InitTargetObjects(nsXBLPrototypeBinding* aBinding,
+ nsIContent* aBoundElement,
+ JS::MutableHandle<JSObject*> aTargetClassObject,
+ bool* aTargetIsNew)
+{
+ nsresult rv = NS_OK;
+
+ if (!mPrecompiledMemberHolder) {
+ rv = CompilePrototypeMembers(aBinding); // This is the first time we've ever installed this binding on an element.
+ // We need to go ahead and compile all methods and properties on a class
+ // in our prototype binding.
+ if (NS_FAILED(rv))
+ return rv;
+
+ MOZ_ASSERT(mPrecompiledMemberHolder);
+ }
+
+ nsIDocument *ownerDoc = aBoundElement->OwnerDoc();
+ nsIGlobalObject *sgo;
+
+ if (!(sgo = ownerDoc->GetScopeObject())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Because our prototype implementation has a class, we need to build up a corresponding
+ // class for the concrete implementation in the bound document.
+ AutoJSContext cx;
+ JS::Rooted<JSObject*> global(cx, sgo->GetGlobalJSObject());
+ JS::Rooted<JS::Value> v(cx);
+
+ JSAutoCompartment ac(cx, global);
+ // Make sure the interface object is created before the prototype object
+ // so that XULElement is hidden from content. See bug 909340.
+ bool defineOnGlobal = dom::XULElementBinding::ConstructorEnabled(cx, global);
+ dom::XULElementBinding::GetConstructorObjectHandle(cx, defineOnGlobal);
+
+ rv = nsContentUtils::WrapNative(cx, aBoundElement, &v,
+ /* aAllowWrapping = */ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JS::Rooted<JSObject*> value(cx, &v.toObject());
+ JSAutoCompartment ac2(cx, value);
+
+ // All of the above code was just obtaining the bound element's script object and its immediate
+ // concrete base class. We need to alter the object so that our concrete class is interposed
+ // between the object and its base class. We become the new base class of the object, and the
+ // object's old base class becomes the new class' base class.
+ rv = aBinding->InitClass(mClassName, cx, value, aTargetClassObject, aTargetIsNew);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ aBoundElement->PreserveWrapper(aBoundElement);
+
+ return rv;
+}
+
+nsresult
+nsXBLProtoImpl::CompilePrototypeMembers(nsXBLPrototypeBinding* aBinding)
+{
+ // We want to pre-compile our implementation's members against a "prototype context". Then when we actually
+ // bind the prototype to a real xbl instance, we'll clone the pre-compiled JS into the real instance's
+ // context.
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(xpc::CompilationScope())))
+ return NS_ERROR_FAILURE;
+ JSContext* cx = jsapi.cx();
+
+ mPrecompiledMemberHolder = JS_NewObjectWithGivenProto(cx, nullptr, nullptr);
+ if (!mPrecompiledMemberHolder)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Now that we have a class object installed, we walk our member list and compile each of our
+ // properties and methods in turn.
+ JS::Rooted<JSObject*> rootedHolder(cx, mPrecompiledMemberHolder);
+ for (nsXBLProtoImplMember* curr = mMembers;
+ curr;
+ curr = curr->GetNext()) {
+ nsresult rv = curr->CompileMember(jsapi, mClassName, rootedHolder);
+ if (NS_FAILED(rv)) {
+ DestroyMembers();
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+nsXBLProtoImpl::LookupMember(JSContext* aCx, nsString& aName,
+ JS::Handle<jsid> aNameAsId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc,
+ JS::Handle<JSObject*> aClassObject)
+{
+ for (nsXBLProtoImplMember* m = mMembers; m; m = m->GetNext()) {
+ if (aName.Equals(m->GetName())) {
+ return JS_GetPropertyDescriptorById(aCx, aClassObject, aNameAsId, aDesc);
+ }
+ }
+ return true;
+}
+
+void
+nsXBLProtoImpl::Trace(const TraceCallbacks& aCallbacks, void *aClosure)
+{
+ // If we don't have a class object then we either didn't compile members
+ // or we only have fields, in both cases there are no cycles through our
+ // members.
+ if (!mPrecompiledMemberHolder) {
+ return;
+ }
+
+ nsXBLProtoImplMember *member;
+ for (member = mMembers; member; member = member->GetNext()) {
+ member->Trace(aCallbacks, aClosure);
+ }
+}
+
+void
+nsXBLProtoImpl::UnlinkJSObjects()
+{
+ if (mPrecompiledMemberHolder) {
+ DestroyMembers();
+ }
+}
+
+nsXBLProtoImplField*
+nsXBLProtoImpl::FindField(const nsString& aFieldName) const
+{
+ for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
+ if (aFieldName.Equals(f->GetName())) {
+ return f;
+ }
+ }
+
+ return nullptr;
+}
+
+bool
+nsXBLProtoImpl::ResolveAllFields(JSContext *cx, JS::Handle<JSObject*> obj) const
+{
+ for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
+ nsDependentString name(f->GetName());
+ bool dummy;
+ if (!::JS_HasUCProperty(cx, obj, name.get(), name.Length(), &dummy)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void
+nsXBLProtoImpl::UndefineFields(JSContext *cx, JS::Handle<JSObject*> obj) const
+{
+ JSAutoRequest ar(cx);
+ for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
+ nsDependentString name(f->GetName());
+
+ const char16_t* s = name.get();
+ bool hasProp;
+ if (::JS_AlreadyHasOwnUCProperty(cx, obj, s, name.Length(), &hasProp) &&
+ hasProp) {
+ JS::ObjectOpResult ignored;
+ ::JS_DeleteUCProperty(cx, obj, s, name.Length(), ignored);
+ }
+ }
+}
+
+void
+nsXBLProtoImpl::DestroyMembers()
+{
+ MOZ_ASSERT(mPrecompiledMemberHolder);
+
+ delete mMembers;
+ mMembers = nullptr;
+ mConstructor = nullptr;
+ mDestructor = nullptr;
+}
+
+nsresult
+nsXBLProtoImpl::Read(nsIObjectInputStream* aStream,
+ nsXBLPrototypeBinding* aBinding)
+{
+ AssertInCompilationScope();
+ AutoJSContext cx;
+ // Set up a class object first so that deserialization is possible
+ mPrecompiledMemberHolder = JS_NewObjectWithGivenProto(cx, nullptr, nullptr);
+ if (!mPrecompiledMemberHolder)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsXBLProtoImplField* previousField = nullptr;
+ nsXBLProtoImplMember* previousMember = nullptr;
+
+ do {
+ XBLBindingSerializeDetails type;
+ nsresult rv = aStream->Read8(&type);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (type == XBLBinding_Serialize_NoMoreItems)
+ break;
+
+ switch (type & XBLBinding_Serialize_Mask) {
+ case XBLBinding_Serialize_Field:
+ {
+ nsXBLProtoImplField* field =
+ new nsXBLProtoImplField(type & XBLBinding_Serialize_ReadOnly);
+ rv = field->Read(aStream);
+ if (NS_FAILED(rv)) {
+ delete field;
+ return rv;
+ }
+
+ if (previousField) {
+ previousField->SetNext(field);
+ }
+ else {
+ mFields = field;
+ }
+ previousField = field;
+
+ break;
+ }
+ case XBLBinding_Serialize_GetterProperty:
+ case XBLBinding_Serialize_SetterProperty:
+ case XBLBinding_Serialize_GetterSetterProperty:
+ {
+ nsAutoString name;
+ nsresult rv = aStream->ReadString(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsXBLProtoImplProperty* prop =
+ new nsXBLProtoImplProperty(name.get(), type & XBLBinding_Serialize_ReadOnly);
+ rv = prop->Read(aStream, type & XBLBinding_Serialize_Mask);
+ if (NS_FAILED(rv)) {
+ delete prop;
+ return rv;
+ }
+
+ previousMember = AddMember(prop, previousMember);
+ break;
+ }
+ case XBLBinding_Serialize_Method:
+ {
+ nsAutoString name;
+ rv = aStream->ReadString(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsXBLProtoImplMethod* method = new nsXBLProtoImplMethod(name.get());
+ rv = method->Read(aStream);
+ if (NS_FAILED(rv)) {
+ delete method;
+ return rv;
+ }
+
+ previousMember = AddMember(method, previousMember);
+ break;
+ }
+ case XBLBinding_Serialize_Constructor:
+ {
+ nsAutoString name;
+ rv = aStream->ReadString(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mConstructor = new nsXBLProtoImplAnonymousMethod(name.get());
+ rv = mConstructor->Read(aStream);
+ if (NS_FAILED(rv)) {
+ delete mConstructor;
+ mConstructor = nullptr;
+ return rv;
+ }
+
+ previousMember = AddMember(mConstructor, previousMember);
+ break;
+ }
+ case XBLBinding_Serialize_Destructor:
+ {
+ nsAutoString name;
+ rv = aStream->ReadString(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDestructor = new nsXBLProtoImplAnonymousMethod(name.get());
+ rv = mDestructor->Read(aStream);
+ if (NS_FAILED(rv)) {
+ delete mDestructor;
+ mDestructor = nullptr;
+ return rv;
+ }
+
+ previousMember = AddMember(mDestructor, previousMember);
+ break;
+ }
+ default:
+ NS_ERROR("Unexpected binding member type");
+ break;
+ }
+ } while (1);
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImpl::Write(nsIObjectOutputStream* aStream,
+ nsXBLPrototypeBinding* aBinding)
+{
+ nsresult rv;
+
+ if (!mPrecompiledMemberHolder) {
+ rv = CompilePrototypeMembers(aBinding);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = aStream->WriteUtf8Z(mClassName.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsXBLProtoImplField* curr = mFields; curr; curr = curr->GetNext()) {
+ rv = curr->Write(aStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ for (nsXBLProtoImplMember* curr = mMembers; curr; curr = curr->GetNext()) {
+ if (curr == mConstructor) {
+ rv = mConstructor->Write(aStream, XBLBinding_Serialize_Constructor);
+ }
+ else if (curr == mDestructor) {
+ rv = mDestructor->Write(aStream, XBLBinding_Serialize_Destructor);
+ }
+ else {
+ rv = curr->Write(aStream);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return aStream->Write8(XBLBinding_Serialize_NoMoreItems);
+}
+
+void
+NS_NewXBLProtoImpl(nsXBLPrototypeBinding* aBinding,
+ const char16_t* aClassName,
+ nsXBLProtoImpl** aResult)
+{
+ nsXBLProtoImpl* impl = new nsXBLProtoImpl();
+ if (aClassName) {
+ impl->mClassName = aClassName;
+ } else {
+ nsCString spec;
+ nsresult rv = aBinding->BindingURI()->GetSpec(spec);
+ // XXX: should handle this better
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ impl->mClassName = NS_ConvertUTF8toUTF16(spec);
+ }
+
+ aBinding->SetImplementation(impl);
+ *aResult = impl;
+}
+
diff --git a/dom/xbl/nsXBLProtoImpl.h b/dom/xbl/nsXBLProtoImpl.h
new file mode 100644
index 000000000..d65637ac2
--- /dev/null
+++ b/dom/xbl/nsXBLProtoImpl.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLProtoImpl_h__
+#define nsXBLProtoImpl_h__
+
+#include "nsMemory.h"
+#include "nsXBLPrototypeHandler.h"
+#include "nsXBLProtoImplMember.h"
+#include "nsXBLProtoImplField.h"
+#include "nsXBLBinding.h"
+
+class nsXBLPrototypeBinding;
+class nsXBLProtoImplAnonymousMethod;
+
+class nsXBLProtoImpl final
+{
+public:
+ nsXBLProtoImpl()
+ : mPrecompiledMemberHolder(nullptr),
+ mMembers(nullptr),
+ mFields(nullptr),
+ mConstructor(nullptr),
+ mDestructor(nullptr)
+ {
+ MOZ_COUNT_CTOR(nsXBLProtoImpl);
+ }
+ ~nsXBLProtoImpl()
+ {
+ MOZ_COUNT_DTOR(nsXBLProtoImpl);
+ // Note: the constructor and destructor are in mMembers, so we'll
+ // clean them up automatically.
+ delete mMembers;
+ delete mFields;
+ }
+
+
+ nsresult InstallImplementation(nsXBLPrototypeBinding* aPrototypeBinding, nsXBLBinding* aBinding);
+
+private:
+ nsresult InitTargetObjects(nsXBLPrototypeBinding* aBinding,
+ nsIContent* aBoundElement,
+ JS::MutableHandle<JSObject*> aTargetClassObject,
+ bool* aTargetIsNew);
+
+public:
+ nsresult CompilePrototypeMembers(nsXBLPrototypeBinding* aBinding);
+
+ bool LookupMember(JSContext* aCx, nsString& aName, JS::Handle<jsid> aNameAsId,
+ JS::MutableHandle<JS::PropertyDescriptor> aDesc,
+ JS::Handle<JSObject*> aClassObject);
+
+ void SetMemberList(nsXBLProtoImplMember* aMemberList)
+ {
+ delete mMembers;
+ mMembers = aMemberList;
+ }
+
+ void SetFieldList(nsXBLProtoImplField* aFieldList)
+ {
+ delete mFields;
+ mFields = aFieldList;
+ }
+
+ void Trace(const TraceCallbacks& aCallbacks, void *aClosure);
+ void UnlinkJSObjects();
+
+ nsXBLProtoImplField* FindField(const nsString& aFieldName) const;
+
+ // Resolve all the fields for this implementation on the object |obj| False
+ // return means a JS exception was set.
+ bool ResolveAllFields(JSContext *cx, JS::Handle<JSObject*> obj) const;
+
+ // Undefine all our fields from object |obj| (which should be a
+ // JSObject for a bound element).
+ void UndefineFields(JSContext* cx, JS::Handle<JSObject*> obj) const;
+
+ bool CompiledMembers() const {
+ return mPrecompiledMemberHolder != nullptr;
+ }
+
+ nsresult Read(nsIObjectInputStream* aStream,
+ nsXBLPrototypeBinding* aBinding);
+ nsresult Write(nsIObjectOutputStream* aStream,
+ nsXBLPrototypeBinding* aBinding);
+
+protected:
+ // used by Read to add each member
+ nsXBLProtoImplMember* AddMember(nsXBLProtoImplMember* aMember,
+ nsXBLProtoImplMember* aPreviousMember)
+ {
+ if (aPreviousMember)
+ aPreviousMember->SetNext(aMember);
+ else
+ mMembers = aMember;
+ return aMember;
+ }
+
+ void DestroyMembers();
+
+public:
+ nsString mClassName; // The name of the class.
+
+protected:
+ JSObject* mPrecompiledMemberHolder; // The class object for the binding. We'll use this to pre-compile properties
+ // and methods for the binding.
+
+ nsXBLProtoImplMember* mMembers; // The members of an implementation are chained in this singly-linked list.
+
+ nsXBLProtoImplField* mFields; // Our fields
+
+public:
+ nsXBLProtoImplAnonymousMethod* mConstructor; // Our class constructor.
+ nsXBLProtoImplAnonymousMethod* mDestructor; // Our class destructor.
+};
+
+void
+NS_NewXBLProtoImpl(nsXBLPrototypeBinding* aBinding,
+ const char16_t* aClassName,
+ nsXBLProtoImpl** aResult);
+
+#endif // nsXBLProtoImpl_h__
diff --git a/dom/xbl/nsXBLProtoImplField.cpp b/dom/xbl/nsXBLProtoImplField.cpp
new file mode 100644
index 000000000..9c9857f1d
--- /dev/null
+++ b/dom/xbl/nsXBLProtoImplField.cpp
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIAtom.h"
+#include "nsIContent.h"
+#include "nsString.h"
+#include "nsJSUtils.h"
+#include "jsapi.h"
+#include "js/CharacterEncoding.h"
+#include "nsUnicharUtils.h"
+#include "nsReadableUtils.h"
+#include "nsXBLProtoImplField.h"
+#include "nsIScriptContext.h"
+#include "nsIURI.h"
+#include "nsXBLSerialize.h"
+#include "nsXBLPrototypeBinding.h"
+#include "mozilla/AddonPathService.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsGlobalWindow.h"
+#include "xpcpublic.h"
+#include "WrapperFactory.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsXBLProtoImplField::nsXBLProtoImplField(const char16_t* aName, const char16_t* aReadOnly)
+ : mNext(nullptr),
+ mFieldText(nullptr),
+ mFieldTextLength(0),
+ mLineNumber(0)
+{
+ MOZ_COUNT_CTOR(nsXBLProtoImplField);
+ mName = NS_strdup(aName); // XXXbz make more sense to use a stringbuffer?
+
+ mJSAttributes = JSPROP_ENUMERATE;
+ if (aReadOnly) {
+ nsAutoString readOnly; readOnly.Assign(aReadOnly);
+ if (readOnly.LowerCaseEqualsLiteral("true"))
+ mJSAttributes |= JSPROP_READONLY;
+ }
+}
+
+
+nsXBLProtoImplField::nsXBLProtoImplField(const bool aIsReadOnly)
+ : mNext(nullptr),
+ mFieldText(nullptr),
+ mFieldTextLength(0),
+ mLineNumber(0)
+{
+ MOZ_COUNT_CTOR(nsXBLProtoImplField);
+
+ mJSAttributes = JSPROP_ENUMERATE;
+ if (aIsReadOnly)
+ mJSAttributes |= JSPROP_READONLY;
+}
+
+nsXBLProtoImplField::~nsXBLProtoImplField()
+{
+ MOZ_COUNT_DTOR(nsXBLProtoImplField);
+ if (mFieldText)
+ free(mFieldText);
+ free(mName);
+ NS_CONTENT_DELETE_LIST_MEMBER(nsXBLProtoImplField, this, mNext);
+}
+
+void
+nsXBLProtoImplField::AppendFieldText(const nsAString& aText)
+{
+ if (mFieldText) {
+ nsDependentString fieldTextStr(mFieldText, mFieldTextLength);
+ nsAutoString newFieldText = fieldTextStr + aText;
+ char16_t* temp = mFieldText;
+ mFieldText = ToNewUnicode(newFieldText);
+ mFieldTextLength = newFieldText.Length();
+ free(temp);
+ }
+ else {
+ mFieldText = ToNewUnicode(aText);
+ mFieldTextLength = aText.Length();
+ }
+}
+
+// XBL fields are represented on elements inheriting that field a bit trickily.
+// When setting up the XBL prototype object, we install accessors for the fields
+// on the prototype object. Those accessors, when used, will then (via
+// InstallXBLField below) reify a property for the field onto the actual XBL-backed
+// element.
+//
+// The accessor property is a plain old property backed by a getter function and
+// a setter function. These properties are backed by the FieldGetter and
+// FieldSetter natives; they're created by InstallAccessors. The precise field to be
+// reified is identified using two extra slots on the getter/setter functions.
+// XBLPROTO_SLOT stores the XBL prototype object that provides the field.
+// FIELD_SLOT stores the name of the field, i.e. its JavaScript property name.
+//
+// This two-step field installation process -- creating an accessor on the
+// prototype, then have that reify an own property on the actual element -- is
+// admittedly convoluted. Better would be for XBL-backed elements to be proxies
+// that could resolve fields onto themselves. But given that XBL bindings are
+// associated with elements mutably -- you can add/remove/change -moz-binding
+// whenever you want, alas -- doing so would require all elements to be proxies,
+// which isn't performant now. So we do this two-step instead.
+static const uint32_t XBLPROTO_SLOT = 0;
+static const uint32_t FIELD_SLOT = 1;
+
+bool
+ValueHasISupportsPrivate(JS::Handle<JS::Value> v)
+{
+ if (!v.isObject()) {
+ return false;
+ }
+
+ const DOMJSClass* domClass = GetDOMClass(&v.toObject());
+ if (domClass) {
+ return domClass->mDOMObjectIsISupports;
+ }
+
+ const JSClass* clasp = ::JS_GetClass(&v.toObject());
+ const uint32_t HAS_PRIVATE_NSISUPPORTS =
+ JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS;
+ return (clasp->flags & HAS_PRIVATE_NSISUPPORTS) == HAS_PRIVATE_NSISUPPORTS;
+}
+
+#ifdef DEBUG
+static bool
+ValueHasISupportsPrivate(JSContext* cx, const JS::Value& aVal)
+{
+ JS::Rooted<JS::Value> v(cx, aVal);
+ return ValueHasISupportsPrivate(v);
+}
+#endif
+
+// Define a shadowing property on |this| for the XBL field defined by the
+// contents of the callee's reserved slots. If the property was defined,
+// *installed will be true, and idp will be set to the property name that was
+// defined.
+static bool
+InstallXBLField(JSContext* cx,
+ JS::Handle<JSObject*> callee, JS::Handle<JSObject*> thisObj,
+ JS::MutableHandle<jsid> idp, bool* installed)
+{
+ *installed = false;
+
+ // First ensure |this| is a reasonable XBL bound node.
+ //
+ // FieldAccessorGuard already determined whether |thisObj| was acceptable as
+ // |this| in terms of not throwing a TypeError. Assert this for good measure.
+ MOZ_ASSERT(ValueHasISupportsPrivate(cx, JS::ObjectValue(*thisObj)));
+
+ // But there are some cases where we must accept |thisObj| but not install a
+ // property on it, or otherwise touch it. Hence this split of |this|-vetting
+ // duties.
+ nsCOMPtr<nsISupports> native = xpc::UnwrapReflectorToISupports(thisObj);
+ if (!native) {
+ // Looks like whatever |thisObj| is it's not our nsIContent. It might well
+ // be the proto our binding installed, however, where the private is the
+ // nsXBLDocumentInfo, so just baul out quietly. Do NOT throw an exception
+ // here.
+ //
+ // We could make this stricter by checking the class maybe, but whatever.
+ return true;
+ }
+
+ nsCOMPtr<nsIContent> xblNode = do_QueryInterface(native);
+ if (!xblNode) {
+ xpc::Throw(cx, NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ // Now that |this| is okay, actually install the field.
+
+ // Because of the possibility (due to XBL binding inheritance, because each
+ // XBL binding lives in its own global object) that |this| might be in a
+ // different compartment from the callee (not to mention that this method can
+ // be called with an arbitrary |this| regardless of how insane XBL is), and
+ // because in this method we've entered |this|'s compartment (see in
+ // Field[GS]etter where we attempt a cross-compartment call), we must enter
+ // the callee's compartment to access its reserved slots.
+ nsXBLPrototypeBinding* protoBinding;
+ nsAutoJSString fieldName;
+ {
+ JSAutoCompartment ac(cx, callee);
+
+ JS::Rooted<JSObject*> xblProto(cx);
+ xblProto = &js::GetFunctionNativeReserved(callee, XBLPROTO_SLOT).toObject();
+
+ JS::Rooted<JS::Value> name(cx, js::GetFunctionNativeReserved(callee, FIELD_SLOT));
+ if (!fieldName.init(cx, name.toString())) {
+ return false;
+ }
+
+ MOZ_ALWAYS_TRUE(JS_ValueToId(cx, name, idp));
+
+ // If a separate XBL scope is being used, the callee is not same-compartment
+ // with the xbl prototype, and the object is a cross-compartment wrapper.
+ xblProto = js::UncheckedUnwrap(xblProto);
+ JSAutoCompartment ac2(cx, xblProto);
+ JS::Value slotVal = ::JS_GetReservedSlot(xblProto, 0);
+ protoBinding = static_cast<nsXBLPrototypeBinding*>(slotVal.toPrivate());
+ MOZ_ASSERT(protoBinding);
+ }
+
+ nsXBLProtoImplField* field = protoBinding->FindField(fieldName);
+ MOZ_ASSERT(field);
+
+ nsresult rv = field->InstallField(thisObj, protoBinding->DocURI(), installed);
+ if (NS_SUCCEEDED(rv)) {
+ return true;
+ }
+
+ if (!::JS_IsExceptionPending(cx)) {
+ xpc::Throw(cx, rv);
+ }
+ return false;
+}
+
+bool
+FieldGetterImpl(JSContext *cx, const JS::CallArgs& args)
+{
+ JS::Handle<JS::Value> thisv = args.thisv();
+ MOZ_ASSERT(ValueHasISupportsPrivate(thisv));
+
+ JS::Rooted<JSObject*> thisObj(cx, &thisv.toObject());
+
+ // We should be in the compartment of |this|. If we got here via nativeCall,
+ // |this| is not same-compartment with |callee|, and it's possible via
+ // asymmetric security semantics that |args.calleev()| is actually a security
+ // wrapper. In this case, we know we want to do an unsafe unwrap, and
+ // InstallXBLField knows how to handle cross-compartment pointers.
+ bool installed = false;
+ JS::Rooted<JSObject*> callee(cx, js::UncheckedUnwrap(&args.calleev().toObject()));
+ JS::Rooted<jsid> id(cx);
+ if (!InstallXBLField(cx, callee, thisObj, &id, &installed)) {
+ return false;
+ }
+
+ if (!installed) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ return JS_GetPropertyById(cx, thisObj, id, args.rval());
+}
+
+static bool
+FieldGetter(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ return JS::CallNonGenericMethod<ValueHasISupportsPrivate, FieldGetterImpl>
+ (cx, args);
+}
+
+bool
+FieldSetterImpl(JSContext *cx, const JS::CallArgs& args)
+{
+ JS::Handle<JS::Value> thisv = args.thisv();
+ MOZ_ASSERT(ValueHasISupportsPrivate(thisv));
+
+ JS::Rooted<JSObject*> thisObj(cx, &thisv.toObject());
+
+ // We should be in the compartment of |this|. If we got here via nativeCall,
+ // |this| is not same-compartment with |callee|, and it's possible via
+ // asymmetric security semantics that |args.calleev()| is actually a security
+ // wrapper. In this case, we know we want to do an unsafe unwrap, and
+ // InstallXBLField knows how to handle cross-compartment pointers.
+ bool installed = false;
+ JS::Rooted<JSObject*> callee(cx, js::UncheckedUnwrap(&args.calleev().toObject()));
+ JS::Rooted<jsid> id(cx);
+ if (!InstallXBLField(cx, callee, thisObj, &id, &installed)) {
+ return false;
+ }
+
+ if (installed) {
+ if (!::JS_SetPropertyById(cx, thisObj, id, args.get(0))) {
+ return false;
+ }
+ }
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+FieldSetter(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ return JS::CallNonGenericMethod<ValueHasISupportsPrivate, FieldSetterImpl>
+ (cx, args);
+}
+
+nsresult
+nsXBLProtoImplField::InstallAccessors(JSContext* aCx,
+ JS::Handle<JSObject*> aTargetClassObject)
+{
+ MOZ_ASSERT(js::IsObjectInContextCompartment(aTargetClassObject, aCx));
+ JS::Rooted<JSObject*> globalObject(aCx, JS_GetGlobalForObject(aCx, aTargetClassObject));
+ JS::Rooted<JSObject*> scopeObject(aCx, xpc::GetXBLScopeOrGlobal(aCx, globalObject));
+ NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
+
+ // Don't install it if the field is empty; see also InstallField which also must
+ // implement the not-empty requirement.
+ if (IsEmpty()) {
+ return NS_OK;
+ }
+
+ // Install a getter/setter pair which will resolve the field onto the actual
+ // object, when invoked.
+
+ // Get the field name as an id.
+ JS::Rooted<jsid> id(aCx);
+ JS::TwoByteChars chars(mName, NS_strlen(mName));
+ if (!JS_CharsToId(aCx, chars, &id))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Properties/Methods have historically taken precendence over fields. We
+ // install members first, so just bounce here if the property is already
+ // defined.
+ bool found = false;
+ if (!JS_AlreadyHasOwnPropertyById(aCx, aTargetClassObject, id, &found))
+ return NS_ERROR_FAILURE;
+ if (found)
+ return NS_OK;
+
+ // FieldGetter and FieldSetter need to run in the XBL scope so that they can
+ // see through any SOWs on their targets.
+
+ // First, enter the XBL scope, and compile the functions there.
+ JSAutoCompartment ac(aCx, scopeObject);
+ JS::Rooted<JS::Value> wrappedClassObj(aCx, JS::ObjectValue(*aTargetClassObject));
+ if (!JS_WrapValue(aCx, &wrappedClassObj))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ JS::Rooted<JSObject*> get(aCx,
+ JS_GetFunctionObject(js::NewFunctionByIdWithReserved(aCx, FieldGetter,
+ 0, 0, id)));
+ if (!get) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ js::SetFunctionNativeReserved(get, XBLPROTO_SLOT, wrappedClassObj);
+ js::SetFunctionNativeReserved(get, FIELD_SLOT,
+ JS::StringValue(JSID_TO_STRING(id)));
+
+ JS::Rooted<JSObject*> set(aCx,
+ JS_GetFunctionObject(js::NewFunctionByIdWithReserved(aCx, FieldSetter,
+ 1, 0, id)));
+ if (!set) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ js::SetFunctionNativeReserved(set, XBLPROTO_SLOT, wrappedClassObj);
+ js::SetFunctionNativeReserved(set, FIELD_SLOT,
+ JS::StringValue(JSID_TO_STRING(id)));
+
+ // Now, re-enter the class object's scope, wrap the getters/setters, and define
+ // them there.
+ JSAutoCompartment ac2(aCx, aTargetClassObject);
+ if (!JS_WrapObject(aCx, &get) || !JS_WrapObject(aCx, &set)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!::JS_DefinePropertyById(aCx, aTargetClassObject, id, JS::UndefinedHandleValue,
+ AccessorAttributes(),
+ JS_DATA_TO_FUNC_PTR(JSNative, get.get()),
+ JS_DATA_TO_FUNC_PTR(JSNative, set.get()))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImplField::InstallField(JS::Handle<JSObject*> aBoundNode,
+ nsIURI* aBindingDocURI,
+ bool* aDidInstall) const
+{
+ NS_PRECONDITION(aBoundNode,
+ "uh-oh, bound node should NOT be null or bad things will "
+ "happen");
+
+ *aDidInstall = false;
+
+ // Empty fields are treated as not actually present.
+ if (IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsAutoMicroTask mt;
+
+ nsAutoCString uriSpec;
+ nsresult rv = aBindingDocURI->GetSpec(uriSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsIGlobalObject* globalObject = xpc::WindowGlobalOrNull(aBoundNode);
+ if (!globalObject) {
+ return NS_OK;
+ }
+
+ // We are going to run script via EvaluateString, so we need a script entry
+ // point, but as this is XBL related it does not appear in the HTML spec.
+ // We need an actual JSContext to do GetScopeForXBLExecution, and it needs to
+ // be in the compartment of globalObject. But we want our XBL execution scope
+ // to be our entry global.
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(globalObject)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ MOZ_ASSERT(!::JS_IsExceptionPending(jsapi.cx()),
+ "Shouldn't get here when an exception is pending!");
+
+ JSAddonId* addonId = MapURIToAddonID(aBindingDocURI);
+
+ // Note: the UNWRAP_OBJECT may mutate boundNode; don't use it after that call.
+ JS::Rooted<JSObject*> boundNode(jsapi.cx(), aBoundNode);
+ Element* boundElement = nullptr;
+ rv = UNWRAP_OBJECT(Element, &boundNode, boundElement);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // First, enter the xbl scope, build the element's scope chain, and use
+ // that as the scope chain for the evaluation.
+ JS::Rooted<JSObject*> scopeObject(jsapi.cx(),
+ xpc::GetScopeForXBLExecution(jsapi.cx(), aBoundNode, addonId));
+ NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
+
+ AutoEntryScript aes(scopeObject, "XBL <field> initialization", true);
+ JSContext* cx = aes.cx();
+
+ JS::Rooted<JS::Value> result(cx);
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(uriSpec.get(), mLineNumber)
+ .setVersion(JSVERSION_LATEST);
+ nsJSUtils::EvaluateOptions evalOptions(cx);
+ if (!nsJSUtils::GetScopeChainForElement(cx, boundElement,
+ evalOptions.scopeChain)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ rv = nsJSUtils::EvaluateString(cx, nsDependentString(mFieldText,
+ mFieldTextLength),
+ scopeObject, options, evalOptions, &result);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW) {
+ // Report the exception now, before we try using the JSContext for
+ // the JS_DefineUCProperty call. Note that this reports in our current
+ // compartment, which is the XBL scope.
+ aes.ReportException();
+ }
+
+ // Now, enter the node's compartment, wrap the eval result, and define it on
+ // the bound node.
+ JSAutoCompartment ac2(cx, aBoundNode);
+ nsDependentString name(mName);
+ if (!JS_WrapValue(cx, &result) ||
+ !::JS_DefineUCProperty(cx, aBoundNode,
+ reinterpret_cast<const char16_t*>(mName),
+ name.Length(), result, mJSAttributes)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ *aDidInstall = true;
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImplField::Read(nsIObjectInputStream* aStream)
+{
+ nsAutoString name;
+ nsresult rv = aStream->ReadString(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mName = ToNewUnicode(name);
+
+ rv = aStream->Read32(&mLineNumber);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString fieldText;
+ rv = aStream->ReadString(fieldText);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mFieldTextLength = fieldText.Length();
+ if (mFieldTextLength)
+ mFieldText = ToNewUnicode(fieldText);
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImplField::Write(nsIObjectOutputStream* aStream)
+{
+ XBLBindingSerializeDetails type = XBLBinding_Serialize_Field;
+
+ if (mJSAttributes & JSPROP_READONLY) {
+ type |= XBLBinding_Serialize_ReadOnly;
+ }
+
+ nsresult rv = aStream->Write8(type);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStream->WriteWStringZ(mName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStream->Write32(mLineNumber);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aStream->WriteWStringZ(mFieldText ? mFieldText : u"");
+}
diff --git a/dom/xbl/nsXBLProtoImplField.h b/dom/xbl/nsXBLProtoImplField.h
new file mode 100644
index 000000000..667f03a61
--- /dev/null
+++ b/dom/xbl/nsXBLProtoImplField.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLProtoImplField_h__
+#define nsXBLProtoImplField_h__
+
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "jsapi.h"
+#include "nsString.h"
+#include "nsXBLProtoImplMember.h"
+
+class nsIObjectInputStream;
+class nsIObjectOutputStream;
+class nsIURI;
+
+class nsXBLProtoImplField
+{
+public:
+ nsXBLProtoImplField(const char16_t* aName, const char16_t* aReadOnly);
+ explicit nsXBLProtoImplField(const bool aIsReadOnly);
+ ~nsXBLProtoImplField();
+
+ void AppendFieldText(const nsAString& aText);
+ void SetLineNumber(uint32_t aLineNumber) {
+ mLineNumber = aLineNumber;
+ }
+
+ nsXBLProtoImplField* GetNext() const { return mNext; }
+ void SetNext(nsXBLProtoImplField* aNext) { mNext = aNext; }
+
+ nsresult InstallField(JS::Handle<JSObject*> aBoundNode,
+ nsIURI* aBindingDocURI,
+ bool* aDidInstall) const;
+
+ nsresult InstallAccessors(JSContext* aCx,
+ JS::Handle<JSObject*> aTargetClassObject);
+
+ nsresult Read(nsIObjectInputStream* aStream);
+ nsresult Write(nsIObjectOutputStream* aStream);
+
+ const char16_t* GetName() const { return mName; }
+
+ unsigned AccessorAttributes() const {
+ return JSPROP_SHARED | JSPROP_GETTER | JSPROP_SETTER |
+ (mJSAttributes & (JSPROP_ENUMERATE | JSPROP_PERMANENT));
+ }
+
+ bool IsEmpty() const { return mFieldTextLength == 0; }
+
+protected:
+ nsXBLProtoImplField* mNext;
+ char16_t* mName;
+ char16_t* mFieldText;
+ uint32_t mFieldTextLength;
+ uint32_t mLineNumber;
+ unsigned mJSAttributes;
+};
+
+#endif // nsXBLProtoImplField_h__
diff --git a/dom/xbl/nsXBLProtoImplMember.h b/dom/xbl/nsXBLProtoImplMember.h
new file mode 100644
index 000000000..4a2789d93
--- /dev/null
+++ b/dom/xbl/nsXBLProtoImplMember.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLProtoImplMember_h__
+#define nsXBLProtoImplMember_h__
+
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "nsString.h"
+#include "nsIServiceManager.h"
+#include "nsContentUtils.h" // For NS_CONTENT_DELETE_LIST_MEMBER.
+#include "nsCycleCollectionParticipant.h"
+
+class nsIObjectOutputStream;
+
+struct nsXBLTextWithLineNumber
+{
+ char16_t* mText;
+ uint32_t mLineNumber;
+
+ nsXBLTextWithLineNumber() :
+ mText(nullptr),
+ mLineNumber(0)
+ {
+ MOZ_COUNT_CTOR(nsXBLTextWithLineNumber);
+ }
+
+ ~nsXBLTextWithLineNumber() {
+ MOZ_COUNT_DTOR(nsXBLTextWithLineNumber);
+ if (mText) {
+ free(mText);
+ }
+ }
+
+ void AppendText(const nsAString& aText) {
+ if (mText) {
+ char16_t* temp = mText;
+ mText = ToNewUnicode(nsDependentString(temp) + aText);
+ free(temp);
+ } else {
+ mText = ToNewUnicode(aText);
+ }
+ }
+
+ char16_t* GetText() {
+ return mText;
+ }
+
+ void SetLineNumber(uint32_t aLineNumber) {
+ mLineNumber = aLineNumber;
+ }
+
+ uint32_t GetLineNumber() {
+ return mLineNumber;
+ }
+};
+
+class nsXBLProtoImplMember
+{
+public:
+ explicit nsXBLProtoImplMember(const char16_t* aName)
+ : mNext(nullptr)
+ , mExposeToUntrustedContent(false)
+ {
+ mName = ToNewUnicode(nsDependentString(aName));
+ }
+ virtual ~nsXBLProtoImplMember() {
+ free(mName);
+ NS_CONTENT_DELETE_LIST_MEMBER(nsXBLProtoImplMember, this, mNext);
+ }
+
+ nsXBLProtoImplMember* GetNext() { return mNext; }
+ void SetNext(nsXBLProtoImplMember* aNext) { mNext = aNext; }
+ bool ShouldExposeToUntrustedContent() { return mExposeToUntrustedContent; }
+ void SetExposeToUntrustedContent(bool aExpose) { mExposeToUntrustedContent = aExpose; }
+ const char16_t* GetName() { return mName; }
+
+ virtual nsresult InstallMember(JSContext* aCx,
+ JS::Handle<JSObject*> aTargetClassObject) = 0;
+ virtual nsresult CompileMember(mozilla::dom::AutoJSAPI& jsapi, const nsString& aClassStr,
+ JS::Handle<JSObject*> aClassObject) = 0;
+
+ virtual void Trace(const TraceCallbacks& aCallbacks, void *aClosure) = 0;
+
+ virtual nsresult Write(nsIObjectOutputStream* aStream)
+ {
+ return NS_OK;
+ }
+
+protected:
+ nsXBLProtoImplMember* mNext; // The members of an implementation are chained.
+ char16_t* mName; // The name of the field, method, or property.
+
+ bool mExposeToUntrustedContent; // If this binding is installed on an element
+ // in an untrusted scope, should this
+ // implementation member be accessible to the
+ // content?
+};
+
+#endif // nsXBLProtoImplMember_h__
diff --git a/dom/xbl/nsXBLProtoImplMethod.cpp b/dom/xbl/nsXBLProtoImplMethod.cpp
new file mode 100644
index 000000000..31b215ab3
--- /dev/null
+++ b/dom/xbl/nsXBLProtoImplMethod.cpp
@@ -0,0 +1,356 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "jsapi.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIGlobalObject.h"
+#include "nsUnicharUtils.h"
+#include "nsReadableUtils.h"
+#include "nsXBLProtoImplMethod.h"
+#include "nsJSUtils.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIXPConnect.h"
+#include "xpcpublic.h"
+#include "nsXBLPrototypeBinding.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsXBLProtoImplMethod::nsXBLProtoImplMethod(const char16_t* aName) :
+ nsXBLProtoImplMember(aName),
+ mMethod()
+{
+ MOZ_COUNT_CTOR(nsXBLProtoImplMethod);
+}
+
+nsXBLProtoImplMethod::~nsXBLProtoImplMethod()
+{
+ MOZ_COUNT_DTOR(nsXBLProtoImplMethod);
+
+ if (!IsCompiled()) {
+ delete GetUncompiledMethod();
+ }
+}
+
+void
+nsXBLProtoImplMethod::AppendBodyText(const nsAString& aText)
+{
+ NS_PRECONDITION(!IsCompiled(),
+ "Must not be compiled when accessing uncompiled method");
+
+ nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod();
+ if (!uncompiledMethod) {
+ uncompiledMethod = new nsXBLUncompiledMethod();
+ SetUncompiledMethod(uncompiledMethod);
+ }
+
+ uncompiledMethod->AppendBodyText(aText);
+}
+
+void
+nsXBLProtoImplMethod::AddParameter(const nsAString& aText)
+{
+ NS_PRECONDITION(!IsCompiled(),
+ "Must not be compiled when accessing uncompiled method");
+
+ if (aText.IsEmpty()) {
+ NS_WARNING("Empty name attribute in xbl:parameter!");
+ return;
+ }
+
+ nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod();
+ if (!uncompiledMethod) {
+ uncompiledMethod = new nsXBLUncompiledMethod();
+ SetUncompiledMethod(uncompiledMethod);
+ }
+
+ uncompiledMethod->AddParameter(aText);
+}
+
+void
+nsXBLProtoImplMethod::SetLineNumber(uint32_t aLineNumber)
+{
+ NS_PRECONDITION(!IsCompiled(),
+ "Must not be compiled when accessing uncompiled method");
+
+ nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod();
+ if (!uncompiledMethod) {
+ uncompiledMethod = new nsXBLUncompiledMethod();
+ SetUncompiledMethod(uncompiledMethod);
+ }
+
+ uncompiledMethod->SetLineNumber(aLineNumber);
+}
+
+nsresult
+nsXBLProtoImplMethod::InstallMember(JSContext* aCx,
+ JS::Handle<JSObject*> aTargetClassObject)
+{
+ NS_PRECONDITION(IsCompiled(),
+ "Should not be installing an uncompiled method");
+ MOZ_ASSERT(js::IsObjectInContextCompartment(aTargetClassObject, aCx));
+
+#ifdef DEBUG
+ {
+ JS::Rooted<JSObject*> globalObject(aCx, JS_GetGlobalForObject(aCx, aTargetClassObject));
+ MOZ_ASSERT(xpc::IsInContentXBLScope(globalObject) ||
+ xpc::IsInAddonScope(globalObject) ||
+ globalObject == xpc::GetXBLScope(aCx, globalObject));
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx) == globalObject);
+ }
+#endif
+
+ JS::Rooted<JSObject*> jsMethodObject(aCx, GetCompiledMethod());
+ if (jsMethodObject) {
+ nsDependentString name(mName);
+
+ JS::Rooted<JSObject*> method(aCx, JS::CloneFunctionObject(aCx, jsMethodObject));
+ NS_ENSURE_TRUE(method, NS_ERROR_OUT_OF_MEMORY);
+
+ if (!::JS_DefineUCProperty(aCx, aTargetClassObject,
+ static_cast<const char16_t*>(mName),
+ name.Length(), method,
+ JSPROP_ENUMERATE)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImplMethod::CompileMember(AutoJSAPI& jsapi, const nsString& aClassStr,
+ JS::Handle<JSObject*> aClassObject)
+{
+ AssertInCompilationScope();
+ NS_PRECONDITION(!IsCompiled(),
+ "Trying to compile an already-compiled method");
+ NS_PRECONDITION(aClassObject,
+ "Must have class object to compile");
+
+ nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod();
+
+ // No parameters or body was supplied, so don't install method.
+ if (!uncompiledMethod) {
+ // Early return after which we consider ourselves compiled.
+ SetCompiledMethod(nullptr);
+
+ return NS_OK;
+ }
+
+ // Don't install method if no name was supplied.
+ if (!mName) {
+ delete uncompiledMethod;
+
+ // Early return after which we consider ourselves compiled.
+ SetCompiledMethod(nullptr);
+
+ return NS_OK;
+ }
+
+ // We have a method.
+ // Allocate an array for our arguments.
+ int32_t paramCount = uncompiledMethod->GetParameterCount();
+ char** args = nullptr;
+ if (paramCount > 0) {
+ args = new char*[paramCount];
+
+ // Add our parameters to our args array.
+ int32_t argPos = 0;
+ for (nsXBLParameter* curr = uncompiledMethod->mParameters;
+ curr;
+ curr = curr->mNext) {
+ args[argPos] = curr->mName;
+ argPos++;
+ }
+ }
+
+ // Get the body
+ nsDependentString body;
+ char16_t *bodyText = uncompiledMethod->mBodyText.GetText();
+ if (bodyText)
+ body.Rebind(bodyText);
+
+ // Now that we have a body and args, compile the function
+ // and then define it.
+ NS_ConvertUTF16toUTF8 cname(mName);
+ NS_ConvertUTF16toUTF8 functionUri(aClassStr);
+ int32_t hash = functionUri.RFindChar('#');
+ if (hash != kNotFound) {
+ functionUri.Truncate(hash);
+ }
+
+ JSContext *cx = jsapi.cx();
+ JSAutoCompartment ac(cx, aClassObject);
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(functionUri.get(),
+ uncompiledMethod->mBodyText.GetLineNumber())
+ .setVersion(JSVERSION_LATEST);
+ JS::Rooted<JSObject*> methodObject(cx);
+ JS::AutoObjectVector emptyVector(cx);
+ nsresult rv = nsJSUtils::CompileFunction(jsapi, emptyVector, options, cname,
+ paramCount,
+ const_cast<const char**>(args),
+ body, methodObject.address());
+
+ // Destroy our uncompiled method and delete our arg list.
+ delete uncompiledMethod;
+ delete [] args;
+ if (NS_FAILED(rv)) {
+ SetUncompiledMethod(nullptr);
+ return rv;
+ }
+
+ SetCompiledMethod(methodObject);
+
+ return NS_OK;
+}
+
+void
+nsXBLProtoImplMethod::Trace(const TraceCallbacks& aCallbacks, void *aClosure)
+{
+ if (IsCompiled() && GetCompiledMethodPreserveColor()) {
+ aCallbacks.Trace(&mMethod.AsHeapObject(), "mMethod", aClosure);
+ }
+}
+
+nsresult
+nsXBLProtoImplMethod::Read(nsIObjectInputStream* aStream)
+{
+ AssertInCompilationScope();
+ MOZ_ASSERT(!IsCompiled() && !GetUncompiledMethod());
+
+ AutoJSContext cx;
+ JS::Rooted<JSObject*> methodObject(cx);
+ nsresult rv = XBL_DeserializeFunction(aStream, &methodObject);
+ if (NS_FAILED(rv)) {
+ SetUncompiledMethod(nullptr);
+ return rv;
+ }
+
+ SetCompiledMethod(methodObject);
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImplMethod::Write(nsIObjectOutputStream* aStream)
+{
+ AssertInCompilationScope();
+ MOZ_ASSERT(IsCompiled());
+ if (GetCompiledMethodPreserveColor()) {
+ nsresult rv = aStream->Write8(XBLBinding_Serialize_Method);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->WriteWStringZ(mName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JS::Rooted<JSObject*> method(RootingCx(), GetCompiledMethod());
+ return XBL_SerializeFunction(aStream, method);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImplAnonymousMethod::Execute(nsIContent* aBoundElement, JSAddonId* aAddonId)
+{
+ MOZ_ASSERT(aBoundElement->IsElement());
+ NS_PRECONDITION(IsCompiled(), "Can't execute uncompiled method");
+
+ if (!GetCompiledMethod()) {
+ // Nothing to do here
+ return NS_OK;
+ }
+
+ // Get the script context the same way
+ // nsXBLProtoImpl::InstallImplementation does.
+ nsIDocument* document = aBoundElement->OwnerDoc();
+
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(document->GetInnerWindow());
+ if (!global) {
+ return NS_OK;
+ }
+
+ nsAutoMicroTask mt;
+
+ // We are going to run script via JS::Call, so we need a script entry point,
+ // but as this is XBL related it does not appear in the HTML spec.
+ // We need an actual JSContext to do GetScopeForXBLExecution, and it needs to
+ // be in the compartment of globalObject. But we want our XBL execution scope
+ // to be our entry global.
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(global)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ JS::Rooted<JSObject*> globalObject(jsapi.cx(), global->GetGlobalJSObject());
+
+ JS::Rooted<JSObject*> scopeObject(jsapi.cx(),
+ xpc::GetScopeForXBLExecution(jsapi.cx(), globalObject, aAddonId));
+ NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
+
+ dom::AutoEntryScript aes(scopeObject,
+ "XBL <constructor>/<destructor> invocation",
+ true);
+ JSContext* cx = aes.cx();
+ JS::AutoObjectVector scopeChain(cx);
+ if (!nsJSUtils::GetScopeChainForElement(cx, aBoundElement->AsElement(),
+ scopeChain)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ MOZ_ASSERT(scopeChain.length() != 0);
+
+ // Clone the function object, using our scope chain (for backwards
+ // compat to the days when this was an event handler).
+ JS::Rooted<JSObject*> jsMethodObject(cx, GetCompiledMethod());
+ JS::Rooted<JSObject*> method(cx, JS::CloneFunctionObject(cx, jsMethodObject,
+ scopeChain));
+ if (!method)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Now call the method
+
+ // Check whether script is enabled.
+ bool scriptAllowed = xpc::Scriptability::Get(method).Allowed();
+
+ if (scriptAllowed) {
+ JS::Rooted<JS::Value> retval(cx);
+ JS::Rooted<JS::Value> methodVal(cx, JS::ObjectValue(*method));
+ // No need to check the return here as AutoEntryScript has taken ownership
+ // of error reporting.
+ ::JS::Call(cx, scopeChain[0], methodVal, JS::HandleValueArray::empty(), &retval);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImplAnonymousMethod::Write(nsIObjectOutputStream* aStream,
+ XBLBindingSerializeDetails aType)
+{
+ AssertInCompilationScope();
+ MOZ_ASSERT(IsCompiled());
+ if (GetCompiledMethodPreserveColor()) {
+ nsresult rv = aStream->Write8(aType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->WriteWStringZ(mName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JS::Rooted<JSObject*> method(RootingCx(), GetCompiledMethod());
+ rv = XBL_SerializeFunction(aStream, method);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xbl/nsXBLProtoImplMethod.h b/dom/xbl/nsXBLProtoImplMethod.h
new file mode 100644
index 000000000..8a616e11e
--- /dev/null
+++ b/dom/xbl/nsXBLProtoImplMethod.h
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLProtoImplMethod_h__
+#define nsXBLProtoImplMethod_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "nsString.h"
+#include "nsXBLMaybeCompiled.h"
+#include "nsXBLProtoImplMember.h"
+#include "nsXBLSerialize.h"
+
+class nsIContent;
+
+struct nsXBLParameter {
+ nsXBLParameter* mNext;
+ char* mName;
+
+ explicit nsXBLParameter(const nsAString& aName) {
+ MOZ_COUNT_CTOR(nsXBLParameter);
+ mName = ToNewCString(aName);
+ mNext = nullptr;
+ }
+
+ ~nsXBLParameter() {
+ MOZ_COUNT_DTOR(nsXBLParameter);
+ free(mName);
+ NS_CONTENT_DELETE_LIST_MEMBER(nsXBLParameter, this, mNext);
+ }
+};
+
+struct nsXBLUncompiledMethod {
+ nsXBLParameter* mParameters;
+ nsXBLParameter* mLastParameter;
+ nsXBLTextWithLineNumber mBodyText;
+
+ nsXBLUncompiledMethod() :
+ mParameters(nullptr),
+ mLastParameter(nullptr),
+ mBodyText()
+ {
+ MOZ_COUNT_CTOR(nsXBLUncompiledMethod);
+ }
+
+ ~nsXBLUncompiledMethod() {
+ MOZ_COUNT_DTOR(nsXBLUncompiledMethod);
+ delete mParameters;
+ }
+
+ int32_t GetParameterCount() {
+ int32_t result = 0;
+ for (nsXBLParameter* curr = mParameters; curr; curr=curr->mNext)
+ result++;
+ return result;
+ }
+
+ void AppendBodyText(const nsAString& aText) {
+ mBodyText.AppendText(aText);
+ }
+
+ void AddParameter(const nsAString& aText) {
+ nsXBLParameter* param = new nsXBLParameter(aText);
+ if (!mParameters)
+ mParameters = param;
+ else
+ mLastParameter->mNext = param;
+ mLastParameter = param;
+ }
+
+ void SetLineNumber(uint32_t aLineNumber) {
+ mBodyText.SetLineNumber(aLineNumber);
+ }
+};
+
+class nsXBLProtoImplMethod: public nsXBLProtoImplMember
+{
+public:
+ explicit nsXBLProtoImplMethod(const char16_t* aName);
+ virtual ~nsXBLProtoImplMethod();
+
+ void AppendBodyText(const nsAString& aBody);
+ void AddParameter(const nsAString& aName);
+
+ void SetLineNumber(uint32_t aLineNumber);
+
+ virtual nsresult InstallMember(JSContext* aCx,
+ JS::Handle<JSObject*> aTargetClassObject) override;
+ virtual nsresult CompileMember(mozilla::dom::AutoJSAPI& jsapi, const nsString& aClassStr,
+ JS::Handle<JSObject*> aClassObject) override;
+
+ virtual void Trace(const TraceCallbacks& aCallbacks, void *aClosure) override;
+
+ nsresult Read(nsIObjectInputStream* aStream);
+ virtual nsresult Write(nsIObjectOutputStream* aStream) override;
+
+ bool IsCompiled() const
+ {
+ return mMethod.IsCompiled();
+ }
+
+ void SetUncompiledMethod(nsXBLUncompiledMethod* aUncompiledMethod)
+ {
+ mMethod.SetUncompiled(aUncompiledMethod);
+ }
+
+ nsXBLUncompiledMethod* GetUncompiledMethod() const
+ {
+ return mMethod.GetUncompiled();
+ }
+
+protected:
+ void SetCompiledMethod(JSObject* aCompiledMethod)
+ {
+ mMethod.SetJSFunction(aCompiledMethod);
+ }
+
+ JSObject* GetCompiledMethod() const
+ {
+ return mMethod.GetJSFunction();
+ }
+
+ JSObject* GetCompiledMethodPreserveColor() const
+ {
+ return mMethod.GetJSFunctionPreserveColor();
+ }
+
+ JS::Heap<nsXBLMaybeCompiled<nsXBLUncompiledMethod> > mMethod;
+};
+
+class nsXBLProtoImplAnonymousMethod : public nsXBLProtoImplMethod {
+public:
+ explicit nsXBLProtoImplAnonymousMethod(const char16_t* aName) :
+ nsXBLProtoImplMethod(aName)
+ {}
+
+ nsresult Execute(nsIContent* aBoundElement, JSAddonId* aAddonId);
+
+ // Override InstallMember; these methods never get installed as members on
+ // binding instantiations (though they may hang out in mMembers on the
+ // prototype implementation).
+ virtual nsresult InstallMember(JSContext* aCx,
+ JS::Handle<JSObject*> aTargetClassObject) override {
+ return NS_OK;
+ }
+
+ using nsXBLProtoImplMethod::Write;
+ nsresult Write(nsIObjectOutputStream* aStream,
+ XBLBindingSerializeDetails aType);
+};
+
+#endif // nsXBLProtoImplMethod_h__
diff --git a/dom/xbl/nsXBLProtoImplProperty.cpp b/dom/xbl/nsXBLProtoImplProperty.cpp
new file mode 100644
index 000000000..557a836da
--- /dev/null
+++ b/dom/xbl/nsXBLProtoImplProperty.cpp
@@ -0,0 +1,370 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "jsapi.h"
+#include "nsIContent.h"
+#include "nsXBLProtoImplProperty.h"
+#include "nsUnicharUtils.h"
+#include "nsReadableUtils.h"
+#include "nsJSUtils.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsXBLSerialize.h"
+#include "xpcpublic.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsXBLProtoImplProperty::nsXBLProtoImplProperty(const char16_t* aName,
+ const char16_t* aGetter,
+ const char16_t* aSetter,
+ const char16_t* aReadOnly,
+ uint32_t aLineNumber) :
+ nsXBLProtoImplMember(aName),
+ mJSAttributes(JSPROP_ENUMERATE)
+#ifdef DEBUG
+ , mIsCompiled(false)
+#endif
+{
+ MOZ_COUNT_CTOR(nsXBLProtoImplProperty);
+
+ if (aReadOnly) {
+ nsAutoString readOnly; readOnly.Assign(*aReadOnly);
+ if (readOnly.LowerCaseEqualsLiteral("true"))
+ mJSAttributes |= JSPROP_READONLY;
+ }
+
+ if (aGetter) {
+ AppendGetterText(nsDependentString(aGetter));
+ SetGetterLineNumber(aLineNumber);
+ }
+ if (aSetter) {
+ AppendSetterText(nsDependentString(aSetter));
+ SetSetterLineNumber(aLineNumber);
+ }
+}
+
+nsXBLProtoImplProperty::nsXBLProtoImplProperty(const char16_t* aName,
+ const bool aIsReadOnly)
+ : nsXBLProtoImplMember(aName),
+ mJSAttributes(JSPROP_ENUMERATE)
+#ifdef DEBUG
+ , mIsCompiled(false)
+#endif
+{
+ MOZ_COUNT_CTOR(nsXBLProtoImplProperty);
+
+ if (aIsReadOnly)
+ mJSAttributes |= JSPROP_READONLY;
+}
+
+nsXBLProtoImplProperty::~nsXBLProtoImplProperty()
+{
+ MOZ_COUNT_DTOR(nsXBLProtoImplProperty);
+
+ if (!mGetter.IsCompiled()) {
+ delete mGetter.GetUncompiled();
+ }
+
+ if (!mSetter.IsCompiled()) {
+ delete mSetter.GetUncompiled();
+ }
+}
+
+void nsXBLProtoImplProperty::EnsureUncompiledText(PropertyOp& aPropertyOp)
+{
+ if (!aPropertyOp.GetUncompiled()) {
+ nsXBLTextWithLineNumber* text = new nsXBLTextWithLineNumber();
+ aPropertyOp.SetUncompiled(text);
+ }
+}
+
+void
+nsXBLProtoImplProperty::AppendGetterText(const nsAString& aText)
+{
+ NS_PRECONDITION(!mIsCompiled,
+ "Must not be compiled when accessing getter text");
+ EnsureUncompiledText(mGetter);
+ mGetter.GetUncompiled()->AppendText(aText);
+}
+
+void
+nsXBLProtoImplProperty::AppendSetterText(const nsAString& aText)
+{
+ NS_PRECONDITION(!mIsCompiled,
+ "Must not be compiled when accessing setter text");
+ EnsureUncompiledText(mSetter);
+ mSetter.GetUncompiled()->AppendText(aText);
+}
+
+void
+nsXBLProtoImplProperty::SetGetterLineNumber(uint32_t aLineNumber)
+{
+ NS_PRECONDITION(!mIsCompiled,
+ "Must not be compiled when accessing getter text");
+ EnsureUncompiledText(mGetter);
+ mGetter.GetUncompiled()->SetLineNumber(aLineNumber);
+}
+
+void
+nsXBLProtoImplProperty::SetSetterLineNumber(uint32_t aLineNumber)
+{
+ NS_PRECONDITION(!mIsCompiled,
+ "Must not be compiled when accessing setter text");
+ EnsureUncompiledText(mSetter);
+ mSetter.GetUncompiled()->SetLineNumber(aLineNumber);
+}
+
+const char* gPropertyArgs[] = { "val" };
+
+nsresult
+nsXBLProtoImplProperty::InstallMember(JSContext *aCx,
+ JS::Handle<JSObject*> aTargetClassObject)
+{
+ NS_PRECONDITION(mIsCompiled,
+ "Should not be installing an uncompiled property");
+ MOZ_ASSERT(mGetter.IsCompiled() && mSetter.IsCompiled());
+ MOZ_ASSERT(js::IsObjectInContextCompartment(aTargetClassObject, aCx));
+
+#ifdef DEBUG
+ {
+ JS::Rooted<JSObject*> globalObject(aCx, JS_GetGlobalForObject(aCx, aTargetClassObject));
+ MOZ_ASSERT(xpc::IsInContentXBLScope(globalObject) ||
+ xpc::IsInAddonScope(globalObject) ||
+ globalObject == xpc::GetXBLScope(aCx, globalObject));
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx) == globalObject);
+ }
+#endif
+
+ JS::Rooted<JSObject*> getter(aCx, mGetter.GetJSFunction());
+ JS::Rooted<JSObject*> setter(aCx, mSetter.GetJSFunction());
+ if (getter || setter) {
+ if (getter) {
+ if (!(getter = JS::CloneFunctionObject(aCx, getter)))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (setter) {
+ if (!(setter = JS::CloneFunctionObject(aCx, setter)))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsDependentString name(mName);
+ if (!::JS_DefineUCProperty(aCx, aTargetClassObject,
+ static_cast<const char16_t*>(mName),
+ name.Length(), JS::UndefinedHandleValue, mJSAttributes,
+ JS_DATA_TO_FUNC_PTR(JSNative, getter.get()),
+ JS_DATA_TO_FUNC_PTR(JSNative, setter.get())))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImplProperty::CompileMember(AutoJSAPI& jsapi, const nsString& aClassStr,
+ JS::Handle<JSObject*> aClassObject)
+{
+ AssertInCompilationScope();
+ NS_PRECONDITION(!mIsCompiled,
+ "Trying to compile an already-compiled property");
+ NS_PRECONDITION(aClassObject,
+ "Must have class object to compile");
+ MOZ_ASSERT(!mGetter.IsCompiled() && !mSetter.IsCompiled());
+ JSContext *cx = jsapi.cx();
+
+ if (!mName)
+ return NS_ERROR_FAILURE; // Without a valid name, we can't install the member.
+
+ // We have a property.
+ nsresult rv = NS_OK;
+
+ nsAutoCString functionUri;
+ if (mGetter.GetUncompiled() || mSetter.GetUncompiled()) {
+ functionUri = NS_ConvertUTF16toUTF8(aClassStr);
+ int32_t hash = functionUri.RFindChar('#');
+ if (hash != kNotFound) {
+ functionUri.Truncate(hash);
+ }
+ }
+
+ bool deletedGetter = false;
+ nsXBLTextWithLineNumber *getterText = mGetter.GetUncompiled();
+ if (getterText && getterText->GetText()) {
+ nsDependentString getter(getterText->GetText());
+ if (!getter.IsEmpty()) {
+ JSAutoCompartment ac(cx, aClassObject);
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(functionUri.get(), getterText->GetLineNumber())
+ .setVersion(JSVERSION_LATEST);
+ nsCString name = NS_LITERAL_CSTRING("get_") + NS_ConvertUTF16toUTF8(mName);
+ JS::Rooted<JSObject*> getterObject(cx);
+ JS::AutoObjectVector emptyVector(cx);
+ rv = nsJSUtils::CompileFunction(jsapi, emptyVector, options, name, 0,
+ nullptr, getter, getterObject.address());
+
+ delete getterText;
+ deletedGetter = true;
+
+ mGetter.SetJSFunction(getterObject);
+
+ if (mGetter.GetJSFunction() && NS_SUCCEEDED(rv)) {
+ mJSAttributes |= JSPROP_GETTER | JSPROP_SHARED;
+ }
+ if (NS_FAILED(rv)) {
+ mGetter.SetJSFunction(nullptr);
+ mJSAttributes &= ~JSPROP_GETTER;
+ /*chaining to return failure*/
+ }
+ }
+ } // if getter is not empty
+
+ if (!deletedGetter) { // Empty getter
+ delete getterText;
+ mGetter.SetJSFunction(nullptr);
+ }
+
+ if (NS_FAILED(rv)) {
+ // We failed to compile our getter. So either we've set it to null, or
+ // it's still set to the text object. In either case, it's safe to return
+ // the error here, since then we'll be cleaned up as uncompiled and that
+ // will be ok. Going on and compiling the setter and _then_ returning an
+ // error, on the other hand, will try to clean up a compiled setter as
+ // uncompiled and crash.
+ return rv;
+ }
+
+ bool deletedSetter = false;
+ nsXBLTextWithLineNumber *setterText = mSetter.GetUncompiled();
+ if (setterText && setterText->GetText()) {
+ nsDependentString setter(setterText->GetText());
+ if (!setter.IsEmpty()) {
+ JSAutoCompartment ac(cx, aClassObject);
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(functionUri.get(), setterText->GetLineNumber())
+ .setVersion(JSVERSION_LATEST);
+ nsCString name = NS_LITERAL_CSTRING("set_") + NS_ConvertUTF16toUTF8(mName);
+ JS::Rooted<JSObject*> setterObject(cx);
+ JS::AutoObjectVector emptyVector(cx);
+ rv = nsJSUtils::CompileFunction(jsapi, emptyVector, options, name, 1,
+ gPropertyArgs, setter,
+ setterObject.address());
+
+ delete setterText;
+ deletedSetter = true;
+ mSetter.SetJSFunction(setterObject);
+
+ if (mSetter.GetJSFunction() && NS_SUCCEEDED(rv)) {
+ mJSAttributes |= JSPROP_SETTER | JSPROP_SHARED;
+ }
+ if (NS_FAILED(rv)) {
+ mSetter.SetJSFunction(nullptr);
+ mJSAttributes &= ~JSPROP_SETTER;
+ /*chaining to return failure*/
+ }
+ }
+ } // if setter wasn't empty....
+
+ if (!deletedSetter) { // Empty setter
+ delete setterText;
+ mSetter.SetJSFunction(nullptr);
+ }
+
+#ifdef DEBUG
+ mIsCompiled = NS_SUCCEEDED(rv);
+#endif
+
+ return rv;
+}
+
+void
+nsXBLProtoImplProperty::Trace(const TraceCallbacks& aCallbacks, void *aClosure)
+{
+ if (mJSAttributes & JSPROP_GETTER) {
+ aCallbacks.Trace(&mGetter.AsHeapObject(), "mGetter", aClosure);
+ }
+
+ if (mJSAttributes & JSPROP_SETTER) {
+ aCallbacks.Trace(&mSetter.AsHeapObject(), "mSetter", aClosure);
+ }
+}
+
+nsresult
+nsXBLProtoImplProperty::Read(nsIObjectInputStream* aStream,
+ XBLBindingSerializeDetails aType)
+{
+ AssertInCompilationScope();
+ MOZ_ASSERT(!mIsCompiled);
+ MOZ_ASSERT(!mGetter.GetUncompiled() && !mSetter.GetUncompiled());
+
+ AutoJSContext cx;
+ JS::Rooted<JSObject*> getterObject(cx);
+ if (aType == XBLBinding_Serialize_GetterProperty ||
+ aType == XBLBinding_Serialize_GetterSetterProperty) {
+ nsresult rv = XBL_DeserializeFunction(aStream, &getterObject);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mJSAttributes |= JSPROP_GETTER | JSPROP_SHARED;
+ }
+ mGetter.SetJSFunction(getterObject);
+
+ JS::Rooted<JSObject*> setterObject(cx);
+ if (aType == XBLBinding_Serialize_SetterProperty ||
+ aType == XBLBinding_Serialize_GetterSetterProperty) {
+ nsresult rv = XBL_DeserializeFunction(aStream, &setterObject);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mJSAttributes |= JSPROP_SETTER | JSPROP_SHARED;
+ }
+ mSetter.SetJSFunction(setterObject);
+
+#ifdef DEBUG
+ mIsCompiled = true;
+#endif
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImplProperty::Write(nsIObjectOutputStream* aStream)
+{
+ AssertInCompilationScope();
+ XBLBindingSerializeDetails type;
+
+ if (mJSAttributes & JSPROP_GETTER) {
+ type = mJSAttributes & JSPROP_SETTER ?
+ XBLBinding_Serialize_GetterSetterProperty :
+ XBLBinding_Serialize_GetterProperty;
+ }
+ else {
+ type = XBLBinding_Serialize_SetterProperty;
+ }
+
+ if (mJSAttributes & JSPROP_READONLY) {
+ type |= XBLBinding_Serialize_ReadOnly;
+ }
+
+ nsresult rv = aStream->Write8(type);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStream->WriteWStringZ(mName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT_IF(mJSAttributes & (JSPROP_GETTER | JSPROP_SETTER), mIsCompiled);
+
+ if (mJSAttributes & JSPROP_GETTER) {
+ JS::Rooted<JSObject*> function(RootingCx(), mGetter.GetJSFunction());
+ rv = XBL_SerializeFunction(aStream, function);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mJSAttributes & JSPROP_SETTER) {
+ JS::Rooted<JSObject*> function(RootingCx(), mSetter.GetJSFunction());
+ rv = XBL_SerializeFunction(aStream, function);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xbl/nsXBLProtoImplProperty.h b/dom/xbl/nsXBLProtoImplProperty.h
new file mode 100644
index 000000000..cf203294f
--- /dev/null
+++ b/dom/xbl/nsXBLProtoImplProperty.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLProtoImplProperty_h__
+#define nsXBLProtoImplProperty_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "nsString.h"
+#include "nsXBLSerialize.h"
+#include "nsXBLMaybeCompiled.h"
+#include "nsXBLProtoImplMember.h"
+
+class nsXBLProtoImplProperty: public nsXBLProtoImplMember
+{
+public:
+ nsXBLProtoImplProperty(const char16_t* aName,
+ const char16_t* aGetter,
+ const char16_t* aSetter,
+ const char16_t* aReadOnly,
+ uint32_t aLineNumber);
+
+ nsXBLProtoImplProperty(const char16_t* aName, const bool aIsReadOnly);
+
+ virtual ~nsXBLProtoImplProperty();
+
+ void AppendGetterText(const nsAString& aGetter);
+ void AppendSetterText(const nsAString& aSetter);
+
+ void SetGetterLineNumber(uint32_t aLineNumber);
+ void SetSetterLineNumber(uint32_t aLineNumber);
+
+ virtual nsresult InstallMember(JSContext* aCx,
+ JS::Handle<JSObject*> aTargetClassObject) override;
+ virtual nsresult CompileMember(mozilla::dom::AutoJSAPI& jsapi, const nsString& aClassStr,
+ JS::Handle<JSObject*> aClassObject) override;
+
+ virtual void Trace(const TraceCallbacks& aCallback, void *aClosure) override;
+
+ nsresult Read(nsIObjectInputStream* aStream,
+ XBLBindingSerializeDetails aType);
+ virtual nsresult Write(nsIObjectOutputStream* aStream) override;
+
+protected:
+ typedef JS::Heap<nsXBLMaybeCompiled<nsXBLTextWithLineNumber> > PropertyOp;
+
+ void EnsureUncompiledText(PropertyOp& aPropertyOp);
+
+ // The raw text for the getter, or the JS object (after compilation).
+ PropertyOp mGetter;
+
+ // The raw text for the setter, or the JS object (after compilation).
+ PropertyOp mSetter;
+
+ unsigned mJSAttributes; // A flag for all our JS properties (getter/setter/readonly/shared/enum)
+
+#ifdef DEBUG
+ bool mIsCompiled;
+#endif
+};
+
+#endif // nsXBLProtoImplProperty_h__
diff --git a/dom/xbl/nsXBLPrototypeBinding.cpp b/dom/xbl/nsXBLPrototypeBinding.cpp
new file mode 100644
index 000000000..c470d887b
--- /dev/null
+++ b/dom/xbl/nsXBLPrototypeBinding.cpp
@@ -0,0 +1,1696 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsIInputStream.h"
+#include "nsNameSpaceManager.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsNetUtil.h"
+#include "plstr.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsIDocument.h"
+#include "nsIXMLContentSink.h"
+#include "nsContentCID.h"
+#include "mozilla/dom/XMLDocument.h"
+#include "nsXBLService.h"
+#include "nsXBLBinding.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsXBLContentSink.h"
+#include "xptinfo.h"
+#include "nsIInterfaceInfoManager.h"
+#include "nsIDocumentObserver.h"
+#include "nsGkAtoms.h"
+#include "nsXBLProtoImpl.h"
+#include "nsCRT.h"
+#include "nsContentUtils.h"
+#include "nsTextFragment.h"
+#include "nsTextNode.h"
+#include "nsIInterfaceInfo.h"
+#include "nsIScriptError.h"
+
+#include "nsCSSRuleProcessor.h"
+#include "nsXBLResourceLoader.h"
+#include "mozilla/AddonPathService.h"
+#include "mozilla/dom/CDATASection.h"
+#include "mozilla/dom/Comment.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+
+#ifdef MOZ_XUL
+#include "nsXULElement.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// Helper Classes =====================================================================
+
+// nsXBLAttributeEntry and helpers. This class is used to efficiently handle
+// attribute changes in anonymous content.
+
+class nsXBLAttributeEntry {
+public:
+ nsXBLAttributeEntry(nsIAtom* aSrcAtom, nsIAtom* aDstAtom,
+ int32_t aDstNameSpace, nsIContent* aContent)
+ : mElement(aContent),
+ mSrcAttribute(aSrcAtom),
+ mDstAttribute(aDstAtom),
+ mDstNameSpace(aDstNameSpace),
+ mNext(nullptr) { }
+
+ ~nsXBLAttributeEntry() {
+ NS_CONTENT_DELETE_LIST_MEMBER(nsXBLAttributeEntry, this, mNext);
+ }
+
+ nsIAtom* GetSrcAttribute() { return mSrcAttribute; }
+ nsIAtom* GetDstAttribute() { return mDstAttribute; }
+ int32_t GetDstNameSpace() { return mDstNameSpace; }
+
+ nsIContent* GetElement() { return mElement; }
+
+ nsXBLAttributeEntry* GetNext() { return mNext; }
+ void SetNext(nsXBLAttributeEntry* aEntry) { mNext = aEntry; }
+
+protected:
+ nsIContent* mElement;
+
+ nsCOMPtr<nsIAtom> mSrcAttribute;
+ nsCOMPtr<nsIAtom> mDstAttribute;
+ int32_t mDstNameSpace;
+ nsXBLAttributeEntry* mNext;
+};
+
+// =============================================================================
+
+// Implementation /////////////////////////////////////////////////////////////////
+
+// Constructors/Destructors
+nsXBLPrototypeBinding::nsXBLPrototypeBinding()
+: mImplementation(nullptr),
+ mBaseBinding(nullptr),
+ mInheritStyle(true),
+ mCheckedBaseProto(false),
+ mKeyHandlersRegistered(false),
+ mChromeOnlyContent(false),
+ mBindToUntrustedContent(false),
+ mResources(nullptr),
+ mBaseNameSpaceID(kNameSpaceID_None)
+{
+ MOZ_COUNT_CTOR(nsXBLPrototypeBinding);
+}
+
+nsresult
+nsXBLPrototypeBinding::Init(const nsACString& aID,
+ nsXBLDocumentInfo* aInfo,
+ nsIContent* aElement,
+ bool aFirstBinding)
+{
+ nsresult rv = aInfo->DocumentURI()->Clone(getter_AddRefs(mBindingURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The binding URI might be an immutable URI (e.g. for about: URIs). In that case,
+ // we'll fail in SetRef below, but that doesn't matter much for now.
+ if (aFirstBinding) {
+ rv = mBindingURI->Clone(getter_AddRefs(mAlternateBindingURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ mBindingURI->SetRef(aID);
+
+ mXBLDocInfoWeak = aInfo;
+
+ // aElement will be null when reading from the cache, but the element will
+ // still be set later.
+ if (aElement) {
+ SetBindingElement(aElement);
+ }
+ return NS_OK;
+}
+
+bool nsXBLPrototypeBinding::CompareBindingURI(nsIURI* aURI) const
+{
+ bool equal = false;
+ mBindingURI->Equals(aURI, &equal);
+ if (!equal && mAlternateBindingURI) {
+ mAlternateBindingURI->Equals(aURI, &equal);
+ }
+ return equal;
+}
+
+void
+nsXBLPrototypeBinding::Traverse(nsCycleCollectionTraversalCallback &cb) const
+{
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "proto mBinding");
+ cb.NoteXPCOMChild(mBinding);
+ if (mResources) {
+ mResources->Traverse(cb);
+ }
+ ImplCycleCollectionTraverse(cb, mInterfaceTable, "proto mInterfaceTable");
+}
+
+void
+nsXBLPrototypeBinding::Unlink()
+{
+ if (mImplementation) {
+ mImplementation->UnlinkJSObjects();
+ }
+
+ if (mResources) {
+ mResources->Unlink();
+ }
+}
+
+void
+nsXBLPrototypeBinding::Trace(const TraceCallbacks& aCallbacks, void *aClosure) const
+{
+ if (mImplementation)
+ mImplementation->Trace(aCallbacks, aClosure);
+}
+
+void
+nsXBLPrototypeBinding::Initialize()
+{
+ nsIContent* content = GetImmediateChild(nsGkAtoms::content);
+ if (content) {
+ ConstructAttributeTable(content);
+ }
+}
+
+nsXBLPrototypeBinding::~nsXBLPrototypeBinding(void)
+{
+ delete mImplementation;
+ MOZ_COUNT_DTOR(nsXBLPrototypeBinding);
+}
+
+void
+nsXBLPrototypeBinding::SetBasePrototype(nsXBLPrototypeBinding* aBinding)
+{
+ if (mBaseBinding == aBinding)
+ return;
+
+ if (mBaseBinding) {
+ NS_ERROR("Base XBL prototype binding is already defined!");
+ return;
+ }
+
+ mBaseBinding = aBinding;
+}
+
+void
+nsXBLPrototypeBinding::SetBindingElement(nsIContent* aElement)
+{
+ mBinding = aElement;
+ if (mBinding->AttrValueIs(kNameSpaceID_None, nsGkAtoms::inheritstyle,
+ nsGkAtoms::_false, eCaseMatters))
+ mInheritStyle = false;
+
+ mChromeOnlyContent = mBinding->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::chromeOnlyContent,
+ nsGkAtoms::_true, eCaseMatters);
+
+ mBindToUntrustedContent = mBinding->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::bindToUntrustedContent,
+ nsGkAtoms::_true, eCaseMatters);
+}
+
+bool
+nsXBLPrototypeBinding::GetAllowScripts() const
+{
+ return mXBLDocInfoWeak->GetScriptAccess();
+}
+
+bool
+nsXBLPrototypeBinding::LoadResources()
+{
+ if (mResources) {
+ bool result;
+ mResources->LoadResources(&result);
+ return result;
+ }
+
+ return true;
+}
+
+nsresult
+nsXBLPrototypeBinding::AddResource(nsIAtom* aResourceType, const nsAString& aSrc)
+{
+ EnsureResources();
+
+ mResources->AddResource(aResourceType, aSrc);
+ return NS_OK;
+}
+
+nsresult
+nsXBLPrototypeBinding::FlushSkinSheets()
+{
+ if (mResources)
+ return mResources->FlushSkinSheets();
+ return NS_OK;
+}
+
+nsresult
+nsXBLPrototypeBinding::BindingAttached(nsIContent* aBoundElement)
+{
+ if (mImplementation && mImplementation->CompiledMembers() &&
+ mImplementation->mConstructor)
+ return mImplementation->mConstructor->Execute(aBoundElement, MapURIToAddonID(mBindingURI));
+ return NS_OK;
+}
+
+nsresult
+nsXBLPrototypeBinding::BindingDetached(nsIContent* aBoundElement)
+{
+ if (mImplementation && mImplementation->CompiledMembers() &&
+ mImplementation->mDestructor)
+ return mImplementation->mDestructor->Execute(aBoundElement, MapURIToAddonID(mBindingURI));
+ return NS_OK;
+}
+
+nsXBLProtoImplAnonymousMethod*
+nsXBLPrototypeBinding::GetConstructor()
+{
+ if (mImplementation)
+ return mImplementation->mConstructor;
+
+ return nullptr;
+}
+
+nsXBLProtoImplAnonymousMethod*
+nsXBLPrototypeBinding::GetDestructor()
+{
+ if (mImplementation)
+ return mImplementation->mDestructor;
+
+ return nullptr;
+}
+
+nsresult
+nsXBLPrototypeBinding::SetConstructor(nsXBLProtoImplAnonymousMethod* aMethod)
+{
+ if (!mImplementation)
+ return NS_ERROR_FAILURE;
+ mImplementation->mConstructor = aMethod;
+ return NS_OK;
+}
+
+nsresult
+nsXBLPrototypeBinding::SetDestructor(nsXBLProtoImplAnonymousMethod* aMethod)
+{
+ if (!mImplementation)
+ return NS_ERROR_FAILURE;
+ mImplementation->mDestructor = aMethod;
+ return NS_OK;
+}
+
+nsresult
+nsXBLPrototypeBinding::InstallImplementation(nsXBLBinding* aBinding)
+{
+ if (mImplementation)
+ return mImplementation->InstallImplementation(this, aBinding);
+ return NS_OK;
+}
+
+// XXXbz this duplicates lots of SetAttrs
+void
+nsXBLPrototypeBinding::AttributeChanged(nsIAtom* aAttribute,
+ int32_t aNameSpaceID,
+ bool aRemoveFlag,
+ nsIContent* aChangedElement,
+ nsIContent* aAnonymousContent,
+ bool aNotify)
+{
+ if (!mAttributeTable)
+ return;
+
+ InnerAttributeTable *attributesNS = mAttributeTable->Get(aNameSpaceID);
+ if (!attributesNS)
+ return;
+
+ nsXBLAttributeEntry* xblAttr = attributesNS->Get(aAttribute);
+ if (!xblAttr)
+ return;
+
+ // Iterate over the elements in the array.
+ nsCOMPtr<nsIContent> content = GetImmediateChild(nsGkAtoms::content);
+ while (xblAttr) {
+ nsIContent* element = xblAttr->GetElement();
+
+ nsCOMPtr<nsIContent> realElement = LocateInstance(aChangedElement, content,
+ aAnonymousContent,
+ element);
+
+ if (realElement) {
+ // Hold a strong reference here so that the atom doesn't go away during
+ // UnsetAttr.
+ nsCOMPtr<nsIAtom> dstAttr = xblAttr->GetDstAttribute();
+ int32_t dstNs = xblAttr->GetDstNameSpace();
+
+ if (aRemoveFlag)
+ realElement->UnsetAttr(dstNs, dstAttr, aNotify);
+ else {
+ bool attrPresent = true;
+ nsAutoString value;
+ // Check to see if the src attribute is xbl:text. If so, then we need to obtain the
+ // children of the real element and get the text nodes' values.
+ if (aAttribute == nsGkAtoms::text && aNameSpaceID == kNameSpaceID_XBL) {
+ nsContentUtils::GetNodeTextContent(aChangedElement, false, value);
+ value.StripChar(char16_t('\n'));
+ value.StripChar(char16_t('\r'));
+ nsAutoString stripVal(value);
+ stripVal.StripWhitespace();
+ if (stripVal.IsEmpty())
+ attrPresent = false;
+ }
+ else {
+ attrPresent = aChangedElement->GetAttr(aNameSpaceID, aAttribute, value);
+ }
+
+ if (attrPresent)
+ realElement->SetAttr(dstNs, dstAttr, value, aNotify);
+ }
+
+ // See if we're the <html> tag in XUL, and see if value is being
+ // set or unset on us. We may also be a tag that is having
+ // xbl:text set on us.
+
+ if ((dstAttr == nsGkAtoms::text && dstNs == kNameSpaceID_XBL) ||
+ (realElement->NodeInfo()->Equals(nsGkAtoms::html,
+ kNameSpaceID_XUL) &&
+ dstAttr == nsGkAtoms::value)) {
+ // Flush out all our kids.
+ uint32_t childCount = realElement->GetChildCount();
+ for (uint32_t i = 0; i < childCount; i++)
+ realElement->RemoveChildAt(0, aNotify);
+
+ if (!aRemoveFlag) {
+ // Construct a new text node and insert it.
+ nsAutoString value;
+ aChangedElement->GetAttr(aNameSpaceID, aAttribute, value);
+ if (!value.IsEmpty()) {
+ RefPtr<nsTextNode> textContent =
+ new nsTextNode(realElement->NodeInfo()->NodeInfoManager());
+
+ textContent->SetText(value, true);
+ realElement->AppendChildTo(textContent, true);
+ }
+ }
+ }
+ }
+
+ xblAttr = xblAttr->GetNext();
+ }
+}
+
+void
+nsXBLPrototypeBinding::SetBaseTag(int32_t aNamespaceID, nsIAtom* aTag)
+{
+ mBaseNameSpaceID = aNamespaceID;
+ mBaseTag = aTag;
+}
+
+nsIAtom*
+nsXBLPrototypeBinding::GetBaseTag(int32_t* aNamespaceID)
+{
+ if (mBaseTag) {
+ *aNamespaceID = mBaseNameSpaceID;
+ return mBaseTag;
+ }
+
+ return nullptr;
+}
+
+bool
+nsXBLPrototypeBinding::ImplementsInterface(REFNSIID aIID) const
+{
+ // Check our IID table.
+ return !!mInterfaceTable.GetWeak(aIID);
+}
+
+// Internal helpers ///////////////////////////////////////////////////////////////////////
+
+nsIContent*
+nsXBLPrototypeBinding::GetImmediateChild(nsIAtom* aTag)
+{
+ for (nsIContent* child = mBinding->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->NodeInfo()->Equals(aTag, kNameSpaceID_XBL)) {
+ return child;
+ }
+ }
+
+ return nullptr;
+}
+
+nsresult
+nsXBLPrototypeBinding::InitClass(const nsString& aClassName,
+ JSContext * aContext,
+ JS::Handle<JSObject*> aScriptObject,
+ JS::MutableHandle<JSObject*> aClassObject,
+ bool* aNew)
+{
+ return nsXBLBinding::DoInitJSClass(aContext, aScriptObject,
+ aClassName, this, aClassObject, aNew);
+}
+
+nsIContent*
+nsXBLPrototypeBinding::LocateInstance(nsIContent* aBoundElement,
+ nsIContent* aTemplRoot,
+ nsIContent* aCopyRoot,
+ nsIContent* aTemplChild)
+{
+ // XXX We will get in trouble if the binding instantiation deviates from the template
+ // in the prototype.
+ if (aTemplChild == aTemplRoot || !aTemplChild)
+ return nullptr;
+
+ nsIContent* templParent = aTemplChild->GetParent();
+
+ // We may be disconnected from our parent during cycle collection.
+ if (!templParent)
+ return nullptr;
+
+ nsIContent *copyParent =
+ templParent == aTemplRoot ? aCopyRoot :
+ LocateInstance(aBoundElement, aTemplRoot, aCopyRoot, templParent);
+
+ if (!copyParent)
+ return nullptr;
+
+ return copyParent->GetChildAt(templParent->IndexOf(aTemplChild));
+}
+
+void
+nsXBLPrototypeBinding::SetInitialAttributes(nsIContent* aBoundElement, nsIContent* aAnonymousContent)
+{
+ if (!mAttributeTable) {
+ return;
+ }
+
+ for (auto iter1 = mAttributeTable->Iter(); !iter1.Done(); iter1.Next()) {
+ InnerAttributeTable* xblAttributes = iter1.UserData();
+ if (xblAttributes) {
+ int32_t srcNamespace = iter1.Key();
+
+ for (auto iter2 = xblAttributes->Iter(); !iter2.Done(); iter2.Next()) {
+ // XXXbz this duplicates lots of AttributeChanged
+ nsXBLAttributeEntry* entry = iter2.UserData();
+ nsIAtom* src = entry->GetSrcAttribute();
+ nsAutoString value;
+ bool attrPresent = true;
+
+ if (src == nsGkAtoms::text && srcNamespace == kNameSpaceID_XBL) {
+ nsContentUtils::GetNodeTextContent(aBoundElement, false, value);
+ value.StripChar(char16_t('\n'));
+ value.StripChar(char16_t('\r'));
+ nsAutoString stripVal(value);
+ stripVal.StripWhitespace();
+
+ if (stripVal.IsEmpty()) {
+ attrPresent = false;
+ }
+ } else {
+ attrPresent = aBoundElement->GetAttr(srcNamespace, src, value);
+ }
+
+ if (attrPresent) {
+ nsIContent* content = GetImmediateChild(nsGkAtoms::content);
+
+ nsXBLAttributeEntry* curr = entry;
+ while (curr) {
+ nsIAtom* dst = curr->GetDstAttribute();
+ int32_t dstNs = curr->GetDstNameSpace();
+ nsIContent* element = curr->GetElement();
+
+ nsIContent* realElement =
+ LocateInstance(aBoundElement, content,
+ aAnonymousContent, element);
+
+ if (realElement) {
+ realElement->SetAttr(dstNs, dst, value, false);
+
+ // XXXndeakin shouldn't this be done in lieu of SetAttr?
+ if ((dst == nsGkAtoms::text && dstNs == kNameSpaceID_XBL) ||
+ (realElement->NodeInfo()->Equals(nsGkAtoms::html,
+ kNameSpaceID_XUL) &&
+ dst == nsGkAtoms::value && !value.IsEmpty())) {
+
+ RefPtr<nsTextNode> textContent =
+ new nsTextNode(realElement->NodeInfo()->NodeInfoManager());
+
+ textContent->SetText(value, false);
+ realElement->AppendChildTo(textContent, false);
+ }
+ }
+
+ curr = curr->GetNext();
+ }
+ }
+ }
+ }
+ }
+}
+
+nsIStyleRuleProcessor*
+nsXBLPrototypeBinding::GetRuleProcessor()
+{
+ if (mResources) {
+ return mResources->GetRuleProcessor();
+ }
+
+ return nullptr;
+}
+
+void
+nsXBLPrototypeBinding::EnsureAttributeTable()
+{
+ if (!mAttributeTable) {
+ mAttributeTable =
+ new nsClassHashtable<nsUint32HashKey, InnerAttributeTable>(2);
+ }
+}
+
+void
+nsXBLPrototypeBinding::AddToAttributeTable(int32_t aSourceNamespaceID, nsIAtom* aSourceTag,
+ int32_t aDestNamespaceID, nsIAtom* aDestTag,
+ nsIContent* aContent)
+{
+ InnerAttributeTable* attributesNS = mAttributeTable->Get(aSourceNamespaceID);
+ if (!attributesNS) {
+ attributesNS = new InnerAttributeTable(2);
+ mAttributeTable->Put(aSourceNamespaceID, attributesNS);
+ }
+
+ nsXBLAttributeEntry* xblAttr =
+ new nsXBLAttributeEntry(aSourceTag, aDestTag, aDestNamespaceID, aContent);
+
+ nsXBLAttributeEntry* entry = attributesNS->Get(aSourceTag);
+ if (!entry) {
+ attributesNS->Put(aSourceTag, xblAttr);
+ } else {
+ while (entry->GetNext())
+ entry = entry->GetNext();
+ entry->SetNext(xblAttr);
+ }
+}
+
+void
+nsXBLPrototypeBinding::ConstructAttributeTable(nsIContent* aElement)
+{
+ // Don't add entries for <children> elements, since those will get
+ // removed from the DOM when we construct the insertion point table.
+ if (!aElement->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
+ nsAutoString inherits;
+ aElement->GetAttr(kNameSpaceID_XBL, nsGkAtoms::inherits, inherits);
+
+ if (!inherits.IsEmpty()) {
+ EnsureAttributeTable();
+
+ // The user specified at least one attribute.
+ char* str = ToNewCString(inherits);
+ char* newStr;
+ // XXX We should use a strtok function that tokenizes PRUnichars
+ // so that we don't have to convert from Unicode to ASCII and then back
+
+ char* token = nsCRT::strtok( str, ", ", &newStr );
+ while( token != nullptr ) {
+ // Build an atom out of this attribute.
+ nsCOMPtr<nsIAtom> atom;
+ int32_t atomNsID = kNameSpaceID_None;
+ nsCOMPtr<nsIAtom> attribute;
+ int32_t attributeNsID = kNameSpaceID_None;
+
+ // Figure out if this token contains a :.
+ nsAutoString attrTok; attrTok.AssignWithConversion(token);
+ int32_t index = attrTok.Find("=", true);
+ nsresult rv;
+ if (index != -1) {
+ // This attribute maps to something different.
+ nsAutoString left, right;
+ attrTok.Left(left, index);
+ attrTok.Right(right, attrTok.Length()-index-1);
+
+ rv = nsContentUtils::SplitQName(aElement, left, &attributeNsID,
+ getter_AddRefs(attribute));
+ if (NS_FAILED(rv))
+ return;
+
+ rv = nsContentUtils::SplitQName(aElement, right, &atomNsID,
+ getter_AddRefs(atom));
+ if (NS_FAILED(rv))
+ return;
+ }
+ else {
+ nsAutoString tok;
+ tok.AssignWithConversion(token);
+ rv = nsContentUtils::SplitQName(aElement, tok, &atomNsID,
+ getter_AddRefs(atom));
+ if (NS_FAILED(rv))
+ return;
+ attribute = atom;
+ attributeNsID = atomNsID;
+ }
+
+ AddToAttributeTable(atomNsID, atom, attributeNsID, attribute, aElement);
+
+ // Now remove the inherits attribute from the element so that it doesn't
+ // show up on clones of the element. It is used
+ // by the template only, and we don't need it anymore.
+ // XXXdwh Don't do this for XUL elements, since it faults them into heavyweight
+ // elements. Should nuke from the prototype instead.
+ // aElement->UnsetAttr(kNameSpaceID_XBL, nsGkAtoms::inherits, false);
+
+ token = nsCRT::strtok( newStr, ", ", &newStr );
+ }
+
+ free(str);
+ }
+ }
+
+ // Recur into our children.
+ for (nsIContent* child = aElement->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ ConstructAttributeTable(child);
+ }
+}
+
+nsresult
+nsXBLPrototypeBinding::ConstructInterfaceTable(const nsAString& aImpls)
+{
+ if (!aImpls.IsEmpty()) {
+ // Obtain the interface info manager that can tell us the IID
+ // for a given interface name.
+ nsCOMPtr<nsIInterfaceInfoManager>
+ infoManager(do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID));
+ if (!infoManager)
+ return NS_ERROR_FAILURE;
+
+ // The user specified at least one attribute.
+ NS_ConvertUTF16toUTF8 utf8impl(aImpls);
+ char* str = utf8impl.BeginWriting();
+ char* newStr;
+ // XXX We should use a strtok function that tokenizes PRUnichars
+ // so that we don't have to convert from Unicode to ASCII and then back
+
+ char* token = nsCRT::strtok( str, ", ", &newStr );
+ while( token != nullptr ) {
+ // get the InterfaceInfo for the name
+ nsCOMPtr<nsIInterfaceInfo> iinfo;
+ infoManager->GetInfoForName(token, getter_AddRefs(iinfo));
+
+ if (iinfo) {
+ // obtain an IID.
+ const nsIID* iid = nullptr;
+ iinfo->GetIIDShared(&iid);
+
+ if (iid) {
+ // We found a valid iid. Add it to our table.
+ mInterfaceTable.Put(*iid, mBinding);
+
+ // this block adds the parent interfaces of each interface
+ // defined in the xbl definition (implements="nsI...")
+ nsCOMPtr<nsIInterfaceInfo> parentInfo;
+ // if it has a parent, add it to the table
+ while (NS_SUCCEEDED(iinfo->GetParent(getter_AddRefs(parentInfo))) && parentInfo) {
+ // get the iid
+ parentInfo->GetIIDShared(&iid);
+
+ // don't add nsISupports to the table
+ if (!iid || iid->Equals(NS_GET_IID(nsISupports)))
+ break;
+
+ // add the iid to the table
+ mInterfaceTable.Put(*iid, mBinding);
+
+ // look for the next parent
+ iinfo = parentInfo;
+ }
+ }
+ }
+
+ token = nsCRT::strtok( newStr, ", ", &newStr );
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLPrototypeBinding::AddResourceListener(nsIContent* aBoundElement)
+{
+ if (!mResources)
+ return NS_ERROR_FAILURE; // Makes no sense to add a listener when the binding
+ // has no resources.
+
+ mResources->AddResourceListener(aBoundElement);
+ return NS_OK;
+}
+
+void
+nsXBLPrototypeBinding::CreateKeyHandlers()
+{
+ nsXBLPrototypeHandler* curr = mPrototypeHandler;
+ while (curr) {
+ nsCOMPtr<nsIAtom> eventAtom = curr->GetEventName();
+ if (eventAtom == nsGkAtoms::keyup ||
+ eventAtom == nsGkAtoms::keydown ||
+ eventAtom == nsGkAtoms::keypress) {
+ uint8_t phase = curr->GetPhase();
+ uint8_t type = curr->GetType();
+
+ int32_t count = mKeyHandlers.Count();
+ int32_t i;
+ nsXBLKeyEventHandler* handler = nullptr;
+ for (i = 0; i < count; ++i) {
+ handler = mKeyHandlers[i];
+ if (handler->Matches(eventAtom, phase, type))
+ break;
+ }
+
+ if (i == count) {
+ RefPtr<nsXBLKeyEventHandler> newHandler =
+ new nsXBLKeyEventHandler(eventAtom, phase, type);
+ mKeyHandlers.AppendObject(newHandler);
+ handler = newHandler;
+ }
+
+ if (handler)
+ handler->AddProtoHandler(curr);
+ }
+
+ curr = curr->GetNextHandler();
+ }
+}
+
+class XBLPrototypeSetupCleanup
+{
+public:
+ XBLPrototypeSetupCleanup(nsXBLDocumentInfo* aDocInfo, const nsACString& aID)
+ : mDocInfo(aDocInfo), mID(aID) {}
+
+ ~XBLPrototypeSetupCleanup()
+ {
+ if (mDocInfo) {
+ mDocInfo->RemovePrototypeBinding(mID);
+ }
+ }
+
+ void Disconnect()
+ {
+ mDocInfo = nullptr;
+ }
+
+ nsXBLDocumentInfo* mDocInfo;
+ nsAutoCString mID;
+};
+
+nsresult
+nsXBLPrototypeBinding::Read(nsIObjectInputStream* aStream,
+ nsXBLDocumentInfo* aDocInfo,
+ nsIDocument* aDocument,
+ uint8_t aFlags)
+{
+ mInheritStyle = (aFlags & XBLBinding_Serialize_InheritStyle) ? true : false;
+ mChromeOnlyContent =
+ (aFlags & XBLBinding_Serialize_ChromeOnlyContent) ? true : false;
+ mBindToUntrustedContent =
+ (aFlags & XBLBinding_Serialize_BindToUntrustedContent) ? true : false;
+
+ // nsXBLContentSink::ConstructBinding doesn't create a binding with an empty
+ // id, so we don't here either.
+ nsAutoCString id;
+ nsresult rv = aStream->ReadCString(id);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(!id.IsEmpty(), NS_ERROR_FAILURE);
+
+ nsAutoCString baseBindingURI;
+ rv = aStream->ReadCString(baseBindingURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCheckedBaseProto = true;
+
+ if (!baseBindingURI.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(mBaseBindingURI), baseBindingURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = ReadNamespace(aStream, mBaseNameSpaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString baseTag;
+ rv = aStream->ReadString(baseTag);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!baseTag.IsEmpty()) {
+ mBaseTag = NS_Atomize(baseTag);
+ }
+
+ mBinding = aDocument->CreateElem(NS_LITERAL_STRING("binding"), nullptr,
+ kNameSpaceID_XBL);
+
+ nsCOMPtr<nsIContent> child;
+ rv = ReadContentNode(aStream, aDocument, aDocument->NodeInfoManager(), getter_AddRefs(child));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Element* rootElement = aDocument->GetRootElement();
+ if (rootElement)
+ rootElement->AppendChildTo(mBinding, false);
+
+ if (child) {
+ mBinding->AppendChildTo(child, false);
+ }
+
+ uint32_t interfaceCount;
+ rv = aStream->Read32(&interfaceCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (; interfaceCount > 0; interfaceCount--) {
+ nsIID iid;
+ rv = aStream->ReadID(&iid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mInterfaceTable.Put(iid, mBinding);
+ }
+
+ // We're not directly using this AutoJSAPI here, but callees use it via
+ // AutoJSContext.
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(xpc::CompilationScope())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool isFirstBinding = aFlags & XBLBinding_Serialize_IsFirstBinding;
+ rv = Init(id, aDocInfo, nullptr, isFirstBinding);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We need to set the prototype binding before reading the nsXBLProtoImpl,
+ // as it may be retrieved within.
+ rv = aDocInfo->SetPrototypeBinding(id, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ XBLPrototypeSetupCleanup cleanup(aDocInfo, id);
+
+ nsAutoCString className;
+ rv = aStream->ReadCString(className);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!className.IsEmpty()) {
+ nsXBLProtoImpl* impl; // NS_NewXBLProtoImpl will set mImplementation for us
+ NS_NewXBLProtoImpl(this, NS_ConvertUTF8toUTF16(className).get(), &impl);
+
+ // This needs to happen after SetPrototypeBinding as calls are made to
+ // retrieve the mapped bindings from within here. However, if an error
+ // occurs, the mapping should be removed again so that we don't keep an
+ // invalid binding around.
+ rv = mImplementation->Read(aStream, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Next read in the handlers.
+ nsXBLPrototypeHandler* previousHandler = nullptr;
+
+ do {
+ XBLBindingSerializeDetails type;
+ rv = aStream->Read8(&type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (type == XBLBinding_Serialize_NoMoreItems)
+ break;
+
+ NS_ASSERTION((type & XBLBinding_Serialize_Mask) == XBLBinding_Serialize_Handler,
+ "invalid handler type");
+
+ nsXBLPrototypeHandler* handler = new nsXBLPrototypeHandler(this);
+ rv = handler->Read(aStream);
+ if (NS_FAILED(rv)) {
+ delete handler;
+ return rv;
+ }
+
+ if (previousHandler) {
+ previousHandler->SetNextHandler(handler);
+ }
+ else {
+ SetPrototypeHandlers(handler);
+ }
+ previousHandler = handler;
+ } while (1);
+
+ if (mBinding) {
+ while (true) {
+ XBLBindingSerializeDetails type;
+ rv = aStream->Read8(&type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (type != XBLBinding_Serialize_Attribute) {
+ break;
+ }
+
+ int32_t attrNamespace;
+ rv = ReadNamespace(aStream, attrNamespace);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString attrPrefix, attrName, attrValue;
+ rv = aStream->ReadString(attrPrefix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->ReadString(attrName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->ReadString(attrValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAtom> atomPrefix = NS_Atomize(attrPrefix);
+ nsCOMPtr<nsIAtom> atomName = NS_Atomize(attrName);
+ mBinding->SetAttr(attrNamespace, atomName, atomPrefix, attrValue, false);
+ }
+ }
+
+ // Finally, read in the resources.
+ while (true) {
+ XBLBindingSerializeDetails type;
+ rv = aStream->Read8(&type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (type == XBLBinding_Serialize_NoMoreItems)
+ break;
+
+ NS_ASSERTION((type & XBLBinding_Serialize_Mask) == XBLBinding_Serialize_Stylesheet ||
+ (type & XBLBinding_Serialize_Mask) == XBLBinding_Serialize_Image, "invalid resource type");
+
+ nsAutoString src;
+ rv = aStream->ReadString(src);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AddResource(type == XBLBinding_Serialize_Stylesheet ? nsGkAtoms::stylesheet :
+ nsGkAtoms::image, src);
+ }
+
+ if (isFirstBinding) {
+ aDocInfo->SetFirstPrototypeBinding(this);
+ }
+
+ cleanup.Disconnect();
+ return NS_OK;
+}
+
+// static
+nsresult
+nsXBLPrototypeBinding::ReadNewBinding(nsIObjectInputStream* aStream,
+ nsXBLDocumentInfo* aDocInfo,
+ nsIDocument* aDocument,
+ uint8_t aFlags)
+{
+ // If the Read() succeeds, |binding| will end up being owned by aDocInfo's
+ // binding table. Otherwise, we must manually delete it.
+ nsXBLPrototypeBinding* binding = new nsXBLPrototypeBinding();
+ nsresult rv = binding->Read(aStream, aDocInfo, aDocument, aFlags);
+ if (NS_FAILED(rv)) {
+ delete binding;
+ }
+ return rv;
+}
+
+nsresult
+nsXBLPrototypeBinding::Write(nsIObjectOutputStream* aStream)
+{
+ // This writes out the binding. Note that mCheckedBaseProto,
+ // mKeyHandlersRegistered and mKeyHandlers are not serialized as they are
+ // computed on demand.
+
+ // We're not directly using this AutoJSAPI here, but callees use it via
+ // AutoJSContext.
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(xpc::CompilationScope())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ uint8_t flags = mInheritStyle ? XBLBinding_Serialize_InheritStyle : 0;
+
+ // mAlternateBindingURI is only set on the first binding.
+ if (mAlternateBindingURI) {
+ flags |= XBLBinding_Serialize_IsFirstBinding;
+ }
+
+ if (mChromeOnlyContent) {
+ flags |= XBLBinding_Serialize_ChromeOnlyContent;
+ }
+
+ if (mBindToUntrustedContent) {
+ flags |= XBLBinding_Serialize_BindToUntrustedContent;
+ }
+
+ nsresult rv = aStream->Write8(flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString id;
+ mBindingURI->GetRef(id);
+ rv = aStream->WriteStringZ(id.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // write out the extends and display attribute values
+ nsAutoCString extends;
+ ResolveBaseBinding();
+ if (mBaseBindingURI) {
+ rv = mBaseBindingURI->GetSpec(extends);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = aStream->WriteStringZ(extends.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = WriteNamespace(aStream, mBaseNameSpaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString baseTag;
+ if (mBaseTag) {
+ mBaseTag->ToString(baseTag);
+ }
+ rv = aStream->WriteWStringZ(baseTag.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIContent* content = GetImmediateChild(nsGkAtoms::content);
+ if (content) {
+ rv = WriteContentNode(aStream, content);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ // Write a marker to indicate that there is no content.
+ rv = aStream->Write8(XBLBinding_Serialize_NoContent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Enumerate and write out the implemented interfaces.
+ rv = aStream->Write32(mInterfaceTable.Count());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto iter = mInterfaceTable.Iter(); !iter.Done(); iter.Next()) {
+ // We can just write out the ids. The cache will be invalidated when a
+ // different build is used, so we don't need to worry about ids changing.
+ aStream->WriteID(iter.Key());
+ }
+
+ // Write out the implementation details.
+ if (mImplementation) {
+ rv = mImplementation->Write(aStream, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ // Write out an empty classname. This indicates that the binding does not
+ // define an implementation.
+ rv = aStream->WriteUtf8Z(EmptyString().get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Write out the handlers.
+ nsXBLPrototypeHandler* handler = mPrototypeHandler;
+ while (handler) {
+ rv = handler->Write(aStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ handler = handler->GetNextHandler();
+ }
+
+ aStream->Write8(XBLBinding_Serialize_NoMoreItems);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mBinding) {
+ uint32_t attributes = mBinding->GetAttrCount();
+ nsAutoString attrValue;
+ for (uint32_t i = 0; i < attributes; ++i) {
+ BorrowedAttrInfo attrInfo = mBinding->GetAttrInfoAt(i);
+ const nsAttrName* name = attrInfo.mName;
+ nsDependentAtomString attrName(attrInfo.mName->LocalName());
+ attrInfo.mValue->ToString(attrValue);
+
+ rv = aStream->Write8(XBLBinding_Serialize_Attribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = WriteNamespace(aStream, name->NamespaceID());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIAtom* prefix = name->GetPrefix();
+ nsAutoString prefixString;
+ if (prefix) {
+ prefix->ToString(prefixString);
+ }
+
+ rv = aStream->WriteWStringZ(prefixString.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->WriteWStringZ(attrName.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->WriteWStringZ(attrValue.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ aStream->Write8(XBLBinding_Serialize_NoMoreItems);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Write out the resources
+ if (mResources) {
+ rv = mResources->Write(aStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Write out an end mark at the end.
+ return aStream->Write8(XBLBinding_Serialize_NoMoreItems);
+}
+
+nsresult
+nsXBLPrototypeBinding::ReadContentNode(nsIObjectInputStream* aStream,
+ nsIDocument* aDocument,
+ nsNodeInfoManager* aNim,
+ nsIContent** aContent)
+{
+ *aContent = nullptr;
+
+ int32_t namespaceID;
+ nsresult rv = ReadNamespace(aStream, namespaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // There is no content to read so just return.
+ if (namespaceID == XBLBinding_Serialize_NoContent)
+ return NS_OK;
+
+ nsCOMPtr<nsIContent> content;
+
+ // If this is a text type, just read the string and return.
+ if (namespaceID == XBLBinding_Serialize_TextNode ||
+ namespaceID == XBLBinding_Serialize_CDATANode ||
+ namespaceID == XBLBinding_Serialize_CommentNode) {
+ switch (namespaceID) {
+ case XBLBinding_Serialize_TextNode:
+ content = new nsTextNode(aNim);
+ break;
+ case XBLBinding_Serialize_CDATANode:
+ content = new CDATASection(aNim);
+ break;
+ case XBLBinding_Serialize_CommentNode:
+ content = new Comment(aNim);
+ break;
+ default:
+ break;
+ }
+
+ nsAutoString text;
+ rv = aStream->ReadString(text);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ content->SetText(text, false);
+ content.swap(*aContent);
+ return NS_OK;
+ }
+
+ // Otherwise, it's an element, so read its tag, attributes and children.
+ nsAutoString prefix, tag;
+ rv = aStream->ReadString(prefix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAtom> prefixAtom;
+ if (!prefix.IsEmpty())
+ prefixAtom = NS_Atomize(prefix);
+
+ rv = aStream->ReadString(tag);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAtom> tagAtom = NS_Atomize(tag);
+ RefPtr<NodeInfo> nodeInfo =
+ aNim->GetNodeInfo(tagAtom, prefixAtom, namespaceID, nsIDOMNode::ELEMENT_NODE);
+
+ uint32_t attrCount;
+ rv = aStream->Read32(&attrCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create XUL prototype elements, or regular elements for other namespaces.
+ // This needs to match the code in nsXBLContentSink::CreateElement.
+#ifdef MOZ_XUL
+ if (namespaceID == kNameSpaceID_XUL) {
+ nsIURI* documentURI = aDocument->GetDocumentURI();
+
+ RefPtr<nsXULPrototypeElement> prototype = new nsXULPrototypeElement();
+
+ prototype->mNodeInfo = nodeInfo;
+
+ nsXULPrototypeAttribute* attrs = nullptr;
+ if (attrCount > 0) {
+ attrs = new nsXULPrototypeAttribute[attrCount];
+ }
+
+ prototype->mAttributes = attrs;
+ prototype->mNumAttributes = attrCount;
+
+ for (uint32_t i = 0; i < attrCount; i++) {
+ rv = ReadNamespace(aStream, namespaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString prefix, name, val;
+ rv = aStream->ReadString(prefix);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStream->ReadString(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStream->ReadString(val);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAtom> nameAtom = NS_Atomize(name);
+ if (namespaceID == kNameSpaceID_None) {
+ attrs[i].mName.SetTo(nameAtom);
+ }
+ else {
+ nsCOMPtr<nsIAtom> prefixAtom;
+ if (!prefix.IsEmpty())
+ prefixAtom = NS_Atomize(prefix);
+
+ RefPtr<NodeInfo> ni =
+ aNim->GetNodeInfo(nameAtom, prefixAtom,
+ namespaceID, nsIDOMNode::ATTRIBUTE_NODE);
+ attrs[i].mName.SetTo(ni);
+ }
+
+ rv = prototype->SetAttrAt(i, val, documentURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<Element> result;
+ nsresult rv =
+ nsXULElement::Create(prototype, aDocument, false, false, getter_AddRefs(result));
+ NS_ENSURE_SUCCESS(rv, rv);
+ content = result;
+ }
+ else {
+#endif
+ nsCOMPtr<Element> element;
+ NS_NewElement(getter_AddRefs(element), nodeInfo.forget(), NOT_FROM_PARSER);
+ content = element;
+
+ for (uint32_t i = 0; i < attrCount; i++) {
+ rv = ReadNamespace(aStream, namespaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString prefix, name, val;
+ rv = aStream->ReadString(prefix);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStream->ReadString(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStream->ReadString(val);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAtom> prefixAtom;
+ if (!prefix.IsEmpty())
+ prefixAtom = NS_Atomize(prefix);
+
+ nsCOMPtr<nsIAtom> nameAtom = NS_Atomize(name);
+ content->SetAttr(namespaceID, nameAtom, prefixAtom, val, false);
+ }
+
+#ifdef MOZ_XUL
+ }
+#endif
+
+ // Now read the attribute forwarding entries (xbl:inherits)
+
+ int32_t srcNamespaceID, destNamespaceID;
+ rv = ReadNamespace(aStream, srcNamespaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (srcNamespaceID != XBLBinding_Serialize_NoMoreAttributes) {
+ nsAutoString srcAttribute, destAttribute;
+ rv = aStream->ReadString(srcAttribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = ReadNamespace(aStream, destNamespaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStream->ReadString(destAttribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAtom> srcAtom = NS_Atomize(srcAttribute);
+ nsCOMPtr<nsIAtom> destAtom = NS_Atomize(destAttribute);
+
+ EnsureAttributeTable();
+ AddToAttributeTable(srcNamespaceID, srcAtom, destNamespaceID, destAtom, content);
+
+ rv = ReadNamespace(aStream, srcNamespaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Finally, read in the child nodes.
+ uint32_t childCount;
+ rv = aStream->Read32(&childCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsCOMPtr<nsIContent> child;
+ ReadContentNode(aStream, aDocument, aNim, getter_AddRefs(child));
+
+ // Child may be null if this was a comment for example and can just be ignored.
+ if (child) {
+ content->AppendChildTo(child, false);
+ }
+ }
+
+ content.swap(*aContent);
+ return NS_OK;
+}
+
+nsresult
+nsXBLPrototypeBinding::WriteContentNode(nsIObjectOutputStream* aStream,
+ nsIContent* aNode)
+{
+ nsresult rv;
+
+ if (!aNode->IsElement()) {
+ // Text is writen out as a single byte for the type, followed by the text.
+ uint8_t type = XBLBinding_Serialize_NoContent;
+ switch (aNode->NodeType()) {
+ case nsIDOMNode::TEXT_NODE:
+ type = XBLBinding_Serialize_TextNode;
+ break;
+ case nsIDOMNode::CDATA_SECTION_NODE:
+ type = XBLBinding_Serialize_CDATANode;
+ break;
+ case nsIDOMNode::COMMENT_NODE:
+ type = XBLBinding_Serialize_CommentNode;
+ break;
+ default:
+ break;
+ }
+
+ rv = aStream->Write8(type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString content;
+ aNode->GetText()->AppendTo(content);
+ return aStream->WriteWStringZ(content.get());
+ }
+
+ // Otherwise, this is an element.
+
+ // Write the namespace id followed by the tag name
+ rv = WriteNamespace(aStream, aNode->GetNameSpaceID());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString prefixStr;
+ aNode->NodeInfo()->GetPrefix(prefixStr);
+ rv = aStream->WriteWStringZ(prefixStr.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->WriteWStringZ(nsDependentAtomString(aNode->NodeInfo()->NameAtom()).get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Write attributes
+ uint32_t count = aNode->GetAttrCount();
+ rv = aStream->Write32(count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t i;
+ for (i = 0; i < count; i++) {
+ // Write out the namespace id, the namespace prefix, the local tag name,
+ // and the value, in that order.
+
+ const BorrowedAttrInfo attrInfo = aNode->GetAttrInfoAt(i);
+ const nsAttrName* name = attrInfo.mName;
+
+ // XXXndeakin don't write out xbl:inherits?
+ int32_t namespaceID = name->NamespaceID();
+ rv = WriteNamespace(aStream, namespaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString prefixStr;
+ nsIAtom* prefix = name->GetPrefix();
+ if (prefix)
+ prefix->ToString(prefixStr);
+ rv = aStream->WriteWStringZ(prefixStr.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->WriteWStringZ(nsDependentAtomString(name->LocalName()).get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString val;
+ attrInfo.mValue->ToString(val);
+ rv = aStream->WriteWStringZ(val.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Write out the attribute fowarding information
+ if (mAttributeTable) {
+ for (auto iter1 = mAttributeTable->Iter(); !iter1.Done(); iter1.Next()) {
+ int32_t srcNamespace = iter1.Key();
+ InnerAttributeTable* xblAttributes = iter1.UserData();
+
+ for (auto iter2 = xblAttributes->Iter(); !iter2.Done(); iter2.Next()) {
+ nsXBLAttributeEntry* entry = iter2.UserData();
+
+ do {
+ if (entry->GetElement() == aNode) {
+ WriteNamespace(aStream, srcNamespace);
+ aStream->WriteWStringZ(
+ nsDependentAtomString(entry->GetSrcAttribute()).get());
+ WriteNamespace(aStream, entry->GetDstNameSpace());
+ aStream->WriteWStringZ(
+ nsDependentAtomString(entry->GetDstAttribute()).get());
+ }
+
+ entry = entry->GetNext();
+ } while (entry);
+ }
+ }
+ }
+ rv = aStream->Write8(XBLBinding_Serialize_NoMoreAttributes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, write out the child nodes.
+ count = aNode->GetChildCount();
+ rv = aStream->Write32(count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (i = 0; i < count; i++) {
+ rv = WriteContentNode(aStream, aNode->GetChildAt(i));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLPrototypeBinding::ReadNamespace(nsIObjectInputStream* aStream,
+ int32_t& aNameSpaceID)
+{
+ uint8_t namespaceID;
+ nsresult rv = aStream->Read8(&namespaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (namespaceID == XBLBinding_Serialize_CustomNamespace) {
+ nsAutoString namesp;
+ rv = aStream->ReadString(namesp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsContentUtils::NameSpaceManager()->RegisterNameSpace(namesp, aNameSpaceID);
+ }
+ else {
+ aNameSpaceID = namespaceID;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLPrototypeBinding::WriteNamespace(nsIObjectOutputStream* aStream,
+ int32_t aNameSpaceID)
+{
+ // Namespaces are stored as a single byte id for well-known namespaces.
+ // This saves time and space as other namespaces aren't very common in
+ // XBL. If another namespace is used however, the namespace id will be
+ // XBLBinding_Serialize_CustomNamespace and the string namespace written
+ // out directly afterwards.
+ nsresult rv;
+
+ if (aNameSpaceID <= kNameSpaceID_LastBuiltin) {
+ rv = aStream->Write8((int8_t)aNameSpaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ rv = aStream->Write8(XBLBinding_Serialize_CustomNamespace);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString namesp;
+ nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNameSpaceID, namesp);
+ aStream->WriteWStringZ(namesp.get());
+ }
+
+ return NS_OK;
+}
+
+
+bool CheckTagNameWhiteList(int32_t aNameSpaceID, nsIAtom *aTagName)
+{
+ static nsIContent::AttrValuesArray kValidXULTagNames[] = {
+ &nsGkAtoms::autorepeatbutton, &nsGkAtoms::box, &nsGkAtoms::browser,
+ &nsGkAtoms::button, &nsGkAtoms::hbox, &nsGkAtoms::image, &nsGkAtoms::menu,
+ &nsGkAtoms::menubar, &nsGkAtoms::menuitem, &nsGkAtoms::menupopup,
+ &nsGkAtoms::row, &nsGkAtoms::slider, &nsGkAtoms::spacer,
+ &nsGkAtoms::splitter, &nsGkAtoms::text, &nsGkAtoms::tree, nullptr};
+
+ uint32_t i;
+ if (aNameSpaceID == kNameSpaceID_XUL) {
+ for (i = 0; kValidXULTagNames[i]; ++i) {
+ if (aTagName == *(kValidXULTagNames[i])) {
+ return true;
+ }
+ }
+ }
+ else if (aNameSpaceID == kNameSpaceID_SVG &&
+ aTagName == nsGkAtoms::generic_) {
+ return true;
+ }
+
+ return false;
+}
+
+nsresult
+nsXBLPrototypeBinding::ResolveBaseBinding()
+{
+ if (mCheckedBaseProto)
+ return NS_OK;
+ mCheckedBaseProto = true;
+
+ nsCOMPtr<nsIDocument> doc = mXBLDocInfoWeak->GetDocument();
+
+ // Check for the presence of 'extends' and 'display' attributes
+ nsAutoString display, extends;
+ mBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::extends, extends);
+ if (extends.IsEmpty())
+ return NS_OK;
+
+ mBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::display, display);
+ bool hasDisplay = !display.IsEmpty();
+
+ nsAutoString value(extends);
+
+ // Now slice 'em up to see what we've got.
+ nsAutoString prefix;
+ int32_t offset;
+ if (hasDisplay) {
+ offset = display.FindChar(':');
+ if (-1 != offset) {
+ display.Left(prefix, offset);
+ display.Cut(0, offset+1);
+ }
+ }
+ else {
+ offset = extends.FindChar(':');
+ if (-1 != offset) {
+ extends.Left(prefix, offset);
+ extends.Cut(0, offset+1);
+ display = extends;
+ }
+ }
+
+ nsAutoString nameSpace;
+
+ if (!prefix.IsEmpty()) {
+ mBinding->LookupNamespaceURI(prefix, nameSpace);
+ if (!nameSpace.IsEmpty()) {
+ int32_t nameSpaceID =
+ nsContentUtils::NameSpaceManager()->GetNameSpaceID(nameSpace,
+ nsContentUtils::IsChromeDoc(doc));
+
+ nsCOMPtr<nsIAtom> tagName = NS_Atomize(display);
+ // Check the white list
+ if (!CheckTagNameWhiteList(nameSpaceID, tagName)) {
+ const char16_t* params[] = { display.get() };
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("XBL"), nullptr,
+ nsContentUtils::eXBL_PROPERTIES,
+ "InvalidExtendsBinding",
+ params, ArrayLength(params),
+ doc->GetDocumentURI());
+ NS_ASSERTION(!nsXBLService::IsChromeOrResourceURI(doc->GetDocumentURI()),
+ "Invalid extends value");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ SetBaseTag(nameSpaceID, tagName);
+ }
+ }
+
+ if (hasDisplay || nameSpace.IsEmpty()) {
+ mBinding->UnsetAttr(kNameSpaceID_None, nsGkAtoms::extends, false);
+ mBinding->UnsetAttr(kNameSpaceID_None, nsGkAtoms::display, false);
+
+ return NS_NewURI(getter_AddRefs(mBaseBindingURI), value,
+ doc->GetDocumentCharacterSet().get(),
+ doc->GetDocBaseURI());
+ }
+
+ return NS_OK;
+}
+
+void
+nsXBLPrototypeBinding::EnsureResources()
+{
+ if (!mResources) {
+ mResources = new nsXBLPrototypeResources(this);
+ }
+}
+
+void
+nsXBLPrototypeBinding::AppendStyleSheet(StyleSheet* aSheet)
+{
+ EnsureResources();
+ mResources->AppendStyleSheet(aSheet);
+}
+
+void
+nsXBLPrototypeBinding::RemoveStyleSheet(StyleSheet* aSheet)
+{
+ if (!mResources) {
+ MOZ_ASSERT(false, "Trying to remove a sheet that does not exist.");
+ return;
+ }
+
+ mResources->RemoveStyleSheet(aSheet);
+}
+void
+nsXBLPrototypeBinding::InsertStyleSheetAt(size_t aIndex, StyleSheet* aSheet)
+{
+ EnsureResources();
+ mResources->InsertStyleSheetAt(aIndex, aSheet);
+}
+
+StyleSheet*
+nsXBLPrototypeBinding::StyleSheetAt(size_t aIndex) const
+{
+ MOZ_ASSERT(mResources);
+ return mResources->StyleSheetAt(aIndex);
+}
+
+size_t
+nsXBLPrototypeBinding::SheetCount() const
+{
+ return mResources ? mResources->SheetCount() : 0;
+}
+
+bool
+nsXBLPrototypeBinding::HasStyleSheets() const
+{
+ return mResources && mResources->HasStyleSheets();
+}
+
+void
+nsXBLPrototypeBinding::AppendStyleSheetsTo(
+ nsTArray<StyleSheet*>& aResult) const
+{
+ if (mResources) {
+ mResources->AppendStyleSheetsTo(aResult);
+ }
+}
diff --git a/dom/xbl/nsXBLPrototypeBinding.h b/dom/xbl/nsXBLPrototypeBinding.h
new file mode 100644
index 000000000..4c51a2083
--- /dev/null
+++ b/dom/xbl/nsXBLPrototypeBinding.h
@@ -0,0 +1,359 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLPrototypeBinding_h__
+#define nsXBLPrototypeBinding_h__
+
+#include "nsAutoPtr.h"
+#include "nsClassHashtable.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsInterfaceHashtable.h"
+#include "nsWeakReference.h"
+#include "nsXBLDocumentInfo.h"
+#include "nsXBLProtoImpl.h"
+#include "nsXBLProtoImplMethod.h"
+#include "nsXBLPrototypeHandler.h"
+#include "nsXBLPrototypeResources.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/StyleSheet.h"
+
+class nsIAtom;
+class nsIContent;
+class nsIDocument;
+class nsXBLAttributeEntry;
+class nsXBLBinding;
+class nsXBLProtoImplField;
+
+// *********************************************************************/
+// The XBLPrototypeBinding class
+
+// Instances of this class are owned by the nsXBLDocumentInfo object returned
+// by XBLDocumentInfo(). Consumers who want to refcount things should refcount
+// that.
+class nsXBLPrototypeBinding final :
+ public mozilla::SupportsWeakPtr<nsXBLPrototypeBinding>
+{
+public:
+ MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsXBLPrototypeBinding)
+
+ nsIContent* GetBindingElement() const { return mBinding; }
+ void SetBindingElement(nsIContent* aElement);
+
+ nsIURI* BindingURI() const { return mBindingURI; }
+ nsIURI* AlternateBindingURI() const { return mAlternateBindingURI; }
+ nsIURI* DocURI() const { return mXBLDocInfoWeak->DocumentURI(); }
+ nsIURI* GetBaseBindingURI() const { return mBaseBindingURI; }
+
+ // Checks if aURI refers to this binding by comparing to both possible
+ // binding URIs.
+ bool CompareBindingURI(nsIURI* aURI) const;
+
+ bool GetAllowScripts() const;
+
+ nsresult BindingAttached(nsIContent* aBoundElement);
+ nsresult BindingDetached(nsIContent* aBoundElement);
+
+ bool LoadResources();
+ nsresult AddResource(nsIAtom* aResourceType, const nsAString& aSrc);
+
+ bool InheritsStyle() const { return mInheritStyle; }
+ void SetInheritsStyle(bool aInheritStyle) { mInheritStyle = aInheritStyle; }
+
+ nsXBLPrototypeHandler* GetPrototypeHandlers() { return mPrototypeHandler; }
+ void SetPrototypeHandlers(nsXBLPrototypeHandler* aHandler) { mPrototypeHandler = aHandler; }
+
+ nsXBLProtoImplAnonymousMethod* GetConstructor();
+ nsresult SetConstructor(nsXBLProtoImplAnonymousMethod* aConstructor);
+ nsXBLProtoImplAnonymousMethod* GetDestructor();
+ nsresult SetDestructor(nsXBLProtoImplAnonymousMethod* aDestructor);
+
+ nsXBLProtoImplField* FindField(const nsString& aFieldName) const
+ {
+ return mImplementation ? mImplementation->FindField(aFieldName) : nullptr;
+ }
+
+ // Resolve all the fields for this binding on the object |obj|.
+ // False return means a JS exception was set.
+ bool ResolveAllFields(JSContext* cx, JS::Handle<JSObject*> obj) const
+ {
+ return !mImplementation || mImplementation->ResolveAllFields(cx, obj);
+ }
+
+ // Undefine all our fields from object |obj| (which should be a
+ // JSObject for a bound element).
+ void UndefineFields(JSContext* cx, JS::Handle<JSObject*> obj) const {
+ if (mImplementation) {
+ mImplementation->UndefineFields(cx, obj);
+ }
+ }
+
+ const nsString& ClassName() const {
+ return mImplementation ? mImplementation->mClassName : EmptyString();
+ }
+
+ nsresult InitClass(const nsString& aClassName, JSContext* aContext,
+ JS::Handle<JSObject*> aScriptObject,
+ JS::MutableHandle<JSObject*> aClassObject,
+ bool* aNew);
+
+ nsresult ConstructInterfaceTable(const nsAString& aImpls);
+
+ void SetImplementation(nsXBLProtoImpl* aImpl) { mImplementation = aImpl; }
+ nsXBLProtoImpl* GetImplementation() { return mImplementation; }
+ nsresult InstallImplementation(nsXBLBinding* aBinding);
+ bool HasImplementation() const { return mImplementation != nullptr; }
+
+ void AttributeChanged(nsIAtom* aAttribute, int32_t aNameSpaceID,
+ bool aRemoveFlag, nsIContent* aChangedElement,
+ nsIContent* aAnonymousContent, bool aNotify);
+
+ void SetBasePrototype(nsXBLPrototypeBinding* aBinding);
+ nsXBLPrototypeBinding* GetBasePrototype() { return mBaseBinding; }
+
+ nsXBLDocumentInfo* XBLDocumentInfo() const { return mXBLDocInfoWeak; }
+ bool IsChrome() { return mXBLDocInfoWeak->IsChrome(); }
+
+ void SetInitialAttributes(nsIContent* aBoundElement, nsIContent* aAnonymousContent);
+
+ void AppendStyleSheet(mozilla::StyleSheet* aSheet);
+ void RemoveStyleSheet(mozilla::StyleSheet* aSheet);
+ void InsertStyleSheetAt(size_t aIndex, mozilla::StyleSheet* aSheet);
+ mozilla::StyleSheet* StyleSheetAt(size_t aIndex) const;
+ size_t SheetCount() const;
+ bool HasStyleSheets() const;
+ void AppendStyleSheetsTo(nsTArray<mozilla::StyleSheet*>& aResult) const;
+
+ nsIStyleRuleProcessor* GetRuleProcessor();
+
+ nsresult FlushSkinSheets();
+
+ nsIAtom* GetBaseTag(int32_t* aNamespaceID);
+ void SetBaseTag(int32_t aNamespaceID, nsIAtom* aTag);
+
+ bool ImplementsInterface(REFNSIID aIID) const;
+
+ nsresult AddResourceListener(nsIContent* aBoundElement);
+
+ void Initialize();
+
+ nsresult ResolveBaseBinding();
+
+ const nsCOMArray<nsXBLKeyEventHandler>* GetKeyEventHandlers()
+ {
+ if (!mKeyHandlersRegistered) {
+ CreateKeyHandlers();
+ mKeyHandlersRegistered = true;
+ }
+
+ return &mKeyHandlers;
+ }
+
+private:
+ nsresult Read(nsIObjectInputStream* aStream,
+ nsXBLDocumentInfo* aDocInfo,
+ nsIDocument* aDocument,
+ uint8_t aFlags);
+
+ /**
+ * Read a new binding from the stream aStream into the xbl document aDocument.
+ * aDocInfo should be the xbl document info for the binding document.
+ * aFlags can contain XBLBinding_Serialize_InheritStyle to indicate that
+ * mInheritStyle flag should be set, and XBLBinding_Serialize_IsFirstBinding
+ * to indicate the first binding in a document.
+ * XBLBinding_Serialize_ChromeOnlyContent indicates that
+ * nsXBLPrototypeBinding::mChromeOnlyContent should be true.
+ * XBLBinding_Serialize_BindToUntrustedContent indicates that
+ * nsXBLPrototypeBinding::mBindToUntrustedContent should be true.
+ */
+public:
+ static nsresult ReadNewBinding(nsIObjectInputStream* aStream,
+ nsXBLDocumentInfo* aDocInfo,
+ nsIDocument* aDocument,
+ uint8_t aFlags);
+
+ /**
+ * Write this binding to the stream.
+ */
+ nsresult Write(nsIObjectOutputStream* aStream);
+
+ /**
+ * Read a content node from aStream and return it in aChild.
+ * aDocument and aNim are the document and node info manager for the document
+ * the child will be inserted into.
+ */
+ nsresult ReadContentNode(nsIObjectInputStream* aStream,
+ nsIDocument* aDocument,
+ nsNodeInfoManager* aNim,
+ nsIContent** aChild);
+
+ /**
+ * Write the content node aNode to aStream.
+ *
+ * This method is called recursively for each child descendant. For the topmost
+ * call, aNode must be an element.
+ *
+ * Text, CDATA and comment nodes are serialized as:
+ * the constant XBLBinding_Serialize_TextNode, XBLBinding_Serialize_CDATANode
+ * or XBLBinding_Serialize_CommentNode
+ * the text for the node
+ * Elements are serialized in the following format:
+ * node's namespace, written with WriteNamespace
+ * node's namespace prefix
+ * node's tag
+ * 32-bit attribute count
+ * table of attributes:
+ * attribute's namespace, written with WriteNamespace
+ * attribute's namespace prefix
+ * attribute's tag
+ * attribute's value
+ * attribute forwarding table:
+ * source namespace
+ * source attribute
+ * destination namespace
+ * destination attribute
+ * the constant XBLBinding_Serialize_NoMoreAttributes
+ * 32-bit count of the number of child nodes
+ * each child node is serialized in the same manner in sequence
+ * the constant XBLBinding_Serialize_NoContent
+ */
+ nsresult WriteContentNode(nsIObjectOutputStream* aStream, nsIContent* aNode);
+
+ /**
+ * Read or write a namespace id from or to aStream. If the namespace matches
+ * one of the built-in ones defined in nsNameSpaceManager.h, it will be written as
+ * a single byte with that value. Otherwise, XBLBinding_Serialize_CustomNamespace is
+ * written out, followed by a string written with writeWStringZ.
+ */
+ nsresult ReadNamespace(nsIObjectInputStream* aStream, int32_t& aNameSpaceID);
+ nsresult WriteNamespace(nsIObjectOutputStream* aStream, int32_t aNameSpaceID);
+
+public:
+ nsXBLPrototypeBinding();
+ ~nsXBLPrototypeBinding();
+
+ // Init must be called after construction to initialize the prototype
+ // binding. It may well throw errors (eg on out-of-memory). Do not confuse
+ // this with the Initialize() method, which must be called after the
+ // binding's handlers, properties, etc are all set.
+ nsresult Init(const nsACString& aRef,
+ nsXBLDocumentInfo* aInfo,
+ nsIContent* aElement,
+ bool aFirstBinding = false);
+
+ void Traverse(nsCycleCollectionTraversalCallback &cb) const;
+ void Unlink();
+ void Trace(const TraceCallbacks& aCallbacks, void *aClosure) const;
+
+// Internal member functions.
+public:
+ /**
+ * GetImmediateChild locates the immediate child of our binding element which
+ * has the localname given by aTag and is in the XBL namespace.
+ */
+ nsIContent* GetImmediateChild(nsIAtom* aTag);
+ nsIContent* LocateInstance(nsIContent* aBoundElt,
+ nsIContent* aTemplRoot,
+ nsIContent* aCopyRoot,
+ nsIContent* aTemplChild);
+
+ bool ChromeOnlyContent() { return mChromeOnlyContent; }
+ bool BindToUntrustedContent() { return mBindToUntrustedContent; }
+
+ typedef nsClassHashtable<nsISupportsHashKey, nsXBLAttributeEntry> InnerAttributeTable;
+
+protected:
+ // Ensure that mAttributeTable has been created.
+ void EnsureAttributeTable();
+ // Ad an entry to the attribute table
+ void AddToAttributeTable(int32_t aSourceNamespaceID, nsIAtom* aSourceTag,
+ int32_t aDestNamespaceID, nsIAtom* aDestTag,
+ nsIContent* aContent);
+ void ConstructAttributeTable(nsIContent* aElement);
+ void CreateKeyHandlers();
+
+private:
+ void EnsureResources();
+
+// MEMBER VARIABLES
+protected:
+ nsCOMPtr<nsIURI> mBindingURI;
+ nsCOMPtr<nsIURI> mAlternateBindingURI; // Alternate id-less URI that is only non-null on the first binding.
+ nsCOMPtr<nsIContent> mBinding; // Strong. We own a ref to our content element in the binding doc.
+ nsAutoPtr<nsXBLPrototypeHandler> mPrototypeHandler; // Strong. DocInfo owns us, and we own the handlers.
+
+ // the url of the base binding
+ nsCOMPtr<nsIURI> mBaseBindingURI;
+
+ nsXBLProtoImpl* mImplementation; // Our prototype implementation (includes methods, properties, fields,
+ // the constructor, and the destructor).
+
+ // Weak. The docinfo will own our base binding.
+ mozilla::WeakPtr<nsXBLPrototypeBinding> mBaseBinding;
+ bool mInheritStyle;
+ bool mCheckedBaseProto;
+ bool mKeyHandlersRegistered;
+ bool mChromeOnlyContent;
+ bool mBindToUntrustedContent;
+
+ nsAutoPtr<nsXBLPrototypeResources> mResources; // If we have any resources, this will be non-null.
+
+ nsXBLDocumentInfo* mXBLDocInfoWeak; // A pointer back to our doc info. Weak, since it owns us.
+
+ // A table for attribute containers. Namespace IDs are used as
+ // keys in the table. Containers are InnerAttributeTables.
+ // This table is used to efficiently handle attribute changes.
+ nsAutoPtr<nsClassHashtable<nsUint32HashKey, InnerAttributeTable>> mAttributeTable;
+
+ class IIDHashKey : public PLDHashEntryHdr
+ {
+ public:
+ typedef const nsIID& KeyType;
+ typedef const nsIID* KeyTypePointer;
+
+ explicit IIDHashKey(const nsIID* aKey)
+ : mKey(*aKey)
+ {}
+ IIDHashKey(const IIDHashKey& aOther)
+ : mKey(aOther.GetKey())
+ {}
+ ~IIDHashKey()
+ {}
+
+ KeyType GetKey() const
+ {
+ return mKey;
+ }
+ bool KeyEquals(const KeyTypePointer aKey) const
+ {
+ return mKey.Equals(*aKey);
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey)
+ {
+ return &aKey;
+ }
+ static PLDHashNumber HashKey(const KeyTypePointer aKey)
+ {
+ // Just use the 32-bit m0 field.
+ return aKey->m0;
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ nsIID mKey;
+ };
+ nsInterfaceHashtable<IIDHashKey, nsIContent> mInterfaceTable; // A table of cached interfaces that we support.
+
+ int32_t mBaseNameSpaceID; // If we extend a tagname/namespace, then that information will
+ nsCOMPtr<nsIAtom> mBaseTag; // be stored in here.
+
+ nsCOMArray<nsXBLKeyEventHandler> mKeyHandlers;
+};
+
+#endif
diff --git a/dom/xbl/nsXBLPrototypeHandler.cpp b/dom/xbl/nsXBLPrototypeHandler.cpp
new file mode 100644
index 000000000..2591a72fd
--- /dev/null
+++ b/dom/xbl/nsXBLPrototypeHandler.cpp
@@ -0,0 +1,1016 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsQueryObject.h"
+#include "nsXBLPrototypeHandler.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsIContent.h"
+#include "nsIAtom.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsNameSpaceManager.h"
+#include "nsIDocument.h"
+#include "nsIController.h"
+#include "nsIControllers.h"
+#include "nsIDOMXULElement.h"
+#include "nsIURI.h"
+#include "nsIDOMHTMLTextAreaElement.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsFocusManager.h"
+#include "nsIFormControl.h"
+#include "nsIDOMEventListener.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIWindowRoot.h"
+#include "nsIDOMWindow.h"
+#include "nsIServiceManager.h"
+#include "nsIScriptError.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsGkAtoms.h"
+#include "nsIXPConnect.h"
+#include "mozilla/AddonPathService.h"
+#include "nsDOMCID.h"
+#include "nsUnicharUtils.h"
+#include "nsCRT.h"
+#include "nsXBLEventHandler.h"
+#include "nsXBLSerialize.h"
+#include "nsJSUtils.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/JSEventHandler.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/EventHandlerBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "xpcpublic.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+uint32_t nsXBLPrototypeHandler::gRefCnt = 0;
+
+int32_t nsXBLPrototypeHandler::kMenuAccessKey = -1;
+
+const int32_t nsXBLPrototypeHandler::cShift = (1<<0);
+const int32_t nsXBLPrototypeHandler::cAlt = (1<<1);
+const int32_t nsXBLPrototypeHandler::cControl = (1<<2);
+const int32_t nsXBLPrototypeHandler::cMeta = (1<<3);
+const int32_t nsXBLPrototypeHandler::cOS = (1<<4);
+
+const int32_t nsXBLPrototypeHandler::cShiftMask = (1<<5);
+const int32_t nsXBLPrototypeHandler::cAltMask = (1<<6);
+const int32_t nsXBLPrototypeHandler::cControlMask = (1<<7);
+const int32_t nsXBLPrototypeHandler::cMetaMask = (1<<8);
+const int32_t nsXBLPrototypeHandler::cOSMask = (1<<9);
+
+const int32_t nsXBLPrototypeHandler::cAllModifiers =
+ cShiftMask | cAltMask | cControlMask | cMetaMask | cOSMask;
+
+nsXBLPrototypeHandler::nsXBLPrototypeHandler(const char16_t* aEvent,
+ const char16_t* aPhase,
+ const char16_t* aAction,
+ const char16_t* aCommand,
+ const char16_t* aKeyCode,
+ const char16_t* aCharCode,
+ const char16_t* aModifiers,
+ const char16_t* aButton,
+ const char16_t* aClickCount,
+ const char16_t* aGroup,
+ const char16_t* aPreventDefault,
+ const char16_t* aAllowUntrusted,
+ nsXBLPrototypeBinding* aBinding,
+ uint32_t aLineNumber)
+ : mHandlerText(nullptr),
+ mLineNumber(aLineNumber),
+ mNextHandler(nullptr),
+ mPrototypeBinding(aBinding)
+{
+ Init();
+
+ ConstructPrototype(nullptr, aEvent, aPhase, aAction, aCommand, aKeyCode,
+ aCharCode, aModifiers, aButton, aClickCount,
+ aGroup, aPreventDefault, aAllowUntrusted);
+}
+
+nsXBLPrototypeHandler::nsXBLPrototypeHandler(nsIContent* aHandlerElement)
+ : mHandlerElement(nullptr),
+ mLineNumber(0),
+ mNextHandler(nullptr),
+ mPrototypeBinding(nullptr)
+{
+ Init();
+
+ // Make sure our prototype is initialized.
+ ConstructPrototype(aHandlerElement);
+}
+
+nsXBLPrototypeHandler::nsXBLPrototypeHandler(nsXBLPrototypeBinding* aBinding)
+ : mHandlerText(nullptr),
+ mLineNumber(0),
+ mNextHandler(nullptr),
+ mPrototypeBinding(aBinding)
+{
+ Init();
+}
+
+nsXBLPrototypeHandler::~nsXBLPrototypeHandler()
+{
+ --gRefCnt;
+ if (mType & NS_HANDLER_TYPE_XUL) {
+ NS_IF_RELEASE(mHandlerElement);
+ } else if (mHandlerText) {
+ free(mHandlerText);
+ }
+
+ // We own the next handler in the chain, so delete it now.
+ NS_CONTENT_DELETE_LIST_MEMBER(nsXBLPrototypeHandler, this, mNextHandler);
+}
+
+already_AddRefed<nsIContent>
+nsXBLPrototypeHandler::GetHandlerElement()
+{
+ if (mType & NS_HANDLER_TYPE_XUL) {
+ nsCOMPtr<nsIContent> element = do_QueryReferent(mHandlerElement);
+ return element.forget();
+ }
+
+ return nullptr;
+}
+
+void
+nsXBLPrototypeHandler::AppendHandlerText(const nsAString& aText)
+{
+ if (mHandlerText) {
+ // Append our text to the existing text.
+ char16_t* temp = mHandlerText;
+ mHandlerText = ToNewUnicode(nsDependentString(temp) + aText);
+ free(temp);
+ }
+ else {
+ mHandlerText = ToNewUnicode(aText);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Get the menu access key from prefs.
+// XXX Eventually pick up using CSS3 key-equivalent property or somesuch
+void
+nsXBLPrototypeHandler::InitAccessKeys()
+{
+ if (kMenuAccessKey >= 0) {
+ return;
+ }
+
+ // Compiled-in defaults, in case we can't get the pref --
+ // mac doesn't have menu shortcuts, other platforms use alt.
+#ifdef XP_MACOSX
+ kMenuAccessKey = 0;
+#else
+ kMenuAccessKey = nsIDOMKeyEvent::DOM_VK_ALT;
+#endif
+
+ // Get the menu access key value from prefs, overriding the default:
+ kMenuAccessKey =
+ Preferences::GetInt("ui.key.menuAccessKey", kMenuAccessKey);
+}
+
+nsresult
+nsXBLPrototypeHandler::ExecuteHandler(EventTarget* aTarget,
+ nsIDOMEvent* aEvent)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ // Prevent default action?
+ if (mType & NS_HANDLER_TYPE_PREVENTDEFAULT) {
+ aEvent->PreventDefault();
+ // If we prevent default, then it's okay for
+ // mHandlerElement and mHandlerText to be null
+ rv = NS_OK;
+ }
+
+ if (!mHandlerElement) // This works for both types of handlers. In both cases, the union's var should be defined.
+ return rv;
+
+ // See if our event receiver is a content node (and not us).
+ bool isXULKey = !!(mType & NS_HANDLER_TYPE_XUL);
+ bool isXBLCommand = !!(mType & NS_HANDLER_TYPE_XBL_COMMAND);
+ NS_ASSERTION(!(isXULKey && isXBLCommand),
+ "can't be both a key and xbl command handler");
+
+ // XUL handlers and commands shouldn't be triggered by non-trusted
+ // events.
+ if (isXULKey || isXBLCommand) {
+ bool trustedEvent = false;
+ aEvent->GetIsTrusted(&trustedEvent);
+
+ if (!trustedEvent)
+ return NS_OK;
+ }
+
+ if (isXBLCommand) {
+ return DispatchXBLCommand(aTarget, aEvent);
+ }
+
+ // If we're executing on a XUL key element, just dispatch a command
+ // event at the element. It will take care of retargeting it to its
+ // command element, if applicable, and executing the event handler.
+ if (isXULKey) {
+ return DispatchXULKeyCommand(aEvent);
+ }
+
+ // Look for a compiled handler on the element.
+ // Should be compiled and bound with "on" in front of the name.
+ nsCOMPtr<nsIAtom> onEventAtom = NS_Atomize(NS_LITERAL_STRING("onxbl") +
+ nsDependentAtomString(mEventName));
+
+ // Compile the handler and bind it to the element.
+ nsCOMPtr<nsIScriptGlobalObject> boundGlobal;
+ nsCOMPtr<nsPIWindowRoot> winRoot = do_QueryInterface(aTarget);
+ if (winRoot) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> window = winRoot->GetWindow()) {
+ nsPIDOMWindowInner* innerWindow = window->GetCurrentInnerWindow();
+ NS_ENSURE_TRUE(innerWindow, NS_ERROR_UNEXPECTED);
+
+ boundGlobal = do_QueryInterface(innerWindow->GetPrivateRoot());
+ }
+ }
+ else boundGlobal = do_QueryInterface(aTarget);
+
+ if (!boundGlobal) {
+ nsCOMPtr<nsIDocument> boundDocument(do_QueryInterface(aTarget));
+ if (!boundDocument) {
+ // We must be an element.
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aTarget));
+ if (!content)
+ return NS_OK;
+ boundDocument = content->OwnerDoc();
+ }
+
+ boundGlobal = do_QueryInterface(boundDocument->GetScopeObject());
+ }
+
+ if (!boundGlobal)
+ return NS_OK;
+
+ nsISupports *scriptTarget;
+
+ if (winRoot) {
+ scriptTarget = boundGlobal;
+ } else {
+ scriptTarget = aTarget;
+ }
+
+ // We're about to create a new JSEventHandler, which means that we need to
+ // Initiatize an AutoJSAPI with aTarget's bound global to make sure any errors
+ // are reported to the correct place.
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(boundGlobal))) {
+ return NS_OK;
+ }
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> handler(cx);
+
+ rv = EnsureEventHandler(jsapi, onEventAtom, &handler);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JSAddonId* addonId = MapURIToAddonID(mPrototypeBinding->DocURI());
+
+ JS::Rooted<JSObject*> globalObject(cx, boundGlobal->GetGlobalJSObject());
+ JS::Rooted<JSObject*> scopeObject(cx, xpc::GetScopeForXBLExecution(cx, globalObject, addonId));
+ NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
+
+ // Bind it to the bound element. Note that if we're using a separate XBL scope,
+ // we'll actually be binding the event handler to a cross-compartment wrapper
+ // to the bound element's reflector.
+
+ // First, enter our XBL scope. This is where the generic handler should have
+ // been compiled, above.
+ JSAutoCompartment ac(cx, scopeObject);
+ JS::Rooted<JSObject*> genericHandler(cx, handler.get());
+ bool ok = JS_WrapObject(cx, &genericHandler);
+ NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
+ MOZ_ASSERT(!js::IsCrossCompartmentWrapper(genericHandler));
+
+ // Build a scope chain in the XBL scope.
+ RefPtr<Element> targetElement = do_QueryObject(scriptTarget);
+ JS::AutoObjectVector scopeChain(cx);
+ ok = nsJSUtils::GetScopeChainForElement(cx, targetElement, scopeChain);
+ NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
+
+ // Next, clone the generic handler with our desired scope chain.
+ JS::Rooted<JSObject*> bound(cx, JS::CloneFunctionObject(cx, genericHandler,
+ scopeChain));
+ NS_ENSURE_TRUE(bound, NS_ERROR_FAILURE);
+
+ RefPtr<EventHandlerNonNull> handlerCallback =
+ new EventHandlerNonNull(nullptr, bound, /* aIncumbentGlobal = */ nullptr);
+
+ TypedEventHandler typedHandler(handlerCallback);
+
+ // Execute it.
+ nsCOMPtr<JSEventHandler> jsEventHandler;
+ rv = NS_NewJSEventHandler(scriptTarget, onEventAtom,
+ typedHandler,
+ getter_AddRefs(jsEventHandler));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Handle the event.
+ jsEventHandler->HandleEvent(aEvent);
+ jsEventHandler->Disconnect();
+ return NS_OK;
+}
+
+nsresult
+nsXBLPrototypeHandler::EnsureEventHandler(AutoJSAPI& jsapi, nsIAtom* aName,
+ JS::MutableHandle<JSObject*> aHandler)
+{
+ JSContext* cx = jsapi.cx();
+
+ // Check to see if we've already compiled this
+ JS::Rooted<JSObject*> globalObject(cx, JS::CurrentGlobalOrNull(cx));
+ nsCOMPtr<nsPIDOMWindowInner> pWindow = xpc::WindowOrNull(globalObject)->AsInner();
+ if (pWindow) {
+ JS::Rooted<JSObject*> cachedHandler(cx, pWindow->GetCachedXBLPrototypeHandler(this));
+ if (cachedHandler) {
+ JS::ExposeObjectToActiveJS(cachedHandler);
+ aHandler.set(cachedHandler);
+ NS_ENSURE_TRUE(aHandler, NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+ }
+
+ // Ensure that we have something to compile
+ nsDependentString handlerText(mHandlerText);
+ NS_ENSURE_TRUE(!handlerText.IsEmpty(), NS_ERROR_FAILURE);
+
+ JSAddonId* addonId = MapURIToAddonID(mPrototypeBinding->DocURI());
+
+ JS::Rooted<JSObject*> scopeObject(cx, xpc::GetScopeForXBLExecution(cx, globalObject, addonId));
+ NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
+
+ nsAutoCString bindingURI;
+ nsresult rv = mPrototypeBinding->DocURI()->GetSpec(bindingURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t argCount;
+ const char **argNames;
+ nsContentUtils::GetEventArgNames(kNameSpaceID_XBL, aName, false, &argCount,
+ &argNames);
+
+ // Compile the event handler in the xbl scope.
+ JSAutoCompartment ac(cx, scopeObject);
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(bindingURI.get(), mLineNumber)
+ .setVersion(JSVERSION_LATEST);
+
+ JS::Rooted<JSObject*> handlerFun(cx);
+ JS::AutoObjectVector emptyVector(cx);
+ rv = nsJSUtils::CompileFunction(jsapi, emptyVector, options,
+ nsAtomCString(aName), argCount,
+ argNames, handlerText,
+ handlerFun.address());
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(handlerFun, NS_ERROR_FAILURE);
+
+ // Wrap the handler into the content scope, since we're about to stash it
+ // on the DOM window and such.
+ JSAutoCompartment ac2(cx, globalObject);
+ bool ok = JS_WrapObject(cx, &handlerFun);
+ NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
+ aHandler.set(handlerFun);
+ NS_ENSURE_TRUE(aHandler, NS_ERROR_FAILURE);
+
+ if (pWindow) {
+ pWindow->CacheXBLPrototypeHandler(this, aHandler);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLPrototypeHandler::DispatchXBLCommand(EventTarget* aTarget, nsIDOMEvent* aEvent)
+{
+ // This is a special-case optimization to make command handling fast.
+ // It isn't really a part of XBL, but it helps speed things up.
+
+ if (aEvent) {
+ // See if preventDefault has been set. If so, don't execute.
+ bool preventDefault = false;
+ aEvent->GetDefaultPrevented(&preventDefault);
+ if (preventDefault) {
+ return NS_OK;
+ }
+ bool dispatchStopped = aEvent->IsDispatchStopped();
+ if (dispatchStopped) {
+ return NS_OK;
+ }
+ }
+
+ // Instead of executing JS, let's get the controller for the bound
+ // element and call doCommand on it.
+ nsCOMPtr<nsIController> controller;
+
+ nsCOMPtr<nsPIDOMWindowOuter> privateWindow;
+ nsCOMPtr<nsPIWindowRoot> windowRoot = do_QueryInterface(aTarget);
+ if (windowRoot) {
+ privateWindow = windowRoot->GetWindow();
+ }
+ else {
+ privateWindow = do_QueryInterface(aTarget);
+ if (!privateWindow) {
+ nsCOMPtr<nsIContent> elt(do_QueryInterface(aTarget));
+ nsCOMPtr<nsIDocument> doc;
+ // XXXbz sXBL/XBL2 issue -- this should be the "scope doc" or
+ // something... whatever we use when wrapping DOM nodes
+ // normally. It's not clear that the owner doc is the right
+ // thing.
+ if (elt)
+ doc = elt->OwnerDoc();
+
+ if (!doc)
+ doc = do_QueryInterface(aTarget);
+
+ if (!doc)
+ return NS_ERROR_FAILURE;
+
+ privateWindow = doc->GetWindow();
+ if (!privateWindow)
+ return NS_ERROR_FAILURE;
+ }
+
+ windowRoot = privateWindow->GetTopWindowRoot();
+ }
+
+ NS_LossyConvertUTF16toASCII command(mHandlerText);
+ if (windowRoot)
+ windowRoot->GetControllerForCommand(command.get(), getter_AddRefs(controller));
+ else
+ controller = GetController(aTarget); // We're attached to the receiver possibly.
+
+ // We are the default action for this command.
+ // Stop any other default action from executing.
+ aEvent->PreventDefault();
+
+ if (mEventName == nsGkAtoms::keypress &&
+ mDetail == nsIDOMKeyEvent::DOM_VK_SPACE &&
+ mMisc == 1) {
+ // get the focused element so that we can pageDown only at
+ // certain times.
+
+ nsCOMPtr<nsPIDOMWindowOuter> windowToCheck;
+ if (windowRoot)
+ windowToCheck = windowRoot->GetWindow();
+ else
+ windowToCheck = privateWindow->GetPrivateRoot();
+
+ nsCOMPtr<nsIContent> focusedContent;
+ if (windowToCheck) {
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ focusedContent =
+ nsFocusManager::GetFocusedDescendant(windowToCheck, true, getter_AddRefs(focusedWindow));
+ }
+
+ // If the focus is in an editable region, don't scroll.
+ if (focusedContent && focusedContent->IsEditable()) {
+ return NS_OK;
+ }
+
+ // If the focus is in a form control, don't scroll.
+ for (nsIContent* c = focusedContent; c; c = c->GetParent()) {
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(c);
+ if (formControl) {
+ return NS_OK;
+ }
+ }
+ }
+
+ if (controller)
+ controller->DoCommand(command.get());
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLPrototypeHandler::DispatchXULKeyCommand(nsIDOMEvent* aEvent)
+{
+ nsCOMPtr<nsIContent> handlerElement = GetHandlerElement();
+ NS_ENSURE_STATE(handlerElement);
+ if (handlerElement->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::disabled,
+ nsGkAtoms::_true,
+ eCaseMatters)) {
+ // Don't dispatch command events for disabled keys.
+ return NS_OK;
+ }
+
+ aEvent->PreventDefault();
+
+ // Copy the modifiers from the key event.
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
+ if (!keyEvent) {
+ NS_ERROR("Trying to execute a key handler for a non-key event!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // XXX We should use mozilla::Modifiers for supporting all modifiers.
+
+ bool isAlt = false;
+ bool isControl = false;
+ bool isShift = false;
+ bool isMeta = false;
+ keyEvent->GetAltKey(&isAlt);
+ keyEvent->GetCtrlKey(&isControl);
+ keyEvent->GetShiftKey(&isShift);
+ keyEvent->GetMetaKey(&isMeta);
+
+ nsContentUtils::DispatchXULCommand(handlerElement, true,
+ nullptr, nullptr,
+ isControl, isAlt, isShift, isMeta);
+ return NS_OK;
+}
+
+already_AddRefed<nsIAtom>
+nsXBLPrototypeHandler::GetEventName()
+{
+ nsCOMPtr<nsIAtom> eventName = mEventName;
+ return eventName.forget();
+}
+
+already_AddRefed<nsIController>
+nsXBLPrototypeHandler::GetController(EventTarget* aTarget)
+{
+ // XXX Fix this so there's a generic interface that describes controllers,
+ // This code should have no special knowledge of what objects might have controllers.
+ nsCOMPtr<nsIControllers> controllers;
+
+ nsCOMPtr<nsIDOMXULElement> xulElement(do_QueryInterface(aTarget));
+ if (xulElement)
+ xulElement->GetControllers(getter_AddRefs(controllers));
+
+ if (!controllers) {
+ nsCOMPtr<nsIDOMHTMLTextAreaElement> htmlTextArea(do_QueryInterface(aTarget));
+ if (htmlTextArea)
+ htmlTextArea->GetControllers(getter_AddRefs(controllers));
+ }
+
+ if (!controllers) {
+ nsCOMPtr<nsIDOMHTMLInputElement> htmlInputElement(do_QueryInterface(aTarget));
+ if (htmlInputElement)
+ htmlInputElement->GetControllers(getter_AddRefs(controllers));
+ }
+
+ if (!controllers) {
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow(do_QueryInterface(aTarget));
+ if (domWindow) {
+ domWindow->GetControllers(getter_AddRefs(controllers));
+ }
+ }
+
+ // Return the first controller.
+ // XXX This code should be checking the command name and using supportscommand and
+ // iscommandenabled.
+ nsCOMPtr<nsIController> controller;
+ if (controllers) {
+ controllers->GetControllerAt(0, getter_AddRefs(controller));
+ }
+
+ return controller.forget();
+}
+
+bool
+nsXBLPrototypeHandler::KeyEventMatched(
+ nsIDOMKeyEvent* aKeyEvent,
+ uint32_t aCharCode,
+ const IgnoreModifierState& aIgnoreModifierState)
+{
+ if (mDetail != -1) {
+ // Get the keycode or charcode of the key event.
+ uint32_t code;
+
+ if (mMisc) {
+ if (aCharCode)
+ code = aCharCode;
+ else
+ aKeyEvent->GetCharCode(&code);
+ if (IS_IN_BMP(code))
+ code = ToLowerCase(char16_t(code));
+ }
+ else
+ aKeyEvent->GetKeyCode(&code);
+
+ if (code != uint32_t(mDetail))
+ return false;
+ }
+
+ return ModifiersMatchMask(aKeyEvent, aIgnoreModifierState);
+}
+
+bool
+nsXBLPrototypeHandler::MouseEventMatched(nsIDOMMouseEvent* aMouseEvent)
+{
+ if (mDetail == -1 && mMisc == 0 && (mKeyMask & cAllModifiers) == 0)
+ return true; // No filters set up. It's generic.
+
+ int16_t button;
+ aMouseEvent->GetButton(&button);
+ if (mDetail != -1 && (button != mDetail))
+ return false;
+
+ int32_t clickcount;
+ aMouseEvent->GetDetail(&clickcount);
+ if (mMisc != 0 && (clickcount != mMisc))
+ return false;
+
+ return ModifiersMatchMask(aMouseEvent, IgnoreModifierState());
+}
+
+struct keyCodeData {
+ const char* str;
+ uint16_t strlength;
+ uint16_t keycode;
+};
+
+// All of these must be uppercase, since the function below does
+// case-insensitive comparison by converting to uppercase.
+// XXX: be sure to check this periodically for new symbol additions!
+static const keyCodeData gKeyCodes[] = {
+
+#define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \
+ { #aDOMKeyName, sizeof(#aDOMKeyName) - 1, aDOMKeyCode },
+#include "mozilla/VirtualKeyCodeList.h"
+#undef NS_DEFINE_VK
+
+ { nullptr, 0, 0 }
+};
+
+int32_t nsXBLPrototypeHandler::GetMatchingKeyCode(const nsAString& aKeyName)
+{
+ nsAutoCString keyName;
+ keyName.AssignWithConversion(aKeyName);
+ ToUpperCase(keyName); // We want case-insensitive comparison with data
+ // stored as uppercase.
+
+ uint32_t keyNameLength = keyName.Length();
+ const char* keyNameStr = keyName.get();
+ for (uint16_t i = 0; i < ArrayLength(gKeyCodes) - 1; ++i) {
+ if (keyNameLength == gKeyCodes[i].strlength &&
+ !nsCRT::strcmp(gKeyCodes[i].str, keyNameStr)) {
+ return gKeyCodes[i].keycode;
+ }
+ }
+
+ return 0;
+}
+
+int32_t nsXBLPrototypeHandler::KeyToMask(int32_t key)
+{
+ switch (key)
+ {
+ case nsIDOMKeyEvent::DOM_VK_META:
+ return cMeta | cMetaMask;
+
+ case nsIDOMKeyEvent::DOM_VK_WIN:
+ return cOS | cOSMask;
+
+ case nsIDOMKeyEvent::DOM_VK_ALT:
+ return cAlt | cAltMask;
+
+ case nsIDOMKeyEvent::DOM_VK_CONTROL:
+ default:
+ return cControl | cControlMask;
+ }
+ return cControl | cControlMask; // for warning avoidance
+}
+
+// static
+int32_t
+nsXBLPrototypeHandler::AccelKeyMask()
+{
+ switch (WidgetInputEvent::AccelModifier()) {
+ case MODIFIER_ALT:
+ return KeyToMask(nsIDOMKeyEvent::DOM_VK_ALT);
+ case MODIFIER_CONTROL:
+ return KeyToMask(nsIDOMKeyEvent::DOM_VK_CONTROL);
+ case MODIFIER_META:
+ return KeyToMask(nsIDOMKeyEvent::DOM_VK_META);
+ case MODIFIER_OS:
+ return KeyToMask(nsIDOMKeyEvent::DOM_VK_WIN);
+ default:
+ MOZ_CRASH("Handle the new result of WidgetInputEvent::AccelModifier()");
+ return 0;
+ }
+}
+
+void
+nsXBLPrototypeHandler::GetEventType(nsAString& aEvent)
+{
+ nsCOMPtr<nsIContent> handlerElement = GetHandlerElement();
+ if (!handlerElement) {
+ aEvent.Truncate();
+ return;
+ }
+ handlerElement->GetAttr(kNameSpaceID_None, nsGkAtoms::event, aEvent);
+
+ if (aEvent.IsEmpty() && (mType & NS_HANDLER_TYPE_XUL))
+ // If no type is specified for a XUL <key> element, let's assume that we're "keypress".
+ aEvent.AssignLiteral("keypress");
+}
+
+void
+nsXBLPrototypeHandler::ConstructPrototype(nsIContent* aKeyElement,
+ const char16_t* aEvent,
+ const char16_t* aPhase,
+ const char16_t* aAction,
+ const char16_t* aCommand,
+ const char16_t* aKeyCode,
+ const char16_t* aCharCode,
+ const char16_t* aModifiers,
+ const char16_t* aButton,
+ const char16_t* aClickCount,
+ const char16_t* aGroup,
+ const char16_t* aPreventDefault,
+ const char16_t* aAllowUntrusted)
+{
+ mType = 0;
+
+ if (aKeyElement) {
+ mType |= NS_HANDLER_TYPE_XUL;
+ MOZ_ASSERT(!mPrototypeBinding);
+ nsCOMPtr<nsIWeakReference> weak = do_GetWeakReference(aKeyElement);
+ if (!weak) {
+ return;
+ }
+ weak.swap(mHandlerElement);
+ }
+ else {
+ mType |= aCommand ? NS_HANDLER_TYPE_XBL_COMMAND : NS_HANDLER_TYPE_XBL_JS;
+ mHandlerText = nullptr;
+ }
+
+ mDetail = -1;
+ mMisc = 0;
+ mKeyMask = 0;
+ mPhase = NS_PHASE_BUBBLING;
+
+ if (aAction)
+ mHandlerText = ToNewUnicode(nsDependentString(aAction));
+ else if (aCommand)
+ mHandlerText = ToNewUnicode(nsDependentString(aCommand));
+
+ nsAutoString event(aEvent);
+ if (event.IsEmpty()) {
+ if (mType & NS_HANDLER_TYPE_XUL)
+ GetEventType(event);
+ if (event.IsEmpty())
+ return;
+ }
+
+ mEventName = NS_Atomize(event);
+
+ if (aPhase) {
+ const nsDependentString phase(aPhase);
+ if (phase.EqualsLiteral("capturing"))
+ mPhase = NS_PHASE_CAPTURING;
+ else if (phase.EqualsLiteral("target"))
+ mPhase = NS_PHASE_TARGET;
+ }
+
+ // Button and clickcount apply only to XBL handlers and don't apply to XUL key
+ // handlers.
+ if (aButton && *aButton)
+ mDetail = *aButton - '0';
+
+ if (aClickCount && *aClickCount)
+ mMisc = *aClickCount - '0';
+
+ // Modifiers are supported by both types of handlers (XUL and XBL).
+ nsAutoString modifiers(aModifiers);
+ if (mType & NS_HANDLER_TYPE_XUL)
+ aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers);
+
+ if (!modifiers.IsEmpty()) {
+ mKeyMask = cAllModifiers;
+ char* str = ToNewCString(modifiers);
+ char* newStr;
+ char* token = nsCRT::strtok( str, ", \t", &newStr );
+ while( token != nullptr ) {
+ if (PL_strcmp(token, "shift") == 0)
+ mKeyMask |= cShift | cShiftMask;
+ else if (PL_strcmp(token, "alt") == 0)
+ mKeyMask |= cAlt | cAltMask;
+ else if (PL_strcmp(token, "meta") == 0)
+ mKeyMask |= cMeta | cMetaMask;
+ else if (PL_strcmp(token, "os") == 0)
+ mKeyMask |= cOS | cOSMask;
+ else if (PL_strcmp(token, "control") == 0)
+ mKeyMask |= cControl | cControlMask;
+ else if (PL_strcmp(token, "accel") == 0)
+ mKeyMask |= AccelKeyMask();
+ else if (PL_strcmp(token, "access") == 0)
+ mKeyMask |= KeyToMask(kMenuAccessKey);
+ else if (PL_strcmp(token, "any") == 0)
+ mKeyMask &= ~(mKeyMask << 5);
+
+ token = nsCRT::strtok( newStr, ", \t", &newStr );
+ }
+
+ free(str);
+ }
+
+ nsAutoString key(aCharCode);
+ if (key.IsEmpty()) {
+ if (mType & NS_HANDLER_TYPE_XUL) {
+ aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key);
+ if (key.IsEmpty())
+ aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::charcode, key);
+ }
+ }
+
+ if (!key.IsEmpty()) {
+ if (mKeyMask == 0)
+ mKeyMask = cAllModifiers;
+ ToLowerCase(key);
+
+ // We have a charcode.
+ mMisc = 1;
+ mDetail = key[0];
+ const uint8_t GTK2Modifiers = cShift | cControl | cShiftMask | cControlMask;
+ if ((mType & NS_HANDLER_TYPE_XUL) &&
+ (mKeyMask & GTK2Modifiers) == GTK2Modifiers &&
+ modifiers.First() != char16_t(',') &&
+ (mDetail == 'u' || mDetail == 'U'))
+ ReportKeyConflict(key.get(), modifiers.get(), aKeyElement, "GTK2Conflict2");
+ const uint8_t WinModifiers = cControl | cAlt | cControlMask | cAltMask;
+ if ((mType & NS_HANDLER_TYPE_XUL) &&
+ (mKeyMask & WinModifiers) == WinModifiers &&
+ modifiers.First() != char16_t(',') &&
+ (('A' <= mDetail && mDetail <= 'Z') ||
+ ('a' <= mDetail && mDetail <= 'z')))
+ ReportKeyConflict(key.get(), modifiers.get(), aKeyElement, "WinConflict2");
+ }
+ else {
+ key.Assign(aKeyCode);
+ if (mType & NS_HANDLER_TYPE_XUL)
+ aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, key);
+
+ if (!key.IsEmpty()) {
+ if (mKeyMask == 0)
+ mKeyMask = cAllModifiers;
+ mDetail = GetMatchingKeyCode(key);
+ }
+ }
+
+ if (aGroup && nsDependentString(aGroup).EqualsLiteral("system"))
+ mType |= NS_HANDLER_TYPE_SYSTEM;
+
+ if (aPreventDefault &&
+ nsDependentString(aPreventDefault).EqualsLiteral("true"))
+ mType |= NS_HANDLER_TYPE_PREVENTDEFAULT;
+
+ if (aAllowUntrusted) {
+ mType |= NS_HANDLER_HAS_ALLOW_UNTRUSTED_ATTR;
+ if (nsDependentString(aAllowUntrusted).EqualsLiteral("true")) {
+ mType |= NS_HANDLER_ALLOW_UNTRUSTED;
+ } else {
+ mType &= ~NS_HANDLER_ALLOW_UNTRUSTED;
+ }
+ }
+}
+
+void
+nsXBLPrototypeHandler::ReportKeyConflict(const char16_t* aKey, const char16_t* aModifiers, nsIContent* aKeyElement, const char *aMessageName)
+{
+ nsCOMPtr<nsIDocument> doc;
+ if (mPrototypeBinding) {
+ nsXBLDocumentInfo* docInfo = mPrototypeBinding->XBLDocumentInfo();
+ if (docInfo) {
+ doc = docInfo->GetDocument();
+ }
+ } else {
+ doc = aKeyElement->OwnerDoc();
+ }
+
+ nsAutoString id;
+ aKeyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
+ const char16_t* params[] = { aKey, aModifiers, id.get() };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("XBL Prototype Handler"), doc,
+ nsContentUtils::eXBL_PROPERTIES,
+ aMessageName,
+ params, ArrayLength(params),
+ nullptr, EmptyString(), mLineNumber);
+}
+
+bool
+nsXBLPrototypeHandler::ModifiersMatchMask(
+ nsIDOMUIEvent* aEvent,
+ const IgnoreModifierState& aIgnoreModifierState)
+{
+ WidgetInputEvent* inputEvent = aEvent->AsEvent()->WidgetEventPtr()->AsInputEvent();
+ NS_ENSURE_TRUE(inputEvent, false);
+
+ if (mKeyMask & cMetaMask) {
+ if (inputEvent->IsMeta() != ((mKeyMask & cMeta) != 0)) {
+ return false;
+ }
+ }
+
+ if ((mKeyMask & cOSMask) && !aIgnoreModifierState.mOS) {
+ if (inputEvent->IsOS() != ((mKeyMask & cOS) != 0)) {
+ return false;
+ }
+ }
+
+ if (mKeyMask & cShiftMask && !aIgnoreModifierState.mShift) {
+ if (inputEvent->IsShift() != ((mKeyMask & cShift) != 0)) {
+ return false;
+ }
+ }
+
+ if (mKeyMask & cAltMask) {
+ if (inputEvent->IsAlt() != ((mKeyMask & cAlt) != 0)) {
+ return false;
+ }
+ }
+
+ if (mKeyMask & cControlMask) {
+ if (inputEvent->IsControl() != ((mKeyMask & cControl) != 0)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsresult
+nsXBLPrototypeHandler::Read(nsIObjectInputStream* aStream)
+{
+ AssertInCompilationScope();
+ nsresult rv = aStream->Read8(&mPhase);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStream->Read8(&mType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStream->Read8(&mMisc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->Read32(reinterpret_cast<uint32_t*>(&mKeyMask));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t detail;
+ rv = aStream->Read32(&detail);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mDetail = detail;
+
+ nsAutoString name;
+ rv = aStream->ReadString(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mEventName = NS_Atomize(name);
+
+ rv = aStream->Read32(&mLineNumber);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString handlerText;
+ rv = aStream->ReadString(handlerText);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!handlerText.IsEmpty())
+ mHandlerText = ToNewUnicode(handlerText);
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLPrototypeHandler::Write(nsIObjectOutputStream* aStream)
+{
+ AssertInCompilationScope();
+ // Make sure we don't write out NS_HANDLER_TYPE_XUL types, as they are used
+ // for <keyset> elements.
+ if ((mType & NS_HANDLER_TYPE_XUL) || !mEventName)
+ return NS_OK;
+
+ XBLBindingSerializeDetails type = XBLBinding_Serialize_Handler;
+
+ nsresult rv = aStream->Write8(type);
+ rv = aStream->Write8(mPhase);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStream->Write8(mType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStream->Write8(mMisc);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStream->Write32(static_cast<uint32_t>(mKeyMask));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStream->Write32(mDetail);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->WriteWStringZ(nsDependentAtomString(mEventName).get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->Write32(mLineNumber);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return aStream->WriteWStringZ(mHandlerText ? mHandlerText : u"");
+}
diff --git a/dom/xbl/nsXBLPrototypeHandler.h b/dom/xbl/nsXBLPrototypeHandler.h
new file mode 100644
index 000000000..6898b73ed
--- /dev/null
+++ b/dom/xbl/nsXBLPrototypeHandler.h
@@ -0,0 +1,243 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLPrototypeHandler_h__
+#define nsXBLPrototypeHandler_h__
+
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIController.h"
+#include "nsAutoPtr.h"
+#include "nsXBLEventHandler.h"
+#include "nsIWeakReference.h"
+#include "nsCycleCollectionParticipant.h"
+#include "js/TypeDecls.h"
+
+class nsIDOMEvent;
+class nsIContent;
+class nsIDOMUIEvent;
+class nsIDOMKeyEvent;
+class nsIDOMMouseEvent;
+class nsIObjectInputStream;
+class nsIObjectOutputStream;
+class nsXBLPrototypeBinding;
+
+namespace mozilla {
+namespace dom {
+class AutoJSAPI;
+class EventTarget;
+} // namespace dom
+} // namespace mozilla
+
+#define NS_HANDLER_TYPE_XBL_JS (1 << 0)
+#define NS_HANDLER_TYPE_XBL_COMMAND (1 << 1)
+#define NS_HANDLER_TYPE_XUL (1 << 2)
+#define NS_HANDLER_HAS_ALLOW_UNTRUSTED_ATTR (1 << 4)
+#define NS_HANDLER_ALLOW_UNTRUSTED (1 << 5)
+#define NS_HANDLER_TYPE_SYSTEM (1 << 6)
+#define NS_HANDLER_TYPE_PREVENTDEFAULT (1 << 7)
+
+// XXX Use nsIDOMEvent:: codes?
+#define NS_PHASE_CAPTURING 1
+#define NS_PHASE_TARGET 2
+#define NS_PHASE_BUBBLING 3
+
+namespace mozilla {
+namespace dom {
+
+struct IgnoreModifierState
+{
+ // When mShift is true, Shift key state will be ignored.
+ bool mShift;
+ // When mOS is true, OS key state will be ignored.
+ bool mOS;
+
+ IgnoreModifierState()
+ : mShift(false)
+ , mOS(false)
+ {
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+class nsXBLPrototypeHandler
+{
+ typedef mozilla::dom::IgnoreModifierState IgnoreModifierState;
+
+public:
+ // This constructor is used by XBL handlers (both the JS and command shorthand variety)
+ nsXBLPrototypeHandler(const char16_t* aEvent, const char16_t* aPhase,
+ const char16_t* aAction, const char16_t* aCommand,
+ const char16_t* aKeyCode, const char16_t* aCharCode,
+ const char16_t* aModifiers, const char16_t* aButton,
+ const char16_t* aClickCount, const char16_t* aGroup,
+ const char16_t* aPreventDefault,
+ const char16_t* aAllowUntrusted,
+ nsXBLPrototypeBinding* aBinding,
+ uint32_t aLineNumber);
+
+ // This constructor is used only by XUL key handlers (e.g., <key>)
+ explicit nsXBLPrototypeHandler(nsIContent* aKeyElement);
+
+ // This constructor is used for handlers loaded from the cache
+ explicit nsXBLPrototypeHandler(nsXBLPrototypeBinding* aBinding);
+
+ ~nsXBLPrototypeHandler();
+
+ bool EventTypeEquals(nsIAtom* aEventType) const
+ {
+ return mEventName == aEventType;
+ }
+
+ // if aCharCode is not zero, it is used instead of the charCode of aKeyEvent.
+ bool KeyEventMatched(nsIDOMKeyEvent* aKeyEvent,
+ uint32_t aCharCode,
+ const IgnoreModifierState& aIgnoreModifierState);
+
+ bool MouseEventMatched(nsIDOMMouseEvent* aMouseEvent);
+ inline bool MouseEventMatched(nsIAtom* aEventType,
+ nsIDOMMouseEvent* aEvent)
+ {
+ if (!EventTypeEquals(aEventType)) {
+ return false;
+ }
+ return MouseEventMatched(aEvent);
+ }
+
+ already_AddRefed<nsIContent> GetHandlerElement();
+
+ void AppendHandlerText(const nsAString& aText);
+
+ uint8_t GetPhase() { return mPhase; }
+ uint8_t GetType() { return mType; }
+
+ nsXBLPrototypeHandler* GetNextHandler() { return mNextHandler; }
+ void SetNextHandler(nsXBLPrototypeHandler* aHandler) { mNextHandler = aHandler; }
+
+ nsresult ExecuteHandler(mozilla::dom::EventTarget* aTarget, nsIDOMEvent* aEvent);
+
+ already_AddRefed<nsIAtom> GetEventName();
+ void SetEventName(nsIAtom* aName) { mEventName = aName; }
+
+ nsXBLEventHandler* GetEventHandler()
+ {
+ if (!mHandler) {
+ mHandler = NS_NewXBLEventHandler(this, mEventName);
+ }
+
+ return mHandler;
+ }
+
+ nsXBLEventHandler* GetCachedEventHandler()
+ {
+ return mHandler;
+ }
+
+ bool HasAllowUntrustedAttr()
+ {
+ return (mType & NS_HANDLER_HAS_ALLOW_UNTRUSTED_ATTR) != 0;
+ }
+
+ // This returns a valid value only if HasAllowUntrustedEventsAttr returns
+ // true.
+ bool AllowUntrustedEvents()
+ {
+ return (mType & NS_HANDLER_ALLOW_UNTRUSTED) != 0;
+ }
+
+ nsresult Read(nsIObjectInputStream* aStream);
+ nsresult Write(nsIObjectOutputStream* aStream);
+
+public:
+ static uint32_t gRefCnt;
+
+protected:
+ void Init() {
+ ++gRefCnt;
+ if (gRefCnt == 1)
+ // Get the primary accelerator key.
+ InitAccessKeys();
+ }
+
+ already_AddRefed<nsIController> GetController(mozilla::dom::EventTarget* aTarget);
+
+ inline int32_t GetMatchingKeyCode(const nsAString& aKeyName);
+ void ConstructPrototype(nsIContent* aKeyElement,
+ const char16_t* aEvent=nullptr, const char16_t* aPhase=nullptr,
+ const char16_t* aAction=nullptr, const char16_t* aCommand=nullptr,
+ const char16_t* aKeyCode=nullptr, const char16_t* aCharCode=nullptr,
+ const char16_t* aModifiers=nullptr, const char16_t* aButton=nullptr,
+ const char16_t* aClickCount=nullptr, const char16_t* aGroup=nullptr,
+ const char16_t* aPreventDefault=nullptr,
+ const char16_t* aAllowUntrusted=nullptr);
+
+ void ReportKeyConflict(const char16_t* aKey, const char16_t* aModifiers, nsIContent* aElement, const char *aMessageName);
+ void GetEventType(nsAString& type);
+ bool ModifiersMatchMask(nsIDOMUIEvent* aEvent,
+ const IgnoreModifierState& aIgnoreModifierState);
+ nsresult DispatchXBLCommand(mozilla::dom::EventTarget* aTarget, nsIDOMEvent* aEvent);
+ nsresult DispatchXULKeyCommand(nsIDOMEvent* aEvent);
+ nsresult EnsureEventHandler(mozilla::dom::AutoJSAPI& jsapi, nsIAtom* aName,
+ JS::MutableHandle<JSObject*> aHandler);
+ static int32_t KeyToMask(int32_t key);
+ static int32_t AccelKeyMask();
+
+ static int32_t kMenuAccessKey;
+ static void InitAccessKeys();
+
+ static const int32_t cShift;
+ static const int32_t cAlt;
+ static const int32_t cControl;
+ static const int32_t cMeta;
+ static const int32_t cOS;
+
+ static const int32_t cShiftMask;
+ static const int32_t cAltMask;
+ static const int32_t cControlMask;
+ static const int32_t cMetaMask;
+ static const int32_t cOSMask;
+
+ static const int32_t cAllModifiers;
+
+protected:
+ union {
+ nsIWeakReference* mHandlerElement; // For XUL <key> element handlers. [STRONG]
+ char16_t* mHandlerText; // For XBL handlers (we don't build an
+ // element for the <handler>, and instead
+ // we cache the JS text or command name
+ // that we should use.
+ };
+
+ uint32_t mLineNumber; // The line number we started at in the XBL file
+
+ // The following four values make up 32 bits.
+ uint8_t mPhase; // The phase (capturing, bubbling)
+ uint8_t mType; // The type of the handler. The handler is either a XUL key
+ // handler, an XBL "command" event, or a normal XBL event with
+ // accompanying JavaScript. The high bit is used to indicate
+ // whether this handler should prevent the default action.
+ uint8_t mMisc; // Miscellaneous extra information. For key events,
+ // stores whether or not we're a key code or char code.
+ // For mouse events, stores the clickCount.
+
+ int32_t mKeyMask; // Which modifier keys this event handler expects to have down
+ // in order to be matched.
+
+ // The primary filter information for mouse/key events.
+ int32_t mDetail; // For key events, contains a charcode or keycode. For
+ // mouse events, stores the button info.
+
+ // Prototype handlers are chained. We own the next handler in the chain.
+ nsXBLPrototypeHandler* mNextHandler;
+ nsCOMPtr<nsIAtom> mEventName; // The type of the event, e.g., "keypress"
+ RefPtr<nsXBLEventHandler> mHandler;
+ nsXBLPrototypeBinding* mPrototypeBinding; // the binding owns us
+};
+
+#endif
diff --git a/dom/xbl/nsXBLPrototypeResources.cpp b/dom/xbl/nsXBLPrototypeResources.cpp
new file mode 100644
index 000000000..a1ec42663
--- /dev/null
+++ b/dom/xbl/nsXBLPrototypeResources.cpp
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStyleRuleProcessor.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsIServiceManager.h"
+#include "nsXBLResourceLoader.h"
+#include "nsXBLPrototypeResources.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsIDocumentObserver.h"
+#include "mozilla/css/Loader.h"
+#include "nsIURI.h"
+#include "nsLayoutCID.h"
+#include "nsCSSRuleProcessor.h"
+#include "nsStyleSet.h"
+#include "mozilla/dom/URL.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+
+using namespace mozilla;
+using mozilla::dom::IsChromeURI;
+
+nsXBLPrototypeResources::nsXBLPrototypeResources(nsXBLPrototypeBinding* aBinding)
+{
+ MOZ_COUNT_CTOR(nsXBLPrototypeResources);
+
+ mLoader = new nsXBLResourceLoader(aBinding, this);
+}
+
+nsXBLPrototypeResources::~nsXBLPrototypeResources()
+{
+ MOZ_COUNT_DTOR(nsXBLPrototypeResources);
+ if (mLoader) {
+ mLoader->mResources = nullptr;
+ }
+}
+
+void
+nsXBLPrototypeResources::AddResource(nsIAtom* aResourceType, const nsAString& aSrc)
+{
+ if (mLoader)
+ mLoader->AddResource(aResourceType, aSrc);
+}
+
+void
+nsXBLPrototypeResources::LoadResources(bool* aResult)
+{
+ if (mLoader)
+ mLoader->LoadResources(aResult);
+ else
+ *aResult = true; // All resources loaded.
+}
+
+void
+nsXBLPrototypeResources::AddResourceListener(nsIContent* aBoundElement)
+{
+ if (mLoader)
+ mLoader->AddResourceListener(aBoundElement);
+}
+
+nsresult
+nsXBLPrototypeResources::FlushSkinSheets()
+{
+ if (mStyleSheetList.Length() == 0)
+ return NS_OK;
+
+ nsCOMPtr<nsIDocument> doc =
+ mLoader->mBinding->XBLDocumentInfo()->GetDocument();
+
+ // If doc is null, we're in the process of tearing things down, so just
+ // return without rebuilding anything.
+ if (!doc) {
+ return NS_OK;
+ }
+
+ // We have scoped stylesheets. Reload any chrome stylesheets we
+ // encounter. (If they aren't skin sheets, it doesn't matter, since
+ // they'll still be in the chrome cache. Skip inline sheets, which
+ // skin sheets can't be, and which in any case don't have a usable
+ // URL to reload.)
+
+ nsTArray<RefPtr<StyleSheet>> oldSheets;
+
+ oldSheets.SwapElements(mStyleSheetList);
+
+ mozilla::css::Loader* cssLoader = doc->CSSLoader();
+
+ for (size_t i = 0, count = oldSheets.Length(); i < count; ++i) {
+ StyleSheet* oldSheet = oldSheets[i];
+
+ nsIURI* uri = oldSheet->GetSheetURI();
+
+ RefPtr<StyleSheet> newSheet;
+ if (!oldSheet->IsInline() && IsChromeURI(uri)) {
+ if (NS_FAILED(cssLoader->LoadSheetSync(uri, &newSheet)))
+ continue;
+ }
+ else {
+ newSheet = oldSheet;
+ }
+
+ mStyleSheetList.AppendElement(newSheet);
+ }
+
+ GatherRuleProcessor();
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLPrototypeResources::Write(nsIObjectOutputStream* aStream)
+{
+ if (mLoader)
+ return mLoader->Write(aStream);
+ return NS_OK;
+}
+
+void
+nsXBLPrototypeResources::Traverse(nsCycleCollectionTraversalCallback &cb)
+{
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "proto mResources mLoader");
+ cb.NoteXPCOMChild(mLoader);
+
+ CycleCollectionNoteChild(cb, mRuleProcessor.get(), "mRuleProcessor");
+ ImplCycleCollectionTraverse(cb, mStyleSheetList, "mStyleSheetList");
+}
+
+void
+nsXBLPrototypeResources::Unlink()
+{
+ mStyleSheetList.Clear();
+ mRuleProcessor = nullptr;
+}
+
+void
+nsXBLPrototypeResources::ClearLoader()
+{
+ mLoader = nullptr;
+}
+
+void
+nsXBLPrototypeResources::GatherRuleProcessor()
+{
+ nsTArray<RefPtr<CSSStyleSheet>> sheets(mStyleSheetList.Length());
+ for (StyleSheet* sheet : mStyleSheetList) {
+ MOZ_ASSERT(sheet->IsGecko(),
+ "GatherRuleProcessor must only be called for "
+ "nsXBLPrototypeResources objects with Gecko-flavored style "
+ "backends");
+ sheets.AppendElement(sheet->AsGecko());
+ }
+ mRuleProcessor = new nsCSSRuleProcessor(Move(sheets),
+ SheetType::Doc,
+ nullptr,
+ mRuleProcessor);
+}
+
+void
+nsXBLPrototypeResources::AppendStyleSheet(StyleSheet* aSheet)
+{
+ mStyleSheetList.AppendElement(aSheet);
+}
+
+void
+nsXBLPrototypeResources::RemoveStyleSheet(StyleSheet* aSheet)
+{
+ mStyleSheetList.RemoveElement(aSheet);
+}
+
+void
+nsXBLPrototypeResources::InsertStyleSheetAt(size_t aIndex, StyleSheet* aSheet)
+{
+ mStyleSheetList.InsertElementAt(aIndex, aSheet);
+}
+
+StyleSheet*
+nsXBLPrototypeResources::StyleSheetAt(size_t aIndex) const
+{
+ return mStyleSheetList[aIndex];
+}
+
+size_t
+nsXBLPrototypeResources::SheetCount() const
+{
+ return mStyleSheetList.Length();
+}
+
+bool
+nsXBLPrototypeResources::HasStyleSheets() const
+{
+ return !mStyleSheetList.IsEmpty();
+}
+
+void
+nsXBLPrototypeResources::AppendStyleSheetsTo(
+ nsTArray<StyleSheet*>& aResult) const
+{
+ aResult.AppendElements(mStyleSheetList);
+}
diff --git a/dom/xbl/nsXBLPrototypeResources.h b/dom/xbl/nsXBLPrototypeResources.h
new file mode 100644
index 000000000..5ac1923ac
--- /dev/null
+++ b/dom/xbl/nsXBLPrototypeResources.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLPrototypeResources_h__
+#define nsXBLPrototypeResources_h__
+
+#include "mozilla/StyleSheet.h"
+#include "nsICSSLoaderObserver.h"
+
+class nsCSSRuleProcessor;
+class nsIAtom;
+class nsIContent;
+class nsXBLPrototypeBinding;
+class nsXBLResourceLoader;
+
+namespace mozilla {
+class CSSStyleSheet;
+} // namespace mozilla
+
+// *********************************************************************/
+// The XBLPrototypeResources class
+
+class nsXBLPrototypeResources
+{
+public:
+ explicit nsXBLPrototypeResources(nsXBLPrototypeBinding* aBinding);
+ ~nsXBLPrototypeResources();
+
+ void LoadResources(bool* aResult);
+ void AddResource(nsIAtom* aResourceType, const nsAString& aSrc);
+ void AddResourceListener(nsIContent* aElement);
+ nsresult FlushSkinSheets();
+
+ nsresult Write(nsIObjectOutputStream* aStream);
+
+ void Traverse(nsCycleCollectionTraversalCallback &cb);
+ void Unlink();
+
+ void ClearLoader();
+
+ void AppendStyleSheet(mozilla::StyleSheet* aSheet);
+ void RemoveStyleSheet(mozilla::StyleSheet* aSheet);
+ void InsertStyleSheetAt(size_t aIndex, mozilla::StyleSheet* aSheet);
+ mozilla::StyleSheet* StyleSheetAt(size_t aIndex) const;
+ size_t SheetCount() const;
+ bool HasStyleSheets() const;
+ void AppendStyleSheetsTo(nsTArray<mozilla::StyleSheet*>& aResult) const;
+
+ /**
+ * Recreates mRuleProcessor to represent the current list of style sheets
+ * stored in mStyleSheetList. (Named GatherRuleProcessor to parallel
+ * nsStyleSet::GatherRuleProcessors.)
+ */
+ void GatherRuleProcessor();
+
+ nsCSSRuleProcessor* GetRuleProcessor() const { return mRuleProcessor; }
+
+private:
+ // A loader object. Exists only long enough to load resources, and then it dies.
+ RefPtr<nsXBLResourceLoader> mLoader;
+
+ // A list of loaded stylesheets for this binding.
+ nsTArray<RefPtr<mozilla::StyleSheet>> mStyleSheetList;
+
+ // The list of stylesheets converted to a rule processor.
+ RefPtr<nsCSSRuleProcessor> mRuleProcessor;
+};
+
+#endif
diff --git a/dom/xbl/nsXBLResourceLoader.cpp b/dom/xbl/nsXBLResourceLoader.cpp
new file mode 100644
index 000000000..e930e6f98
--- /dev/null
+++ b/dom/xbl/nsXBLResourceLoader.cpp
@@ -0,0 +1,294 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsIStyleRuleProcessor.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsIPresShell.h"
+#include "nsXBLService.h"
+#include "nsIServiceManager.h"
+#include "nsXBLResourceLoader.h"
+#include "nsXBLPrototypeResources.h"
+#include "nsIDocumentObserver.h"
+#include "imgILoader.h"
+#include "imgRequestProxy.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Loader.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsGkAtoms.h"
+#include "nsFrameManager.h"
+#include "nsStyleContext.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsCSSRuleProcessor.h"
+#include "nsContentUtils.h"
+#include "nsStyleSet.h"
+#include "nsIScriptSecurityManager.h"
+
+using namespace mozilla;
+
+NS_IMPL_CYCLE_COLLECTION(nsXBLResourceLoader, mBoundElements)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXBLResourceLoader)
+ NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXBLResourceLoader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXBLResourceLoader)
+
+struct nsXBLResource
+{
+ nsXBLResource* mNext;
+ nsIAtom* mType;
+ nsString mSrc;
+
+ nsXBLResource(nsIAtom* aType, const nsAString& aSrc)
+ {
+ MOZ_COUNT_CTOR(nsXBLResource);
+ mNext = nullptr;
+ mType = aType;
+ mSrc = aSrc;
+ }
+
+ ~nsXBLResource()
+ {
+ MOZ_COUNT_DTOR(nsXBLResource);
+ NS_CONTENT_DELETE_LIST_MEMBER(nsXBLResource, this, mNext);
+ }
+};
+
+nsXBLResourceLoader::nsXBLResourceLoader(nsXBLPrototypeBinding* aBinding,
+ nsXBLPrototypeResources* aResources)
+:mBinding(aBinding),
+ mResources(aResources),
+ mResourceList(nullptr),
+ mLastResource(nullptr),
+ mLoadingResources(false),
+ mInLoadResourcesFunc(false),
+ mPendingSheets(0)
+{
+}
+
+nsXBLResourceLoader::~nsXBLResourceLoader()
+{
+ delete mResourceList;
+}
+
+void
+nsXBLResourceLoader::LoadResources(bool* aResult)
+{
+ mInLoadResourcesFunc = true;
+
+ if (mLoadingResources) {
+ *aResult = (mPendingSheets == 0);
+ mInLoadResourcesFunc = false;
+ return;
+ }
+
+ mLoadingResources = true;
+ *aResult = true;
+
+ // Declare our loaders.
+ nsCOMPtr<nsIDocument> doc = mBinding->XBLDocumentInfo()->GetDocument();
+
+ mozilla::css::Loader* cssLoader = doc->CSSLoader();
+ nsIURI *docURL = doc->GetDocumentURI();
+ nsIPrincipal* docPrincipal = doc->NodePrincipal();
+
+ nsCOMPtr<nsIURI> url;
+
+ for (nsXBLResource* curr = mResourceList; curr; curr = curr->mNext) {
+ if (curr->mSrc.IsEmpty())
+ continue;
+
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(url), curr->mSrc,
+ doc->GetDocumentCharacterSet().get(), docURL)))
+ continue;
+
+ if (curr->mType == nsGkAtoms::image) {
+ // Now kick off the image load...
+ // Passing nullptr for pretty much everything -- cause we don't care!
+ // XXX: initialDocumentURI is nullptr!
+ RefPtr<imgRequestProxy> req;
+ nsContentUtils::LoadImage(url, doc, doc, docPrincipal, docURL,
+ doc->GetReferrerPolicy(), nullptr,
+ nsIRequest::LOAD_BACKGROUND, EmptyString(),
+ getter_AddRefs(req));
+ }
+ else if (curr->mType == nsGkAtoms::stylesheet) {
+ // Kick off the load of the stylesheet.
+
+ // Always load chrome synchronously
+ // XXXbz should that still do a content policy check?
+ bool chrome;
+ nsresult rv;
+ if (NS_SUCCEEDED(url->SchemeIs("chrome", &chrome)) && chrome)
+ {
+ rv = nsContentUtils::GetSecurityManager()->
+ CheckLoadURIWithPrincipal(docPrincipal, url,
+ nsIScriptSecurityManager::ALLOW_CHROME);
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<StyleSheet> sheet;
+ rv = cssLoader->LoadSheetSync(url, &sheet);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Load failed!!!");
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = StyleSheetLoaded(sheet, false, NS_OK);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Processing the style sheet failed!!!");
+ }
+ }
+ }
+ else
+ {
+ rv = cssLoader->LoadSheet(url, false, docPrincipal, EmptyCString(), this);
+ if (NS_SUCCEEDED(rv))
+ ++mPendingSheets;
+ }
+ }
+ }
+
+ *aResult = (mPendingSheets == 0);
+ mInLoadResourcesFunc = false;
+
+ // Destroy our resource list.
+ delete mResourceList;
+ mResourceList = nullptr;
+}
+
+// nsICSSLoaderObserver
+NS_IMETHODIMP
+nsXBLResourceLoader::StyleSheetLoaded(StyleSheet* aSheet,
+ bool aWasAlternate,
+ nsresult aStatus)
+{
+ if (!mResources) {
+ // Our resources got destroyed -- just bail out
+ return NS_OK;
+ }
+
+ mResources->AppendStyleSheet(aSheet);
+
+ if (!mInLoadResourcesFunc)
+ mPendingSheets--;
+
+ if (mPendingSheets == 0) {
+ // All stylesheets are loaded.
+ mResources->GatherRuleProcessor();
+
+ // XXX Check for mPendingScripts when scripts also come online.
+ if (!mInLoadResourcesFunc)
+ NotifyBoundElements();
+ }
+ return NS_OK;
+}
+
+void
+nsXBLResourceLoader::AddResource(nsIAtom* aResourceType, const nsAString& aSrc)
+{
+ nsXBLResource* res = new nsXBLResource(aResourceType, aSrc);
+ if (!mResourceList)
+ mResourceList = res;
+ else
+ mLastResource->mNext = res;
+
+ mLastResource = res;
+}
+
+void
+nsXBLResourceLoader::AddResourceListener(nsIContent* aBoundElement)
+{
+ if (aBoundElement) {
+ mBoundElements.AppendObject(aBoundElement);
+ }
+}
+
+void
+nsXBLResourceLoader::NotifyBoundElements()
+{
+ nsXBLService* xblService = nsXBLService::GetInstance();
+ if (!xblService)
+ return;
+
+ nsIURI* bindingURI = mBinding->BindingURI();
+
+ uint32_t eltCount = mBoundElements.Count();
+ for (uint32_t j = 0; j < eltCount; j++) {
+ nsCOMPtr<nsIContent> content = mBoundElements.ObjectAt(j);
+
+ bool ready = false;
+ xblService->BindingReady(content, bindingURI, &ready);
+
+ if (ready) {
+ // We need the document to flush out frame construction and
+ // such, so we want to use the current document.
+ nsIDocument* doc = content->GetUncomposedDoc();
+
+ if (doc) {
+ // Flush first to make sure we can get the frame for content
+ doc->FlushPendingNotifications(Flush_Frames);
+
+ // If |content| is (in addition to having binding |mBinding|)
+ // also a descendant of another element with binding |mBinding|,
+ // then we might have just constructed it due to the
+ // notification of its parent. (We can know about both if the
+ // binding loads were triggered from the DOM rather than frame
+ // construction.) So we have to check both whether the element
+ // has a primary frame and whether it's in the undisplayed map
+ // before sending a ContentInserted notification, or bad things
+ // will happen.
+ nsIPresShell *shell = doc->GetShell();
+ if (shell) {
+ nsIFrame* childFrame = content->GetPrimaryFrame();
+ if (!childFrame) {
+ // Check to see if it's in the undisplayed content map.
+ nsStyleContext* sc =
+ shell->FrameManager()->GetUndisplayedContent(content);
+
+ if (!sc) {
+ shell->RecreateFramesFor(content);
+ }
+ }
+ }
+
+ // Flush again
+ // XXXbz why is this needed?
+ doc->FlushPendingNotifications(Flush_ContentAndNotify);
+ }
+ }
+ }
+
+ // Clear out the whole array.
+ mBoundElements.Clear();
+
+ // Delete ourselves.
+ mResources->ClearLoader();
+}
+
+nsresult
+nsXBLResourceLoader::Write(nsIObjectOutputStream* aStream)
+{
+ nsresult rv;
+
+ for (nsXBLResource* curr = mResourceList; curr; curr = curr->mNext) {
+ if (curr->mType == nsGkAtoms::image)
+ rv = aStream->Write8(XBLBinding_Serialize_Image);
+ else if (curr->mType == nsGkAtoms::stylesheet)
+ rv = aStream->Write8(XBLBinding_Serialize_Stylesheet);
+ else
+ continue;
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->WriteWStringZ(curr->mSrc.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xbl/nsXBLResourceLoader.h b/dom/xbl/nsXBLResourceLoader.h
new file mode 100644
index 000000000..1311f11f7
--- /dev/null
+++ b/dom/xbl/nsXBLResourceLoader.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLResourceLoader_h
+#define nsXBLResourceLoader_h
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsCOMArray.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsIContent;
+class nsIAtom;
+class nsXBLPrototypeResources;
+class nsXBLPrototypeBinding;
+struct nsXBLResource;
+class nsIObjectOutputStream;
+
+// *********************************************************************/
+// The XBLResourceLoader class
+
+class nsXBLResourceLoader : public nsICSSLoaderObserver
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsXBLResourceLoader)
+
+ // nsICSSLoaderObserver
+ NS_IMETHOD StyleSheetLoaded(mozilla::StyleSheet* aSheet,
+ bool aWasAlternate,
+ nsresult aStatus) override;
+
+ void LoadResources(bool* aResult);
+ void AddResource(nsIAtom* aResourceType, const nsAString& aSrc);
+ void AddResourceListener(nsIContent* aElement);
+
+ nsXBLResourceLoader(nsXBLPrototypeBinding* aBinding,
+ nsXBLPrototypeResources* aResources);
+
+ void NotifyBoundElements();
+
+ nsresult Write(nsIObjectOutputStream* aStream);
+
+// MEMBER VARIABLES
+ nsXBLPrototypeBinding* mBinding; // A pointer back to our binding.
+ nsXBLPrototypeResources* mResources; // A pointer back to our resources
+ // information. May be null if the
+ // resources have already been
+ // destroyed.
+
+ nsXBLResource* mResourceList; // The list of resources we need to load.
+ nsXBLResource* mLastResource;
+
+ bool mLoadingResources;
+ // We need mInLoadResourcesFunc because we do a mixture of sync and
+ // async loads.
+ bool mInLoadResourcesFunc;
+ int16_t mPendingSheets; // The number of stylesheets that have yet to load.
+
+ // Bound elements that are waiting on the stylesheets and scripts.
+ nsCOMArray<nsIContent> mBoundElements;
+
+protected:
+ virtual ~nsXBLResourceLoader();
+};
+
+#endif
diff --git a/dom/xbl/nsXBLSerialize.cpp b/dom/xbl/nsXBLSerialize.cpp
new file mode 100644
index 000000000..b08c25965
--- /dev/null
+++ b/dom/xbl/nsXBLSerialize.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsXBLSerialize.h"
+
+#include "jsfriendapi.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsIXPConnect.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+
+nsresult
+XBL_SerializeFunction(nsIObjectOutputStream* aStream,
+ JS::Handle<JSObject*> aFunction)
+{
+ AssertInCompilationScope();
+ AutoJSContext cx;
+ MOZ_ASSERT(js::GetContextCompartment(cx) == js::GetObjectCompartment(aFunction));
+ return nsContentUtils::XPConnect()->WriteFunction(aStream, cx, aFunction);
+}
+
+nsresult
+XBL_DeserializeFunction(nsIObjectInputStream* aStream,
+ JS::MutableHandle<JSObject*> aFunctionObjectp)
+{
+ AssertInCompilationScope();
+ AutoJSContext cx;
+ return nsContentUtils::XPConnect()->ReadFunction(aStream, cx,
+ aFunctionObjectp.address());
+}
diff --git a/dom/xbl/nsXBLSerialize.h b/dom/xbl/nsXBLSerialize.h
new file mode 100644
index 000000000..2c1dedc33
--- /dev/null
+++ b/dom/xbl/nsXBLSerialize.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLSerialize_h__
+#define nsXBLSerialize_h__
+
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "js/TypeDecls.h"
+
+typedef uint8_t XBLBindingSerializeDetails;
+
+// A version number to ensure we don't load cached data in a different
+// file format.
+#define XBLBinding_Serialize_Version 0x00000004
+
+// Set for the first binding in a document
+#define XBLBinding_Serialize_IsFirstBinding 1
+
+// Set to indicate that nsXBLPrototypeBinding::mInheritStyle should be true
+#define XBLBinding_Serialize_InheritStyle 2
+
+// Set to indicate that nsXBLPrototypeBinding::mChromeOnlyContent should be true
+#define XBLBinding_Serialize_ChromeOnlyContent 4
+
+// Set to indicate that nsXBLPrototypeBinding::mBindToUntrustedContent should be true
+#define XBLBinding_Serialize_BindToUntrustedContent 8
+
+// Appears at the end of the serialized data to indicate that no more bindings
+// are present for this document.
+#define XBLBinding_Serialize_NoMoreBindings 0x80
+
+// Implementation member types. The serialized value for each member contains one
+// of these values, combined with the read-only flag XBLBinding_Serialize_ReadOnly.
+// Use XBLBinding_Serialize_Mask to filter out the read-only flag and check for
+// just the member type.
+#define XBLBinding_Serialize_NoMoreItems 0 // appears at the end of the members list
+#define XBLBinding_Serialize_Field 1
+#define XBLBinding_Serialize_GetterProperty 2
+#define XBLBinding_Serialize_SetterProperty 3
+#define XBLBinding_Serialize_GetterSetterProperty 4
+#define XBLBinding_Serialize_Method 5
+#define XBLBinding_Serialize_Constructor 6
+#define XBLBinding_Serialize_Destructor 7
+#define XBLBinding_Serialize_Handler 8
+#define XBLBinding_Serialize_Image 9
+#define XBLBinding_Serialize_Stylesheet 10
+#define XBLBinding_Serialize_Attribute 0xA
+#define XBLBinding_Serialize_Mask 0x0F
+#define XBLBinding_Serialize_ReadOnly 0x80
+
+// Appears at the end of the list of insertion points to indicate that there
+// are no more.
+#define XBLBinding_Serialize_NoMoreInsertionPoints 0xFFFFFFFF
+
+// When serializing content nodes, a single-byte namespace id is written out
+// first. The special values below can appear in place of a namespace id.
+
+// Indicates that this is not one of the built-in namespaces defined in
+// nsNameSpaceManager.h. The string form will be serialized immediately
+// following.
+#define XBLBinding_Serialize_CustomNamespace 0xFE
+
+// Flags to indicate a non-element node. Otherwise, it is an element.
+#define XBLBinding_Serialize_TextNode 0xFB
+#define XBLBinding_Serialize_CDATANode 0xFC
+#define XBLBinding_Serialize_CommentNode 0xFD
+
+// Indicates that there is no content to serialize/deserialize
+#define XBLBinding_Serialize_NoContent 0xFF
+
+// Appears at the end of the forwarded attributes list to indicate that there
+// are no more attributes.
+#define XBLBinding_Serialize_NoMoreAttributes 0xFF
+
+static_assert(XBLBinding_Serialize_CustomNamespace >= kNameSpaceID_LastBuiltin,
+ "The custom namespace should not be in use as a real namespace");
+
+nsresult
+XBL_SerializeFunction(nsIObjectOutputStream* aStream,
+ JS::Handle<JSObject*> aFunctionObject);
+
+nsresult
+XBL_DeserializeFunction(nsIObjectInputStream* aStream,
+ JS::MutableHandle<JSObject*> aFunctionObject);
+
+#endif // nsXBLSerialize_h__
diff --git a/dom/xbl/nsXBLService.cpp b/dom/xbl/nsXBLService.cpp
new file mode 100644
index 000000000..1475b1368
--- /dev/null
+++ b/dom/xbl/nsXBLService.cpp
@@ -0,0 +1,1090 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsNetUtil.h"
+#include "nsXBLService.h"
+#include "nsXBLWindowKeyHandler.h"
+#include "nsIInputStream.h"
+#include "nsNameSpaceManager.h"
+#include "nsIURI.h"
+#include "nsIDOMElement.h"
+#include "nsIURL.h"
+#include "nsIChannel.h"
+#include "nsXPIDLString.h"
+#include "plstr.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIXMLContentSink.h"
+#include "nsContentCID.h"
+#include "mozilla/dom/XMLDocument.h"
+#include "nsGkAtoms.h"
+#include "nsIMemory.h"
+#include "nsIObserverService.h"
+#include "nsIDOMNodeList.h"
+#include "nsXBLContentSink.h"
+#include "nsXBLBinding.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsXBLDocumentInfo.h"
+#include "nsCRT.h"
+#include "nsContentUtils.h"
+#include "nsSyncLoadService.h"
+#include "nsContentPolicyUtils.h"
+#include "nsTArray.h"
+#include "nsError.h"
+
+#include "nsIPresShell.h"
+#include "nsIDocumentObserver.h"
+#include "nsFrameManager.h"
+#include "nsStyleContext.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIScriptError.h"
+#include "nsXBLSerialize.h"
+
+#ifdef MOZ_XUL
+#include "nsXULPrototypeCache.h"
+#endif
+#include "nsIDOMEventListener.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define NS_MAX_XBL_BINDING_RECURSION 20
+
+nsXBLService* nsXBLService::gInstance = nullptr;
+
+static bool
+IsAncestorBinding(nsIDocument* aDocument,
+ nsIURI* aChildBindingURI,
+ nsIContent* aChild)
+{
+ NS_ASSERTION(aDocument, "expected a document");
+ NS_ASSERTION(aChildBindingURI, "expected a binding URI");
+ NS_ASSERTION(aChild, "expected a child content");
+
+ uint32_t bindingRecursion = 0;
+ for (nsIContent *bindingParent = aChild->GetBindingParent();
+ bindingParent;
+ bindingParent = bindingParent->GetBindingParent()) {
+ nsXBLBinding* binding = bindingParent->GetXBLBinding();
+ if (!binding) {
+ continue;
+ }
+
+ if (binding->PrototypeBinding()->CompareBindingURI(aChildBindingURI)) {
+ ++bindingRecursion;
+ if (bindingRecursion < NS_MAX_XBL_BINDING_RECURSION) {
+ continue;
+ }
+ NS_ConvertUTF8toUTF16 bindingURI(aChildBindingURI->GetSpecOrDefault());
+ const char16_t* params[] = { bindingURI.get() };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("XBL"), aDocument,
+ nsContentUtils::eXBL_PROPERTIES,
+ "TooDeepBindingRecursion",
+ params, ArrayLength(params));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Individual binding requests.
+class nsXBLBindingRequest
+{
+public:
+ nsCOMPtr<nsIURI> mBindingURI;
+ nsCOMPtr<nsIContent> mBoundElement;
+
+ void DocumentLoaded(nsIDocument* aBindingDoc)
+ {
+ // We only need the document here to cause frame construction, so
+ // we need the current doc, not the owner doc.
+ nsIDocument* doc = mBoundElement->GetUncomposedDoc();
+ if (!doc)
+ return;
+
+ // Destroy the frames for mBoundElement.
+ nsIContent* destroyedFramesFor = nullptr;
+ nsIPresShell* shell = doc->GetShell();
+ if (shell) {
+ shell->DestroyFramesFor(mBoundElement, &destroyedFramesFor);
+ }
+ MOZ_ASSERT(!mBoundElement->GetPrimaryFrame());
+
+ // Get the binding.
+ bool ready = false;
+ nsXBLService::GetInstance()->BindingReady(mBoundElement, mBindingURI, &ready);
+ if (!ready)
+ return;
+
+ // If |mBoundElement| is (in addition to having binding |mBinding|)
+ // also a descendant of another element with binding |mBinding|,
+ // then we might have just constructed it due to the
+ // notification of its parent. (We can know about both if the
+ // binding loads were triggered from the DOM rather than frame
+ // construction.) So we have to check both whether the element
+ // has a primary frame and whether it's in the frame manager maps
+ // before sending a ContentInserted notification, or bad things
+ // will happen.
+ MOZ_ASSERT(shell == doc->GetShell());
+ if (shell) {
+ nsIFrame* childFrame = mBoundElement->GetPrimaryFrame();
+ if (!childFrame) {
+ // Check to see if it's in the undisplayed content map...
+ nsFrameManager* fm = shell->FrameManager();
+ nsStyleContext* sc = fm->GetUndisplayedContent(mBoundElement);
+ if (!sc) {
+ // or in the display:contents map.
+ sc = fm->GetDisplayContentsStyleFor(mBoundElement);
+ }
+ if (!sc) {
+ shell->CreateFramesFor(destroyedFramesFor);
+ }
+ }
+ }
+ }
+
+ nsXBLBindingRequest(nsIURI* aURI, nsIContent* aBoundElement)
+ : mBindingURI(aURI),
+ mBoundElement(aBoundElement)
+ {
+ }
+};
+
+// nsXBLStreamListener, a helper class used for
+// asynchronous parsing of URLs
+/* Header file */
+class nsXBLStreamListener final : public nsIStreamListener,
+ public nsIDOMEventListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ nsXBLStreamListener(nsIDocument* aBoundDocument,
+ nsIXMLContentSink* aSink,
+ nsIDocument* aBindingDocument);
+
+ void AddRequest(nsXBLBindingRequest* aRequest) { mBindingRequests.AppendElement(aRequest); }
+ bool HasRequest(nsIURI* aURI, nsIContent* aBoundElement);
+
+private:
+ ~nsXBLStreamListener();
+
+ nsCOMPtr<nsIStreamListener> mInner;
+ AutoTArray<nsXBLBindingRequest*, 8> mBindingRequests;
+
+ nsCOMPtr<nsIWeakReference> mBoundDocument;
+ nsCOMPtr<nsIXMLContentSink> mSink; // Only set until OnStartRequest
+ nsCOMPtr<nsIDocument> mBindingDocument; // Only set until OnStartRequest
+};
+
+/* Implementation file */
+NS_IMPL_ISUPPORTS(nsXBLStreamListener,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIDOMEventListener)
+
+nsXBLStreamListener::nsXBLStreamListener(nsIDocument* aBoundDocument,
+ nsIXMLContentSink* aSink,
+ nsIDocument* aBindingDocument)
+: mSink(aSink), mBindingDocument(aBindingDocument)
+{
+ /* member initializers and constructor code */
+ mBoundDocument = do_GetWeakReference(aBoundDocument);
+}
+
+nsXBLStreamListener::~nsXBLStreamListener()
+{
+ for (uint32_t i = 0; i < mBindingRequests.Length(); i++) {
+ nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
+ delete req;
+ }
+}
+
+NS_IMETHODIMP
+nsXBLStreamListener::OnDataAvailable(nsIRequest *request, nsISupports* aCtxt,
+ nsIInputStream* aInStr,
+ uint64_t aSourceOffset, uint32_t aCount)
+{
+ if (mInner)
+ return mInner->OnDataAvailable(request, aCtxt, aInStr, aSourceOffset, aCount);
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsXBLStreamListener::OnStartRequest(nsIRequest* request, nsISupports* aCtxt)
+{
+ // Make sure we don't hold on to the sink and binding document past this point
+ nsCOMPtr<nsIXMLContentSink> sink;
+ mSink.swap(sink);
+ nsCOMPtr<nsIDocument> doc;
+ mBindingDocument.swap(doc);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsILoadGroup> group;
+ request->GetLoadGroup(getter_AddRefs(group));
+
+ nsresult rv = doc->StartDocumentLoad("loadAsInteractiveData",
+ channel,
+ group,
+ nullptr,
+ getter_AddRefs(mInner),
+ true,
+ sink);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make sure to add ourselves as a listener after StartDocumentLoad,
+ // since that resets the event listners on the document.
+ doc->AddEventListener(NS_LITERAL_STRING("load"), this, false);
+
+ return mInner->OnStartRequest(request, aCtxt);
+}
+
+NS_IMETHODIMP
+nsXBLStreamListener::OnStopRequest(nsIRequest* request, nsISupports* aCtxt, nsresult aStatus)
+{
+ nsresult rv = NS_OK;
+ if (mInner) {
+ rv = mInner->OnStopRequest(request, aCtxt, aStatus);
+ }
+
+ // Don't hold onto the inner listener; holding onto it can create a cycle
+ // with the document
+ mInner = nullptr;
+
+ return rv;
+}
+
+bool
+nsXBLStreamListener::HasRequest(nsIURI* aURI, nsIContent* aElt)
+{
+ // XXX Could be more efficient.
+ uint32_t count = mBindingRequests.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
+ bool eq;
+ if (req->mBoundElement == aElt &&
+ NS_SUCCEEDED(req->mBindingURI->Equals(aURI, &eq)) && eq)
+ return true;
+ }
+
+ return false;
+}
+
+nsresult
+nsXBLStreamListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsresult rv = NS_OK;
+ uint32_t i;
+ uint32_t count = mBindingRequests.Length();
+
+ // Get the binding document; note that we don't hold onto it in this object
+ // to avoid creating a cycle
+ Event* event = aEvent->InternalDOMEvent();
+ EventTarget* target = event->GetCurrentTarget();
+ nsCOMPtr<nsIDocument> bindingDocument = do_QueryInterface(target);
+ NS_ASSERTION(bindingDocument, "Event not targeted at document?!");
+
+ // See if we're still alive.
+ nsCOMPtr<nsIDocument> doc(do_QueryReferent(mBoundDocument));
+ if (!doc) {
+ NS_WARNING("XBL load did not complete until after document went away! Modal dialog bug?\n");
+ }
+ else {
+ // We have to do a flush prior to notification of the document load.
+ // This has to happen since the HTML content sink can be holding on
+ // to notifications related to our children (e.g., if you bind to the
+ // <body> tag) that result in duplication of content.
+ // We need to get the sink's notifications flushed and then make the binding
+ // ready.
+ if (count > 0) {
+ nsXBLBindingRequest* req = mBindingRequests.ElementAt(0);
+ nsIDocument* document = req->mBoundElement->GetUncomposedDoc();
+ if (document)
+ document->FlushPendingNotifications(Flush_ContentAndNotify);
+ }
+
+ // Remove ourselves from the set of pending docs.
+ nsBindingManager *bindingManager = doc->BindingManager();
+ nsIURI* documentURI = bindingDocument->GetDocumentURI();
+ bindingManager->RemoveLoadingDocListener(documentURI);
+
+ if (!bindingDocument->GetRootElement()) {
+ // FIXME: How about an error console warning?
+ NS_WARNING("XBL doc with no root element - this usually shouldn't happen");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Put our doc info in the doc table.
+ nsBindingManager *xblDocBindingManager = bindingDocument->BindingManager();
+ RefPtr<nsXBLDocumentInfo> info =
+ xblDocBindingManager->GetXBLDocumentInfo(documentURI);
+ xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle.
+ if (!info) {
+ if (nsXBLService::IsChromeOrResourceURI(documentURI)) {
+ NS_WARNING("An XBL file is malformed. Did you forget the XBL namespace on the bindings tag?");
+ }
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("XBL"), nullptr,
+ nsContentUtils::eXBL_PROPERTIES,
+ "MalformedXBL",
+ nullptr, 0, documentURI);
+ return NS_ERROR_FAILURE;
+ }
+
+ // If the doc is a chrome URI, then we put it into the XUL cache.
+#ifdef MOZ_XUL
+ if (nsXBLService::IsChromeOrResourceURI(documentURI)) {
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
+ if (cache && cache->IsEnabled())
+ cache->PutXBLDocumentInfo(info);
+ }
+#endif
+
+ bindingManager->PutXBLDocumentInfo(info);
+
+ // Notify all pending requests that their bindings are
+ // ready and can be installed.
+ for (i = 0; i < count; i++) {
+ nsXBLBindingRequest* req = mBindingRequests.ElementAt(i);
+ req->DocumentLoaded(bindingDocument);
+ }
+ }
+
+ target->RemoveEventListener(NS_LITERAL_STRING("load"), this, false);
+
+ return rv;
+}
+
+// Implementation /////////////////////////////////////////////////////////////////
+
+// Implement our nsISupports methods
+NS_IMPL_ISUPPORTS(nsXBLService, nsISupportsWeakReference)
+
+void
+nsXBLService::Init()
+{
+ gInstance = new nsXBLService();
+ NS_ADDREF(gInstance);
+}
+
+// Constructors/Destructors
+nsXBLService::nsXBLService(void)
+{
+}
+
+nsXBLService::~nsXBLService(void)
+{
+}
+
+// static
+bool
+nsXBLService::IsChromeOrResourceURI(nsIURI* aURI)
+{
+ bool isChrome = false;
+ bool isResource = false;
+ if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) &&
+ NS_SUCCEEDED(aURI->SchemeIs("resource", &isResource)))
+ return (isChrome || isResource);
+ return false;
+}
+
+
+// This function loads a particular XBL file and installs all of the bindings
+// onto the element.
+nsresult
+nsXBLService::LoadBindings(nsIContent* aContent, nsIURI* aURL,
+ nsIPrincipal* aOriginPrincipal,
+ nsXBLBinding** aBinding, bool* aResolveStyle)
+{
+ NS_PRECONDITION(aOriginPrincipal, "Must have an origin principal");
+
+ *aBinding = nullptr;
+ *aResolveStyle = false;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIDocument> document = aContent->OwnerDoc();
+
+ nsAutoCString urlspec;
+ bool ok = nsContentUtils::GetWrapperSafeScriptFilename(document, aURL,
+ urlspec, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (ok) {
+ // Block an attempt to load a binding that has special wrapper
+ // automation needs.
+ return NS_OK;
+ }
+
+ nsXBLBinding *binding = aContent->GetXBLBinding();
+ if (binding) {
+ if (binding->MarkedForDeath()) {
+ FlushStyleBindings(aContent);
+ binding = nullptr;
+ }
+ else {
+ // See if the URIs match.
+ if (binding->PrototypeBinding()->CompareBindingURI(aURL))
+ return NS_OK;
+ FlushStyleBindings(aContent);
+ binding = nullptr;
+ }
+ }
+
+ bool ready;
+ RefPtr<nsXBLBinding> newBinding;
+ if (NS_FAILED(rv = GetBinding(aContent, aURL, false, aOriginPrincipal,
+ &ready, getter_AddRefs(newBinding)))) {
+ return rv;
+ }
+
+ if (!newBinding) {
+#ifdef DEBUG
+ nsAutoCString str(NS_LITERAL_CSTRING("Failed to locate XBL binding. XBL is now using id instead of name to reference bindings. Make sure you have switched over. The invalid binding name is: ") + aURL->GetSpecOrDefault());
+ NS_ERROR(str.get());
+#endif
+ return NS_OK;
+ }
+
+ if (::IsAncestorBinding(document, aURL, aContent)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // We loaded a style binding. It goes on the end.
+ if (binding) {
+ // Get the last binding that is in the append layer.
+ binding->RootBinding()->SetBaseBinding(newBinding);
+ }
+ else {
+ // Install the binding on the content node.
+ aContent->SetXBLBinding(newBinding);
+ }
+
+ {
+ nsAutoScriptBlocker scriptBlocker;
+
+ // Set the binding's bound element.
+ newBinding->SetBoundElement(aContent);
+
+ // Tell the binding to build the anonymous content.
+ newBinding->GenerateAnonymousContent();
+
+ // Tell the binding to install event handlers
+ newBinding->InstallEventHandlers();
+
+ // Set up our properties
+ rv = newBinding->InstallImplementation();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Figure out if we have any scoped sheets. If so, we do a second resolve.
+ *aResolveStyle = newBinding->HasStyleSheets();
+
+ newBinding.forget(aBinding);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLService::FlushStyleBindings(nsIContent* aContent)
+{
+ nsCOMPtr<nsIDocument> document = aContent->OwnerDoc();
+
+ nsXBLBinding *binding = aContent->GetXBLBinding();
+ if (binding) {
+ // Clear out the script references.
+ binding->ChangeDocument(document, nullptr);
+
+ aContent->SetXBLBinding(nullptr); // Flush old style bindings
+ }
+
+ return NS_OK;
+}
+
+//
+// AttachGlobalKeyHandler
+//
+// Creates a new key handler and prepares to listen to key events on the given
+// event receiver (either a document or an content node). If the receiver is content,
+// then extra work needs to be done to hook it up to the document (XXX WHY??)
+//
+nsresult
+nsXBLService::AttachGlobalKeyHandler(EventTarget* aTarget)
+{
+ // check if the receiver is a content node (not a document), and hook
+ // it to the document if that is the case.
+ nsCOMPtr<EventTarget> piTarget = aTarget;
+ nsCOMPtr<nsIContent> contentNode(do_QueryInterface(aTarget));
+ if (contentNode) {
+ // Only attach if we're really in a document
+ nsCOMPtr<nsIDocument> doc = contentNode->GetUncomposedDoc();
+ if (doc)
+ piTarget = doc; // We're a XUL keyset. Attach to our document.
+ }
+
+ if (!piTarget)
+ return NS_ERROR_FAILURE;
+
+ EventListenerManager* manager = piTarget->GetOrCreateListenerManager();
+ if (!manager)
+ return NS_ERROR_FAILURE;
+
+ // the listener already exists, so skip this
+ if (contentNode && contentNode->GetProperty(nsGkAtoms::listener))
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(contentNode));
+
+ // Create the key handler
+ RefPtr<nsXBLWindowKeyHandler> handler =
+ NS_NewXBLWindowKeyHandler(elt, piTarget);
+
+ handler->InstallKeyboardEventListenersTo(manager);
+
+ if (contentNode)
+ return contentNode->SetProperty(nsGkAtoms::listener,
+ handler.forget().take(),
+ nsPropertyTable::SupportsDtorFunc, true);
+
+ // The reference to the handler will be maintained by the event target,
+ // and, if there is a content node, the property.
+ return NS_OK;
+}
+
+//
+// DetachGlobalKeyHandler
+//
+// Removes a key handler added by DeatchGlobalKeyHandler.
+//
+nsresult
+nsXBLService::DetachGlobalKeyHandler(EventTarget* aTarget)
+{
+ nsCOMPtr<EventTarget> piTarget = aTarget;
+ nsCOMPtr<nsIContent> contentNode(do_QueryInterface(aTarget));
+ if (!contentNode) // detaching is only supported for content nodes
+ return NS_ERROR_FAILURE;
+
+ // Only attach if we're really in a document
+ nsCOMPtr<nsIDocument> doc = contentNode->GetUncomposedDoc();
+ if (doc)
+ piTarget = do_QueryInterface(doc);
+
+ if (!piTarget)
+ return NS_ERROR_FAILURE;
+
+ EventListenerManager* manager = piTarget->GetOrCreateListenerManager();
+ if (!manager)
+ return NS_ERROR_FAILURE;
+
+ nsIDOMEventListener* handler =
+ static_cast<nsIDOMEventListener*>(contentNode->GetProperty(nsGkAtoms::listener));
+ if (!handler)
+ return NS_ERROR_FAILURE;
+
+ static_cast<nsXBLWindowKeyHandler*>(handler)->
+ RemoveKeyboardEventListenersFrom(manager);
+
+ contentNode->DeleteProperty(nsGkAtoms::listener);
+
+ return NS_OK;
+}
+
+// Internal helper methods ////////////////////////////////////////////////////////////////
+
+nsresult
+nsXBLService::BindingReady(nsIContent* aBoundElement,
+ nsIURI* aURI,
+ bool* aIsReady)
+{
+ // Don't do a security check here; we know this binding is set to go.
+ return GetBinding(aBoundElement, aURI, true, nullptr, aIsReady, nullptr);
+}
+
+nsresult
+nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
+ bool aPeekOnly, nsIPrincipal* aOriginPrincipal,
+ bool* aIsReady, nsXBLBinding** aResult)
+{
+ // More than 6 binding URIs are rare, see bug 55070 comment 18.
+ AutoTArray<nsCOMPtr<nsIURI>, 6> uris;
+ return GetBinding(aBoundElement, aURI, aPeekOnly, aOriginPrincipal, aIsReady,
+ aResult, uris);
+}
+
+static bool
+MayBindToContent(nsXBLPrototypeBinding* aProtoBinding, nsIContent* aBoundElement,
+ nsIURI* aURI)
+{
+ // If this binding explicitly allows untrusted content, we're done.
+ if (aProtoBinding->BindToUntrustedContent()) {
+ return true;
+ }
+
+ // We let XUL content and content in XUL documents through, since XUL is
+ // restricted anyway and we want to minimize remote XUL breakage.
+ if (aBoundElement->IsXULElement() ||
+ aBoundElement->OwnerDoc()->IsXULElement()) {
+ return true;
+ }
+
+ // Similarly, we make an exception for anonymous content (which
+ // lives in the XBL scope), because it's already protected from content,
+ // and tends to use a lot of bindings that we wouldn't otherwise need to
+ // whitelist.
+ if (aBoundElement->IsInAnonymousSubtree()) {
+ return true;
+ }
+
+ // Allow if the bound content subsumes the binding.
+ nsCOMPtr<nsIDocument> bindingDoc = aProtoBinding->XBLDocumentInfo()->GetDocument();
+ NS_ENSURE_TRUE(bindingDoc, false);
+ if (aBoundElement->NodePrincipal()->Subsumes(bindingDoc->NodePrincipal())) {
+ return true;
+ }
+
+ // One last special case: we need to watch out for in-document data: URI
+ // bindings from remote-XUL-whitelisted domains (especially tests), because
+ // they end up with a null principal (rather than inheriting the document's
+ // principal), which causes them to fail the check above.
+ if (nsContentUtils::AllowXULXBLForPrincipal(aBoundElement->NodePrincipal())) {
+ bool isDataURI = false;
+ nsresult rv = aURI->SchemeIs("data", &isDataURI);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (isDataURI) {
+ return true;
+ }
+ }
+
+ // Disallow.
+ return false;
+}
+
+nsresult
+nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
+ bool aPeekOnly, nsIPrincipal* aOriginPrincipal,
+ bool* aIsReady, nsXBLBinding** aResult,
+ nsTArray<nsCOMPtr<nsIURI>>& aDontExtendURIs)
+{
+ NS_ASSERTION(aPeekOnly || aResult,
+ "Must have non-null out param if not just peeking to see "
+ "whether the binding is ready");
+
+ if (aResult)
+ *aResult = nullptr;
+
+ if (!aURI)
+ return NS_ERROR_FAILURE;
+
+ nsAutoCString ref;
+ aURI->GetRef(ref);
+
+ nsCOMPtr<nsIDocument> boundDocument = aBoundElement->OwnerDoc();
+
+ RefPtr<nsXBLDocumentInfo> docInfo;
+ nsresult rv = LoadBindingDocumentInfo(aBoundElement, boundDocument, aURI,
+ aOriginPrincipal,
+ false, getter_AddRefs(docInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!docInfo)
+ return NS_ERROR_FAILURE;
+
+ WeakPtr<nsXBLPrototypeBinding> protoBinding =
+ docInfo->GetPrototypeBinding(ref);
+
+ if (!protoBinding) {
+#ifdef DEBUG
+ nsAutoCString message("Unable to locate an XBL binding for URI ");
+ message += aURI->GetSpecOrDefault();
+ message += " in document ";
+ message += boundDocument->GetDocumentURI()->GetSpecOrDefault();
+ NS_WARNING(message.get());
+#endif
+ return NS_ERROR_FAILURE;
+ }
+
+ // If the binding isn't whitelisted, refuse to apply it to content that
+ // doesn't subsume it (modulo a few exceptions).
+ if (!MayBindToContent(protoBinding, aBoundElement, aURI)) {
+#ifdef DEBUG
+ nsAutoCString message("Permission denied to apply binding ");
+ message += aURI->GetSpecOrDefault();
+ message += " to unprivileged content. Set bindToUntrustedContent=true on "
+ "the binding to override this restriction.";
+ NS_WARNING(message.get());
+#endif
+ return NS_ERROR_FAILURE;
+ }
+
+ aDontExtendURIs.AppendElement(protoBinding->BindingURI());
+ nsCOMPtr<nsIURI> altBindingURI = protoBinding->AlternateBindingURI();
+ if (altBindingURI) {
+ aDontExtendURIs.AppendElement(altBindingURI);
+ }
+
+ // Our prototype binding must have all its resources loaded.
+ bool ready = protoBinding->LoadResources();
+ if (!ready) {
+ // Add our bound element to the protos list of elts that should
+ // be notified when the stylesheets and scripts finish loading.
+ protoBinding->AddResourceListener(aBoundElement);
+ return NS_ERROR_FAILURE; // The binding isn't ready yet.
+ }
+
+ rv = protoBinding->ResolveBaseBinding();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> baseBindingURI;
+ WeakPtr<nsXBLPrototypeBinding> baseProto = protoBinding->GetBasePrototype();
+ if (baseProto) {
+ baseBindingURI = baseProto->BindingURI();
+ }
+ else {
+ baseBindingURI = protoBinding->GetBaseBindingURI();
+ if (baseBindingURI) {
+ uint32_t count = aDontExtendURIs.Length();
+ for (uint32_t index = 0; index < count; ++index) {
+ bool equal;
+ rv = aDontExtendURIs[index]->Equals(baseBindingURI, &equal);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (equal) {
+ NS_ConvertUTF8toUTF16
+ protoSpec(protoBinding->BindingURI()->GetSpecOrDefault());
+ NS_ConvertUTF8toUTF16 baseSpec(baseBindingURI->GetSpecOrDefault());
+ const char16_t* params[] = { protoSpec.get(), baseSpec.get() };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("XBL"), nullptr,
+ nsContentUtils::eXBL_PROPERTIES,
+ "CircularExtendsBinding",
+ params, ArrayLength(params),
+ boundDocument->GetDocumentURI());
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+ }
+ }
+
+ RefPtr<nsXBLBinding> baseBinding;
+ if (baseBindingURI) {
+ nsCOMPtr<nsIContent> child = protoBinding->GetBindingElement();
+ rv = GetBinding(aBoundElement, baseBindingURI, aPeekOnly,
+ child->NodePrincipal(), aIsReady,
+ getter_AddRefs(baseBinding), aDontExtendURIs);
+ if (NS_FAILED(rv))
+ return rv; // We aren't ready yet.
+ }
+
+ *aIsReady = true;
+
+ if (!aPeekOnly) {
+ // Make a new binding
+ NS_ENSURE_STATE(protoBinding);
+ nsXBLBinding *newBinding = new nsXBLBinding(protoBinding);
+
+ if (baseBinding) {
+ if (!baseProto) {
+ protoBinding->SetBasePrototype(baseBinding->PrototypeBinding());
+ }
+ newBinding->SetBaseBinding(baseBinding);
+ }
+
+ NS_ADDREF(*aResult = newBinding);
+ }
+
+ return NS_OK;
+}
+
+static bool
+IsSystemOrChromeURLPrincipal(nsIPrincipal* aPrincipal)
+{
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ return true;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ aPrincipal->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_TRUE(uri, false);
+
+ bool isChrome = false;
+ return NS_SUCCEEDED(uri->SchemeIs("chrome", &isChrome)) && isChrome;
+}
+
+nsresult
+nsXBLService::LoadBindingDocumentInfo(nsIContent* aBoundElement,
+ nsIDocument* aBoundDocument,
+ nsIURI* aBindingURI,
+ nsIPrincipal* aOriginPrincipal,
+ bool aForceSyncLoad,
+ nsXBLDocumentInfo** aResult)
+{
+ NS_PRECONDITION(aBindingURI, "Must have a binding URI");
+ NS_PRECONDITION(!aOriginPrincipal || aBoundDocument,
+ "If we're doing a security check, we better have a document!");
+
+ *aResult = nullptr;
+ // Allow XBL in unprivileged documents if it's specified in a privileged or
+ // chrome: stylesheet. This allows themes to specify XBL bindings.
+ if (aOriginPrincipal && !IsSystemOrChromeURLPrincipal(aOriginPrincipal)) {
+ NS_ENSURE_TRUE(!aBoundDocument || aBoundDocument->AllowXULXBL(),
+ NS_ERROR_XBL_BLOCKED);
+ }
+
+ RefPtr<nsXBLDocumentInfo> info;
+
+ nsCOMPtr<nsIURI> documentURI;
+ nsresult rv = aBindingURI->CloneIgnoringRef(getter_AddRefs(documentURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsBindingManager *bindingManager = nullptr;
+
+ // The first thing to check is the binding manager, which (if it exists)
+ // should have a reference to the nsXBLDocumentInfo if this document
+ // has ever loaded this binding before.
+ if (aBoundDocument) {
+ bindingManager = aBoundDocument->BindingManager();
+ info = bindingManager->GetXBLDocumentInfo(documentURI);
+ if (aBoundDocument->IsStaticDocument() &&
+ IsChromeOrResourceURI(aBindingURI)) {
+ aForceSyncLoad = true;
+ }
+ }
+
+ // It's possible the document is already being loaded. If so, there's no
+ // document yet, but we need to glom on our request so that it will be
+ // processed whenever the doc does finish loading.
+ NodeInfo *ni = nullptr;
+ if (aBoundElement)
+ ni = aBoundElement->NodeInfo();
+
+ if (!info && bindingManager &&
+ (!ni || !(ni->Equals(nsGkAtoms::scrollbar, kNameSpaceID_XUL) ||
+ ni->Equals(nsGkAtoms::thumb, kNameSpaceID_XUL) ||
+ ((ni->Equals(nsGkAtoms::input) ||
+ ni->Equals(nsGkAtoms::select)) &&
+ aBoundElement->IsHTMLElement()))) && !aForceSyncLoad) {
+ nsCOMPtr<nsIStreamListener> listener;
+ if (bindingManager)
+ listener = bindingManager->GetLoadingDocListener(documentURI);
+ if (listener) {
+ nsXBLStreamListener* xblListener =
+ static_cast<nsXBLStreamListener*>(listener.get());
+ // Create a new load observer.
+ if (!xblListener->HasRequest(aBindingURI, aBoundElement)) {
+ nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI, aBoundElement);
+ xblListener->AddRequest(req);
+ }
+ return NS_OK;
+ }
+ }
+
+#ifdef MOZ_XUL
+ // The second line of defense is the global nsXULPrototypeCache,
+ // if it's being used.
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
+ bool useXULCache = cache && cache->IsEnabled();
+
+ if (!info && useXULCache) {
+ // This cache crosses the entire product, so that any XBL bindings that are
+ // part of chrome will be reused across all XUL documents.
+ info = cache->GetXBLDocumentInfo(documentURI);
+ }
+
+ bool useStartupCache = useXULCache && IsChromeOrResourceURI(documentURI);
+
+ if (!info) {
+ // Next, look in the startup cache
+ if (!info && useStartupCache) {
+ rv = nsXBLDocumentInfo::ReadPrototypeBindings(documentURI, getter_AddRefs(info));
+ if (NS_SUCCEEDED(rv)) {
+ cache->PutXBLDocumentInfo(info);
+ }
+ }
+ }
+#endif
+
+ if (!info) {
+ // Finally, if all lines of defense fail, we go and fetch the binding
+ // document.
+
+ // Always load chrome synchronously
+ bool chrome;
+ if (NS_SUCCEEDED(documentURI->SchemeIs("chrome", &chrome)) && chrome)
+ aForceSyncLoad = true;
+
+ nsCOMPtr<nsIDocument> document;
+ rv = FetchBindingDocument(aBoundElement, aBoundDocument, documentURI,
+ aBindingURI, aOriginPrincipal, aForceSyncLoad,
+ getter_AddRefs(document));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (document) {
+ nsBindingManager *xblDocBindingManager = document->BindingManager();
+ info = xblDocBindingManager->GetXBLDocumentInfo(documentURI);
+ if (!info) {
+ NS_ERROR("An XBL file is malformed. Did you forget the XBL namespace on the bindings tag?");
+ return NS_ERROR_FAILURE;
+ }
+ xblDocBindingManager->RemoveXBLDocumentInfo(info); // Break the self-imposed cycle.
+
+ // If the doc is a chrome URI, then we put it into the XUL cache.
+#ifdef MOZ_XUL
+ if (useStartupCache) {
+ cache->PutXBLDocumentInfo(info);
+
+ // now write the bindings into the startup cache
+ info->WritePrototypeBindings();
+ }
+#endif
+ }
+ }
+
+ if (info && bindingManager) {
+ // Cache it in our binding manager's document table. This way,
+ // we can ensure that if the document has loaded this binding
+ // before, it can continue to use it even if the XUL prototype
+ // cache gets flushed. That way, if a flush does occur, we
+ // don't get into a weird state where we're using different
+ // XBLDocumentInfos for the same XBL document in a single
+ // document that has loaded some bindings.
+ bindingManager->PutXBLDocumentInfo(info);
+ }
+
+ info.forget(aResult);
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLService::FetchBindingDocument(nsIContent* aBoundElement, nsIDocument* aBoundDocument,
+ nsIURI* aDocumentURI, nsIURI* aBindingURI,
+ nsIPrincipal* aOriginPrincipal, bool aForceSyncLoad,
+ nsIDocument** aResult)
+{
+ nsresult rv = NS_OK;
+ // Initialize our out pointer to nullptr
+ *aResult = nullptr;
+
+ // Now we have to synchronously load the binding file.
+ // Create an XML content sink and a parser.
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ if (aBoundDocument)
+ loadGroup = aBoundDocument->GetDocumentLoadGroup();
+
+ // We really shouldn't have to force a sync load for anything here... could
+ // we get away with not doing that? Not sure.
+ if (IsChromeOrResourceURI(aDocumentURI))
+ aForceSyncLoad = true;
+
+ // Create document and contentsink and set them up.
+ nsCOMPtr<nsIDocument> doc;
+ rv = NS_NewXMLDocument(getter_AddRefs(doc));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIXMLContentSink> xblSink;
+ rv = NS_NewXBLContentSink(getter_AddRefs(xblSink), doc, aDocumentURI, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Open channel
+ // Note: There are some cases where aOriginPrincipal and aBoundDocument are purposely
+ // set to null (to bypass security checks) when calling LoadBindingDocumentInfo() which calls
+ // FetchBindingDocument(). LoadInfo will end up with no principal or node in those cases,
+ // so we use systemPrincipal. This achieves the same result of bypassing security checks,
+ // but it gives the wrong information to potential future consumers of loadInfo.
+ nsCOMPtr<nsIChannel> channel;
+
+ if (aOriginPrincipal) {
+ // if there is an originPrincipal we should also have aBoundDocument
+ MOZ_ASSERT(aBoundDocument, "can not create a channel without aBoundDocument");
+
+ rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(channel),
+ aDocumentURI,
+ aBoundDocument,
+ aOriginPrincipal,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS |
+ nsILoadInfo::SEC_ALLOW_CHROME,
+ nsIContentPolicy::TYPE_XBL,
+ loadGroup);
+ }
+ else {
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ aDocumentURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS,
+ nsIContentPolicy::TYPE_XBL,
+ loadGroup);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aForceSyncLoad) {
+ // We can be asynchronous
+ nsXBLStreamListener* xblListener =
+ new nsXBLStreamListener(aBoundDocument, xblSink, doc);
+
+ // Add ourselves to the list of loading docs.
+ nsBindingManager *bindingManager;
+ if (aBoundDocument)
+ bindingManager = aBoundDocument->BindingManager();
+ else
+ bindingManager = nullptr;
+
+ if (bindingManager)
+ bindingManager->PutLoadingDocListener(aDocumentURI, xblListener);
+
+ // Add our request.
+ nsXBLBindingRequest* req = new nsXBLBindingRequest(aBindingURI,
+ aBoundElement);
+ xblListener->AddRequest(req);
+
+ // Now kick off the async read.
+ rv = channel->AsyncOpen2(xblListener);
+ if (NS_FAILED(rv)) {
+ // Well, we won't be getting a load. Make sure to clean up our stuff!
+ if (bindingManager) {
+ bindingManager->RemoveLoadingDocListener(aDocumentURI);
+ }
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = doc->StartDocumentLoad("loadAsInteractiveData",
+ channel,
+ loadGroup,
+ nullptr,
+ getter_AddRefs(listener),
+ true,
+ xblSink);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now do a blocking synchronous parse of the file.
+ nsCOMPtr<nsIInputStream> in;
+ rv = channel->Open2(getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsSyncLoadService::PushSyncStreamToListener(in, listener, channel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ doc.swap(*aResult);
+
+ return NS_OK;
+}
diff --git a/dom/xbl/nsXBLService.h b/dom/xbl/nsXBLService.h
new file mode 100644
index 000000000..5082bc42b
--- /dev/null
+++ b/dom/xbl/nsXBLService.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//////////////////////////////////////////////////////////////////////////////////////////
+
+#ifndef nsXBLService_h_
+#define nsXBLService_h_
+
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "nsTArray.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+
+class nsXBLBinding;
+class nsXBLDocumentInfo;
+class nsIContent;
+class nsIDocument;
+class nsString;
+class nsIURI;
+class nsIPrincipal;
+
+namespace mozilla {
+namespace dom {
+class EventTarget;
+} // namespace dom
+} // namespace mozilla
+
+class nsXBLService final : public nsSupportsWeakReference
+{
+ NS_DECL_ISUPPORTS
+
+ static nsXBLService* gInstance;
+
+ static void Init();
+
+ static void Shutdown() {
+ NS_IF_RELEASE(gInstance);
+ }
+
+ static nsXBLService* GetInstance() { return gInstance; }
+
+ static bool IsChromeOrResourceURI(nsIURI* aURI);
+
+ // This function loads a particular XBL file and installs all of the bindings
+ // onto the element. aOriginPrincipal must not be null here.
+ nsresult LoadBindings(nsIContent* aContent, nsIURI* aURL,
+ nsIPrincipal* aOriginPrincipal,
+ nsXBLBinding** aBinding, bool* aResolveStyle);
+
+ // Indicates whether or not a binding is fully loaded.
+ nsresult BindingReady(nsIContent* aBoundElement, nsIURI* aURI, bool* aIsReady);
+
+ // This method checks the hashtable and then calls FetchBindingDocument on a
+ // miss. aOriginPrincipal or aBoundDocument may be null to bypass security
+ // checks.
+ nsresult LoadBindingDocumentInfo(nsIContent* aBoundElement,
+ nsIDocument* aBoundDocument,
+ nsIURI* aBindingURI,
+ nsIPrincipal* aOriginPrincipal,
+ bool aForceSyncLoad,
+ nsXBLDocumentInfo** aResult);
+
+ // Used by XUL key bindings and for window XBL.
+ static nsresult AttachGlobalKeyHandler(mozilla::dom::EventTarget* aTarget);
+ static nsresult DetachGlobalKeyHandler(mozilla::dom::EventTarget* aTarget);
+
+private:
+ nsXBLService();
+ virtual ~nsXBLService();
+
+protected:
+ // This function clears out the bindings on a given content node.
+ nsresult FlushStyleBindings(nsIContent* aContent);
+
+ // This method synchronously loads and parses an XBL file.
+ nsresult FetchBindingDocument(nsIContent* aBoundElement, nsIDocument* aBoundDocument,
+ nsIURI* aDocumentURI, nsIURI* aBindingURI,
+ nsIPrincipal* aOriginPrincipal, bool aForceSyncLoad,
+ nsIDocument** aResult);
+
+ /**
+ * This method calls the one below with an empty |aDontExtendURIs| array.
+ */
+ nsresult GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
+ bool aPeekFlag, nsIPrincipal* aOriginPrincipal,
+ bool* aIsReady, nsXBLBinding** aResult);
+
+ /**
+ * This method loads a binding doc and then builds the specific binding
+ * required. It can also peek without building.
+ * @param aBoundElement the element to get a binding for
+ * @param aURI the binding URI
+ * @param aPeekFlag if true then just peek to see if the binding is ready
+ * @param aIsReady [out] if the binding is ready or not
+ * @param aResult [out] where to store the resulting binding (not used if
+ * aPeekFlag is true, otherwise it must be non-null)
+ * @param aDontExtendURIs a set of URIs that are already bound to this
+ * element. If a binding extends any of these then further loading
+ * is aborted (because it would lead to the binding extending itself)
+ * and NS_ERROR_ILLEGAL_VALUE is returned.
+ *
+ * @note This method always calls LoadBindingDocumentInfo(), so it's
+ * enough to funnel all security checks through that function.
+ */
+ nsresult GetBinding(nsIContent* aBoundElement, nsIURI* aURI,
+ bool aPeekFlag, nsIPrincipal* aOriginPrincipal,
+ bool* aIsReady, nsXBLBinding** aResult,
+ nsTArray<nsCOMPtr<nsIURI>>& aDontExtendURIs);
+
+// MEMBER VARIABLES
+public:
+ static bool gDisableChromeCache;
+ static bool gAllowDataURIs; // Whether we should allow data
+ // urls in -moz-binding. Needed for
+ // testing.
+};
+
+#endif
diff --git a/dom/xbl/nsXBLWindowKeyHandler.cpp b/dom/xbl/nsXBLWindowKeyHandler.cpp
new file mode 100644
index 000000000..011b5c36b
--- /dev/null
+++ b/dom/xbl/nsXBLWindowKeyHandler.cpp
@@ -0,0 +1,892 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsXBLPrototypeHandler.h"
+#include "nsXBLWindowKeyHandler.h"
+#include "nsIContent.h"
+#include "nsIAtom.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsXBLService.h"
+#include "nsIServiceManager.h"
+#include "nsGkAtoms.h"
+#include "nsXBLDocumentInfo.h"
+#include "nsIDOMElement.h"
+#include "nsFocusManager.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIPresShell.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStateManager.h"
+#include "nsISelectionController.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "nsIEditor.h"
+#include "nsIHTMLEditor.h"
+#include "nsIDOMDocument.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+class nsXBLSpecialDocInfo : public nsIObserver
+{
+public:
+ RefPtr<nsXBLDocumentInfo> mHTMLBindings;
+ RefPtr<nsXBLDocumentInfo> mUserHTMLBindings;
+
+ static const char sHTMLBindingStr[];
+ static const char sUserHTMLBindingStr[];
+
+ bool mInitialized;
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ void LoadDocInfo();
+ void GetAllHandlers(const char* aType,
+ nsXBLPrototypeHandler** handler,
+ nsXBLPrototypeHandler** userHandler);
+ void GetHandlers(nsXBLDocumentInfo* aInfo,
+ const nsACString& aRef,
+ nsXBLPrototypeHandler** aResult);
+
+ nsXBLSpecialDocInfo() : mInitialized(false) {}
+
+protected:
+ virtual ~nsXBLSpecialDocInfo() {}
+
+};
+
+const char nsXBLSpecialDocInfo::sHTMLBindingStr[] =
+ "chrome://global/content/platformHTMLBindings.xml";
+
+NS_IMPL_ISUPPORTS(nsXBLSpecialDocInfo, nsIObserver)
+
+NS_IMETHODIMP
+nsXBLSpecialDocInfo::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"), "wrong topic");
+
+ // On shutdown, clear our fields to avoid an extra cycle collection.
+ mHTMLBindings = nullptr;
+ mUserHTMLBindings = nullptr;
+ mInitialized = false;
+ nsContentUtils::UnregisterShutdownObserver(this);
+
+ return NS_OK;
+}
+
+void nsXBLSpecialDocInfo::LoadDocInfo()
+{
+ if (mInitialized)
+ return;
+ mInitialized = true;
+ nsContentUtils::RegisterShutdownObserver(this);
+
+ nsXBLService* xblService = nsXBLService::GetInstance();
+ if (!xblService)
+ return;
+
+ // Obtain the platform doc info
+ nsCOMPtr<nsIURI> bindingURI;
+ NS_NewURI(getter_AddRefs(bindingURI), sHTMLBindingStr);
+ if (!bindingURI) {
+ return;
+ }
+ xblService->LoadBindingDocumentInfo(nullptr, nullptr,
+ bindingURI,
+ nullptr,
+ true,
+ getter_AddRefs(mHTMLBindings));
+
+ const nsAdoptingCString& userHTMLBindingStr =
+ Preferences::GetCString("dom.userHTMLBindings.uri");
+ if (!userHTMLBindingStr.IsEmpty()) {
+ NS_NewURI(getter_AddRefs(bindingURI), userHTMLBindingStr);
+ if (!bindingURI) {
+ return;
+ }
+
+ xblService->LoadBindingDocumentInfo(nullptr, nullptr,
+ bindingURI,
+ nullptr,
+ true,
+ getter_AddRefs(mUserHTMLBindings));
+ }
+}
+
+//
+// GetHandlers
+//
+//
+void
+nsXBLSpecialDocInfo::GetHandlers(nsXBLDocumentInfo* aInfo,
+ const nsACString& aRef,
+ nsXBLPrototypeHandler** aResult)
+{
+ nsXBLPrototypeBinding* binding = aInfo->GetPrototypeBinding(aRef);
+
+ NS_ASSERTION(binding, "No binding found for the XBL window key handler.");
+ if (!binding)
+ return;
+
+ *aResult = binding->GetPrototypeHandlers();
+}
+
+void
+nsXBLSpecialDocInfo::GetAllHandlers(const char* aType,
+ nsXBLPrototypeHandler** aHandler,
+ nsXBLPrototypeHandler** aUserHandler)
+{
+ if (mUserHTMLBindings) {
+ nsAutoCString type(aType);
+ type.AppendLiteral("User");
+ GetHandlers(mUserHTMLBindings, type, aUserHandler);
+ }
+ if (mHTMLBindings) {
+ GetHandlers(mHTMLBindings, nsDependentCString(aType), aHandler);
+ }
+}
+
+// Init statics
+nsXBLSpecialDocInfo* nsXBLWindowKeyHandler::sXBLSpecialDocInfo = nullptr;
+uint32_t nsXBLWindowKeyHandler::sRefCnt = 0;
+
+nsXBLWindowKeyHandler::nsXBLWindowKeyHandler(nsIDOMElement* aElement,
+ EventTarget* aTarget)
+ : mTarget(aTarget),
+ mHandler(nullptr),
+ mUserHandler(nullptr)
+{
+ mWeakPtrForElement = do_GetWeakReference(aElement);
+ ++sRefCnt;
+}
+
+nsXBLWindowKeyHandler::~nsXBLWindowKeyHandler()
+{
+ // If mWeakPtrForElement is non-null, we created a prototype handler.
+ if (mWeakPtrForElement)
+ delete mHandler;
+
+ --sRefCnt;
+ if (!sRefCnt) {
+ NS_IF_RELEASE(sXBLSpecialDocInfo);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsXBLWindowKeyHandler,
+ nsIDOMEventListener)
+
+static void
+BuildHandlerChain(nsIContent* aContent, nsXBLPrototypeHandler** aResult)
+{
+ *aResult = nullptr;
+
+ // Since we chain each handler onto the next handler,
+ // we'll enumerate them here in reverse so that when we
+ // walk the chain they'll come out in the original order
+ for (nsIContent* key = aContent->GetLastChild();
+ key;
+ key = key->GetPreviousSibling()) {
+
+ if (key->NodeInfo()->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
+ // Check whether the key element has empty value at key/char attribute.
+ // Such element is used by localizers for alternative shortcut key
+ // definition on the locale. See bug 426501.
+ nsAutoString valKey, valCharCode, valKeyCode;
+ bool attrExists =
+ key->GetAttr(kNameSpaceID_None, nsGkAtoms::key, valKey) ||
+ key->GetAttr(kNameSpaceID_None, nsGkAtoms::charcode, valCharCode) ||
+ key->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, valKeyCode);
+ if (attrExists &&
+ valKey.IsEmpty() && valCharCode.IsEmpty() && valKeyCode.IsEmpty())
+ continue;
+
+ nsXBLPrototypeHandler* handler = new nsXBLPrototypeHandler(key);
+
+ handler->SetNextHandler(*aResult);
+ *aResult = handler;
+ }
+ }
+}
+
+//
+// EnsureHandlers
+//
+// Lazily load the XBL handlers. Overridden to handle being attached
+// to a particular element rather than the document
+//
+nsresult
+nsXBLWindowKeyHandler::EnsureHandlers()
+{
+ nsCOMPtr<Element> el = GetElement();
+ NS_ENSURE_STATE(!mWeakPtrForElement || el);
+ if (el) {
+ // We are actually a XUL <keyset>.
+ if (mHandler)
+ return NS_OK;
+
+ nsCOMPtr<nsIContent> content(do_QueryInterface(el));
+ BuildHandlerChain(content, &mHandler);
+ } else { // We are an XBL file of handlers.
+ if (!sXBLSpecialDocInfo) {
+ sXBLSpecialDocInfo = new nsXBLSpecialDocInfo();
+ NS_ADDREF(sXBLSpecialDocInfo);
+ }
+ sXBLSpecialDocInfo->LoadDocInfo();
+
+ // Now determine which handlers we should be using.
+ if (IsHTMLEditableFieldFocused()) {
+ sXBLSpecialDocInfo->GetAllHandlers("editor", &mHandler, &mUserHandler);
+ }
+ else {
+ sXBLSpecialDocInfo->GetAllHandlers("browser", &mHandler, &mUserHandler);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLWindowKeyHandler::WalkHandlers(nsIDOMKeyEvent* aKeyEvent, nsIAtom* aEventType)
+{
+ bool prevent;
+ aKeyEvent->AsEvent()->GetDefaultPrevented(&prevent);
+ if (prevent)
+ return NS_OK;
+
+ bool trustedEvent = false;
+ // Don't process the event if it was not dispatched from a trusted source
+ aKeyEvent->AsEvent()->GetIsTrusted(&trustedEvent);
+
+ if (!trustedEvent)
+ return NS_OK;
+
+ nsresult rv = EnsureHandlers();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDisabled;
+ nsCOMPtr<Element> el = GetElement(&isDisabled);
+ if (!el) {
+ if (mUserHandler) {
+ WalkHandlersInternal(aKeyEvent, aEventType, mUserHandler, true);
+ aKeyEvent->AsEvent()->GetDefaultPrevented(&prevent);
+ if (prevent)
+ return NS_OK; // Handled by the user bindings. Our work here is done.
+ }
+ }
+
+ // skip keysets that are disabled
+ if (el && isDisabled) {
+ return NS_OK;
+ }
+
+ WalkHandlersInternal(aKeyEvent, aEventType, mHandler, true);
+
+ return NS_OK;
+}
+
+void
+nsXBLWindowKeyHandler::InstallKeyboardEventListenersTo(
+ EventListenerManager* aEventListenerManager)
+{
+ // For marking each keyboard event as if it's reserved by chrome,
+ // nsXBLWindowKeyHandlers need to listen each keyboard events before
+ // web contents.
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("keydown"),
+ TrustedEventsAtCapture());
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("keyup"),
+ TrustedEventsAtCapture());
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("keypress"),
+ TrustedEventsAtCapture());
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("mozkeydownonplugin"),
+ TrustedEventsAtCapture());
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("mozkeyuponplugin"),
+ TrustedEventsAtCapture());
+
+ // For reducing the IPC cost, preventing to dispatch reserved keyboard
+ // events into the content process.
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("keydown"),
+ TrustedEventsAtSystemGroupCapture());
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("keyup"),
+ TrustedEventsAtSystemGroupCapture());
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("keypress"),
+ TrustedEventsAtSystemGroupCapture());
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("mozkeydownonplugin"),
+ TrustedEventsAtSystemGroupCapture());
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("mozkeyuponplugin"),
+ TrustedEventsAtSystemGroupCapture());
+
+ // Handle keyboard events in bubbling phase of the system event group.
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("keydown"),
+ TrustedEventsAtSystemGroupBubble());
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("keyup"),
+ TrustedEventsAtSystemGroupBubble());
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("keypress"),
+ TrustedEventsAtSystemGroupBubble());
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("mozkeydownonplugin"),
+ TrustedEventsAtSystemGroupBubble());
+ aEventListenerManager->AddEventListenerByType(
+ this, NS_LITERAL_STRING("mozkeyuponplugin"),
+ TrustedEventsAtSystemGroupBubble());
+}
+
+void
+nsXBLWindowKeyHandler::RemoveKeyboardEventListenersFrom(
+ EventListenerManager* aEventListenerManager)
+{
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("keydown"),
+ TrustedEventsAtCapture());
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("keyup"),
+ TrustedEventsAtCapture());
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("keypress"),
+ TrustedEventsAtCapture());
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("mozkeydownonplugin"),
+ TrustedEventsAtCapture());
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("mozkeyuponplugin"),
+ TrustedEventsAtCapture());
+
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("keydown"),
+ TrustedEventsAtSystemGroupCapture());
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("keyup"),
+ TrustedEventsAtSystemGroupCapture());
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("keypress"),
+ TrustedEventsAtSystemGroupCapture());
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("mozkeydownonplugin"),
+ TrustedEventsAtSystemGroupCapture());
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("mozkeyuponplugin"),
+ TrustedEventsAtSystemGroupCapture());
+
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("keydown"),
+ TrustedEventsAtSystemGroupBubble());
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("keyup"),
+ TrustedEventsAtSystemGroupBubble());
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("keypress"),
+ TrustedEventsAtSystemGroupBubble());
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("mozkeydownonplugin"),
+ TrustedEventsAtSystemGroupBubble());
+ aEventListenerManager->RemoveEventListenerByType(
+ this, NS_LITERAL_STRING("mozkeyuponplugin"),
+ TrustedEventsAtSystemGroupBubble());
+}
+
+nsIAtom*
+nsXBLWindowKeyHandler::ConvertEventToDOMEventType(
+ const WidgetKeyboardEvent& aWidgetKeyboardEvent) const
+{
+ if (aWidgetKeyboardEvent.IsKeyDownOrKeyDownOnPlugin()) {
+ return nsGkAtoms::keydown;
+ }
+ if (aWidgetKeyboardEvent.IsKeyUpOrKeyUpOnPlugin()) {
+ return nsGkAtoms::keyup;
+ }
+ if (aWidgetKeyboardEvent.mMessage == eKeyPress) {
+ return nsGkAtoms::keypress;
+ }
+ MOZ_ASSERT_UNREACHABLE(
+ "All event messages which this instance listens to should be handled");
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsXBLWindowKeyHandler::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent(do_QueryInterface(aEvent));
+ NS_ENSURE_TRUE(keyEvent, NS_ERROR_INVALID_ARG);
+
+ uint16_t eventPhase;
+ aEvent->GetEventPhase(&eventPhase);
+ if (eventPhase == nsIDOMEvent::CAPTURING_PHASE) {
+ if (aEvent->WidgetEventPtr()->mFlags.mInSystemGroup) {
+ HandleEventOnCaptureInSystemEventGroup(keyEvent);
+ } else {
+ HandleEventOnCaptureInDefaultEventGroup(keyEvent);
+ }
+ return NS_OK;
+ }
+
+ WidgetKeyboardEvent* widgetKeyboardEvent =
+ aEvent->WidgetEventPtr()->AsKeyboardEvent();
+ if (widgetKeyboardEvent->IsKeyEventOnPlugin()) {
+ // key events on plugin shouldn't execute shortcut key handlers which are
+ // not reserved.
+ if (!widgetKeyboardEvent->mIsReserved) {
+ return NS_OK;
+ }
+
+ // If the event is untrusted event or was already consumed, do nothing.
+ if (!widgetKeyboardEvent->IsTrusted() ||
+ widgetKeyboardEvent->DefaultPrevented()) {
+ return NS_OK;
+ }
+
+ // XXX Don't check isReserved here because even if the handler in this
+ // instance isn't reserved but another instance reserves the key
+ // combination, it will be executed when the event is normal keyboard
+ // events...
+ bool isReserved = false;
+ if (!HasHandlerForEvent(keyEvent, &isReserved)) {
+ return NS_OK;
+ }
+ }
+
+ nsCOMPtr<nsIAtom> eventTypeAtom =
+ ConvertEventToDOMEventType(*widgetKeyboardEvent);
+ return WalkHandlers(keyEvent, eventTypeAtom);
+}
+
+void
+nsXBLWindowKeyHandler::HandleEventOnCaptureInDefaultEventGroup(
+ nsIDOMKeyEvent* aEvent)
+{
+ WidgetKeyboardEvent* widgetKeyboardEvent =
+ aEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
+
+ if (widgetKeyboardEvent->mIsReserved) {
+ MOZ_RELEASE_ASSERT(
+ widgetKeyboardEvent->mFlags.mOnlySystemGroupDispatchInContent);
+ MOZ_RELEASE_ASSERT(
+ widgetKeyboardEvent->mFlags.mNoCrossProcessBoundaryForwarding);
+ return;
+ }
+
+ bool isReserved = false;
+ if (HasHandlerForEvent(aEvent, &isReserved) && isReserved) {
+ widgetKeyboardEvent->mIsReserved = true;
+ // For reserved commands (such as Open New Tab), we don't to wait for
+ // the content to answer (so mWantReplyFromContentProcess remains false),
+ // neither to give a chance for content to override its behavior.
+ widgetKeyboardEvent->StopCrossProcessForwarding();
+ // If the key combination is reserved by chrome, we shouldn't expose the
+ // keyboard event to web contents because such keyboard events shouldn't be
+ // cancelable. So, it's not good behavior to fire keyboard events but
+ // to ignore the defaultPrevented attribute value in chrome.
+ widgetKeyboardEvent->mFlags.mOnlySystemGroupDispatchInContent = true;
+ }
+}
+
+void
+nsXBLWindowKeyHandler::HandleEventOnCaptureInSystemEventGroup(
+ nsIDOMKeyEvent* aEvent)
+{
+ WidgetKeyboardEvent* widgetEvent =
+ aEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
+
+ if (widgetEvent->mFlags.mNoCrossProcessBoundaryForwarding ||
+ widgetEvent->mFlags.mOnlySystemGroupDispatchInContent) {
+ return;
+ }
+
+ nsCOMPtr<mozilla::dom::Element> originalTarget =
+ do_QueryInterface(aEvent->AsEvent()->WidgetEventPtr()->mOriginalTarget);
+ if (!EventStateManager::IsRemoteTarget(originalTarget)) {
+ return;
+ }
+
+ if (!HasHandlerForEvent(aEvent)) {
+ return;
+ }
+
+ // Inform the child process that this is a event that we want a reply
+ // from.
+ widgetEvent->mFlags.mWantReplyFromContentProcess = true;
+ // If this event hadn't been marked as mNoCrossProcessBoundaryForwarding
+ // yet, it means it wasn't processed by content. We'll not call any
+ // of the handlers at this moment, and will wait for the event to be
+ // redispatched with mNoCrossProcessBoundaryForwarding = 1 to process it.
+ // XXX Why not StopImmediatePropagation()?
+ aEvent->AsEvent()->StopPropagation();
+}
+
+bool
+nsXBLWindowKeyHandler::IsHTMLEditableFieldFocused()
+{
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm)
+ return false;
+
+ nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
+ fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
+ if (!focusedWindow)
+ return false;
+
+ auto* piwin = nsPIDOMWindowOuter::From(focusedWindow);
+ nsIDocShell *docShell = piwin->GetDocShell();
+ if (!docShell) {
+ return false;
+ }
+
+ nsCOMPtr<nsIEditor> editor;
+ docShell->GetEditor(getter_AddRefs(editor));
+ nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(editor);
+ if (!htmlEditor) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ editor->GetDocument(getter_AddRefs(domDocument));
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDocument);
+ if (doc->HasFlag(NODE_IS_EDITABLE)) {
+ // Don't need to perform any checks in designMode documents.
+ return true;
+ }
+
+ nsCOMPtr<nsIDOMElement> focusedElement;
+ fm->GetFocusedElement(getter_AddRefs(focusedElement));
+ nsCOMPtr<nsINode> focusedNode = do_QueryInterface(focusedElement);
+ if (focusedNode) {
+ // If there is a focused element, make sure it's in the active editing host.
+ // Note that GetActiveEditingHost finds the current editing host based on
+ // the document's selection. Even though the document selection is usually
+ // collapsed to where the focus is, but the page may modify the selection
+ // without our knowledge, in which case this check will do something useful.
+ nsCOMPtr<Element> activeEditingHost = htmlEditor->GetActiveEditingHost();
+ if (!activeEditingHost) {
+ return false;
+ }
+ return nsContentUtils::ContentIsDescendantOf(focusedNode, activeEditingHost);
+ }
+
+ return false;
+}
+
+//
+// WalkHandlersInternal and WalkHandlersAndExecute
+//
+// Given a particular DOM event and a pointer to the first handler in the list,
+// scan through the list to find something to handle the event. If aExecute = true,
+// the handler will be executed; otherwise just return an answer telling if a handler
+// for that event was found.
+//
+bool
+nsXBLWindowKeyHandler::WalkHandlersInternal(nsIDOMKeyEvent* aKeyEvent,
+ nsIAtom* aEventType,
+ nsXBLPrototypeHandler* aHandler,
+ bool aExecute,
+ bool* aOutReservedForChrome)
+{
+ WidgetKeyboardEvent* nativeKeyboardEvent =
+ aKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
+ MOZ_ASSERT(nativeKeyboardEvent);
+
+ AutoShortcutKeyCandidateArray shortcutKeys;
+ nativeKeyboardEvent->GetShortcutKeyCandidates(shortcutKeys);
+
+ if (shortcutKeys.IsEmpty()) {
+ return WalkHandlersAndExecute(aKeyEvent, aEventType, aHandler,
+ 0, IgnoreModifierState(),
+ aExecute, aOutReservedForChrome);
+ }
+
+ for (uint32_t i = 0; i < shortcutKeys.Length(); ++i) {
+ ShortcutKeyCandidate& key = shortcutKeys[i];
+ IgnoreModifierState ignoreModifierState;
+ ignoreModifierState.mShift = key.mIgnoreShift;
+ if (WalkHandlersAndExecute(aKeyEvent, aEventType, aHandler,
+ key.mCharCode, ignoreModifierState,
+ aExecute, aOutReservedForChrome)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+nsXBLWindowKeyHandler::WalkHandlersAndExecute(
+ nsIDOMKeyEvent* aKeyEvent,
+ nsIAtom* aEventType,
+ nsXBLPrototypeHandler* aFirstHandler,
+ uint32_t aCharCode,
+ const IgnoreModifierState& aIgnoreModifierState,
+ bool aExecute,
+ bool* aOutReservedForChrome)
+{
+ if (aOutReservedForChrome) {
+ *aOutReservedForChrome = false;
+ }
+
+ WidgetKeyboardEvent* widgetKeyboardEvent =
+ aKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
+ if (NS_WARN_IF(!widgetKeyboardEvent)) {
+ return false;
+ }
+
+ // Try all of the handlers until we find one that matches the event.
+ for (nsXBLPrototypeHandler* handler = aFirstHandler;
+ handler;
+ handler = handler->GetNextHandler()) {
+ bool stopped = aKeyEvent->AsEvent()->IsDispatchStopped();
+ if (stopped) {
+ // The event is finished, don't execute any more handlers
+ return false;
+ }
+
+ if (aExecute) {
+ // If the event is eKeyDownOnPlugin, it should execute either keydown
+ // handler or keypress handler because eKeyDownOnPlugin events are
+ // never followed by keypress events.
+ if (widgetKeyboardEvent->mMessage == eKeyDownOnPlugin) {
+ if (!handler->EventTypeEquals(nsGkAtoms::keydown) &&
+ !handler->EventTypeEquals(nsGkAtoms::keypress)) {
+ continue;
+ }
+ // The other event types should exactly be matched with the handler's
+ // event type.
+ } else if (!handler->EventTypeEquals(aEventType)) {
+ continue;
+ }
+ } else {
+ if (handler->EventTypeEquals(nsGkAtoms::keypress)) {
+ // If the handler is a keypress event handler, we also need to check
+ // if coming keydown event is a preceding event of reserved key
+ // combination because if default action of a keydown event is
+ // prevented, following keypress event won't be fired. However, if
+ // following keypress event is reserved, we shouldn't allow web
+ // contents to prevent the default of the preceding keydown event.
+ if (aEventType != nsGkAtoms::keydown &&
+ aEventType != nsGkAtoms::keypress) {
+ continue;
+ }
+ } else if (!handler->EventTypeEquals(aEventType)) {
+ // Otherwise, aEventType should exactly be matched.
+ continue;
+ }
+ }
+
+ // Check if the keyboard event *may* execute the handler.
+ if (!handler->KeyEventMatched(aKeyEvent, aCharCode, aIgnoreModifierState)) {
+ continue; // try the next one
+ }
+
+ // Before executing this handler, check that it's not disabled,
+ // and that it has something to do (oncommand of the <key> or its
+ // <command> is non-empty).
+ nsCOMPtr<Element> commandElement;
+ if (!GetElementForHandler(handler, getter_AddRefs(commandElement))) {
+ continue;
+ }
+
+ bool isReserved = false;
+ if (commandElement) {
+ if (aExecute && !IsExecutableElement(commandElement)) {
+ continue;
+ }
+
+ isReserved =
+ commandElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::reserved,
+ nsGkAtoms::_true, eCaseMatters);
+ if (aOutReservedForChrome) {
+ *aOutReservedForChrome = isReserved;
+ }
+ }
+
+ if (!aExecute) {
+ if (handler->EventTypeEquals(aEventType)) {
+ return true;
+ }
+ // If the command is reserved and the event is keydown, check also if
+ // the handler is for keypress because if following keypress event is
+ // reserved, we shouldn't dispatch the event into web contents.
+ if (isReserved &&
+ aEventType == nsGkAtoms::keydown &&
+ handler->EventTypeEquals(nsGkAtoms::keypress)) {
+ return true;
+ }
+ // Otherwise, we've not found a handler for the event yet.
+ continue;
+ }
+
+ // If it's not reserved and the event is a key event on a plugin,
+ // the handler shouldn't be executed.
+ if (!isReserved && widgetKeyboardEvent->IsKeyEventOnPlugin()) {
+ return false;
+ }
+
+ nsCOMPtr<EventTarget> target;
+ nsCOMPtr<Element> chromeHandlerElement = GetElement();
+ if (chromeHandlerElement) {
+ // XXX commandElement may be nullptr...
+ target = commandElement;
+ } else {
+ target = mTarget;
+ }
+
+ // XXX Do we execute only one handler even if the handler neither stops
+ // propagation nor prevents default of the event?
+ nsresult rv = handler->ExecuteHandler(target, aKeyEvent->AsEvent());
+ if (NS_SUCCEEDED(rv)) {
+ return true;
+ }
+ }
+
+#ifdef XP_WIN
+ // Windows native applications ignore Windows-Logo key state when checking
+ // shortcut keys even if the key is pressed. Therefore, if there is no
+ // shortcut key which exactly matches current modifier state, we should
+ // retry to look for a shortcut key without the Windows-Logo key press.
+ if (!aIgnoreModifierState.mOS && widgetKeyboardEvent->IsOS()) {
+ IgnoreModifierState ignoreModifierState(aIgnoreModifierState);
+ ignoreModifierState.mOS = true;
+ return WalkHandlersAndExecute(aKeyEvent, aEventType, aFirstHandler,
+ aCharCode, ignoreModifierState, aExecute);
+ }
+#endif
+
+ return false;
+}
+
+bool
+nsXBLWindowKeyHandler::HasHandlerForEvent(nsIDOMKeyEvent* aEvent,
+ bool* aOutReservedForChrome)
+{
+ WidgetKeyboardEvent* widgetKeyboardEvent =
+ aEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
+ if (NS_WARN_IF(!widgetKeyboardEvent) || !widgetKeyboardEvent->IsTrusted()) {
+ return false;
+ }
+
+ nsresult rv = EnsureHandlers();
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool isDisabled;
+ nsCOMPtr<Element> el = GetElement(&isDisabled);
+ if (el && isDisabled) {
+ return false;
+ }
+
+ nsCOMPtr<nsIAtom> eventTypeAtom =
+ ConvertEventToDOMEventType(*widgetKeyboardEvent);
+ return WalkHandlersInternal(aEvent, eventTypeAtom, mHandler, false,
+ aOutReservedForChrome);
+}
+
+already_AddRefed<Element>
+nsXBLWindowKeyHandler::GetElement(bool* aIsDisabled)
+{
+ nsCOMPtr<Element> element = do_QueryReferent(mWeakPtrForElement);
+ if (element && aIsDisabled) {
+ *aIsDisabled = element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters);
+ }
+ return element.forget();
+}
+
+bool
+nsXBLWindowKeyHandler::GetElementForHandler(nsXBLPrototypeHandler* aHandler,
+ Element** aElementForHandler)
+{
+ MOZ_ASSERT(aElementForHandler);
+ *aElementForHandler = nullptr;
+
+ nsCOMPtr<nsIContent> keyContent = aHandler->GetHandlerElement();
+ if (!keyContent) {
+ return true; // XXX Even though no key element?
+ }
+
+ nsCOMPtr<Element> chromeHandlerElement = GetElement();
+ if (!chromeHandlerElement) {
+ NS_WARNING_ASSERTION(keyContent->IsInUncomposedDoc(), "uncomposed");
+ nsCOMPtr<Element> keyElement = do_QueryInterface(keyContent);
+ keyElement.swap(*aElementForHandler);
+ return true;
+ }
+
+ // We are in a XUL doc. Obtain our command attribute.
+ nsAutoString command;
+ keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
+ if (command.IsEmpty()) {
+ // There is no command element associated with the key element.
+ NS_WARNING_ASSERTION(keyContent->IsInUncomposedDoc(), "uncomposed");
+ nsCOMPtr<Element> keyElement = do_QueryInterface(keyContent);
+ keyElement.swap(*aElementForHandler);
+ return true;
+ }
+
+ // XXX Shouldn't we check this earlier?
+ nsIDocument* doc = keyContent->GetUncomposedDoc();
+ if (NS_WARN_IF(!doc)) {
+ return false;
+ }
+
+ nsCOMPtr<Element> commandElement =
+ do_QueryInterface(doc->GetElementById(command));
+ if (!commandElement) {
+ NS_ERROR("A XUL <key> is observing a command that doesn't exist. "
+ "Unable to execute key binding!");
+ return false;
+ }
+
+ commandElement.swap(*aElementForHandler);
+ return true;
+}
+
+bool
+nsXBLWindowKeyHandler::IsExecutableElement(Element* aElement) const
+{
+ if (!aElement) {
+ return false;
+ }
+
+ nsAutoString value;
+ aElement->GetAttribute(NS_LITERAL_STRING("disabled"), value);
+ if (value.EqualsLiteral("true")) {
+ return false;
+ }
+
+ aElement->GetAttribute(NS_LITERAL_STRING("oncommand"), value);
+ if (value.IsEmpty()) {
+ return false;
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+
+already_AddRefed<nsXBLWindowKeyHandler>
+NS_NewXBLWindowKeyHandler(nsIDOMElement* aElement, EventTarget* aTarget)
+{
+ RefPtr<nsXBLWindowKeyHandler> result =
+ new nsXBLWindowKeyHandler(aElement, aTarget);
+ return result.forget();
+}
diff --git a/dom/xbl/nsXBLWindowKeyHandler.h b/dom/xbl/nsXBLWindowKeyHandler.h
new file mode 100644
index 000000000..0509e85e4
--- /dev/null
+++ b/dom/xbl/nsXBLWindowKeyHandler.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXBLWindowKeyHandler_h__
+#define nsXBLWindowKeyHandler_h__
+
+#include "mozilla/EventForwards.h"
+#include "nsWeakPtr.h"
+#include "nsIDOMEventListener.h"
+
+class nsIAtom;
+class nsIDOMElement;
+class nsIDOMKeyEvent;
+class nsXBLSpecialDocInfo;
+class nsXBLPrototypeHandler;
+
+namespace mozilla {
+class EventListenerManager;
+namespace dom {
+class Element;
+class EventTarget;
+struct IgnoreModifierState;
+} // namespace dom
+} // namespace mozilla
+
+class nsXBLWindowKeyHandler : public nsIDOMEventListener
+{
+ typedef mozilla::dom::IgnoreModifierState IgnoreModifierState;
+ typedef mozilla::EventListenerManager EventListenerManager;
+
+public:
+ nsXBLWindowKeyHandler(nsIDOMElement* aElement, mozilla::dom::EventTarget* aTarget);
+
+ void InstallKeyboardEventListenersTo(
+ EventListenerManager* aEventListenerManager);
+ void RemoveKeyboardEventListenersFrom(
+ EventListenerManager* aEventListenerManager);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+protected:
+ virtual ~nsXBLWindowKeyHandler();
+
+ nsresult WalkHandlers(nsIDOMKeyEvent* aKeyEvent, nsIAtom* aEventType);
+
+ // walk the handlers, looking for one to handle the event
+ bool WalkHandlersInternal(nsIDOMKeyEvent* aKeyEvent,
+ nsIAtom* aEventType,
+ nsXBLPrototypeHandler* aHandler,
+ bool aExecute,
+ bool* aOutReservedForChrome = nullptr);
+
+ // walk the handlers for aEvent, aCharCode and aIgnoreModifierState. Execute
+ // it if aExecute = true.
+ bool WalkHandlersAndExecute(nsIDOMKeyEvent* aKeyEvent, nsIAtom* aEventType,
+ nsXBLPrototypeHandler* aHandler,
+ uint32_t aCharCode,
+ const IgnoreModifierState& aIgnoreModifierState,
+ bool aExecute,
+ bool* aOutReservedForChrome = nullptr);
+
+ // HandleEvent function for the capturing phase in the default event group.
+ void HandleEventOnCaptureInDefaultEventGroup(nsIDOMKeyEvent* aEvent);
+ // HandleEvent function for the capturing phase in the system event group.
+ void HandleEventOnCaptureInSystemEventGroup(nsIDOMKeyEvent* aEvent);
+
+ // Check if any handler would handle the given event. Optionally returns
+ // whether the command handler for the event is marked with the "reserved"
+ // attribute.
+ bool HasHandlerForEvent(nsIDOMKeyEvent* aEvent,
+ bool* aOutReservedForChrome = nullptr);
+
+ // Returns event type for matching between aWidgetKeyboardEvent and
+ // shortcut key handlers. This is used for calling WalkHandlers(),
+ // WalkHandlersInternal() and WalkHandlersAndExecute().
+ nsIAtom* ConvertEventToDOMEventType(
+ const mozilla::WidgetKeyboardEvent& aWidgetKeyboardEvent) const;
+
+ // lazily load the handlers. Overridden to handle being attached
+ // to a particular element rather than the document
+ nsresult EnsureHandlers();
+
+ // Is an HTML editable element focused
+ bool IsHTMLEditableFieldFocused();
+
+ // Returns the element which was passed as a parameter to the constructor,
+ // unless the element has been removed from the document. Optionally returns
+ // whether the disabled attribute is set on the element (assuming the element
+ // is non-null).
+ already_AddRefed<mozilla::dom::Element> GetElement(bool* aIsDisabled = nullptr);
+
+ /**
+ * GetElementForHandler() retrieves an element for the handler. The element
+ * may be a command element or a key element.
+ *
+ * @param aHandler The handler.
+ * @param aElementForHandler Must not be nullptr. The element is returned to
+ * this.
+ * @return true if the handler is valid. Otherwise, false.
+ */
+ bool GetElementForHandler(nsXBLPrototypeHandler* aHandler,
+ mozilla::dom::Element** aElementForHandler);
+
+ /**
+ * IsExecutableElement() returns true if aElement is executable.
+ * Otherwise, false. aElement should be a command element or a key element.
+ */
+ bool IsExecutableElement(mozilla::dom::Element* aElement) const;
+
+ // Using weak pointer to the DOM Element.
+ nsWeakPtr mWeakPtrForElement;
+ mozilla::dom::EventTarget* mTarget; // weak ref
+
+ // these are not owning references; the prototype handlers are owned
+ // by the prototype bindings which are owned by the docinfo.
+ nsXBLPrototypeHandler* mHandler; // platform bindings
+ nsXBLPrototypeHandler* mUserHandler; // user-specific bindings
+
+ // holds document info about bindings
+ static nsXBLSpecialDocInfo* sXBLSpecialDocInfo;
+ static uint32_t sRefCnt;
+};
+
+already_AddRefed<nsXBLWindowKeyHandler>
+NS_NewXBLWindowKeyHandler(nsIDOMElement* aElement,
+ mozilla::dom::EventTarget* aTarget);
+
+#endif
diff --git a/dom/xbl/test/bug310107-resource.xhtml b/dom/xbl/test/bug310107-resource.xhtml
new file mode 100644
index 000000000..2a3f1df35
--- /dev/null
+++ b/dom/xbl/test/bug310107-resource.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <style>
+ #bar {
+ -moz-binding: url("#binding");
+ }
+ </style>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="binding">
+ <implementation>
+ <property name="prop" readonly="true" exposeToUntrustedContent="true"
+ onget="return 2;"/>
+ </implementation>
+ </binding>
+ </bindings>
+ </head>
+ <!-- Use a timeout so that we get bfcached -->
+ <body onload="setTimeout(window.opener.runTest, 100)">
+ <div id="bar"></div>
+ </body>
+</html>
diff --git a/dom/xbl/test/chrome.ini b/dom/xbl/test/chrome.ini
new file mode 100644
index 000000000..40606de12
--- /dev/null
+++ b/dom/xbl/test/chrome.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+support-files =
+ file_bug944407.xml
+ file_bug950909.xml
+ file_fieldScopeChain.xml
+
+[test_bug378518.xul]
+[test_bug398135.xul]
+[test_bug398492.xul]
+[test_bug721452.xul]
+[test_bug723676.xul]
+[test_bug772966.xul]
+[test_bug944407.xul]
+[test_bug950909.xul]
+[test_fieldScopeChain.html]
diff --git a/dom/xbl/test/file_bug372769.xhtml b/dom/xbl/test/file_bug372769.xhtml
new file mode 100644
index 000000000..79a36be85
--- /dev/null
+++ b/dom/xbl/test/file_bug372769.xhtml
@@ -0,0 +1,181 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=372769
+-->
+<head>
+ <title>Test for Bug 372769</title>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="test1">
+ <implementation>
+ <field name="one">1</field>
+ <field name="two">9</field>
+ <field name="three">3</field>
+ <field name="four">10</field>
+ <field name="five">11</field>
+ <field name="six">this.four = 4; 6;</field>
+ <field name="seven">this.five = 5; 7;</field>
+ </implementation>
+ </binding>
+
+ <binding id="test2">
+ <implementation>
+ <!-- Tests for recursive resolves -->
+ <field name="eight">this.eight</field>
+ <field name="nine">this.ten</field>
+ <field name="ten">this.nine</field>
+ <!-- Tests for non-DOM overrides -->
+ <field name="eleven">11</field>
+ <field name="twelve">12</field>
+ <!-- Tests for DOM overrides -->
+ <field name="parentNode">this.parentNode</field>
+ <field name="ownerDocument">"ownerDocument override"</field>
+ </implementation>
+ </binding>
+
+ <binding id="test3-ancestor">
+ <implementation>
+ <field name="thirteen">"13 ancestor"</field>
+ <field name="fourteen">"14 ancestor"</field>
+ <property name="fifteen" readonly="true" onget="return '15 ancestor'"/>
+ </implementation>
+ </binding>
+
+ <binding id="test3" extends="#test3-ancestor">
+ <implementation>
+ <field name="thirteen">13</field>
+ <field name="fifteen">15</field>
+ <field name="sixteen">16</field>
+ <field name="sixteen">"16 later"</field>
+ <field name="seventeen">17</field>
+ <field name="eighteen">"18 field"</field>
+ <property name="eighteen" readonly="true" onget="return 18"/>
+ <property name="nineteen" readonly="true" onget="return 19"/>
+ <field name="nineteen">"19 field"</field>
+ </implementation>
+ </binding>
+
+ <binding id="test4">
+ <implementation>
+ <field name="twenty">for (var i in this) ; 20;</field>
+ </implementation>
+ </binding>
+
+ <binding id="test5">
+ <implementation>
+ <field name="twenty-one">for (var i in this) ; 21;</field>
+ </implementation>
+ </binding>
+</bindings>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=372769">Mozilla Bug 372769</a>
+<p id="display1" style="-moz-binding: url(#test1)"></p>
+<p id="display2" style="-moz-binding: url(#test2)"></p>
+<p id="display3" style="-moz-binding: url(#test3)"></p>
+<p id="display4" style="-moz-binding: url(#test4)"></p>
+<p id="display5" style="-moz-binding: url(#test5)"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for Bug 372769 **/
+
+SimpleTest = parent.SimpleTest;
+ok = parent.ok;
+is = parent.is;
+$ = function(x) { return document.getElementById(x); }
+
+window.onload = function() {
+ var d = $("display1");
+ is(d.one, 1, "Should be able to read field");
+
+ d.two = 2;
+ is(d.two, 2, "Should be able to write field");
+
+ is("three" in d, true, 'Should have a property named "three"');
+ is(d.three, 3, "Should be 3");
+
+ is(d.four, 10, "Unexpected value so far");
+
+ // Save "five" for now
+
+ is(d.six, 6, "Should be 6");
+
+ is(d.four, 4, "Now should be 4");
+
+ d.four = 9;
+ is(d.four, 9, "Just set it to 9");
+
+ var found = false;
+ for (var prop in d) {
+ if (prop == "seven") {
+ found = true;
+ break;
+ }
+ }
+ is(found, true, "Enumeration is broken");
+
+ is(d.four, 9, "Shouldn't have rerun field six");
+ is(d.five, 11, "Shouldn't have run field 7");
+ is(d.seven, 7, "Should be 7")
+ is(d.five, 5, "Should have run field 7");
+
+ d = $("display2");
+ is(typeof(d.eight), "undefined", "Recursive resolve should bail out");
+ is(typeof(d.nine), "undefined", "Recursive double resolve should bail out");
+ is(typeof(d.ten), "undefined",
+ "This recursive double resolve should bail out too");
+
+ // Get .eleven so it's resolved now
+ is(d.eleven, 11, "Unexpected value for .eleven");
+ var newProto = {};
+ newProto.eleven = "Proto 11";
+ newProto.twelve = "Proto 12";
+ newProto.__proto__ = d.__proto__;
+ d.__proto__ = newProto;
+ is(d.eleven, 11, "Proto should not have affected this");
+ is(d.twelve, "Proto 12", "Proto should have overridden 'twelve'");
+
+ is(d.parentNode, undefined, "We overrode this, yes we did");
+ is(typeof(d.parentNode), "undefined", "This is a recursive resolve too");
+ is(d.ownerDocument, "ownerDocument override",
+ "Should have overridden ownerDocument");
+
+ d = $("display3");
+ is(d.thirteen, 13, "descendant should win here");
+ is(d.fourteen, "14 ancestor",
+ "ancestor should win if descendant does nothing")
+ is(d.fifteen, 15,
+ "Field beats ancestor's property, since the latter lives on higher proto")
+ is(d.sixteen, 16, "First field wins");
+ is(d.__proto__.seventeen, undefined, "Shouldn't have this on proto");
+ is(typeof(d.__proto__.seventeen), "undefined",
+ "Really, should be undefined");
+ is(d.seventeen, 17, "Should have this prop on the node itself, though");
+ is(d.eighteen, 18, "Property beats field");
+ is(d.nineteen, 19, "Property still beats field");
+
+ d = $("display4");
+ is(d.twenty, 20, "Should be 20");
+
+ d = $("display5");
+ found = false;
+ for (var prop2 in d) {
+ if (prop2 == "twenty-one") {
+ found = true;
+ break;
+ }
+ }
+ is(found, true, "Enumeration is broken");
+ is(d["twenty-one"], 21, "Should be 21");
+ SimpleTest.finish();
+}
+]]>
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xbl/test/file_bug379959_cross.html b/dom/xbl/test/file_bug379959_cross.html
new file mode 100644
index 000000000..80e2be97d
--- /dev/null
+++ b/dom/xbl/test/file_bug379959_cross.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+#div1 {
+ color: green;
+ -moz-binding: url(file_bug379959_xbl.xml#xbltest);
+}
+#div2 {
+ color: green;
+ -moz-binding: url(http://example.com/tests/dom/xbl/test/file_bug379959_xbl.xml#xbltest);
+}
+</style>
+<body>
+<div id="div1"></div>
+<div id="div2"></div>
+<script>
+onload = function() {
+ // same origin should be allowed
+ var nodes1 = SpecialPowers.wrap(document).getAnonymousNodes(document.getElementById('div1'));
+ parent.postMessage({test: "sameOriginIsAllowed",
+ result: nodes1 ? nodes1.length : 0,
+ senderURL: "http://mochi.test:8888"}, "*");
+
+ // cross origin should be blocked
+ var nodes2 = SpecialPowers.wrap(document).getAnonymousNodes(document.getElementById('div2'));
+ parent.postMessage({test: "crossOriginIsBlocked",
+ result: nodes2 ? nodes2.length : 0,
+ senderURL: "http://mochi.test:8888"}, "*");
+}
+</script>
+</html>
diff --git a/dom/xbl/test/file_bug379959_data.html b/dom/xbl/test/file_bug379959_data.html
new file mode 100644
index 000000000..64fe3e45a
--- /dev/null
+++ b/dom/xbl/test/file_bug379959_data.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+#d {
+ color: green;
+ -moz-binding: url(data:text/xml;charset=utf-8,%3C%3Fxml%20version%3D%221.0%22%3F%3E%0A%3Cbindings%20id%3D%22xbltestBindings%22%20xmlns%3D%22http%3A//www.mozilla.org/xbl%22%3E%0A%20%20%3Cbinding%20id%3D%22xbltest%22%3E%3Ccontent%3EPASS%3C/content%3E%3C/binding%3E%0A%3C/bindings%3E%0A);
+}
+</style>
+<body>
+<div id="d"></div>
+<script>
+onload = function() {
+ var nodes = SpecialPowers.wrap(document).getAnonymousNodes(document.getElementById('d'));
+ parent.postMessage({test: "dataIsAllowed",
+ result: nodes ? nodes.length : 0,
+ senderURL: "http://mochi.test:8888"}, "*");
+}
+</script>
+</html>
diff --git a/dom/xbl/test/file_bug379959_xbl.xml b/dom/xbl/test/file_bug379959_xbl.xml
new file mode 100644
index 000000000..c791a2e2b
--- /dev/null
+++ b/dom/xbl/test/file_bug379959_xbl.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<bindings id="xbltestBindings" xmlns="http://www.mozilla.org/xbl">
+ <binding id="xbltest"><content>PASS</content></binding>
+</bindings>
diff --git a/dom/xbl/test/file_bug397934.xhtml b/dom/xbl/test/file_bug397934.xhtml
new file mode 100644
index 000000000..c8505aab4
--- /dev/null
+++ b/dom/xbl/test/file_bug397934.xhtml
@@ -0,0 +1,117 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=397934
+-->
+<head>
+ <title>Test for Bug 397934</title>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="ancestor">
+ <implementation>
+ <field name="testAncestor">"ancestor"</field>
+ </implementation>
+ </binding>
+ <binding id="test1" extends="#ancestor">
+ <implementation>
+ <constructor>
+ this.storage = window;
+ </constructor>
+ <field name="window"></field>
+ <field name="storage">null</field>
+ <field name="parentNode"></field>
+ <field name="ownerDocument">"ownerDocument"</field>
+ <field name="emptyTest1"></field>
+ <field name="emptyTest1">"empty1"</field>
+ <field name="emptyTest2">"empty2"</field>
+ <field name="emptyTest2"></field>
+ <field name="testAncestor"></field>
+ <field name="testEvalOnce">XPCNativeWrapper.unwrap(window).counter++; undefined</field>
+ </implementation>
+ </binding>
+ <binding id="test2" extends="#ancestor">
+ <implementation>
+ <constructor>
+ this.storage = window;
+ </constructor>
+ <field name="window">undefined</field>
+ <field name="storage">null</field>
+ <field name="parentNode">undefined</field>
+ <field name="ownerDocument">"ownerDocument"</field>
+ <field name="emptyTest1">undefined</field>
+ <field name="emptyTest1">"empty1"</field>
+ <field name="emptyTest2">"empty2"</field>
+ <field name="emptyTest2">undefined</field>
+ <field name="testAncestor">undefined</field>
+ </implementation>
+ </binding>
+ </bindings>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=397934">Mozilla Bug 397934</a>
+<p id="display1" style="-moz-binding: url(#test1)"></p>
+<p id="display2" style="-moz-binding: url(#test2)"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for Bug 397934 **/
+SimpleTest = parent.SimpleTest;
+ok = parent.ok;
+is = parent.is;
+$ = function(x) { return document.getElementById(x); }
+window.onload = function() {
+ var d;
+ d = $("display1");
+ is(d.storage, window,
+ "test1" +
+ ": |window| in the constructor should have resolved to our window");
+ is(d.ownerDocument, "ownerDocument",
+ "test1" + ": Control to test field overriding DOM prop");
+ is(d.parentNode, document.body, "test1" + ": unexpected parent");
+ is(d.emptyTest1, undefined,
+ "test1" + ": First field wins even if undefined");
+ is(typeof(d.emptyTest1), "undefined",
+ "test1" + ": First field wins even if undefined, double-check");
+ is(d.emptyTest2, "empty2",
+ "test1" + ": First field wins");
+ is(d.testAncestor, "ancestor",
+ "test1" + ": Empty field should not override ancestor binding");
+
+ var win = window;
+ win.counter = 0;
+ d.testEvalOnce;
+ d.testEvalOnce;
+ is(win.counter, 1, "Field should have evaluated once and only once");
+
+ d = $("display2");
+ is(d.storage, undefined,
+ "test2" +
+ ": |window| in the constructor should have resolved to undefined");
+ is(typeof(d.storage), "undefined",
+ "test2" +
+ ": |window| in the constructor should really have resolved to undefined");
+ is(d.ownerDocument, "ownerDocument",
+ "test2" + ": Control to test field overriding DOM prop");
+ is(d.parentNode, undefined, "test2" + ": unexpected parent");
+ is(typeof(d.parentNode), "undefined", "test2" + ": unexpected parent for real");
+ is(d.emptyTest1, undefined,
+ "test2" + ": First field wins even if undefined");
+ is(typeof(d.emptyTest1), "undefined",
+ "test2" + ": First field wins even if undefined, double-check");
+ is(d.emptyTest2, "empty2",
+ "test2" + ": First field wins");
+ is(d.testAncestor, undefined,
+ "test2" + ": Undefined field should override ancestor binding");
+ is(typeof(d.testAncestor), "undefined",
+ "test2" + ": Undefined field should really override ancestor binding");
+ SimpleTest.finish();
+}
+
+]]>
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xbl/test/file_bug481558.xbl b/dom/xbl/test/file_bug481558.xbl
new file mode 100644
index 000000000..f9ae7be1c
--- /dev/null
+++ b/dom/xbl/test/file_bug481558.xbl
@@ -0,0 +1,14 @@
+<bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<binding id="test">
+ <content>
+ <children/>
+ Binding Attached
+ </content>
+ <implementation>
+ <property name="xblBoundProperty" onget="return 1;"
+ exposeToUntrustedContent="true"/>
+ </implementation>
+</binding>
+</bindings>
diff --git a/dom/xbl/test/file_bug481558css.sjs b/dom/xbl/test/file_bug481558css.sjs
new file mode 100644
index 000000000..a44cb9c95
--- /dev/null
+++ b/dom/xbl/test/file_bug481558css.sjs
@@ -0,0 +1,17 @@
+function handleRequest(request, response)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ [name, value] = val.split('=');
+ query[name] = unescape(value);
+ });
+
+ response.setHeader("Content-Type", "text/css", false);
+ css = "#" + query.id + " { -moz-binding: url(\"";
+ if (query.server) {
+ css += "http://" + query.server + "/tests/dom/xbl/test/";
+ }
+ css += "file_bug481558.xbl#test\"); }";
+
+ response.write(css);
+}
diff --git a/dom/xbl/test/file_bug591198_inner.html b/dom/xbl/test/file_bug591198_inner.html
new file mode 100644
index 000000000..89b547a39
--- /dev/null
+++ b/dom/xbl/test/file_bug591198_inner.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <style>
+ #b {
+ -moz-binding: url("file_bug591198_xbl.xml#xbltest");
+ }
+ span {
+ white-space: nowrap;
+ }
+ </style>
+ <script>
+function sendResults() {
+ var res = {
+ widths: []
+ };
+
+ ps = document.getElementsByTagName('span');
+ for (var i = 0; i < ps.length; i++) {
+ res.widths.push(ps[i].offsetWidth);
+ }
+
+ try {
+ res.anonChildCount =
+ SpecialPowers.wrap(document).getAnonymousNodes(document.getElementById('b')).length;
+ }
+ catch (ex) {}
+
+ parent.postMessage(JSON.stringify(res), "*");
+}
+ </script>
+ </head>
+ <body onload="sendResults();">
+ <div><span id=b>long long text here</span></div>
+ <div><span>long long text here</span></div>
+ <div><span>PASS</span></div>
+ </body>
+</html>
diff --git a/dom/xbl/test/file_bug591198_xbl.xml b/dom/xbl/test/file_bug591198_xbl.xml
new file mode 100644
index 000000000..f69959b47
--- /dev/null
+++ b/dom/xbl/test/file_bug591198_xbl.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<bindings id="xbltestBindings" xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+ <binding id="xbltest"><content>PASS<html:b style="display:none"><children/></html:b></content></binding>
+</bindings>
diff --git a/dom/xbl/test/file_bug821850.xhtml b/dom/xbl/test/file_bug821850.xhtml
new file mode 100644
index 000000000..0d68a908f
--- /dev/null
+++ b/dom/xbl/test/file_bug821850.xhtml
@@ -0,0 +1,299 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=821850
+-->
+<head>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="testBinding">
+ <implementation>
+ <constructor>
+ // Store a property as an expando on the bound element.
+ this._prop = "propVal";
+
+ // Wait for both constructors to fire.
+ window.constructorCount = (window.constructorCount + 1) || 1;
+ if (window.constructorCount != 3)
+ return;
+
+ // Grab some basic infrastructure off the content window.
+ var win = XPCNativeWrapper.unwrap(window);
+ SpecialPowers = win.SpecialPowers;
+ Cu = SpecialPowers.Cu;
+ is = win.is;
+ ok = win.ok;
+ SimpleTest = win.SimpleTest;
+
+ // Stick some expandos on the content window.
+ window.xrayExpando = 3;
+ win.primitiveExpando = 11;
+ win.stringExpando = "stringExpando";
+ win.objectExpando = { foo: 12 };
+ win.globalExpando = SpecialPowers.unwrap(Cu.getGlobalForObject({}));
+ win.functionExpando = function() { return "called" };
+ win.functionExpando.prop = 2;
+
+ // Make sure we're Xraying.
+ ok(Cu.isXrayWrapper(window), "Window is Xrayed");
+ ok(Cu.isXrayWrapper(document), "Document is Xrayed");
+
+ var bound = document.getElementById('bound');
+ var bound2 = document.getElementById('bound2');
+ var bound3 = document.getElementById('bound3');
+ ok(bound, "bound is non-null");
+ is(bound.method('baz'), "method:baz", "Xray methods work");
+ is(bound.prop, "propVal", "Property Xrays work");
+ is(bound.primitiveField, undefined, "Xrays don't show fields");
+ is(bound.wrappedJSObject.primitiveField, 2, "Waiving Xrays show fields");
+
+ // Check exposure behavior.
+ is(typeof bound.unexposedMethod, 'function',
+ "Unexposed method should be visible to XBL");
+ is(typeof bound.wrappedJSObject.unexposedMethod, 'undefined',
+ "Unexposed method should not be defined in content");
+ is(typeof bound.unexposedProperty, 'number',
+ "Unexposed property should be visible to XBL");
+ is(typeof bound.wrappedJSObject.unexposedProperty, 'undefined',
+ "Unexposed property should not be defined in content");
+
+ // Check that here HTMLImageElement.QueryInterface works
+ var img = document.querySelector("img");
+ ok("QueryInterface" in img,
+ "Should have a img.QueryInterface here");
+ is(img.QueryInterface(Components.interfaces.nsIImageLoadingContent),
+ img, "Should be able to QI the image");
+
+ // Make sure standard constructors work right in the presence of
+ // sandboxPrototype and Xray-resolved constructors.
+ is(window.Function, XPCNativeWrapper(window.wrappedJSObject.Function),
+ "window.Function comes from the window, not the global");
+ ok(Function != window.Function, "Function constructors are distinct");
+ is(Object.getPrototypeOf(Function.prototype), Object.getPrototypeOf({foo: 42}),
+ "Function constructor is local");
+
+ ok(Object.getPrototypeOf(bound.wrappedJSObject) == Object.getPrototypeOf(bound2.wrappedJSObject),
+ "Div and div should have the same content-side prototype");
+ ok(Object.getPrototypeOf(bound.wrappedJSObject) != Object.getPrototypeOf(bound3.wrappedJSObject),
+ "Div and span should have different content-side prototypes");
+ ok(bound.wrappedJSObject.method == bound3.wrappedJSObject.method,
+ "Methods should be shared");
+
+ // This gets invoked by an event handler.
+ window.finish = function() {
+ // Content messed with stuff. Make sure we still see the right thing.
+ is(bound.method('bay'), "method:bay", "Xray methods work");
+ is(bound.wrappedJSObject.method('bay'), "hah", "Xray waived methods work");
+ is(bound.prop, "set:someOtherVal", "Xray props work");
+ is(bound.wrappedJSObject.prop, "redefined", "Xray waived props work");
+ is(bound.wrappedJSObject.primitiveField, 321, "Can't do anything about redefined fields");
+
+ SimpleTest.finish();
+ }
+
+ // Hand things off to content. Content will call us back.
+ win.go();
+ </constructor>
+ <field name="primitiveField">2</field>
+ <method name="unexposedMethod"><body></body></method>
+ <property name="unexposedProperty" onget="return 2;" readonly="true"></property>
+ <method name="method" exposeToUntrustedContent="true">
+ <parameter name="arg" />
+ <body>
+ return "method:" + arg;
+ </body>
+ </method>
+ <method name="passMeAJSObject" exposeToUntrustedContent="true">
+ <parameter name="arg" />
+ <body>
+ is(typeof arg.prop, 'undefined', "No properties");
+ is(arg.wrappedJSObject.prop, 2, "Underlying object has properties");
+ is(Object.getOwnPropertyNames(arg).length, 0, "Should have no own properties");
+ is(Object.getOwnPropertyNames(arg.wrappedJSObject).length, 1, "Underlying object has properties");
+ arg.foo = 2;
+ is(arg.foo, 2, "Should place expandos");
+ is(typeof arg.wrappedJSObject.foo, 'undefined', "Expandos should be invisible to content");
+ </body>
+ </method>
+ <property name="prop" exposeToUntrustedContent="true">
+ <getter>return this._prop;</getter>
+ <setter>this._prop = "set:" + val;</setter>
+ </property>
+ </implementation>
+ <handlers>
+ <handler event="testevent" action="ok(true, 'called event handler'); finish();" allowuntrusted="true"/>
+ <handler event="testtrusted" action="ok(true, 'called trusted handler'); window.wrappedJSObject.triggeredTrustedHandler = true;"/>
+ <handler event="keyup" action="ok(true, 'called untrusted key handler'); window.wrappedJSObject.triggeredUntrustedKeyHandler = true;" allowuntrusted="true"/>
+ <handler event="keydown" action="ok(true, 'called trusted key handler'); window.wrappedJSObject.triggeredTrustedKeyHandler = true;"/>
+ </handlers>
+ </binding>
+ </bindings>
+ <script type="application/javascript">
+ <![CDATA[
+
+ ok = parent.ok;
+ is = parent.is;
+ SimpleTest = parent.SimpleTest;
+ SpecialPowers = parent.SpecialPowers;
+
+ // Test the Xray waiving behavior when accessing fields. We should be able to
+ // see sequential JS-implemented properties, but should regain Xrays when we
+ // hit a native property.
+ window.contentVal = { foo: 10, rabbit: { hole: { bar: 100, win: window} } };
+ ok(true, "Set contentVal");
+
+ // Check that we're not exposing QueryInterface to non-XBL code
+ ok(!("QueryInterface" in document),
+ "Should not have a document.QueryInterface here");
+
+ function go() {
+ "use strict";
+
+ // Test what we can and cannot access in the XBL scope.
+ is(typeof window.xrayExpando, "undefined", "Xray expandos are private to the caller");
+ is(window.primitiveExpando, 11, "Can see waived expandos");
+ is(window.stringExpando, "stringExpando", "Can see waived expandos");
+ is(typeof window.objectExpando, "object", "object expando exists");
+ checkThrows(() => window.objectExpando.foo);
+ is(SpecialPowers.wrap(window.objectExpando).foo, 12, "SpecialPowers sees the right thing");
+ is(typeof window.globalExpando, "object", "Can see global object");
+ checkThrows(() => window.globalExpando.win);
+ is(window.functionExpando(), "called", "XBL functions are callable");
+ checkThrows(() => window.functionExpando.prop);
+
+ // Inspect the bound element.
+ var bound = document.getElementById('bound');
+ is(bound.primitiveField, 2, "Can see primitive fields");
+ is(bound.method("foo"), "method:foo", "Can invoke XBL method from content");
+ is(bound.prop, "propVal", "Can access properties from content");
+ bound.prop = "someOtherVal";
+ is(bound.prop, "set:someOtherVal", "Can set properties from content");
+
+ // Make sure that XBL scopes get opaque wrappers.
+ //
+ // Note: Now that we have object Xrays, we have to pass in a more obscure
+ // ES type that we don't Xray to test this.
+ var proto = bound.__proto__;
+ var nonXrayableObject = new WeakMap();
+ nonXrayableObject.prop = 2;
+ proto.passMeAJSObject(nonXrayableObject);
+
+ //
+ // Try sticking a bunch of stuff on the prototype object.
+ //
+
+ proto.someExpando = 201;
+ is(bound.someExpando, 201, "Can stick non-XBL properties on the proto");
+
+ // Previously, this code checked that content couldn't tamper with its XBL
+ // prototype. But we decided to allow this to reduce regression risk, so for
+ // now just check that this works.
+ function checkMayTamper(obj, propName, desc) {
+ var accessor = !('value' in Object.getOwnPropertyDescriptor(obj, propName));
+ if (!accessor)
+ checkAllowed(function() { obj[propName] = function() {} }, desc + ": assign");
+ checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true, value: 3}) }, desc + ": define with value");
+ checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true, writable: true}) }, desc + ": make writable");
+ checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true}) }, desc + ": make configurable");
+ checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true, get: function() {}}) }, desc + ": define with getter");
+ checkAllowed(function() { Object.defineProperty(obj, propName, {configurable: true, set: function() {}}) }, desc + ": define with setter");
+
+ // Windows are implemented as proxies, and Proxy::delete_ doesn't currently
+ // pass strict around. Work around it in the window.binding case by just
+ // checking if delete returns false.
+ // manually.
+ checkAllowed(function() { delete obj[propName]; }, desc + ": delete");
+
+ if (!accessor)
+ checkAllowed(function() { obj[propName] = function() {} }, desc + ": assign (again)");
+ }
+
+ // Make sure content can do whatever it wants with the prototype.
+ checkMayTamper(proto, 'method', "XBL Proto Method");
+ checkMayTamper(proto, 'prop', "XBL Proto Prop");
+ checkMayTamper(proto, 'primitiveField', "XBL Field Accessor");
+
+ // Tamper with the derived object. This doesn't affect the XBL scope thanks
+ // to Xrays.
+ bound.method = function() { return "heh"; };
+ Object.defineProperty(bound, 'method', {value: function() { return "hah" }});
+ Object.defineProperty(bound, 'prop', {value: "redefined"});
+ bound.primitiveField = 321;
+
+ // Untrusted events should not trigger event handlers without
+ // exposeToUntrustedContent=true.
+ window.triggeredTrustedHandler = false;
+ var untrustedEvent = new CustomEvent('testtrusted');
+ is(untrustedEvent.type, 'testtrusted', "Constructor should see type");
+ bound.dispatchEvent(untrustedEvent);
+ ok(!window.triggeredTrustedHandler, "untrusted events should not trigger trusted handler");
+ var trustedEvent = new CustomEvent('testtrusted');
+ is(trustedEvent.type, 'testtrusted', "Wrapped constructor should see type");
+ SpecialPowers.wrap(bound).dispatchEvent(trustedEvent); // Dispatch over SpecialPowers to make the event trusted.
+ ok(window.triggeredTrustedHandler, "trusted events should trigger trusted handler");
+
+ //
+ // We check key events as well, since they're implemented differently.
+ //
+ // NB: We don't check isTrusted on the events we create here, because
+ // according to smaug, old-style event initialization doesn't mark the
+ // event as trusted until it's dispatched.
+ //
+
+ window.triggeredUntrustedKeyHandler = false;
+ window.triggeredTrustedKeyHandler = false;
+
+ // Untrusted event, permissive handler.
+ var untrustedKeyEvent = document.createEvent('KeyboardEvent');
+ untrustedKeyEvent.initEvent('keyup', true, true);
+ bound.dispatchEvent(untrustedKeyEvent);
+ ok(window.triggeredUntrustedKeyHandler, "untrusted key events should trigger untrusted handler");
+
+ // Untrusted event, strict handler.
+ var fakeTrustedKeyEvent = document.createEvent('KeyboardEvent');
+ fakeTrustedKeyEvent.initEvent('keydown', true, true);
+ bound.dispatchEvent(fakeTrustedKeyEvent);
+ ok(!window.triggeredTrustedKeyHandler, "untrusted key events should not trigger trusted handler");
+
+ // Trusted event, strict handler. Dispatch using SpecialPowers to make the event trusted.
+ var trustedKeyEvent = document.createEvent('KeyboardEvent');
+ trustedKeyEvent.initEvent('keydown', true, true);
+ SpecialPowers.wrap(bound).dispatchEvent(trustedKeyEvent);
+ ok(window.triggeredTrustedKeyHandler, "trusted key events should trigger trusted handler");
+
+ // Hand control back to the XBL scope by dispatching an event on the bound element.
+ bound.dispatchEvent(new CustomEvent('testevent'));
+ }
+
+ function checkThrows(fn) {
+ try { fn(); ok(false, "Should have thrown"); }
+ catch (e) { ok(!!/denied|insecure/.exec(e), "Should have thrown security exception: " + e); }
+ }
+
+ function checkAllowed(fn, desc) {
+ try { fn(); ok(true, desc + ": Didn't throw"); }
+ catch (e) { ok(false, desc + ": Threw: " + e); }
+ }
+
+ function setup() {
+ // When the bindings are applied, the constructor will be invoked and the
+ // test will continue.
+ document.getElementById('bound').style.MozBinding = 'url(#testBinding)';
+ document.getElementById('bound2').style.MozBinding = 'url(#testBinding)';
+ document.getElementById('bound3').style.MozBinding = 'url(#testBinding)';
+ }
+
+ ]]>
+</script>
+</head>
+<body onload="setup()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=821850">Mozilla Bug 821850</a>
+<p id="display"></p>
+<div id="content">
+ <div id="bound">Bound element</div>
+ <div id="bound2">Bound element</div>
+ <span id="bound3">Bound element</span>
+ <img/>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/xbl/test/file_bug844783.xhtml b/dom/xbl/test/file_bug844783.xhtml
new file mode 100644
index 000000000..a2e04edf2
--- /dev/null
+++ b/dom/xbl/test/file_bug844783.xhtml
@@ -0,0 +1,54 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=844783
+-->
+<head>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="testBinding">
+ <implementation>
+ <constructor>
+ is(windowExpando, 42, "Expandos show up without explicit waiving");
+ someFunction();
+ ok(called, "Function called");
+ go();
+ </constructor>
+ </implementation>
+ <handlers>
+ <handler event="testevent" action="is(boundExpando, 11, 'See expandos on |this|'); SimpleTest.finish();"/>
+ </handlers>
+ </binding>
+ </bindings>
+ <script type="application/javascript">
+ <![CDATA[
+
+ is = parent.is;
+ ok = parent.ok;
+ SimpleTest = parent.SimpleTest;
+ window.windowExpando = 42;
+ window.someFunction = function() { ok(true, "Called"); window.called = true; };
+
+ function go() {
+ var bound = document.getElementById('bound');
+ bound.boundExpando = 11;
+ bound.dispatchEvent(new CustomEvent('testevent'));
+ }
+
+ function setup() {
+ // When the bindings are applied, the constructor will be invoked and the
+ // test will continue.
+ document.getElementById('bound').style.MozBinding = 'url(#testBinding)';
+ }
+
+ ]]>
+</script>
+</head>
+<body onload="setup()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=844783">Mozilla Bug 844783</a>
+<p id="display"></p>
+<div id="content">
+ <div id="bound">Bound element</div>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/xbl/test/file_bug944407.html b/dom/xbl/test/file_bug944407.html
new file mode 100644
index 000000000..3e4a0e80f
--- /dev/null
+++ b/dom/xbl/test/file_bug944407.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="deny" style="-moz-binding: url(file_bug944407.xml#testAllowScript)"></div>
+<div id="allow" style="-moz-binding: url(chrome://mochitests/content/chrome/dom/xbl/test/file_bug944407.xml#testAllowScript)"</div>
+<script>/* Flush layout with a script tab - see bug 944407 comment 37. */</script>
+</body>
+</html>
diff --git a/dom/xbl/test/file_bug944407.xml b/dom/xbl/test/file_bug944407.xml
new file mode 100644
index 000000000..e48271b9f
--- /dev/null
+++ b/dom/xbl/test/file_bug944407.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<bindings id="testBindings" xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+ <binding id="testAllowScript" bindToUntrustedContent="true">
+ <implementation>
+ <property name="someProp" onget="return 2;" readonly="true"></property>
+ <method name="someMethod"><body> return 3; </body></method>
+ <method name="startTest">
+ <body>
+ <![CDATA[
+ // Make sure we only get constructed when we're loaded from a domain
+ // with script enabled.
+ is(this.id, 'allow', "XBL should only be bound when the origin of the binding allows scripts");
+
+ var t = this;
+ doFinish = function() {
+ // Take a moment to make sure that other constructors don't run when they shouldn't.
+ if (t.id == 'allow')
+ setTimeout(SpecialPowers.wrap(window.parent).finish, 100);
+ }
+
+ onTestEvent = function(target) {
+ ok(true, 'called event handler');
+
+ // Try calling event handlers on anonymous content.
+ var e = new MouseEvent('click');
+ document.getAnonymousNodes(target)[1].dispatchEvent(e);
+ ok(window.calledEventHandlerOnAC, "Should invoke event handler on AC");
+
+ // Now, dispatch a key event to test key handlers and move the test along.
+ var k = document.createEvent('KeyboardEvent');
+ k.initEvent('keyup', true, true);
+ target.dispatchEvent(k);
+ }
+
+ // Check the implementation.
+ is(this.someProp, 2, "Properties work");
+ is(this.someMethod(), 3, "Methods work");
+
+ // Kick over to the event handlers. This tests XBL event handlers,
+ // XBL key handlers, and event handlers on anonymous content.
+ this.dispatchEvent(new CustomEvent('testEvent'));
+ ]]>
+ </body>
+ </method>
+
+ <constructor>
+ <![CDATA[
+ win = XPCNativeWrapper.unwrap(window);
+ SpecialPowers = win.SpecialPowers;
+ ok = win.ok = SpecialPowers.wrap(window.parent).ok;
+ is = win.is = SpecialPowers.wrap(window.parent).is;
+ info = win.info = SpecialPowers.wrap(window.parent).info;
+
+ info("Invoked constructor for " + this.id);
+
+ var t = this;
+ window.addEventListener('load', function loadListener() {
+ window.removeEventListener('load', loadListener);
+ // Wait two refresh-driver ticks to make sure that the constructor runs
+ // for both |allow| and |deny| if it's ever going to.
+ //
+ // See bug 944407 comment 37.
+ info("Invoked load listener for " + t.id);
+ window.requestAnimationFrame(function() { window.requestAnimationFrame(t.startTest.bind(t)); });
+ });
+ ]]>
+ </constructor>
+ </implementation>
+ <handlers>
+ <handler event="testEvent" action="onTestEvent(this)" allowuntrusted="true"/>
+ <handler event="keyup" action="ok(true, 'called key handler'); doFinish();" allowuntrusted="true"/>
+ </handlers>
+ <content>Anonymous Content<html:div onclick="window.calledEventHandlerOnAC = true;"></html:div><html:b style="display:none"><children/></html:b></content>
+ </binding>
+</bindings>
diff --git a/dom/xbl/test/file_bug946815.xhtml b/dom/xbl/test/file_bug946815.xhtml
new file mode 100644
index 000000000..ca8718b7b
--- /dev/null
+++ b/dom/xbl/test/file_bug946815.xhtml
@@ -0,0 +1,97 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=946815
+-->
+<head>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="testBinding">
+ <implementation>
+ <constructor>
+ // Grab some basic infrastructure off the content window.
+ var win = XPCNativeWrapper.unwrap(window);
+ SpecialPowers = win.SpecialPowers;
+ Cu = SpecialPowers.Cu;
+ is = win.is;
+ ok = win.ok;
+ SimpleTest = win.SimpleTest;
+
+ var bound = document.getElementById('bound');
+
+ // This gets invoked by an event handler.
+ window.finish = function() {
+ // XBL scope, with 'wifi-manage' scope
+ testWifiPermissionFromXbl(true, true /* with wifi-manage permission */);
+ SimpleTest.finish();
+ }
+
+ eval('var testWifiPermissionFromXbl = ' + win.testWifiPermissionFromContent.toSource());
+
+ // XBL scope, with no 'wifi-manage' permission
+ testWifiPermissionFromXbl(true, false /* without wifi-manage permission */);
+
+ // Hand things off to content. Content will call us back.
+ win.go();
+ </constructor>
+ </implementation>
+ <handlers>
+ <handler event="testevent" action="ok(true, 'called event handler'); finish();" allowuntrusted="true"/>
+ </handlers>
+ </binding>
+ </bindings>
+ <script type="application/javascript">
+ <![CDATA[
+
+ ok = parent.ok;
+ is = parent.is;
+ SimpleTest = parent.SimpleTest;
+ SpecialPowers = parent.SpecialPowers;
+
+ function go() {
+ "use strict";
+
+ // Content scope, with no 'wifi-manage' permission
+ testWifiPermissionFromContent(false, false /* without wifi-manage permission */);
+
+ SpecialPowers.pushPermissions([{ "type": "wifi-manage", "allow": 1, "context": window.document }], function() {
+ testWifiPermissionFromContent(false, true /* with wifi-manage permission */);
+ // Hand control back to the XBL scope by dispatching an event on the bound element.
+ bound.dispatchEvent(new CustomEvent('testevent'));
+ });
+ }
+
+ function testWifiPermissionFromContent(aIsXblScope, aExpectedWifiPermission) {
+ // Even though the function name has suggested we are either in content or
+ // XBL scope, the argument |aIsXblScope| is still required to print
+ // descriptive enough message for the test.
+
+ // If this test fails, something must be wrong with the permission manipulation.
+ // Check test_bug946815.html to see if we removed wifi-manage permission in
+ // the beginning and if we forgot to add permission back during testing.
+ is(aExpectedWifiPermission, SpecialPowers.hasPermission("wifi-manage", window.document),
+ "'wifi-manage' permission is not as expected! Expected: " + aExpectedWifiPermission);
+
+ is(typeof window.MozWifiP2pManager, (aExpectedWifiPermission ? "function" : "undefined"),
+ (aIsXblScope ? "XBL" : "Content") + " should" + (aExpectedWifiPermission ? "" : " NOT") +
+ " see MozWifiP2pManager with" + (aExpectedWifiPermission ? "" : "out") +
+ " 'wifi-manage' permission." );
+ }
+
+ function setup() {
+ // When the bindings are applied, the constructor will be invoked and the
+ // test will continue.
+ document.getElementById('bound').style.MozBinding = 'url(#testBinding)';
+ }
+
+ ]]>
+</script>
+</head>
+<body onload="setup()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=946815">Mozilla Bug 946815</a>
+<p id="display"></p>
+<div id="content">
+ <div id="bound">Bound element</div>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/xbl/test/file_bug950909.html b/dom/xbl/test/file_bug950909.html
new file mode 100644
index 000000000..a33d9bbf8
--- /dev/null
+++ b/dom/xbl/test/file_bug950909.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div style="-moz-binding: url(chrome://mochitests/content/chrome/dom/xbl/test/file_bug950909.xml#testBinding"></div>
+</body>
+</html>
diff --git a/dom/xbl/test/file_bug950909.xml b/dom/xbl/test/file_bug950909.xml
new file mode 100644
index 000000000..c711d9ed4
--- /dev/null
+++ b/dom/xbl/test/file_bug950909.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<bindings id="chromeTestBindings" xmlns="http://www.mozilla.org/xbl">
+ <binding id="testBinding" bindToUntrustedContent="true">
+ <implementation implements="nsIObserver">
+ <constructor>
+ <![CDATA[
+ // This binding gets applied to a content object, and thus is actually
+ // running in a content XBL scope.
+ var win = XPCNativeWrapper.unwrap(window);
+ var SpecialPowers = win.SpecialPowers;
+ var ok = SpecialPowers.unwrap(SpecialPowers.wrap(window).parent.ok);
+ ok(win != window, "Should be running in an XBL scope with Xrays");
+
+ // Generate an XPCWrappedJS for the reflector. We need to do this
+ // explicitly with a testing helper, because we're converging on
+ // removing XPConnect from the web, which means that it won't be
+ // possible to generate an XPCWrappedJS from content (or XBL) code.
+ var scope = {};
+ var holder = SpecialPowers.Cu.generateXPCWrappedJS(this, scope);
+
+ // Now, QI |this|, which will generate an aggregated native.
+ this.QueryInterface(Components.interfaces.nsIObserver);
+
+ ok(true, "Didn't assert or crash");
+
+ SpecialPowers.wrap(window).parent.SimpleTest.finish();
+ ]]>
+ </constructor>
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body><![CDATA[]]></body>
+ </method>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/dom/xbl/test/file_fieldScopeChain.xml b/dom/xbl/test/file_fieldScopeChain.xml
new file mode 100644
index 000000000..80e6cf477
--- /dev/null
+++ b/dom/xbl/test/file_fieldScopeChain.xml
@@ -0,0 +1,8 @@
+<bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+ <binding id="foo">
+ <implementation>
+ <field name="bar">baz</field>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/dom/xbl/test/mochitest.ini b/dom/xbl/test/mochitest.ini
new file mode 100644
index 000000000..2804d501f
--- /dev/null
+++ b/dom/xbl/test/mochitest.ini
@@ -0,0 +1,43 @@
+[DEFAULT]
+support-files =
+ bug310107-resource.xhtml
+ file_bug372769.xhtml
+ file_bug379959_cross.html
+ file_bug379959_data.html
+ file_bug379959_xbl.xml
+ file_bug397934.xhtml
+ file_bug481558.xbl
+ file_bug481558css.sjs
+ file_bug591198_inner.html
+ file_bug591198_xbl.xml
+ file_bug821850.xhtml
+ file_bug844783.xhtml
+ file_bug944407.html
+ file_bug944407.xml
+ file_bug946815.xhtml
+ file_bug950909.html
+
+[test_bug310107.html]
+[test_bug366770.html]
+[test_bug371724.xhtml]
+[test_bug372769.html]
+[test_bug378866.xhtml]
+[test_bug379959.html]
+[test_bug389322.xhtml]
+[test_bug397934.html]
+[test_bug400705.xhtml]
+[test_bug401907.xhtml]
+[test_bug403162.xhtml]
+[test_bug468210.xhtml]
+[test_bug481558.html]
+[test_bug526178.xhtml]
+[test_bug542406.xhtml]
+[test_bug591198.html]
+[test_bug639338.xhtml]
+[test_bug790265.xhtml]
+[test_bug821850.html]
+[test_bug844783.html]
+[test_bug872273.xhtml]
+[test_bug1086996.xhtml]
+[test_bug1098628_throw_from_construct.xhtml]
+[test_bug1359859.xhtml] \ No newline at end of file
diff --git a/dom/xbl/test/test_bug1086996.xhtml b/dom/xbl/test/test_bug1086996.xhtml
new file mode 100644
index 000000000..c60855a00
--- /dev/null
+++ b/dom/xbl/test/test_bug1086996.xhtml
@@ -0,0 +1,62 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1086996
+-->
+<head>
+ <bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+ <binding id="handlerBinding">
+ <implementation>
+ <constructor>
+ <![CDATA[XPCNativeWrapper.unwrap(window).constructedHandlerBinding();]]>
+ </constructor>
+ </implementation>
+ <handlers>
+ <handler event="testevent" action="XPCNativeWrapper.unwrap(window).gotEvent();" allowuntrusted="true"/>
+ </handlers>
+ </binding>
+ <binding id="mainBinding">
+ <content>
+ <html:p id="acWithBinding" style="-moz-binding: url(#handlerBinding)"> Anonymous Content</html:p>
+ </content>
+ <implementation>
+ <method name="doEventDispatch" exposeToUntrustedContent="true">
+ <body>
+ <![CDATA[document.getAnonymousNodes(this)[0].dispatchEvent(new CustomEvent('testevent'));]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+ </bindings>
+ <title>Test for Bug 1086996</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1086996">Mozilla Bug 1086996</a>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 1086996 **/
+SimpleTest.waitForExplicitFinish();
+function constructedHandlerBinding() {
+ ok(true, "Constructed handler binding!");
+ setTimeout(function() { $('boundContent').doEventDispatch(); }, 0);
+}
+
+function gotEvent() {
+ ok(true, "Successfully triggered event handler");
+ SimpleTest.finish();
+}
+
+]]>
+</script>
+</pre>
+<!-- This div needs to come after the <script> so we don't run the binding ctor
+ before the <script> has been parsed -->
+<div id="boundContent" style="-moz-binding: url(#mainBinding)"></div>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug1098628_throw_from_construct.xhtml b/dom/xbl/test/test_bug1098628_throw_from_construct.xhtml
new file mode 100644
index 000000000..22bc88798
--- /dev/null
+++ b/dom/xbl/test/test_bug1098628_throw_from_construct.xhtml
@@ -0,0 +1,40 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1098628
+-->
+<head>
+ <title>Test for Bug 1098628</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script class="testbody" type="text/javascript">
+ <![CDATA[
+
+ /** Test for Bug 1098628 **/
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.monitorConsole(SimpleTest.finish, [{errorMessage: new RegExp('flimfniffle')}]);
+ addLoadEvent(function() {
+ SimpleTest.executeSoon(SimpleTest.endMonitorConsole);
+ });
+ ]]>
+ </script>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="test">
+ <implementation>
+ <constructor><![CDATA[
+ throw "flimfniffle";
+ ]]></constructor>
+ </implementation>
+ </binding>
+ </bindings>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1098628">Mozilla Bug 1098628</a>
+<p id="display" style="-moz-binding: url(#test)"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
diff --git a/dom/xbl/test/test_bug1359859.xhtml b/dom/xbl/test/test_bug1359859.xhtml
new file mode 100644
index 000000000..564c2f96d
--- /dev/null
+++ b/dom/xbl/test/test_bug1359859.xhtml
@@ -0,0 +1,41 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1359859
+ -->
+ <head>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="testBinding">
+ <implementation>
+ <constructor>
+ XPCNativeWrapper.unwrap(window).running();
+ this.constructed = true;
+ throw new Error("Constructor threw");
+ </constructor>
+ <field name="throwingField">throw new Error("field threw")</field>
+ <field name="normalField">"hello"</field>
+ </implementation>
+ </binding>
+ </bindings>
+ <script>
+ // We need to wait for the binding to load.
+ SimpleTest.waitForExplicitFinish();
+ function running() {
+ // Wait for the rest of the constructor to run
+ SimpleTest.executeSoon(function() {
+ is(document.getElementById("bound").throwingField, undefined,
+ "Should not have a value for a throwing field");
+ is(document.getElementById("bound").normalField, "hello",
+ "Binding should be installed");
+ // The real test is that we haven't gotten any error events so far.
+ SimpleTest.finish();
+ });
+ }
+ </script>
+ </head>
+ <body>
+ <div id="bound" style="-moz-binding: url(#testBinding)"/>
+ </body>
+</html>
+
diff --git a/dom/xbl/test/test_bug310107.html b/dom/xbl/test/test_bug310107.html
new file mode 100644
index 000000000..17e642b97
--- /dev/null
+++ b/dom/xbl/test/test_bug310107.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=310107
+-->
+<head>
+ <title>Test for Bug 310107</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=310107">Mozilla Bug 310107</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 310107 **/
+SimpleTest.waitForExplicitFinish();
+
+var win = window.open("bug310107-resource.xhtml", "", "width=10, height=10");
+
+function runTest() {
+ window.el = win.document.getElementById("bar");
+ window.doc = win.document;
+ is(window.el.prop, 2, "Unexpected prop value at load");
+
+ win.location = "data:text/html,<body onload='window.opener.loadDone()'>";
+}
+
+function loadDone() {
+ is(window.el.prop, 2, "Prop still 2 because we're in bfcache");
+ is(window.el, window.doc.getElementById("bar"),
+ "document didn't get bfcached?");
+
+ // Remove our node from the DOM
+ window.el.parentNode.removeChild(window.el);
+
+ is(window.el.prop, undefined, "Binding should have been detached");
+ is(typeof(window.el.prop), "undefined", "Really undefined");
+
+ win.close();
+
+ SimpleTest.finish();
+}
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xbl/test/test_bug366770.html b/dom/xbl/test/test_bug366770.html
new file mode 100644
index 000000000..d3314ecca
--- /dev/null
+++ b/dom/xbl/test/test_bug366770.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=366770
+-->
+<head>
+ <title>Test for Bug 366770</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=366770">Mozilla Bug 366770</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+
+ <!-- data: URI below corresponds to:
+ <?xml version="1.0"?>
+ <bindings id="xbltestBindings" xmlns="http://www.mozilla.org/xbl">
+ <binding id="xbltest">
+ <content>PASS</content>
+ <implementation>
+ <constructor>
+ var win = XPCNativeWrapper.unwrap(window);
+ win.document.bindingConstructorRan = true;
+ win.ok(true, "binding URI with no fragment applied");
+ win.SimpleTest.finish();
+ </constructor>
+ </implementation>
+ </binding>
+ </bindings>
+ -->
+ <span id="span" style="-moz-binding: url(data:text/xml;base64,ICA8YmluZGluZ3MgaWQ9InhibHRlc3RCaW5kaW5ncyIgeG1sbnM9Imh0dHA6Ly93d3cubW96aWxsYS5vcmcveGJsIj4KICAgIDxiaW5kaW5nIGlkPSJ4Ymx0ZXN0Ij4KICAgICAgPGNvbnRlbnQ+UEFTUzwvY29udGVudD4KICAgICAgPGltcGxlbWVudGF0aW9uPgogICAgICAgIDxjb25zdHJ1Y3Rvcj4KICAgICAgICAgIHZhciB3aW4gPSBYUENOYXRpdmVXcmFwcGVyLnVud3JhcCh3aW5kb3cpOwogICAgICAgICAgd2luLmRvY3VtZW50LmJpbmRpbmdDb25zdHJ1Y3RvclJhbiA9IHRydWU7CiAgICAgICAgICB3aW4ub2sodHJ1ZSwgImJpbmRpbmcgVVJJIHdpdGggbm8gZnJhZ21lbnQgYXBwbGllZCIpOwogICAgICAgICAgd2luLlNpbXBsZVRlc3QuZmluaXNoKCk7CiAgICAgICAgPC9jb25zdHJ1Y3Rvcj4KICAgICAgPC9pbXBsZW1lbnRhdGlvbj4KICAgIDwvYmluZGluZz4KICA8L2JpbmRpbmdzPg==);"></span>
+
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ /** Test for Bug 366770 **/
+ SimpleTest.waitForExplicitFinish();
+ </script>
+ </pre>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug371724.xhtml b/dom/xbl/test/test_bug371724.xhtml
new file mode 100644
index 000000000..69a317fde
--- /dev/null
+++ b/dom/xbl/test/test_bug371724.xhtml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=371724
+-->
+<head>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="rd">
+ <implementation>
+ <property name="hallo" onget="return true;" readonly="true" exposeToUntrustedContent="true"/>
+ </implementation>
+ </binding>
+ </bindings>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=371724">Mozilla Bug 371724</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <div id="a" style="-moz-binding:url('#rd');">Success!</div>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ /** Test for Bug 371724 **/
+ SimpleTest.waitForExplicitFinish();
+ function checkReadOnly() {
+ var elt = document.getElementById('a');
+ var oldValue = elt.hallo;
+ var actual;
+ try {
+ elt.hallo = false;
+ actual = elt.hallo;
+ } catch (ex) {
+ actual = "" + ex;
+ }
+ is(actual, true,
+ "Setting a readonly xbl property should do nothing if !strict");
+ checkReadOnlyStrict();
+ }
+ function checkReadOnlyStrict() {
+ "use strict";
+ var elt = document.getElementById('a');
+ var actual;
+ try {
+ elt.hallo = false;
+ actual = "should have thrown";
+ } catch (ex) {
+ actual = ex instanceof TypeError;
+ }
+ is(actual, true,
+ "Setting a readonly xbl property should throw a TypeError exception if strict");
+ SimpleTest.finish();
+ }
+ addLoadEvent(checkReadOnly);
+ </script>
+ </pre>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug372769.html b/dom/xbl/test/test_bug372769.html
new file mode 100644
index 000000000..c5cbbb7de
--- /dev/null
+++ b/dom/xbl/test/test_bug372769.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=372769
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 372769</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ // Embed the real test. It will take care of everything else.
+ function setup() {
+ SpecialPowers.pushPrefEnv({'set': [['dom.use_xbl_scopes_for_remote_xul', false]]}, go);
+ }
+ function go() {
+ $('ifr').setAttribute('src', 'file_bug372769.xhtml');
+ }
+
+ </script>
+</head>
+<body onload="setup();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=372769">Mozilla Bug 372769</a>
+<p id="display"></p>
+<div id="content">
+<iframe id="ifr"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug378518.xul b/dom/xbl/test/test_bug378518.xul
new file mode 100644
index 000000000..0486f3480
--- /dev/null
+++ b/dom/xbl/test/test_bug378518.xul
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=378518
+-->
+<window title="Mozilla Bug 378518"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="mybinding" extends="xul:checkbox">
+ <content>
+ </content>
+ </binding>
+
+ </bindings>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=378518"
+ target="_blank">Mozilla Bug 378518</a>
+ </body>
+
+ <!-- The elements we're testing with -->
+ <command id="myBoxCommand" oncommand="myBoxClicked = true;"/>
+ <command id="myCheckBoxCommand" oncommand="myCheckBoxClicked = true;"/>
+ <command id="myExtendedBoxCommand" oncommand="myExtendedBoxClicked = true;"/>
+
+ <box id="myBox" command="myBoxCommand">
+ <label>myBox</label>
+ </box>
+
+ <checkbox id="myCheckBox" command="myCheckBoxCommand" label="myCheckBox"/>
+
+ <box id="myExtendedBox" command="myExtendedBoxCommand"
+ style="-moz-binding:url(#mybinding)">
+ <label>myExtendedBox</label>
+ </box>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"> <![CDATA[
+
+ SimpleTest.expectAssertions(1);
+
+ var myBoxClicked = false;
+ var myCheckBoxClicked = false;
+ var myExtendedBoxClicked = false;
+
+ function testClick(elemName) {
+ var wu =
+ window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+ var a = document.getElementById(elemName).getBoundingClientRect();
+ wu.sendMouseEvent('mousedown', a.left + 1, a.top + 1, 0, 1, 0);
+ wu.sendMouseEvent('mouseup', a.left + 1, a.top + 1, 0, 1, 0);
+ }
+
+ function doTest() {
+ testClick('myBox');
+ testClick('myCheckBox');
+ testClick('myExtendedBox');
+ ok(!myBoxClicked, "Shouldn't fire");
+ ok(myCheckBoxClicked, "Should fire");
+ ok(!myExtendedBoxClicked, "Shouldn't fire");
+ SimpleTest.finish();
+ }
+
+ /** Test for Bug 378518 **/
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ setTimeout(doTest, 0);
+ });
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/xbl/test/test_bug378866.xhtml b/dom/xbl/test/test_bug378866.xhtml
new file mode 100644
index 000000000..5a5b9a09a
--- /dev/null
+++ b/dom/xbl/test/test_bug378866.xhtml
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=378866
+-->
+<head>
+ <title>Test for Bug 378866</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <bindings xmlns="http://www.mozilla.org/xbl" xmlns:html="http://www.w3.org/1999/xhtml">
+ <binding id="b1">
+ <content><html:span><html:span>
+ <children/>
+ </html:span></html:span></content>
+ </binding>
+</bindings>
+
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=378866">Mozilla Bug 378866</a>
+<p id="display"></p>
+<div id="content">
+ <span id="grandparent" style="-moz-binding: url(#b1);">
+ <span id="parent">
+ <span id="child"/>
+ </span>
+ </span>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for Bug 378866 **/
+
+function runTest() {
+ var anon = SpecialPowers.wrap(document).getAnonymousNodes(document.getElementById('grandparent'));
+ var child = SpecialPowers.wrap(document).getElementById('child');
+ var insertionPoint = anon[0].childNodes[0];
+ insertionPoint.parentNode.removeChild(insertionPoint);
+ child.appendChild(insertionPoint);
+
+ var e = document.createEvent("Event");
+ e.initEvent("foo", true, true);
+ child.dispatchEvent(e);
+ ok(true, "Moving insertion point shouldn't cause problems in event dispatching");
+ addLoadEvent(SimpleTest.finish);
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+]]>
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xbl/test/test_bug379959.html b/dom/xbl/test/test_bug379959.html
new file mode 100644
index 000000000..2b31b54c5
--- /dev/null
+++ b/dom/xbl/test/test_bug379959.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=379959
+-->
+<head>
+ <title>Test for Bug 379959</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="runTest();">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=379959">Mozilla Bug 379959</a>
+ <p id="display">
+ Note: In order to re-run this test correctly you need to shift-reload
+ rather than simply reload. If you just reload we will restore the
+ previous url in the iframe which will result in an extra unexpected
+ message.
+ </p>
+ <div id="content" style="display: none"></div>
+ <iframe id="dataFrame"></iframe>
+ <iframe id="originFrame"></iframe>
+
+ <pre id="test">
+ <script class="testbody" type="application/javascript;version=1.7">
+
+SimpleTest.waitForExplicitFinish();
+
+var seenData = false;
+var seenSameOrigin = false;
+var seenCrossOrign = false;
+
+function receiveMessage(e) {
+ is(e.origin, "http://mochi.test:8888", "wrong sender!");
+
+ if (e.data.test === "dataIsAllowed") {
+ is(e.data.result, 1, "data-url load should have succeeded");
+ seenData = true;
+ }
+ else if (e.data.test === "sameOriginIsAllowed") {
+ is(e.data.result, 1, "same site load should have succeeded");
+ seenSameOrigin = true;
+ }
+ else if (e.data.test === "crossOriginIsBlocked") {
+ is(e.data.result, 0, "cross site load should have failed");
+ seenCrossOrign = true;
+ }
+ else {
+ ok (false, "unrecognized test");
+ }
+
+ if (seenData && seenSameOrigin && seenCrossOrign) {
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+ }
+}
+
+window.addEventListener("message", receiveMessage, false);
+
+function runTest() {
+ // make sure data: is allowed
+ document.getElementById('dataFrame').src = "file_bug379959_data.html";
+ // make sure same-origin is allowed but cross site is blocked
+ document.getElementById('originFrame').src = "file_bug379959_cross.html";
+}
+
+ </script>
+ </pre>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug389322.xhtml b/dom/xbl/test/test_bug389322.xhtml
new file mode 100644
index 000000000..4b80346bd
--- /dev/null
+++ b/dom/xbl/test/test_bug389322.xhtml
@@ -0,0 +1,126 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=389322
+-->
+<head>
+ <title>Test for Bug 389322</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script>var ctorRan = false;</script>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="test">
+ <implementation>
+ <field name="field"><![CDATA[
+ (function () {
+ try {
+ eval("let x = 1;");
+ var success = true;
+ }
+ catch (e) { success = false; }
+ XPCNativeWrapper.unwrap(window).report("XBL fields", success)
+ return ""
+ }())
+ ]]></field>
+ <property name="property">
+ <getter><![CDATA[
+ try {
+ eval("let x = 1;");
+ var success = true;
+ }
+ catch (e) { success = false; }
+ XPCNativeWrapper.unwrap(window).report("XBL property getters", success)
+ return 1
+ ]]></getter>
+ <setter><![CDATA[
+ try {
+ eval("let x = 1;");
+ var success = true
+ }
+ catch (e) { success = false }
+ XPCNativeWrapper.unwrap(window).report("XBL property setters", success)
+ return val
+ ]]></setter>
+ </property>
+ <method name="method">
+ <body><![CDATA[
+ try {
+ eval("let x = 1;");
+ var success = true;
+
+ }
+ catch (e) { success = false; }
+ XPCNativeWrapper.unwrap(window).report("XBL methods", success)
+ ]]></body>
+ </method>
+ <constructor><![CDATA[
+ this.property += 1
+ var x = this.field;
+ this.method()
+ try {
+ eval("let x = 1;");
+ var success = true
+ }
+ catch (e) { success = false }
+ var win = XPCNativeWrapper.unwrap(window);
+ win.report("XBL constructors", success)
+
+ var ev = document.createEvent("Events")
+ ev.initEvent("custom", false, false)
+ this.dispatchEvent(ev)
+ win.ctorRan = true;
+ ]]></constructor>
+ </implementation>
+ <handlers>
+ <handler action='
+ try {
+ eval("let x = 1;");
+ var success = true
+ }
+ catch (e) { success = false }
+ report("XBL event handlers", success);
+ ' event="custom"/>
+ </handlers>
+ </binding>
+ </bindings>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=389322">Mozilla Bug 389322</a>
+<p id="display" style="-moz-binding: url(#test)"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for Bug 389322 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ is(ctorRan, true, "Constructor should have run");
+});
+addLoadEvent(SimpleTest.finish);
+
+function report(testName, success) {
+ is(success, true, "JS 1.7 should work in " + testName);
+}
+]]>
+</script>
+<script type="text/javascript; version=1.7"><![CDATA[
+ try {
+ eval("let x = 1;");
+ var success = true;
+ }
+ catch (e) { success = false; }
+ report("HTML script tags with explicit version", success)
+]]></script>
+<script type="text/javascript"><![CDATA[
+ try {
+ eval("let x = 1;");
+ var success = true;
+ }
+ catch (e) { success = false; }
+ is(success, true, "let should work in versionless HTML script tags");
+]]></script>
+</pre>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug397934.html b/dom/xbl/test/test_bug397934.html
new file mode 100644
index 000000000..098b4c7b5
--- /dev/null
+++ b/dom/xbl/test/test_bug397934.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=397934
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 397934</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ // Embed the real test. It will take care of everything else.
+ function setup() {
+ SpecialPowers.pushPrefEnv({'set': [['dom.use_xbl_scopes_for_remote_xul', false]]}, go);
+ }
+ function go() {
+ $('ifr').setAttribute('src', 'file_bug397934.xhtml');
+ }
+
+ </script>
+</head>
+<body onload="setup();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=397934">Mozilla Bug 397934</a>
+<p id="display"></p>
+<div id="content">
+<iframe id="ifr"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug398135.xul b/dom/xbl/test/test_bug398135.xul
new file mode 100644
index 000000000..6b0b68c62
--- /dev/null
+++ b/dom/xbl/test/test_bug398135.xul
@@ -0,0 +1,135 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=398135
+-->
+<window title="Mozilla Bug 398135"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript">window.log = ""</script>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="ancestor">
+ <implementation>
+ <constructor>
+ window.log += "ancestorConstructor:";
+ </constructor>
+ <destructor>
+ window.log += "ancestorDestructor:";
+ </destructor>
+ <field name="ancestorField">"ancestorField"</field>
+ <property name="ancestorProp" onget="return 'ancestorProp'"/>
+ <method name="ancestorMethod">
+ <body>
+ return "ancestorMethod";
+ </body>
+ </method>
+ </implementation>
+ </binding>
+ <binding id="test" extends="#ancestor">
+ <implementation>
+ <constructor>
+ window.log += "descendantConstructor:";
+ </constructor>
+ <destructor>
+ window.log += "descendantDestructor:";
+ </destructor>
+ <field name="descendantField">"descendantField"</field>
+ <field name="contentField">
+ document.getAnonymousNodes(this)[0];
+ </field>
+ <property name="descendantProp" onget="return 'descendantProp'"/>
+ <method name="descendantMethod">
+ <body>
+ return "descendantMethod";
+ </body>
+ </method>
+ </implementation>
+ <content>
+ <span/>
+ <children/>
+ </content>
+ </binding>
+ </bindings>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=398135"
+ target="_blank">Mozilla Bug 398135</a>
+ </body>
+
+ <hbox id="display" style="-moz-binding: url(#test)"></hbox>
+
+<script type="application/javascript">
+<![CDATA[
+/** Test for Bug 398135 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var d;
+ d = $("display");
+
+ function testInTree(type) {
+ is(d.ancestorField, "ancestorField", "Wrong ancestor field " + type);
+ is(d.descendantField, "descendantField", "Wrong descendant field " + type);
+ is(d.ancestorProp, "ancestorProp", "Wrong ancestor prop " + type);
+ is(d.descendantProp, "descendantProp", "Wrong descendant prop " + type);
+ is(d.ancestorMethod(), "ancestorMethod", "Wrong ancestor method " + type);
+ is(d.descendantMethod(), "descendantMethod",
+ "Wrong descendant method " + type);
+ is(d.contentField, document.getAnonymousNodes(d)[0],
+ "Unexpected content field " + type);
+ }
+
+ function testNotInTree(type) {
+ is(typeof(d.ancestorField), "undefined", "Wrong ancestor field " + type);
+ is(typeof(d.descendantField), "undefined",
+ "Wrong descendant field " + type);
+ is(typeof(d.ancestorProp), "undefined", "Wrong ancestor prop " + type);
+ is(typeof(d.descendantProp), "undefined", "Wrong descendant prop " + type);
+ is(typeof(d.ancestorMethod), "undefined", "Wrong ancestor method " + type);
+ is(typeof(d.descendantMethod), "undefined",
+ "Wrong descendant method " + type);
+ is(typeof(d.contentField), "undefined",
+ "Unexpected content field " + type);
+ }
+
+ is(window.log, "ancestorConstructor:descendantConstructor:",
+ "Constructors did not fire?");
+ window.log = "";
+ testInTree("before removal");
+
+ var parent = d.parentNode;
+ var nextSibling = d.nextSibling;
+ parent.removeChild(d);
+ testNotInTree("after first removal");
+
+ todo(window.log == "descendantDestructor:ancestorDestructor:",
+ "Destructors did not fire");
+ window.log = "";
+
+ parent.insertBefore(d, nextSibling);
+ is(window.log, "ancestorConstructor:descendantConstructor:",
+ "Constructors did not fire a second time?");
+ window.log = "";
+ testInTree("after reinsertion");
+
+ // Now munge the proto chain to test the robustness of the proto-unhooking
+ // code
+ var origProto = d.__proto__;
+ var origProtoProto = origProto.__proto__;
+ var newProto = new Object();
+ origProto.__proto__ = newProto;
+ newProto.__proto__ = origProtoProto;
+
+ parent.removeChild(d);
+ todo(window.log == "descendantDestructor:ancestorDestructor:",
+ "Destructors did not fire a second time?");
+
+ testNotInTree("after second removal");
+});
+addLoadEvent(SimpleTest.finish);
+
+]]>
+</script>
+</window>
+
diff --git a/dom/xbl/test/test_bug398492.xul b/dom/xbl/test/test_bug398492.xul
new file mode 100644
index 000000000..a854d50fc
--- /dev/null
+++ b/dom/xbl/test/test_bug398492.xul
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=398492
+-->
+<window title="Mozilla Bug 398492"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="test">
+ <content>
+ <xul:hbox id="xxx"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <children/>
+ </xul:hbox>
+ </content>
+ </binding>
+ </bindings>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=398492"
+ target="_blank">Mozilla Bug 398492</a>
+ </body>
+
+ <hbox id="testbox" style="-moz-binding: url(#test)">Text</hbox>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 398492 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function getXBLParent(node) {
+ var utils = Components.classes["@mozilla.org/inspector/dom-utils;1"]
+ .getService(Components.interfaces.inIDOMUtils);
+ return utils.getParentForNode(node, true);
+ }
+
+ addLoadEvent(function() {
+ var n = $("testbox");
+ var kid = n.firstChild;
+ var anonKid = document.getAnonymousNodes(n)[0];
+ is(anonKid instanceof XULElement, true, "Must be a XUL element");
+ is(anonKid, getXBLParent(kid), "Unexpected anonymous nodes");
+
+ var n2 = n.cloneNode(true);
+ var kid2 = n2.firstChild;
+ var anonKid2 = document.getAnonymousNodes(n2)[0];
+ is(anonKid2 instanceof XULElement, true,
+ "Must be a XUL element after clone");
+ is(anonKid2, getXBLParent(kid2),
+ "Unexpected anonymous nodes after clone");
+
+ var n3 = document.createElement("hbox");
+ document.documentElement.appendChild(n3);
+ var kid3 = document.createTextNode("Text");
+ n3.appendChild(kid3);
+
+ // Note - we rely on the fact that the binding is preloaded
+ // by the other hbox here, so that the binding will be applied
+ // sync.
+ n3.style.MozBinding = "url(" + document.location.href + "#test)";
+ n3.getBoundingClientRect(); // Flush styles.
+
+ var anonKid3 = document.getAnonymousNodes(n3)[0];
+ is(anonKid3 instanceof XULElement, true,
+ "Must be a XUL element after addBinding");
+ is(anonKid3, getXBLParent(kid3),
+ "Unexpected anonymous nodes after addBinding");
+
+
+ n.removeChild(kid);
+ isnot(anonKid, getXBLParent(kid),
+ "Should have removed kid from insertion point");
+
+ n2.removeChild(kid2);
+ isnot(anonKid2, getXBLParent(kid2),
+ "Should have removed kid2 from insertion point");
+
+ n3.removeChild(kid3);
+ isnot(anonKid3, getXBLParent(kid3),
+ "Should have removed kid3 from insertion point");
+
+ SimpleTest.finish();
+ });
+
+
+ ]]></script>
+</window>
diff --git a/dom/xbl/test/test_bug400705.xhtml b/dom/xbl/test/test_bug400705.xhtml
new file mode 100644
index 000000000..6ed687d59
--- /dev/null
+++ b/dom/xbl/test/test_bug400705.xhtml
@@ -0,0 +1,48 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=400705
+-->
+<head>
+ <title>Test for Bug 400705</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="test">
+ <implementation>
+ <field name="a">XPCNativeWrapper.unwrap(window).countera++</field>
+ <field name="b">XPCNativeWrapper.unwrap(window).counterb++</field>
+ </implementation>
+ </binding>
+ </bindings>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=400705">Mozilla Bug 400705</a>
+<p id="display" style="-moz-binding: url(#test)"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+/** Test for Bug 400705 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ window.countera = 0;
+ window.counterb = 0;
+
+ var d = $("display");
+ // Control to make sure fields are lazy and all
+ d.a;
+ is(window.countera, 1, "Should have evaluated field");
+
+ d.parentNode.removeChild(d);
+ is(window.counterb, 0, "Should not evaluate field on binding teardown");
+ SimpleTest.finish();
+});
+]]>
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xbl/test/test_bug401907.xhtml b/dom/xbl/test/test_bug401907.xhtml
new file mode 100644
index 000000000..0bc368e29
--- /dev/null
+++ b/dom/xbl/test/test_bug401907.xhtml
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=401907
+-->
+<head>
+ <title>Test for Bug 401907</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="binding">
+ <implementation>
+ <constructor>
+ var win = XPCNativeWrapper.unwrap(window);
+ win.ok(true, "First binding with ID 'binding' should be used!");
+ win.testRun = true;
+ if (win.needsFinish) {
+ win.SimpleTest.finish();
+ }
+ </constructor>
+ </implementation>
+ </binding>
+ <binding id="binding">
+ <implementation>
+ <constructor>
+ var win = XPCNativeWrapper.unwrap(window);
+ win.ok(false, "First binding with ID 'binding' should be used!");
+ win.testRun = true;
+ if (win.needsFinish) {
+ win.SimpleTest.finish();
+ }
+ </constructor>
+ </implementation>
+ </binding>
+ </bindings>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=401907">Mozilla Bug 401907</a>
+<p id="display"></p>
+<div id="content">
+ <div style="-moz-binding: url(#binding)">Bound element</div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for Bug 401907 **/
+if (!window.testRun) {
+ window.needsFinish = true;
+ SimpleTest.waitForExplicitFinish();
+}
+
+]]>
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xbl/test/test_bug403162.xhtml b/dom/xbl/test/test_bug403162.xhtml
new file mode 100644
index 000000000..96c9d77ed
--- /dev/null
+++ b/dom/xbl/test/test_bug403162.xhtml
@@ -0,0 +1,57 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=403162
+-->
+<head>
+ <title>Test for Bug 403162</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="test">
+ <handlers>
+ <handler event="foo" action="XPCNativeWrapper.unwrap(window).triggerCount++" allowuntrusted="true"/>
+ </handlers>
+ </binding>
+ </bindings>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=403162">Mozilla Bug 403162</a>
+<p id="display" style="-moz-binding: url(#test)"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+var triggerCount = 0;
+
+function dispatchEvent(t) {
+ var ev = document.createEvent("Events");
+ ev.initEvent("foo", true, true);
+ t.dispatchEvent(ev);
+}
+
+/** Test for Bug 403162 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var t = $("display");
+ is(triggerCount, 0, "Haven't dispatched event");
+
+ dispatchEvent(t);
+ is(triggerCount, 1, "Dispatched once");
+
+ dispatchEvent(t);
+ is(triggerCount, 2, "Dispatched twice");
+
+ t.parentNode.removeChild(t);
+ dispatchEvent(t);
+ is(triggerCount, 2, "Listener should be gone now");
+
+ SimpleTest.finish();
+});
+]]>
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xbl/test/test_bug468210.xhtml b/dom/xbl/test/test_bug468210.xhtml
new file mode 100644
index 000000000..72834d241
--- /dev/null
+++ b/dom/xbl/test/test_bug468210.xhtml
@@ -0,0 +1,51 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=468210
+-->
+<head>
+ <title>Test for Bug 468210</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <!-- Keep the stuff inside <content> without spaces, so that the getAnonymousNodes works right -->
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="foo">
+ <content><span xmlns='http://www.w3.org/1999/xhtml'><children xmlns="http://www.mozilla.org/xbl"/></span></content>
+ </binding>
+ </bindings>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=468210">Mozilla Bug 468210</a>
+<p id="display">
+ <div id="d" style="-moz-binding: url(#foo);"></div>
+ <a name="foo"></a>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 468210 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var div = $("d");
+ var n = document.anchors.length;
+ is(n, 1, "Unexpected number of anchors");
+ var anon = SpecialPowers.wrap(document).getAnonymousNodes(div)[0];
+ ok(SpecialPowers.call_Instanceof(anon, HTMLSpanElement), "Unexpected node");
+ is(SpecialPowers.unwrap(anon.parentNode), div, "Unexpected parent");
+ document.body.appendChild(div);
+ is(anon.parentNode, null, "Parent should have become null");
+ // An attr set to test notifications
+ anon.setAttribute("h", "i");
+});
+addLoadEvent(SimpleTest.finish);
+
+
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug481558.html b/dom/xbl/test/test_bug481558.html
new file mode 100644
index 000000000..72b418987
--- /dev/null
+++ b/dom/xbl/test/test_bug481558.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=481558
+-->
+<head>
+ <title>Test for Bug 481558</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+ <link rel="stylesheet" type="text/css"
+ href="file_bug481558css.sjs?id=id1">
+ <link rel="stylesheet" type="text/css"
+ href="file_bug481558css.sjs?id=id2&server=example.com">
+ <link rel="stylesheet" type="text/css" href="http://example.com/tests/dom/xbl/test/file_bug481558css.sjs?id=id3">
+ <link rel="stylesheet" type="text/css" href="http://example.com/tests/dom/xbl/test/file_bug481558css.sjs?id=id4&server=localhost:8888">
+</head>
+<body onload="runTest();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=481558">Mozilla Bug 481558</a>
+<p id="id1"></p>
+<p id="id2"></p>
+<p id="id3"></p>
+<p id="id4"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+function runTest() {
+ is ($('id1').xblBoundProperty, 1, "XBL should be attached");
+ is ($('id2').xblBoundProperty, undefined, "XBL shouldn't be attached");
+ is ($('id3').xblBoundProperty, undefined, "XBL shouldn't be attached");
+ is ($('id4').xblBoundProperty, undefined, "XBL shouldn't be attached");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug526178.xhtml b/dom/xbl/test/test_bug526178.xhtml
new file mode 100644
index 000000000..602a405a1
--- /dev/null
+++ b/dom/xbl/test/test_bug526178.xhtml
@@ -0,0 +1,80 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="display: none">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=526178
+-->
+<head>
+ <title>Test for Bug 526178</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style id="sheet">
+ #content * {
+ display: block;
+ -moz-binding: url("#binding");
+ }
+ </style>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="binding">
+ <implementation>
+ <constructor>
+ var win = XPCNativeWrapper.unwrap(window);
+ win.logString += this.localName;
+ win.bindingDone();
+ </constructor>
+ </implementation>
+ </binding>
+ </bindings>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=526178">Mozilla Bug 526178</a>
+<div id="content">
+ <a>
+ <b>
+ <c/>
+ </b>
+ <d/>
+ <e style="display: inline">
+ <f style="display: inline">
+ <g style="display: inline"/>
+ <h style="display: none"/>
+ <i style="display: inline"/>
+ </f>
+ <j style="display: none"/>
+ <k style="display: inline">
+ <l style="display: inline"/>
+ <m/>
+ <n style="display: inline"/>
+ </k>
+ </e>
+ <o style="display: none"/>
+ <p/>
+ </a>
+</div>
+<p id="display">
+</p>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 526178 **/
+var logString = "";
+// Add one for the root
+var pendingBindings = $("content").getElementsByTagName("*").length + 1;
+function bindingDone() {
+ if (--pendingBindings == 0) {
+ is(logString, "apoeknmljfihgdbchtml");
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// Have to add the rule for the root dynamically so the binding doesn't try
+// to load before bindingDone() is defined.
+$("sheet").sheet.insertRule(':root { -moz-binding: url("#binding"); }', 0);
+document.documentElement.style.display = "";
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug542406.xhtml b/dom/xbl/test/test_bug542406.xhtml
new file mode 100644
index 000000000..880d69a8e
--- /dev/null
+++ b/dom/xbl/test/test_bug542406.xhtml
@@ -0,0 +1,44 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=542406
+-->
+<head>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="foo">
+ <content><children/></content>
+ <implementation>
+ <field name="one" readonly="true">1</field>
+ <field name="three">3</field>
+ </implementation>
+ </binding>
+ </bindings>
+ <title>Test for Bug 542406</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=542406">Mozilla Bug 542406</a>
+<p id="display" style="-moz-binding: url(#foo)"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 542406 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ is($("display").one, 1, "Field one not installed?");
+ $("display").one = 2;
+ is($("display").one, 1, "Field one not readonly");
+ is($("display").three, 3, "Field three not installed?");
+ $("display").three = 4;
+ is($("display").three, 4, "Field three readonly?");
+ SimpleTest.finish();
+});
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug591198.html b/dom/xbl/test/test_bug591198.html
new file mode 100644
index 000000000..2c7df3613
--- /dev/null
+++ b/dom/xbl/test/test_bug591198.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=591198
+-->
+<head>
+ <title>Test for Bug 591198</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body onload="gen.next();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=591198">Mozilla Bug 591198</a>
+<iframe id=iframe></iframe>
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.8">
+
+SimpleTest.waitForExplicitFinish();
+
+gen = runTest();
+
+function runTest() {
+ let iframe = $('iframe');
+ window.addEventListener("message", function(e) {
+ gen.send(JSON.parse(e.data));
+ }, false);
+
+ iframe.src = "file_bug591198_inner.html";
+ let res = (yield);
+ is(res.widths[0], res.widths[2], "binding was rendered");
+ isnot(res.widths[0], res.widths[1], "binding was rendered");
+ is(res.anonChildCount, 2, "correct number of anon children");
+
+ iframe.src = "http://noxul.example.com/tests/dom/xbl/test/file_bug591198_inner.html";
+ res = (yield);
+ is(res.widths[0], res.widths[1], "binding was not rendered");
+ isnot(res.widths[0], res.widths[2], "binding was not rendered");
+ is("anonChildCount" in res, false, "no anon children");
+
+ SimpleTest.finish();
+ yield undefined;
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug639338.xhtml b/dom/xbl/test/test_bug639338.xhtml
new file mode 100644
index 000000000..e88ff1abc
--- /dev/null
+++ b/dom/xbl/test/test_bug639338.xhtml
@@ -0,0 +1,66 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=403162
+-->
+<head>
+ <title>Test for Bug 639338</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="test">
+ <handlers>
+ <handler event="DOMMouseScroll" action="XPCNativeWrapper.unwrap(window).triggerCount++" allowuntrusted="true"/>
+ <handler event="DOMMouseScroll" modifiers="shift" action="XPCNativeWrapper.unwrap(window).shiftCount++" allowuntrusted="true"/>
+ <handler event="DOMMouseScroll" modifiers="control" action="XPCNativeWrapper.unwrap(window).controlCount++" allowuntrusted="true"/>
+ </handlers>
+ </binding>
+ </bindings>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=639338">Mozilla Bug 639338</a>
+<p id="display" style="-moz-binding: url(#test)"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+var triggerCount = 0;
+var shiftCount = 0;
+var controlCount = 0;
+
+function dispatchEvent(t, controlKey, shiftKey) {
+ var ev = document.createEvent("MouseScrollEvents");
+ ev.initMouseScrollEvent("DOMMouseScroll", true, true, window, 0, 0, 0, 0, 0, controlKey, false, shiftKey, false, 0, null, 0);
+ t.dispatchEvent(ev);
+}
+
+/** Test for Bug 403162 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var t = $("display");
+ is(triggerCount, 0, "Haven't dispatched event");
+
+ dispatchEvent(t, false, false);
+ is(triggerCount, 1, "Dispatched once");
+ is(shiftCount, 0, "Not shift");
+ is(controlCount, 0, "Not control");
+
+ dispatchEvent(t, false, true);
+ is(triggerCount, 2, "Dispatched twice");
+ is(shiftCount, 1, "Shift");
+ is(controlCount, 0, "Not control");
+
+ dispatchEvent(t, true, false);
+ is(triggerCount, 3, "Dispatched thrice");
+ is(shiftCount, 1, "Not shift");
+ is(controlCount, 1, "Control");
+
+ SimpleTest.finish();
+});
+]]>
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xbl/test/test_bug721452.xul b/dom/xbl/test/test_bug721452.xul
new file mode 100644
index 000000000..d1393d542
--- /dev/null
+++ b/dom/xbl/test/test_bug721452.xul
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+ok(true, "Handler with empty action didn't crash")
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml"/>
+
+<box style="-moz-binding: url(#binding)"/>
+
+<xbl:bindings xmlns:xbl="http://www.mozilla.org/xbl">
+ <xbl:binding id="binding">
+ <xbl:content/>
+ <xbl:handlers>
+ <xbl:handler nt="action" action=""/>
+ </xbl:handlers>
+ </xbl:binding>
+</xbl:bindings>
+
+</window>
diff --git a/dom/xbl/test/test_bug723676.xul b/dom/xbl/test/test_bug723676.xul
new file mode 100644
index 000000000..29561a57e
--- /dev/null
+++ b/dom/xbl/test/test_bug723676.xul
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+ok(true, "Method with empty body didn't crash");
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml"/>
+
+<box style="-moz-binding: url(#binding)"/>
+
+<xbl:bindings xmlns:xbl="http://www.mozilla.org/xbl">
+ <xbl:binding id="binding">
+ <xbl:implementation>
+ <xbl:method name="init"/>
+ </xbl:implementation>
+ </xbl:binding>
+</xbl:bindings>
+
+
+</window>
diff --git a/dom/xbl/test/test_bug772966.xul b/dom/xbl/test/test_bug772966.xul
new file mode 100644
index 000000000..d58d5525d
--- /dev/null
+++ b/dom/xbl/test/test_bug772966.xul
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=772966
+-->
+<window title="Mozilla Bug 772966"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTest()">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=772966"
+ target="_blank">Mozilla Bug 772966</a>
+ </body>
+
+ <script>
+ function runTest() {
+ is(document.getElementById('b').test(123, 123, 123), 2, "Should have 2 params.");
+ }
+ </script>
+
+ <box id="b" style="-moz-binding: url(#binding)"/>
+
+ <xbl:bindings xmlns:xbl="http://www.mozilla.org/xbl">
+ <xbl:binding id="binding">
+ <xbl:implementation>
+ <xbl:method name="test">
+ <xbl:parameter name="p1"/>
+ <xbl:parameter name=""/>
+ <xbl:parameter name="p2"/>
+ <xbl:body><![CDATA[
+ return arguments.callee.length;
+ ]]></xbl:body>
+ </xbl:method>
+ </xbl:implementation>
+ </xbl:binding>
+ </xbl:bindings>
+
+
+</window>
diff --git a/dom/xbl/test/test_bug790265.xhtml b/dom/xbl/test/test_bug790265.xhtml
new file mode 100644
index 000000000..1ce8363a7
--- /dev/null
+++ b/dom/xbl/test/test_bug790265.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=790265
+-->
+<head>
+ <title>Test for Bug 790265</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="binding">
+ <implementation>
+ <method name="foo" exposeToUntrustedContent="true">
+ <body><![CDATA[
+ return this;
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+ </bindings>
+ <style>
+ #toBind { display: none; }
+ #toBind.bound { -moz-binding: url(#binding); }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=790265">Mozilla Bug 790265</a>
+<p id="display"></p>
+<div id="content">
+ <div id="toBind">Bound element</div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ // Flush out style on $("toBind")
+ $("toBind").offsetWidth;
+ ok(!("foo" in $("toBind")), "Should not have binding applied");
+ is(getComputedStyle($("toBind")).MozBinding, "none",
+ "Computed binding should be none");
+ $("toBind").className = "bound";
+ // Flush the style change, so we kick off the binding load before onload
+ // fires and thus block onload.
+ $("toBind").offsetWidth;
+ addLoadEvent(function() {
+ ok("foo" in $("toBind"), "Should have binding applied");
+ is($("toBind").foo(), $("toBind"), "Should have correct return value");
+ SimpleTest.finish();
+ });
+]]>
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xbl/test/test_bug821850.html b/dom/xbl/test/test_bug821850.html
new file mode 100644
index 000000000..9ac7cdc61
--- /dev/null
+++ b/dom/xbl/test/test_bug821850.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=821850
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 821850</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for XBL scope behavior. **/
+ SimpleTest.waitForExplicitFinish();
+
+ // Embed the real test. It will take care of everything else.
+ //
+ // NB: This two-layer setup used to exist because XBL scopes were behind a
+ // pref, and we wanted to make sure that we properly set the pref before
+ // loading the real test. This stuff is no longer behind a pref, but we just
+ // leave the structure as-is for expediency.
+ function setup() {
+ $('ifr').setAttribute('src', 'file_bug821850.xhtml');
+ }
+
+ </script>
+</head>
+<body onload="setup();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=821850">Mozilla Bug 821850</a>
+<p id="display"></p>
+<div id="content">
+<iframe id="ifr"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug844783.html b/dom/xbl/test/test_bug844783.html
new file mode 100644
index 000000000..ba99f08f1
--- /dev/null
+++ b/dom/xbl/test/test_bug844783.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=844783
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 844783</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for XBL running in content scope for XUL-whitelisted domains. **/
+ SimpleTest.waitForExplicitFinish();
+
+ // Disable the automation pref before loading the test to make sure we load
+ // the proper configuration.
+ function setup() {
+ SpecialPowers.pushPrefEnv({clear: [['dom.use_xbl_scopes_for_remote_xul']] }, continueSetup);
+ }
+
+ // Embed the real test. It will take care of everything else.
+ function continueSetup() {
+ $('ifr').setAttribute('src', 'file_bug844783.xhtml');
+ }
+
+ </script>
+</head>
+<body onload="setup();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=844783">Mozilla Bug 844783</a>
+<p id="display"></p>
+<div id="content">
+<iframe id="ifr"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug872273.xhtml b/dom/xbl/test/test_bug872273.xhtml
new file mode 100644
index 000000000..18417028c
--- /dev/null
+++ b/dom/xbl/test/test_bug872273.xhtml
@@ -0,0 +1,53 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=872273
+-->
+<head>
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="foo">
+ <implementation>
+ <method name="throwSomething" exposeToUntrustedContent="true">
+ <body>
+ throw new Error("foopy");
+ </body>
+ </method>
+ </implementation>
+ </binding>
+ </bindings>
+ <title>Test for Bug 872273</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=872273">Mozilla Bug 872273</a>
+<p id="display" style="-moz-binding: url(#foo)"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 872273 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+
+ // Prevent the test from failing when the exception hits onerror.
+ SimpleTest.expectUncaughtException();
+
+ // Tell the test to expect exactly one console error with the given parameters,
+ // with SimpleTest.finish as a continuation function.
+ SimpleTest.monitorConsole(SimpleTest.finish, [{errorMessage: new RegExp('foopy')}]);
+
+ // Schedule the console accounting (and continuation) to run next, right
+ // after we throw (below).
+ SimpleTest.executeSoon(SimpleTest.endMonitorConsole);
+
+ // Throw.
+ $('display').throwSomething();
+});
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xbl/test/test_bug944407.xul b/dom/xbl/test/test_bug944407.xul
new file mode 100644
index 000000000..1efc82c03
--- /dev/null
+++ b/dom/xbl/test/test_bug944407.xul
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=944407
+-->
+<window title="Mozilla Bug 944407"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=944407"
+ target="_blank">Mozilla Bug 944407</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for XBL bindings with script disabled. **/
+ SimpleTest.waitForExplicitFinish();
+ const Cu = Components.utils;
+ Cu.import('resource://gre/modules/Services.jsm');
+
+ function go() {
+
+ // Disable javascript, and load the frame.
+ function loadFrame() {
+ ok(!Services.prefs.getBoolPref('javascript.enabled'), "Javascript should be disabled");
+ $('ifr').setAttribute('src', 'http://mochi.test:8888/tests/dom/xbl/test/file_bug944407.html');
+ }
+ SpecialPowers.pushPrefEnv({ set: [['javascript.enabled', false]] }, loadFrame);
+ }
+
+ function finish() {
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(go);
+ ]]>
+ </script>
+ <iframe id='ifr' />
+</window>
diff --git a/dom/xbl/test/test_bug950909.xul b/dom/xbl/test/test_bug950909.xul
new file mode 100644
index 000000000..2147b0fcd
--- /dev/null
+++ b/dom/xbl/test/test_bug950909.xul
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=950909
+-->
+<window title="Mozilla Bug 950909"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=950909"
+ target="_blank">Mozilla Bug 950909</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /*
+ * Test for bug 950909. This has to be a chrome tests because content needs
+ * to apply a chrome binding (file_bug950909.xml), which will only be registered
+ * as a chrome:// URI during mochitest-chrome runs. And the binding has to be
+ * served from a chrome origin, because otherwise implements="nsIFoo" attributes
+ * are ignored. So this test needs 3 files, all told. Ugh.
+ */
+
+ // Just wait. When the iframe loads, it'll apply the binding, which will
+ // trigger the constructor for the binding.
+ SimpleTest.waitForExplicitFinish();
+
+ ]]>
+ </script>
+ <iframe src="http://example.com/tests/dom/xbl/test/file_bug950909.html"/>
+</window>
diff --git a/dom/xbl/test/test_fieldScopeChain.html b/dom/xbl/test/test_fieldScopeChain.html
new file mode 100644
index 000000000..c688ca64f
--- /dev/null
+++ b/dom/xbl/test/test_fieldScopeChain.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1095660
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug **/
+ SimpleTest.waitForExplicitFinish();
+ window.baz = 1;
+ document.baz = 2;
+ addLoadEvent(function() {
+ is(document.querySelector("pre").bar, 2,
+ "Should have document on field scope chain");
+ SimpleTest.finish();
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1095660">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test" style="-moz-binding: url(file_fieldScopeChain.xml#foo)">
+</pre>
+</body>
+</html>