/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * Base class for all our document implementations. */ #include "nsDocument.h" #include "nsIDocumentInlines.h" #include "mozilla/ArrayUtils.h" #include "mozilla/AutoRestore.h" #include "mozilla/BinarySearch.h" #include "mozilla/DebugOnly.h" #include "mozilla/IntegerRange.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Likely.h" #include #include "mozilla/Logging.h" #include "plstr.h" #include "prprf.h" #include "mozilla/Telemetry.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsILoadContext.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 "nsDocShellLoadTypes.h" #include "nsIDocShellTreeItem.h" #include "nsCOMArray.h" #include "nsQueryObject.h" #include "nsDOMClassInfo.h" #include "mozilla/Services.h" #include "nsScreen.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 "mozilla/dom/workers/ServiceWorkerManager.h" #include "imgLoader.h" #include "nsCanvasFrame.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 "nsISimpleEnumerator.h" #include "nsStyleSheetService.h" #include "nsNetUtil.h" // for NS_NewURI #include "nsIInputStreamChannel.h" #include "nsIAuthPrompt.h" #include "nsIAuthPrompt2.h" #include "nsIScriptSecurityManager.h" #include "nsIPermissionManager.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 "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 "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 "mozilla/dom/PageTransitionEvent.h" #include "mozilla/dom/StyleRuleChangeEvent.h" #include "mozilla/dom/StyleSheetChangeEvent.h" #include "mozilla/dom/StyleSheetApplicableStateChangeEvent.h" #include "nsJSUtils.h" #include "nsFrameLoader.h" #include "nsEscape.h" #include "nsObjectLoadingContent.h" #include "nsHtml5TreeOpExecutor.h" #include "mozilla/dom/HTMLLinkElement.h" #include "mozilla/dom/HTMLMediaElement.h" #include "mozilla/dom/HTMLIFrameElement.h" #include "mozilla/dom/HTMLImageElement.h" #include "mozilla/dom/MediaSource.h" #include "mozAutoDocUpdate.h" #include "nsGlobalWindow.h" #include "mozilla/dom/EncodingUtils.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 "mozilla/dom/nsCSPContext.h" #include "mozilla/dom/nsCSPService.h" #include "nsHTMLStyleSheet.h" #include "nsHTMLCSSStyleSheet.h" #include "SVGAttrAnimationRuleProcessor.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 "mozilla/Preferences.h" #include "imgILoader.h" #include "imgRequestProxy.h" #include "nsWrapperCacheInlines.h" #include "nsSandboxFlags.h" #include "nsIAppsService.h" #include "mozilla/dom/AnonymousContent.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/DocumentFragment.h" #include "mozilla/dom/DocumentTimeline.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/HTMLBodyElement.h" #include "mozilla/dom/HTMLInputElement.h" #include "mozilla/dom/MediaQueryList.h" #include "mozilla/dom/NodeFilterBinding.h" #include "mozilla/OwningNonNull.h" #include "mozilla/dom/TabChild.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 "mozilla/dom/XPathNSResolverBinding.h" #include "mozilla/dom/XPathResult.h" #include "nsIDocumentEncoder.h" #include "nsIDocumentActivity.h" #include "nsIStructuredCloneContainer.h" #include "nsIMutableArray.h" #include "nsContentPermissionHelper.h" #include "mozilla/dom/DOMStringList.h" #include "nsWindowMemoryReporter.h" #include "nsLocation.h" #include "mozilla/dom/FontFaceSet.h" #include "mozilla/dom/BoxObject.h" #include "gfxVR.h" #include "gfxPrefs.h" #include "nsISupportsPrimitives.h" #include "mozilla/DocLoadingTimelineMarker.h" #include "nsISpeculativeConnect.h" #ifdef MOZ_MEDIA_NAVIGATOR #include "mozilla/MediaManager.h" #endif // MOZ_MEDIA_NAVIGATOR #ifdef MOZ_WEBRTC #include "IPeerConnection.h" #endif // MOZ_WEBRTC using namespace mozilla; using namespace mozilla::dom; typedef nsTArray LinkArray; static LazyLogModule gDocumentLeakPRLog("DocumentLeak"); static LazyLogModule gCspPRLog("CSP"); #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.IsEmpty() && !mNameContentList && !mChangeCallbacks && !mImageElement; } Element* nsIdentifierMapEntry::GetIdElement() { return mIdContentList.SafeElementAt(0); } Element* nsIdentifierMapEntry::GetImageIdElement() { return mImageElement ? mImageElement.get() : GetIdElement(); } void nsIdentifierMapEntry::AppendAllIdContent(nsCOMArray* aElements) { for (size_t i = 0; i < mIdContentList.Length(); ++i) { aElements->AppendObject(mIdContentList[i]); } } void nsIdentifierMapEntry::AddContentChangeCallback(nsIDocument::IDTargetObserver aCallback, void* aData, bool aForImage) { if (!mChangeCallbacks) { mChangeCallbacks = new nsTHashtable; } 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; } } namespace mozilla { namespace dom { NS_IMPL_CYCLE_COLLECTION_CLASS(Registry) NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Registry) for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) { aCallbacks.Trace(&iter.UserData()->mPrototype, "mCustomDefinitions prototype", aClosure); } NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Registry) for (auto iter = tmp->mCustomDefinitions.Iter(); !iter.Done(); iter.Next()) { nsAutoPtr& callbacks = iter.UserData()->mCallbacks; if (callbacks->mAttributeChangedCallback.WasPassed()) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCustomDefinitions->mCallbacks->mAttributeChangedCallback"); cb.NoteXPCOMChild(callbacks->mAttributeChangedCallback.Value()); } if (callbacks->mCreatedCallback.WasPassed()) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCustomDefinitions->mCallbacks->mCreatedCallback"); cb.NoteXPCOMChild(callbacks->mCreatedCallback.Value()); } if (callbacks->mAttachedCallback.WasPassed()) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCustomDefinitions->mCallbacks->mAttachedCallback"); cb.NoteXPCOMChild(callbacks->mAttachedCallback.Value()); } if (callbacks->mDetachedCallback.WasPassed()) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCustomDefinitions->mCallbacks->mDetachedCallback"); cb.NoteXPCOMChild(callbacks->mDetachedCallback.Value()); } } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Registry) tmp->mCustomDefinitions.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; // The callback hasn't actually been invoked yet, but we need to flip // this now in order to enqueue the attached callback. This is a spec // bug (w3c bug 27437). mOwnerData->mCreatedCallbackInvoked = true; // If ELEMENT is in a document and this document has a browsing context, // enqueue attached callback for ELEMENT. nsIDocument* document = mThisObject->GetComposedDoc(); if (document && document->GetDocShell()) { document->EnqueueLifecycleCallback(nsIDocument::eAttached, mThisObject); } 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 void nsIdentifierMapEntry::FireChangeCallbacks(Element* aOldElement, Element* aNewElement, bool aImageOnly) { if (!mChangeCallbacks) return; for (auto iter = mChangeCallbacks->ConstIter(); !iter.Done(); iter.Next()) { nsIdentifierMapEntry::ChangeCallbackEntry* entry = iter.Get(); // 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 (entry->mKey.mForImage ? (mImageElement && !aImageOnly) : aImageOnly) { continue; } if (!entry->mKey.mCallback(aOldElement, aNewElement, entry->mKey.mData)) { iter.Remove(); } } } namespace { struct PositionComparator { Element* const mElement; explicit PositionComparator(Element* const aElement) : mElement(aElement) {} int operator()(void* aElement) const { Element* curElement = static_cast(aElement); if (mElement == curElement) { return 0; } if (nsContentUtils::PositionIsBefore(mElement, curElement)) { return -1; } return 1; } }; } // namespace bool nsIdentifierMapEntry::AddIdElement(Element* aElement) { NS_PRECONDITION(aElement, "Must have element"); NS_PRECONDITION(!mIdContentList.Contains(nullptr), "Why is null in our list?"); #ifdef DEBUG Element* currentElement = mIdContentList.SafeElementAt(0); #endif // Common case if (mIdContentList.IsEmpty()) { 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. size_t idx; if (BinarySearchIf(mIdContentList, 0, mIdContentList.Length(), PositionComparator(aElement), &idx)) { // 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 (!mIdContentList.InsertElementAt(idx, aElement)) { return false; } if (idx == 0) { Element* oldElement = 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()->IsHTMLDocument() || mIdContentList.Contains(aElement), "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 = mIdContentList.SafeElementAt(0); mIdContentList.RemoveElement(aElement); if (currentElement == aElement) { FireChangeCallbacks(currentElement, 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); } size_t nsIdentifierMapEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { return nsStringHashKey::SizeOfExcludingThis(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; }; /** * 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. */ RefPtr 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(); } return mLength; } CSSStyleSheet* nsDOMStyleSheetList::IndexedGetter(uint32_t aIndex, bool& aFound) { if (!mDocument || aIndex >= (uint32_t)mDocument->GetNumberOfStyleSheets()) { aFound = false; return nullptr; } aFound = true; CSSStyleSheet* 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, CSSStyleSheet* aStyleSheet, bool aDocumentSheet) { if (aDocumentSheet && -1 != mLength) { mLength++; } } void nsDOMStyleSheetList::StyleSheetRemoved(nsIDocument *aDocument, CSSStyleSheet* aStyleSheet, bool aDocumentSheet) { if (aDocumentSheet && -1 != mLength) { 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; } RefPtr 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; } void nsExternalResourceMap::EnumerateResources(nsIDocument::nsSubDocEnumFunc aCallback, void* aData) { for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) { nsExternalResourceMap::ExternalResource* resource = iter.UserData(); if (resource->mDocument && !aCallback(resource->mDocument, aData)) { break; } } } void nsExternalResourceMap::Traverse(nsCycleCollectionTraversalCallback* aCallback) const { // mPendingLoads will get cleared out as the requests complete, so // no need to worry about those here. for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) { nsExternalResourceMap::ExternalResource* resource = iter.UserData(); NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, "mExternalResourceMap.mMap entry" "->mDocument"); aCallback->NoteXPCOMChild(resource->mDocument); NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, "mExternalResourceMap.mMap entry" "->mViewer"); aCallback->NoteXPCOMChild(resource->mViewer); NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, "mExternalResourceMap.mMap entry" "->mLoadGroup"); aCallback->NoteXPCOMChild(resource->mLoadGroup); } } void nsExternalResourceMap::HideViewers() { for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) { nsCOMPtr viewer = iter.UserData()->mViewer; if (viewer) { viewer->Hide(); } } } void nsExternalResourceMap::ShowViewers() { for (auto iter = mMap.Iter(); !iter.Done(); iter.Next()) { nsCOMPtr viewer = iter.UserData()->mViewer; if (viewer) { viewer->Show(); } } } void TransferZoomLevels(nsIDocument* aFromDoc, nsIDocument* aToDoc) { MOZ_ASSERT(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) { MOZ_ASSERT(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"); RefPtr 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, 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"); nsCOMPtr loadGroup = aRequestingNode->OwnerDoc()->GetDocumentLoadGroup(); nsresult rv = NS_OK; nsCOMPtr channel; rv = NS_NewChannel(getter_AddRefs(channel), aURI, aRequestingNode, nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS, nsIContentPolicy::TYPE_OTHER, loadGroup); NS_ENSURE_SUCCESS(rv, rv); mURI = aURI; return channel->AsyncOpen2(this); } 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); \ 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 final : public DOMStringList { public: explicit nsDOMStyleSheetSetList(nsIDocument* aDocument); void Disconnect() { mDocument = nullptr; } virtual void EnsureFresh() 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() { MOZ_ASSERT(NS_IsMainThread()); 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++) { CSSStyleSheet* 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, "nsIDocument::SelectorCache") { } // 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 final : public nsRunnable { public: explicit SelectorCacheKeyDeleter(SelectorCacheKey* aToDelete) : mSelector(aToDelete) { MOZ_COUNT_CTOR(SelectorCacheKeyDeleter); } protected: ~SelectorCacheKeyDeleter() { MOZ_COUNT_DTOR(SelectorCacheKeyDeleter); } public: 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(FrameRequestCallback& aCallback, int32_t aHandle) : mCallback(&aCallback), mHandle(aHandle) {} // Conversion operator so that we can append these to a // FrameRequestCallbackList operator const RefPtr& () 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; } RefPtr mCallback; int32_t mHandle; }; static already_AddRefed nullNodeInfo; // ================================================================== // = // ================================================================== nsIDocument::nsIDocument() : nsINode(nullNodeInfo), mReferrerPolicySet(false), mReferrerPolicy(mozilla::net::RP_Default), mUpgradeInsecureRequests(false), mUpgradeInsecurePreloads(false), 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), mFontFaceSetDirty(true), mGetUserFontSetCalled(false), mPostedFlushUserFontSet(false), mDidFireDOMContentLoaded(true), mFrameRequestCallbacksScheduled(false), mLinksEnabled(true), mPartID(0), mUserHasInteracted(false) { SetInDocument(); PR_INIT_CLIST(&mDOMMediaQueryLists); } // 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)); if (gDocumentLeakPRLog) MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this)); // Start out mLastStyleSheetSet as null, per spec SetDOMStringToNull(mLastStyleSheetSet); // void state used to differentiate an empty source from an unselected source mPreloadPictureFoundSource.SetIsVoid(true); if (!sProcessingStack) { sProcessingStack.emplace(); // Add the base queue sentinel to the processing stack. sProcessingStack->AppendElement((CustomElementData*) nullptr); } } void nsDocument::ClearAllBoxObjects() { if (mBoxObjectTable) { for (auto iter = mBoxObjectTable->Iter(); !iter.Done(); iter.Next()) { nsPIBoxObject* boxObject = iter.UserData(); if (boxObject) { boxObject->Clear(); } } delete mBoxObjectTable; mBoxObjectTable = nullptr; } } nsIDocument::~nsIDocument() { MOZ_ASSERT(PR_CLIST_IS_EMPTY(&mDOMMediaQueryLists), "must not have media query lists left"); if (mNodeInfoManager) { mNodeInfoManager->DropDocumentReference(); } UnlinkOriginalDocumentIfStatic(); } bool nsDocument::IsAboutPage() { nsCOMPtr principal = GetPrincipal(); nsCOMPtr uri; principal->GetURI(getter_AddRefs(uri)); bool isAboutScheme = true; if (uri) { uri->SchemeIs("about", &isAboutScheme); } return isAboutScheme; } nsDocument::~nsDocument() { if (gDocumentLeakPRLog) MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p destroyed", this)); NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document"); // Note: This assert is only non-fatal because mochitest-bc triggers // it... as well as the preceding assert about !mIsShowing. NS_ASSERTION(!mObservingAppThemeChanged, "Document leaked to shutdown, then the observer service dropped " "its ref to us so we were able to go away."); if (IsTopLevelContentDocument()) { //don't report for about: pages if (!IsAboutPage()) { // 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); } } ReportUseCounters(); 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. delete mSubDocuments; mSubDocuments = nullptr; // Destroy link map now so we don't waste time removing // links one by one DestroyElementMaps(); nsAutoScriptBlocker scriptBlocker; for (uint32_t indx = mChildren.ChildCount(); indx-- != 0; ) { mChildren.ChildAt(indx)->UnbindFromTree(); mChildren.RemoveChildAt(indx); } mFirstChild = nullptr; mCachedRootElement = nullptr; // Let the stylesheets know we're going away for (CSSStyleSheet* sheet : mStyleSheets) { sheet->SetOwningDocument(nullptr); } if (mAttrStyleSheet) { mAttrStyleSheet->SetOwningDocument(nullptr); } // We don't own the mOnDemandBuiltInUASheets, so we don't need to reset them. if (mListenerManager) { mListenerManager->Disconnect(); UnsetFlags(NODE_HAS_LISTENERMANAGER); } if (mScriptLoader) { mScriptLoader->DropDocumentReference(); } if (mCSSLoader) { // Could be null here if Init() failed or if we have been unlinked. mCSSLoader->DropDocumentReference(); } if (mStyleImageLoader) { mStyleImageLoader->DropDocumentReference(); } delete mHeaderData; ClearAllBoxObjects(); mPendingTitleChangeEvent.Revoke(); // 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(); nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { os->RemoveObserver(this, "service-worker-get-client"); } } NS_INTERFACE_TABLE_HEAD(nsDocument) NS_WRAPPERCACHE_INTERFACE_TABLE_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_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(); } if (tmp->mExpandoAndGeneration.expando.isObject()) { JS::ExposeObjectToActiveJS( &(tmp->mExpandoAndGeneration.expando.toObject())); } 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 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; } for (auto iter = tmp->mIdentifierMap.ConstIter(); !iter.Done(); iter.Next()) { iter.Get()->Traverse(&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) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet) // 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) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMasterDocument) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImportManager) for (auto iter = tmp->mRadioGroups.Iter(); !iter.Done(); iter.Next()) { nsRadioGroupStruct* radioGroup = iter.UserData(); NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( cb, "mRadioGroups entry->mSelectedRadioButton"); cb.NoteXPCOMChild(ToSupports(radioGroup->mSelectedRadioButton)); uint32_t i, count = radioGroup->mRadioButtons.Count(); for (i = 0; i < count; ++i) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( cb, "mRadioGroups entry->mRadioButtons[i]"); cb.NoteXPCOMChild(radioGroup->mRadioButtons[i]); } } // 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) { for (auto iter = tmp->mBoxObjectTable->Iter(); !iter.Done(); iter.Next()) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mBoxObjectTable entry"); cb.NoteXPCOMChild(iter.UserData()); } } 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(mOrientationPendingPromise) 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(mDocumentTimeline) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRegistry) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents) // Traverse all our nsCOMArrays. NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnDemandBuiltInUASheets) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSubImportLinks) 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); } // Traverse animation components if (tmp->mAnimationController) { tmp->mAnimationController->Traverse(&cb); } if (tmp->mSubDocuments) { for (auto iter = tmp->mSubDocuments->Iter(); !iter.Done(); iter.Next()) { auto entry = static_cast(iter.Get()); 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); } } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCSSLoader) // We own only the items in mDOMMediaQueryLists that have listeners; // this reference is managed by their AddListener and RemoveListener // methods. for (PRCList *l = PR_LIST_HEAD(&tmp->mDOMMediaQueryLists); l != &tmp->mDOMMediaQueryLists; l = PR_NEXT_LINK(l)) { MediaQueryList *mql = static_cast(l); if (mql->HasListeners()) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item"); cb.NoteXPCOMChild(mql); } } 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; tmp->UnlinkOriginalDocumentIfStatic(); 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(mCachedEncoder) NS_IMPL_CYCLE_COLLECTION_UNLINK(mUndoManager) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker) NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner) NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection) NS_IMPL_CYCLE_COLLECTION_UNLINK(mRegistry) NS_IMPL_CYCLE_COLLECTION_UNLINK(mMasterDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise) NS_IMPL_CYCLE_COLLECTION_UNLINK(mImportManager) NS_IMPL_CYCLE_COLLECTION_UNLINK(mSubImportLinks) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet) tmp->mParentDocument = nullptr; NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages) tmp->ClearAllBoxObjects(); 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; } delete tmp->mSubDocuments; tmp->mSubDocuments = nullptr; tmp->mFrameRequestCallbacks.Clear(); MOZ_RELEASE_ASSERT(!tmp->mFrameRequestCallbacksScheduled, "How did we get here without our presshell going away " "first?"); 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->DropDocumentReference(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mCSSLoader) } // We own only the items in mDOMMediaQueryLists that have listeners; // this reference is managed by their AddListener and RemoveListener // methods. for (PRCList *l = PR_LIST_HEAD(&tmp->mDOMMediaQueryLists); l != &tmp->mDOMMediaQueryLists; ) { PRCList *next = PR_NEXT_LINK(l); MediaQueryList *mql = static_cast(l); mql->RemoveAllListeners(); l = next; } tmp->mInUnlinkOrDeletion = false; NS_IMPL_CYCLE_COLLECTION_UNLINK_END nsresult nsDocument::Init() { if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) { return NS_ERROR_ALREADY_INITIALIZED; } // 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); MOZ_ASSERT(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::NativeGlobal(xpc::PrivilegedJunkScope()); NS_ENSURE_TRUE(global, NS_ERROR_FAILURE); mScopeObject = do_GetWeakReference(global); MOZ_ASSERT(mScopeObject); mScriptLoader = new nsScriptLoader(this); mozilla::HoldJSObjects(this); nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { os->AddObserver(this, "service-worker-get-client", /* ownsWeak */ true); } 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::GetChannelResultPrincipal. // Note: this should match nsDocShell::OnLoadingSite NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); nsIScriptSecurityManager *securityManager = nsContentUtils::GetSecurityManager(); if (securityManager) { securityManager->GetChannelResultPrincipal(aChannel, getter_AddRefs(principal)); } } ResetToURI(uri, aLoadGroup, principal); // Note that, since mTiming does not change during a reset, the // navigationStart time remains unchanged and therefore any future new // timeline will have the same global clock time as the old one. if (mDocumentTimeline) { nsRefreshDriver* rd = mPresShell && mPresShell->GetPresContext() ? mPresShell->GetPresContext()->RefreshDriver() : nullptr; if (rd) { mDocumentTimeline->NotifyRefreshDriverDestroying(rd); } mDocumentTimeline = nullptr; } 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 = nullptr; } } mChannel = aChannel; } void nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup, nsIPrincipal* aPrincipal) { NS_PRECONDITION(aURI, "Null URI passed to ResetToURI"); if (gDocumentLeakPRLog && MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) { nsAutoCString spec; aURI->GetSpec(spec); PR_LogPrint("DOCUMENT %p ResetToURI %s", this, spec.get()); } mSecurityInfo = nullptr; mDocumentLoadGroup = nullptr; // Delete references to sub-documents and kill the subdocument map, // if any. It holds strong references delete 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; if (!mMasterDocument) { // "When creating an import, use the registry of the master document." // Note: at this point the mMasterDocument is already set for imports // (and only for imports) 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 = nullptr; // 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 loadContext(mDocumentContainer); if (!loadContext && aLoadGroup) { nsCOMPtr cbs; aLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs)); loadContext = do_GetInterface(cbs); } MOZ_ASSERT(loadContext, "must have a load context or pass in an explicit principal"); nsCOMPtr principal; nsresult rv = securityManager-> GetLoadContextCodebasePrincipal(mDocumentURI, loadContext, 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 for (CSSStyleSheet* sheet : Reversed(mStyleSheets)) { sheet->SetOwningDocument(nullptr); if (sheet->IsApplicable()) { nsCOMPtr shell = GetShell(); if (shell) { shell->StyleSet()->RemoveDocStyleSheet(sheet); } } // XXX Tell observers? } } void nsDocument::RemoveStyleSheetsFromStyleSets( nsTArray>& aSheets, SheetType aType) { // The stylesheets should forget us for (CSSStyleSheet* sheet : Reversed(aSheets)) { 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(mOnDemandBuiltInUASheets, SheetType::Agent); RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAgentSheet], SheetType::Agent); RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eUserSheet], SheetType::User); RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAuthorSheet], SheetType::Doc); nsStyleSheetService *sheetService = nsStyleSheetService::GetInstance(); if (sheetService) { RemoveStyleSheetsFromStyleSets(*sheetService->AuthorStyleSheets(), SheetType::Doc); } // Release all the sheets mStyleSheets.Clear(); mOnDemandBuiltInUASheets.Clear(); for (auto& sheets : mAdditionalSheets) { sheets.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(); } if (!mSVGAttrAnimationRuleProcessor) { mSVGAttrAnimationRuleProcessor = new mozilla::SVGAttrAnimationRuleProcessor(); } // Now set up our style sets nsCOMPtr shell = GetShell(); if (shell) { FillStyleSet(shell->StyleSet()); } } static void AppendSheetsToStyleSet(nsStyleSet* aStyleSet, const nsTArray>& aSheets, SheetType aType) { for (CSSStyleSheet* sheet : Reversed(aSheets)) { aStyleSet->AppendStyleSheet(aType, sheet); } } void nsDocument::FillStyleSet(nsStyleSet* aStyleSet) { NS_PRECONDITION(aStyleSet, "Must have a style set"); NS_PRECONDITION(aStyleSet->SheetCount(SheetType::Doc) == 0, "Style set already has document sheets?"); for (CSSStyleSheet* sheet : Reversed(mStyleSheets)) { if (sheet->IsApplicable()) { aStyleSet->AddDocStyleSheet(sheet, this); } } nsStyleSheetService *sheetService = nsStyleSheetService::GetInstance(); if (sheetService) { for (CSSStyleSheet* sheet : *sheetService->AuthorStyleSheets()) { aStyleSet->AppendStyleSheet(SheetType::Doc, sheet); } } // Iterate backwards to maintain order for (CSSStyleSheet* sheet : Reversed(mOnDemandBuiltInUASheets)) { if (sheet->IsApplicable()) { aStyleSet->PrependStyleSheet(SheetType::Agent, sheet); } } AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAgentSheet], SheetType::Agent); AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eUserSheet], SheetType::User); AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAuthorSheet], SheetType::Doc); } static void WarnIfSandboxIneffective(nsIDocShell* aDocShell, uint32_t aSandboxFlags, nsIChannel* aChannel) { // If the document is sandboxed (via the HTML5 iframe sandbox // attribute) and both the allow-scripts and allow-same-origin // keywords are supplied, the sandboxed document can call into its // parent document and remove its sandboxing entirely - we print a // warning to the web console in this case. if (aSandboxFlags & SANDBOXED_NAVIGATION && !(aSandboxFlags & SANDBOXED_SCRIPTS) && !(aSandboxFlags & SANDBOXED_ORIGIN)) { nsCOMPtr parentAsItem; aDocShell->GetSameTypeParent(getter_AddRefs(parentAsItem)); nsCOMPtr parentDocShell = do_QueryInterface(parentAsItem); if (!parentDocShell) { return; } // Don't warn if our parent is not the top-level document. nsCOMPtr grandParentAsItem; parentDocShell->GetSameTypeParent(getter_AddRefs(grandParentAsItem)); if (grandParentAsItem) { return; } nsCOMPtr parentChannel; parentDocShell->GetCurrentDocumentChannel(getter_AddRefs(parentChannel)); if (!parentChannel) { return; } nsresult rv = nsContentUtils::CheckSameOrigin(aChannel, parentChannel); if (NS_FAILED(rv)) { return; } nsCOMPtr parentDocument = do_GetInterface(parentDocShell); nsCOMPtr iframeUri; parentChannel->GetURI(getter_AddRefs(iframeUri)); nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Iframe Sandbox"), parentDocument, nsContentUtils::eSECURITY_PROPERTIES, "BothAllowScriptsAndSameOriginPresent", nullptr, 0, iframeUri); } } nsresult nsDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel, nsILoadGroup* aLoadGroup, nsISupports* aContainer, nsIStreamListener **aDocListener, bool aReset, nsIContentSink* aSink) { if (gDocumentLeakPRLog && MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) { nsCOMPtr uri; aChannel->GetURI(getter_AddRefs(uri)); nsAutoCString spec; if (uri) uri->GetSpec(spec); PR_LogPrint("DOCUMENT %p StartDocumentLoad %s", this, spec.get()); } MOZ_ASSERT(NodePrincipal()->GetAppId() != nsIScriptSecurityManager::UNKNOWN_APP_ID, "Document should never have UNKNOWN_APP_ID"); 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; 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); WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel()); } // The CSP directive upgrade-insecure-requests not only applies to the // toplevel document, but also to nested documents. Let's propagate that // flag from the parent to the nested document. nsCOMPtr treeItem = this->GetDocShell(); if (treeItem) { nsCOMPtr sameTypeParent; treeItem->GetSameTypeParent(getter_AddRefs(sameTypeParent)); if (sameTypeParent) { mUpgradeInsecureRequests = sameTypeParent->GetDocument()->GetUpgradeInsecureRequests(false); // if the parent document makes use of upgrade-insecure-requests // then subdocument preloads should always be upgraded. mUpgradeInsecurePreloads = mUpgradeInsecureRequests || sameTypeParent->GetDocument()->GetUpgradeInsecureRequests(true); } } // If this is not a data document, set CSP. if (!mLoadedAsData) { nsresult rv = InitCSP(aChannel); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } 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, bool aReportOnly) { // 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, aReportOnly, false); NS_ENSURE_SUCCESS(rv, rv); { MOZ_LOG(gCspPRLog, LogLevel::Debug, ("CSP refined with policy: \"%s\"", NS_ConvertUTF16toUTF8(policy).get())); } } return NS_OK; } bool nsDocument::IsLoopDocument(nsIChannel *aChannel) { nsCOMPtr chanURI; nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(chanURI)); NS_ENSURE_SUCCESS(rv, false); bool isAbout = false; bool isLoop = false; rv = chanURI->SchemeIs("about", &isAbout); NS_ENSURE_SUCCESS(rv, false); if (isAbout) { nsCOMPtr loopURI; rv = NS_NewURI(getter_AddRefs(loopURI), "about:loopconversation"); NS_ENSURE_SUCCESS(rv, false); rv = chanURI->EqualsExceptRef(loopURI, &isLoop); NS_ENSURE_SUCCESS(rv, false); if (!isLoop) { rv = NS_NewURI(getter_AddRefs(loopURI), "about:looppanel"); NS_ENSURE_SUCCESS(rv, false); rv = chanURI->EqualsExceptRef(loopURI, &isLoop); NS_ENSURE_SUCCESS(rv, false); } } return isLoop; } void nsDocument::ApplySettingsFromCSP(bool aSpeculative) { nsresult rv = NS_OK; if (!aSpeculative) { // 1) apply settings from regular CSP nsCOMPtr csp; rv = NodePrincipal()->GetCsp(getter_AddRefs(csp)); NS_ENSURE_SUCCESS_VOID(rv); if (csp) { // Set up any Referrer Policy specified by CSP bool hasReferrerPolicy = false; uint32_t referrerPolicy = mozilla::net::RP_Default; rv = csp->GetReferrerPolicy(&referrerPolicy, &hasReferrerPolicy); NS_ENSURE_SUCCESS_VOID(rv); if (hasReferrerPolicy) { mReferrerPolicy = static_cast(referrerPolicy); mReferrerPolicySet = true; } // Set up 'upgrade-insecure-requests' if not already inherited // from the parent context or set by any other CSP. if (!mUpgradeInsecureRequests) { rv = csp->GetUpgradeInsecureRequests(&mUpgradeInsecureRequests); NS_ENSURE_SUCCESS_VOID(rv); } if (!mUpgradeInsecurePreloads) { mUpgradeInsecurePreloads = mUpgradeInsecureRequests; } } return; } // 2) apply settings from speculative csp if (!mUpgradeInsecurePreloads) { nsCOMPtr preloadCsp; rv = NodePrincipal()->GetPreloadCsp(getter_AddRefs(preloadCsp)); NS_ENSURE_SUCCESS_VOID(rv); if (preloadCsp) { preloadCsp->GetUpgradeInsecureRequests(&mUpgradeInsecurePreloads); } } } nsresult nsDocument::InitCSP(nsIChannel* aChannel) { nsCOMPtr csp; if (!CSPService::sCSPEnabled) { MOZ_LOG(gCspPRLog, LogLevel::Debug, ("CSP is disabled, skipping CSP init for document %p", this)); return NS_OK; } nsAutoCString tCspHeaderValue, tCspROHeaderValue; nsCOMPtr httpChannel = do_QueryInterface(aChannel); if (httpChannel) { 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); // 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 = false; bool applyAppManifestCSP = false; nsAutoString appManifestCSP; nsAutoString appDefaultCSP; if (appStatus != nsIPrincipal::APP_STATUS_NOT_INSTALLED) { nsCOMPtr appsService = do_GetService(APPS_SERVICE_CONTRACTID); if (appsService) { uint32_t appId = principal->GetAppId(); appsService->GetManifestCSPByLocalId(appId, appManifestCSP); if (!appManifestCSP.IsEmpty()) { applyAppManifestCSP = true; } appsService->GetDefaultCSPByLocalId(appId, appDefaultCSP); if (!appDefaultCSP.IsEmpty()) { applyAppDefaultCSP = true; } } } // Check if this is part of the Loop/Hello service bool applyLoopCSP = IsLoopDocument(aChannel); // If there's no CSP to apply, go ahead and return early if (!applyAppDefaultCSP && !applyAppManifestCSP && !applyLoopCSP && cspHeaderValue.IsEmpty() && cspROHeaderValue.IsEmpty()) { if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) { nsCOMPtr chanURI; aChannel->GetURI(getter_AddRefs(chanURI)); nsAutoCString aspec; chanURI->GetAsciiSpec(aspec); MOZ_LOG(gCspPRLog, LogLevel::Debug, ("no CSP for document, %s, %s", aspec.get(), applyAppDefaultCSP ? "is app" : "not an app")); } return NS_OK; } MOZ_LOG(gCspPRLog, LogLevel::Debug, ("Document is an app or CSP header specified %p", this)); 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) { MOZ_LOG(gCspPRLog, LogLevel::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.")); return NS_OK; } } csp = do_CreateInstance("@mozilla.org/cspcontext;1", &rv); if (NS_FAILED(rv)) { MOZ_LOG(gCspPRLog, LogLevel::Debug, ("Failed to create CSP object: %x", rv)); 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(this, nullptr); // ----- if the doc is an app and we want a default CSP, apply it. if (applyAppDefaultCSP) { csp->AppendPolicy(appDefaultCSP, false, false); } // ----- if the doc is an app and specifies a CSP in its manifest, apply it. if (applyAppManifestCSP) { csp->AppendPolicy(appManifestCSP, false, false); } // ----- if the doc is part of Loop, apply the loop CSP if (applyLoopCSP) { nsAdoptingString loopCSP; loopCSP = Preferences::GetString("loop.CSP"); NS_ASSERTION(loopCSP, "Missing loop.CSP preference"); // If the pref has been removed, we continue without setting a CSP if (loopCSP) { csp->AppendPolicy(loopCSP, false, false); } } // ----- if there's a full-strength CSP header, apply it. if (!cspHeaderValue.IsEmpty()) { rv = AppendCSPFromHeader(csp, cspHeaderValue, false); NS_ENSURE_SUCCESS(rv, rv); } // ----- if there's a report-only CSP header, apply it. if (!cspROHeaderValue.IsEmpty()) { rv = AppendCSPFromHeader(csp, cspROHeaderValue, true); NS_ENSURE_SUCCESS(rv, rv); } // XXX. When we implement CSP sandbox (bug 671389), we need to also // add bugs 1073952 and 1377426 to avoid a nasty hole. // ----- 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); if (NS_FAILED(rv) || !safeAncestry) { MOZ_LOG(gCspPRLog, LogLevel::Debug, ("CSP doesn't like frame's ancestry, not loading.")); // stop! ERROR page! aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION); } } rv = principal->SetCsp(csp); NS_ENSURE_SUCCESS(rv, rv); MOZ_LOG(gCspPRLog, LogLevel::Debug, ("Inserted CSP into principal %p", principal)); ApplySettingsFromCSP(false); 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; } static void GetFormattedTimeString(PRTime aTime, nsAString& aFormattedTimeString) { PRExplodedTime prtime; PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime); // "MM/DD/YYYY hh:mm:ss" char formatedTime[24]; if (PR_snprintf(formatedTime, sizeof(formatedTime), "%02ld/%02ld/%04hd %02ld:%02ld:%02ld", prtime.tm_month + 1, prtime.tm_mday, prtime.tm_year, prtime.tm_hour , prtime.tm_min, prtime.tm_sec)) { CopyASCIItoUTF16(nsDependentCString(formatedTime), aFormattedTimeString); } 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. aFormattedTimeString.AssignLiteral(MOZ_UTF16("01/01/1970 00:00:00")); } } void nsIDocument::GetLastModified(nsAString& aLastModified) const { if (!mLastModified.IsEmpty()) { aLastModified.Assign(mLastModified); } else { GetFormattedTimeString(PR_Now(), aLastModified); } } 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); } RefPtr undoManager = mUndoManager; return undoManager.forget(); } bool nsDocument::IsWebAnimationsEnabled(JSContext* /*unused*/, JSObject* /*unused*/) { MOZ_ASSERT(NS_IsMainThread()); return nsContentUtils::IsCallerChrome() || Preferences::GetBool("dom.animations-api.core.enabled"); } DocumentTimeline* nsDocument::Timeline() { if (!mDocumentTimeline) { mDocumentTimeline = new DocumentTimeline(this); } return mDocumentTimeline; } /* 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.StealNSResult(); } 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; } nsCOMPtr piWindow = do_QueryInterface(focusedWindow); MOZ_ASSERT(piWindow); // Are we an ancestor of the focused DOMWindow? for (nsIDocument* currentDoc = piWindow->GetDoc(); 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. RefPtr 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); } void nsIDocument::ElementsFromPoint(float aX, float aY, nsTArray>& aElements) { ElementsFromPointHelper(aX, aY, nsIDocument::FLUSH_LAYOUT, aElements); } Element* nsDocument::ElementFromPointHelper(float aX, float aY, bool aIgnoreRootScrollFrame, bool aFlushLayout) { nsAutoTArray, 1> elementArray; ElementsFromPointHelper(aX, aY, ((aIgnoreRootScrollFrame ? nsIDocument::IGNORE_ROOT_SCROLL_FRAME : 0) | (aFlushLayout ? nsIDocument::FLUSH_LAYOUT : 0) | nsIDocument::IS_ELEMENT_FROM_POINT), elementArray); if (elementArray.IsEmpty()) { return nullptr; } return elementArray[0]; } void nsDocument::ElementsFromPointHelper(float aX, float aY, uint32_t aFlags, nsTArray>& aElements) { // As per the the spec, we return null if either coord is negative if (!(aFlags & nsIDocument::IGNORE_ROOT_SCROLL_FRAME) && (aX < 0 || aY < 0)) { return; } 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 (aFlags & nsIDocument::FLUSH_LAYOUT) { FlushPendingNotifications(Flush_Layout); } nsIPresShell *ps = GetShell(); if (!ps) { return; } nsIFrame *rootFrame = ps->GetRootFrame(); // XUL docs, unlike HTML, have no frame tree until everything's done loading if (!rootFrame) { return; // return null to premature XUL callers as a reminder to wait } nsTArray outFrames; // Emulate what GetFrameAtPoint does, since we want all the frames under our // point. nsLayoutUtils::GetFramesForArea(rootFrame, nsRect(pt, nsSize(1, 1)), outFrames, nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC | ((aFlags & nsIDocument::IGNORE_ROOT_SCROLL_FRAME) ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0)); // Dunno when this would ever happen, as we should at least have a root frame under us? if (outFrames.IsEmpty()) { return; } // 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()) { // If this helper is called via ElementsFromPoint, we need to make sure // our frame is an element. Otherwise return whatever the top frame is // even if it isn't the top-painted element. if (!(aFlags & nsIDocument::IS_ELEMENT_FROM_POINT)) { continue; } node = node->GetParent(); } if (node && node != lastAdded) { aElements.AppendElement(node->AsElement()); lastAdded = node; // If this helper is called via ElementFromPoint, just return the first // element we find. if (aFlags & nsIDocument::IS_ELEMENT_FROM_POINT) { return; } } } } 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; } } // Check if CSP allows this base-uri nsCOMPtr csp; nsresult rv = NodePrincipal()->GetCsp(getter_AddRefs(csp)); NS_ENSURE_SUCCESS(rv, rv); if (csp && aURI) { bool permitsBaseURI = false; // base-uri is only enforced if explicitly defined in the // policy - do *not* consult default-src, see: // http://www.w3.org/TR/CSP2/#directive-default-src rv = csp->Permits(aURI, nsIContentSecurityPolicy::BASE_URI_DIRECTIVE, true, &permitsBaseURI); NS_ENSURE_SUCCESS(rv, rv); if (!permitsBaseURI) { 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)) { if (mMasterDocument && !aCharSetID.EqualsLiteral("UTF-8")) { // Imports are always UTF-8 return; } 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; } // Referrer policy spec says to ignore any empty referrer policies. if (aHeaderField == nsGkAtoms::referrer && !aData.IsEmpty()) { ReferrerPolicy policy = mozilla::net::ReferrerPolicyFromString(aData); // Referrer policy spec (section 6.1) says that we always use the newest // referrer policy we find mReferrerPolicy = policy; mReferrerPolicySet = true; } } 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); RefPtr 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(); UpdateFrameRequestCallbackSchedulingState(); // Now that we have a shell, we might have @font-face rules. RebuildUserFontSet(); return shell.forget(); } void nsIDocument::UpdateFrameRequestCallbackSchedulingState(nsIPresShell* aOldShell) { // If the condition for shouldBeScheduled changes to depend on some other // variable, add UpdateFrameRequestCallbackSchedulingState() calls to the // places where that variable can change. bool shouldBeScheduled = mPresShell && IsEventHandlingEnabled() && !AnimationsPaused() && !mFrameRequestCallbacks.IsEmpty(); if (shouldBeScheduled == mFrameRequestCallbacksScheduled) { // nothing to do return; } nsIPresShell* presShell = aOldShell ? aOldShell : mPresShell; MOZ_RELEASE_ASSERT(presShell); nsRefreshDriver* rd = presShell->GetPresContext()->RefreshDriver(); if (shouldBeScheduled) { rd->ScheduleFrameRequestCallbacks(this); } else { rd->RevokeFrameRequestCallbacks(this); } mFrameRequestCallbacksScheduled = shouldBeScheduled; } void nsIDocument::TakeFrameRequestCallbacks(FrameRequestCallbackList& aCallbacks) { aCallbacks.AppendElements(mFrameRequestCallbacks); mFrameRequestCallbacks.Clear(); // No need to manually remove ourselves from the refresh driver; it will // handle that part. But we do have to update our state. mFrameRequestCallbacksScheduled = false; } bool nsIDocument::ShouldThrottleFrameRequests() { if (mStaticCloneCount > 0) { // Even if we're not visible, a static clone may be, so run at full speed. return false; } if (Hidden()) { // We're not visible (probably in a background tab or the bf cache). return true; } if (!mPresShell) { return false; // Can't do anything smarter. } nsIFrame* frame = mPresShell->GetRootFrame(); if (!frame) { return false; // Can't do anything smarter. } nsIFrame* displayRootFrame = nsLayoutUtils::GetDisplayRootFrame(frame); if (!displayRootFrame) { return false; // Can't do anything smarter. } if (!displayRootFrame->DidPaintPresShell(mPresShell)) { // We didn't get painted during the last paint, so we're not visible. // Throttle. Note that because we have to paint this document at least // once to unthrottle it, we will drop one requestAnimationFrame frame // when a document that previously wasn't visible scrolls into view. This // is acceptable since it would happen outside the viewport on APZ // platforms and is unlikely to be human-perceivable on non-APZ platforms. return true; } // We got painted during the last paint, so run at full speed. return false; } void nsDocument::DeleteShell() { mExternalResourceMap.HideViewers(); if (nsPresContext* presContext = mPresShell->GetPresContext()) { presContext->RefreshDriver()->CancelPendingEvents(this); } // 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. for (auto iter = mImageTracker.Iter(); !iter.Done(); iter.Next()) { iter.Key()->RequestDiscard(); } // Now that we no longer have a shell, we need to forget about any FontFace // objects for @font-face rules that came from the style set. RebuildUserFontSet(); nsIPresShell* oldShell = mPresShell; mPresShell = nullptr; UpdateFrameRequestCallbackSchedulingState(oldShell); } 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 void SubDocInitEntry(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; } nsresult nsDocument::SetSubDocumentFor(Element* aElement, nsIDocument* aSubDoc) { NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED); if (!aSubDoc) { // aSubDoc is nullptr, remove the mapping if (mSubDocuments) { mSubDocuments->Remove(aElement); } } else { if (!mSubDocuments) { // Create a new hashtable static const PLDHashTableOps hash_table_ops = { PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub, PLDHashTable::MoveEntryStub, SubDocClearEntry, SubDocInitEntry }; mSubDocuments = new PLDHashTable(&hash_table_ops, sizeof(SubDocMapEntry)); } // Add a mapping to the hash table auto entry = static_cast(mSubDocuments->Add(aElement, fallible)); 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()) { auto entry = static_cast (mSubDocuments->Search(aContent->AsElement())); if (entry) { return entry->mSubDocument; } } return nullptr; } Element* nsDocument::FindContentForSubDocument(nsIDocument *aDocument) const { NS_ENSURE_TRUE(aDocument, nullptr); if (!mSubDocuments) { return nullptr; } for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) { auto entry = static_cast(iter.Get()); if (entry->mSubDocument == aDocument) { return entry->mKey; } } return nullptr; } 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); } 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; } void nsDocument::EnsureOnDemandBuiltInUASheet(CSSStyleSheet* aSheet) { if (mOnDemandBuiltInUASheets.Contains(aSheet)) { return; } BeginUpdate(UPDATE_STYLE); AddOnDemandBuiltInUASheet(aSheet); EndUpdate(UPDATE_STYLE); } void nsDocument::AddOnDemandBuiltInUASheet(CSSStyleSheet* aSheet) { MOZ_ASSERT(!mOnDemandBuiltInUASheets.Contains(aSheet)); // Prepend here so that we store the sheets in mOnDemandBuiltInUASheets in // the same order that they should end up in the style set. mOnDemandBuiltInUASheets.InsertElementAt(0, aSheet); if (aSheet->IsApplicable()) { // This is like |AddStyleSheetToStyleSets|, but for an agent sheet. nsCOMPtr shell = GetShell(); if (shell) { // Note that prepending here is necessary to make sure that html.css etc. // do not override Firefox OS/Mobile's content.css sheet. Maybe we should // have an insertion point to match the order of // nsDocumentViewer::CreateStyleSet though? shell->StyleSet()->PrependStyleSheet(SheetType::Agent, aSheet); } } NotifyStyleSheetAdded(aSheet, false); } int32_t nsDocument::GetNumberOfStyleSheets() const { return mStyleSheets.Length(); } CSSStyleSheet* nsDocument::GetStyleSheetAt(int32_t aIndex) const { return mStyleSheets.SafeElementAt(aIndex, nullptr); } int32_t nsDocument::GetIndexOfStyleSheet(CSSStyleSheet* aSheet) const { return mStyleSheets.IndexOf(aSheet); } void nsDocument::AddStyleSheetToStyleSets(CSSStyleSheet* aSheet) { nsCOMPtr shell = GetShell(); if (shell) { shell->StyleSet()->AddDocStyleSheet(aSheet, this); } } #define DO_STYLESHEET_NOTIFICATION(className, type, memberName, argName) \ do { \ className##Init init; \ init.mBubbles = true; \ init.mCancelable = true; \ init.mStylesheet = aSheet; \ init.memberName = argName; \ \ RefPtr event = \ className::Constructor(this, NS_LITERAL_STRING(type), init); \ event->SetTrusted(true); \ event->SetTarget(this); \ RefPtr asyncDispatcher = \ new AsyncEventDispatcher(this, event); \ asyncDispatcher->mOnlyChromeDispatch = true; \ asyncDispatcher->PostDOMEvent(); \ } while (0); void nsDocument::NotifyStyleSheetAdded(CSSStyleSheet* aSheet, bool aDocumentSheet) { NS_DOCUMENT_NOTIFY_OBSERVERS(StyleSheetAdded, (this, aSheet, aDocumentSheet)); if (StyleSheetChangeEventsEnabled()) { DO_STYLESHEET_NOTIFICATION(StyleSheetChangeEvent, "StyleSheetAdded", mDocumentSheet, aDocumentSheet); } } void nsDocument::NotifyStyleSheetRemoved(CSSStyleSheet* aSheet, bool aDocumentSheet) { NS_DOCUMENT_NOTIFY_OBSERVERS(StyleSheetRemoved, (this, aSheet, aDocumentSheet)); if (StyleSheetChangeEventsEnabled()) { DO_STYLESHEET_NOTIFICATION(StyleSheetChangeEvent, "StyleSheetRemoved", mDocumentSheet, aDocumentSheet); } } void nsDocument::AddStyleSheet(CSSStyleSheet* aSheet) { NS_PRECONDITION(aSheet, "null arg"); mStyleSheets.AppendElement(aSheet); aSheet->SetOwningDocument(this); if (aSheet->IsApplicable()) { AddStyleSheetToStyleSets(aSheet); } NotifyStyleSheetAdded(aSheet, true); } void nsDocument::RemoveStyleSheetFromStyleSets(CSSStyleSheet* aSheet) { nsCOMPtr shell = GetShell(); if (shell) { shell->StyleSet()->RemoveDocStyleSheet(aSheet); } } void nsDocument::RemoveStyleSheet(CSSStyleSheet* aSheet) { NS_PRECONDITION(aSheet, "null arg"); RefPtr sheet = aSheet; // hold ref so it won't die too soon if (!mStyleSheets.RemoveElement(aSheet)) { NS_ASSERTION(mInUnlinkOrDeletion, "stylesheet not found"); return; } if (!mIsGoingAway) { if (aSheet->IsApplicable()) { RemoveStyleSheetFromStyleSets(aSheet); } NotifyStyleSheetRemoved(aSheet, true); } aSheet->SetOwningDocument(nullptr); } void nsDocument::UpdateStyleSheets(nsTArray>& aOldSheets, nsTArray>& aNewSheets) { BeginUpdate(UPDATE_STYLE); // XXX Need to set the sheet on the ownernode, if any NS_PRECONDITION(aOldSheets.Length() == aNewSheets.Length(), "The lists must be the same length!"); int32_t count = aOldSheets.Length(); RefPtr 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. CSSStyleSheet* newSheet = aNewSheets[i]; if (newSheet) { mStyleSheets.InsertElementAt(oldIndex, newSheet); newSheet->SetOwningDocument(this); if (newSheet->IsApplicable()) { AddStyleSheetToStyleSets(newSheet); } NotifyStyleSheetAdded(newSheet, true); } } EndUpdate(UPDATE_STYLE); } void nsDocument::InsertStyleSheetAt(CSSStyleSheet* aSheet, int32_t aIndex) { NS_PRECONDITION(aSheet, "null ptr"); mStyleSheets.InsertElementAt(aIndex, aSheet); aSheet->SetOwningDocument(this); if (aSheet->IsApplicable()) { AddStyleSheetToStyleSets(aSheet); } NotifyStyleSheetAdded(aSheet, true); } void nsDocument::SetStyleSheetApplicableState(CSSStyleSheet* aSheet, bool aApplicable) { NS_PRECONDITION(aSheet, "null arg"); // If we're actually in the document style sheet list if (mStyleSheets.IndexOf(aSheet) != mStyleSheets.NoIndex) { 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(StyleSheetApplicableStateChangeEvent, "StyleSheetApplicableStateChanged", mApplicable, aApplicable); } if (!mSSApplicableStateNotificationPending) { nsCOMPtr 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); } } static SheetType ConvertAdditionalSheetType(nsIDocument::additionalSheetType aType) { switch(aType) { case nsIDocument::eAgentSheet: return SheetType::Agent; case nsIDocument::eUserSheet: return SheetType::User; case nsIDocument::eAuthorSheet: return SheetType::Doc; default: MOZ_ASSERT(false, "wrong type"); // we must return something although this should never happen return SheetType::Count; } } static int32_t FindSheet(const nsTArray>& aSheets, nsIURI* aSheetURI) { for (int32_t i = aSheets.Length() - 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. RefPtr loader = new css::Loader(); css::SheetParsingMode parsingMode; switch (aType) { case nsIDocument::eAgentSheet: parsingMode = css::eAgentSheetFeatures; break; case nsIDocument::eUserSheet: parsingMode = css::eUserSheetFeatures; break; case nsIDocument::eAuthorSheet: parsingMode = css::eAuthorSheetFeatures; break; default: MOZ_CRASH("impossible value for aType"); } RefPtr sheet; nsresult rv = loader->LoadSheetSync(aSheetURI, parsingMode, true, getter_AddRefs(sheet)); NS_ENSURE_SUCCESS(rv, rv); sheet->SetOwningDocument(this); MOZ_ASSERT(sheet->IsApplicable()); return AddAdditionalStyleSheet(aType, sheet); } nsresult nsDocument::AddAdditionalStyleSheet(additionalSheetType aType, CSSStyleSheet* aSheet) { if (mAdditionalSheets[aType].Contains(aSheet)) return NS_ERROR_INVALID_ARG; if (!aSheet->IsApplicable()) return NS_ERROR_INVALID_ARG; mAdditionalSheets[aType].AppendElement(aSheet); BeginUpdate(UPDATE_STYLE); nsCOMPtr shell = GetShell(); if (shell) { SheetType type = ConvertAdditionalSheetType(aType); shell->StyleSet()->AppendStyleSheet(type, aSheet); } // Passing false, so documet.styleSheets.length will not be affected by // these additional sheets. NotifyStyleSheetAdded(aSheet, false); EndUpdate(UPDATE_STYLE); return NS_OK; } void nsDocument::RemoveAdditionalStyleSheet(additionalSheetType aType, nsIURI* aSheetURI) { MOZ_ASSERT(aSheetURI); nsTArray>& sheets = mAdditionalSheets[aType]; int32_t i = FindSheet(mAdditionalSheets[aType], aSheetURI); if (i >= 0) { RefPtr sheetRef = sheets[i]; sheets.RemoveElementAt(i); BeginUpdate(UPDATE_STYLE); if (!mIsGoingAway) { MOZ_ASSERT(sheetRef->IsApplicable()); nsCOMPtr shell = GetShell(); if (shell) { 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); } } CSSStyleSheet* nsDocument::FirstAdditionalAuthorSheet() { return mAdditionalSheets[eAuthorSheet].SafeElementAt(0, nullptr); } nsIGlobalObject* nsDocument::GetScopeObject() const { nsCOMPtr scope(do_QueryReferent(mScopeObject)); return scope; } void nsDocument::SetScopeObject(nsIGlobalObject* aGlobal) { mScopeObject = do_GetWeakReference(aGlobal); if (aGlobal) { mHasHadScriptHandlingObject = true; } } #ifdef MOZ_EME static void CheckIfContainsEMEContent(nsISupports* aSupports, void* aContainsEME) { nsCOMPtr domMediaElem(do_QueryInterface(aSupports)); if (domMediaElem) { nsCOMPtr content(do_QueryInterface(domMediaElem)); MOZ_ASSERT(content, "aSupports is not a content"); HTMLMediaElement* mediaElem = static_cast(content.get()); bool* contains = static_cast(aContainsEME); if (mediaElem->GetMediaKeys()) { *contains = true; } } } bool nsDocument::ContainsEMEContent() { bool containsEME = false; EnumerateActivityObservers(CheckIfContainsEMEContent, static_cast(&containsEME)); return containsEME; } #endif // MOZ_EME static void CheckIfContainsMSEContent(nsISupports* aSupports, void* aContainsMSE) { nsCOMPtr domMediaElem(do_QueryInterface(aSupports)); if (domMediaElem) { nsCOMPtr content(do_QueryInterface(domMediaElem)); MOZ_ASSERT(content, "aSupports is not a content"); HTMLMediaElement* mediaElem = static_cast(content.get()); bool* contains = static_cast(aContainsMSE); RefPtr ms = mediaElem->GetMozMediaSourceObject(); if (ms) { *contains = true; } } } bool nsDocument::ContainsMSEContent() { bool containsMSE = false; EnumerateActivityObservers(CheckIfContainsMSEContent, static_cast(&containsMSE)); return containsMSE; } static void NotifyActivityChanged(nsISupports *aSupports, void *aUnused) { nsCOMPtr domMediaElem(do_QueryInterface(aSupports)); if (domMediaElem) { nsCOMPtr content(do_QueryInterface(domMediaElem)); MOZ_ASSERT(content, "aSupports is not a content"); HTMLMediaElement* mediaElem = static_cast(content.get()); mediaElem->NotifyOwnerDocumentActivityChanged(); } nsCOMPtr objectLoadingContent(do_QueryInterface(aSupports)); if (objectLoadingContent) { nsObjectLoadingContent* olc = static_cast(objectLoadingContent.get()); olc->NotifyOwnerDocumentActivityChanged(); } nsCOMPtr objectDocumentActivity(do_QueryInterface(aSupports)); if (objectDocumentActivity) { objectDocumentActivity->NotifyOwnerDocumentActivityChanged(); } } void nsIDocument::SetContainer(nsDocShell* aContainer) { if (aContainer) { mDocumentContainer = aContainer; } else { mDocumentContainer = WeakPtr(); } EnumerateActivityObservers(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); } static_cast(this)->SetIsContentDocument(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 MOZ_ASSERT(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(); // 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); } } } // BlockOnload() might be called before mScriptGlobalObject is set. // We may need to add the blocker once mScriptGlobalObject is set. bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject; mScriptGlobalObject = aScriptGlobalObject; if (needOnloadBlocker) { EnsureOnloadBlocker(); } UpdateFrameRequestCallbackSchedulingState(); 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; } } 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. nsCOMPtr csp; NodePrincipal()->GetCsp(getter_AddRefs(csp)); if (csp) { static_cast(csp.get())->flushConsoleMessages(); } 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. dom::VisibilityState oldState = mVisibilityState; mVisibilityState = GetVisibilityState(); // When the visibility is changed, notify it to observers. // Some observers need the notification, for example HTMLMediaElement uses // it to update internal media resource allocation. // When video is loaded via VideoDocument, HTMLMediaElement and MediaDecoder // creation are already done before nsDocument::SetScriptGlobalObject() call. // MediaDecoder decides whether starting decoding is decided based on // document's visibility. When the MediaDecoder is created, // nsDocument::SetScriptGlobalObject() is not yet called and document is // hidden state. Therefore the MediaDecoder decides that decoding is // not yet necessary. But soon after nsDocument::SetScriptGlobalObject() // call, the document becomes not hidden. At the time, MediaDecoder needs // to know it and needs to start updating decoding. if (oldState != mVisibilityState) { EnumerateActivityObservers(NotifyActivityChanged, nullptr); } // The global in the template contents owner document should be the same. if (mTemplateContentsOwner && mTemplateContentsOwner != this) { mTemplateContentsOwner->SetScriptGlobalObject(aScriptGlobalObject); } if (!mMaybeServiceWorkerControlled && mDocumentContainer && mScriptGlobalObject && GetChannel()) { nsCOMPtr docShell(mDocumentContainer); uint32_t loadType; docShell->GetLoadType(&loadType); // If we are shift-reloaded, don't associate with a ServiceWorker. if (IsForceReloadType(loadType)) { NS_WARNING("Page was shift reloaded, skipping ServiceWorker control"); return; } nsCOMPtr swm = mozilla::services::GetServiceWorkerManager(); if (swm) { nsAutoString documentId; static_cast(docShell.get())->GetInterceptedDocumentId(documentId); swm->MaybeStartControlling(this, documentId); mMaybeServiceWorkerControlled = true; } } } 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!"); #ifdef DEBUG nsCOMPtr win = do_QueryInterface(aScriptObject); NS_ASSERTION(!win || win->IsInnerWindow(), "Should have inner window here!"); #endif if (aScriptObject) { mScopeObject = do_GetWeakReference(aScriptObject); mHasHadScriptHandlingObject = true; mHasHadDefaultView = false; } } bool nsDocument::IsTopLevelContentDocument() { return mIsTopLevelContentDocument; } void nsDocument::SetIsTopLevelContentDocument(bool aIsTopLevelContentDocument) { mIsTopLevelContentDocument = aIsTopLevelContentDocument; } bool nsDocument::IsContentDocument() const { return mIsContentDocument; } void nsDocument::SetIsContentDocument(bool aIsContentDocument) { mIsContentDocument = aIsContentDocument; } 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) { // The docshell returns the outer window we are done. nsCOMPtr kungfuDeathGrip(mDocumentContainer); if (kungfuDeathGrip) { win = kungfuDeathGrip->GetWindow(); } } 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 nsTArray* 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(entry); } } } 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(); // DOM manipulation after content loaded should not care if the element // came from the preloader. mPreloadedPreconnects.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, false); RefPtr timelines = TimelineConsumers::Get(); nsIDocShell* docShell = this->GetDocShell(); if (timelines && timelines->HasConsumer(docShell)) { timelines->AddMarkerForDocShell(docShell, MakeUnique("document::DOMContentLoaded")); } 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) { RefPtr 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); } if (mMaybeServiceWorkerControlled) { using mozilla::dom::workers::ServiceWorkerManager; RefPtr swm = ServiceWorkerManager::GetInstance(); if (swm) { swm->MaybeCheckNavigationUpdate(this); } } 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) { nsCOMPtr 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(CSSStyleSheet* aSheet, css::Rule* aStyleRule) { NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleChanged, (this, aSheet, aStyleRule)); if (StyleSheetChangeEventsEnabled()) { DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent, "StyleRuleChanged", mRule, aStyleRule ? aStyleRule->GetDOMRule() : nullptr); } } void nsDocument::StyleRuleAdded(CSSStyleSheet* aSheet, css::Rule* aStyleRule) { NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleAdded, (this, aSheet, aStyleRule)); if (StyleSheetChangeEventsEnabled()) { DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent, "StyleRuleAdded", mRule, aStyleRule ? aStyleRule->GetDOMRule() : nullptr); } } void nsDocument::StyleRuleRemoved(CSSStyleSheet* aSheet, css::Rule* aStyleRule) { NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleRemoved, (this, aSheet, aStyleRule)); if (StyleSheetChangeEventsEnabled()) { DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent, "StyleRuleRemoved", mRule, aStyleRule ? aStyleRule->GetDOMRule() : nullptr); } } #undef DO_STYLESHEET_NOTIFICATION already_AddRefed nsIDocument::InsertAnonymousContent(Element& aElement, ErrorResult& aRv) { nsIPresShell* shell = GetShell(); if (!shell || !shell->GetCanvasFrame()) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsAutoScriptBlocker scriptBlocker; nsCOMPtr container = shell->GetCanvasFrame() ->GetCustomContentContainer(); if (!container) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } // Clone the node to avoid returning a direct reference nsCOMPtr clonedElement = aElement.CloneNode(true, aRv); if (aRv.Failed()) { return nullptr; } // Insert the element into the container nsresult rv; rv = container->AppendChildTo(clonedElement->AsContent(), true); if (NS_FAILED(rv)) { return nullptr; } RefPtr anonymousContent = new AnonymousContent(clonedElement->AsElement()); mAnonymousContents.AppendElement(anonymousContent); shell->GetCanvasFrame()->ShowCustomContentContainer(); return anonymousContent.forget(); } void nsIDocument::RemoveAnonymousContent(AnonymousContent& aContent, ErrorResult& aRv) { nsIPresShell* shell = GetShell(); if (!shell || !shell->GetCanvasFrame()) { aRv.Throw(NS_ERROR_UNEXPECTED); return; } nsAutoScriptBlocker scriptBlocker; nsCOMPtr container = shell->GetCanvasFrame() ->GetCustomContentContainer(); if (!container) { aRv.Throw(NS_ERROR_UNEXPECTED); return; } // Iterate over mAnonymousContents to find and remove the given node. for (size_t i = 0, len = mAnonymousContents.Length(); i < len; ++i) { if (mAnonymousContents[i] == &aContent) { // Get the node from the customContent nsCOMPtr node = aContent.GetContentNode(); // Remove the entry in mAnonymousContents mAnonymousContents.RemoveElementAt(i); // Remove the node from its container container->RemoveChild(*node, aRv); if (aRv.Failed()) { return; } break; } } if (mAnonymousContents.IsEmpty()) { shell->GetCanvasFrame()->HideCustomContentContainer(); } } // // 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.StealNSResult(); } 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.StealNSResult()); 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 = IsHTMLDocument() && !IsLowercaseASCII(aTagName); nsAutoString lcTagName; if (needsLowercase) { nsContentUtils::ASCIIToLower(aTagName, lcTagName); } return CreateElem(needsLowercase ? lcTagName : aTagName, nullptr, mDefaultElementType); } void nsDocument::SetupCustomElement(Element* aElement, uint32_t aNamespaceID, const nsAString* aTypeExtension) { if (!mRegistry) { return; } nsCOMPtr tagAtom = aElement->NodeInfo()->NameAtom(); nsCOMPtr typeAtom = aTypeExtension ? do_GetAtom(*aTypeExtension) : tagAtom; if (aTypeExtension && !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::is)) { // Custom element setup in the parser happens after the "is" // attribute is added. aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::is, *aTypeExtension, true); } 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 enqueue callback or adjust // the "is" attribute, 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; } // 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) { RefPtr elem = nsIDocument::CreateElement(aTagName, rv); if (rv.Failed()) { return nullptr; } if (!aTagName.Equals(aTypeExtension)) { // Custom element type can not extend itself. SetupCustomElement(elem, GetDefaultNamespaceID(), &aTypeExtension); } 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.StealNSResult()); return CallQueryInterface(element, aReturn); } already_AddRefed nsIDocument::CreateElementNS(const nsAString& aNamespaceURI, const nsAString& aQualifiedName, ErrorResult& rv) { RefPtr 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) { RefPtr 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; } } if (!aQualifiedName.Equals(aTypeExtension)) { // A custom element type can not extend itself. SetupCustomElement(elem, nameSpaceId, &aTypeExtension); } 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 { RefPtr 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 { RefPtr 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 { RefPtr 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.StealNSResult(); } already_AddRefed nsIDocument::CreateCDATASection(const nsAString& aData, ErrorResult& rv) { if (IsHTMLDocument()) { 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; } RefPtr 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.StealNSResult(); } 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; } RefPtr 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.StealNSResult(); } 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; } nsAutoString name; if (IsHTMLDocument()) { nsContentUtils::ASCIIToLower(aName, name); } else { name = aName; } RefPtr nodeInfo; res = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_None, nsIDOMNode::ATTRIBUTE_NODE, getter_AddRefs(nodeInfo)); if (NS_FAILED(res)) { rv.Throw(res); return nullptr; } RefPtr 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.StealNSResult(); } already_AddRefed nsIDocument::CreateAttributeNS(const nsAString& aNamespaceURI, const nsAString& aQualifiedName, ErrorResult& rv) { WarnOnceAbout(eCreateAttributeNS); RefPtr nodeInfo; rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName, mNodeInfoManager, nsIDOMNode::ATTRIBUTE_NODE, getter_AddRefs(nodeInfo)); if (rv.Failed()) { return nullptr; } RefPtr 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())); nsAutoJSString 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 element = document->CreateElem(localName, nullptr, definition->mNamespaceID); NS_ENSURE_TRUE(element, true); if (definition->mLocalName != typeAtom) { // This element is a custom element by extension, thus we need to // do some special setup. For non-extended custom elements, this happens // when the element is created. document->SetupCustomElement(element, definition->mNamespaceID, &elemName); } nsresult rv = nsContentUtils::WrapNative(aCx, element, element, args.rval()); NS_ENSURE_SUCCESS(rv, true); return true; } bool nsDocument::IsWebComponentsEnabled(JSContext* aCx, JSObject* aObject) { JS::Rooted obj(aCx, aObject); if (Preferences::GetBool("dom.webcomponents.enabled")) { return true; } // Check for the webcomponents permission. See Bug 1181555. JSAutoCompartment ac(aCx, obj); JS::Rooted global(aCx, JS_GetGlobalForObject(aCx, obj)); nsCOMPtr window = do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(global)); if (window) { nsresult rv; nsCOMPtr permMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, false); uint32_t perm; rv = permMgr->TestPermissionFromWindow( window, "moz-extremely-unstable-and-will-change-webcomponents", &perm); NS_ENSURE_SUCCESS(rv, false); return perm == nsIPermissionManager::ALLOW_ACTION; } return false; } nsresult nsDocument::RegisterUnresolvedElement(Element* aElement, nsIAtom* aTypeName) { if (!mRegistry) { return NS_OK; } mozilla::dom::NodeInfo* 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.