diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/xbl | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/xbl')
155 files changed, 18678 insertions, 0 deletions
diff --git a/dom/xbl/XBLChildrenElement.cpp b/dom/xbl/XBLChildrenElement.cpp new file mode 100644 index 0000000000..e4058a7890 --- /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 0000000000..4714da4a84 --- /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 0000000000..9f05c2dd6c --- /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 0000000000..eb4454d28f --- /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 0000000000..03363c1b5e --- /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 0000000000..03264c2709 --- /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 0000000000..1084da8144 --- /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 0000000000..9f05c2dd6c --- /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 0000000000..eb4454d28f --- /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 0000000000..76c214ffa1 --- /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 0000000000..a19686291c --- /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 0000000000..9f05c2dd6c --- /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 0000000000..eb4454d28f --- /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 0000000000..b705923999 --- /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 0000000000..09574b363c --- /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 0000000000..7c52359d70 --- /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 0000000000..9f05c2dd6c --- /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 0000000000..eb4454d28f --- /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 0000000000..75645f1f6a --- /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 0000000000..9f05c2dd6c --- /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 0000000000..eb4454d28f --- /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 0000000000..1612138e1d --- /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 0000000000..bfc42773bc --- /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 0000000000..455ac9e05f --- /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 0000000000..63915dbe88 --- /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 0000000000..bfd5476e6f --- /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 0000000000..74dc5d8080 --- /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 0000000000..eae6f1574b --- /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 0000000000..4af1fbd52c --- /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 0000000000..48d5c17010 --- /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 0000000000..2064f89779 --- /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 0000000000..0263d30958 --- /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 0000000000..dbaa153840 --- /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 0000000000..3e73f1355a --- /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 0000000000..4a250e31c2 --- /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 0000000000..162494b2cd --- /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 0000000000..0b42ac8138 --- /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 0000000000..3db89d49a0 --- /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 0000000000..55457f79e2 --- /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 0000000000..4e22b8f4ea --- /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 0000000000..c7b562b52d --- /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 0000000000..46b0fd6f8d --- /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 0000000000..3b3dc1f870 --- /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 0000000000..758fb04cd8 --- /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 0000000000..763672b3f6 --- /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 0000000000..3795ca2205 --- /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 0000000000..cee274fcd3 --- /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 0000000000..ea821b76ed --- /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 0000000000..8ec4250753 --- /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 0000000000..4b91f00fcc --- /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 0000000000..b2940e638a --- /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 0000000000..586ac05f10 --- /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 0000000000..31a8316f62 --- /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 0000000000..2ea1f7212b --- /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 0000000000..a9fec3b509 --- /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 0000000000..a665afc9c2 --- /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 0000000000..49d5407d46 --- /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 0000000000..17e4002b49 --- /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 0000000000..365ecfc9ec --- /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 0000000000..1c568ae15b --- /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 0000000000..2e76fb8462 --- /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 0000000000..a82871f718 --- /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 0000000000..402e680eec --- /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 0000000000..f2f9bb352a --- /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 0000000000..a6598de9c6 --- /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 0000000000..26424c7d85 --- /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 0000000000..7b3dea03a4 --- /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 0000000000..0950db0361 --- /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 0000000000..405c7aac7b --- /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 0000000000..a71ff21887 --- /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 0000000000..6ae17c4c02 --- /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 0000000000..b977c3e879 --- /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 0000000000..4d5c9fb74f --- /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 0000000000..3c220c0f3a --- /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 0000000000..283775dc69 --- /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 0000000000..79ea005e02 --- /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 0000000000..6a873cf3ba --- /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 0000000000..8c74cabdb1 --- /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 0000000000..d4b366b0ee --- /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 0000000000..4db9cabf01 --- /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 0000000000..d65637ac24 --- /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 0000000000..9c9857f1df --- /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 0000000000..667f03a61c --- /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 0000000000..4a2789d934 --- /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 0000000000..31b215ab37 --- /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 0000000000..8a616e11e1 --- /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 0000000000..557a836da3 --- /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 0000000000..cf203294f1 --- /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 0000000000..c470d887b1 --- /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 0000000000..4c51a2083f --- /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 0000000000..2591a72fd9 --- /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 0000000000..6898b73ede --- /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 0000000000..a1ec426635 --- /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 0000000000..5ac1923ac9 --- /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 0000000000..e930e6f989 --- /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 0000000000..1311f11f7f --- /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 0000000000..b08c259657 --- /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 0000000000..2c1dedc334 --- /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 0000000000..1475b1368d --- /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 0000000000..5082bc42b2 --- /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 0000000000..011b5c36bb --- /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 0000000000..0509e85e46 --- /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 0000000000..2a3f1df358 --- /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 0000000000..40606de12c --- /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 0000000000..79a36be854 --- /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 0000000000..80e2be97d4 --- /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 0000000000..64fe3e45a7 --- /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 0000000000..c791a2e2b4 --- /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 0000000000..c8505aab47 --- /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 0000000000..f9ae7be1c9 --- /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 0000000000..a44cb9c956 --- /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 0000000000..89b547a39f --- /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 0000000000..f69959b47c --- /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 0000000000..0d68a908f8 --- /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 0000000000..a2e04edf28 --- /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 0000000000..3e4a0e80f6 --- /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 0000000000..e48271b9f3 --- /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 0000000000..ca8718b7ba --- /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 0000000000..a33d9bbf8d --- /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 0000000000..c711d9ed42 --- /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 0000000000..80e6cf4776 --- /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 0000000000..2804d501f7 --- /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 0000000000..c60855a005 --- /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 0000000000..22bc88798d --- /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 0000000000..564c2f96d8 --- /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 0000000000..17e642b974 --- /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 0000000000..d3314ecca4 --- /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 0000000000..69a317fde4 --- /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 0000000000..c5cbbb7dea --- /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 0000000000..0486f34806 --- /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 0000000000..5a5b9a09a7 --- /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 0000000000..2b31b54c57 --- /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 0000000000..4b80346bd2 --- /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 0000000000..098b4c7b54 --- /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 0000000000..6b0b68c626 --- /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 0000000000..a854d50fc9 --- /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 0000000000..6ed687d595 --- /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 0000000000..0bc368e29f --- /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 0000000000..96c9d77ed9 --- /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 0000000000..72834d241d --- /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 0000000000..72b418987f --- /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 0000000000..602a405a1f --- /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 0000000000..880d69a8e2 --- /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 0000000000..2c7df3613d --- /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 0000000000..e88ff1abcc --- /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 0000000000..d1393d5422 --- /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 0000000000..29561a57e7 --- /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 0000000000..d58d5525d2 --- /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 0000000000..1ce8363a7e --- /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 0000000000..9ac7cdc61a --- /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 0000000000..ba99f08f11 --- /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 0000000000..18417028c3 --- /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 0000000000..1efc82c038 --- /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 0000000000..2147b0fcd2 --- /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 0000000000..c688ca64fa --- /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> |