diff -r 000000000000 -r 6474c204b198 content/base/src/nsDocument.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/base/src/nsDocument.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,12287 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=78: */ +/* 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/. */ + +/* + * Base class for all our document implementations. + */ + +#include "nsDocument.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Likely.h" +#include + +#ifdef MOZ_LOGGING +// so we can get logging even in release builds +#define FORCE_PR_LOG 1 +#endif +#include "prlog.h" +#include "plstr.h" +#include "prprf.h" + +#include "mozilla/Telemetry.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsUnicharUtils.h" +#include "nsContentList.h" +#include "nsIObserver.h" +#include "nsIBaseWindow.h" +#include "mozilla/css/Loader.h" +#include "mozilla/css/ImageLoader.h" +#include "nsDocShell.h" +#include "nsIDocShellTreeItem.h" +#include "nsCOMArray.h" +#include "nsDOMClassInfo.h" +#include "nsCxPusher.h" + +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/EventStateManager.h" +#include "nsIDOMNodeFilter.h" + +#include "nsIDOMStyleSheet.h" +#include "mozilla/dom/Attr.h" +#include "nsIDOMDOMImplementation.h" +#include "nsIDOMDocumentXBL.h" +#include "mozilla/dom/Element.h" +#include "nsGenericHTMLElement.h" +#include "mozilla/dom/CDATASection.h" +#include "mozilla/dom/ProcessingInstruction.h" +#include "nsDOMString.h" +#include "nsNodeUtils.h" +#include "nsLayoutUtils.h" // for GetFrameForPoint +#include "nsIFrame.h" +#include "nsITabChild.h" + +#include "nsRange.h" +#include "nsIDOMText.h" +#include "nsIDOMComment.h" +#include "mozilla/dom/DocumentType.h" +#include "mozilla/dom/NodeIterator.h" +#include "mozilla/dom/TreeWalker.h" + +#include "nsIServiceManager.h" + +#include "nsContentCID.h" +#include "nsError.h" +#include "nsPresShell.h" +#include "nsPresContext.h" +#include "nsIJSON.h" +#include "nsThreadUtils.h" +#include "nsNodeInfoManager.h" +#include "nsIFileChannel.h" +#include "nsIMultiPartChannel.h" +#include "nsIRefreshURI.h" +#include "nsIWebNavigation.h" +#include "nsIScriptError.h" +#include "nsStyleSheetService.h" + +#include "nsNetUtil.h" // for NS_MakeAbsoluteURI + +#include "nsIScriptSecurityManager.h" +#include "nsIPrincipal.h" + +#include "nsIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsIDOMElement.h" +#include "nsFocusManager.h" + +// for radio group stuff +#include "nsIDOMHTMLInputElement.h" +#include "nsIRadioVisitor.h" +#include "nsIFormControl.h" + +#include "nsBidiUtils.h" + +#include "nsIDOMUserDataHandler.h" +#include "nsIDOMXPathExpression.h" +#include "nsIDOMXPathNSResolver.h" +#include "nsIParserService.h" +#include "nsContentCreatorFunctions.h" + +#include "nsIScriptContext.h" +#include "nsBindingManager.h" +#include "nsIDOMHTMLDocument.h" +#include "nsHTMLDocument.h" +#include "nsIDOMHTMLFormElement.h" +#include "nsIRequest.h" +#include "nsHostObjectProtocolHandler.h" + +#include "nsCharsetAlias.h" +#include "nsCharsetSource.h" +#include "nsIParser.h" +#include "nsIContentSink.h" + +#include "nsDateTimeFormatCID.h" +#include "nsIDateTimeFormat.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventStates.h" +#include "mozilla/InternalMutationEvent.h" +#include "nsDOMCID.h" + +#include "jsapi.h" +#include "nsIXPConnect.h" +#include "nsCCUncollectableMarker.h" +#include "nsIContentPolicy.h" +#include "nsContentPolicyUtils.h" +#include "nsICategoryManager.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsIDocumentLoader.h" +#include "nsIContentViewer.h" +#include "nsIXMLContentSink.h" +#include "nsIXULDocument.h" +#include "nsIPrompt.h" +#include "nsIPropertyBag2.h" +#include "nsIDOMPageTransitionEvent.h" +#include "nsIDOMStyleRuleChangeEvent.h" +#include "nsIDOMStyleSheetChangeEvent.h" +#include "nsIDOMStyleSheetApplicableStateChangeEvent.h" +#include "nsJSUtils.h" +#include "nsFrameLoader.h" +#include "nsEscape.h" +#include "nsObjectLoadingContent.h" +#include "nsHtml5TreeOpExecutor.h" +#include "nsIDOMElementReplaceEvent.h" +#include "mozilla/dom/HTMLLinkElement.h" +#include "mozilla/dom/HTMLMediaElement.h" +#ifdef MOZ_MEDIA_NAVIGATOR +#include "mozilla/MediaManager.h" +#endif // MOZ_MEDIA_NAVIGATOR +#ifdef MOZ_WEBRTC +#include "IPeerConnection.h" +#endif // MOZ_WEBRTC + +#include "mozAutoDocUpdate.h" +#include "nsGlobalWindow.h" +#include "mozilla/dom/EncodingUtils.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "nsDOMNavigationTiming.h" + +#include "nsSMILAnimationController.h" +#include "imgIContainer.h" +#include "nsSVGUtils.h" +#include "SVGElementFactory.h" + +#include "nsRefreshDriver.h" + +// FOR CSP (autogenerated by xpidl) +#include "nsIContentSecurityPolicy.h" +#include "nsCSPService.h" +#include "nsHTMLStyleSheet.h" +#include "nsHTMLCSSStyleSheet.h" +#include "mozilla/dom/DOMImplementation.h" +#include "mozilla/dom/ShadowRoot.h" +#include "mozilla/dom/Comment.h" +#include "nsTextNode.h" +#include "mozilla/dom/Link.h" +#include "mozilla/dom/HTMLElementBinding.h" +#include "mozilla/dom/SVGElementBinding.h" +#include "nsXULAppAPI.h" +#include "mozilla/dom/Touch.h" +#include "mozilla/dom/TouchEvent.h" +#include "GeneratedEvents.h" + +#include "mozilla/Preferences.h" + +#include "imgILoader.h" +#include "imgRequestProxy.h" +#include "nsWrapperCacheInlines.h" +#include "nsSandboxFlags.h" +#include "nsIAppsService.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/DocumentFragment.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/HTMLBodyElement.h" +#include "mozilla/dom/HTMLInputElement.h" +#include "mozilla/dom/NodeFilterBinding.h" +#include "mozilla/dom/OwningNonNull.h" +#include "mozilla/dom/UndoManager.h" +#include "mozilla/dom/WebComponentsBinding.h" +#include "nsFrame.h" +#include "nsDOMCaretPosition.h" +#include "nsIDOMHTMLTextAreaElement.h" +#include "nsViewportInfo.h" +#include "nsIContentPermissionPrompt.h" +#include "mozilla/StaticPtr.h" +#include "nsITextControlElement.h" +#include "nsIDOMNSEditableElement.h" +#include "nsIEditor.h" +#include "nsIDOMCSSStyleRule.h" +#include "mozilla/css/Rule.h" +#include "nsIDOMLocation.h" +#include "nsIHttpChannelInternal.h" +#include "nsISecurityConsoleMessage.h" +#include "nsCharSeparatedTokenizer.h" +#include "mozilla/dom/XPathEvaluator.h" +#include "nsIDocumentEncoder.h" +#include "nsIStructuredCloneContainer.h" +#include "nsIMutableArray.h" +#include "nsContentPermissionHelper.h" +#include "mozilla/dom/DOMStringList.h" +#include "nsWindowMemoryReporter.h" + +using namespace mozilla; +using namespace mozilla::dom; + +typedef nsTArray LinkArray; + +#ifdef PR_LOGGING +static PRLogModuleInfo* gDocumentLeakPRLog; +static PRLogModuleInfo* gCspPRLog; +#endif + +#define NAME_NOT_VALID ((nsSimpleContentList*)1) + +nsIdentifierMapEntry::~nsIdentifierMapEntry() +{ +} + +void +nsIdentifierMapEntry::Traverse(nsCycleCollectionTraversalCallback* aCallback) +{ + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, + "mIdentifierMap mNameContentList"); + aCallback->NoteXPCOMChild(static_cast(mNameContentList)); + + if (mImageElement) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, + "mIdentifierMap mImageElement element"); + nsIContent* imageElement = mImageElement; + aCallback->NoteXPCOMChild(imageElement); + } +} + +bool +nsIdentifierMapEntry::IsEmpty() +{ + return mIdContentList.Count() == 0 && !mNameContentList && + !mChangeCallbacks && !mImageElement; +} + +Element* +nsIdentifierMapEntry::GetIdElement() +{ + return static_cast(mIdContentList.SafeElementAt(0)); +} + +Element* +nsIdentifierMapEntry::GetImageIdElement() +{ + return mImageElement ? mImageElement.get() : GetIdElement(); +} + +void +nsIdentifierMapEntry::AppendAllIdContent(nsCOMArray* aElements) +{ + for (int32_t i = 0; i < mIdContentList.Count(); ++i) { + aElements->AppendObject(static_cast(mIdContentList[i])); + } +} + +void +nsIdentifierMapEntry::AddContentChangeCallback(nsIDocument::IDTargetObserver aCallback, + void* aData, bool aForImage) +{ + if (!mChangeCallbacks) { + mChangeCallbacks = new nsTHashtable; + if (!mChangeCallbacks) + return; + } + + ChangeCallback cc = { aCallback, aData, aForImage }; + mChangeCallbacks->PutEntry(cc); +} + +void +nsIdentifierMapEntry::RemoveContentChangeCallback(nsIDocument::IDTargetObserver aCallback, + void* aData, bool aForImage) +{ + if (!mChangeCallbacks) + return; + ChangeCallback cc = { aCallback, aData, aForImage }; + mChangeCallbacks->RemoveEntry(cc); + if (mChangeCallbacks->Count() == 0) { + mChangeCallbacks = nullptr; + } +} + +struct FireChangeArgs { + Element* mFrom; + Element* mTo; + bool mImageOnly; + bool mHaveImageOverride; +}; + +namespace mozilla { +namespace dom { + +static PLDHashOperator +CustomDefinitionsTraverse(CustomElementHashKey* aKey, + CustomElementDefinition* aDefinition, + void* aArg) +{ + nsCycleCollectionTraversalCallback* cb = + static_cast(aArg); + + nsAutoPtr& callbacks = aDefinition->mCallbacks; + + if (callbacks->mAttributeChangedCallback.WasPassed()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, + "mCustomDefinitions->mCallbacks->mAttributeChangedCallback"); + cb->NoteXPCOMChild(aDefinition->mCallbacks->mAttributeChangedCallback.Value()); + } + + if (callbacks->mCreatedCallback.WasPassed()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, + "mCustomDefinitions->mCallbacks->mCreatedCallback"); + cb->NoteXPCOMChild(aDefinition->mCallbacks->mCreatedCallback.Value()); + } + + if (callbacks->mAttachedCallback.WasPassed()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, + "mCustomDefinitions->mCallbacks->mAttachedCallback"); + cb->NoteXPCOMChild(aDefinition->mCallbacks->mAttachedCallback.Value()); + } + + if (callbacks->mDetachedCallback.WasPassed()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, + "mCustomDefinitions->mCallbacks->mDetachedCallback"); + cb->NoteXPCOMChild(aDefinition->mCallbacks->mDetachedCallback.Value()); + } + + return PL_DHASH_NEXT; +} + +static PLDHashOperator +CandidatesTraverse(CustomElementHashKey* aKey, + nsTArray>* aData, + void* aArg) +{ + nsCycleCollectionTraversalCallback *cb = + static_cast(aArg); + for (size_t i = 0; i < aData->Length(); ++i) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mCandidatesMap->Element"); + cb->NoteXPCOMChild(aData->ElementAt(i)); + } + return PL_DHASH_NEXT; +} + +struct CustomDefinitionTraceArgs +{ + const TraceCallbacks& callbacks; + void* closure; +}; + +static PLDHashOperator +CustomDefinitionTrace(CustomElementHashKey *aKey, + CustomElementDefinition *aData, + void *aArg) +{ + CustomDefinitionTraceArgs* traceArgs = static_cast(aArg); + MOZ_ASSERT(aData, "Definition must not be null"); + traceArgs->callbacks.Trace(&aData->mPrototype, "mCustomDefinitions prototype", + traceArgs->closure); + return PL_DHASH_NEXT; +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(Registry) + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Registry) + CustomDefinitionTraceArgs customDefinitionArgs = { aCallbacks, aClosure }; + tmp->mCustomDefinitions.EnumerateRead(CustomDefinitionTrace, + &customDefinitionArgs); +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Registry) + tmp->mCustomDefinitions.EnumerateRead(CustomDefinitionsTraverse, &cb); + tmp->mCandidatesMap.EnumerateRead(CandidatesTraverse, &cb); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Registry) + tmp->mCustomDefinitions.Clear(); + tmp->mCandidatesMap.Clear(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Registry) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(Registry) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Registry) + +Registry::Registry() +{ + mozilla::HoldJSObjects(this); +} + +Registry::~Registry() +{ + mozilla::DropJSObjects(this); +} + +void +CustomElementCallback::Call() +{ + ErrorResult rv; + switch (mType) { + case nsIDocument::eCreated: + // For the duration of this callback invocation, the element is being created + // flag must be set to true. + mOwnerData->mElementIsBeingCreated = true; + mOwnerData->mCreatedCallbackInvoked = true; + static_cast(mCallback.get())->Call(mThisObject, rv); + mOwnerData->mElementIsBeingCreated = false; + break; + case nsIDocument::eAttached: + static_cast(mCallback.get())->Call(mThisObject, rv); + break; + case nsIDocument::eDetached: + static_cast(mCallback.get())->Call(mThisObject, rv); + break; + case nsIDocument::eAttributeChanged: + static_cast(mCallback.get())->Call(mThisObject, + mArgs.name, mArgs.oldValue, mArgs.newValue, rv); + break; + } +} + +void +CustomElementCallback::Traverse(nsCycleCollectionTraversalCallback& aCb) const +{ + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mThisObject"); + aCb.NoteXPCOMChild(mThisObject); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mCallback"); + aCb.NoteXPCOMChild(mCallback); +} + +CustomElementCallback::CustomElementCallback(Element* aThisObject, + nsIDocument::ElementCallbackType aCallbackType, + mozilla::dom::CallbackFunction* aCallback, + CustomElementData* aOwnerData) + : mThisObject(aThisObject), + mCallback(aCallback), + mType(aCallbackType), + mOwnerData(aOwnerData) +{ +} + +CustomElementDefinition::CustomElementDefinition(JSObject* aPrototype, + nsIAtom* aType, + nsIAtom* aLocalName, + LifecycleCallbacks* aCallbacks, + uint32_t aNamespaceID, + uint32_t aDocOrder) + : mPrototype(aPrototype), + mType(aType), + mLocalName(aLocalName), + mCallbacks(aCallbacks), + mNamespaceID(aNamespaceID), + mDocOrder(aDocOrder) +{ +} + +CustomElementData::CustomElementData(nsIAtom* aType) + : mType(aType), + mCurrentCallback(-1), + mElementIsBeingCreated(false), + mCreatedCallbackInvoked(true), + mAssociatedMicroTask(-1) +{ +} + +void +CustomElementData::RunCallbackQueue() +{ + // Note: It's possible to re-enter this method. + while (static_cast(++mCurrentCallback) < mCallbackQueue.Length()) { + mCallbackQueue[mCurrentCallback]->Call(); + } + + mCallbackQueue.Clear(); + mCurrentCallback = -1; +} + +} // namespace dom +} // namespace mozilla + +static PLDHashOperator +FireChangeEnumerator(nsIdentifierMapEntry::ChangeCallbackEntry *aEntry, void *aArg) +{ + FireChangeArgs* args = static_cast(aArg); + // Don't fire image changes for non-image observers, and don't fire element + // changes for image observers when an image override is active. + if (aEntry->mKey.mForImage ? (args->mHaveImageOverride && !args->mImageOnly) : + args->mImageOnly) + return PL_DHASH_NEXT; + return aEntry->mKey.mCallback(args->mFrom, args->mTo, aEntry->mKey.mData) + ? PL_DHASH_NEXT : PL_DHASH_REMOVE; +} + +void +nsIdentifierMapEntry::FireChangeCallbacks(Element* aOldElement, + Element* aNewElement, + bool aImageOnly) +{ + if (!mChangeCallbacks) + return; + + FireChangeArgs args = { aOldElement, aNewElement, aImageOnly, !!mImageElement }; + mChangeCallbacks->EnumerateEntries(FireChangeEnumerator, &args); +} + +bool +nsIdentifierMapEntry::AddIdElement(Element* aElement) +{ + NS_PRECONDITION(aElement, "Must have element"); + NS_PRECONDITION(mIdContentList.IndexOf(nullptr) < 0, + "Why is null in our list?"); + +#ifdef DEBUG + Element* currentElement = + static_cast(mIdContentList.SafeElementAt(0)); +#endif + + // Common case + if (mIdContentList.Count() == 0) { + if (!mIdContentList.AppendElement(aElement)) + return false; + NS_ASSERTION(currentElement == nullptr, "How did that happen?"); + FireChangeCallbacks(nullptr, aElement); + return true; + } + + // We seem to have multiple content nodes for the same id, or XUL is messing + // with us. Search for the right place to insert the content. + int32_t start = 0; + int32_t end = mIdContentList.Count(); + do { + NS_ASSERTION(start < end, "Bogus start/end"); + + int32_t cur = (start + end) / 2; + NS_ASSERTION(cur >= start && cur < end, "What happened here?"); + + Element* curElement = static_cast(mIdContentList[cur]); + if (curElement == aElement) { + // Already in the list, so already in the right spot. Get out of here. + // XXXbz this only happens because XUL does all sorts of random + // UpdateIdTableEntry calls. Hate, hate, hate! + return true; + } + + if (nsContentUtils::PositionIsBefore(aElement, curElement)) { + end = cur; + } else { + start = cur + 1; + } + } while (start != end); + + if (!mIdContentList.InsertElementAt(aElement, start)) + return false; + + if (start == 0) { + Element* oldElement = + static_cast(mIdContentList.SafeElementAt(1)); + NS_ASSERTION(currentElement == oldElement, "How did that happen?"); + FireChangeCallbacks(oldElement, aElement); + } + return true; +} + +void +nsIdentifierMapEntry::RemoveIdElement(Element* aElement) +{ + NS_PRECONDITION(aElement, "Missing element"); + + // This should only be called while the document is in an update. + // Assertions near the call to this method guarantee this. + + // This could fire in OOM situations + // Only assert this in HTML documents for now as XUL does all sorts of weird + // crap. + NS_ASSERTION(!aElement->OwnerDoc()->IsHTML() || + mIdContentList.IndexOf(aElement) >= 0, + "Removing id entry that doesn't exist"); + + // XXXbz should this ever Compact() I guess when all the content is gone + // we'll just get cleaned up in the natural order of things... + Element* currentElement = + static_cast(mIdContentList.SafeElementAt(0)); + mIdContentList.RemoveElement(aElement); + if (currentElement == aElement) { + FireChangeCallbacks(currentElement, + static_cast(mIdContentList.SafeElementAt(0))); + } +} + +void +nsIdentifierMapEntry::SetImageElement(Element* aElement) +{ + Element* oldElement = GetImageIdElement(); + mImageElement = aElement; + Element* newElement = GetImageIdElement(); + if (oldElement != newElement) { + FireChangeCallbacks(oldElement, newElement, true); + } +} + +void +nsIdentifierMapEntry::AddNameElement(nsINode* aNode, Element* aElement) +{ + if (!mNameContentList) { + mNameContentList = new nsSimpleContentList(aNode); + } + + mNameContentList->AppendElement(aElement); +} + +void +nsIdentifierMapEntry::RemoveNameElement(Element* aElement) +{ + if (mNameContentList) { + mNameContentList->RemoveElement(aElement); + } +} + +bool +nsIdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty() +{ + Element* idElement = GetIdElement(); + return idElement && + nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement); +} + +// static +size_t +nsIdentifierMapEntry::SizeOfExcludingThis(nsIdentifierMapEntry* aEntry, + MallocSizeOf aMallocSizeOf, + void*) +{ + return aEntry->GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +// Helper structs for the content->subdoc map + +class SubDocMapEntry : public PLDHashEntryHdr +{ +public: + // Both of these are strong references + Element *mKey; // must be first, to look like PLDHashEntryStub + nsIDocument *mSubDocument; +}; + +struct FindContentData +{ + FindContentData(nsIDocument *aSubDoc) + : mSubDocument(aSubDoc), mResult(nullptr) + { + } + + nsISupports *mSubDocument; + Element *mResult; +}; + + +/** + * A struct that holds all the information about a radio group. + */ +struct nsRadioGroupStruct +{ + nsRadioGroupStruct() + : mRequiredRadioCount(0) + , mGroupSuffersFromValueMissing(false) + {} + + /** + * A strong pointer to the currently selected radio button. + */ + nsRefPtr mSelectedRadioButton; + nsCOMArray mRadioButtons; + uint32_t mRequiredRadioCount; + bool mGroupSuffersFromValueMissing; +}; + + +nsDOMStyleSheetList::nsDOMStyleSheetList(nsIDocument *aDocument) +{ + mLength = -1; + // Not reference counted to avoid circular references. + // The document will tell us when its going away. + mDocument = aDocument; + mDocument->AddObserver(this); +} + +nsDOMStyleSheetList::~nsDOMStyleSheetList() +{ + if (mDocument) { + mDocument->RemoveObserver(this); + } +} + +NS_IMPL_ISUPPORTS_INHERITED(nsDOMStyleSheetList, StyleSheetList, + nsIDocumentObserver, + nsIMutationObserver) + +uint32_t +nsDOMStyleSheetList::Length() +{ + if (!mDocument) { + return 0; + } + + // XXX Find the number and then cache it. We'll use the + // observer notification to figure out if new ones have + // been added or removed. + if (-1 == mLength) { + mLength = mDocument->GetNumberOfStyleSheets(); + +#ifdef DEBUG + int32_t i; + for (i = 0; i < mLength; i++) { + nsIStyleSheet *sheet = mDocument->GetStyleSheetAt(i); + nsCOMPtr domss(do_QueryInterface(sheet)); + NS_ASSERTION(domss, "All \"normal\" sheets implement nsIDOMStyleSheet"); + } +#endif + } + return mLength; +} + +nsCSSStyleSheet* +nsDOMStyleSheetList::IndexedGetter(uint32_t aIndex, bool& aFound) +{ + if (!mDocument || aIndex >= (uint32_t)mDocument->GetNumberOfStyleSheets()) { + aFound = false; + return nullptr; + } + + aFound = true; + nsIStyleSheet *sheet = mDocument->GetStyleSheetAt(aIndex); + NS_ASSERTION(sheet, "Must have a sheet"); + + return static_cast(sheet); +} + +void +nsDOMStyleSheetList::NodeWillBeDestroyed(const nsINode *aNode) +{ + mDocument = nullptr; +} + +void +nsDOMStyleSheetList::StyleSheetAdded(nsIDocument *aDocument, + nsIStyleSheet* aStyleSheet, + bool aDocumentSheet) +{ + if (aDocumentSheet && -1 != mLength) { + nsCOMPtr domss(do_QueryInterface(aStyleSheet)); + if (domss) { + mLength++; + } + } +} + +void +nsDOMStyleSheetList::StyleSheetRemoved(nsIDocument *aDocument, + nsIStyleSheet* aStyleSheet, + bool aDocumentSheet) +{ + if (aDocumentSheet && -1 != mLength) { + nsCOMPtr domss(do_QueryInterface(aStyleSheet)); + if (domss) { + mLength--; + } + } +} + +// nsOnloadBlocker implementation +NS_IMPL_ISUPPORTS(nsOnloadBlocker, nsIRequest) + +NS_IMETHODIMP +nsOnloadBlocker::GetName(nsACString &aResult) +{ + aResult.AssignLiteral("about:document-onload-blocker"); + return NS_OK; +} + +NS_IMETHODIMP +nsOnloadBlocker::IsPending(bool *_retval) +{ + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +nsOnloadBlocker::GetStatus(nsresult *status) +{ + *status = NS_OK; + return NS_OK; +} + +NS_IMETHODIMP +nsOnloadBlocker::Cancel(nsresult status) +{ + return NS_OK; +} +NS_IMETHODIMP +nsOnloadBlocker::Suspend(void) +{ + return NS_OK; +} +NS_IMETHODIMP +nsOnloadBlocker::Resume(void) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsOnloadBlocker::GetLoadGroup(nsILoadGroup * *aLoadGroup) +{ + *aLoadGroup = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsOnloadBlocker::SetLoadGroup(nsILoadGroup * aLoadGroup) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsOnloadBlocker::GetLoadFlags(nsLoadFlags *aLoadFlags) +{ + *aLoadFlags = nsIRequest::LOAD_NORMAL; + return NS_OK; +} + +NS_IMETHODIMP +nsOnloadBlocker::SetLoadFlags(nsLoadFlags aLoadFlags) +{ + return NS_OK; +} + +// ================================================================== + +nsExternalResourceMap::nsExternalResourceMap() + : mHaveShutDown(false) +{ +} + +nsIDocument* +nsExternalResourceMap::RequestResource(nsIURI* aURI, + nsINode* aRequestingNode, + nsDocument* aDisplayDocument, + ExternalResourceLoad** aPendingLoad) +{ + // If we ever start allowing non-same-origin loads here, we might need to do + // something interesting with aRequestingPrincipal even for the hashtable + // gets. + NS_PRECONDITION(aURI, "Must have a URI"); + NS_PRECONDITION(aRequestingNode, "Must have a node"); + *aPendingLoad = nullptr; + if (mHaveShutDown) { + return nullptr; + } + + // First, make sure we strip the ref from aURI. + nsCOMPtr clone; + nsresult rv = aURI->CloneIgnoringRef(getter_AddRefs(clone)); + if (NS_FAILED(rv) || !clone) { + return nullptr; + } + + ExternalResource* resource; + mMap.Get(clone, &resource); + if (resource) { + return resource->mDocument; + } + + nsRefPtr load; + mPendingLoads.Get(clone, getter_AddRefs(load)); + if (load) { + load.forget(aPendingLoad); + return nullptr; + } + + load = new PendingLoad(aDisplayDocument); + + mPendingLoads.Put(clone, load); + + if (NS_FAILED(load->StartLoad(clone, aRequestingNode))) { + // Make sure we don't thrash things by trying this load again, since + // chances are it failed for good reasons (security check, etc). + AddExternalResource(clone, nullptr, nullptr, aDisplayDocument); + } else { + load.forget(aPendingLoad); + } + + return nullptr; +} + +struct +nsExternalResourceEnumArgs +{ + nsIDocument::nsSubDocEnumFunc callback; + void *data; +}; + +static PLDHashOperator +ExternalResourceEnumerator(nsIURI* aKey, + nsExternalResourceMap::ExternalResource* aData, + void* aClosure) +{ + nsExternalResourceEnumArgs* args = + static_cast(aClosure); + bool next = + aData->mDocument ? args->callback(aData->mDocument, args->data) : true; + return next ? PL_DHASH_NEXT : PL_DHASH_STOP; +} + +void +nsExternalResourceMap::EnumerateResources(nsIDocument::nsSubDocEnumFunc aCallback, + void* aData) +{ + nsExternalResourceEnumArgs args = { aCallback, aData }; + mMap.EnumerateRead(ExternalResourceEnumerator, &args); +} + +static PLDHashOperator +ExternalResourceTraverser(nsIURI* aKey, + nsExternalResourceMap::ExternalResource* aData, + void* aClosure) +{ + nsCycleCollectionTraversalCallback *cb = + static_cast(aClosure); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, + "mExternalResourceMap.mMap entry" + "->mDocument"); + cb->NoteXPCOMChild(aData->mDocument); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, + "mExternalResourceMap.mMap entry" + "->mViewer"); + cb->NoteXPCOMChild(aData->mViewer); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, + "mExternalResourceMap.mMap entry" + "->mLoadGroup"); + cb->NoteXPCOMChild(aData->mLoadGroup); + + return PL_DHASH_NEXT; +} + +void +nsExternalResourceMap::Traverse(nsCycleCollectionTraversalCallback* aCallback) const +{ + // mPendingLoads will get cleared out as the requests complete, so + // no need to worry about those here. + mMap.EnumerateRead(ExternalResourceTraverser, aCallback); +} + +static PLDHashOperator +ExternalResourceHider(nsIURI* aKey, + nsExternalResourceMap::ExternalResource* aData, + void* aClosure) +{ + if (aData->mViewer) { + aData->mViewer->Hide(); + } + return PL_DHASH_NEXT; +} + +void +nsExternalResourceMap::HideViewers() +{ + mMap.EnumerateRead(ExternalResourceHider, nullptr); +} + +static PLDHashOperator +ExternalResourceShower(nsIURI* aKey, + nsExternalResourceMap::ExternalResource* aData, + void* aClosure) +{ + if (aData->mViewer) { + aData->mViewer->Show(); + } + return PL_DHASH_NEXT; +} + +void +nsExternalResourceMap::ShowViewers() +{ + mMap.EnumerateRead(ExternalResourceShower, nullptr); +} + +void +TransferZoomLevels(nsIDocument* aFromDoc, + nsIDocument* aToDoc) +{ + NS_ABORT_IF_FALSE(aFromDoc && aToDoc, + "transferring zoom levels from/to null doc"); + + nsIPresShell* fromShell = aFromDoc->GetShell(); + if (!fromShell) + return; + + nsPresContext* fromCtxt = fromShell->GetPresContext(); + if (!fromCtxt) + return; + + nsIPresShell* toShell = aToDoc->GetShell(); + if (!toShell) + return; + + nsPresContext* toCtxt = toShell->GetPresContext(); + if (!toCtxt) + return; + + toCtxt->SetFullZoom(fromCtxt->GetFullZoom()); + toCtxt->SetBaseMinFontSize(fromCtxt->BaseMinFontSize()); + toCtxt->SetTextZoom(fromCtxt->TextZoom()); +} + +void +TransferShowingState(nsIDocument* aFromDoc, nsIDocument* aToDoc) +{ + NS_ABORT_IF_FALSE(aFromDoc && aToDoc, + "transferring showing state from/to null doc"); + + if (aFromDoc->IsShowing()) { + aToDoc->OnPageShow(true, nullptr); + } +} + +nsresult +nsExternalResourceMap::AddExternalResource(nsIURI* aURI, + nsIContentViewer* aViewer, + nsILoadGroup* aLoadGroup, + nsIDocument* aDisplayDocument) +{ + NS_PRECONDITION(aURI, "Unexpected call"); + NS_PRECONDITION((aViewer && aLoadGroup) || (!aViewer && !aLoadGroup), + "Must have both or neither"); + + nsRefPtr load; + mPendingLoads.Get(aURI, getter_AddRefs(load)); + mPendingLoads.Remove(aURI); + + nsresult rv = NS_OK; + + nsCOMPtr doc; + if (aViewer) { + doc = aViewer->GetDocument(); + NS_ASSERTION(doc, "Must have a document"); + + nsCOMPtr xulDoc = do_QueryInterface(doc); + if (xulDoc) { + // We don't handle XUL stuff here yet. + rv = NS_ERROR_NOT_AVAILABLE; + } else { + doc->SetDisplayDocument(aDisplayDocument); + + // Make sure that hiding our viewer will tear down its presentation. + aViewer->SetSticky(false); + + rv = aViewer->Init(nullptr, nsIntRect(0, 0, 0, 0)); + if (NS_SUCCEEDED(rv)) { + rv = aViewer->Open(nullptr, nullptr); + } + } + + if (NS_FAILED(rv)) { + doc = nullptr; + aViewer = nullptr; + aLoadGroup = nullptr; + } + } + + ExternalResource* newResource = new ExternalResource(); + mMap.Put(aURI, newResource); + + newResource->mDocument = doc; + newResource->mViewer = aViewer; + newResource->mLoadGroup = aLoadGroup; + if (doc) { + TransferZoomLevels(aDisplayDocument, doc); + TransferShowingState(aDisplayDocument, doc); + } + + const nsTArray< nsCOMPtr > & obs = load->Observers(); + for (uint32_t i = 0; i < obs.Length(); ++i) { + obs[i]->Observe(doc, "external-resource-document-created", nullptr); + } + + return rv; +} + +NS_IMPL_ISUPPORTS(nsExternalResourceMap::PendingLoad, + nsIStreamListener, + nsIRequestObserver) + +NS_IMETHODIMP +nsExternalResourceMap::PendingLoad::OnStartRequest(nsIRequest *aRequest, + nsISupports *aContext) +{ + nsExternalResourceMap& map = mDisplayDocument->ExternalResourceMap(); + if (map.HaveShutDown()) { + return NS_BINDING_ABORTED; + } + + nsCOMPtr viewer; + nsCOMPtr loadGroup; + nsresult rv = SetupViewer(aRequest, getter_AddRefs(viewer), + getter_AddRefs(loadGroup)); + + // Make sure to do this no matter what + nsresult rv2 = map.AddExternalResource(mURI, viewer, loadGroup, + mDisplayDocument); + if (NS_FAILED(rv)) { + return rv; + } + if (NS_FAILED(rv2)) { + mTargetListener = nullptr; + return rv2; + } + + return mTargetListener->OnStartRequest(aRequest, aContext); +} + +nsresult +nsExternalResourceMap::PendingLoad::SetupViewer(nsIRequest* aRequest, + nsIContentViewer** aViewer, + nsILoadGroup** aLoadGroup) +{ + NS_PRECONDITION(!mTargetListener, "Unexpected call to OnStartRequest"); + *aViewer = nullptr; + *aLoadGroup = nullptr; + + nsCOMPtr chan(do_QueryInterface(aRequest)); + NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED); + + nsCOMPtr httpChannel(do_QueryInterface(aRequest)); + if (httpChannel) { + bool requestSucceeded; + if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) || + !requestSucceeded) { + // Bail out on this load, since it looks like we have an HTTP error page + return NS_BINDING_ABORTED; + } + } + + nsAutoCString type; + chan->GetContentType(type); + + nsCOMPtr loadGroup; + chan->GetLoadGroup(getter_AddRefs(loadGroup)); + + // Give this document its own loadgroup + nsCOMPtr newLoadGroup = + do_CreateInstance(NS_LOADGROUP_CONTRACTID); + NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY); + newLoadGroup->SetLoadGroup(loadGroup); + + nsCOMPtr callbacks; + loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); + + nsCOMPtr newCallbacks = + new LoadgroupCallbacks(callbacks); + newLoadGroup->SetNotificationCallbacks(newCallbacks); + + // This is some serious hackery cribbed from docshell + nsCOMPtr catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE); + nsXPIDLCString contractId; + nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", type.get(), + getter_Copies(contractId)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr docLoaderFactory = + do_GetService(contractId); + NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE); + + nsCOMPtr viewer; + nsCOMPtr listener; + rv = docLoaderFactory->CreateInstance("external-resource", chan, newLoadGroup, + type.get(), nullptr, nullptr, + getter_AddRefs(listener), + getter_AddRefs(viewer)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED); + + nsCOMPtr parser = do_QueryInterface(listener); + if (!parser) { + /// We don't want to deal with the various fake documents yet + return NS_ERROR_NOT_IMPLEMENTED; + } + + // We can't handle HTML and other weird things here yet. + nsIContentSink* sink = parser->GetContentSink(); + nsCOMPtr xmlSink = do_QueryInterface(sink); + if (!xmlSink) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + listener.swap(mTargetListener); + viewer.forget(aViewer); + newLoadGroup.forget(aLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsExternalResourceMap::PendingLoad::OnDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aStream, + uint64_t aOffset, + uint32_t aCount) +{ + NS_PRECONDITION(mTargetListener, "Shouldn't be getting called!"); + if (mDisplayDocument->ExternalResourceMap().HaveShutDown()) { + return NS_BINDING_ABORTED; + } + return mTargetListener->OnDataAvailable(aRequest, aContext, aStream, aOffset, + aCount); +} + +NS_IMETHODIMP +nsExternalResourceMap::PendingLoad::OnStopRequest(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus) +{ + // mTargetListener might be null if SetupViewer or AddExternalResource failed + if (mTargetListener) { + nsCOMPtr listener; + mTargetListener.swap(listener); + return listener->OnStopRequest(aRequest, aContext, aStatus); + } + + return NS_OK; +} + +nsresult +nsExternalResourceMap::PendingLoad::StartLoad(nsIURI* aURI, + nsINode* aRequestingNode) +{ + NS_PRECONDITION(aURI, "Must have a URI"); + NS_PRECONDITION(aRequestingNode, "Must have a node"); + + // Time to start a load. First, the security checks. + + nsIPrincipal* requestingPrincipal = aRequestingNode->NodePrincipal(); + + nsresult rv = nsContentUtils::GetSecurityManager()-> + CheckLoadURIWithPrincipal(requestingPrincipal, aURI, + nsIScriptSecurityManager::STANDARD); + NS_ENSURE_SUCCESS(rv, rv); + + // Allow data URIs and other URI's that inherit their principal by passing + // true as the 3rd argument of CheckMayLoad, since we want + // to allow external resources from data URIs regardless of the difference + // in URI scheme. + rv = requestingPrincipal->CheckMayLoad(aURI, true, true); + NS_ENSURE_SUCCESS(rv, rv); + + int16_t shouldLoad = nsIContentPolicy::ACCEPT; + rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_OTHER, + aURI, + requestingPrincipal, + aRequestingNode, + EmptyCString(), //mime guess + nullptr, //extra + &shouldLoad, + nsContentUtils::GetContentPolicy(), + nsContentUtils::GetSecurityManager()); + if (NS_FAILED(rv)) return rv; + if (NS_CP_REJECTED(shouldLoad)) { + // Disallowed by content policy + return NS_ERROR_CONTENT_BLOCKED; + } + + nsIDocument* doc = aRequestingNode->OwnerDoc(); + + nsCOMPtr req = nsContentUtils::GetSameOriginChecker(); + NS_ENSURE_TRUE(req, NS_ERROR_OUT_OF_MEMORY); + + nsCOMPtr loadGroup = doc->GetDocumentLoadGroup(); + nsCOMPtr channel; + rv = NS_NewChannel(getter_AddRefs(channel), aURI, nullptr, loadGroup, req); + NS_ENSURE_SUCCESS(rv, rv); + + mURI = aURI; + + return channel->AsyncOpen(this, nullptr); +} + +NS_IMPL_ISUPPORTS(nsExternalResourceMap::LoadgroupCallbacks, + nsIInterfaceRequestor) + +#define IMPL_SHIM(_i) \ + NS_IMPL_ISUPPORTS(nsExternalResourceMap::LoadgroupCallbacks::_i##Shim, _i) + +IMPL_SHIM(nsILoadContext) +IMPL_SHIM(nsIProgressEventSink) +IMPL_SHIM(nsIChannelEventSink) +IMPL_SHIM(nsISecurityEventSink) +IMPL_SHIM(nsIApplicationCacheContainer) + +#undef IMPL_SHIM + +#define IID_IS(_i) aIID.Equals(NS_GET_IID(_i)) + +#define TRY_SHIM(_i) \ + PR_BEGIN_MACRO \ + if (IID_IS(_i)) { \ + nsCOMPtr<_i> real = do_GetInterface(mCallbacks); \ + if (!real) { \ + return NS_NOINTERFACE; \ + } \ + nsCOMPtr<_i> shim = new _i##Shim(this, real); \ + if (!shim) { \ + return NS_ERROR_OUT_OF_MEMORY; \ + } \ + shim.forget(aSink); \ + return NS_OK; \ + } \ + PR_END_MACRO + +NS_IMETHODIMP +nsExternalResourceMap::LoadgroupCallbacks::GetInterface(const nsIID & aIID, + void **aSink) +{ + if (mCallbacks && + (IID_IS(nsIPrompt) || IID_IS(nsIAuthPrompt) || IID_IS(nsIAuthPrompt2) || + IID_IS(nsITabChild))) { + return mCallbacks->GetInterface(aIID, aSink); + } + + *aSink = nullptr; + + TRY_SHIM(nsILoadContext); + TRY_SHIM(nsIProgressEventSink); + TRY_SHIM(nsIChannelEventSink); + TRY_SHIM(nsISecurityEventSink); + TRY_SHIM(nsIApplicationCacheContainer); + + return NS_NOINTERFACE; +} + +#undef TRY_SHIM +#undef IID_IS + +nsExternalResourceMap::ExternalResource::~ExternalResource() +{ + if (mViewer) { + mViewer->Close(nullptr); + mViewer->Destroy(); + } +} + +// ================================================================== +// = +// ================================================================== + +// If we ever have an nsIDocumentObserver notification for stylesheet title +// changes we should update the list from that instead of overriding +// EnsureFresh. +class nsDOMStyleSheetSetList MOZ_FINAL : public DOMStringList +{ +public: + nsDOMStyleSheetSetList(nsIDocument* aDocument); + + void Disconnect() + { + mDocument = nullptr; + } + + virtual void EnsureFresh() MOZ_OVERRIDE; + +protected: + nsIDocument* mDocument; // Our document; weak ref. It'll let us know if it + // dies. +}; + +nsDOMStyleSheetSetList::nsDOMStyleSheetSetList(nsIDocument* aDocument) + : mDocument(aDocument) +{ + NS_ASSERTION(mDocument, "Must have document!"); +} + +void +nsDOMStyleSheetSetList::EnsureFresh() +{ + mNames.Clear(); + + if (!mDocument) { + return; // Spec says "no exceptions", and we have no style sets if we have + // no document, for sure + } + + int32_t count = mDocument->GetNumberOfStyleSheets(); + nsAutoString title; + for (int32_t index = 0; index < count; index++) { + nsIStyleSheet* sheet = mDocument->GetStyleSheetAt(index); + NS_ASSERTION(sheet, "Null sheet in sheet list!"); + sheet->GetTitle(title); + if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) { + return; + } + } +} + +// ================================================================== +nsIDocument::SelectorCache::SelectorCache() + : nsExpirationTracker(1000) { } + +// CacheList takes ownership of aSelectorList. +void nsIDocument::SelectorCache::CacheList(const nsAString& aSelector, + nsCSSSelectorList* aSelectorList) +{ + SelectorCacheKey* key = new SelectorCacheKey(aSelector); + mTable.Put(key->mKey, aSelectorList); + AddObject(key); +} + +class nsIDocument::SelectorCacheKeyDeleter MOZ_FINAL : public nsRunnable +{ +public: + explicit SelectorCacheKeyDeleter(SelectorCacheKey* aToDelete) + : mSelector(aToDelete) + { + MOZ_COUNT_CTOR(SelectorCacheKeyDeleter); + } + + ~SelectorCacheKeyDeleter() + { + MOZ_COUNT_DTOR(SelectorCacheKeyDeleter); + } + + NS_IMETHOD Run() + { + return NS_OK; + } + +private: + nsAutoPtr mSelector; +}; + +void nsIDocument::SelectorCache::NotifyExpired(SelectorCacheKey* aSelector) +{ + RemoveObject(aSelector); + mTable.Remove(aSelector->mKey); + nsCOMPtr runnable = new SelectorCacheKeyDeleter(aSelector); + NS_DispatchToCurrentThread(runnable); +} + + +struct nsIDocument::FrameRequest +{ + FrameRequest(const FrameRequestCallbackHolder& aCallback, + int32_t aHandle) : + mCallback(aCallback), + mHandle(aHandle) + {} + + // Conversion operator so that we can append these to a + // FrameRequestCallbackList + operator const FrameRequestCallbackHolder& () const { + return mCallback; + } + + // Comparator operators to allow RemoveElementSorted with an + // integer argument on arrays of FrameRequest + bool operator==(int32_t aHandle) const { + return mHandle == aHandle; + } + bool operator<(int32_t aHandle) const { + return mHandle < aHandle; + } + + FrameRequestCallbackHolder mCallback; + int32_t mHandle; +}; + +static already_AddRefed nullNodeInfo(nullptr); + +// ================================================================== +// = +// ================================================================== +nsIDocument::nsIDocument() + : nsINode(nullNodeInfo), + mCharacterSet(NS_LITERAL_CSTRING("ISO-8859-1")), + mNodeInfoManager(nullptr), + mCompatMode(eCompatibility_FullStandards), + mVisibilityState(dom::VisibilityState::Hidden), + mIsInitialDocumentInWindow(false), + mMayStartLayout(true), + mVisible(true), + mRemovedFromDocShell(false), + // mAllowDNSPrefetch starts true, so that we can always reliably && it + // with various values that might disable it. Since we never prefetch + // unless we get a window, and in that case the docshell value will get + // &&-ed in, this is safe. + mAllowDNSPrefetch(true), + mIsBeingUsedAsImage(false), + mHasLinksToUpdate(false), + mPartID(0), + mDidFireDOMContentLoaded(true) +{ + SetInDocument(); +} + +// NOTE! nsDocument::operator new() zeroes out all members, so don't +// bother initializing members to 0. + +nsDocument::nsDocument(const char* aContentType) + : nsIDocument() + , mAnimatingImages(true) + , mViewportType(Unknown) +{ + SetContentTypeInternal(nsDependentCString(aContentType)); + +#ifdef PR_LOGGING + if (!gDocumentLeakPRLog) + gDocumentLeakPRLog = PR_NewLogModule("DocumentLeak"); + + if (gDocumentLeakPRLog) + PR_LOG(gDocumentLeakPRLog, PR_LOG_DEBUG, + ("DOCUMENT %p created", this)); + + if (!gCspPRLog) + gCspPRLog = PR_NewLogModule("CSP"); +#endif + + // Start out mLastStyleSheetSet as null, per spec + SetDOMStringToNull(mLastStyleSheetSet); + + if (sProcessingStack.empty()) { + sProcessingStack.construct(); + // Add the base queue sentinel to the processing stack. + sProcessingStack.ref().AppendElement((CustomElementData*) nullptr); + } +} + +static PLDHashOperator +ClearAllBoxObjects(nsIContent* aKey, nsPIBoxObject* aBoxObject, void* aUserArg) +{ + if (aBoxObject) { + aBoxObject->Clear(); + } + return PL_DHASH_NEXT; +} + +nsIDocument::~nsIDocument() +{ + if (mNodeInfoManager) { + mNodeInfoManager->DropDocumentReference(); + } +} + + +nsDocument::~nsDocument() +{ +#ifdef PR_LOGGING + if (gDocumentLeakPRLog) + PR_LOG(gDocumentLeakPRLog, PR_LOG_DEBUG, + ("DOCUMENT %p destroyed", this)); +#endif + + NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document"); + + if (IsTopLevelContentDocument()) { + //don't report for about: pages + nsCOMPtr principal = GetPrincipal(); + nsCOMPtr uri; + principal->GetURI(getter_AddRefs(uri)); + bool isAboutScheme = true; + if (uri) { + uri->SchemeIs("about", &isAboutScheme); + } + + if (!isAboutScheme) { + // Record the page load + uint32_t pageLoaded = 1; + Accumulate(Telemetry::MIXED_CONTENT_UNBLOCK_COUNTER, pageLoaded); + // Record the mixed content status of the docshell in Telemetry + enum { + NO_MIXED_CONTENT = 0, // There is no Mixed Content on the page + MIXED_DISPLAY_CONTENT = 1, // The page attempted to load Mixed Display Content + MIXED_ACTIVE_CONTENT = 2, // The page attempted to load Mixed Active Content + MIXED_DISPLAY_AND_ACTIVE_CONTENT = 3 // The page attempted to load Mixed Display & Mixed Active Content + }; + + bool mixedActiveLoaded = GetHasMixedActiveContentLoaded(); + bool mixedActiveBlocked = GetHasMixedActiveContentBlocked(); + + bool mixedDisplayLoaded = GetHasMixedDisplayContentLoaded(); + bool mixedDisplayBlocked = GetHasMixedDisplayContentBlocked(); + + bool hasMixedDisplay = (mixedDisplayBlocked || mixedDisplayLoaded); + bool hasMixedActive = (mixedActiveBlocked || mixedActiveLoaded); + + uint32_t mixedContentLevel = NO_MIXED_CONTENT; + if (hasMixedDisplay && hasMixedActive) { + mixedContentLevel = MIXED_DISPLAY_AND_ACTIVE_CONTENT; + } else if (hasMixedActive){ + mixedContentLevel = MIXED_ACTIVE_CONTENT; + } else if (hasMixedDisplay) { + mixedContentLevel = MIXED_DISPLAY_CONTENT; + } + Accumulate(Telemetry::MIXED_CONTENT_PAGE_LOAD, mixedContentLevel); + } + } + + mInDestructor = true; + mInUnlinkOrDeletion = true; + + mRegistry = nullptr; + + mozilla::DropJSObjects(this); + + // Clear mObservers to keep it in sync with the mutationobserver list + mObservers.Clear(); + + if (mStyleSheetSetList) { + mStyleSheetSetList->Disconnect(); + } + + if (mAnimationController) { + mAnimationController->Disconnect(); + } + + mParentDocument = nullptr; + + // Kill the subdocument map, doing this will release its strong + // references, if any. + if (mSubDocuments) { + PL_DHashTableDestroy(mSubDocuments); + + mSubDocuments = nullptr; + } + + // Destroy link map now so we don't waste time removing + // links one by one + DestroyElementMaps(); + + nsAutoScriptBlocker scriptBlocker; + + int32_t indx; // must be signed + uint32_t count = mChildren.ChildCount(); + for (indx = int32_t(count) - 1; indx >= 0; --indx) { + mChildren.ChildAt(indx)->UnbindFromTree(); + mChildren.RemoveChildAt(indx); + } + mFirstChild = nullptr; + mCachedRootElement = nullptr; + + // Let the stylesheets know we're going away + indx = mStyleSheets.Count(); + while (--indx >= 0) { + mStyleSheets[indx]->SetOwningDocument(nullptr); + } + indx = mCatalogSheets.Count(); + while (--indx >= 0) { + static_cast(mCatalogSheets[indx])->SetOwningNode(nullptr); + mCatalogSheets[indx]->SetOwningDocument(nullptr); + } + if (mAttrStyleSheet) { + mAttrStyleSheet->SetOwningDocument(nullptr); + } + + if (mListenerManager) { + mListenerManager->Disconnect(); + UnsetFlags(NODE_HAS_LISTENERMANAGER); + } + + if (mScriptLoader) { + mScriptLoader->DropDocumentReference(); + } + + if (mCSSLoader) { + // Could be null here if Init() failed + mCSSLoader->DropDocumentReference(); + } + + if (mStyleImageLoader) { + mStyleImageLoader->DropDocumentReference(); + } + + delete mHeaderData; + + if (mBoxObjectTable) { + mBoxObjectTable->EnumerateRead(ClearAllBoxObjects, nullptr); + delete mBoxObjectTable; + } + + mPendingTitleChangeEvent.Revoke(); + + for (uint32_t i = 0; i < mHostObjectURIs.Length(); ++i) { + nsHostObjectProtocolHandler::RemoveDataEntry(mHostObjectURIs[i]); + } + + // We don't want to leave residual locks on images. Make sure we're in an + // unlocked state, and then clear the table. + SetImageLockingState(false); + mImageTracker.Clear(); + + mPlugins.Clear(); +} + +NS_INTERFACE_TABLE_HEAD(nsDocument) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_TABLE_BEGIN + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsDocument, nsISupports, nsINode) + NS_INTERFACE_TABLE_ENTRY(nsDocument, nsINode) + NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDocument) + NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMDocument) + NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMNode) + NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMDocumentXBL) + NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIScriptObjectPrincipal) + NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMEventTarget) + NS_INTERFACE_TABLE_ENTRY(nsDocument, mozilla::dom::EventTarget) + NS_INTERFACE_TABLE_ENTRY(nsDocument, nsISupportsWeakReference) + NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIRadioGroupContainer) + NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIMutationObserver) + NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIApplicationCacheContainer) + NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIObserver) + NS_INTERFACE_TABLE_ENTRY(nsDocument, nsIDOMXPathEvaluator) + NS_INTERFACE_TABLE_END + NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsDocument) + NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIDOMXPathNSResolver, + new nsNode3Tearoff(this)) +NS_INTERFACE_MAP_END + + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocument) +NS_IMETHODIMP_(MozExternalRefCountType) +nsDocument::Release() +{ + NS_PRECONDITION(0 != mRefCnt, "dup release"); + NS_ASSERT_OWNINGTHREAD(nsDocument); + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(nsDocument)::Upcast(this); + bool shouldDelete = false; + nsrefcnt count = mRefCnt.decr(base, &shouldDelete); + NS_LOG_RELEASE(this, count, "nsDocument"); + if (count == 0) { + if (mStackRefCnt && !mNeedsReleaseAfterStackRefCntRelease) { + mNeedsReleaseAfterStackRefCntRelease = true; + NS_ADDREF_THIS(); + return mRefCnt.get(); + } + mRefCnt.incr(base); + nsNodeUtils::LastRelease(this); + mRefCnt.decr(base); + if (shouldDelete) { + mRefCnt.stabilizeForDeletion(); + DeleteCycleCollectable(); + } + } + return count; +} + +NS_IMETHODIMP_(void) +nsDocument::DeleteCycleCollectable() +{ + delete this; +} + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDocument) + if (Element::CanSkip(tmp, aRemovingAllowed)) { + EventListenerManager* elm = tmp->GetExistingListenerManager(); + if (elm) { + elm->MarkForCC(); + } + return true; + } +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsDocument) + return Element::CanSkipInCC(tmp); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDocument) + return Element::CanSkipThis(tmp); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END + +static PLDHashOperator +SubDocTraverser(PLDHashTable *table, PLDHashEntryHdr *hdr, uint32_t number, + void *arg) +{ + SubDocMapEntry *entry = static_cast(hdr); + nsCycleCollectionTraversalCallback *cb = + static_cast(arg); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mSubDocuments entry->mKey"); + cb->NoteXPCOMChild(entry->mKey); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mSubDocuments entry->mSubDocument"); + cb->NoteXPCOMChild(entry->mSubDocument); + + return PL_DHASH_NEXT; +} + +static PLDHashOperator +RadioGroupsTraverser(const nsAString& aKey, nsRadioGroupStruct* aData, + void* aClosure) +{ + nsCycleCollectionTraversalCallback *cb = + static_cast(aClosure); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, + "mRadioGroups entry->mSelectedRadioButton"); + cb->NoteXPCOMChild(ToSupports(aData->mSelectedRadioButton)); + + uint32_t i, count = aData->mRadioButtons.Count(); + for (i = 0; i < count; ++i) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, + "mRadioGroups entry->mRadioButtons[i]"); + cb->NoteXPCOMChild(aData->mRadioButtons[i]); + } + + return PL_DHASH_NEXT; +} + +static PLDHashOperator +BoxObjectTraverser(nsIContent* key, nsPIBoxObject* boxObject, void* userArg) +{ + nsCycleCollectionTraversalCallback *cb = + static_cast(userArg); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "mBoxObjectTable entry"); + cb->NoteXPCOMChild(boxObject); + + return PL_DHASH_NEXT; +} + +static PLDHashOperator +IdentifierMapEntryTraverse(nsIdentifierMapEntry *aEntry, void *aArg) +{ + nsCycleCollectionTraversalCallback *cb = + static_cast(aArg); + aEntry->Traverse(cb); + return PL_DHASH_NEXT; +} + +static const char* kNSURIs[] = { + "([none])", + "(xmlns)", + "(xml)", + "(xhtml)", + "(XLink)", + "(XSLT)", + "(XBL)", + "(MathML)", + "(RDF)", + "(XUL)" +}; + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument) + if (MOZ_UNLIKELY(cb.WantDebugInfo())) { + char name[512]; + nsAutoCString loadedAsData; + if (tmp->IsLoadedAsData()) { + loadedAsData.AssignLiteral("data"); + } else { + loadedAsData.AssignLiteral("normal"); + } + uint32_t nsid = tmp->GetDefaultNamespaceID(); + nsAutoCString uri; + if (tmp->mDocumentURI) + tmp->mDocumentURI->GetSpec(uri); + if (nsid < ArrayLength(kNSURIs)) { + PR_snprintf(name, sizeof(name), "nsDocument %s %s %s", + loadedAsData.get(), kNSURIs[nsid], uri.get()); + } + else { + PR_snprintf(name, sizeof(name), "nsDocument %s %s", + loadedAsData.get(), uri.get()); + } + cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name); + } + else { + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsDocument, tmp->mRefCnt.get()) + } + + // Always need to traverse script objects, so do that before we check + // if we're uncollectable. + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS + + if (!nsINode::Traverse(tmp, cb)) { + return NS_SUCCESS_INTERRUPTED_TRAVERSE; + } + + tmp->mIdentifierMap.EnumerateEntries(IdentifierMapEntryTraverse, &cb); + + tmp->mExternalResourceMap.Traverse(&cb); + + // Traverse the mChildren nsAttrAndChildArray. + for (int32_t indx = int32_t(tmp->mChildren.ChildCount()); indx > 0; --indx) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mChildren[i]"); + cb.NoteXPCOMChild(tmp->mChildren.ChildAt(indx - 1)); + } + + // Traverse all nsIDocument pointer members. + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument) + + // Traverse all nsDocument nsCOMPtrs. + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader) + + tmp->mRadioGroups.EnumerateRead(RadioGroupsTraverser, &cb); + + // The boxobject for an element will only exist as long as it's in the + // document, so we'll traverse the table here instead of from the element. + if (tmp->mBoxObjectTable) { + tmp->mBoxObjectTable->EnumerateRead(BoxObjectTraverser, &cb); + } + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleAttrStyleSheet) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mXPathEvaluator) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLayoutHistoryState) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnloadBlocker) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFirstBaseNodeWithHref) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMImplementation) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageMaps) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStateObjectCached) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUndoManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRegistry) + + // Traverse all our nsCOMArrays. + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCatalogSheets) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages) + + for (uint32_t i = 0; i < tmp->mFrameRequestCallbacks.Length(); ++i) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFrameRequestCallbacks[i]"); + cb.NoteXPCOMChild(tmp->mFrameRequestCallbacks[i].mCallback.GetISupports()); + } + + // Traverse animation components + if (tmp->mAnimationController) { + tmp->mAnimationController->Traverse(&cb); + } + + if (tmp->mSubDocuments && tmp->mSubDocuments->ops) { + PL_DHashTableEnumerate(tmp->mSubDocuments, SubDocTraverser, &cb); + } + + if (tmp->mCSSLoader) { + tmp->mCSSLoader->TraverseCachedSheets(cb); + } + + for (uint32_t i = 0; i < tmp->mHostObjectURIs.Length(); ++i) { + nsHostObjectProtocolHandler::Traverse(tmp->mHostObjectURIs[i], cb); + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocument) + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDocument) + if (tmp->PreservingWrapper()) { + NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mExpandoAndGeneration.expando); + } + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument) + tmp->mInUnlinkOrDeletion = true; + + // Clear out our external resources + tmp->mExternalResourceMap.Shutdown(); + + nsAutoScriptBlocker scriptBlocker; + + nsINode::Unlink(tmp); + + // Unlink the mChildren nsAttrAndChildArray. + for (int32_t indx = int32_t(tmp->mChildren.ChildCount()) - 1; + indx >= 0; --indx) { + tmp->mChildren.ChildAt(indx)->UnbindFromTree(); + tmp->mChildren.RemoveChildAt(indx); + } + tmp->mFirstChild = nullptr; + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mXPathEvaluator) + tmp->mCachedRootElement = nullptr; // Avoid a dangling pointer + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDisplayDocument) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFirstBaseNodeWithHref) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mUndoManager) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRegistry) + + tmp->mParentDocument = nullptr; + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages) + + + if (tmp->mBoxObjectTable) { + tmp->mBoxObjectTable->EnumerateRead(ClearAllBoxObjects, nullptr); + delete tmp->mBoxObjectTable; + tmp->mBoxObjectTable = nullptr; + } + + if (tmp->mListenerManager) { + tmp->mListenerManager->Disconnect(); + tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER); + tmp->mListenerManager = nullptr; + } + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets) + + if (tmp->mStyleSheetSetList) { + tmp->mStyleSheetSetList->Disconnect(); + tmp->mStyleSheetSetList = nullptr; + } + + if (tmp->mSubDocuments) { + PL_DHashTableDestroy(tmp->mSubDocuments); + tmp->mSubDocuments = nullptr; + } + + tmp->mFrameRequestCallbacks.Clear(); + + tmp->mRadioGroups.Clear(); + + // nsDocument has a pretty complex destructor, so we're going to + // assume that *most* cycles you actually want to break somewhere + // else, and not unlink an awful lot here. + + tmp->mIdentifierMap.Clear(); + tmp->mExpandoAndGeneration.Unlink(); + + if (tmp->mAnimationController) { + tmp->mAnimationController->Unlink(); + } + + tmp->mPendingTitleChangeEvent.Revoke(); + + if (tmp->mCSSLoader) { + tmp->mCSSLoader->UnlinkCachedSheets(); + } + + for (uint32_t i = 0; i < tmp->mHostObjectURIs.Length(); ++i) { + nsHostObjectProtocolHandler::RemoveDataEntry(tmp->mHostObjectURIs[i]); + } + + tmp->mInUnlinkOrDeletion = false; +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +static bool sPrefsInitialized = false; +static uint32_t sOnloadDecodeLimit = 0; + +nsresult +nsDocument::Init() +{ + if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + if (!sPrefsInitialized) { + sPrefsInitialized = true; + Preferences::AddUintVarCache(&sOnloadDecodeLimit, "image.onload.decode.limit", 0); + } + + // Force initialization. + nsINode::nsSlots* slots = Slots(); + + // Prepend self as mutation-observer whether we need it or not (some + // subclasses currently do, other don't). This is because the code in + // nsNodeUtils always notifies the first observer first, expecting the + // first observer to be the document. + NS_ENSURE_TRUE(slots->mMutationObservers.PrependElementUnlessExists(static_cast(this)), + NS_ERROR_OUT_OF_MEMORY); + + + mOnloadBlocker = new nsOnloadBlocker(); + mCSSLoader = new mozilla::css::Loader(this); + // Assume we're not quirky, until we know otherwise + mCSSLoader->SetCompatibilityMode(eCompatibility_FullStandards); + + mStyleImageLoader = new mozilla::css::ImageLoader(this); + + mNodeInfoManager = new nsNodeInfoManager(); + nsresult rv = mNodeInfoManager->Init(this); + NS_ENSURE_SUCCESS(rv, rv); + + // mNodeInfo keeps NodeInfoManager alive! + mNodeInfo = mNodeInfoManager->GetDocumentNodeInfo(); + NS_ENSURE_TRUE(mNodeInfo, NS_ERROR_OUT_OF_MEMORY); + NS_ABORT_IF_FALSE(mNodeInfo->NodeType() == nsIDOMNode::DOCUMENT_NODE, + "Bad NodeType in aNodeInfo"); + + NS_ASSERTION(OwnerDoc() == this, "Our nodeinfo is busted!"); + + // If after creation the owner js global is not set for a document + // we use the default compartment for this document, instead of creating + // wrapper in some random compartment when the document is exposed to js + // via some events. + nsCOMPtr global = xpc::GetJunkScopeGlobal(); + NS_ENSURE_TRUE(global, NS_ERROR_FAILURE); + mScopeObject = do_GetWeakReference(global); + MOZ_ASSERT(mScopeObject); + + mScriptLoader = new nsScriptLoader(this); + + mozilla::HoldJSObjects(this); + + return NS_OK; +} + +void +nsIDocument::DeleteAllProperties() +{ + for (uint32_t i = 0; i < GetPropertyTableCount(); ++i) { + PropertyTable(i)->DeleteAllProperties(); + } +} + +void +nsIDocument::DeleteAllPropertiesFor(nsINode* aNode) +{ + for (uint32_t i = 0; i < GetPropertyTableCount(); ++i) { + PropertyTable(i)->DeleteAllPropertiesFor(aNode); + } +} + +nsPropertyTable* +nsIDocument::GetExtraPropertyTable(uint16_t aCategory) +{ + NS_ASSERTION(aCategory > 0, "Category 0 should have already been handled"); + while (aCategory >= mExtraPropertyTables.Length() + 1) { + mExtraPropertyTables.AppendElement(new nsPropertyTable()); + } + return mExtraPropertyTables[aCategory - 1]; +} + +bool +nsIDocument::IsVisibleConsideringAncestors() const +{ + const nsIDocument *parent = this; + do { + if (!parent->IsVisible()) { + return false; + } + } while ((parent = parent->GetParentDocument())); + + return true; + } + +void +nsDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) +{ + nsCOMPtr uri; + nsCOMPtr principal; + if (aChannel) { + // Note: this code is duplicated in XULDocument::StartDocumentLoad and + // nsScriptSecurityManager::GetChannelPrincipal. + // Note: this should match nsDocShell::OnLoadingSite + NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + + nsIScriptSecurityManager *securityManager = + nsContentUtils::GetSecurityManager(); + if (securityManager) { + securityManager->GetChannelPrincipal(aChannel, + getter_AddRefs(principal)); + } + } + + ResetToURI(uri, aLoadGroup, principal); + + nsCOMPtr bag = do_QueryInterface(aChannel); + if (bag) { + nsCOMPtr baseURI; + bag->GetPropertyAsInterface(NS_LITERAL_STRING("baseURI"), + NS_GET_IID(nsIURI), getter_AddRefs(baseURI)); + if (baseURI) { + mDocumentBaseURI = baseURI; + mChromeXHRDocBaseURI = baseURI; + } + } + + mChannel = aChannel; +} + +void +nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup, + nsIPrincipal* aPrincipal) +{ + NS_PRECONDITION(aURI, "Null URI passed to ResetToURI"); + +#ifdef PR_LOGGING + if (gDocumentLeakPRLog && PR_LOG_TEST(gDocumentLeakPRLog, PR_LOG_DEBUG)) { + nsAutoCString spec; + aURI->GetSpec(spec); + PR_LogPrint("DOCUMENT %p ResetToURI %s", this, spec.get()); + } +#endif + + mSecurityInfo = nullptr; + + mDocumentLoadGroup = nullptr; + + // Delete references to sub-documents and kill the subdocument map, + // if any. It holds strong references + if (mSubDocuments) { + PL_DHashTableDestroy(mSubDocuments); + + mSubDocuments = nullptr; + } + + // Destroy link map now so we don't waste time removing + // links one by one + DestroyElementMaps(); + + bool oldVal = mInUnlinkOrDeletion; + mInUnlinkOrDeletion = true; + uint32_t count = mChildren.ChildCount(); + { // Scope for update + MOZ_AUTO_DOC_UPDATE(this, UPDATE_CONTENT_MODEL, true); + for (int32_t i = int32_t(count) - 1; i >= 0; i--) { + nsCOMPtr content = mChildren.ChildAt(i); + + nsIContent* previousSibling = content->GetPreviousSibling(); + + if (nsINode::GetFirstChild() == content) { + mFirstChild = content->GetNextSibling(); + } + mChildren.RemoveChildAt(i); + nsNodeUtils::ContentRemoved(this, content, i, previousSibling); + content->UnbindFromTree(); + } + mCachedRootElement = nullptr; + } + mInUnlinkOrDeletion = oldVal; + + mRegistry = nullptr; + + // Reset our stylesheets + ResetStylesheetsToURI(aURI); + + // Release the listener manager + if (mListenerManager) { + mListenerManager->Disconnect(); + mListenerManager = nullptr; + } + + // Release the stylesheets list. + mDOMStyleSheets = nullptr; + + // Release our principal after tearing down the document, rather than before. + // This ensures that, during teardown, the document and the dying window (which + // already nulled out its document pointer and cached the principal) have + // matching principals. + SetPrincipal(nullptr); + + // Clear the original URI so SetDocumentURI sets it. + mOriginalURI = nullptr; + + SetDocumentURI(aURI); + mChromeXHRDocURI = aURI; + // If mDocumentBaseURI is null, nsIDocument::GetBaseURI() returns + // mDocumentURI. + mDocumentBaseURI = nullptr; + mChromeXHRDocBaseURI = nullptr; + + if (aLoadGroup) { + mDocumentLoadGroup = do_GetWeakReference(aLoadGroup); + // there was an assertion here that aLoadGroup was not null. This + // is no longer valid: nsDocShell::SetDocument does not create a + // load group, and it works just fine + + // XXXbz what does "just fine" mean exactly? And given that there + // is no nsDocShell::SetDocument, what is this talking about? + } + + mLastModified.Truncate(); + // XXXbz I guess we're assuming that the caller will either pass in + // a channel with a useful type or call SetContentType? + SetContentTypeInternal(EmptyCString()); + mContentLanguage.Truncate(); + mBaseTarget.Truncate(); + mReferrer.Truncate(); + + mXMLDeclarationBits = 0; + + // Now get our new principal + if (aPrincipal) { + SetPrincipal(aPrincipal); + } else { + nsIScriptSecurityManager *securityManager = + nsContentUtils::GetSecurityManager(); + if (securityManager) { + nsCOMPtr docShell(mDocumentContainer); + + if (!docShell && aLoadGroup) { + nsCOMPtr cbs; + aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs)); + docShell = do_GetInterface(cbs); + } + + MOZ_ASSERT(docShell, + "must be in a docshell or pass in an explicit principal"); + + nsCOMPtr principal; + nsresult rv = securityManager-> + GetDocShellCodebasePrincipal(mDocumentURI, docShell, + getter_AddRefs(principal)); + if (NS_SUCCEEDED(rv)) { + SetPrincipal(principal); + } + } + } + + // Refresh the principal on the compartment. + nsPIDOMWindow* win = GetInnerWindow(); + if (win) { + win->RefreshCompartmentPrincipal(); + } +} + +void +nsDocument::RemoveDocStyleSheetsFromStyleSets() +{ + // The stylesheets should forget us + int32_t indx = mStyleSheets.Count(); + while (--indx >= 0) { + nsIStyleSheet* sheet = mStyleSheets[indx]; + sheet->SetOwningDocument(nullptr); + + if (sheet->IsApplicable()) { + nsCOMPtr shell = GetShell(); + if (shell) { + shell->StyleSet()->RemoveDocStyleSheet(sheet); + } + } + // XXX Tell observers? + } +} + +void +nsDocument::RemoveStyleSheetsFromStyleSets(nsCOMArray& aSheets, nsStyleSet::sheetType aType) +{ + // The stylesheets should forget us + int32_t indx = aSheets.Count(); + while (--indx >= 0) { + nsIStyleSheet* sheet = aSheets[indx]; + sheet->SetOwningDocument(nullptr); + + if (sheet->IsApplicable()) { + nsCOMPtr shell = GetShell(); + if (shell) { + shell->StyleSet()->RemoveStyleSheet(aType, sheet); + } + } + + // XXX Tell observers? + } + +} + +void +nsDocument::ResetStylesheetsToURI(nsIURI* aURI) +{ + MOZ_ASSERT(aURI); + + mozAutoDocUpdate upd(this, UPDATE_STYLE, true); + RemoveDocStyleSheetsFromStyleSets(); + RemoveStyleSheetsFromStyleSets(mCatalogSheets, nsStyleSet::eAgentSheet); + RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAgentSheet], nsStyleSet::eAgentSheet); + RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eUserSheet], nsStyleSet::eUserSheet); + RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAuthorSheet], nsStyleSet::eDocSheet); + + // Release all the sheets + mStyleSheets.Clear(); + for (uint32_t i = 0; i < SheetTypeCount; ++i) + mAdditionalSheets[i].Clear(); + + // NOTE: We don't release the catalog sheets. It doesn't really matter + // now, but it could in the future -- in which case not releasing them + // is probably the right thing to do. + + // Now reset our inline style and attribute sheets. + if (mAttrStyleSheet) { + mAttrStyleSheet->Reset(); + mAttrStyleSheet->SetOwningDocument(this); + } else { + mAttrStyleSheet = new nsHTMLStyleSheet(this); + } + + if (!mStyleAttrStyleSheet) { + mStyleAttrStyleSheet = new nsHTMLCSSStyleSheet(); + } + + // Now set up our style sets + nsCOMPtr shell = GetShell(); + if (shell) { + FillStyleSet(shell->StyleSet()); + } +} + +static bool +AppendAuthorSheet(nsIStyleSheet *aSheet, void *aData) +{ + nsStyleSet *styleSet = static_cast(aData); + styleSet->AppendStyleSheet(nsStyleSet::eDocSheet, aSheet); + return true; +} + +static void +AppendSheetsToStyleSet(nsStyleSet* aStyleSet, + const nsCOMArray& aSheets, + nsStyleSet::sheetType aType) +{ + for (int32_t i = aSheets.Count() - 1; i >= 0; --i) { + aStyleSet->AppendStyleSheet(aType, aSheets[i]); + } +} + + +void +nsDocument::FillStyleSet(nsStyleSet* aStyleSet) +{ + NS_PRECONDITION(aStyleSet, "Must have a style set"); + NS_PRECONDITION(aStyleSet->SheetCount(nsStyleSet::eDocSheet) == 0, + "Style set already has document sheets?"); + + // We could consider moving this to nsStyleSet::Init, to match its + // handling of the eAnimationSheet and eTransitionSheet levels. + aStyleSet->DirtyRuleProcessors(nsStyleSet::ePresHintSheet); + aStyleSet->DirtyRuleProcessors(nsStyleSet::eStyleAttrSheet); + + int32_t i; + for (i = mStyleSheets.Count() - 1; i >= 0; --i) { + nsIStyleSheet* sheet = mStyleSheets[i]; + if (sheet->IsApplicable()) { + aStyleSet->AddDocStyleSheet(sheet, this); + } + } + + nsStyleSheetService *sheetService = nsStyleSheetService::GetInstance(); + if (sheetService) { + sheetService->AuthorStyleSheets()->EnumerateForwards(AppendAuthorSheet, + aStyleSet); + } + + for (i = mCatalogSheets.Count() - 1; i >= 0; --i) { + nsIStyleSheet* sheet = mCatalogSheets[i]; + if (sheet->IsApplicable()) { + aStyleSet->AppendStyleSheet(nsStyleSet::eAgentSheet, sheet); + } + } + + AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAgentSheet], + nsStyleSet::eAgentSheet); + AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eUserSheet], + nsStyleSet::eUserSheet); + AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAuthorSheet], + nsStyleSet::eDocSheet); +} + +nsresult +nsDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel, + nsILoadGroup* aLoadGroup, + nsISupports* aContainer, + nsIStreamListener **aDocListener, + bool aReset, nsIContentSink* aSink) +{ +#ifdef PR_LOGGING + if (gDocumentLeakPRLog && PR_LOG_TEST(gDocumentLeakPRLog, PR_LOG_DEBUG)) { + nsCOMPtr uri; + aChannel->GetURI(getter_AddRefs(uri)); + nsAutoCString spec; + if (uri) + uri->GetSpec(spec); + PR_LogPrint("DOCUMENT %p StartDocumentLoad %s", this, spec.get()); + } +#endif + +#ifdef DEBUG + { + uint32_t appId; + nsresult rv = NodePrincipal()->GetAppId(&appId); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_ASSERT(appId != nsIScriptSecurityManager::UNKNOWN_APP_ID, + "Document should never have UNKNOWN_APP_ID"); + } +#endif + + MOZ_ASSERT(GetReadyStateEnum() == nsIDocument::READYSTATE_UNINITIALIZED, + "Bad readyState"); + SetReadyStateInternal(READYSTATE_LOADING); + + if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) { + mLoadedAsData = true; + // We need to disable script & style loading in this case. + // We leave them disabled even in EndLoad(), and let anyone + // who puts the document on display to worry about enabling. + + // Do not load/process scripts when loading as data + ScriptLoader()->SetEnabled(false); + + // styles + CSSLoader()->SetEnabled(false); // Do not load/process styles when loading as data + } else if (nsCRT::strcmp("external-resource", aCommand) == 0) { + // Allow CSS, but not scripts + ScriptLoader()->SetEnabled(false); + } + + mMayStartLayout = false; + + mHaveInputEncoding = true; + + if (aReset) { + Reset(aChannel, aLoadGroup); + } + + nsAutoCString contentType; + nsCOMPtr bag = do_QueryInterface(aChannel); + if ((bag && NS_SUCCEEDED(bag->GetPropertyAsACString( + NS_LITERAL_STRING("contentType"), contentType))) || + NS_SUCCEEDED(aChannel->GetContentType(contentType))) { + // XXX this is only necessary for viewsource: + nsACString::const_iterator start, end, semicolon; + contentType.BeginReading(start); + contentType.EndReading(end); + semicolon = start; + FindCharInReadable(';', semicolon, end); + SetContentTypeInternal(Substring(start, semicolon)); + } + + RetrieveRelevantHeaders(aChannel); + + mChannel = aChannel; + nsCOMPtr inStrmChan = do_QueryInterface(mChannel); + if (inStrmChan) { + bool isSrcdocChannel; + inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel); + if (isSrcdocChannel) { + mIsSrcdocDocument = true; + } + } + + // If this document is being loaded by a docshell, copy its sandbox flags + // to the document. These are immutable after being set here. + nsCOMPtr docShell = do_QueryInterface(aContainer); + + if (docShell) { + nsresult rv = docShell->GetSandboxFlags(&mSandboxFlags); + NS_ENSURE_SUCCESS(rv, rv); + } + + // If this is not a data document, set CSP. + if (!mLoadedAsData) { + nsresult rv = InitCSP(aChannel); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +void +CSPErrorQueue::Add(const char* aMessageName) +{ + mErrors.AppendElement(aMessageName); +} + +void +CSPErrorQueue::Flush(nsIDocument* aDocument) +{ + for (uint32_t i = 0; i < mErrors.Length(); i++) { + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("CSP"), aDocument, + nsContentUtils::eSECURITY_PROPERTIES, + mErrors[i]); + } + mErrors.Clear(); +} + +void +nsDocument::SendToConsole(nsCOMArray& aMessages) +{ + for (uint32_t i = 0; i < aMessages.Length(); ++i) { + nsAutoString messageTag; + aMessages[i]->GetTag(messageTag); + + nsAutoString category; + aMessages[i]->GetCategory(category); + + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_ConvertUTF16toUTF8(category), + this, nsContentUtils::eSECURITY_PROPERTIES, + NS_ConvertUTF16toUTF8(messageTag).get()); + } +} + +static nsresult +AppendCSPFromHeader(nsIContentSecurityPolicy* csp, const nsAString& aHeaderValue, + nsIURI* aSelfURI, bool aReportOnly, bool aSpecCompliant) +{ + // Need to tokenize the header value since multiple headers could be + // concatenated into one comma-separated list of policies. + // See RFC2616 section 4.2 (last paragraph) + nsresult rv = NS_OK; + nsCharSeparatedTokenizer tokenizer(aHeaderValue, ','); + while (tokenizer.hasMoreTokens()) { + const nsSubstring& policy = tokenizer.nextToken(); + rv = csp->AppendPolicy(policy, aSelfURI, aReportOnly, aSpecCompliant); + NS_ENSURE_SUCCESS(rv, rv); +#ifdef PR_LOGGING + { + PR_LOG(gCspPRLog, PR_LOG_DEBUG, + ("CSP refined with policy: \"%s\"", + NS_ConvertUTF16toUTF8(policy).get())); + } +#endif + } + return NS_OK; +} + +nsresult +nsDocument::InitCSP(nsIChannel* aChannel) +{ + nsCOMPtr csp; + if (!CSPService::sCSPEnabled) { +#ifdef PR_LOGGING + PR_LOG(gCspPRLog, PR_LOG_DEBUG, + ("CSP is disabled, skipping CSP init for document %p", this)); +#endif + return NS_OK; + } + + nsAutoCString tCspHeaderValue, tCspROHeaderValue; + nsAutoCString tCspOldHeaderValue, tCspOldROHeaderValue; + + nsCOMPtr httpChannel = do_QueryInterface(aChannel); + if (httpChannel) { + httpChannel->GetResponseHeader( + NS_LITERAL_CSTRING("x-content-security-policy"), + tCspOldHeaderValue); + + httpChannel->GetResponseHeader( + NS_LITERAL_CSTRING("x-content-security-policy-report-only"), + tCspOldROHeaderValue); + + httpChannel->GetResponseHeader( + NS_LITERAL_CSTRING("content-security-policy"), + tCspHeaderValue); + + httpChannel->GetResponseHeader( + NS_LITERAL_CSTRING("content-security-policy-report-only"), + tCspROHeaderValue); + } + NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue); + NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue); + NS_ConvertASCIItoUTF16 cspOldHeaderValue(tCspOldHeaderValue); + NS_ConvertASCIItoUTF16 cspOldROHeaderValue(tCspOldROHeaderValue); + + // Only use the CSP 1.0 spec compliant headers if a pref to do so + // is set. This lets us turn on the 1.0 parser per platform. This + // pref is also set by the tests for 1.0 spec compliant CSP. + bool specCompliantEnabled = + Preferences::GetBool("security.csp.speccompliant"); + + // If spec compliant pref isn't set, pretend we never got these headers. + if ((!cspHeaderValue.IsEmpty() || !cspROHeaderValue.IsEmpty()) && + !specCompliantEnabled) { + PR_LOG(gCspPRLog, PR_LOG_DEBUG, + ("Got spec compliant CSP headers but pref was not set")); + cspHeaderValue.Truncate(); + cspROHeaderValue.Truncate(); + } + + // If both the new header AND the old header are present, warn that + // the old header will be ignored. Otherwise, if the old header is + // present, warn that it will be deprecated. + bool oldHeaderIsPresent = !cspOldHeaderValue.IsEmpty() || !cspOldROHeaderValue.IsEmpty(); + bool newHeaderIsPresent = !cspHeaderValue.IsEmpty() || !cspROHeaderValue.IsEmpty(); + + if (oldHeaderIsPresent && newHeaderIsPresent) { + mCSPWebConsoleErrorQueue.Add("BothCSPHeadersPresent"); + } else if (oldHeaderIsPresent) { + mCSPWebConsoleErrorQueue.Add("OldCSPHeaderDeprecated"); + } + + // Figure out if we need to apply an app default CSP or a CSP from an app manifest + nsIPrincipal* principal = NodePrincipal(); + + uint16_t appStatus = principal->GetAppStatus(); + bool applyAppDefaultCSP = appStatus == nsIPrincipal::APP_STATUS_PRIVILEGED || + appStatus == nsIPrincipal::APP_STATUS_CERTIFIED; + bool applyAppManifestCSP = false; + + nsAutoString appManifestCSP; + if (appStatus != nsIPrincipal::APP_STATUS_NOT_INSTALLED) { + nsCOMPtr appsService = do_GetService(APPS_SERVICE_CONTRACTID); + if (appsService) { + uint32_t appId = 0; + if (NS_SUCCEEDED(principal->GetAppId(&appId))) { + appsService->GetCSPByLocalId(appId, appManifestCSP); + if (!appManifestCSP.IsEmpty()) { + applyAppManifestCSP = true; + } + } + } + } + + // If there's no CSP to apply, go ahead and return early + if (!applyAppDefaultCSP && + !applyAppManifestCSP && + cspHeaderValue.IsEmpty() && + cspROHeaderValue.IsEmpty() && + cspOldHeaderValue.IsEmpty() && + cspOldROHeaderValue.IsEmpty()) { +#ifdef PR_LOGGING + nsCOMPtr chanURI; + aChannel->GetURI(getter_AddRefs(chanURI)); + nsAutoCString aspec; + chanURI->GetAsciiSpec(aspec); + PR_LOG(gCspPRLog, PR_LOG_DEBUG, + ("no CSP for document, %s, %s", + aspec.get(), + applyAppDefaultCSP ? "is app" : "not an app")); +#endif + return NS_OK; + } + +#ifdef PR_LOGGING + PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("Document is an app or CSP header specified %p", this)); +#endif + + nsresult rv; + + // If Document is an app check to see if we already set CSP and return early + // if that is indeed the case. + // + // In general (see bug 947831), we should not be setting CSP on a principal + // that aliases another document. For non-app code this is not a problem + // since we only share the underlying principal with nested browsing + // contexts for which a header cannot be set (e.g., about:blank and + // about:srcodoc iframes) and thus won't try to set the CSP again. This + // check ensures that we do not try to set CSP for an app. + if (applyAppDefaultCSP || applyAppManifestCSP) { + nsCOMPtr csp; + rv = principal->GetCsp(getter_AddRefs(csp)); + NS_ENSURE_SUCCESS(rv, rv); + + if (csp) { +#ifdef PR_LOGGING + PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("%s %s %s", + "This document is sharing principal with another document.", + "Since the document is an app, CSP was already set.", + "Skipping attempt to set CSP.")); +#endif + return NS_OK; + } + } + + // create new CSP object + csp = do_CreateInstance("@mozilla.org/contentsecuritypolicy;1", &rv); + + if (NS_FAILED(rv)) { +#ifdef PR_LOGGING + PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("Failed to create CSP object: %x", rv)); +#endif + return rv; + } + + // used as a "self" identifier for the CSP. + nsCOMPtr selfURI; + aChannel->GetURI(getter_AddRefs(selfURI)); + + // Store the request context for violation reports + csp->SetRequestContext(nullptr, nullptr, nullptr, aChannel); + + // ----- if the doc is an app and we want a default CSP, apply it. + if (applyAppDefaultCSP) { + nsAdoptingString appCSP; + if (appStatus == nsIPrincipal::APP_STATUS_PRIVILEGED) { + appCSP = Preferences::GetString("security.apps.privileged.CSP.default"); + NS_ASSERTION(appCSP, "App, but no default CSP in security.apps.privileged.CSP.default"); + } else if (appStatus == nsIPrincipal::APP_STATUS_CERTIFIED) { + appCSP = Preferences::GetString("security.apps.certified.CSP.default"); + NS_ASSERTION(appCSP, "App, but no default CSP in security.apps.certified.CSP.default"); + } + + if (appCSP) { + // Use the 1.0 CSP parser for apps if the pref to do so is set. + csp->AppendPolicy(appCSP, selfURI, false, specCompliantEnabled); + } + } + + // ----- if the doc is an app and specifies a CSP in its manifest, apply it. + if (applyAppManifestCSP) { + // Use the 1.0 CSP parser for apps if the pref to do so is set. + csp->AppendPolicy(appManifestCSP, selfURI, false, specCompliantEnabled); + } + + // While we are supporting both CSP 1.0 and the x- headers, the 1.0 headers + // take priority. If both are present, the x-* headers are ignored. + + // ----- if there's a full-strength CSP header, apply it. + if (!cspHeaderValue.IsEmpty()) { + rv = AppendCSPFromHeader(csp, cspHeaderValue, selfURI, false, true); + NS_ENSURE_SUCCESS(rv, rv); + } else if (!cspOldHeaderValue.IsEmpty()) { + rv = AppendCSPFromHeader(csp, cspOldHeaderValue, selfURI, false, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // ----- if there's a report-only CSP header, apply it. + if (!cspROHeaderValue.IsEmpty()) { + rv = AppendCSPFromHeader(csp, cspROHeaderValue, selfURI, true, true); + NS_ENSURE_SUCCESS(rv, rv); + } else if (!cspOldROHeaderValue.IsEmpty()) { + rv = AppendCSPFromHeader(csp, cspOldROHeaderValue, selfURI, true, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // ----- Enforce frame-ancestor policy on any applied policies + nsCOMPtr docShell(mDocumentContainer); + if (docShell) { + bool safeAncestry = false; + + // PermitsAncestry sends violation reports when necessary + rv = csp->PermitsAncestry(docShell, &safeAncestry); + NS_ENSURE_SUCCESS(rv, rv); + + if (!safeAncestry) { +#ifdef PR_LOGGING + PR_LOG(gCspPRLog, PR_LOG_DEBUG, + ("CSP doesn't like frame's ancestry, not loading.")); +#endif + // stop! ERROR page! + aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION); + } + } + + rv = principal->SetCsp(csp); + NS_ENSURE_SUCCESS(rv, rv); +#ifdef PR_LOGGING + PR_LOG(gCspPRLog, PR_LOG_DEBUG, + ("Inserted CSP into principal %p", principal)); +#endif + + return NS_OK; +} + +void +nsDocument::StopDocumentLoad() +{ + if (mParser) { + mParserAborted = true; + mParser->Terminate(); + } +} + +void +nsDocument::SetDocumentURI(nsIURI* aURI) +{ + nsCOMPtr oldBase = GetDocBaseURI(); + mDocumentURI = NS_TryToMakeImmutable(aURI); + nsIURI* newBase = GetDocBaseURI(); + + bool equalBases = false; + // Changing just the ref of a URI does not change how relative URIs would + // resolve wrt to it, so we can treat the bases as equal as long as they're + // equal ignoring the ref. + if (oldBase && newBase) { + oldBase->EqualsExceptRef(newBase, &equalBases); + } + else { + equalBases = !oldBase && !newBase; + } + + // If this is the first time we're setting the document's URI, set the + // document's original URI. + if (!mOriginalURI) + mOriginalURI = mDocumentURI; + + // If changing the document's URI changed the base URI of the document, we + // need to refresh the hrefs of all the links on the page. + if (!equalBases) { + RefreshLinkHrefs(); + } +} + +void +nsDocument::SetChromeXHRDocURI(nsIURI* aURI) +{ + mChromeXHRDocURI = aURI; +} + +void +nsDocument::SetChromeXHRDocBaseURI(nsIURI* aURI) +{ + mChromeXHRDocBaseURI = aURI; +} + +NS_IMETHODIMP +nsDocument::GetLastModified(nsAString& aLastModified) +{ + nsIDocument::GetLastModified(aLastModified); + return NS_OK; +} + +void +nsIDocument::GetLastModified(nsAString& aLastModified) const +{ + if (!mLastModified.IsEmpty()) { + aLastModified.Assign(mLastModified); + } else { + // If we for whatever reason failed to find the last modified time + // (or even the current time), fall back to what NS4.x returned. + aLastModified.Assign(NS_LITERAL_STRING("01/01/1970 00:00:00")); + } +} + +void +nsDocument::AddToNameTable(Element *aElement, nsIAtom* aName) +{ + MOZ_ASSERT(nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement), + "Only put elements that need to be exposed as document['name'] in " + "the named table."); + + nsIdentifierMapEntry *entry = + mIdentifierMap.PutEntry(nsDependentAtomString(aName)); + + // Null for out-of-memory + if (entry) { + if (!entry->HasNameElement() && + !entry->HasIdElementExposedAsHTMLDocumentProperty()) { + ++mExpandoAndGeneration.generation; + } + entry->AddNameElement(this, aElement); + } +} + +void +nsDocument::RemoveFromNameTable(Element *aElement, nsIAtom* aName) +{ + // Speed up document teardown + if (mIdentifierMap.Count() == 0) + return; + + nsIdentifierMapEntry *entry = + mIdentifierMap.GetEntry(nsDependentAtomString(aName)); + if (!entry) // Could be false if the element was anonymous, hence never added + return; + + entry->RemoveNameElement(aElement); + if (!entry->HasNameElement() && + !entry->HasIdElementExposedAsHTMLDocumentProperty()) { + ++mExpandoAndGeneration.generation; + } +} + +void +nsDocument::AddToIdTable(Element *aElement, nsIAtom* aId) +{ + nsIdentifierMapEntry *entry = + mIdentifierMap.PutEntry(nsDependentAtomString(aId)); + + if (entry) { /* True except on OOM */ + if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) && + !entry->HasNameElement() && + !entry->HasIdElementExposedAsHTMLDocumentProperty()) { + ++mExpandoAndGeneration.generation; + } + entry->AddIdElement(aElement); + } +} + +void +nsDocument::RemoveFromIdTable(Element *aElement, nsIAtom* aId) +{ + NS_ASSERTION(aId, "huhwhatnow?"); + + // Speed up document teardown + if (mIdentifierMap.Count() == 0) { + return; + } + + nsIdentifierMapEntry *entry = + mIdentifierMap.GetEntry(nsDependentAtomString(aId)); + if (!entry) // Can be null for XML elements with changing ids. + return; + + entry->RemoveIdElement(aElement); + if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) && + !entry->HasNameElement() && + !entry->HasIdElementExposedAsHTMLDocumentProperty()) { + ++mExpandoAndGeneration.generation; + } + if (entry->IsEmpty()) { + mIdentifierMap.RawRemoveEntry(entry); + } +} + +nsIPrincipal* +nsDocument::GetPrincipal() +{ + return NodePrincipal(); +} + +extern bool sDisablePrefetchHTTPSPref; + +void +nsDocument::SetPrincipal(nsIPrincipal *aNewPrincipal) +{ + if (aNewPrincipal && mAllowDNSPrefetch && sDisablePrefetchHTTPSPref) { + nsCOMPtr uri; + aNewPrincipal->GetURI(getter_AddRefs(uri)); + bool isHTTPS; + if (!uri || NS_FAILED(uri->SchemeIs("https", &isHTTPS)) || + isHTTPS) { + mAllowDNSPrefetch = false; + } + } + mNodeInfoManager->SetDocumentPrincipal(aNewPrincipal); +} + +NS_IMETHODIMP +nsDocument::GetApplicationCache(nsIApplicationCache **aApplicationCache) +{ + NS_IF_ADDREF(*aApplicationCache = mApplicationCache); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocument::SetApplicationCache(nsIApplicationCache *aApplicationCache) +{ + mApplicationCache = aApplicationCache; + + return NS_OK; +} + +NS_IMETHODIMP +nsDocument::GetContentType(nsAString& aContentType) +{ + CopyUTF8toUTF16(GetContentTypeInternal(), aContentType); + + return NS_OK; +} + +void +nsDocument::SetContentType(const nsAString& aContentType) +{ + NS_ASSERTION(GetContentTypeInternal().IsEmpty() || + GetContentTypeInternal().Equals(NS_ConvertUTF16toUTF8(aContentType)), + "Do you really want to change the content-type?"); + + SetContentTypeInternal(NS_ConvertUTF16toUTF8(aContentType)); +} + +nsresult +nsDocument::GetAllowPlugins(bool * aAllowPlugins) +{ + // First, we ask our docshell if it allows plugins. + nsCOMPtr docShell(mDocumentContainer); + + if (docShell) { + docShell->GetAllowPlugins(aAllowPlugins); + + // If the docshell allows plugins, we check whether + // we are sandboxed and plugins should not be allowed. + if (*aAllowPlugins) + *aAllowPlugins = !(mSandboxFlags & SANDBOXED_PLUGINS); + } + + return NS_OK; +} + +already_AddRefed +nsDocument::GetUndoManager() +{ + Element* rootElement = GetRootElement(); + if (!rootElement) { + return nullptr; + } + + if (!mUndoManager) { + mUndoManager = new UndoManager(rootElement); + } + + nsRefPtr undoManager = mUndoManager; + return undoManager.forget(); +} + +/* Return true if the document is in the focused top-level window, and is an + * ancestor of the focused DOMWindow. */ +NS_IMETHODIMP +nsDocument::HasFocus(bool* aResult) +{ + ErrorResult rv; + *aResult = nsIDocument::HasFocus(rv); + return rv.ErrorCode(); +} + +bool +nsIDocument::HasFocus(ErrorResult& rv) const +{ + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm) { + rv.Throw(NS_ERROR_NOT_AVAILABLE); + return false; + } + + // Is there a focused DOMWindow? + nsCOMPtr focusedWindow; + fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); + if (!focusedWindow) { + return false; + } + + // Are we an ancestor of the focused DOMWindow? + nsCOMPtr domDocument; + focusedWindow->GetDocument(getter_AddRefs(domDocument)); + nsCOMPtr document = do_QueryInterface(domDocument); + + for (nsIDocument* currentDoc = document; currentDoc; + currentDoc = currentDoc->GetParentDocument()) { + if (currentDoc == this) { + // Yes, we are an ancestor + return true; + } + } + + return false; +} + +NS_IMETHODIMP +nsDocument::GetReferrer(nsAString& aReferrer) +{ + nsIDocument::GetReferrer(aReferrer); + return NS_OK; +} + +void +nsIDocument::GetReferrer(nsAString& aReferrer) const +{ + if (mIsSrcdocDocument && mParentDocument) + mParentDocument->GetReferrer(aReferrer); + else + CopyUTF8toUTF16(mReferrer, aReferrer); +} + +nsresult +nsIDocument::GetSrcdocData(nsAString &aSrcdocData) +{ + if (mIsSrcdocDocument) { + nsCOMPtr inStrmChan = do_QueryInterface(mChannel); + if (inStrmChan) { + return inStrmChan->GetSrcdocData(aSrcdocData); + } + } + aSrcdocData = NullString(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocument::GetActiveElement(nsIDOMElement **aElement) +{ + nsCOMPtr el(do_QueryInterface(nsIDocument::GetActiveElement())); + el.forget(aElement); + return NS_OK; +} + +Element* +nsIDocument::GetActiveElement() +{ + // Get the focused element. + nsCOMPtr window = GetWindow(); + if (window) { + nsCOMPtr focusedWindow; + nsIContent* focusedContent = + nsFocusManager::GetFocusedDescendant(window, false, + getter_AddRefs(focusedWindow)); + // be safe and make sure the element is from this document + if (focusedContent && focusedContent->OwnerDoc() == this) { + if (focusedContent->ChromeOnlyAccess()) { + focusedContent = focusedContent->FindFirstNonChromeOnlyAccessContent(); + } + if (focusedContent) { + return focusedContent->AsElement(); + } + } + } + + // No focused element anywhere in this document. Try to get the BODY. + nsRefPtr htmlDoc = AsHTMLDocument(); + if (htmlDoc) { + // Because of IE compatibility, return null when html document doesn't have + // a body. + return htmlDoc->GetBody(); + } + + // If we couldn't get a BODY, return the root element. + return GetDocumentElement(); +} + +NS_IMETHODIMP +nsDocument::GetCurrentScript(nsIDOMElement **aElement) +{ + nsCOMPtr el(do_QueryInterface(nsIDocument::GetCurrentScript())); + el.forget(aElement); + return NS_OK; +} + +Element* +nsIDocument::GetCurrentScript() +{ + nsCOMPtr el(do_QueryInterface(ScriptLoader()->GetCurrentScript())); + return el; +} + +NS_IMETHODIMP +nsDocument::ElementFromPoint(float aX, float aY, nsIDOMElement** aReturn) +{ + Element* el = nsIDocument::ElementFromPoint(aX, aY); + nsCOMPtr retval = do_QueryInterface(el); + retval.forget(aReturn); + return NS_OK; +} + +Element* +nsIDocument::ElementFromPoint(float aX, float aY) +{ + return ElementFromPointHelper(aX, aY, false, true); +} + +Element* +nsDocument::ElementFromPointHelper(float aX, float aY, + bool aIgnoreRootScrollFrame, + bool aFlushLayout) +{ + // As per the the spec, we return null if either coord is negative + if (!aIgnoreRootScrollFrame && (aX < 0 || aY < 0)) { + return nullptr; + } + + nscoord x = nsPresContext::CSSPixelsToAppUnits(aX); + nscoord y = nsPresContext::CSSPixelsToAppUnits(aY); + nsPoint pt(x, y); + + // Make sure the layout information we get is up-to-date, and + // ensure we get a root frame (for everything but XUL) + if (aFlushLayout) + FlushPendingNotifications(Flush_Layout); + + nsIPresShell *ps = GetShell(); + if (!ps) { + return nullptr; + } + nsIFrame *rootFrame = ps->GetRootFrame(); + + // XUL docs, unlike HTML, have no frame tree until everything's done loading + if (!rootFrame) { + return nullptr; // return null to premature XUL callers as a reminder to wait + } + + nsIFrame *ptFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, pt, + nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC | + (aIgnoreRootScrollFrame ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0)); + if (!ptFrame) { + return nullptr; + } + + nsIContent* elem = GetContentInThisDocument(ptFrame); + if (elem && !elem->IsElement()) { + elem = elem->GetParent(); + } + return elem ? elem->AsElement() : nullptr; +} + +nsresult +nsDocument::NodesFromRectHelper(float aX, float aY, + float aTopSize, float aRightSize, + float aBottomSize, float aLeftSize, + bool aIgnoreRootScrollFrame, + bool aFlushLayout, + nsIDOMNodeList** aReturn) +{ + NS_ENSURE_ARG_POINTER(aReturn); + + nsSimpleContentList* elements = new nsSimpleContentList(this); + NS_ADDREF(elements); + *aReturn = elements; + + // Following the same behavior of elementFromPoint, + // we don't return anything if either coord is negative + if (!aIgnoreRootScrollFrame && (aX < 0 || aY < 0)) + return NS_OK; + + nscoord x = nsPresContext::CSSPixelsToAppUnits(aX - aLeftSize); + nscoord y = nsPresContext::CSSPixelsToAppUnits(aY - aTopSize); + nscoord w = nsPresContext::CSSPixelsToAppUnits(aLeftSize + aRightSize) + 1; + nscoord h = nsPresContext::CSSPixelsToAppUnits(aTopSize + aBottomSize) + 1; + + nsRect rect(x, y, w, h); + + // Make sure the layout information we get is up-to-date, and + // ensure we get a root frame (for everything but XUL) + if (aFlushLayout) { + FlushPendingNotifications(Flush_Layout); + } + + nsIPresShell *ps = GetShell(); + NS_ENSURE_STATE(ps); + nsIFrame *rootFrame = ps->GetRootFrame(); + + // XUL docs, unlike HTML, have no frame tree until everything's done loading + if (!rootFrame) + return NS_OK; // return nothing to premature XUL callers as a reminder to wait + + nsAutoTArray outFrames; + nsLayoutUtils::GetFramesForArea(rootFrame, rect, outFrames, + nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC | + (aIgnoreRootScrollFrame ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0)); + + // Used to filter out repeated elements in sequence. + nsIContent* lastAdded = nullptr; + + for (uint32_t i = 0; i < outFrames.Length(); i++) { + nsIContent* node = GetContentInThisDocument(outFrames[i]); + + if (node && !node->IsElement() && !node->IsNodeOfType(nsINode::eTEXT)) { + // We have a node that isn't an element or a text node, + // use its parent content instead. + node = node->GetParent(); + } + if (node && node != lastAdded) { + elements->AppendElement(node); + lastAdded = node; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocument::GetElementsByClassName(const nsAString& aClasses, + nsIDOMNodeList** aReturn) +{ + *aReturn = nsIDocument::GetElementsByClassName(aClasses).take(); + return NS_OK; +} + +already_AddRefed +nsIDocument::GetElementsByClassName(const nsAString& aClasses) +{ + return nsContentUtils::GetElementsByClassName(this, aClasses); +} + +NS_IMETHODIMP +nsDocument::ReleaseCapture() +{ + nsIDocument::ReleaseCapture(); + return NS_OK; +} + +void +nsIDocument::ReleaseCapture() const +{ + // only release the capture if the caller can access it. This prevents a + // page from stopping a scrollbar grab for example. + nsCOMPtr node = nsIPresShell::GetCapturingContent(); + if (node && nsContentUtils::CanCallerAccess(node)) { + nsIPresShell::SetCapturingContent(nullptr, 0); + } +} + +already_AddRefed +nsIDocument::GetBaseURI(bool aTryUseXHRDocBaseURI) const +{ + nsCOMPtr uri; + if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) { + uri = mChromeXHRDocBaseURI; + } else { + uri = GetDocBaseURI(); + } + + return uri.forget(); +} + +nsresult +nsDocument::SetBaseURI(nsIURI* aURI) +{ + if (!aURI && !mDocumentBaseURI) { + return NS_OK; + } + + // Don't do anything if the URI wasn't actually changed. + if (aURI && mDocumentBaseURI) { + bool equalBases = false; + mDocumentBaseURI->Equals(aURI, &equalBases); + if (equalBases) { + return NS_OK; + } + } + + if (aURI) { + mDocumentBaseURI = NS_TryToMakeImmutable(aURI); + } else { + mDocumentBaseURI = nullptr; + } + RefreshLinkHrefs(); + + return NS_OK; +} + +void +nsDocument::GetBaseTarget(nsAString &aBaseTarget) +{ + aBaseTarget = mBaseTarget; +} + +void +nsDocument::SetDocumentCharacterSet(const nsACString& aCharSetID) +{ + // XXX it would be a good idea to assert the sanity of the argument, + // but before we figure out what to do about non-Encoding Standard + // encodings in the charset menu and in mailnews, assertions are futile. + if (!mCharacterSet.Equals(aCharSetID)) { + mCharacterSet = aCharSetID; + + int32_t n = mCharSetObservers.Length(); + + for (int32_t i = 0; i < n; i++) { + nsIObserver* observer = mCharSetObservers.ElementAt(i); + + observer->Observe(static_cast(this), "charset", + NS_ConvertASCIItoUTF16(aCharSetID).get()); + } + } +} + +nsresult +nsDocument::AddCharSetObserver(nsIObserver* aObserver) +{ + NS_ENSURE_ARG_POINTER(aObserver); + + NS_ENSURE_TRUE(mCharSetObservers.AppendElement(aObserver), NS_ERROR_FAILURE); + + return NS_OK; +} + +void +nsDocument::RemoveCharSetObserver(nsIObserver* aObserver) +{ + mCharSetObservers.RemoveElement(aObserver); +} + +void +nsDocument::GetHeaderData(nsIAtom* aHeaderField, nsAString& aData) const +{ + aData.Truncate(); + const nsDocHeaderData* data = mHeaderData; + while (data) { + if (data->mField == aHeaderField) { + aData = data->mData; + + break; + } + data = data->mNext; + } +} + +void +nsDocument::SetHeaderData(nsIAtom* aHeaderField, const nsAString& aData) +{ + if (!aHeaderField) { + NS_ERROR("null headerField"); + return; + } + + if (!mHeaderData) { + if (!aData.IsEmpty()) { // don't bother storing empty string + mHeaderData = new nsDocHeaderData(aHeaderField, aData); + } + } + else { + nsDocHeaderData* data = mHeaderData; + nsDocHeaderData** lastPtr = &mHeaderData; + bool found = false; + do { // look for existing and replace + if (data->mField == aHeaderField) { + if (!aData.IsEmpty()) { + data->mData.Assign(aData); + } + else { // don't store empty string + *lastPtr = data->mNext; + data->mNext = nullptr; + delete data; + } + found = true; + + break; + } + lastPtr = &(data->mNext); + data = *lastPtr; + } while (data); + + if (!aData.IsEmpty() && !found) { + // didn't find, append + *lastPtr = new nsDocHeaderData(aHeaderField, aData); + } + } + + if (aHeaderField == nsGkAtoms::headerContentLanguage) { + CopyUTF16toUTF8(aData, mContentLanguage); + } + + if (aHeaderField == nsGkAtoms::headerDefaultStyle) { + // Only mess with our stylesheets if we don't have a lastStyleSheetSet, per + // spec. + if (DOMStringIsNull(mLastStyleSheetSet)) { + // Calling EnableStyleSheetsForSetInternal, not SetSelectedStyleSheetSet, + // per spec. The idea here is that we're changing our preferred set and + // that shouldn't change the value of lastStyleSheetSet. Also, we're + // using the Internal version so we can update the CSSLoader and not have + // to worry about null strings. + EnableStyleSheetsForSetInternal(aData, true); + } + } + + if (aHeaderField == nsGkAtoms::refresh) { + // We get into this code before we have a script global yet, so get to + // our container via mDocumentContainer. + nsCOMPtr refresher(mDocumentContainer); + if (refresher) { + // Note: using mDocumentURI instead of mBaseURI here, for consistency + // (used to just use the current URI of our webnavigation, but that + // should really be the same thing). Note that this code can run + // before the current URI of the webnavigation has been updated, so we + // can't assert equality here. + refresher->SetupRefreshURIFromHeader(mDocumentURI, NodePrincipal(), + NS_ConvertUTF16toUTF8(aData)); + } + } + + if (aHeaderField == nsGkAtoms::headerDNSPrefetchControl && + mAllowDNSPrefetch) { + // Chromium treats any value other than 'on' (case insensitive) as 'off'. + mAllowDNSPrefetch = aData.IsEmpty() || aData.LowerCaseEqualsLiteral("on"); + } + + if (aHeaderField == nsGkAtoms::viewport || + aHeaderField == nsGkAtoms::handheldFriendly || + aHeaderField == nsGkAtoms::viewport_minimum_scale || + aHeaderField == nsGkAtoms::viewport_maximum_scale || + aHeaderField == nsGkAtoms::viewport_initial_scale || + aHeaderField == nsGkAtoms::viewport_height || + aHeaderField == nsGkAtoms::viewport_width || + aHeaderField == nsGkAtoms::viewport_user_scalable) { + mViewportType = Unknown; + } +} + +void +nsDocument::TryChannelCharset(nsIChannel *aChannel, + int32_t& aCharsetSource, + nsACString& aCharset, + nsHtml5TreeOpExecutor* aExecutor) +{ + if (aChannel) { + nsAutoCString charsetVal; + nsresult rv = aChannel->GetContentCharset(charsetVal); + if (NS_SUCCEEDED(rv)) { + nsAutoCString preferred; + if(EncodingUtils::FindEncodingForLabel(charsetVal, preferred)) { + aCharset = preferred; + aCharsetSource = kCharsetFromChannel; + return; + } else if (aExecutor && !charsetVal.IsEmpty()) { + aExecutor->ComplainAboutBogusProtocolCharset(this); + } + } + } +} + +already_AddRefed +nsDocument::CreateShell(nsPresContext* aContext, nsViewManager* aViewManager, + nsStyleSet* aStyleSet) +{ + // Don't add anything here. Add it to |doCreateShell| instead. + // This exists so that subclasses can pass other values for the 4th + // parameter some of the time. + return doCreateShell(aContext, aViewManager, aStyleSet, + eCompatibility_FullStandards); +} + +already_AddRefed +nsDocument::doCreateShell(nsPresContext* aContext, + nsViewManager* aViewManager, nsStyleSet* aStyleSet, + nsCompatibility aCompatMode) +{ + NS_ASSERTION(!mPresShell, "We have a presshell already!"); + + NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr); + + FillStyleSet(aStyleSet); + + nsRefPtr shell = new PresShell; + shell->Init(this, aContext, aViewManager, aStyleSet, aCompatMode); + + // Note: we don't hold a ref to the shell (it holds a ref to us) + mPresShell = shell; + + // Make sure to never paint if we belong to an invisible DocShell. + nsCOMPtr docShell(mDocumentContainer); + if (docShell && docShell->IsInvisible()) + shell->SetNeverPainting(true); + + mExternalResourceMap.ShowViewers(); + + MaybeRescheduleAnimationFrameNotifications(); + + return shell.forget(); +} + +void +nsDocument::MaybeRescheduleAnimationFrameNotifications() +{ + if (!mPresShell || !IsEventHandlingEnabled()) { + // bail out for now, until one of those conditions changes + return; + } + + nsRefreshDriver* rd = mPresShell->GetPresContext()->RefreshDriver(); + if (!mFrameRequestCallbacks.IsEmpty()) { + rd->ScheduleFrameRequestCallbacks(this); + } +} + +void +nsIDocument::TakeFrameRequestCallbacks(FrameRequestCallbackList& aCallbacks) +{ + aCallbacks.AppendElements(mFrameRequestCallbacks); + mFrameRequestCallbacks.Clear(); +} + +PLDHashOperator RequestDiscardEnumerator(imgIRequest* aKey, + uint32_t aData, + void* userArg) +{ + aKey->RequestDiscard(); + return PL_DHASH_NEXT; +} + +void +nsDocument::DeleteShell() +{ + mExternalResourceMap.HideViewers(); + if (IsEventHandlingEnabled()) { + RevokeAnimationFrameNotifications(); + } + + // When our shell goes away, request that all our images be immediately + // discarded, so we don't carry around decoded image data for a document we + // no longer intend to paint. + mImageTracker.EnumerateRead(RequestDiscardEnumerator, nullptr); + + mPresShell = nullptr; +} + +void +nsDocument::RevokeAnimationFrameNotifications() +{ + if (!mFrameRequestCallbacks.IsEmpty()) { + mPresShell->GetPresContext()->RefreshDriver()-> + RevokeFrameRequestCallbacks(this); + } +} + +static void +SubDocClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry) +{ + SubDocMapEntry *e = static_cast(entry); + + NS_RELEASE(e->mKey); + if (e->mSubDocument) { + e->mSubDocument->SetParentDocument(nullptr); + NS_RELEASE(e->mSubDocument); + } +} + +static bool +SubDocInitEntry(PLDHashTable *table, PLDHashEntryHdr *entry, const void *key) +{ + SubDocMapEntry *e = + const_cast + (static_cast(entry)); + + e->mKey = const_cast(static_cast(key)); + NS_ADDREF(e->mKey); + + e->mSubDocument = nullptr; + return true; +} + +nsresult +nsDocument::SetSubDocumentFor(Element* aElement, nsIDocument* aSubDoc) +{ + NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED); + + if (!aSubDoc) { + // aSubDoc is nullptr, remove the mapping + + if (mSubDocuments) { + SubDocMapEntry *entry = + static_cast + (PL_DHashTableOperate(mSubDocuments, aElement, + PL_DHASH_LOOKUP)); + + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + PL_DHashTableRawRemove(mSubDocuments, entry); + } + } + } else { + if (!mSubDocuments) { + // Create a new hashtable + + static const PLDHashTableOps hash_table_ops = + { + PL_DHashAllocTable, + PL_DHashFreeTable, + PL_DHashVoidPtrKeyStub, + PL_DHashMatchEntryStub, + PL_DHashMoveEntryStub, + SubDocClearEntry, + PL_DHashFinalizeStub, + SubDocInitEntry + }; + + mSubDocuments = PL_NewDHashTable(&hash_table_ops, nullptr, + sizeof(SubDocMapEntry), 16); + if (!mSubDocuments) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + // Add a mapping to the hash table + SubDocMapEntry *entry = + static_cast + (PL_DHashTableOperate(mSubDocuments, aElement, + PL_DHASH_ADD)); + + if (!entry) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (entry->mSubDocument) { + entry->mSubDocument->SetParentDocument(nullptr); + + // Release the old sub document + NS_RELEASE(entry->mSubDocument); + } + + entry->mSubDocument = aSubDoc; + NS_ADDREF(entry->mSubDocument); + + aSubDoc->SetParentDocument(this); + } + + return NS_OK; +} + +nsIDocument* +nsDocument::GetSubDocumentFor(nsIContent *aContent) const +{ + if (mSubDocuments && aContent->IsElement()) { + SubDocMapEntry *entry = + static_cast + (PL_DHashTableOperate(mSubDocuments, aContent->AsElement(), + PL_DHASH_LOOKUP)); + + if (PL_DHASH_ENTRY_IS_BUSY(entry)) { + return entry->mSubDocument; + } + } + + return nullptr; +} + +static PLDHashOperator +FindContentEnumerator(PLDHashTable *table, PLDHashEntryHdr *hdr, + uint32_t number, void *arg) +{ + SubDocMapEntry *entry = static_cast(hdr); + FindContentData *data = static_cast(arg); + + if (entry->mSubDocument == data->mSubDocument) { + data->mResult = entry->mKey; + + return PL_DHASH_STOP; + } + + return PL_DHASH_NEXT; +} + +Element* +nsDocument::FindContentForSubDocument(nsIDocument *aDocument) const +{ + NS_ENSURE_TRUE(aDocument, nullptr); + + if (!mSubDocuments) { + return nullptr; + } + + FindContentData data(aDocument); + PL_DHashTableEnumerate(mSubDocuments, FindContentEnumerator, &data); + + return data.mResult; +} + +bool +nsDocument::IsNodeOfType(uint32_t aFlags) const +{ + return !(aFlags & ~eDOCUMENT); +} + +Element* +nsIDocument::GetRootElement() const +{ + return (mCachedRootElement && mCachedRootElement->GetParentNode() == this) ? + mCachedRootElement : GetRootElementInternal(); +} + +Element* +nsDocument::GetRootElementInternal() const +{ + // Loop backwards because any non-elements, such as doctypes and PIs + // are likely to appear before the root element. + uint32_t i; + for (i = mChildren.ChildCount(); i > 0; --i) { + nsIContent* child = mChildren.ChildAt(i - 1); + if (child->IsElement()) { + const_cast(this)->mCachedRootElement = child->AsElement(); + return child->AsElement(); + } + } + + const_cast(this)->mCachedRootElement = nullptr; + return nullptr; +} + +nsIContent * +nsDocument::GetChildAt(uint32_t aIndex) const +{ + return mChildren.GetSafeChildAt(aIndex); +} + +int32_t +nsDocument::IndexOf(const nsINode* aPossibleChild) const +{ + return mChildren.IndexOfChild(aPossibleChild); +} + +uint32_t +nsDocument::GetChildCount() const +{ + return mChildren.ChildCount(); +} + +nsIContent * const * +nsDocument::GetChildArray(uint32_t* aChildCount) const +{ + return mChildren.GetChildArray(aChildCount); +} + + +nsresult +nsDocument::InsertChildAt(nsIContent* aKid, uint32_t aIndex, + bool aNotify) +{ + if (aKid->IsElement() && GetRootElement()) { + NS_WARNING("Inserting root element when we already have one"); + return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR; + } + + return doInsertChildAt(aKid, aIndex, aNotify, mChildren); +} + +nsresult +nsDocument::AppendChildTo(nsIContent* aKid, bool aNotify) +{ + // Make sure to _not_ call the subclass InsertChildAt here. If + // subclasses wanted to hook into this stuff, they would have + // overridden AppendChildTo. + // XXXbz maybe this should just be a non-virtual method on nsINode? + // Feels that way to me... + return nsDocument::InsertChildAt(aKid, GetChildCount(), aNotify); +} + +void +nsDocument::RemoveChildAt(uint32_t aIndex, bool aNotify) +{ + nsCOMPtr oldKid = GetChildAt(aIndex); + if (!oldKid) { + return; + } + + if (oldKid->IsElement()) { + // Destroy the link map up front before we mess with the child list. + DestroyElementMaps(); + } + + doRemoveChildAt(aIndex, aNotify, oldKid, mChildren); + mCachedRootElement = nullptr; +} + +int32_t +nsDocument::GetNumberOfStyleSheets() const +{ + return mStyleSheets.Count(); +} + +nsIStyleSheet* +nsDocument::GetStyleSheetAt(int32_t aIndex) const +{ + NS_ENSURE_TRUE(0 <= aIndex && aIndex < mStyleSheets.Count(), nullptr); + return mStyleSheets[aIndex]; +} + +int32_t +nsDocument::GetIndexOfStyleSheet(nsIStyleSheet* aSheet) const +{ + return mStyleSheets.IndexOf(aSheet); +} + +void +nsDocument::AddStyleSheetToStyleSets(nsIStyleSheet* aSheet) +{ + nsCOMPtr shell = GetShell(); + if (shell) { + shell->StyleSet()->AddDocStyleSheet(aSheet, this); + } +} + +#define DO_STYLESHEET_NOTIFICATION(createFunc, concreteInterface, initMethod, type, ...) \ + do { \ + nsCOMPtr event; \ + nsresult rv = createFunc(getter_AddRefs(event), this, \ + mPresShell ? \ + mPresShell->GetPresContext() : nullptr, \ + nullptr); \ + if (NS_FAILED(rv)) { \ + return; \ + } \ + nsCOMPtr cssSheet(do_QueryInterface(aSheet)); \ + if (!cssSheet) { \ + return; \ + } \ + nsCOMPtr ssEvent(do_QueryInterface(event)); \ + MOZ_ASSERT(ssEvent); \ + ssEvent->initMethod(NS_LITERAL_STRING(type), true, true, \ + cssSheet, __VA_ARGS__); \ + event->SetTrusted(true); \ + event->SetTarget(this); \ + nsRefPtr asyncDispatcher = \ + new AsyncEventDispatcher(this, event); \ + asyncDispatcher->mDispatchChromeOnly = true; \ + asyncDispatcher->PostDOMEvent(); \ + } while (0); + +void +nsDocument::NotifyStyleSheetAdded(nsIStyleSheet* aSheet, bool aDocumentSheet) +{ + NS_DOCUMENT_NOTIFY_OBSERVERS(StyleSheetAdded, (this, aSheet, aDocumentSheet)); + + if (StyleSheetChangeEventsEnabled()) { + DO_STYLESHEET_NOTIFICATION(NS_NewDOMStyleSheetChangeEvent, + nsIDOMStyleSheetChangeEvent, + InitStyleSheetChangeEvent, + "StyleSheetAdded", + aDocumentSheet); + } +} + +void +nsDocument::NotifyStyleSheetRemoved(nsIStyleSheet* aSheet, bool aDocumentSheet) +{ + NS_DOCUMENT_NOTIFY_OBSERVERS(StyleSheetRemoved, (this, aSheet, aDocumentSheet)); + + if (StyleSheetChangeEventsEnabled()) { + DO_STYLESHEET_NOTIFICATION(NS_NewDOMStyleSheetChangeEvent, + nsIDOMStyleSheetChangeEvent, + InitStyleSheetChangeEvent, + "StyleSheetRemoved", + aDocumentSheet); + } +} + +void +nsDocument::AddStyleSheet(nsIStyleSheet* aSheet) +{ + NS_PRECONDITION(aSheet, "null arg"); + mStyleSheets.AppendObject(aSheet); + aSheet->SetOwningDocument(this); + + if (aSheet->IsApplicable()) { + AddStyleSheetToStyleSets(aSheet); + } + + NotifyStyleSheetAdded(aSheet, true); +} + +void +nsDocument::RemoveStyleSheetFromStyleSets(nsIStyleSheet* aSheet) +{ + nsCOMPtr shell = GetShell(); + if (shell) { + shell->StyleSet()->RemoveDocStyleSheet(aSheet); + } +} + +void +nsDocument::RemoveStyleSheet(nsIStyleSheet* aSheet) +{ + NS_PRECONDITION(aSheet, "null arg"); + nsCOMPtr sheet = aSheet; // hold ref so it won't die too soon + + if (!mStyleSheets.RemoveObject(aSheet)) { + NS_ASSERTION(mInUnlinkOrDeletion, "stylesheet not found"); + return; + } + + if (!mIsGoingAway) { + if (aSheet->IsApplicable()) { + RemoveStyleSheetFromStyleSets(aSheet); + } + + NotifyStyleSheetRemoved(aSheet, true); + } + + aSheet->SetOwningDocument(nullptr); +} + +void +nsDocument::UpdateStyleSheets(nsCOMArray& aOldSheets, + nsCOMArray& aNewSheets) +{ + BeginUpdate(UPDATE_STYLE); + + // XXX Need to set the sheet on the ownernode, if any + NS_PRECONDITION(aOldSheets.Count() == aNewSheets.Count(), + "The lists must be the same length!"); + int32_t count = aOldSheets.Count(); + + nsCOMPtr oldSheet; + int32_t i; + for (i = 0; i < count; ++i) { + oldSheet = aOldSheets[i]; + + // First remove the old sheet. + NS_ASSERTION(oldSheet, "None of the old sheets should be null"); + int32_t oldIndex = mStyleSheets.IndexOf(oldSheet); + RemoveStyleSheet(oldSheet); // This does the right notifications + + // Now put the new one in its place. If it's null, just ignore it. + nsIStyleSheet* newSheet = aNewSheets[i]; + if (newSheet) { + mStyleSheets.InsertObjectAt(newSheet, oldIndex); + newSheet->SetOwningDocument(this); + if (newSheet->IsApplicable()) { + AddStyleSheetToStyleSets(newSheet); + } + + NotifyStyleSheetAdded(newSheet, true); + } + } + + EndUpdate(UPDATE_STYLE); +} + +void +nsDocument::InsertStyleSheetAt(nsIStyleSheet* aSheet, int32_t aIndex) +{ + NS_PRECONDITION(aSheet, "null ptr"); + mStyleSheets.InsertObjectAt(aSheet, aIndex); + + aSheet->SetOwningDocument(this); + + if (aSheet->IsApplicable()) { + AddStyleSheetToStyleSets(aSheet); + } + + NotifyStyleSheetAdded(aSheet, true); +} + + +void +nsDocument::SetStyleSheetApplicableState(nsIStyleSheet* aSheet, + bool aApplicable) +{ + NS_PRECONDITION(aSheet, "null arg"); + + // If we're actually in the document style sheet list + if (-1 != mStyleSheets.IndexOf(aSheet)) { + if (aApplicable) { + AddStyleSheetToStyleSets(aSheet); + } else { + RemoveStyleSheetFromStyleSets(aSheet); + } + } + + // We have to always notify, since this will be called for sheets + // that are children of sheets in our style set, as well as some + // sheets for nsHTMLEditor. + + NS_DOCUMENT_NOTIFY_OBSERVERS(StyleSheetApplicableStateChanged, + (this, aSheet, aApplicable)); + + if (StyleSheetChangeEventsEnabled()) { + DO_STYLESHEET_NOTIFICATION(NS_NewDOMStyleSheetApplicableStateChangeEvent, + nsIDOMStyleSheetApplicableStateChangeEvent, + InitStyleSheetApplicableStateChangeEvent, + "StyleSheetApplicableStateChanged", + aApplicable); + } + + if (!mSSApplicableStateNotificationPending) { + nsRefPtr notification = NS_NewRunnableMethod(this, + &nsDocument::NotifyStyleSheetApplicableStateChanged); + mSSApplicableStateNotificationPending = + NS_SUCCEEDED(NS_DispatchToCurrentThread(notification)); + } +} + +void +nsDocument::NotifyStyleSheetApplicableStateChanged() +{ + mSSApplicableStateNotificationPending = false; + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers(static_cast(this), + "style-sheet-applicable-state-changed", + nullptr); + } +} + +// These three functions are a lot like the implementation of the +// corresponding API for regular stylesheets. + +int32_t +nsDocument::GetNumberOfCatalogStyleSheets() const +{ + return mCatalogSheets.Count(); +} + +nsIStyleSheet* +nsDocument::GetCatalogStyleSheetAt(int32_t aIndex) const +{ + NS_ENSURE_TRUE(0 <= aIndex && aIndex < mCatalogSheets.Count(), nullptr); + return mCatalogSheets[aIndex]; +} + +void +nsDocument::AddCatalogStyleSheet(nsCSSStyleSheet* aSheet) +{ + mCatalogSheets.AppendObject(aSheet); + aSheet->SetOwningDocument(this); + aSheet->SetOwningNode(this); + + if (aSheet->IsApplicable()) { + // This is like |AddStyleSheetToStyleSets|, but for an agent sheet. + nsCOMPtr shell = GetShell(); + if (shell) { + shell->StyleSet()->AppendStyleSheet(nsStyleSet::eAgentSheet, aSheet); + } + } + + NotifyStyleSheetAdded(aSheet, false); +} + +void +nsDocument::EnsureCatalogStyleSheet(const char *aStyleSheetURI) +{ + mozilla::css::Loader* cssLoader = CSSLoader(); + if (cssLoader->GetEnabled()) { + int32_t sheetCount = GetNumberOfCatalogStyleSheets(); + for (int32_t i = 0; i < sheetCount; i++) { + nsIStyleSheet* sheet = GetCatalogStyleSheetAt(i); + NS_ASSERTION(sheet, "unexpected null stylesheet in the document"); + if (sheet) { + nsAutoCString uriStr; + sheet->GetSheetURI()->GetSpec(uriStr); + if (uriStr.Equals(aStyleSheetURI)) + return; + } + } + + nsCOMPtr uri; + NS_NewURI(getter_AddRefs(uri), aStyleSheetURI); + if (uri) { + nsRefPtr sheet; + cssLoader->LoadSheetSync(uri, true, true, getter_AddRefs(sheet)); + if (sheet) { + BeginUpdate(UPDATE_STYLE); + AddCatalogStyleSheet(sheet); + EndUpdate(UPDATE_STYLE); + } + } + } +} + +static nsStyleSet::sheetType +ConvertAdditionalSheetType(nsIDocument::additionalSheetType aType) +{ + switch(aType) { + case nsIDocument::eAgentSheet: + return nsStyleSet::eAgentSheet; + case nsIDocument::eUserSheet: + return nsStyleSet::eUserSheet; + case nsIDocument::eAuthorSheet: + return nsStyleSet::eDocSheet; + default: + NS_ASSERTION(false, "wrong type"); + // we must return something although this should never happen + return nsStyleSet::eSheetTypeCount; + } +} + +static int32_t +FindSheet(const nsCOMArray& aSheets, nsIURI* aSheetURI) +{ + for (int32_t i = aSheets.Count() - 1; i >= 0; i-- ) { + bool bEqual; + nsIURI* uri = aSheets[i]->GetSheetURI(); + + if (uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &bEqual)) && bEqual) + return i; + } + + return -1; +} + +nsresult +nsDocument::LoadAdditionalStyleSheet(additionalSheetType aType, nsIURI* aSheetURI) +{ + NS_PRECONDITION(aSheetURI, "null arg"); + + // Checking if we have loaded this one already. + if (FindSheet(mAdditionalSheets[aType], aSheetURI) >= 0) + return NS_ERROR_INVALID_ARG; + + // Loading the sheet sync. + nsRefPtr loader = new mozilla::css::Loader(); + + nsRefPtr sheet; + nsresult rv = loader->LoadSheetSync(aSheetURI, aType == eAgentSheet, + true, getter_AddRefs(sheet)); + NS_ENSURE_SUCCESS(rv, rv); + + mAdditionalSheets[aType].AppendObject(sheet); + sheet->SetOwningDocument(this); + MOZ_ASSERT(sheet->IsApplicable()); + + BeginUpdate(UPDATE_STYLE); + nsCOMPtr shell = GetShell(); + if (shell) { + nsStyleSet::sheetType type = ConvertAdditionalSheetType(aType); + shell->StyleSet()->AppendStyleSheet(type, sheet); + } + + // Passing false, so documet.styleSheets.length will not be affected by + // these additional sheets. + NotifyStyleSheetAdded(sheet, false); + EndUpdate(UPDATE_STYLE); + + return NS_OK; +} + +void +nsDocument::RemoveAdditionalStyleSheet(additionalSheetType aType, nsIURI* aSheetURI) +{ + MOZ_ASSERT(aSheetURI); + + nsCOMArray& sheets = mAdditionalSheets[aType]; + + int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI); + if (i >= 0) { + nsCOMPtr sheetRef = sheets[i]; + sheets.RemoveObjectAt(i); + + BeginUpdate(UPDATE_STYLE); + if (!mIsGoingAway) { + MOZ_ASSERT(sheetRef->IsApplicable()); + nsCOMPtr shell = GetShell(); + if (shell) { + nsStyleSet::sheetType type = ConvertAdditionalSheetType(aType); + shell->StyleSet()->RemoveStyleSheet(type, sheetRef); + } + } + + // Passing false, so documet.styleSheets.length will not be affected by + // these additional sheets. + NotifyStyleSheetRemoved(sheetRef, false); + EndUpdate(UPDATE_STYLE); + + sheetRef->SetOwningDocument(nullptr); + } +} + +nsIStyleSheet* +nsDocument::FirstAdditionalAuthorSheet() +{ + return mAdditionalSheets[eAuthorSheet].SafeObjectAt(0); +} + +nsIGlobalObject* +nsDocument::GetScopeObject() const +{ + nsCOMPtr scope(do_QueryReferent(mScopeObject)); + return scope; +} + +void +nsDocument::SetScopeObject(nsIGlobalObject* aGlobal) +{ + mScopeObject = do_GetWeakReference(aGlobal); + if (aGlobal) { + mHasHadScriptHandlingObject = true; + } +} + +static void +NotifyActivityChanged(nsIContent *aContent, void *aUnused) +{ + nsCOMPtr domMediaElem(do_QueryInterface(aContent)); + if (domMediaElem) { + HTMLMediaElement* mediaElem = static_cast(aContent); + mediaElem->NotifyOwnerDocumentActivityChanged(); + } + nsCOMPtr objectLoadingContent(do_QueryInterface(aContent)); + if (objectLoadingContent) { + nsObjectLoadingContent* olc = static_cast(objectLoadingContent.get()); + olc->NotifyOwnerDocumentActivityChanged(); + } +} + +void +nsIDocument::SetContainer(nsDocShell* aContainer) +{ + if (aContainer) { + mDocumentContainer = aContainer->asWeakPtr(); + } else { + mDocumentContainer = WeakPtr(); + } + + EnumerateFreezableElements(NotifyActivityChanged, nullptr); + if (!aContainer) { + return; + } + + // Get the Docshell + if (aContainer->ItemType() == nsIDocShellTreeItem::typeContent) { + // check if same type root + nsCOMPtr sameTypeRoot; + aContainer->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); + NS_ASSERTION(sameTypeRoot, "No document shell root tree item from document shell tree item!"); + + if (sameTypeRoot == aContainer) { + static_cast(this)->SetIsTopLevelContentDocument(true); + } + } +} + +nsISupports* +nsIDocument::GetContainer() const +{ + return static_cast(mDocumentContainer); +} + +void +nsDocument::SetScriptGlobalObject(nsIScriptGlobalObject *aScriptGlobalObject) +{ +#ifdef DEBUG + { + nsCOMPtr win(do_QueryInterface(aScriptGlobalObject)); + + NS_ASSERTION(!win || win->IsInnerWindow(), + "Script global object must be an inner window!"); + } +#endif + NS_ABORT_IF_FALSE(aScriptGlobalObject || !mAnimationController || + mAnimationController->IsPausedByType( + nsSMILTimeContainer::PAUSE_PAGEHIDE | + nsSMILTimeContainer::PAUSE_BEGIN), + "Clearing window pointer while animations are unpaused"); + + if (mScriptGlobalObject && !aScriptGlobalObject) { + // We're detaching from the window. We need to grab a pointer to + // our layout history state now. + mLayoutHistoryState = GetLayoutHistoryState(); + + if (mPresShell && !EventHandlingSuppressed()) { + RevokeAnimationFrameNotifications(); + } + + // Also make sure to remove our onload blocker now if we haven't done it yet + if (mOnloadBlockCount != 0) { + nsCOMPtr loadGroup = GetDocumentLoadGroup(); + if (loadGroup) { + loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK); + } + } + } + + mScriptGlobalObject = aScriptGlobalObject; + + if (aScriptGlobalObject) { + mHasHadScriptHandlingObject = true; + mHasHadDefaultView = true; + // Go back to using the docshell for the layout history state + mLayoutHistoryState = nullptr; + mScopeObject = do_GetWeakReference(aScriptGlobalObject); +#ifdef DEBUG + if (!mWillReparent) { + // We really shouldn't have a wrapper here but if we do we need to make sure + // it has the correct parent. + JSObject *obj = GetWrapperPreserveColor(); + if (obj) { + JSObject *newScope = aScriptGlobalObject->GetGlobalJSObject(); + NS_ASSERTION(js::GetGlobalForObjectCrossCompartment(obj) == newScope, + "Wrong scope, this is really bad!"); + } + } +#endif + + if (mAllowDNSPrefetch) { + nsCOMPtr docShell(mDocumentContainer); + if (docShell) { +#ifdef DEBUG + nsCOMPtr webNav = + do_GetInterface(aScriptGlobalObject); + NS_ASSERTION(SameCOMIdentity(webNav, docShell), + "Unexpected container or script global?"); +#endif + bool allowDNSPrefetch; + docShell->GetAllowDNSPrefetch(&allowDNSPrefetch); + mAllowDNSPrefetch = allowDNSPrefetch; + } + } + + MaybeRescheduleAnimationFrameNotifications(); + mRegistry = new Registry(); + } + + // Remember the pointer to our window (or lack there of), to avoid + // having to QI every time it's asked for. + nsCOMPtr window = do_QueryInterface(mScriptGlobalObject); + mWindow = window; + + // Now that we know what our window is, we can flush the CSP errors to the + // Web Console. We are flushing all messages that occured and were stored + // in the queue prior to this point. + FlushCSPWebConsoleErrorQueue(); + nsCOMPtr internalChannel = + do_QueryInterface(GetChannel()); + if (internalChannel) { + nsCOMArray messages; + internalChannel->TakeAllSecurityMessages(messages); + SendToConsole(messages); + } + + // Set our visibility state, but do not fire the event. This is correct + // because either we're coming out of bfcache (in which case IsVisible() will + // still test false at this point and no state change will happen) or we're + // doing the initial document load and don't want to fire the event for this + // change. + mVisibilityState = GetVisibilityState(); +} + +nsIScriptGlobalObject* +nsDocument::GetScriptHandlingObjectInternal() const +{ + MOZ_ASSERT(!mScriptGlobalObject, + "Do not call this when mScriptGlobalObject is set!"); + if (mHasHadDefaultView) { + return nullptr; + } + + nsCOMPtr scriptHandlingObject = + do_QueryReferent(mScopeObject); + nsCOMPtr win = do_QueryInterface(scriptHandlingObject); + if (win) { + NS_ASSERTION(win->IsInnerWindow(), "Should have inner window here!"); + nsPIDOMWindow* outer = win->GetOuterWindow(); + if (!outer || outer->GetCurrentInnerWindow() != win) { + NS_WARNING("Wrong inner/outer window combination!"); + return nullptr; + } + } + return scriptHandlingObject; +} +void +nsDocument::SetScriptHandlingObject(nsIScriptGlobalObject* aScriptObject) +{ + NS_ASSERTION(!mScriptGlobalObject || + mScriptGlobalObject == aScriptObject, + "Wrong script object!"); + nsCOMPtr win = do_QueryInterface(aScriptObject); + NS_ASSERTION(!win || win->IsInnerWindow(), "Should have inner window here!"); + if (aScriptObject) { + mScopeObject = do_GetWeakReference(aScriptObject); + mHasHadScriptHandlingObject = true; + mHasHadDefaultView = false; + } +} + +bool +nsDocument::IsTopLevelContentDocument() +{ + return mIsTopLevelContentDocument; +} + +void +nsDocument::SetIsTopLevelContentDocument(bool aIsTopLevelContentDocument) +{ + mIsTopLevelContentDocument = aIsTopLevelContentDocument; +} + +nsPIDOMWindow * +nsDocument::GetWindowInternal() const +{ + MOZ_ASSERT(!mWindow, "This should not be called when mWindow is not null!"); + // Let's use mScriptGlobalObject. Even if the document is already removed from + // the docshell, the outer window might be still obtainable from the it. + nsCOMPtr win; + if (mRemovedFromDocShell) { + nsCOMPtr requestor(mDocumentContainer); + if (requestor) { + // The docshell returns the outer window we are done. + win = do_GetInterface(requestor); + } + } else { + win = do_QueryInterface(mScriptGlobalObject); + if (win) { + // mScriptGlobalObject is always the inner window, let's get the outer. + win = win->GetOuterWindow(); + } + } + + return win; +} + +nsScriptLoader* +nsDocument::ScriptLoader() +{ + return mScriptLoader; +} + +bool +nsDocument::InternalAllowXULXBL() +{ + if (nsContentUtils::AllowXULXBLForPrincipal(NodePrincipal())) { + mAllowXULXBL = eTriTrue; + return true; + } + + mAllowXULXBL = eTriFalse; + return false; +} + +// Note: We don't hold a reference to the document observer; we assume +// that it has a live reference to the document. +void +nsDocument::AddObserver(nsIDocumentObserver* aObserver) +{ + NS_ASSERTION(mObservers.IndexOf(aObserver) == nsTArray::NoIndex, + "Observer already in the list"); + mObservers.AppendElement(aObserver); + AddMutationObserver(aObserver); +} + +bool +nsDocument::RemoveObserver(nsIDocumentObserver* aObserver) +{ + // If we're in the process of destroying the document (and we're + // informing the observers of the destruction), don't remove the + // observers from the list. This is not a big deal, since we + // don't hold a live reference to the observers. + if (!mInDestructor) { + RemoveMutationObserver(aObserver); + return mObservers.RemoveElement(aObserver); + } + + return mObservers.Contains(aObserver); +} + +void +nsDocument::MaybeEndOutermostXBLUpdate() +{ + // Only call BindingManager()->EndOutermostUpdate() when + // we're not in an update and it is safe to run scripts. + if (mUpdateNestLevel == 0 && mInXBLUpdate) { + if (nsContentUtils::IsSafeToRunScript()) { + mInXBLUpdate = false; + BindingManager()->EndOutermostUpdate(); + } else if (!mInDestructor) { + nsContentUtils::AddScriptRunner( + NS_NewRunnableMethod(this, &nsDocument::MaybeEndOutermostXBLUpdate)); + } + } +} + +void +nsDocument::BeginUpdate(nsUpdateType aUpdateType) +{ + if (mUpdateNestLevel == 0 && !mInXBLUpdate) { + mInXBLUpdate = true; + BindingManager()->BeginOutermostUpdate(); + } + + ++mUpdateNestLevel; + nsContentUtils::AddScriptBlocker(); + NS_DOCUMENT_NOTIFY_OBSERVERS(BeginUpdate, (this, aUpdateType)); +} + +void +nsDocument::EndUpdate(nsUpdateType aUpdateType) +{ + NS_DOCUMENT_NOTIFY_OBSERVERS(EndUpdate, (this, aUpdateType)); + + nsContentUtils::RemoveScriptBlocker(); + + --mUpdateNestLevel; + + // This set of updates may have created XBL bindings. Let the + // binding manager know we're done. + MaybeEndOutermostXBLUpdate(); + + MaybeInitializeFinalizeFrameLoaders(); +} + +void +nsDocument::BeginLoad() +{ + // Block onload here to prevent having to deal with blocking and + // unblocking it while we know the document is loading. + BlockOnload(); + mDidFireDOMContentLoaded = false; + BlockDOMContentLoaded(); + + if (mScriptLoader) { + mScriptLoader->BeginDeferringScripts(); + } + + NS_DOCUMENT_NOTIFY_OBSERVERS(BeginLoad, (this)); +} + +void +nsDocument::ReportEmptyGetElementByIdArg() +{ + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("DOM"), this, + nsContentUtils::eDOM_PROPERTIES, + "EmptyGetElementByIdParam"); +} + +Element* +nsDocument::GetElementById(const nsAString& aElementId) +{ + if (!CheckGetElementByIdArg(aElementId)) { + return nullptr; + } + + nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aElementId); + return entry ? entry->GetIdElement() : nullptr; +} + +const nsSmallVoidArray* +nsDocument::GetAllElementsForId(const nsAString& aElementId) const +{ + if (aElementId.IsEmpty()) { + return nullptr; + } + + nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aElementId); + return entry ? entry->GetIdElements() : nullptr; +} + +NS_IMETHODIMP +nsDocument::GetElementById(const nsAString& aId, nsIDOMElement** aReturn) +{ + Element *content = GetElementById(aId); + if (content) { + return CallQueryInterface(content, aReturn); + } + + *aReturn = nullptr; + + return NS_OK; +} + +Element* +nsDocument::AddIDTargetObserver(nsIAtom* aID, IDTargetObserver aObserver, + void* aData, bool aForImage) +{ + nsDependentAtomString id(aID); + + if (!CheckGetElementByIdArg(id)) + return nullptr; + + nsIdentifierMapEntry *entry = mIdentifierMap.PutEntry(id); + NS_ENSURE_TRUE(entry, nullptr); + + entry->AddContentChangeCallback(aObserver, aData, aForImage); + return aForImage ? entry->GetImageIdElement() : entry->GetIdElement(); +} + +void +nsDocument::RemoveIDTargetObserver(nsIAtom* aID, IDTargetObserver aObserver, + void* aData, bool aForImage) +{ + nsDependentAtomString id(aID); + + if (!CheckGetElementByIdArg(id)) + return; + + nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(id); + if (!entry) { + return; + } + + entry->RemoveContentChangeCallback(aObserver, aData, aForImage); +} + +NS_IMETHODIMP +nsDocument::MozSetImageElement(const nsAString& aImageElementId, + nsIDOMElement* aImageElement) +{ + nsCOMPtr el = do_QueryInterface(aImageElement); + MozSetImageElement(aImageElementId, el); + return NS_OK; +} + +void +nsDocument::MozSetImageElement(const nsAString& aImageElementId, + Element* aElement) +{ + if (aImageElementId.IsEmpty()) + return; + + // Hold a script blocker while calling SetImageElement since that can call + // out to id-observers + nsAutoScriptBlocker scriptBlocker; + + nsIdentifierMapEntry *entry = mIdentifierMap.PutEntry(aImageElementId); + if (entry) { + entry->SetImageElement(aElement); + if (entry->IsEmpty()) { + mIdentifierMap.RemoveEntry(aImageElementId); + } + } +} + +Element* +nsDocument::LookupImageElement(const nsAString& aId) +{ + if (aId.IsEmpty()) + return nullptr; + + nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aId); + return entry ? entry->GetImageIdElement() : nullptr; +} + +void +nsDocument::DispatchContentLoadedEvents() +{ + // If you add early returns from this method, make sure you're + // calling UnblockOnload properly. + + // Unpin references to preloaded images + mPreloadingImages.Clear(); + + if (mTiming) { + mTiming->NotifyDOMContentLoadedStart(nsIDocument::GetDocumentURI()); + } + + // Dispatch observer notification to notify observers document is interactive. + nsCOMPtr os = mozilla::services::GetObserverService(); + nsIPrincipal *principal = GetPrincipal(); + os->NotifyObservers(static_cast(this), + nsContentUtils::IsSystemPrincipal(principal) ? + "chrome-document-interactive" : + "content-document-interactive", + nullptr); + + // Fire a DOM event notifying listeners that this document has been + // loaded (excluding images and other loads initiated by this + // document). + nsContentUtils::DispatchTrustedEvent(this, static_cast(this), + NS_LITERAL_STRING("DOMContentLoaded"), + true, true); + + if (mTiming) { + mTiming->NotifyDOMContentLoadedEnd(nsIDocument::GetDocumentURI()); + } + + // If this document is a [i]frame, fire a DOMFrameContentLoaded + // event on all parent documents notifying that the HTML (excluding + // other external files such as images and stylesheets) in a frame + // has finished loading. + + // target_frame is the [i]frame element that will be used as the + // target for the event. It's the [i]frame whose content is done + // loading. + nsCOMPtr target_frame; + + if (mParentDocument) { + target_frame = mParentDocument->FindContentForSubDocument(this); + } + + if (target_frame) { + nsCOMPtr parent = mParentDocument; + do { + nsCOMPtr domDoc = do_QueryInterface(parent); + + nsCOMPtr event; + if (domDoc) { + domDoc->CreateEvent(NS_LITERAL_STRING("Events"), + getter_AddRefs(event)); + + } + + if (event) { + event->InitEvent(NS_LITERAL_STRING("DOMFrameContentLoaded"), true, + true); + + event->SetTarget(target_frame); + event->SetTrusted(true); + + // To dispatch this event we must manually call + // EventDispatcher::Dispatch() on the ancestor document since the + // target is not in the same document, so the event would never reach + // the ancestor document if we used the normal event + // dispatching code. + + WidgetEvent* innerEvent = event->GetInternalNSEvent(); + if (innerEvent) { + nsEventStatus status = nsEventStatus_eIgnore; + + nsIPresShell *shell = parent->GetShell(); + if (shell) { + nsRefPtr context = shell->GetPresContext(); + + if (context) { + EventDispatcher::Dispatch(parent, context, innerEvent, event, + &status); + } + } + } + } + + parent = parent->GetParentDocument(); + } while (parent); + } + + // If the document has a manifest attribute, fire a MozApplicationManifest + // event. + Element* root = GetRootElement(); + if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::manifest)) { + nsContentUtils::DispatchChromeEvent(this, static_cast(this), + NS_LITERAL_STRING("MozApplicationManifest"), + true, true); + } + + UnblockOnload(true); +} + +void +nsDocument::EndLoad() +{ + // Drop the ref to our parser, if any, but keep hold of the sink so that we + // can flush it from FlushPendingNotifications as needed. We might have to + // do that to get a StartLayout() to happen. + if (mParser) { + mWeakSink = do_GetWeakReference(mParser->GetContentSink()); + mParser = nullptr; + } + + NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this)); + + UnblockDOMContentLoaded(); +} + +void +nsDocument::UnblockDOMContentLoaded() +{ + MOZ_ASSERT(mBlockDOMContentLoaded); + if (--mBlockDOMContentLoaded != 0 || mDidFireDOMContentLoaded) { + return; + } + mDidFireDOMContentLoaded = true; + + MOZ_ASSERT(mReadyState == READYSTATE_INTERACTIVE); + if (!mSynchronousDOMContentLoaded) { + nsRefPtr ev = + NS_NewRunnableMethod(this, &nsDocument::DispatchContentLoadedEvents); + NS_DispatchToCurrentThread(ev); + } else { + DispatchContentLoadedEvents(); + } +} + +void +nsDocument::ContentStateChanged(nsIContent* aContent, EventStates aStateMask) +{ + NS_PRECONDITION(!nsContentUtils::IsSafeToRunScript(), + "Someone forgot a scriptblocker"); + NS_DOCUMENT_NOTIFY_OBSERVERS(ContentStateChanged, + (this, aContent, aStateMask)); +} + +void +nsDocument::DocumentStatesChanged(EventStates aStateMask) +{ + // Invalidate our cached state. + mGotDocumentState &= ~aStateMask; + mDocumentState &= ~aStateMask; + + NS_DOCUMENT_NOTIFY_OBSERVERS(DocumentStatesChanged, (this, aStateMask)); +} + +void +nsDocument::StyleRuleChanged(nsIStyleSheet* aSheet, + nsIStyleRule* aOldStyleRule, + nsIStyleRule* aNewStyleRule) +{ + NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleChanged, + (this, aSheet, + aOldStyleRule, aNewStyleRule)); + + if (StyleSheetChangeEventsEnabled()) { + nsCOMPtr rule = do_QueryInterface(aNewStyleRule); + DO_STYLESHEET_NOTIFICATION(NS_NewDOMStyleRuleChangeEvent, + nsIDOMStyleRuleChangeEvent, + InitStyleRuleChangeEvent, + "StyleRuleChanged", + rule ? rule->GetDOMRule() : nullptr); + } +} + +void +nsDocument::StyleRuleAdded(nsIStyleSheet* aSheet, + nsIStyleRule* aStyleRule) +{ + NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleAdded, + (this, aSheet, aStyleRule)); + + if (StyleSheetChangeEventsEnabled()) { + nsCOMPtr rule = do_QueryInterface(aStyleRule); + DO_STYLESHEET_NOTIFICATION(NS_NewDOMStyleRuleChangeEvent, + nsIDOMStyleRuleChangeEvent, + InitStyleRuleChangeEvent, + "StyleRuleAdded", + rule ? rule->GetDOMRule() : nullptr); + } +} + +void +nsDocument::StyleRuleRemoved(nsIStyleSheet* aSheet, + nsIStyleRule* aStyleRule) +{ + NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleRemoved, + (this, aSheet, aStyleRule)); + + if (StyleSheetChangeEventsEnabled()) { + nsCOMPtr rule = do_QueryInterface(aStyleRule); + DO_STYLESHEET_NOTIFICATION(NS_NewDOMStyleRuleChangeEvent, + nsIDOMStyleRuleChangeEvent, + InitStyleRuleChangeEvent, + "StyleRuleRemoved", + rule ? rule->GetDOMRule() : nullptr); + } +} + +#undef DO_STYLESHEET_NOTIFICATION + + +// +// nsIDOMDocument interface +// +DocumentType* +nsIDocument::GetDoctype() const +{ + for (nsIContent* child = GetFirstChild(); + child; + child = child->GetNextSibling()) { + if (child->NodeType() == nsIDOMNode::DOCUMENT_TYPE_NODE) { + return static_cast(child); + } + } + return nullptr; +} + +NS_IMETHODIMP +nsDocument::GetDoctype(nsIDOMDocumentType** aDoctype) +{ + MOZ_ASSERT(aDoctype); + nsCOMPtr doctype = nsIDocument::GetDoctype(); + doctype.forget(aDoctype); + return NS_OK; +} + +NS_IMETHODIMP +nsDocument::GetImplementation(nsIDOMDOMImplementation** aImplementation) +{ + ErrorResult rv; + *aImplementation = GetImplementation(rv); + if (rv.Failed()) { + MOZ_ASSERT(!*aImplementation); + return rv.ErrorCode(); + } + NS_ADDREF(*aImplementation); + return NS_OK; +} + +DOMImplementation* +nsDocument::GetImplementation(ErrorResult& rv) +{ + if (!mDOMImplementation) { + nsCOMPtr uri; + NS_NewURI(getter_AddRefs(uri), "about:blank"); + if (!uri) { + rv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + bool hasHadScriptObject = true; + nsIScriptGlobalObject* scriptObject = + GetScriptHandlingObject(hasHadScriptObject); + if (!scriptObject && hasHadScriptObject) { + rv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + mDOMImplementation = new DOMImplementation(this, + scriptObject ? scriptObject : GetScopeObject(), uri, uri); + } + + return mDOMImplementation; +} + +NS_IMETHODIMP +nsDocument::GetDocumentElement(nsIDOMElement** aDocumentElement) +{ + NS_ENSURE_ARG_POINTER(aDocumentElement); + + Element* root = GetRootElement(); + if (root) { + return CallQueryInterface(root, aDocumentElement); + } + + *aDocumentElement = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +nsDocument::CreateElement(const nsAString& aTagName, + nsIDOMElement** aReturn) +{ + *aReturn = nullptr; + ErrorResult rv; + nsCOMPtr element = nsIDocument::CreateElement(aTagName, rv); + NS_ENSURE_FALSE(rv.Failed(), rv.ErrorCode()); + return CallQueryInterface(element, aReturn); +} + +bool IsLowercaseASCII(const nsAString& aValue) +{ + int32_t len = aValue.Length(); + for (int32_t i = 0; i < len; ++i) { + char16_t c = aValue[i]; + if (!(0x0061 <= (c) && ((c) <= 0x007a))) { + return false; + } + } + return true; +} + +already_AddRefed +nsIDocument::CreateElement(const nsAString& aTagName, ErrorResult& rv) +{ + rv = nsContentUtils::CheckQName(aTagName, false); + if (rv.Failed()) { + return nullptr; + } + + bool needsLowercase = IsHTML() && !IsLowercaseASCII(aTagName); + nsAutoString lcTagName; + if (needsLowercase) { + nsContentUtils::ASCIIToLower(aTagName, lcTagName); + } + + nsCOMPtr content; + rv = CreateElem(needsLowercase ? lcTagName : aTagName, + nullptr, mDefaultElementType, getter_AddRefs(content)); + if (rv.Failed()) { + return nullptr; + } + return dont_AddRef(content.forget().take()->AsElement()); +} + +void +nsDocument::SwizzleCustomElement(Element* aElement, + const nsAString& aTypeExtension, + uint32_t aNamespaceID, + ErrorResult& rv) +{ + nsCOMPtr typeAtom(do_GetAtom(aTypeExtension)); + nsCOMPtr tagAtom = aElement->Tag(); + if (!mRegistry || tagAtom == typeAtom) { + return; + } + + CustomElementDefinition* data; + CustomElementHashKey key(aNamespaceID, typeAtom); + if (!mRegistry->mCustomDefinitions.Get(&key, &data)) { + // The type extension doesn't exist in the registry, + // thus we don't need to swizzle, but it is possibly + // an upgrade candidate. + RegisterUnresolvedElement(aElement, typeAtom); + return; + } + + if (data->mLocalName != tagAtom) { + // The element doesn't match the local name for the + // definition, thus the element isn't a custom element + // and we don't need to do anything more. + return; + } + + if (!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::is)) { + // Swizzling in the parser happens after the "is" attribute is added. + aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::is, aTypeExtension, true); + } + + // Enqueuing the created callback will set the CustomElementData on the + // element, causing prototype swizzling to occur in Element::WrapObject. + EnqueueLifecycleCallback(nsIDocument::eCreated, aElement, nullptr, data); +} + +already_AddRefed +nsDocument::CreateElement(const nsAString& aTagName, + const nsAString& aTypeExtension, + ErrorResult& rv) +{ + nsRefPtr elem = nsIDocument::CreateElement(aTagName, rv); + if (rv.Failed()) { + return nullptr; + } + + SwizzleCustomElement(elem, aTypeExtension, + GetDefaultNamespaceID(), rv); + if (rv.Failed()) { + return nullptr; + } + + return elem.forget(); +} + +NS_IMETHODIMP +nsDocument::CreateElementNS(const nsAString& aNamespaceURI, + const nsAString& aQualifiedName, + nsIDOMElement** aReturn) +{ + *aReturn = nullptr; + ErrorResult rv; + nsCOMPtr element = + nsIDocument::CreateElementNS(aNamespaceURI, aQualifiedName, rv); + NS_ENSURE_FALSE(rv.Failed(), rv.ErrorCode()); + return CallQueryInterface(element, aReturn); +} + +already_AddRefed +nsIDocument::CreateElementNS(const nsAString& aNamespaceURI, + const nsAString& aQualifiedName, + ErrorResult& rv) +{ + nsCOMPtr nodeInfo; + rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, + aQualifiedName, + mNodeInfoManager, + nsIDOMNode::ELEMENT_NODE, + getter_AddRefs(nodeInfo)); + if (rv.Failed()) { + return nullptr; + } + + nsCOMPtr element; + rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(), + NOT_FROM_PARSER); + if (rv.Failed()) { + return nullptr; + } + return element.forget(); +} + +already_AddRefed +nsDocument::CreateElementNS(const nsAString& aNamespaceURI, + const nsAString& aQualifiedName, + const nsAString& aTypeExtension, + ErrorResult& rv) +{ + nsRefPtr elem = nsIDocument::CreateElementNS(aNamespaceURI, + aQualifiedName, + rv); + if (rv.Failed()) { + return nullptr; + } + + int32_t nameSpaceId = kNameSpaceID_Wildcard; + if (!aNamespaceURI.EqualsLiteral("*")) { + rv = nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI, + nameSpaceId); + if (rv.Failed()) { + return nullptr; + } + } + + SwizzleCustomElement(elem, aTypeExtension, nameSpaceId, rv); + if (rv.Failed()) { + return nullptr; + } + + return elem.forget(); +} + +NS_IMETHODIMP +nsDocument::CreateTextNode(const nsAString& aData, nsIDOMText** aReturn) +{ + *aReturn = nsIDocument::CreateTextNode(aData).take(); + return NS_OK; +} + +already_AddRefed +nsIDocument::CreateTextNode(const nsAString& aData) const +{ + nsRefPtr text = new nsTextNode(mNodeInfoManager); + // Don't notify; this node is still being created. + text->SetText(aData, false); + return text.forget(); +} + +NS_IMETHODIMP +nsDocument::CreateDocumentFragment(nsIDOMDocumentFragment** aReturn) +{ + *aReturn = nsIDocument::CreateDocumentFragment().take(); + return NS_OK; +} + +already_AddRefed +nsIDocument::CreateDocumentFragment() const +{ + nsRefPtr frag = new DocumentFragment(mNodeInfoManager); + return frag.forget(); +} + +NS_IMETHODIMP +nsDocument::CreateComment(const nsAString& aData, nsIDOMComment** aReturn) +{ + *aReturn = nsIDocument::CreateComment(aData).take(); + return NS_OK; +} + +// Unfortunately, bareword "Comment" is ambiguous with some Mac system headers. +already_AddRefed +nsIDocument::CreateComment(const nsAString& aData) const +{ + nsRefPtr comment = new dom::Comment(mNodeInfoManager); + + // Don't notify; this node is still being created. + comment->SetText(aData, false); + return comment.forget(); +} + +NS_IMETHODIMP +nsDocument::CreateCDATASection(const nsAString& aData, + nsIDOMCDATASection** aReturn) +{ + NS_ENSURE_ARG_POINTER(aReturn); + ErrorResult rv; + *aReturn = nsIDocument::CreateCDATASection(aData, rv).take(); + return rv.ErrorCode(); +} + +already_AddRefed +nsIDocument::CreateCDATASection(const nsAString& aData, + ErrorResult& rv) +{ + if (IsHTML()) { + rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + if (FindInReadable(NS_LITERAL_STRING("]]>"), aData)) { + rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR); + return nullptr; + } + + nsRefPtr cdata = new CDATASection(mNodeInfoManager); + + // Don't notify; this node is still being created. + cdata->SetText(aData, false); + + return cdata.forget(); +} + +NS_IMETHODIMP +nsDocument::CreateProcessingInstruction(const nsAString& aTarget, + const nsAString& aData, + nsIDOMProcessingInstruction** aReturn) +{ + ErrorResult rv; + *aReturn = + nsIDocument::CreateProcessingInstruction(aTarget, aData, rv).take(); + return rv.ErrorCode(); +} + +already_AddRefed +nsIDocument::CreateProcessingInstruction(const nsAString& aTarget, + const nsAString& aData, + ErrorResult& rv) const +{ + nsresult res = nsContentUtils::CheckQName(aTarget, false); + if (NS_FAILED(res)) { + rv.Throw(res); + return nullptr; + } + + if (FindInReadable(NS_LITERAL_STRING("?>"), aData)) { + rv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR); + return nullptr; + } + + nsRefPtr pi = + NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData); + + return pi.forget(); +} + +NS_IMETHODIMP +nsDocument::CreateAttribute(const nsAString& aName, + nsIDOMAttr** aReturn) +{ + ErrorResult rv; + *aReturn = nsIDocument::CreateAttribute(aName, rv).take(); + return rv.ErrorCode(); +} + +already_AddRefed +nsIDocument::CreateAttribute(const nsAString& aName, ErrorResult& rv) +{ + WarnOnceAbout(eCreateAttribute); + + if (!mNodeInfoManager) { + rv.Throw(NS_ERROR_NOT_INITIALIZED); + return nullptr; + } + + nsresult res = nsContentUtils::CheckQName(aName, false); + if (NS_FAILED(res)) { + rv.Throw(res); + return nullptr; + } + + nsCOMPtr nodeInfo; + res = mNodeInfoManager->GetNodeInfo(aName, nullptr, kNameSpaceID_None, + nsIDOMNode::ATTRIBUTE_NODE, + getter_AddRefs(nodeInfo)); + if (NS_FAILED(res)) { + rv.Throw(res); + return nullptr; + } + + nsRefPtr attribute = new Attr(nullptr, nodeInfo.forget(), + EmptyString(), false); + return attribute.forget(); +} + +NS_IMETHODIMP +nsDocument::CreateAttributeNS(const nsAString & aNamespaceURI, + const nsAString & aQualifiedName, + nsIDOMAttr **aResult) +{ + ErrorResult rv; + *aResult = + nsIDocument::CreateAttributeNS(aNamespaceURI, aQualifiedName, rv).take(); + return rv.ErrorCode(); +} + +already_AddRefed +nsIDocument::CreateAttributeNS(const nsAString& aNamespaceURI, + const nsAString& aQualifiedName, + ErrorResult& rv) +{ + WarnOnceAbout(eCreateAttributeNS); + + nsCOMPtr nodeInfo; + rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, + aQualifiedName, + mNodeInfoManager, + nsIDOMNode::ATTRIBUTE_NODE, + getter_AddRefs(nodeInfo)); + if (rv.Failed()) { + return nullptr; + } + + nsRefPtr attribute = new Attr(nullptr, nodeInfo.forget(), + EmptyString(), true); + return attribute.forget(); +} + +bool +nsDocument::CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp) +{ + JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp); + + JS::Rooted global(aCx, + JS_GetGlobalForObject(aCx, &args.callee())); + nsCOMPtr window = do_QueryWrapper(aCx, global); + MOZ_ASSERT(window, "Should have a non-null window"); + + nsDocument* document = static_cast(window->GetDoc()); + + // Function name is the type of the custom element. + JSString* jsFunName = + JS_GetFunctionId(JS_ValueToFunction(aCx, args.calleev())); + nsDependentJSString elemName; + if (!elemName.init(aCx, jsFunName)) { + return true; + } + + nsCOMPtr typeAtom(do_GetAtom(elemName)); + CustomElementHashKey key(kNameSpaceID_Unknown, typeAtom); + CustomElementDefinition* definition; + if (!document->mRegistry || + !document->mRegistry->mCustomDefinitions.Get(&key, &definition)) { + return true; + } + + nsDependentAtomString localName(definition->mLocalName); + + nsCOMPtr newElement; + nsresult rv = document->CreateElem(localName, nullptr, + definition->mNamespaceID, + getter_AddRefs(newElement)); + NS_ENSURE_SUCCESS(rv, true); + + ErrorResult errorResult; + nsCOMPtr element = do_QueryInterface(newElement); + document->SwizzleCustomElement(element, elemName, definition->mNamespaceID, + errorResult); + if (errorResult.Failed()) { + return true; + } + + rv = nsContentUtils::WrapNative(aCx, newElement, newElement, args.rval()); + NS_ENSURE_SUCCESS(rv, true); + + return true; +} + +bool +nsDocument::IsRegisterElementEnabled(JSContext* aCx, JSObject* aObject) +{ + JS::Rooted obj(aCx, aObject); + return Preferences::GetBool("dom.webcomponents.enabled") || + IsInCertifiedApp(aCx, obj); +} + +nsresult +nsDocument::RegisterUnresolvedElement(Element* aElement, nsIAtom* aTypeName) +{ + if (!mRegistry) { + return NS_OK; + } + + nsINodeInfo* info = aElement->NodeInfo(); + + // Candidate may be a custom element through extension, + // in which case the custom element type name will not + // match the element tag name. e.g.