/* -*- 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/AnimationComparator.h" #include "mozilla/ArrayUtils.h" #include "mozilla/AutoRestore.h" #include "mozilla/BinarySearch.h" #include "mozilla/DebugOnly.h" #include "mozilla/EffectSet.h" #include "mozilla/IntegerRange.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Likely.h" #include #include "mozilla/Logging.h" #include "plstr.h" #include "mozilla/Sprintf.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsILoadContext.h" #include "nsITextControlFrame.h" #include "nsNumberControlFrame.h" #include "nsUnicharUtils.h" #include "nsContentList.h" #include "nsCSSPseudoElements.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 "nsDSURIContentListener.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 "nsNullPrincipal.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 "xpcpublic.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 "nsRefreshDriver.h" // FOR CSP (autogenerated by xpidl) #include "nsIContentSecurityPolicy.h" #include "mozilla/dom/nsCSPContext.h" #include "mozilla/dom/nsCSPService.h" #include "mozilla/dom/nsCSPUtils.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 "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 "nsIAddonPolicyService.h" #include "mozilla/dom/AnimatableBinding.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/ImageTracker.h" #include "mozilla/dom/MediaQueryList.h" #include "mozilla/dom/NodeFilterBinding.h" #include "mozilla/OwningNonNull.h" #include "mozilla/dom/TabChild.h" #include "mozilla/dom/WebComponentsBinding.h" #include "mozilla/dom/CustomElementRegistryBinding.h" #include "mozilla/dom/CustomElementRegistry.h" #include "nsFrame.h" #include "nsDOMCaretPosition.h" #include "nsIDOMHTMLTextAreaElement.h" #include "nsViewportInfo.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 "mozilla/dom/DOMStringList.h" #include "nsWindowMemoryReporter.h" #include "mozilla/dom/Location.h" #include "mozilla/dom/FontFaceSet.h" #include "mozilla/dom/BoxObject.h" #include "gfxPrefs.h" #include "nsISupportsPrimitives.h" #include "mozilla/StyleSetHandle.h" #include "mozilla/StyleSetHandleInlines.h" #include "mozilla/StyleSheet.h" #include "mozilla/StyleSheetInlines.h" #include "mozilla/dom/SVGSVGElement.h" #include "mozilla/dom/DocGroup.h" #include "mozilla/dom/TabGroup.h" #include "mozilla/DocLoadingTimelineMarker.h" #include "nsISpeculativeConnect.h" #include "mozilla/MediaManager.h" #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"); static nsresult GetHttpChannelHelper(nsIChannel* aChannel, nsIHttpChannel** aHttpChannel) { nsCOMPtr httpChannel = do_QueryInterface(aChannel); if (httpChannel) { httpChannel.forget(aHttpChannel); return NS_OK; } nsCOMPtr multipart = do_QueryInterface(aChannel); if (!multipart) { *aHttpChannel = nullptr; return NS_OK; } nsCOMPtr baseChannel; nsresult rv = multipart->GetBaseChannel(getter_AddRefs(baseChannel)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } httpChannel = do_QueryInterface(baseChannel); httpChannel.forget(aHttpChannel); return NS_OK; } #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; } } 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(); } } } void nsIdentifierMapEntry::ClearAndNotify() { Element* currentElement = mIdContentList.SafeElementAt(0); mIdContentList.Clear(); if (currentElement) { FireChangeCallbacks(currentElement, nullptr); } mNameContentList = nullptr; if (mImageElement) { SetImageElement(nullptr); } mChangeCallbacks = nullptr; } 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; } StyleSheet* nsDOMStyleSheetList::IndexedGetter(uint32_t aIndex, bool& aFound) { if (!mDocument || aIndex >= (uint32_t)mDocument->GetNumberOfStyleSheets()) { aFound = false; return nullptr; } aFound = true; return mDocument->GetStyleSheetAt(aIndex); } void nsDOMStyleSheetList::NodeWillBeDestroyed(const nsINode *aNode) { mDocument = nullptr; } void nsDOMStyleSheetList::StyleSheetAdded(StyleSheet* aStyleSheet, bool aDocumentSheet) { if (aDocumentSheet && -1 != mLength) { mLength++; } } void nsDOMStyleSheetList::StyleSheetRemoved(StyleSheet* 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()); toCtxt->SetOverrideDPPX(fromCtxt->GetOverrideDPPX()); } 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++) { StyleSheet* sheet = mDocument->GetStyleSheetAt(index); NS_ASSERTION(sheet, "Null sheet in sheet list!"); // XXXheycam ServoStyleSheets don't expose their title yet. if (sheet->IsServo()) { NS_ERROR("stylo: ServoStyleSets don't expose their title yet"); continue; } sheet->AsGecko()->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 Runnable { public: explicit SelectorCacheKeyDeleter(SelectorCacheKey* aToDelete) : mSelector(aToDelete) { MOZ_COUNT_CTOR(SelectorCacheKeyDeleter); } protected: ~SelectorCacheKeyDeleter() { MOZ_COUNT_DTOR(SelectorCacheKeyDeleter); } public: NS_IMETHOD Run() override { 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), mBlockAllMixedContent(false), mBlockAllMixedContentPreloads(false), 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), mHasScrollLinkedEffect(false), mFrameRequestCallbacksScheduled(false), mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS), mPartID(0), mUserHasInteracted(false) { SetIsInDocument(); 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() , 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); mEverInForeground = false; } 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(); } if (mDocGroup) { mDocGroup->RemoveDocument(this); } 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"); mInDestructor = true; mInUnlinkOrDeletion = true; mozilla::DropJSObjects(this); // Clear mObservers to keep it in sync with the mutationobserver list mObservers.Clear(); mIntersectionObservers.Clear(); if (mStyleSheetSetList) { mStyleSheetSetList->Disconnect(); } if (mAnimationController) { mAnimationController->Disconnect(); } MOZ_ASSERT(mTimelines.isEmpty()); mParentDocument = nullptr; // Kill the subdocument map, doing this will release its strong // references, if any. delete mSubDocuments; mSubDocuments = nullptr; nsAutoScriptBlocker scriptBlocker; // Destroy link map now so we don't waste time removing // links one by one DestroyElementMaps(); 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 (StyleSheet* sheet : mStyleSheets) { sheet->SetOwningDocument(nullptr); } for (auto& sheets : mAdditionalSheets) { for (StyleSheet* sheet : sheets) { 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(); mPlugins.Clear(); } 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(); } 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) uri = tmp->mDocumentURI->GetSpecOrDefault(); if (nsid < ArrayLength(kNSURIs)) { SprintfLiteral(name, "nsDocument %s %s %s", loadedAsData.get(), kNSURIs[nsid], uri.get()); } else { SprintfLiteral(name, "nsDocument %s %s", loadedAsData.get(), uri.get()); } cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name); } else { NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsDocument, tmp->mRefCnt.get()) } if (!nsINode::Traverse(tmp, cb)) { return NS_SUCCESS_INTERRUPTED_TRAVERSE; } if (tmp->mMaybeEndOutermostXBLUpdateRunner) { // The cached runnable keeps a reference to the document object.. NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mMaybeEndOutermostXBLUpdateRunner.mObj"); cb.NoteXPCOMChild(ToSupports(tmp)); } 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(mDocumentTimeline) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection) 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_WRAPPERCACHE(nsDocument) 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(mMaybeEndOutermostXBLUpdateRunner) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMImplementation) NS_IMPL_CYCLE_COLLECTION_UNLINK(mImageMaps) NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder) 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(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) NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers) 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.OwnerUnlinked(); 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 dom::ScriptLoader(this); mozilla::HoldJSObjects(this); return NS_OK; } void nsIDocument::DeleteAllProperties() { for (uint32_t i = 0; i < GetPropertyTableCount(); ++i) { PropertyTable(i)->DeleteAllProperties(); } } void nsIDocument::DeleteAllPropertiesFor(nsINode* aNode) { for (uint32_t i = 0; i < GetPropertyTableCount(); ++i) { PropertyTable(i)->DeleteAllPropertiesFor(aNode); } } nsPropertyTable* nsIDocument::GetExtraPropertyTable(uint16_t aCategory) { NS_ASSERTION(aCategory > 0, "Category 0 should have already been handled"); while (aCategory >= mExtraPropertyTables.Length() + 1) { mExtraPropertyTables.AppendElement(new nsPropertyTable()); } return mExtraPropertyTables[aCategory - 1]; } bool nsIDocument::IsVisibleConsideringAncestors() const { const nsIDocument *parent = this; do { if (!parent->IsVisible()) { return false; } } while ((parent = parent->GetParentDocument())); return true; } void nsDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) { nsCOMPtr uri; nsCOMPtr principal; if (aChannel) { // Note: this code is duplicated in XULDocument::StartDocumentLoad and // nsScriptSecurityManager::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. 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::DisconnectNodeTree() { // Delete references to sub-documents and kill the subdocument map, // if any. This is not strictly needed, but makes the node tree // teardown a bit faster. delete mSubDocuments; mSubDocuments = nullptr; bool oldVal = mInUnlinkOrDeletion; mInUnlinkOrDeletion = true; uint32_t count = mChildren.ChildCount(); { // Scope for update MOZ_AUTO_DOC_UPDATE(this, UPDATE_CONTENT_MODEL, true); // Destroy link map now so we don't waste time removing // links one by one DestroyElementMaps(); 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); if (content == mCachedRootElement) { // Immediately clear mCachedRootElement, now that it's been removed // from mChildren, so that GetRootElement() will stop returning this // now-stale value. mCachedRootElement = nullptr; } nsNodeUtils::ContentRemoved(this, content, i, previousSibling); content->UnbindFromTree(); } MOZ_ASSERT(!mCachedRootElement, "After removing all children, there should be no root elem"); } mInUnlinkOrDeletion = oldVal; } 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)) { PR_LogPrint("DOCUMENT %p ResetToURI %s", this, aURI->GetSpecOrDefault().get()); } mSecurityInfo = nullptr; mDocumentLoadGroup = nullptr; // Reset our stylesheets ResetStylesheetsToURI(aURI); // Release the listener manager if (mListenerManager) { mListenerManager->Disconnect(); mListenerManager = nullptr; } DisconnectNodeTree(); // 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. if (nsPIDOMWindowInner* win = GetInnerWindow()) { nsGlobalWindow::Cast(win)->RefreshCompartmentPrincipal(); } } void nsDocument::RemoveDocStyleSheetsFromStyleSets() { // The stylesheets should forget us for (StyleSheet* sheet : Reversed(mStyleSheets)) { sheet->SetOwningDocument(nullptr); if (sheet->IsApplicable()) { nsCOMPtr shell = GetShell(); if (shell) { shell->StyleSet()->RemoveDocStyleSheet(sheet); } } // XXX Tell observers? } } void nsDocument::RemoveStyleSheetsFromStyleSets( const nsTArray>& aSheets, SheetType aType) { // The stylesheets should forget us for (StyleSheet* 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); if (mStyleSetFilled) { // Skip removing style sheets from the style set if we know we haven't // filled the style set. (This allows us to avoid calling // GetStyleBackendType() too early.) RemoveDocStyleSheetsFromStyleSets(); RemoveStyleSheetsFromStyleSets(mOnDemandBuiltInUASheets, SheetType::Agent); RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAgentSheet], SheetType::Agent); RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eUserSheet], SheetType::User); RemoveStyleSheetsFromStyleSets(mAdditionalSheets[eAuthorSheet], SheetType::Doc); if (GetStyleBackendType() == StyleBackendType::Gecko) { nsStyleSheetService *sheetService = nsStyleSheetService::GetInstance(); if (sheetService) { RemoveStyleSheetsFromStyleSets(*sheetService->AuthorStyleSheets(), SheetType::Doc); } } else { NS_ERROR("stylo: nsStyleSheetService doesn't handle ServoStyleSheets yet"); } mStyleSetFilled = false; } // 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(StyleSetHandle aStyleSet, const nsTArray>& aSheets, SheetType aType) { for (StyleSheet* sheet : Reversed(aSheets)) { aStyleSet->AppendStyleSheet(aType, sheet); } } void nsDocument::FillStyleSet(StyleSetHandle aStyleSet) { NS_PRECONDITION(aStyleSet, "Must have a style set"); NS_PRECONDITION(aStyleSet->SheetCount(SheetType::Doc) == 0, "Style set already has document sheets?"); MOZ_ASSERT(!mStyleSetFilled); for (StyleSheet* sheet : Reversed(mStyleSheets)) { if (sheet->IsApplicable()) { aStyleSet->AddDocStyleSheet(sheet, this); } } if (aStyleSet->IsGecko()) { nsStyleSheetService *sheetService = nsStyleSheetService::GetInstance(); if (sheetService) { for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) { aStyleSet->AppendStyleSheet(SheetType::Doc, sheet); } } // Iterate backwards to maintain order for (StyleSheet* sheet : Reversed(mOnDemandBuiltInUASheets)) { if (sheet->IsApplicable()) { aStyleSet->PrependStyleSheet(SheetType::Agent, sheet); } } } else { NS_WARNING("stylo: Not yet checking nsStyleSheetService for Servo-backed " "documents. See bug 1290224"); } AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAgentSheet], SheetType::Agent); AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eUserSheet], SheetType::User); AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAuthorSheet], SheetType::Doc); mStyleSetFilled = true; } 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 = parentDocShell->GetDocument(); 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)); PR_LogPrint("DOCUMENT %p StartDocumentLoad %s", this, uri ? uri->GetSpecOrDefault().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, and store the fullscreen enabled flag. These are // immutable after being set here. nsCOMPtr docShell = do_QueryInterface(aContainer); // If this is an error page, don't inherit sandbox flags from docshell nsCOMPtr loadInfo = aChannel->GetLoadInfo(); if (docShell && !(loadInfo && loadInfo->GetLoadErrorPage())) { 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) { nsIDocument* doc = sameTypeParent->GetDocument(); mBlockAllMixedContent = doc->GetBlockAllMixedContent(false); // if the parent document makes use of block-all-mixed-content // then subdocument preloads should always be blocked. mBlockAllMixedContentPreloads = mBlockAllMixedContent || doc->GetBlockAllMixedContent(true); mUpgradeInsecureRequests = doc->GetUpgradeInsecureRequests(false); // if the parent document makes use of upgrade-insecure-requests // then subdocument preloads should always be upgraded. mUpgradeInsecurePreloads = mUpgradeInsecureRequests || doc->GetUpgradeInsecureRequests(true); } } // If this is not a data document, set CSP. if (!mLoadedAsData) { nsresult rv = InitCSP(aChannel); NS_ENSURE_SUCCESS(rv, rv); } // XFO needs to be checked after CSP because it is ignored if // the CSP defines frame-ancestors. if (!nsDSURIContentListener::CheckFrameOptions(aChannel, docShell, NodePrincipal())) { MOZ_LOG(gCspPRLog, LogLevel::Debug, ("XFO doesn't like frame's ancestry, not loading.")); // stop! ERROR page! aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION); } 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()); } } 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 'block-all-mixed-content' if not already inherited // from the parent context or set by any other CSP. if (!mBlockAllMixedContent) { rv = csp->GetBlockAllMixedContent(&mBlockAllMixedContent); NS_ENSURE_SUCCESS_VOID(rv); } if (!mBlockAllMixedContentPreloads) { mBlockAllMixedContentPreloads = mBlockAllMixedContent; } // 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 nsCOMPtr preloadCsp; rv = NodePrincipal()->GetPreloadCsp(getter_AddRefs(preloadCsp)); NS_ENSURE_SUCCESS_VOID(rv); if (preloadCsp) { if (!mBlockAllMixedContentPreloads) { rv = preloadCsp->GetBlockAllMixedContent(&mBlockAllMixedContentPreloads); NS_ENSURE_SUCCESS_VOID(rv); } if (!mUpgradeInsecurePreloads) { rv = preloadCsp->GetUpgradeInsecureRequests(&mUpgradeInsecurePreloads); NS_ENSURE_SUCCESS_VOID(rv); } } } nsresult nsDocument::InitCSP(nsIChannel* aChannel) { MOZ_ASSERT(!mScriptGlobalObject, "CSP must be initialized before mScriptGlobalObject is set!"); 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; nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } 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); // Check if this is a document from a WebExtension. nsString addonId; nsCOMPtr principal = NodePrincipal(); principal->GetAddonId(addonId); bool applyAddonCSP = !addonId.IsEmpty(); // Check if this is a signed content to apply default CSP. bool applySignedContentCSP = false; nsCOMPtr loadInfo = aChannel->GetLoadInfo(); if (loadInfo && loadInfo->GetVerifySignedContent()) { applySignedContentCSP = true; } // If there's no CSP to apply, go ahead and return early if (!applyAddonCSP && !applySignedContentCSP && 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", aspec.get())); } return NS_OK; } MOZ_LOG(gCspPRLog, LogLevel::Debug, ("Document is an add-on or CSP header specified %p", this)); nsCOMPtr csp; rv = principal->EnsureCSP(this, getter_AddRefs(csp)); NS_ENSURE_SUCCESS(rv, rv); // ----- if the doc is an addon, apply its CSP. if (applyAddonCSP) { nsCOMPtr aps = do_GetService("@mozilla.org/addons/policy-service;1"); nsAutoString addonCSP; rv = aps->GetBaseCSP(addonCSP); if (NS_SUCCEEDED(rv)) { csp->AppendPolicy(addonCSP, false, false); } rv = aps->GetAddonCSP(addonId, addonCSP); if (NS_SUCCEEDED(rv)) { csp->AppendPolicy(addonCSP, false, false); } } // ----- if the doc is a signed content, apply the default CSP. // Note that when the content signing becomes a standard, we might have // to restrict this enforcement to "remote content" only. if (applySignedContentCSP) { nsAdoptingString signedContentCSP = Preferences::GetString("security.signed_content.CSP.default"); csp->AppendPolicy(signedContentCSP, false, false); } // ----- if there's a full-strength CSP header, apply it. if (!cspHeaderValue.IsEmpty()) { rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false); NS_ENSURE_SUCCESS(rv, rv); } // ----- if there's a report-only CSP header, apply it. if (!cspROHeaderValue.IsEmpty()) { rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true); NS_ENSURE_SUCCESS(rv, rv); } // ----- Enforce sandbox policy if supplied in CSP header // The document may already have some sandbox flags set (e.g. if the document // is an iframe with the sandbox attribute set). If we have a CSP sandbox // directive, intersect the CSP sandbox flags with the existing flags. This // corresponds to the _least_ permissive policy. uint32_t cspSandboxFlags = SANDBOXED_NONE; rv = csp->GetCSPSandboxFlags(&cspSandboxFlags); NS_ENSURE_SUCCESS(rv, rv); // Probably the iframe sandbox attribute already caused the creation of a // new NullPrincipal. Only create a new NullPrincipal if CSP requires so // and no one has been created yet. bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) && !(mSandboxFlags & SANDBOXED_ORIGIN); mSandboxFlags |= cspSandboxFlags; if (needNewNullPrincipal) { principal = nsNullPrincipal::CreateWithInheritedAttributes(principal); principal->SetCsp(csp); SetPrincipal(principal); } // ----- 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); } } 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 (SprintfLiteral(formatedTime, "%02d/%02d/%04d %02d:%02d:%02d", prtime.tm_month + 1, prtime.tm_mday, int(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(u"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.RemoveEntry(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); #ifdef DEBUG // Validate that the docgroup is set correctly by calling its getter and // triggering its sanity check. // // If we're setting the principal to null, we don't want to perform the check, // as the document is entering an intermediate state where it does not have a // principal. It will be given another real principal shortly which we will // check. It's not unsafe to have a document which has a null principal in the // same docgroup as another document, so this should not be a problem. if (aNewPrincipal) { GetDocGroup(); } #endif } mozilla::dom::DocGroup* nsIDocument::GetDocGroup() { #ifdef DEBUG // Sanity check that we have an up-to-date and accurate docgroup if (mDocGroup) { nsAutoCString docGroupKey; mozilla::dom::DocGroup::GetKey(NodePrincipal(), docGroupKey); MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey)); // XXX: Check that the TabGroup is correct as well! } #endif return mDocGroup; } 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) { 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; } bool nsDocument::IsElementAnimateEnabled(JSContext* /*unused*/, JSObject* /*unused*/) { MOZ_ASSERT(NS_IsMainThread()); return nsContentUtils::IsCallerChrome() || Preferences::GetBool("dom.animations-api.core.enabled") || Preferences::GetBool("dom.animations-api.element-animate.enabled"); } 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, TimeDuration(0)); } return mDocumentTimeline; } void nsDocument::GetAnimations(nsTArray>& aAnimations) { // Hold a strong ref for the root element since Element::GetAnimations() calls // FlushPendingNotifications() which may destroy the element. RefPtr root = GetRootElement(); if (!root) { return; } AnimationFilter filter; filter.mSubtree = true; root->GetAnimations(filter, aAnimations); } SVGSVGElement* nsIDocument::GetSVGRootElement() const { Element* root = GetRootElement(); if (!root || !root->IsSVGElement(nsGkAtoms::svg)) { return nullptr; } return static_cast(root); } /* 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; } nsPIDOMWindowOuter* piWindow = nsPIDOMWindowOuter::From(focusedWindow); // 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. if (nsCOMPtr window = GetWindow()) { 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) { AutoTArray, 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 AutoTArray 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(); } void nsDocument::SetBaseURI(nsIURI* aURI) { if (!aURI && !mDocumentBaseURI) { return; } // Don't do anything if the URI wasn't actually changed. if (aURI && mDocumentBaseURI) { bool equalBases = false; mDocumentBaseURI->Equals(aURI, &equalBases); if (equalBases) { return; } } if (aURI) { mDocumentBaseURI = NS_TryToMakeImmutable(aURI); } else { mDocumentBaseURI = nullptr; } RefreshLinkHrefs(); } 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 nsIDocument::GetSandboxFlagsAsString(nsAString& aFlags) { nsContentUtils::SandboxFlagsToString(mSandboxFlags, aFlags); } 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); // If policy is not the empty string, then set element's node document's // referrer policy to policy if (policy != mozilla::net::RP_Unset) { // Referrer policy spec (section 6.1) says that we always use the newest // referrer policy we find mReferrerPolicy = policy; mReferrerPolicySet = true; } } if (aHeaderField == nsGkAtoms::headerReferrerPolicy && !aData.IsEmpty()) { ReferrerPolicy policy = nsContentUtils::GetReferrerPolicyFromHeader(aData); if (policy != mozilla::net::RP_Unset) { 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, StyleSetHandle 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); } already_AddRefed nsDocument::doCreateShell(nsPresContext* aContext, nsViewManager* aViewManager, StyleSetHandle aStyleSet) { 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); // 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. ImageTracker()->RequestDiscardAll(); // 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); mStyleSetFilled = false; } 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(); } // Preemptively clear mCachedRootElement, since we may be about to remove it // from our child list, and we don't want to return this maybe-obsolete value // from any GetRootElement() calls that happen inside of doRemoveChildAt(). // (NOTE: for this to be useful, doRemoveChildAt() must NOT trigger any // GetRootElement() calls until after it's removed the child from mChildren. // Any call before that point would restore this soon-to-be-obsolete cached // answer, and our clearing here would be fruitless.) mCachedRootElement = nullptr; doRemoveChildAt(aIndex, aNotify, oldKid, mChildren); MOZ_ASSERT(mCachedRootElement != oldKid, "Stale pointer in mCachedRootElement, after we tried to clear it " "(maybe somebody called GetRootElement() too early?)"); } void nsDocument::EnsureOnDemandBuiltInUASheet(StyleSheet* aSheet) { if (mOnDemandBuiltInUASheets.Contains(aSheet)) { return; } BeginUpdate(UPDATE_STYLE); AddOnDemandBuiltInUASheet(aSheet); EndUpdate(UPDATE_STYLE); } void nsDocument::AddOnDemandBuiltInUASheet(StyleSheet* 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(); } StyleSheet* nsDocument::GetStyleSheetAt(int32_t aIndex) const { return mStyleSheets.SafeElementAt(aIndex, nullptr); } int32_t nsDocument::GetIndexOfStyleSheet(const StyleSheet* aSheet) const { return mStyleSheets.IndexOf(aSheet); } void nsDocument::AddStyleSheetToStyleSets(StyleSheet* 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; \ /* XXXheycam ServoStyleSheet doesn't implement DOM interfaces yet */ \ if (aSheet->IsServo()) { \ NS_ERROR("stylo: can't dispatch events for ServoStyleSheets yet"); \ } \ init.mStylesheet = aSheet->IsGecko() ? aSheet->AsGecko() : nullptr; \ 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(StyleSheet* aSheet, bool aDocumentSheet) { NS_DOCUMENT_NOTIFY_OBSERVERS(StyleSheetAdded, (aSheet, aDocumentSheet)); if (StyleSheetChangeEventsEnabled()) { DO_STYLESHEET_NOTIFICATION(StyleSheetChangeEvent, "StyleSheetAdded", mDocumentSheet, aDocumentSheet); } } void nsDocument::NotifyStyleSheetRemoved(StyleSheet* aSheet, bool aDocumentSheet) { NS_DOCUMENT_NOTIFY_OBSERVERS(StyleSheetRemoved, (aSheet, aDocumentSheet)); if (StyleSheetChangeEventsEnabled()) { DO_STYLESHEET_NOTIFICATION(StyleSheetChangeEvent, "StyleSheetRemoved", mDocumentSheet, aDocumentSheet); } } void nsDocument::AddStyleSheet(StyleSheet* aSheet) { NS_PRECONDITION(aSheet, "null arg"); mStyleSheets.AppendElement(aSheet); aSheet->SetOwningDocument(this); if (aSheet->IsApplicable()) { AddStyleSheetToStyleSets(aSheet); } NotifyStyleSheetAdded(aSheet, true); } void nsDocument::RemoveStyleSheetFromStyleSets(StyleSheet* aSheet) { nsCOMPtr shell = GetShell(); if (shell) { shell->StyleSet()->RemoveDocStyleSheet(aSheet); } } void nsDocument::RemoveStyleSheet(StyleSheet* 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. StyleSheet* 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(StyleSheet* 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(StyleSheet* 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 HTMLEditor. NS_DOCUMENT_NOTIFY_OBSERVERS(StyleSheetApplicableStateChanged, (aSheet)); if (StyleSheetChangeEventsEnabled()) { DO_STYLESHEET_NOTIFICATION(StyleSheetApplicableStateChangeEvent, "StyleSheetApplicableStateChanged", mApplicable, aApplicable); } if (!mSSApplicableStateNotificationPending) { nsCOMPtr notification = 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(GetStyleBackendType()); 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, &sheet); NS_ENSURE_SUCCESS(rv, rv); sheet->SetOwningDocument(this); MOZ_ASSERT(sheet->IsApplicable()); return AddAdditionalStyleSheet(aType, sheet); } nsresult nsDocument::AddAdditionalStyleSheet(additionalSheetType aType, StyleSheet* 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); } } StyleSheet* nsDocument::GetFirstAdditionalAuthorSheet() { return mAdditionalSheets[eAuthorSheet].SafeElementAt(0); } nsIGlobalObject* nsDocument::GetScopeObject() const { nsCOMPtr scope(do_QueryReferent(mScopeObject)); return scope; } void nsDocument::SetScopeObject(nsIGlobalObject* aGlobal) { mScopeObject = do_GetWeakReference(aGlobal); if (aGlobal) { mHasHadScriptHandlingObject = true; nsCOMPtr window = do_QueryInterface(aGlobal); if (window) { // We want to get the tabgroup unconditionally, such that we can make // certain that it is cached in the inner window early enough. mozilla::dom::TabGroup* tabgroup = window->TabGroup(); // We should already have the principal, and now that we have been added to a // window, we should be able to join a DocGroup! nsAutoCString docGroupKey; mozilla::dom::DocGroup::GetKey(NodePrincipal(), docGroupKey); if (!mDocGroup) { mDocGroup = tabgroup->AddDocument(docGroupKey, this); MOZ_ASSERT(mDocGroup); } } } } #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); } } using mozilla::dom::workers::ServiceWorkerManager; RefPtr swm = ServiceWorkerManager::GetInstance(); if (swm) { ErrorResult error; if (swm->IsControlled(this, error)) { imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this); if (loader) { loader->ClearCacheForControlledDocument(this); } // We may become controlled again if this document comes back out // of bfcache. Clear our state to allow that to happen. Only // clear this flag if we are actually controlled, though, so pages // that were force reloaded don't become controlled when they // come out of bfcache. mMaybeServiceWorkerControlled = false; } swm->MaybeStopControlling(this); } // Remove ourself from the list of clients. We only register // content principal documents in this list. if (!nsContentUtils::IsSystemPrincipal(GetPrincipal()) && !GetPrincipal()->GetIsNullPrincipal()) { nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { os->RemoveObserver(this, "service-worker-get-client"); } } } else if (!mScriptGlobalObject && aScriptGlobalObject && mDocumentContainer && GetChannel() && !nsContentUtils::IsSystemPrincipal(GetPrincipal()) && !GetPrincipal()->GetIsNullPrincipal()) { // This document is being activated. Register it in the list of // clients. We only do this for content principal documents // since we can never observe system or null principals. nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { os->AddObserver(this, "service-worker-get-client", /* ownsWeak */ false); } } // 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) { // Go back to using the docshell for the layout history state mLayoutHistoryState = nullptr; SetScopeObject(aScriptGlobalObject); mHasHadDefaultView = true; 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; } } } // 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) { // If this document is being resurrected from the bfcache, then we may // already have a document ID. In that case reuse the same ID. Otherwise // get our document ID from the docshell. nsString documentId(GetId()); if (documentId.IsEmpty()) { 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) { nsPIDOMWindowOuter* 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!"); if (aScriptObject) { SetScopeObject(aScriptObject); 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; } nsPIDOMWindowOuter* 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 { if (nsCOMPtr inner = do_QueryInterface(mScriptGlobalObject)) { // mScriptGlobalObject is always the inner window, let's get the outer. win = inner->GetOuterWindow(); } } return win; } ScriptLoader* 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) { if (!mMaybeEndOutermostXBLUpdateRunner) { mMaybeEndOutermostXBLUpdateRunner = NewRunnableMethod(this, &nsDocument::MaybeEndOutermostXBLUpdate); } nsContentUtils::AddScriptRunner(mMaybeEndOutermostXBLUpdateRunner); } } } 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(); if (os) { 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->WidgetEventPtr(); 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 = 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(StyleSheet* aSheet, css::Rule* aStyleRule) { NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleChanged, (aSheet)); if (StyleSheetChangeEventsEnabled()) { DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent, "StyleRuleChanged", mRule, aStyleRule ? aStyleRule->GetDOMRule() : nullptr); } } void nsDocument::StyleRuleAdded(StyleSheet* aSheet, css::Rule* aStyleRule) { NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleAdded, (aSheet)); if (StyleSheetChangeEventsEnabled()) { DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent, "StyleRuleAdded", mRule, aStyleRule ? aStyleRule->GetDOMRule() : nullptr); } } void nsDocument::StyleRuleRemoved(StyleSheet* aSheet, css::Rule* aStyleRule) { NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleRemoved, (aSheet)); 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(); } } Element* nsIDocument::GetAnonRootIfInAnonymousContentContainer(nsINode* aNode) const { if (!aNode->IsInNativeAnonymousSubtree()) { return nullptr; } nsIPresShell* shell = GetShell(); if (!shell || !shell->GetCanvasFrame()) { return nullptr; } nsAutoScriptBlocker scriptBlocker; nsCOMPtr customContainer = shell->GetCanvasFrame() ->GetCustomContentContainer(); if (!customContainer) { return nullptr; } // An arbitrary number of elements can be inserted as children of the custom // container frame. We want the one that was added that contains aNode, so // we need to keep track of the last child separately using |child| here. nsINode* child = aNode; nsINode* parent = aNode->GetParentNode(); while (parent && parent->IsInNativeAnonymousSubtree()) { if (parent == customContainer) { return child->IsElement() ? child->AsElement() : nullptr; } child = parent; parent = child->GetParentNode(); } return nullptr; } // // 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; ElementCreationOptionsOrString options; options.SetAsString(); nsCOMPtr element = CreateElement(aTagName, options, 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 nsDocument::GetCustomElementRegistry() { nsAutoString contentType; GetContentType(contentType); if (!IsHTMLDocument() && !contentType.EqualsLiteral("application/xhtml+xml")) { return nullptr; } nsCOMPtr window( do_QueryInterface(mScriptGlobalObject ? mScriptGlobalObject : GetScopeObject())); if (!window) { return nullptr; } RefPtr registry = window->CustomElements(); if (!registry) { return nullptr; } return registry.forget(); } already_AddRefed nsDocument::CreateElement(const nsAString& aTagName, const ElementCreationOptionsOrString& aOptions, 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); } const nsString* is = nullptr; if (aOptions.IsElementCreationOptions()) { // Throw NotFoundError if 'is' is not-null and definition is null is = CheckCustomElementName(aOptions.GetAsElementCreationOptions(), needsLowercase ? lcTagName : aTagName, mDefaultElementType, rv); if (rv.Failed()) { return nullptr; } } RefPtr elem = CreateElem( needsLowercase ? lcTagName : aTagName, nullptr, mDefaultElementType, is); return elem.forget(); } NS_IMETHODIMP nsDocument::CreateElementNS(const nsAString& aNamespaceURI, const nsAString& aQualifiedName, nsIDOMElement** aReturn) { *aReturn = nullptr; ElementCreationOptionsOrString options; options.SetAsString(); ErrorResult rv; nsCOMPtr element = CreateElementNS(aNamespaceURI, aQualifiedName, options, rv); NS_ENSURE_FALSE(rv.Failed(), rv.StealNSResult()); return CallQueryInterface(element, aReturn); } already_AddRefed nsDocument::CreateElementNS(const nsAString& aNamespaceURI, const nsAString& aQualifiedName, const ElementCreationOptionsOrString& aOptions, ErrorResult& rv) { RefPtr nodeInfo; rv = nsContentUtils::GetNodeInfoFromQName(aNamespaceURI, aQualifiedName, mNodeInfoManager, nsIDOMNode::ELEMENT_NODE, getter_AddRefs(nodeInfo)); if (rv.Failed()) { return nullptr; } const nsString* is = nullptr; if (aOptions.IsElementCreationOptions()) { // Throw NotFoundError if 'is' is not-null and definition is null is = CheckCustomElementName(aOptions.GetAsElementCreationOptions(), aQualifiedName, nodeInfo->NamespaceID(), rv); if (rv.Failed()) { return nullptr; } } nsCOMPtr element; rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(), NOT_FROM_PARSER, is); if (rv.Failed()) { return nullptr; } return element.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()); 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()); 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())); RefPtr window; UNWRAP_OBJECT(Window, global, window); 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; } RefPtr registry = window->CustomElements(); if (!registry) { return true; } nsCOMPtr typeAtom(NS_Atomize(elemName)); CustomElementDefinition* definition = registry->mCustomDefinitions.Get(typeAtom); if (!definition) { return true; } nsDependentAtomString localName(definition->mLocalName); nsCOMPtr element = document->CreateElem(localName, nullptr, kNameSpaceID_XHTML); 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. nsContentUtils::SetupCustomElement(element, &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)); return IsWebComponentsEnabled(window); } bool nsDocument::IsWebComponentsEnabled(dom::NodeInfo* aNodeInfo) { //if (Preferences::GetBool("dom.webcomponents.enabled")) { // return true; //} nsIDocument* doc = aNodeInfo->GetDocument(); // Use GetScopeObject() here so that data documents work the same way as the // main document they're associated with. nsCOMPtr window = do_QueryInterface(doc->GetScopeObject()); return IsWebComponentsEnabled(window); } bool nsDocument::IsWebComponentsEnabled(nsPIDOMWindowInner* aWindow) { /* if (aWindow) { nsresult rv; nsCOMPtr permMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, false); uint32_t perm; rv = permMgr->TestPermissionFromWindow( aWindow, "moz-extremely-unstable-and-will-change-webcomponents", &perm); NS_ENSURE_SUCCESS(rv, false); return perm == nsIPermissionManager::ALLOW_ACTION; } */ return false; } void nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType, const ElementRegistrationOptions& aOptions, JS::MutableHandle aRetval, ErrorResult& rv) { RefPtr registry(GetCustomElementRegistry()); if (!registry) { rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return; } // Unconditionally convert TYPE to lowercase. nsAutoString lcType; nsContentUtils::ASCIIToLower(aType, lcType); nsIGlobalObject* sgo = GetScopeObject(); if (!sgo) { rv.Throw(NS_ERROR_UNEXPECTED); return; } JS::Rooted global(aCx, sgo->GetGlobalJSObject()); JS::Rooted protoObject(aCx); if (!aOptions.mPrototype) { JS::Rooted htmlProto(aCx); htmlProto = HTMLElementBinding::GetProtoObjectHandle(aCx); if (!htmlProto) { rv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } protoObject = JS_NewObjectWithGivenProto(aCx, nullptr, htmlProto); if (!protoObject) { rv.Throw(NS_ERROR_UNEXPECTED); return; } } else { protoObject = aOptions.mPrototype; // Get the unwrapped prototype to do some checks. JS::Rooted protoObjectUnwrapped(aCx, js::CheckedUnwrap(protoObject)); if (!protoObjectUnwrapped) { // If the caller's compartment does not have permission to access the // unwrapped prototype then throw. rv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } // If PROTOTYPE is already an interface prototype object for any interface // object or PROTOTYPE has a non-configurable property named constructor, // throw a NotSupportedError and stop. const js::Class* clasp = js::GetObjectClass(protoObjectUnwrapped); if (IsDOMIfaceAndProtoClass(clasp)) { rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return; } JS::Rooted descRoot(aCx); JS::MutableHandle desc(&descRoot); // This check may go through a wrapper, but as we checked above // it should be transparent or an xray. This should be fine for now, // until the spec is sorted out. if (!JS_GetPropertyDescriptor(aCx, protoObject, "constructor", desc)) { rv.Throw(NS_ERROR_UNEXPECTED); return; } if (!desc.configurable()) { rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return; } } JS::Rooted constructor(aCx); { // Go into the document's global compartment when creating the constructor // function because we want to get the correct document (where the // definition is registered) when it is called. JSAutoCompartment ac(aCx, global); // Create constructor to return. Store the name of the custom element as the // name of the function. constructor = JS_NewFunction(aCx, nsDocument::CustomElementConstructor, 0, JSFUN_CONSTRUCTOR, NS_ConvertUTF16toUTF8(lcType).get()); if (!constructor) { rv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } } JS::Rooted wrappedConstructor(aCx); wrappedConstructor = JS_GetFunctionObject(constructor); if (!JS_WrapObject(aCx, &wrappedConstructor)) { rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return; } if (!JS_LinkConstructorAndPrototype(aCx, wrappedConstructor, protoObject)) { rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return; } ElementDefinitionOptions options; if (!aOptions.mExtends.IsVoid()) { // Only convert NAME to lowercase in HTML documents. nsAutoString lcName; IsHTMLDocument() ? nsContentUtils::ASCIIToLower(aOptions.mExtends, lcName) : lcName.Assign(aOptions.mExtends); options.mExtends.Construct(lcName); } RootedCallback> functionConstructor(aCx); functionConstructor = new binding_detail::FastFunction(aCx, wrappedConstructor, sgo); registry->Define(lcType, functionConstructor, options, rv); aRetval.set(wrappedConstructor); } NS_IMETHODIMP nsDocument::GetElementsByTagName(const nsAString& aTagname, nsIDOMNodeList** aReturn) { RefPtr list = GetElementsByTagName(aTagname); NS_ENSURE_TRUE(list, NS_ERROR_OUT_OF_MEMORY); // transfer ref to aReturn list.forget(aReturn); return NS_OK; } long nsDocument::BlockedTrackingNodeCount() const { return mBlockedTrackingNodes.Length(); } already_AddRefed nsDocument::BlockedTrackingNodes() const { RefPtr list = new nsSimpleContentList(nullptr); nsTArray blockedTrackingNodes; blockedTrackingNodes = mBlockedTrackingNodes; for (unsigned long i = 0; i < blockedTrackingNodes.Length(); i++) { nsWeakPtr weakNode = blockedTrackingNodes[i]; nsCOMPtr node = do_QueryReferent(weakNode); // Consider only nodes to which we have managed to get strong references. // Coping with nullptrs since it's expected for nodes to disappear when // nobody else is referring to them. if (node) { list->AppendElement(node); } } return list.forget(); } already_AddRefed nsIDocument::GetElementsByTagNameNS(const nsAString& aNamespaceURI, const nsAString& aLocalName, ErrorResult& aResult) { int32_t nameSpaceId = kNameSpaceID_Wildcard; if (!aNamespaceURI.EqualsLiteral("*")) { aResult = nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI, nameSpaceId); if (aResult.Failed()) { return nullptr; } } NS_ASSERTION(nameSpaceId != kNameSpaceID_Unknown, "Unexpected namespace ID!"); return NS_GetContentList(this, nameSpaceId, aLocalName); } NS_IMETHODIMP nsDocument::GetElementsByTagNameNS(const nsAString& aNamespaceURI, const nsAString& aLocalName, nsIDOMNodeList** aReturn) { ErrorResult rv; RefPtr list = nsIDocument::GetElementsByTagNameNS(aNamespaceURI, aLocalName, rv); if (rv.Failed()) { return rv.StealNSResult(); } // transfer ref to aReturn list.forget(aReturn); return NS_OK; } NS_IMETHODIMP nsDocument::GetStyleSheets(nsIDOMStyleSheetList** aStyleSheets) { NS_ADDREF(*aStyleSheets = StyleSheets()); return NS_OK; } StyleSheetList* nsDocument::StyleSheets() { if (!mDOMStyleSheets) { mDOMStyleSheets = new nsDOMStyleSheetList(this); } return mDOMStyleSheets; } NS_IMETHODIMP nsDocument::GetMozSelectedStyleSheetSet(nsAString& aSheetSet) { nsIDocument::GetSelectedStyleSheetSet(aSheetSet); return NS_OK; } void nsIDocument::GetSelectedStyleSheetSet(nsAString& aSheetSet) { aSheetSet.Truncate(); // Look through our sheets, find the selected set title int32_t count = GetNumberOfStyleSheets(); nsAutoString title; for (int32_t index = 0; index < count; index++) { StyleSheet* sheet = GetStyleSheetAt(index); NS_ASSERTION(sheet, "Null sheet in sheet list!"); // XXXheycam Make this work with ServoStyleSheets. if (sheet->IsServo()) { NS_ERROR("stylo: can't handle alternate ServoStyleSheets yet"); continue; } bool disabled; sheet->AsGecko()->GetDisabled(&disabled); if (disabled) { // Disabled sheets don't affect the currently selected set continue; } sheet->AsGecko()->GetTitle(title); if (aSheetSet.IsEmpty()) { aSheetSet = title; } else if (!title.IsEmpty() && !aSheetSet.Equals(title)) { // Sheets from multiple sets enabled; return null string, per spec. SetDOMStringToNull(aSheetSet); return; } } } NS_IMETHODIMP nsDocument::SetMozSelectedStyleSheetSet(const nsAString& aSheetSet) { SetSelectedStyleSheetSet(aSheetSet); return NS_OK; } void nsDocument::SetSelectedStyleSheetSet(const nsAString& aSheetSet) { if (DOMStringIsNull(aSheetSet)) { return; } // Must update mLastStyleSheetSet before doing anything else with stylesheets // or CSSLoaders. mLastStyleSheetSet = aSheetSet; EnableStyleSheetsForSetInternal(aSheetSet, true); } NS_IMETHODIMP nsDocument::GetLastStyleSheetSet(nsAString& aSheetSet) { nsString sheetSet; GetLastStyleSheetSet(sheetSet); aSheetSet = sheetSet; return NS_OK; } void nsDocument::GetLastStyleSheetSet(nsString& aSheetSet) { aSheetSet = mLastStyleSheetSet; } NS_IMETHODIMP nsDocument::GetPreferredStyleSheetSet(nsAString& aSheetSet) { nsIDocument::GetPreferredStyleSheetSet(aSheetSet); return NS_OK; } void nsIDocument::GetPreferredStyleSheetSet(nsAString& aSheetSet) { GetHeaderData(nsGkAtoms::headerDefaultStyle, aSheetSet); } NS_IMETHODIMP nsDocument::GetStyleSheetSets(nsISupports** aList) { NS_ADDREF(*aList = StyleSheetSets()); return NS_OK; } DOMStringList* nsDocument::StyleSheetSets() { if (!mStyleSheetSetList) { mStyleSheetSetList = new nsDOMStyleSheetSetList(this); } return mStyleSheetSetList; } NS_IMETHODIMP nsDocument::MozEnableStyleSheetsForSet(const nsAString& aSheetSet) { EnableStyleSheetsForSet(aSheetSet); return NS_OK; } void nsDocument::EnableStyleSheetsForSet(const nsAString& aSheetSet) { // Per spec, passing in null is a no-op. if (!DOMStringIsNull(aSheetSet)) { // Note: must make sure to not change the CSSLoader's preferred sheet -- // that value should be equal to either our lastStyleSheetSet (if that's // non-null) or to our preferredStyleSheetSet. And this method doesn't // change either of those. EnableStyleSheetsForSetInternal(aSheetSet, false); } } void nsDocument::EnableStyleSheetsForSetInternal(const nsAString& aSheetSet, bool aUpdateCSSLoader) { BeginUpdate(UPDATE_STYLE); int32_t count = GetNumberOfStyleSheets(); nsAutoString title; for (int32_t index = 0; index < count; index++) { StyleSheet* sheet = GetStyleSheetAt(index); NS_ASSERTION(sheet, "Null sheet in sheet list!"); // XXXheycam Make this work with ServoStyleSheets. if (sheet->IsServo()) { NS_ERROR("stylo: can't handle alternate ServoStyleSheets yet"); continue; } sheet->AsGecko()->GetTitle(title); if (!title.IsEmpty()) { sheet->AsGecko()->SetEnabled(title.Equals(aSheetSet)); } } if (aUpdateCSSLoader) { CSSLoader()->SetPreferredSheet(aSheetSet); } EndUpdate(UPDATE_STYLE); } NS_IMETHODIMP nsDocument::GetCharacterSet(nsAString& aCharacterSet) { nsIDocument::GetCharacterSet(aCharacterSet); return NS_OK; } void nsIDocument::GetCharacterSet(nsAString& aCharacterSet) const { CopyASCIItoUTF16(GetDocumentCharacterSet(), aCharacterSet); } NS_IMETHODIMP nsDocument::ImportNode(nsIDOMNode* aImportedNode, bool aDeep, uint8_t aArgc, nsIDOMNode** aResult) { if (aArgc == 0) { aDeep = true; } *aResult = nullptr; nsCOMPtr imported = do_QueryInterface(aImportedNode); NS_ENSURE_TRUE(imported, NS_ERROR_UNEXPECTED); ErrorResult rv; nsCOMPtr result = nsIDocument::ImportNode(*imported, aDeep, rv); if (rv.Failed()) { return rv.StealNSResult(); } NS_ADDREF(*aResult = result->AsDOMNode()); return NS_OK; } already_AddRefed nsIDocument::ImportNode(nsINode& aNode, bool aDeep, ErrorResult& rv) const { nsINode* imported = &aNode; switch (imported->NodeType()) { case nsIDOMNode::DOCUMENT_NODE: { break; } case nsIDOMNode::DOCUMENT_FRAGMENT_NODE: { if (ShadowRoot::FromNode(imported)) { break; } MOZ_FALLTHROUGH; } case nsIDOMNode::ATTRIBUTE_NODE: case nsIDOMNode::ELEMENT_NODE: case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: case nsIDOMNode::TEXT_NODE: case nsIDOMNode::CDATA_SECTION_NODE: case nsIDOMNode::COMMENT_NODE: case nsIDOMNode::DOCUMENT_TYPE_NODE: { nsCOMPtr newNode; nsCOMArray nodesWithProperties; rv = nsNodeUtils::Clone(imported, aDeep, mNodeInfoManager, nodesWithProperties, getter_AddRefs(newNode)); if (rv.Failed()) { return nullptr; } return newNode.forget(); } default: { NS_WARNING("Don't know how to clone this nodetype for importNode."); } } rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return nullptr; } NS_IMETHODIMP nsDocument::LoadBindingDocument(const nsAString& aURI) { ErrorResult rv; nsIDocument::LoadBindingDocument(aURI, nsContentUtils::GetCurrentJSContext() ? Some(nsContentUtils::SubjectPrincipal()) : Nothing(), rv); return rv.StealNSResult(); } void nsIDocument::LoadBindingDocument(const nsAString& aURI, nsIPrincipal& aSubjectPrincipal, ErrorResult& rv) { LoadBindingDocument(aURI, Some(&aSubjectPrincipal), rv); } void nsIDocument::LoadBindingDocument(const nsAString& aURI, const Maybe& aSubjectPrincipal, ErrorResult& rv) { nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), aURI, mCharacterSet.get(), GetDocBaseURI()); if (rv.Failed()) { return; } // Note - This computation of subjectPrincipal isn't necessarily sensical. // It's just designed to preserve the old semantics during a mass-conversion // patch. nsCOMPtr subjectPrincipal = aSubjectPrincipal.isSome() ? aSubjectPrincipal.value() : NodePrincipal(); BindingManager()->LoadBindingDocument(this, uri, subjectPrincipal); } NS_IMETHODIMP nsDocument::GetBindingParent(nsIDOMNode* aNode, nsIDOMElement** aResult) { nsCOMPtr node = do_QueryInterface(aNode); NS_ENSURE_ARG_POINTER(node); Element* bindingParent = nsIDocument::GetBindingParent(*node); nsCOMPtr retval = do_QueryInterface(bindingParent); retval.forget(aResult); return NS_OK; } Element* nsIDocument::GetBindingParent(nsINode& aNode) { nsCOMPtr content(do_QueryInterface(&aNode)); if (!content) return nullptr; nsIContent* bindingParent = content->GetBindingParent(); return bindingParent ? bindingParent->AsElement() : nullptr; } static Element* GetElementByAttribute(nsIContent* aContent, nsIAtom* aAttrName, const nsAString& aAttrValue, bool aUniversalMatch) { if (aUniversalMatch ? aContent->HasAttr(kNameSpaceID_None, aAttrName) : aContent->AttrValueIs(kNameSpaceID_None, aAttrName, aAttrValue, eCaseMatters)) { return aContent->AsElement(); } for (nsIContent* child = aContent->GetFirstChild(); child; child = child->GetNextSibling()) { Element* matchedElement = GetElementByAttribute(child, aAttrName, aAttrValue, aUniversalMatch); if (matchedElement) return matchedElement; } return nullptr; } Element* nsDocument::GetAnonymousElementByAttribute(nsIContent* aElement, nsIAtom* aAttrName, const nsAString& aAttrValue) const { nsINodeList* nodeList = BindingManager()->GetAnonymousNodesFor(aElement); if (!nodeList) return nullptr; uint32_t length = 0; nodeList->GetLength(&length); bool universalMatch = aAttrValue.EqualsLiteral("*"); for (uint32_t i = 0; i < length; ++i) { nsIContent* current = nodeList->Item(i); Element* matchedElm = GetElementByAttribute(current, aAttrName, aAttrValue, universalMatch); if (matchedElm) return matchedElm; } return nullptr; } NS_IMETHODIMP nsDocument::GetAnonymousElementByAttribute(nsIDOMElement* aElement, const nsAString& aAttrName, const nsAString& aAttrValue, nsIDOMElement** aResult) { nsCOMPtr element = do_QueryInterface(aElement); NS_ENSURE_ARG_POINTER(element); Element* anonEl = nsIDocument::GetAnonymousElementByAttribute(*element, aAttrName, aAttrValue); nsCOMPtr retval = do_QueryInterface(anonEl); retval.forget(aResult); return NS_OK; } Element* nsIDocument::GetAnonymousElementByAttribute(Element& aElement, const nsAString& aAttrName, const nsAString& aAttrValue) { nsCOMPtr attribute = NS_Atomize(aAttrName); return GetAnonymousElementByAttribute(&aElement, attribute, aAttrValue); } NS_IMETHODIMP nsDocument::GetAnonymousNodes(nsIDOMElement* aElement, nsIDOMNodeList** aResult) { *aResult = nullptr; nsCOMPtr content(do_QueryInterface(aElement)); return BindingManager()->GetAnonymousNodesFor(content, aResult); } nsINodeList* nsIDocument::GetAnonymousNodes(Element& aElement) { return BindingManager()->GetAnonymousNodesFor(&aElement); } NS_IMETHODIMP nsDocument::CreateRange(nsIDOMRange** aReturn) { ErrorResult rv; *aReturn = nsIDocument::CreateRange(rv).take(); return rv.StealNSResult(); } already_AddRefed nsIDocument::CreateRange(ErrorResult& rv) { RefPtr range = new nsRange(this); nsresult res = range->Set(this, 0, this, 0); if (NS_FAILED(res)) { rv.Throw(res); return nullptr; } return range.forget(); } NS_IMETHODIMP nsDocument::CreateNodeIterator(nsIDOMNode *aRoot, uint32_t aWhatToShow, nsIDOMNodeFilter *aFilter, uint8_t aOptionalArgc, nsIDOMNodeIterator **_retval) { *_retval = nullptr; if (!aOptionalArgc) { aWhatToShow = nsIDOMNodeFilter::SHOW_ALL; } if (!aRoot) { return NS_ERROR_DOM_NOT_SUPPORTED_ERR; } nsCOMPtr root = do_QueryInterface(aRoot); NS_ENSURE_TRUE(root, NS_ERROR_UNEXPECTED); ErrorResult rv; *_retval = nsIDocument::CreateNodeIterator(*root, aWhatToShow, NodeFilterHolder(aFilter), rv).take(); return rv.StealNSResult(); } already_AddRefed nsIDocument::CreateNodeIterator(nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter, ErrorResult& rv) const { return CreateNodeIterator(aRoot, aWhatToShow, NodeFilterHolder(aFilter), rv); } already_AddRefed nsIDocument::CreateNodeIterator(nsINode& aRoot, uint32_t aWhatToShow, NodeFilterHolder aFilter, ErrorResult& rv) const { nsINode* root = &aRoot; RefPtr iterator = new NodeIterator(root, aWhatToShow, Move(aFilter)); return iterator.forget(); } NS_IMETHODIMP nsDocument::CreateTreeWalker(nsIDOMNode *aRoot, uint32_t aWhatToShow, nsIDOMNodeFilter *aFilter, uint8_t aOptionalArgc, nsIDOMTreeWalker **_retval) { *_retval = nullptr; if (!aOptionalArgc) { aWhatToShow = nsIDOMNodeFilter::SHOW_ALL; } nsCOMPtr root = do_QueryInterface(aRoot); NS_ENSURE_TRUE(root, NS_ERROR_DOM_NOT_SUPPORTED_ERR); ErrorResult rv; *_retval = nsIDocument::CreateTreeWalker(*root, aWhatToShow, NodeFilterHolder(aFilter), rv).take(); return rv.StealNSResult(); } already_AddRefed nsIDocument::CreateTreeWalker(nsINode& aRoot, uint32_t aWhatToShow, NodeFilter* aFilter, ErrorResult& rv) const { return CreateTreeWalker(aRoot, aWhatToShow, NodeFilterHolder(aFilter), rv); } already_AddRefed nsIDocument::CreateTreeWalker(nsINode& aRoot, uint32_t aWhatToShow, NodeFilterHolder aFilter, ErrorResult& rv) const { nsINode* root = &aRoot; RefPtr walker = new TreeWalker(root, aWhatToShow, Move(aFilter)); return walker.forget(); } NS_IMETHODIMP nsDocument::GetDefaultView(mozIDOMWindowProxy** aDefaultView) { *aDefaultView = nullptr; nsCOMPtr win = GetWindow(); win.forget(aDefaultView); return NS_OK; } NS_IMETHODIMP nsDocument::GetLocation(nsIDOMLocation **_retval) { *_retval = nsIDocument::GetLocation().take(); return NS_OK; } already_AddRefed nsIDocument::GetLocation() const { nsCOMPtr w = do_QueryInterface(mScriptGlobalObject); if (!w) { return nullptr; } nsGlobalWindow* window = nsGlobalWindow::Cast(w); ErrorResult dummy; RefPtr loc = window->GetLocation(dummy); dummy.SuppressException(); return loc.forget(); } Element* nsIDocument::GetHtmlElement() const { Element* rootElement = GetRootElement(); if (rootElement && rootElement->IsHTMLElement(nsGkAtoms::html)) return rootElement; return nullptr; } Element* nsIDocument::GetHtmlChildElement(nsIAtom* aTag) { Element* html = GetHtmlElement(); if (!html) return nullptr; // Look for the element with aTag inside html. This needs to run // forwards to find the first such element. for (nsIContent* child = html->GetFirstChild(); child; child = child->GetNextSibling()) { if (child->IsHTMLElement(aTag)) return child->AsElement(); } return nullptr; } Element* nsDocument::GetTitleElement() { // mMayHaveTitleElement will have been set to true if any HTML or SVG // element has been bound to this document. So if it's false, // we know there is nothing to do here. This avoids us having to search // the whole DOM if someone calls document.title on a large document // without a title. if (!mMayHaveTitleElement) return nullptr; Element* root = GetRootElement(); if (root && root->IsSVGElement(nsGkAtoms::svg)) { // In SVG, the document's title must be a child for (nsIContent* child = root->GetFirstChild(); child; child = child->GetNextSibling()) { if (child->IsSVGElement(nsGkAtoms::title)) { return child->AsElement(); } } return nullptr; } // We check the HTML namespace even for non-HTML documents, except SVG. This // matches the spec and the behavior of all tested browsers. RefPtr<nsContentList> list = NS_GetContentList(this, kNameSpaceID_XHTML, NS_LITERAL_STRING("title")); nsIContent* first = list->Item(0, false); return first ? first->AsElement() : nullptr; } NS_IMETHODIMP nsDocument::GetTitle(nsAString& aTitle) { nsString title; GetTitle(title); aTitle = title; return NS_OK; } void nsDocument::GetTitle(nsString& aTitle) { aTitle.Truncate(); Element* rootElement = GetRootElement(); if (!rootElement) { return; } nsAutoString tmp; #ifdef MOZ_XUL if (rootElement->IsXULElement()) { rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::title, tmp); } else #endif { Element* title = GetTitleElement(); if (!title) { return; } nsContentUtils::GetNodeTextContent(title, false, tmp); } tmp.CompressWhitespace(); aTitle = tmp; } NS_IMETHODIMP nsDocument::SetTitle(const nsAString& aTitle) { Element* rootElement = GetRootElement(); if (!rootElement) { return NS_OK; } #ifdef MOZ_XUL if (rootElement->IsXULElement()) { return rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title, aTitle, true); } #endif // Batch updates so that mutation events don't change "the title // element" under us mozAutoDocUpdate updateBatch(this, UPDATE_CONTENT_MODEL, true); nsCOMPtr<Element> title = GetTitleElement(); if (rootElement->IsSVGElement(nsGkAtoms::svg)) { if (!title) { RefPtr<mozilla::dom::NodeInfo> titleInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::title, nullptr, kNameSpaceID_SVG, nsIDOMNode::ELEMENT_NODE); NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(), NOT_FROM_PARSER); if (!title) { return NS_OK; } rootElement->InsertChildAt(title, 0, true); } } else if (rootElement->IsHTMLElement()) { if (!title) { Element* head = GetHeadElement(); if (!head) { return NS_OK; } RefPtr<mozilla::dom::NodeInfo> titleInfo; titleInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::title, nullptr, kNameSpaceID_XHTML, nsIDOMNode::ELEMENT_NODE); title = NS_NewHTMLTitleElement(titleInfo.forget()); if (!title) { return NS_OK; } head->AppendChildTo(title, true); } } else { return NS_OK; } return nsContentUtils::SetNodeTextContent(title, aTitle, false); } void nsDocument::SetTitle(const nsAString& aTitle, ErrorResult& rv) { rv = SetTitle(aTitle); } void nsDocument::NotifyPossibleTitleChange(bool aBoundTitleElement) { NS_ASSERTION(!mInUnlinkOrDeletion || !aBoundTitleElement, "Setting a title while unlinking or destroying the element?"); if (mInUnlinkOrDeletion) { return; } if (aBoundTitleElement) { mMayHaveTitleElement = true; } if (mPendingTitleChangeEvent.IsPending()) return; RefPtr<nsRunnableMethod<nsDocument, void, false> > event = NewNonOwningRunnableMethod(this, &nsDocument::DoNotifyPossibleTitleChange); nsresult rv = NS_DispatchToCurrentThread(event); if (NS_SUCCEEDED(rv)) { mPendingTitleChangeEvent = event; } } void nsDocument::DoNotifyPossibleTitleChange() { mPendingTitleChangeEvent.Forget(); mHaveFiredTitleChange = true; nsAutoString title; GetTitle(title); nsCOMPtr<nsIPresShell> shell = GetShell(); if (shell) { nsCOMPtr<nsISupports> container = shell->GetPresContext()->GetContainerWeak(); if (container) { nsCOMPtr<nsIBaseWindow> docShellWin = do_QueryInterface(container); if (docShellWin) { docShellWin->SetTitle(title.get()); } } } // Fire a DOM event for the title change. nsContentUtils::DispatchChromeEvent(this, static_cast<nsIDocument*>(this), NS_LITERAL_STRING("DOMTitleChanged"), true, true); } already_AddRefed<BoxObject> nsDocument::GetBoxObjectFor(Element* aElement, ErrorResult& aRv) { if (!aElement) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsIDocument* doc = aElement->OwnerDoc(); if (doc != this) { aRv.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR); return nullptr; } if (!mHasWarnedAboutBoxObjects && !aElement->IsXULElement()) { mHasWarnedAboutBoxObjects = true; nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("BoxObjects"), this, nsContentUtils::eDOM_PROPERTIES, "UseOfGetBoxObjectForWarning"); } if (!mBoxObjectTable) { mBoxObjectTable = new nsInterfaceHashtable<nsPtrHashKey<nsIContent>, nsPIBoxObject>(6); } else { nsCOMPtr<nsPIBoxObject> boxObject = mBoxObjectTable->Get(aElement); if (boxObject) { return boxObject.forget().downcast<BoxObject>(); } } int32_t namespaceID; nsCOMPtr<nsIAtom> tag = BindingManager()->ResolveTag(aElement, &namespaceID); nsAutoCString contractID("@mozilla.org/layout/xul-boxobject"); if (namespaceID == kNameSpaceID_XUL) { if (tag == nsGkAtoms::browser || tag == nsGkAtoms::editor || tag == nsGkAtoms::iframe) contractID += "-container"; else if (tag == nsGkAtoms::menu) contractID += "-menu"; else if (tag == nsGkAtoms::popup || tag == nsGkAtoms::menupopup || tag == nsGkAtoms::panel || tag == nsGkAtoms::tooltip) contractID += "-popup"; else if (tag == nsGkAtoms::tree) contractID += "-tree"; else if (tag == nsGkAtoms::listbox) contractID += "-listbox"; else if (tag == nsGkAtoms::scrollbox) contractID += "-scrollbox"; } contractID += ";1"; nsCOMPtr<nsPIBoxObject> boxObject(do_CreateInstance(contractID.get())); if (!boxObject) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } boxObject->Init(aElement); if (mBoxObjectTable) { mBoxObjectTable->Put(aElement, boxObject.get()); } return boxObject.forget().downcast<BoxObject>(); } void nsDocument::ClearBoxObjectFor(nsIContent* aContent) { if (mBoxObjectTable) { nsPIBoxObject *boxObject = mBoxObjectTable->GetWeak(aContent); if (boxObject) { boxObject->Clear(); mBoxObjectTable->Remove(aContent); } } } already_AddRefed<MediaQueryList> nsIDocument::MatchMedia(const nsAString& aMediaQueryList) { RefPtr<MediaQueryList> result = new MediaQueryList(this, aMediaQueryList); // Insert the new item at the end of the linked list. PR_INSERT_BEFORE(result, &mDOMMediaQueryLists); return result.forget(); } void nsDocument::FlushSkinBindings() { BindingManager()->FlushSkinBindings(); } nsresult nsDocument::InitializeFrameLoader(nsFrameLoader* aLoader) { mInitializableFrameLoaders.RemoveElement(aLoader); // Don't even try to initialize. if (mInDestructor) { NS_WARNING("Trying to initialize a frame loader while" "document is being deleted"); return NS_ERROR_FAILURE; } mInitializableFrameLoaders.AppendElement(aLoader); if (!mFrameLoaderRunner) { mFrameLoaderRunner = NewRunnableMethod(this, &nsDocument::MaybeInitializeFinalizeFrameLoaders); NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY); nsContentUtils::AddScriptRunner(mFrameLoaderRunner); } return NS_OK; } nsresult nsDocument::FinalizeFrameLoader(nsFrameLoader* aLoader, nsIRunnable* aFinalizer) { mInitializableFrameLoaders.RemoveElement(aLoader); if (mInDestructor) { return NS_ERROR_FAILURE; } mFrameLoaderFinalizers.AppendElement(aFinalizer); if (!mFrameLoaderRunner) { mFrameLoaderRunner = NewRunnableMethod(this, &nsDocument::MaybeInitializeFinalizeFrameLoaders); NS_ENSURE_TRUE(mFrameLoaderRunner, NS_ERROR_OUT_OF_MEMORY); nsContentUtils::AddScriptRunner(mFrameLoaderRunner); } return NS_OK; } void nsDocument::MaybeInitializeFinalizeFrameLoaders() { if (mDelayFrameLoaderInitialization || mUpdateNestLevel != 0) { // This method will be recalled when mUpdateNestLevel drops to 0, // or when !mDelayFrameLoaderInitialization. mFrameLoaderRunner = nullptr; return; } // We're not in an update, but it is not safe to run scripts, so // postpone frameloader initialization and finalization. if (!nsContentUtils::IsSafeToRunScript()) { if (!mInDestructor && !mFrameLoaderRunner && (mInitializableFrameLoaders.Length() || mFrameLoaderFinalizers.Length())) { mFrameLoaderRunner = NewRunnableMethod(this, &nsDocument::MaybeInitializeFinalizeFrameLoaders); nsContentUtils::AddScriptRunner(mFrameLoaderRunner); } return; } mFrameLoaderRunner = nullptr; // Don't use a temporary array for mInitializableFrameLoaders, because // loading a frame may cause some other frameloader to be removed from the // array. But be careful to keep the loader alive when starting the load! while (mInitializableFrameLoaders.Length()) { RefPtr<nsFrameLoader> loader = mInitializableFrameLoaders[0]; mInitializableFrameLoaders.RemoveElementAt(0); NS_ASSERTION(loader, "null frameloader in the array?"); loader->ReallyStartLoading(); } uint32_t length = mFrameLoaderFinalizers.Length(); if (length > 0) { nsTArray<nsCOMPtr<nsIRunnable> > finalizers; mFrameLoaderFinalizers.SwapElements(finalizers); for (uint32_t i = 0; i < length; ++i) { finalizers[i]->Run(); } } } void nsDocument::TryCancelFrameLoaderInitialization(nsIDocShell* aShell) { uint32_t length = mInitializableFrameLoaders.Length(); for (uint32_t i = 0; i < length; ++i) { if (mInitializableFrameLoaders[i]->GetExistingDocShell() == aShell) { mInitializableFrameLoaders.RemoveElementAt(i); return; } } } nsIDocument* nsDocument::RequestExternalResource(nsIURI* aURI, nsINode* aRequestingNode, ExternalResourceLoad** aPendingLoad) { NS_PRECONDITION(aURI, "Must have a URI"); NS_PRECONDITION(aRequestingNode, "Must have a node"); if (mDisplayDocument) { return mDisplayDocument->RequestExternalResource(aURI, aRequestingNode, aPendingLoad); } return mExternalResourceMap.RequestResource(aURI, aRequestingNode, this, aPendingLoad); } void nsDocument::EnumerateExternalResources(nsSubDocEnumFunc aCallback, void* aData) { mExternalResourceMap.EnumerateResources(aCallback, aData); } nsSMILAnimationController* nsDocument::GetAnimationController() { // We create the animation controller lazily because most documents won't want // one and only SVG documents and the like will call this if (mAnimationController) return mAnimationController; // Refuse to create an Animation Controller for data documents. if (mLoadedAsData || mLoadedAsInteractiveData) return nullptr; mAnimationController = new nsSMILAnimationController(this); // If there's a presContext then check the animation mode and pause if // necessary. nsIPresShell *shell = GetShell(); if (mAnimationController && shell) { nsPresContext *context = shell->GetPresContext(); if (context && context->ImageAnimationMode() == imgIContainer::kDontAnimMode) { mAnimationController->Pause(nsSMILTimeContainer::PAUSE_USERPREF); } } // If we're hidden (or being hidden), notify the newly-created animation // controller. (Skip this check for SVG-as-an-image documents, though, // because they don't get OnPageShow / OnPageHide calls). if (!mIsShowing && !mIsBeingUsedAsImage) { mAnimationController->OnPageHide(); } return mAnimationController; } PendingAnimationTracker* nsDocument::GetOrCreatePendingAnimationTracker() { if (!mPendingAnimationTracker) { mPendingAnimationTracker = new PendingAnimationTracker(this); } return mPendingAnimationTracker; } /** * Retrieve the "direction" property of the document. * * @lina 01/09/2001 */ NS_IMETHODIMP nsDocument::GetDir(nsAString& aDirection) { nsIDocument::GetDir(aDirection); return NS_OK; } void nsIDocument::GetDir(nsAString& aDirection) const { aDirection.Truncate(); Element* rootElement = GetHtmlElement(); if (rootElement) { static_cast<nsGenericHTMLElement*>(rootElement)->GetDir(aDirection); } } /** * Set the "direction" property of the document. * * @lina 01/09/2001 */ NS_IMETHODIMP nsDocument::SetDir(const nsAString& aDirection) { nsIDocument::SetDir(aDirection); return NS_OK; } void nsIDocument::SetDir(const nsAString& aDirection) { Element* rootElement = GetHtmlElement(); if (rootElement) { rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, aDirection, true); } } NS_IMETHODIMP nsDocument::GetInputEncoding(nsAString& aInputEncoding) { nsIDocument::GetCharacterSet(aInputEncoding); return NS_OK; } NS_IMETHODIMP nsDocument::GetMozSyntheticDocument(bool *aSyntheticDocument) { *aSyntheticDocument = mIsSyntheticDocument; return NS_OK; } NS_IMETHODIMP nsDocument::GetDocumentURI(nsAString& aDocumentURI) { nsString temp; nsresult rv = nsIDocument::GetDocumentURI(temp); aDocumentURI = temp; return rv; } nsresult nsIDocument::GetDocumentURI(nsString& aDocumentURI) const { if (mDocumentURI) { nsAutoCString uri; nsresult rv = mDocumentURI->GetSpec(uri); NS_ENSURE_SUCCESS(rv, rv); CopyUTF8toUTF16(uri, aDocumentURI); } else { aDocumentURI.Truncate(); } return NS_OK; } // Alias of above NS_IMETHODIMP nsDocument::GetURL(nsAString& aURL) { return GetDocumentURI(aURL); } nsresult nsIDocument::GetURL(nsString& aURL) const { return GetDocumentURI(aURL); } void nsIDocument::GetDocumentURIFromJS(nsString& aDocumentURI, ErrorResult& aRv) const { if (!mChromeXHRDocURI || !nsContentUtils::IsCallerChrome()) { aRv = GetDocumentURI(aDocumentURI); return; } nsAutoCString uri; nsresult res = mChromeXHRDocURI->GetSpec(uri); if (NS_FAILED(res)) { aRv.Throw(res); return; } CopyUTF8toUTF16(uri, aDocumentURI); } nsIURI* nsIDocument::GetDocumentURIObject() const { if (!mChromeXHRDocURI) { return GetDocumentURI(); } return mChromeXHRDocURI; } // Returns "BackCompat" if we are in quirks mode, "CSS1Compat" if we are // in almost standards or full standards mode. See bug 105640. This was // implemented to match MSIE's compatMode property. NS_IMETHODIMP nsDocument::GetCompatMode(nsAString& aCompatMode) { nsString temp; nsIDocument::GetCompatMode(temp); aCompatMode = temp; return NS_OK; } void nsIDocument::GetCompatMode(nsString& aCompatMode) const { NS_ASSERTION(mCompatMode == eCompatibility_NavQuirks || mCompatMode == eCompatibility_AlmostStandards || mCompatMode == eCompatibility_FullStandards, "mCompatMode is neither quirks nor strict for this document"); if (mCompatMode == eCompatibility_NavQuirks) { aCompatMode.AssignLiteral("BackCompat"); } else { aCompatMode.AssignLiteral("CSS1Compat"); } } void nsDOMAttributeMap::BlastSubtreeToPieces(nsINode *aNode) { if (aNode->IsElement()) { Element *element = aNode->AsElement(); const nsDOMAttributeMap *map = element->GetAttributeMap(); if (map) { // This non-standard style of iteration is presumably used because some // of the code in the loop body can trigger element removal, which // invalidates the iterator. while (true) { auto iter = map->mAttributeCache.ConstIter(); if (iter.Done()) { break; } nsCOMPtr<nsIAttribute> attr = iter.UserData(); NS_ASSERTION(attr.get(), "non-nsIAttribute somehow made it into the hashmap?!"); BlastSubtreeToPieces(attr); DebugOnly<nsresult> rv = element->UnsetAttr(attr->NodeInfo()->NamespaceID(), attr->NodeInfo()->NameAtom(), false); // XXX Should we abort here? NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!"); } } } uint32_t count = aNode->GetChildCount(); for (uint32_t i = 0; i < count; ++i) { BlastSubtreeToPieces(aNode->GetFirstChild()); aNode->RemoveChildAt(0, false); } } NS_IMETHODIMP nsDocument::AdoptNode(nsIDOMNode *aAdoptedNode, nsIDOMNode **aResult) { *aResult = nullptr; nsCOMPtr<nsINode> adoptedNode = do_QueryInterface(aAdoptedNode); NS_ENSURE_TRUE(adoptedNode, NS_ERROR_UNEXPECTED); ErrorResult rv; nsINode* result = nsIDocument::AdoptNode(*adoptedNode, rv); if (rv.Failed()) { return rv.StealNSResult(); } NS_ADDREF(*aResult = result->AsDOMNode()); return NS_OK; } nsINode* nsIDocument::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv) { nsINode* adoptedNode = &aAdoptedNode; // Scope firing mutation events so that we don't carry any state that // might be stale { nsINode* parent = adoptedNode->GetParentNode(); if (parent) { nsContentUtils::MaybeFireNodeRemoved(adoptedNode, parent, adoptedNode->OwnerDoc()); } } nsAutoScriptBlocker scriptBlocker; switch (adoptedNode->NodeType()) { case nsIDOMNode::ATTRIBUTE_NODE: { // Remove from ownerElement. RefPtr<Attr> adoptedAttr = static_cast<Attr*>(adoptedNode); nsCOMPtr<Element> ownerElement = adoptedAttr->GetOwnerElement(rv); if (rv.Failed()) { return nullptr; } if (ownerElement) { RefPtr<Attr> newAttr = ownerElement->RemoveAttributeNode(*adoptedAttr, rv); if (rv.Failed()) { return nullptr; } newAttr.swap(adoptedAttr); } break; } case nsIDOMNode::DOCUMENT_FRAGMENT_NODE: { if (ShadowRoot::FromNode(adoptedNode)) { rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR); return nullptr; } MOZ_FALLTHROUGH; } case nsIDOMNode::ELEMENT_NODE: case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: case nsIDOMNode::TEXT_NODE: case nsIDOMNode::CDATA_SECTION_NODE: case nsIDOMNode::COMMENT_NODE: case nsIDOMNode::DOCUMENT_TYPE_NODE: { // Don't allow adopting a node's anonymous subtree out from under it. if (adoptedNode->AsContent()->IsRootOfAnonymousSubtree()) { rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return nullptr; } // We don't want to adopt an element into its own contentDocument or into // a descendant contentDocument, so we check if the frameElement of this // document or any of its parents is the adopted node or one of its // descendants. nsIDocument *doc = this; do { if (nsPIDOMWindowOuter *win = doc->GetWindow()) { nsCOMPtr<nsINode> node = win->GetFrameElementInternal(); if (node && nsContentUtils::ContentIsDescendantOf(node, adoptedNode)) { rv.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR); return nullptr; } } } while ((doc = doc->GetParentDocument())); // Remove from parent. nsCOMPtr<nsINode> parent = adoptedNode->GetParentNode(); if (parent) { int32_t idx = parent->IndexOf(adoptedNode); MOZ_ASSERT(idx >= 0); parent->RemoveChildAt(idx, true); } else { MOZ_ASSERT(!adoptedNode->IsInUncomposedDoc()); // If we're adopting a node that's not in a document, it might still // have a binding applied. Remove the binding from the element now // that it's getting adopted into a new document. // TODO Fully tear down the binding. adoptedNode->AsContent()->SetXBLBinding(nullptr); } break; } case nsIDOMNode::DOCUMENT_NODE: { rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return nullptr; } default: { NS_WARNING("Don't know how to adopt this nodetype for adoptNode."); rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return nullptr; } } nsCOMPtr<nsIDocument> oldDocument = adoptedNode->OwnerDoc(); bool sameDocument = oldDocument == this; AutoJSContext cx; JS::Rooted<JSObject*> newScope(cx, nullptr); if (!sameDocument) { newScope = GetWrapper(); if (!newScope && GetScopeObject() && GetScopeObject()->GetGlobalJSObject()) { // Make sure cx is in a semi-sane compartment before we call WrapNative. // It's kind of irrelevant, given that we're passing aAllowWrapping = // false, and documents should always insist on being wrapped in an // canonical scope. But we try to pass something sane anyway. JSAutoCompartment ac(cx, GetScopeObject()->GetGlobalJSObject()); JS::Rooted<JS::Value> v(cx); rv = nsContentUtils::WrapNative(cx, this, this, &v, /* aAllowWrapping = */ false); if (rv.Failed()) return nullptr; newScope = &v.toObject(); } } nsCOMArray<nsINode> nodesWithProperties; rv = nsNodeUtils::Adopt(adoptedNode, sameDocument ? nullptr : mNodeInfoManager, newScope, nodesWithProperties); if (rv.Failed()) { // Disconnect all nodes from their parents, since some have the old document // as their ownerDocument and some have this as their ownerDocument. nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode); if (!sameDocument && oldDocument) { uint32_t count = nodesWithProperties.Count(); for (uint32_t j = 0; j < oldDocument->GetPropertyTableCount(); ++j) { for (uint32_t i = 0; i < count; ++i) { // Remove all properties. oldDocument->PropertyTable(j)-> DeleteAllPropertiesFor(nodesWithProperties[i]); } } } return nullptr; } uint32_t count = nodesWithProperties.Count(); if (!sameDocument && oldDocument) { for (uint32_t j = 0; j < oldDocument->GetPropertyTableCount(); ++j) { nsPropertyTable *oldTable = oldDocument->PropertyTable(j); nsPropertyTable *newTable = PropertyTable(j); for (uint32_t i = 0; i < count; ++i) { rv = oldTable->TransferOrDeleteAllPropertiesFor(nodesWithProperties[i], newTable); } } if (rv.Failed()) { // Disconnect all nodes from their parents. nsDOMAttributeMap::BlastSubtreeToPieces(adoptedNode); return nullptr; } } NS_ASSERTION(adoptedNode->OwnerDoc() == this, "Should still be in the document we just got adopted into"); return adoptedNode; } nsViewportInfo nsDocument::GetViewportInfo(const ScreenIntSize& aDisplaySize) { // Compute the CSS-to-LayoutDevice pixel scale as the product of the // widget scale and the full zoom. nsPresContext* context = mPresShell->GetPresContext(); // When querying the full zoom, get it from the device context rather than // directly from the pres context, because the device context's value can // include an adjustment necessay to keep the number of app units per device // pixel an integer, and we want the adjusted value. float fullZoom = context ? context->DeviceContext()->GetFullZoom() : 1.0; fullZoom = (fullZoom == 0.0) ? 1.0 : fullZoom; CSSToLayoutDeviceScale layoutDeviceScale = context ? context->CSSToDevPixelScale() : CSSToLayoutDeviceScale(1); CSSToScreenScale defaultScale = layoutDeviceScale * LayoutDeviceToScreenScale(1.0); // Special behaviour for desktop mode, provided we are not on an about: page nsPIDOMWindowOuter* win = GetWindow(); if (win && win->IsDesktopModeViewport() && !IsAboutPage()) { CSSCoord viewportWidth = gfxPrefs::DesktopViewportWidth() / fullZoom; CSSToScreenScale scaleToFit(aDisplaySize.width / viewportWidth); float aspectRatio = (float)aDisplaySize.height / aDisplaySize.width; CSSSize viewportSize(viewportWidth, viewportWidth * aspectRatio); ScreenIntSize fakeDesktopSize = RoundedToInt(viewportSize * scaleToFit); return nsViewportInfo(fakeDesktopSize, scaleToFit, /*allowZoom*/ true); } if (!gfxPrefs::MetaViewportEnabled()) { return nsViewportInfo(aDisplaySize, defaultScale, /*allowZoom*/ false); } // In cases where the width of the CSS viewport is less than or equal to the width // of the display (i.e. width <= device-width) then we disable double-tap-to-zoom // behaviour. See bug 941995 for details. switch (mViewportType) { case DisplayWidthHeight: return nsViewportInfo(aDisplaySize, defaultScale, /*allowZoom*/ true); case Unknown: { nsAutoString viewport; GetHeaderData(nsGkAtoms::viewport, viewport); if (viewport.IsEmpty()) { // If the docType specifies that we are on a site optimized for mobile, // then we want to return specially crafted defaults for the viewport info. nsCOMPtr<nsIDOMDocumentType> docType; nsresult rv = GetDoctype(getter_AddRefs(docType)); if (NS_SUCCEEDED(rv) && docType) { nsAutoString docId; rv = docType->GetPublicId(docId); if (NS_SUCCEEDED(rv)) { if ((docId.Find("WAP") != -1) || (docId.Find("Mobile") != -1) || (docId.Find("WML") != -1)) { // We're making an assumption that the docType can't change here mViewportType = DisplayWidthHeight; return nsViewportInfo(aDisplaySize, defaultScale, /*allowZoom*/true); } } } nsAutoString handheldFriendly; GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly); if (handheldFriendly.EqualsLiteral("true")) { mViewportType = DisplayWidthHeight; return nsViewportInfo(aDisplaySize, defaultScale, /*allowZoom*/true); } } nsAutoString minScaleStr; GetHeaderData(nsGkAtoms::viewport_minimum_scale, minScaleStr); nsresult errorCode; mScaleMinFloat = LayoutDeviceToScreenScale(minScaleStr.ToFloat(&errorCode)); if (NS_FAILED(errorCode)) { mScaleMinFloat = kViewportMinScale; } mScaleMinFloat = mozilla::clamped( mScaleMinFloat, kViewportMinScale, kViewportMaxScale); nsAutoString maxScaleStr; GetHeaderData(nsGkAtoms::viewport_maximum_scale, maxScaleStr); // We define a special error code variable for the scale and max scale, // because they are used later (see the width calculations). nsresult scaleMaxErrorCode; mScaleMaxFloat = LayoutDeviceToScreenScale(maxScaleStr.ToFloat(&scaleMaxErrorCode)); if (NS_FAILED(scaleMaxErrorCode)) { mScaleMaxFloat = kViewportMaxScale; } mScaleMaxFloat = mozilla::clamped( mScaleMaxFloat, kViewportMinScale, kViewportMaxScale); nsAutoString scaleStr; GetHeaderData(nsGkAtoms::viewport_initial_scale, scaleStr); nsresult scaleErrorCode; mScaleFloat = LayoutDeviceToScreenScale(scaleStr.ToFloat(&scaleErrorCode)); nsAutoString widthStr, heightStr; GetHeaderData(nsGkAtoms::viewport_height, heightStr); GetHeaderData(nsGkAtoms::viewport_width, widthStr); mAutoSize = false; if (widthStr.EqualsLiteral("device-width")) { mAutoSize = true; } if (widthStr.IsEmpty() && (heightStr.EqualsLiteral("device-height") || (mScaleFloat.scale == 1.0))) { mAutoSize = true; } nsresult widthErrorCode, heightErrorCode; mViewportSize.width = widthStr.ToInteger(&widthErrorCode); mViewportSize.height = heightStr.ToInteger(&heightErrorCode); // If width or height has not been set to a valid number by this point, // fall back to a default value. mValidWidth = (!widthStr.IsEmpty() && NS_SUCCEEDED(widthErrorCode) && mViewportSize.width > 0); mValidHeight = (!heightStr.IsEmpty() && NS_SUCCEEDED(heightErrorCode) && mViewportSize.height > 0); mAllowZoom = true; nsAutoString userScalable; GetHeaderData(nsGkAtoms::viewport_user_scalable, userScalable); if ((userScalable.EqualsLiteral("0")) || (userScalable.EqualsLiteral("no")) || (userScalable.EqualsLiteral("false"))) { mAllowZoom = false; } mScaleStrEmpty = scaleStr.IsEmpty(); mWidthStrEmpty = widthStr.IsEmpty(); mValidScaleFloat = !scaleStr.IsEmpty() && NS_SUCCEEDED(scaleErrorCode); mValidMaxScale = !maxScaleStr.IsEmpty() && NS_SUCCEEDED(scaleMaxErrorCode); mViewportType = Specified; MOZ_FALLTHROUGH; } case Specified: default: LayoutDeviceToScreenScale effectiveMinScale = mScaleMinFloat; LayoutDeviceToScreenScale effectiveMaxScale = mScaleMaxFloat; bool effectiveValidMaxScale = mValidMaxScale; bool effectiveAllowZoom = mAllowZoom; if (gfxPrefs::ForceUserScalable()) { // If the pref to force user-scalable is enabled, we ignore the values // from the meta-viewport tag for these properties and just assume they // allow the page to be scalable. Note in particular that this code is // in the "Specified" branch of the enclosing switch statement, so that // calls to GetViewportInfo always use the latest value of the // ForceUserScalable pref. Other codepaths that return nsViewportInfo // instances are all consistent with ForceUserScalable() already. effectiveMinScale = kViewportMinScale; effectiveMaxScale = kViewportMaxScale; effectiveValidMaxScale = true; effectiveAllowZoom = true; } CSSSize size = mViewportSize; if (!mValidWidth) { if (mValidHeight && !aDisplaySize.IsEmpty()) { size.width = size.height * aDisplaySize.width / aDisplaySize.height; } else { // Stretch CSS pixel size of viewport to keep device pixel size // unchanged after full zoom applied. // See bug 974242. size.width = gfxPrefs::DesktopViewportWidth() / fullZoom; } } if (!mValidHeight) { if (!aDisplaySize.IsEmpty()) { size.height = size.width * aDisplaySize.height / aDisplaySize.width; } else { size.height = size.width; } } CSSToScreenScale scaleFloat = mScaleFloat * layoutDeviceScale; CSSToScreenScale scaleMinFloat = effectiveMinScale * layoutDeviceScale; CSSToScreenScale scaleMaxFloat = effectiveMaxScale * layoutDeviceScale; if (mAutoSize) { // aDisplaySize is in screen pixels; convert them to CSS pixels for the viewport size. CSSToScreenScale defaultPixelScale = layoutDeviceScale * LayoutDeviceToScreenScale(1.0f); size = ScreenSize(aDisplaySize) / defaultPixelScale; } size.width = clamped(size.width, float(kViewportMinSize.width), float(kViewportMaxSize.width)); // Also recalculate the default zoom, if it wasn't specified in the metadata, // and the width is specified. if (mScaleStrEmpty && !mWidthStrEmpty) { CSSToScreenScale defaultScale(float(aDisplaySize.width) / size.width); scaleFloat = (scaleFloat > defaultScale) ? scaleFloat : defaultScale; } size.height = clamped(size.height, float(kViewportMinSize.height), float(kViewportMaxSize.height)); // We need to perform a conversion, but only if the initial or maximum // scale were set explicitly by the user. if (mValidScaleFloat && scaleFloat >= scaleMinFloat && scaleFloat <= scaleMaxFloat) { CSSSize displaySize = ScreenSize(aDisplaySize) / scaleFloat; size.width = std::max(size.width, displaySize.width); size.height = std::max(size.height, displaySize.height); } else if (effectiveValidMaxScale) { CSSSize displaySize = ScreenSize(aDisplaySize) / scaleMaxFloat; size.width = std::max(size.width, displaySize.width); size.height = std::max(size.height, displaySize.height); } return nsViewportInfo(scaleFloat, scaleMinFloat, scaleMaxFloat, size, mAutoSize, effectiveAllowZoom); } } EventListenerManager* nsDocument::GetOrCreateListenerManager() { if (!mListenerManager) { mListenerManager = new EventListenerManager(static_cast<EventTarget*>(this)); SetFlags(NODE_HAS_LISTENERMANAGER); } return mListenerManager; } EventListenerManager* nsDocument::GetExistingListenerManager() const { return mListenerManager; } nsresult nsDocument::PreHandleEvent(EventChainPreVisitor& aVisitor) { aVisitor.mCanHandle = true; // FIXME! This is a hack to make middle mouse paste working also in Editor. // Bug 329119 aVisitor.mForceContentDispatch = true; // Load events must not propagate to |window| object, see bug 335251. if (aVisitor.mEvent->mMessage != eLoad) { nsGlobalWindow* window = nsGlobalWindow::Cast(GetWindow()); aVisitor.mParentTarget = window ? window->GetTargetForEventTargetChain() : nullptr; } return NS_OK; } NS_IMETHODIMP nsDocument::CreateEvent(const nsAString& aEventType, nsIDOMEvent** aReturn) { NS_ENSURE_ARG_POINTER(aReturn); ErrorResult rv; *aReturn = nsIDocument::CreateEvent(aEventType, rv).take(); return rv.StealNSResult(); } already_AddRefed<Event> nsIDocument::CreateEvent(const nsAString& aEventType, ErrorResult& rv) const { nsIPresShell *shell = GetShell(); nsPresContext *presContext = nullptr; if (shell) { // Retrieve the context presContext = shell->GetPresContext(); } // Create event even without presContext. RefPtr<Event> ev = EventDispatcher::CreateEvent(const_cast<nsIDocument*>(this), presContext, nullptr, aEventType); if (!ev) { rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return nullptr; } WidgetEvent* e = ev->WidgetEventPtr(); e->mFlags.mBubbles = false; e->mFlags.mCancelable = false; return ev.forget(); } void nsDocument::FlushPendingNotifications(mozFlushType aType) { nsDocumentOnStack dos(this); // We need to flush the sink for non-HTML documents (because the XML // parser still does insertion with deferred notifications). We // also need to flush the sink if this is a layout-related flush, to // make sure that layout is started as needed. But we can skip that // part if we have no presshell or if it's already done an initial // reflow. if ((!IsHTMLDocument() || (aType > Flush_ContentAndNotify && mPresShell && !mPresShell->DidInitialize())) && (mParser || mWeakSink)) { nsCOMPtr<nsIContentSink> sink; if (mParser) { sink = mParser->GetContentSink(); } else { sink = do_QueryReferent(mWeakSink); if (!sink) { mWeakSink = nullptr; } } // Determine if it is safe to flush the sink notifications // by determining if it safe to flush all the presshells. if (sink && (aType == Flush_Content || IsSafeToFlush())) { sink->FlushPendingNotifications(aType); } } // Should we be flushing pending binding constructors in here? if (aType <= Flush_ContentAndNotify) { // Nothing to do here return; } // If we have a parent we must flush the parent too to ensure that our // container is reflowed if its size was changed. But if it's not safe to // flush ourselves, then don't flush the parent, since that can cause things // like resizes of our frame's widget, which we can't handle while flushing // is unsafe. // Since media queries mean that a size change of our container can // affect style, we need to promote a style flush on ourself to a // layout flush on our parent, since we need our container to be the // correct size to determine the correct style. if (mParentDocument && IsSafeToFlush()) { mozFlushType parentType = aType; if (aType >= Flush_Style) parentType = std::max(Flush_Layout, aType); mParentDocument->FlushPendingNotifications(parentType); } // We can optimize away getting our presshell and calling // FlushPendingNotifications on it if we don't need a flush of the sort we're // looking at. The one exception is if mInFlush is true, because in that // case we might have set mNeedStyleFlush and mNeedLayoutFlush to false // already but the presshell hasn't actually done the corresponding work yet. // So if mInFlush and reentering this code, we need to flush the presshell. if (mNeedStyleFlush || (mNeedLayoutFlush && aType >= Flush_InterruptibleLayout) || aType >= Flush_Display || mInFlush) { nsCOMPtr<nsIPresShell> shell = GetShell(); if (shell) { mNeedStyleFlush = false; mNeedLayoutFlush = mNeedLayoutFlush && (aType < Flush_InterruptibleLayout); // mInFlush is a bitfield, so can't us AutoRestore here. But we // need to keep track of multi-level reentry correctly, so need // to restore the old mInFlush value. bool oldInFlush = mInFlush; mInFlush = true; shell->FlushPendingNotifications(aType); mInFlush = oldInFlush; } } } static bool Copy(nsIDocument* aDocument, void* aData) { nsTArray<nsCOMPtr<nsIDocument> >* resources = static_cast<nsTArray<nsCOMPtr<nsIDocument> >* >(aData); resources->AppendElement(aDocument); return true; } void nsDocument::FlushExternalResources(mozFlushType aType) { NS_ASSERTION(aType >= Flush_Style, "should only need to flush for style or higher in external resources"); if (GetDisplayDocument()) { return; } nsTArray<nsCOMPtr<nsIDocument> > resources; EnumerateExternalResources(Copy, &resources); for (uint32_t i = 0; i < resources.Length(); i++) { resources[i]->FlushPendingNotifications(aType); } } void nsDocument::SetXMLDeclaration(const char16_t *aVersion, const char16_t *aEncoding, const int32_t aStandalone) { if (!aVersion || *aVersion == '\0') { mXMLDeclarationBits = 0; return; } mXMLDeclarationBits = XML_DECLARATION_BITS_DECLARATION_EXISTS; if (aEncoding && *aEncoding != '\0') { mXMLDeclarationBits |= XML_DECLARATION_BITS_ENCODING_EXISTS; } if (aStandalone == 1) { mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS | XML_DECLARATION_BITS_STANDALONE_YES; } else if (aStandalone == 0) { mXMLDeclarationBits |= XML_DECLARATION_BITS_STANDALONE_EXISTS; } } void nsDocument::GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding, nsAString& aStandalone) { aVersion.Truncate(); aEncoding.Truncate(); aStandalone.Truncate(); if (!(mXMLDeclarationBits & XML_DECLARATION_BITS_DECLARATION_EXISTS)) { return; } // always until we start supporting 1.1 etc. aVersion.AssignLiteral("1.0"); if (mXMLDeclarationBits & XML_DECLARATION_BITS_ENCODING_EXISTS) { // This is what we have stored, not necessarily what was written // in the original GetCharacterSet(aEncoding); } if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_EXISTS) { if (mXMLDeclarationBits & XML_DECLARATION_BITS_STANDALONE_YES) { aStandalone.AssignLiteral("yes"); } else { aStandalone.AssignLiteral("no"); } } } bool nsDocument::IsScriptEnabled() { // If this document is sandboxed without 'allow-scripts' // script is not enabled if (HasScriptsBlockedBySandbox()) { return false; } nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(GetInnerWindow()); if (!globalObject && mMasterDocument) { globalObject = do_QueryInterface(mMasterDocument->GetInnerWindow()); } if (!globalObject || !globalObject->GetGlobalJSObject()) { return false; } return xpc::Scriptability::Get(globalObject->GetGlobalJSObject()).Allowed(); } nsRadioGroupStruct* nsDocument::GetRadioGroupInternal(const nsAString& aName) const { #ifdef DEBUG if (IsHTMLDocument()) { nsAutoString lcName; ToLowerCase(aName, lcName); MOZ_ASSERT(aName == lcName); } #endif nsRadioGroupStruct* radioGroup; if (!mRadioGroups.Get(aName, &radioGroup)) { return nullptr; } return radioGroup; } nsRadioGroupStruct* nsDocument::GetRadioGroup(const nsAString& aName) const { nsAutoString tmKey(aName); if (IsHTMLDocument()) { ToLowerCase(tmKey); //should case-insensitive. } return GetRadioGroupInternal(tmKey); } nsRadioGroupStruct* nsDocument::GetOrCreateRadioGroup(const nsAString& aName) { nsAutoString tmKey(aName); if (IsHTMLDocument()) { ToLowerCase(tmKey); //should case-insensitive. } if (nsRadioGroupStruct* radioGroup = GetRadioGroupInternal(tmKey)) { return radioGroup; } nsAutoPtr<nsRadioGroupStruct> newRadioGroup(new nsRadioGroupStruct()); mRadioGroups.Put(tmKey, newRadioGroup); return newRadioGroup.forget(); } void nsDocument::SetCurrentRadioButton(const nsAString& aName, HTMLInputElement* aRadio) { nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); radioGroup->mSelectedRadioButton = aRadio; } HTMLInputElement* nsDocument::GetCurrentRadioButton(const nsAString& aName) { return GetOrCreateRadioGroup(aName)->mSelectedRadioButton; } NS_IMETHODIMP nsDocument::GetNextRadioButton(const nsAString& aName, const bool aPrevious, HTMLInputElement* aFocusedRadio, HTMLInputElement** aRadioOut) { // XXX Can we combine the HTML radio button method impls of // nsDocument and nsHTMLFormControl? // XXX Why is HTML radio button stuff in nsDocument, as // opposed to nsHTMLDocument? *aRadioOut = nullptr; nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); // Return the radio button relative to the focused radio button. // If no radio is focused, get the radio relative to the selected one. RefPtr<HTMLInputElement> currentRadio; if (aFocusedRadio) { currentRadio = aFocusedRadio; } else { currentRadio = radioGroup->mSelectedRadioButton; if (!currentRadio) { return NS_ERROR_FAILURE; } } int32_t index = radioGroup->mRadioButtons.IndexOf(currentRadio); if (index < 0) { return NS_ERROR_FAILURE; } int32_t numRadios = radioGroup->mRadioButtons.Count(); RefPtr<HTMLInputElement> radio; do { if (aPrevious) { if (--index < 0) { index = numRadios -1; } } else if (++index >= numRadios) { index = 0; } NS_ASSERTION(static_cast<nsGenericHTMLFormElement*>(radioGroup->mRadioButtons[index])->IsHTMLElement(nsGkAtoms::input), "mRadioButtons holding a non-radio button"); radio = static_cast<HTMLInputElement*>(radioGroup->mRadioButtons[index]); } while (radio->Disabled() && radio != currentRadio); radio.forget(aRadioOut); return NS_OK; } void nsDocument::AddToRadioGroup(const nsAString& aName, nsIFormControl* aRadio) { nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); radioGroup->mRadioButtons.AppendObject(aRadio); nsCOMPtr<nsIContent> element = do_QueryInterface(aRadio); NS_ASSERTION(element, "radio controls have to be content elements"); if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) { radioGroup->mRequiredRadioCount++; } } void nsDocument::RemoveFromRadioGroup(const nsAString& aName, nsIFormControl* aRadio) { nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); radioGroup->mRadioButtons.RemoveObject(aRadio); nsCOMPtr<nsIContent> element = do_QueryInterface(aRadio); NS_ASSERTION(element, "radio controls have to be content elements"); if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::required)) { NS_ASSERTION(radioGroup->mRequiredRadioCount != 0, "mRequiredRadioCount about to wrap below 0!"); radioGroup->mRequiredRadioCount--; } } NS_IMETHODIMP nsDocument::WalkRadioGroup(const nsAString& aName, nsIRadioVisitor* aVisitor, bool aFlushContent) { nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); for (int i = 0; i < radioGroup->mRadioButtons.Count(); i++) { if (!aVisitor->Visit(radioGroup->mRadioButtons[i])) { return NS_OK; } } return NS_OK; } uint32_t nsDocument::GetRequiredRadioCount(const nsAString& aName) const { nsRadioGroupStruct* radioGroup = GetRadioGroup(aName); return radioGroup ? radioGroup->mRequiredRadioCount : 0; } void nsDocument::RadioRequiredWillChange(const nsAString& aName, bool aRequiredAdded) { nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); if (aRequiredAdded) { radioGroup->mRequiredRadioCount++; } else { NS_ASSERTION(radioGroup->mRequiredRadioCount != 0, "mRequiredRadioCount about to wrap below 0!"); radioGroup->mRequiredRadioCount--; } } bool nsDocument::GetValueMissingState(const nsAString& aName) const { nsRadioGroupStruct* radioGroup = GetRadioGroup(aName); return radioGroup && radioGroup->mGroupSuffersFromValueMissing; } void nsDocument::SetValueMissingState(const nsAString& aName, bool aValue) { nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); radioGroup->mGroupSuffersFromValueMissing = aValue; } void nsDocument::RetrieveRelevantHeaders(nsIChannel *aChannel) { PRTime modDate = 0; nsresult rv; nsCOMPtr<nsIHttpChannel> httpChannel; rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel)); if (NS_WARN_IF(NS_FAILED(rv))) { return; } if (httpChannel) { nsAutoCString tmp; rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("last-modified"), tmp); if (NS_SUCCEEDED(rv)) { PRTime time; PRStatus st = PR_ParseTimeString(tmp.get(), true, &time); if (st == PR_SUCCESS) { modDate = time; } } // The misspelled key 'referer' is as per the HTTP spec rv = httpChannel->GetRequestHeader(NS_LITERAL_CSTRING("referer"), mReferrer); static const char *const headers[] = { "default-style", "content-style-type", "content-language", "content-disposition", "refresh", "x-dns-prefetch-control", "x-frame-options", "referrer-policy", // add more http headers if you need // XXXbz don't add content-location support without reading bug // 238654 and its dependencies/dups first. 0 }; nsAutoCString headerVal; const char *const *name = headers; while (*name) { rv = httpChannel->GetResponseHeader(nsDependentCString(*name), headerVal); if (NS_SUCCEEDED(rv) && !headerVal.IsEmpty()) { nsCOMPtr<nsIAtom> key = NS_Atomize(*name); SetHeaderData(key, NS_ConvertASCIItoUTF16(headerVal)); } ++name; } } else { nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(aChannel); if (fileChannel) { nsCOMPtr<nsIFile> file; fileChannel->GetFile(getter_AddRefs(file)); if (file) { PRTime msecs; rv = file->GetLastModifiedTime(&msecs); if (NS_SUCCEEDED(rv)) { modDate = msecs * int64_t(PR_USEC_PER_MSEC); } } } else { nsAutoCString contentDisp; rv = aChannel->GetContentDispositionHeader(contentDisp); if (NS_SUCCEEDED(rv)) { SetHeaderData(nsGkAtoms::headerContentDisposition, NS_ConvertASCIItoUTF16(contentDisp)); } } } mLastModified.Truncate(); if (modDate != 0) { GetFormattedTimeString(modDate, mLastModified); } } already_AddRefed<Element> nsDocument::CreateElem(const nsAString& aName, nsIAtom *aPrefix, int32_t aNamespaceID, const nsAString* aIs) { #ifdef DEBUG nsAutoString qName; if (aPrefix) { aPrefix->ToString(qName); qName.Append(':'); } qName.Append(aName); // Note: "a:b:c" is a valid name in non-namespaces XML, and // nsDocument::CreateElement can call us with such a name and no prefix, // which would cause an error if we just used true here. bool nsAware = aPrefix != nullptr || aNamespaceID != GetDefaultNamespaceID(); NS_ASSERTION(NS_SUCCEEDED(nsContentUtils::CheckQName(qName, nsAware)), "Don't pass invalid prefixes to nsDocument::CreateElem, " "check caller."); #endif RefPtr<mozilla::dom::NodeInfo> nodeInfo; mNodeInfoManager->GetNodeInfo(aName, aPrefix, aNamespaceID, nsIDOMNode::ELEMENT_NODE, getter_AddRefs(nodeInfo)); NS_ENSURE_TRUE(nodeInfo, nullptr); nsCOMPtr<Element> element; nsresult rv = NS_NewElement(getter_AddRefs(element), nodeInfo.forget(), NOT_FROM_PARSER, aIs); return NS_SUCCEEDED(rv) ? element.forget() : nullptr; } bool nsDocument::IsSafeToFlush() const { nsIPresShell* shell = GetShell(); if (!shell) return true; return shell->IsSafeToFlush(); } void nsDocument::Sanitize() { // Sanitize the document by resetting all password fields and any form // fields with autocomplete=off to their default values. We do this now, // instead of when the presentation is restored, to offer some protection // in case there is ever an exploit that allows a cached document to be // accessed from a different document. // First locate all input elements, regardless of whether they are // in a form, and reset the password and autocomplete=off elements. RefPtr<nsContentList> nodes = GetElementsByTagName(NS_LITERAL_STRING("input")); nsAutoString value; uint32_t length = nodes->Length(true); for (uint32_t i = 0; i < length; ++i) { NS_ASSERTION(nodes->Item(i), "null item in node list!"); RefPtr<HTMLInputElement> input = HTMLInputElement::FromContentOrNull(nodes->Item(i)); if (!input) continue; bool resetValue = false; input->GetAttribute(NS_LITERAL_STRING("autocomplete"), value); if (value.LowerCaseEqualsLiteral("off")) { resetValue = true; } else { input->GetType(value); if (value.LowerCaseEqualsLiteral("password")) resetValue = true; } if (resetValue) { input->Reset(); } } // Now locate all _form_ elements that have autocomplete=off and reset them nodes = GetElementsByTagName(NS_LITERAL_STRING("form")); length = nodes->Length(true); for (uint32_t i = 0; i < length; ++i) { NS_ASSERTION(nodes->Item(i), "null item in nodelist"); nsCOMPtr<nsIDOMHTMLFormElement> form = do_QueryInterface(nodes->Item(i)); if (!form) continue; nodes->Item(i)->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, value); if (value.LowerCaseEqualsLiteral("off")) form->Reset(); } } struct SubDocEnumArgs { nsIDocument::nsSubDocEnumFunc callback; void *data; }; void nsDocument::EnumerateSubDocuments(nsSubDocEnumFunc aCallback, void *aData) { if (!mSubDocuments) { return; } // PLDHashTable::Iterator can't handle modifications while iterating so we // copy all entries to an array first before calling any callbacks. AutoTArray<nsCOMPtr<nsIDocument>, 8> subdocs; for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) { auto entry = static_cast<SubDocMapEntry*>(iter.Get()); nsIDocument* subdoc = entry->mSubDocument; if (subdoc) { subdocs.AppendElement(subdoc); } } for (auto subdoc : subdocs) { if (!aCallback(subdoc, aData)) { break; } } } #ifdef DEBUG_bryner #define DEBUG_PAGE_CACHE #endif bool nsDocument::CanSavePresentation(nsIRequest *aNewRequest) { if (EventHandlingSuppressed()) { return false; } // Do not allow suspended windows to be placed in the // bfcache. This method is also used to verify a document // coming out of the bfcache is ok to restore, though. So // we only want to block suspend windows that aren't also // frozen. nsPIDOMWindowInner* win = GetInnerWindow(); if (win && win->IsSuspended() && !win->IsFrozen()) { return false; } // Check our event listener manager for unload/beforeunload listeners. nsCOMPtr<EventTarget> piTarget = do_QueryInterface(mScriptGlobalObject); if (piTarget) { EventListenerManager* manager = piTarget->GetExistingListenerManager(); if (manager && manager->HasUnloadListeners()) { return false; } } // Check if we have pending network requests nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup(); if (loadGroup) { nsCOMPtr<nsISimpleEnumerator> requests; loadGroup->GetRequests(getter_AddRefs(requests)); bool hasMore = false; // We want to bail out if we have any requests other than aNewRequest (or // in the case when aNewRequest is a part of a multipart response the base // channel the multipart response is coming in on). nsCOMPtr<nsIChannel> baseChannel; nsCOMPtr<nsIMultiPartChannel> part(do_QueryInterface(aNewRequest)); if (part) { part->GetBaseChannel(getter_AddRefs(baseChannel)); } while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr<nsISupports> elem; requests->GetNext(getter_AddRefs(elem)); nsCOMPtr<nsIRequest> request = do_QueryInterface(elem); if (request && request != aNewRequest && request != baseChannel) { #ifdef DEBUG_PAGE_CACHE nsAutoCString requestName, docSpec; request->GetName(requestName); if (mDocumentURI) mDocumentURI->GetSpec(docSpec); printf("document %s has request %s\n", docSpec.get(), requestName.get()); #endif return false; } } } // Check if we have active GetUserMedia use if (MediaManager::Exists() && win && MediaManager::Get()->IsWindowStillActive(win->WindowID())) { return false; } #ifdef MOZ_WEBRTC // Check if we have active PeerConnections nsCOMPtr<IPeerConnectionManager> pcManager = do_GetService(IPEERCONNECTION_MANAGER_CONTRACTID); if (pcManager && win) { bool active; pcManager->HasActivePeerConnection(win->WindowID(), &active); if (active) { return false; } } #endif // MOZ_WEBRTC #ifdef MOZ_EME // Don't save presentations for documents containing EME content, so that // CDMs reliably shutdown upon user navigation. if (ContainsEMEContent()) { return false; } #endif // Don't save presentations for documents containing MSE content, to // reduce memory usage. if (ContainsMSEContent()) { return false; } if (mSubDocuments) { for (auto iter = mSubDocuments->Iter(); !iter.Done(); iter.Next()) { auto entry = static_cast<SubDocMapEntry*>(iter.Get()); nsIDocument* subdoc = entry->mSubDocument; // The aIgnoreRequest we were passed is only for us, so don't pass it on. bool canCache = subdoc ? subdoc->CanSavePresentation(nullptr) : false; if (!canCache) { return false; } } } #ifdef MOZ_WEBSPEECH if (win) { auto* globalWindow = nsGlobalWindow::Cast(win); if (globalWindow->HasActiveSpeechSynthesis()) { return false; } } #endif return true; } void nsDocument::Destroy() { // The ContentViewer wants to release the document now. So, tell our content // to drop any references to the document so that it can be destroyed. if (mIsGoingAway) return; mIsGoingAway = true; SetScriptGlobalObject(nullptr); RemovedFromDocShell(); bool oldVal = mInUnlinkOrDeletion; mInUnlinkOrDeletion = true; uint32_t i, count = mChildren.ChildCount(); for (i = 0; i < count; ++i) { mChildren.ChildAt(i)->DestroyContent(); } mInUnlinkOrDeletion = oldVal; mLayoutHistoryState = nullptr; // Shut down our external resource map. We might not need this for // leak-fixing if we fix nsDocumentViewer to do cycle-collection, but // tearing down all those frame trees right now is the right thing to do. mExternalResourceMap.Shutdown(); } void nsDocument::RemovedFromDocShell() { if (mRemovedFromDocShell) return; mRemovedFromDocShell = true; EnumerateActivityObservers(NotifyActivityChanged, nullptr); uint32_t i, count = mChildren.ChildCount(); for (i = 0; i < count; ++i) { mChildren.ChildAt(i)->SaveSubtreeState(); } } already_AddRefed<nsILayoutHistoryState> nsDocument::GetLayoutHistoryState() const { nsCOMPtr<nsILayoutHistoryState> state; if (!mScriptGlobalObject) { state = mLayoutHistoryState; } else { nsCOMPtr<nsIDocShell> docShell(mDocumentContainer); if (docShell) { docShell->GetLayoutHistoryState(getter_AddRefs(state)); } } return state.forget(); } void nsDocument::EnsureOnloadBlocker() { // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup // -- it's not ours. if (mOnloadBlockCount != 0 && mScriptGlobalObject) { nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup(); if (loadGroup) { // Check first to see if mOnloadBlocker is in the loadgroup. nsCOMPtr<nsISimpleEnumerator> requests; loadGroup->GetRequests(getter_AddRefs(requests)); bool hasMore = false; while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr<nsISupports> elem; requests->GetNext(getter_AddRefs(elem)); nsCOMPtr<nsIRequest> request = do_QueryInterface(elem); if (request && request == mOnloadBlocker) { return; } } // Not in the loadgroup, so add it. loadGroup->AddRequest(mOnloadBlocker, nullptr); } } } void nsDocument::AsyncBlockOnload() { while (mAsyncOnloadBlockCount) { --mAsyncOnloadBlockCount; BlockOnload(); } } void nsDocument::BlockOnload() { if (mDisplayDocument) { mDisplayDocument->BlockOnload(); return; } // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup // -- it's not ours. if (mOnloadBlockCount == 0 && mScriptGlobalObject) { if (!nsContentUtils::IsSafeToRunScript()) { // Because AddRequest may lead to OnStateChange calls in chrome, // block onload only when there are no script blockers. ++mAsyncOnloadBlockCount; if (mAsyncOnloadBlockCount == 1) { nsContentUtils::AddScriptRunner( NewRunnableMethod(this, &nsDocument::AsyncBlockOnload)); } return; } nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup(); if (loadGroup) { loadGroup->AddRequest(mOnloadBlocker, nullptr); } } ++mOnloadBlockCount; } void nsDocument::UnblockOnload(bool aFireSync) { if (mDisplayDocument) { mDisplayDocument->UnblockOnload(aFireSync); return; } if (mOnloadBlockCount == 0 && mAsyncOnloadBlockCount == 0) { NS_NOTREACHED("More UnblockOnload() calls than BlockOnload() calls; dropping call"); return; } --mOnloadBlockCount; if (mOnloadBlockCount == 0) { if (mScriptGlobalObject) { // Only manipulate the loadgroup in this case, because if mScriptGlobalObject // is null, it's not ours. if (aFireSync && mAsyncOnloadBlockCount == 0) { // Increment mOnloadBlockCount, since DoUnblockOnload will decrement it ++mOnloadBlockCount; DoUnblockOnload(); } else { PostUnblockOnloadEvent(); } } else if (mIsBeingUsedAsImage) { // To correctly unblock onload for a document that contains an SVG // image, we need to know when all of the SVG document's resources are // done loading, in a way comparable to |window.onload|. We fire this // event to indicate that the SVG should be considered fully loaded. // Because scripting is disabled on SVG-as-image documents, this event // is not accessible to content authors. (See bug 837315.) RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(this, NS_LITERAL_STRING("MozSVGAsImageDocumentLoad"), false, false); asyncDispatcher->PostDOMEvent(); } } } class nsUnblockOnloadEvent : public Runnable { public: explicit nsUnblockOnloadEvent(nsDocument* aDoc) : mDoc(aDoc) {} NS_IMETHOD Run() override { mDoc->DoUnblockOnload(); return NS_OK; } private: RefPtr<nsDocument> mDoc; }; void nsDocument::PostUnblockOnloadEvent() { nsCOMPtr<nsIRunnable> evt = new nsUnblockOnloadEvent(this); nsresult rv = NS_DispatchToCurrentThread(evt); if (NS_SUCCEEDED(rv)) { // Stabilize block count so we don't post more events while this one is up ++mOnloadBlockCount; } else { NS_WARNING("failed to dispatch nsUnblockOnloadEvent"); } } void nsDocument::DoUnblockOnload() { NS_PRECONDITION(!mDisplayDocument, "Shouldn't get here for resource document"); NS_PRECONDITION(mOnloadBlockCount != 0, "Shouldn't have a count of zero here, since we stabilized in " "PostUnblockOnloadEvent"); --mOnloadBlockCount; if (mOnloadBlockCount != 0) { // We blocked again after the last unblock. Nothing to do here. We'll // post a new event when we unblock again. return; } if (mAsyncOnloadBlockCount != 0) { // We need to wait until the async onload block has been handled. PostUnblockOnloadEvent(); } // If mScriptGlobalObject is null, we shouldn't be messing with the loadgroup // -- it's not ours. if (mScriptGlobalObject) { nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup(); if (loadGroup) { loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK); } } } nsIContent* nsDocument::GetContentInThisDocument(nsIFrame* aFrame) const { for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) { nsIContent* content = f->GetContent(); if (!content || content->IsInAnonymousSubtree()) continue; if (content->OwnerDoc() == this) { return content; } // We must be in a subdocument so jump directly to the root frame. // GetParentOrPlaceholderForCrossDoc gets called immediately to jump up to // the containing document. f = f->PresContext()->GetPresShell()->GetRootFrame(); } return nullptr; } void nsDocument::DispatchPageTransition(EventTarget* aDispatchTarget, const nsAString& aType, bool aPersisted) { if (!aDispatchTarget) { return; } PageTransitionEventInit init; init.mBubbles = true; init.mCancelable = true; init.mPersisted = aPersisted; RefPtr<PageTransitionEvent> event = PageTransitionEvent::Constructor(this, aType, init); event->SetTrusted(true); event->SetTarget(this); EventDispatcher::DispatchDOMEvent(aDispatchTarget, nullptr, event, nullptr, nullptr); } static bool NotifyPageShow(nsIDocument* aDocument, void* aData) { const bool* aPersistedPtr = static_cast<const bool*>(aData); aDocument->OnPageShow(*aPersistedPtr, nullptr); return true; } void nsDocument::OnPageShow(bool aPersisted, EventTarget* aDispatchStartTarget) { mVisible = true; EnumerateActivityObservers(NotifyActivityChanged, nullptr); EnumerateExternalResources(NotifyPageShow, &aPersisted); Element* root = GetRootElement(); if (aPersisted && root) { // Send out notifications that our <link> elements are attached. RefPtr<nsContentList> links = NS_GetContentList(root, kNameSpaceID_XHTML, NS_LITERAL_STRING("link")); uint32_t linkCount = links->Length(true); for (uint32_t i = 0; i < linkCount; ++i) { static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkAdded(); } } // See nsIDocument if (!aDispatchStartTarget) { // Set mIsShowing before firing events, in case those event handlers // move us around. mIsShowing = true; } if (mAnimationController) { mAnimationController->OnPageShow(); } if (aPersisted) { ImageTracker()->SetAnimatingState(true); } UpdateVisibilityState(); nsCOMPtr<EventTarget> target = aDispatchStartTarget; if (!target) { target = do_QueryInterface(GetWindow()); } // Dispatch observer notification to notify observers page is shown. nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); if (os) { nsIPrincipal *principal = GetPrincipal(); os->NotifyObservers(static_cast<nsIDocument*>(this), nsContentUtils::IsSystemPrincipal(principal) ? "chrome-page-shown" : "content-page-shown", nullptr); } DispatchPageTransition(target, NS_LITERAL_STRING("pageshow"), aPersisted); } static bool NotifyPageHide(nsIDocument* aDocument, void* aData) { const bool* aPersistedPtr = static_cast<const bool*>(aData); aDocument->OnPageHide(*aPersistedPtr, nullptr); return true; } static void DispatchCustomEventWithFlush(nsINode* aTarget, const nsAString& aEventType, bool aBubbles, bool aOnlyChromeDispatch) { RefPtr<Event> event = NS_NewDOMEvent(aTarget, nullptr, nullptr); event->InitEvent(aEventType, aBubbles, false); event->SetTrusted(true); if (aOnlyChromeDispatch) { event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; } if (nsIPresShell* shell = aTarget->OwnerDoc()->GetShell()) { shell->GetPresContext()-> RefreshDriver()->ScheduleEventDispatch(aTarget, event); } } static void DispatchFullScreenChange(nsIDocument* aTarget) { DispatchCustomEventWithFlush( aTarget, NS_LITERAL_STRING("fullscreenchange"), /* Bubbles */ true, /* OnlyChrome */ false); } static void ClearPendingFullscreenRequests(nsIDocument* aDoc); void nsDocument::OnPageHide(bool aPersisted, EventTarget* aDispatchStartTarget) { // Send out notifications that our <link> elements are detached, // but only if this is not a full unload. Element* root = GetRootElement(); if (aPersisted && root) { RefPtr<nsContentList> links = NS_GetContentList(root, kNameSpaceID_XHTML, NS_LITERAL_STRING("link")); uint32_t linkCount = links->Length(true); for (uint32_t i = 0; i < linkCount; ++i) { static_cast<HTMLLinkElement*>(links->Item(i, false))->LinkRemoved(); } } // See nsIDocument if (!aDispatchStartTarget) { // Set mIsShowing before firing events, in case those event handlers // move us around. mIsShowing = false; } if (mAnimationController) { mAnimationController->OnPageHide(); } // We do not stop the animations (bug 1024343) // when the page is refreshing while being dragged out nsDocShell* docShell = mDocumentContainer.get(); if (aPersisted && !(docShell && docShell->InFrameSwap())) { ImageTracker()->SetAnimatingState(false); } ExitPointerLock(); // Now send out a PageHide event. nsCOMPtr<EventTarget> target = aDispatchStartTarget; if (!target) { target = do_QueryInterface(GetWindow()); } // Dispatch observer notification to notify observers page is hidden. nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); if (os) { nsIPrincipal* principal = GetPrincipal(); os->NotifyObservers(static_cast<nsIDocument*>(this), nsContentUtils::IsSystemPrincipal(principal) ? "chrome-page-hidden" : "content-page-hidden", nullptr); } { PageUnloadingEventTimeStamp timeStamp(this); DispatchPageTransition(target, NS_LITERAL_STRING("pagehide"), aPersisted); } mVisible = false; UpdateVisibilityState(); EnumerateExternalResources(NotifyPageHide, &aPersisted); EnumerateActivityObservers(NotifyActivityChanged, nullptr); ClearPendingFullscreenRequests(this); if (GetFullscreenElement()) { // If this document was fullscreen, we should exit fullscreen in this // doctree branch. This ensures that if the user navigates while in // fullscreen mode we don't leave its still visible ancestor documents // in fullscreen mode. So exit fullscreen in the document's fullscreen // root document, as this will exit fullscreen in all the root's // descendant documents. Note that documents are removed from the // doctree by the time OnPageHide() is called, so we must store a // reference to the root (in nsDocument::mFullscreenRoot) since we can't // just traverse the doctree to get the root. nsIDocument::ExitFullscreenInDocTree(this); // Since the document is removed from the doctree before OnPageHide() is // called, ExitFullscreen() can't traverse from the root down to *this* // document, so we must manually call CleanupFullscreenState() below too. // Note that CleanupFullscreenState() clears nsDocument::mFullscreenRoot, // so we *must* call it after ExitFullscreen(), not before. // OnPageHide() is called in every hidden (i.e. descendant) document, // so calling CleanupFullscreenState() here will ensure all hidden // documents have their fullscreen state reset. CleanupFullscreenState(); // If anyone was listening to this document's state, advertizing the state // change would be the least of the politeness. DispatchFullScreenChange(this); } } void nsDocument::WillDispatchMutationEvent(nsINode* aTarget) { NS_ASSERTION(mSubtreeModifiedDepth != 0 || mSubtreeModifiedTargets.Count() == 0, "mSubtreeModifiedTargets not cleared after dispatching?"); ++mSubtreeModifiedDepth; if (aTarget) { // MayDispatchMutationEvent is often called just before this method, // so it has already appended the node to mSubtreeModifiedTargets. int32_t count = mSubtreeModifiedTargets.Count(); if (!count || mSubtreeModifiedTargets[count - 1] != aTarget) { mSubtreeModifiedTargets.AppendObject(aTarget); } } } void nsDocument::MutationEventDispatched(nsINode* aTarget) { --mSubtreeModifiedDepth; if (mSubtreeModifiedDepth == 0) { int32_t count = mSubtreeModifiedTargets.Count(); if (!count) { return; } nsPIDOMWindowInner* window = GetInnerWindow(); if (window && !window->HasMutationListeners(NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) { mSubtreeModifiedTargets.Clear(); return; } nsCOMArray<nsINode> realTargets; for (int32_t i = 0; i < count; ++i) { nsINode* possibleTarget = mSubtreeModifiedTargets[i]; nsCOMPtr<nsIContent> content = do_QueryInterface(possibleTarget); if (content && content->ChromeOnlyAccess()) { continue; } nsINode* commonAncestor = nullptr; int32_t realTargetCount = realTargets.Count(); for (int32_t j = 0; j < realTargetCount; ++j) { commonAncestor = nsContentUtils::GetCommonAncestor(possibleTarget, realTargets[j]); if (commonAncestor) { realTargets.ReplaceObjectAt(commonAncestor, j); break; } } if (!commonAncestor) { realTargets.AppendObject(possibleTarget); } } mSubtreeModifiedTargets.Clear(); int32_t realTargetCount = realTargets.Count(); for (int32_t k = 0; k < realTargetCount; ++k) { InternalMutationEvent mutation(true, eLegacySubtreeModified); (new AsyncEventDispatcher(realTargets[k], mutation))-> RunDOMEventWhenSafe(); } } } void nsDocument::AddStyleRelevantLink(Link* aLink) { NS_ASSERTION(aLink, "Passing in a null link. Expect crashes RSN!"); #ifdef DEBUG nsPtrHashKey<Link>* entry = mStyledLinks.GetEntry(aLink); NS_ASSERTION(!entry, "Document already knows about this Link!"); mStyledLinksCleared = false; #endif (void)mStyledLinks.PutEntry(aLink); } void nsDocument::ForgetLink(Link* aLink) { NS_ASSERTION(aLink, "Passing in a null link. Expect crashes RSN!"); #ifdef DEBUG nsPtrHashKey<Link>* entry = mStyledLinks.GetEntry(aLink); NS_ASSERTION(entry || mStyledLinksCleared, "Document knows nothing about this Link!"); #endif mStyledLinks.RemoveEntry(aLink); } void nsDocument::DestroyElementMaps() { #ifdef DEBUG mStyledLinksCleared = true; #endif mStyledLinks.Clear(); // Notify ID change listeners before clearing the identifier map. for (auto iter = mIdentifierMap.Iter(); !iter.Done(); iter.Next()) { iter.Get()->ClearAndNotify(); } mIdentifierMap.Clear(); ++mExpandoAndGeneration.generation; } void nsDocument::RefreshLinkHrefs() { // Get a list of all links we know about. We will reset them, which will // remove them from the document, so we need a copy of what is in the // hashtable. LinkArray linksToNotify(mStyledLinks.Count()); for (auto iter = mStyledLinks.ConstIter(); !iter.Done(); iter.Next()) { linksToNotify.AppendElement(iter.Get()->GetKey()); } // Reset all of our styled links. nsAutoScriptBlocker scriptBlocker; for (LinkArray::size_type i = 0; i < linksToNotify.Length(); i++) { linksToNotify[i]->ResetLinkState(true, linksToNotify[i]->ElementHasHref()); } } nsresult nsDocument::CloneDocHelper(nsDocument* clone) const { clone->mIsStaticDocument = mCreatingStaticClone; // Init document nsresult rv = clone->Init(); NS_ENSURE_SUCCESS(rv, rv); if (mCreatingStaticClone) { nsCOMPtr<nsILoadGroup> loadGroup; // |mDocumentContainer| is the container of the document that is being // created and not the original container. See CreateStaticClone function(). nsCOMPtr<nsIDocumentLoader> docLoader(mDocumentContainer); if (docLoader) { docLoader->GetLoadGroup(getter_AddRefs(loadGroup)); } nsCOMPtr<nsIChannel> channel = GetChannel(); nsCOMPtr<nsIURI> uri; if (channel) { NS_GetFinalChannelURI(channel, getter_AddRefs(uri)); } else { uri = nsIDocument::GetDocumentURI(); } clone->mChannel = channel; if (uri) { clone->ResetToURI(uri, loadGroup, NodePrincipal()); } clone->SetContainer(mDocumentContainer); } // Now ensure that our clone has the same URI, base URI, and principal as us. // We do this after the mCreatingStaticClone block above, because that block // can set the base URI to an incorrect value in cases when base URI // information came from the channel. So we override explicitly, and do it // for all these properties, in case ResetToURI messes with any of the rest of // them. clone->nsDocument::SetDocumentURI(nsIDocument::GetDocumentURI()); clone->SetChromeXHRDocURI(mChromeXHRDocURI); clone->SetPrincipal(NodePrincipal()); clone->mDocumentBaseURI = mDocumentBaseURI; clone->SetChromeXHRDocBaseURI(mChromeXHRDocBaseURI); // Set scripting object bool hasHadScriptObject = true; nsIScriptGlobalObject* scriptObject = GetScriptHandlingObject(hasHadScriptObject); NS_ENSURE_STATE(scriptObject || !hasHadScriptObject); if (scriptObject) { clone->SetScriptHandlingObject(scriptObject); } else { clone->SetScopeObject(GetScopeObject()); } // Make the clone a data document clone->SetLoadedAsData(true); // Misc state // State from nsIDocument clone->mCharacterSet = mCharacterSet; clone->mCharacterSetSource = mCharacterSetSource; clone->mCompatMode = mCompatMode; clone->mBidiOptions = mBidiOptions; clone->mContentLanguage = mContentLanguage; clone->SetContentTypeInternal(GetContentTypeInternal()); clone->mSecurityInfo = mSecurityInfo; // State from nsDocument clone->mType = mType; clone->mXMLDeclarationBits = mXMLDeclarationBits; clone->mBaseTarget = mBaseTarget; return NS_OK; } void nsDocument::SetReadyStateInternal(ReadyState rs, bool updateTimingInformation) { mReadyState = rs; if (rs == READYSTATE_UNINITIALIZED) { // Transition back to uninitialized happens only to keep assertions happy // right before readyState transitions to something else. Make this // transition undetectable by Web content. return; } if (updateTimingInformation && READYSTATE_LOADING == rs) { mLoadingTimeStamp = mozilla::TimeStamp::Now(); } if (updateTimingInformation && mTiming) { switch (rs) { case READYSTATE_LOADING: mTiming->NotifyDOMLoading(nsIDocument::GetDocumentURI()); break; case READYSTATE_INTERACTIVE: mTiming->NotifyDOMInteractive(nsIDocument::GetDocumentURI()); break; case READYSTATE_COMPLETE: mTiming->NotifyDOMComplete(nsIDocument::GetDocumentURI()); break; default: NS_WARNING("Unexpected ReadyState value"); break; } } RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(this, NS_LITERAL_STRING("readystatechange"), false, false); asyncDispatcher->RunDOMEventWhenSafe(); } NS_IMETHODIMP nsDocument::GetReadyState(nsAString& aReadyState) { nsIDocument::GetReadyState(aReadyState); return NS_OK; } void nsIDocument::GetReadyState(nsAString& aReadyState) const { switch(mReadyState) { case READYSTATE_LOADING : aReadyState.AssignLiteral(u"loading"); break; case READYSTATE_INTERACTIVE : aReadyState.AssignLiteral(u"interactive"); break; case READYSTATE_COMPLETE : aReadyState.AssignLiteral(u"complete"); break; default: aReadyState.AssignLiteral(u"uninitialized"); } } namespace { struct SuppressArgs { nsIDocument::SuppressionType mWhat; uint32_t mIncrease; }; } // namespace static bool SuppressEventHandlingInDocument(nsIDocument* aDocument, void* aData) { SuppressArgs* args = static_cast<SuppressArgs*>(aData); aDocument->SuppressEventHandling(args->mWhat, args->mIncrease); return true; } void nsDocument::SuppressEventHandling(nsIDocument::SuppressionType aWhat, uint32_t aIncrease) { if (aWhat == eAnimationsOnly) { mAnimationsPaused += aIncrease; } else { mEventsSuppressed += aIncrease; for (uint32_t i = 0; i < aIncrease; ++i) { ScriptLoader()->AddExecuteBlocker(); } } UpdateFrameRequestCallbackSchedulingState(); SuppressArgs args = { aWhat, aIncrease }; EnumerateSubDocuments(SuppressEventHandlingInDocument, &args); } static void FireOrClearDelayedEvents(nsTArray<nsCOMPtr<nsIDocument> >& aDocuments, bool aFireEvents) { nsIFocusManager* fm = nsFocusManager::GetFocusManager(); if (!fm) return; for (uint32_t i = 0; i < aDocuments.Length(); ++i) { // NB: Don't bother trying to fire delayed events on documents that were // closed before this event ran. if (!aDocuments[i]->EventHandlingSuppressed()) { fm->FireDelayedEvents(aDocuments[i]); nsCOMPtr<nsIPresShell> shell = aDocuments[i]->GetShell(); if (shell) { // Only fire events for active documents. bool fire = aFireEvents && aDocuments[i]->GetInnerWindow() && aDocuments[i]->GetInnerWindow()->IsCurrentInnerWindow(); shell->FireOrClearDelayedEvents(fire); } } } } void nsDocument::PreloadPictureOpened() { mPreloadPictureDepth++; } void nsDocument::PreloadPictureClosed() { mPreloadPictureDepth--; if (mPreloadPictureDepth == 0) { mPreloadPictureFoundSource.SetIsVoid(true); } else { MOZ_ASSERT(mPreloadPictureDepth >= 0); } } void nsDocument::PreloadPictureImageSource(const nsAString& aSrcsetAttr, const nsAString& aSizesAttr, const nsAString& aTypeAttr, const nsAString& aMediaAttr) { // Nested pictures are not valid syntax, so while we'll eventually load them, // it's not worth tracking sources mixed between nesting levels to preload // them effectively. if (mPreloadPictureDepth == 1 && mPreloadPictureFoundSource.IsVoid()) { // <picture> selects the first matching source, so if this returns a URI we // needn't consider new sources until a new <picture> is encountered. bool found = HTMLImageElement::SelectSourceForTagWithAttrs(this, true, NullString(), aSrcsetAttr, aSizesAttr, aTypeAttr, aMediaAttr, mPreloadPictureFoundSource); if (found && mPreloadPictureFoundSource.IsVoid()) { // Found an empty source, which counts mPreloadPictureFoundSource.SetIsVoid(false); } } } already_AddRefed<nsIURI> nsDocument::ResolvePreloadImage(nsIURI *aBaseURI, const nsAString& aSrcAttr, const nsAString& aSrcsetAttr, const nsAString& aSizesAttr, bool *aIsImgSet) { nsString sourceURL; bool isImgSet; if (mPreloadPictureDepth == 1 && !mPreloadPictureFoundSource.IsVoid()) { // We're in a <picture> element and found a URI from a source previous to // this image, use it. sourceURL = mPreloadPictureFoundSource; isImgSet = true; } else { // Otherwise try to use this <img> as a source HTMLImageElement::SelectSourceForTagWithAttrs(this, false, aSrcAttr, aSrcsetAttr, aSizesAttr, NullString(), NullString(), sourceURL); isImgSet = !aSrcsetAttr.IsEmpty(); } // Empty sources are not loaded by <img> (i.e. not resolved to the baseURI) if (sourceURL.IsEmpty()) { return nullptr; } // Construct into URI using passed baseURI (the parser may know of base URI // changes that have not reached us) nsresult rv; nsCOMPtr<nsIURI> uri; rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), sourceURL, this, aBaseURI); if (NS_FAILED(rv)) { return nullptr; } *aIsImgSet = isImgSet; // We don't clear mPreloadPictureFoundSource because subsequent <img> tags in // this this <picture> share the same <sources> (though this is not valid per // spec) return uri.forget(); } void nsDocument::MaybePreLoadImage(nsIURI* uri, const nsAString &aCrossOriginAttr, ReferrerPolicy aReferrerPolicy, bool aIsImgSet) { // Early exit if the img is already present in the img-cache // which indicates that the "real" load has already started and // that we shouldn't preload it. if (nsContentUtils::IsImageInCache(uri, static_cast<nsIDocument *>(this))) { return; } nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL; switch (Element::StringToCORSMode(aCrossOriginAttr)) { case CORS_NONE: // Nothing to do break; case CORS_ANONYMOUS: loadFlags |= imgILoader::LOAD_CORS_ANONYMOUS; break; case CORS_USE_CREDENTIALS: loadFlags |= imgILoader::LOAD_CORS_USE_CREDENTIALS; break; default: MOZ_CRASH("Unknown CORS mode!"); } nsContentPolicyType policyType = aIsImgSet ? nsIContentPolicy::TYPE_IMAGESET : nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD; // Image not in cache - trigger preload RefPtr<imgRequestProxy> request; nsresult rv = nsContentUtils::LoadImage(uri, static_cast<nsINode*>(this), this, NodePrincipal(), mDocumentURI, // uri of document used as referrer aReferrerPolicy, nullptr, // no observer loadFlags, NS_LITERAL_STRING("img"), getter_AddRefs(request), policyType); // Pin image-reference to avoid evicting it from the img-cache before // the "real" load occurs. Unpinned in DispatchContentLoadedEvents and // unlink if (NS_SUCCEEDED(rv)) { mPreloadingImages.Put(uri, request.forget()); } } void nsDocument::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) { nsCOMPtr<nsIURI> uri; if (NS_FAILED(aOrigURI->Clone(getter_AddRefs(uri)))) { return; } // The URI created here is used in 2 contexts. One is nsISpeculativeConnect // which ignores the path and uses only the origin. The other is for the // document mPreloadedPreconnects de-duplication hash. Anonymous vs // non-Anonymous preconnects create different connections on the wire and // therefore should not be considred duplicates of each other and we // normalize the path before putting it in the hash to accomplish that. if (aCORSMode == CORS_ANONYMOUS) { uri->SetPath(NS_LITERAL_CSTRING("/anonymous")); } else { uri->SetPath(NS_LITERAL_CSTRING("/")); } if (mPreloadedPreconnects.Contains(uri)) { return; } mPreloadedPreconnects.Put(uri, true); nsCOMPtr<nsISpeculativeConnect> speculator(do_QueryInterface(nsContentUtils::GetIOService())); if (!speculator) { return; } if (aCORSMode == CORS_ANONYMOUS) { speculator->SpeculativeAnonymousConnect2(uri, NodePrincipal(), nullptr); } else { speculator->SpeculativeConnect2(uri, NodePrincipal(), nullptr); } } void nsDocument::ForgetImagePreload(nsIURI* aURI) { // Checking count is faster than hashing the URI in the common // case of empty table. if (mPreloadingImages.Count() != 0) { nsCOMPtr<imgIRequest> req; mPreloadingImages.Remove(aURI, getter_AddRefs(req)); if (req) { // Make sure to cancel the request so imagelib knows it's gone. req->CancelAndForgetObserver(NS_BINDING_ABORTED); } } } EventStates nsDocument::GetDocumentState() { if (!mGotDocumentState.HasState(NS_DOCUMENT_STATE_RTL_LOCALE)) { if (IsDocumentRightToLeft()) { mDocumentState |= NS_DOCUMENT_STATE_RTL_LOCALE; } mGotDocumentState |= NS_DOCUMENT_STATE_RTL_LOCALE; } if (!mGotDocumentState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) { nsIPresShell* shell = GetShell(); if (shell && shell->GetPresContext() && shell->GetPresContext()->IsTopLevelWindowInactive()) { mDocumentState |= NS_DOCUMENT_STATE_WINDOW_INACTIVE; } mGotDocumentState |= NS_DOCUMENT_STATE_WINDOW_INACTIVE; } return mDocumentState; } namespace { /** * Stub for LoadSheet(), since all we want is to get the sheet into * the CSSLoader's style cache */ class StubCSSLoaderObserver final : public nsICSSLoaderObserver { ~StubCSSLoaderObserver() {} public: NS_IMETHOD StyleSheetLoaded(StyleSheet*, bool, nsresult) override { return NS_OK; } NS_DECL_ISUPPORTS }; NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver) } // namespace void nsDocument::PreloadStyle(nsIURI* uri, const nsAString& charset, const nsAString& aCrossOriginAttr, const ReferrerPolicy aReferrerPolicy, const nsAString& aIntegrity) { // The CSSLoader will retain this object after we return. nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver(); // Charset names are always ASCII. CSSLoader()->LoadSheet(uri, true, NodePrincipal(), NS_LossyConvertUTF16toASCII(charset), obs, Element::StringToCORSMode(aCrossOriginAttr), aReferrerPolicy, aIntegrity); } nsresult nsDocument::LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet, RefPtr<mozilla::StyleSheet>* aSheet) { css::SheetParsingMode mode = isAgentSheet ? css::eAgentSheetFeatures : css::eAuthorSheetFeatures; return CSSLoader()->LoadSheetSync(uri, mode, isAgentSheet, aSheet); } class nsDelayedEventDispatcher : public Runnable { public: explicit nsDelayedEventDispatcher(nsTArray<nsCOMPtr<nsIDocument>>& aDocuments) { mDocuments.SwapElements(aDocuments); } virtual ~nsDelayedEventDispatcher() {} NS_IMETHOD Run() override { FireOrClearDelayedEvents(mDocuments, true); return NS_OK; } private: nsTArray<nsCOMPtr<nsIDocument> > mDocuments; }; namespace { struct UnsuppressArgs { explicit UnsuppressArgs(nsIDocument::SuppressionType aWhat) : mWhat(aWhat) { } nsIDocument::SuppressionType mWhat; nsTArray<nsCOMPtr<nsIDocument>> mDocs; }; } // namespace static bool GetAndUnsuppressSubDocuments(nsIDocument* aDocument, void* aData) { UnsuppressArgs* args = static_cast<UnsuppressArgs*>(aData); if (args->mWhat != nsIDocument::eAnimationsOnly && aDocument->EventHandlingSuppressed() > 0) { static_cast<nsDocument*>(aDocument)->DecreaseEventSuppression(); aDocument->ScriptLoader()->RemoveExecuteBlocker(); } else if (args->mWhat == nsIDocument::eAnimationsOnly && aDocument->AnimationsPaused()) { static_cast<nsDocument*>(aDocument)->ResumeAnimations(); } if (args->mWhat != nsIDocument::eAnimationsOnly) { // No need to remember documents if we only care about animation frames. args->mDocs.AppendElement(aDocument); } aDocument->EnumerateSubDocuments(GetAndUnsuppressSubDocuments, aData); return true; } void nsDocument::UnsuppressEventHandlingAndFireEvents(nsIDocument::SuppressionType aWhat, bool aFireEvents) { UnsuppressArgs args(aWhat); GetAndUnsuppressSubDocuments(this, &args); if (aWhat == nsIDocument::eAnimationsOnly) { // No need to fire events if we only care about animations here. return; } if (aFireEvents) { NS_DispatchToCurrentThread(new nsDelayedEventDispatcher(args.mDocs)); } else { FireOrClearDelayedEvents(args.mDocs, false); } } nsISupports* nsDocument::GetCurrentContentSink() { return mParser ? mParser->GetContentSink() : nullptr; } nsIDocument* nsDocument::GetTemplateContentsOwner() { if (!mTemplateContentsOwner) { bool hasHadScriptObject = true; nsIScriptGlobalObject* scriptObject = GetScriptHandlingObject(hasHadScriptObject); nsCOMPtr<nsIDOMDocument> domDocument; nsresult rv = NS_NewDOMDocument(getter_AddRefs(domDocument), EmptyString(), // aNamespaceURI EmptyString(), // aQualifiedName nullptr, // aDoctype nsIDocument::GetDocumentURI(), nsIDocument::GetDocBaseURI(), NodePrincipal(), true, // aLoadedAsData scriptObject, // aEventObject DocumentFlavorHTML); NS_ENSURE_SUCCESS(rv, nullptr); mTemplateContentsOwner = do_QueryInterface(domDocument); NS_ENSURE_TRUE(mTemplateContentsOwner, nullptr); nsDocument* doc = static_cast<nsDocument*>(mTemplateContentsOwner.get()); if (!scriptObject) { mTemplateContentsOwner->SetScopeObject(GetScopeObject()); } doc->mHasHadScriptHandlingObject = hasHadScriptObject; // Set |doc| as the template contents owner of itself so that // |doc| is the template contents owner of template elements created // by |doc|. doc->mTemplateContentsOwner = doc; } return mTemplateContentsOwner; } void nsDocument::SetScrollToRef(nsIURI *aDocumentURI) { if (!aDocumentURI) { return; } nsAutoCString ref; // Since all URI's that pass through here aren't URL's we can't // rely on the nsIURI implementation for providing a way for // finding the 'ref' part of the URI, we'll haveto revert to // string routines for finding the data past '#' nsresult rv = aDocumentURI->GetSpec(ref); if (NS_FAILED(rv)) { Unused << aDocumentURI->GetRef(mScrollToRef); return; } nsReadingIterator<char> start, end; ref.BeginReading(start); ref.EndReading(end); if (FindCharInReadable('#', start, end)) { ++start; // Skip over the '#' mScrollToRef = Substring(start, end); } } void nsDocument::ScrollToRef() { if (mScrolledToRefAlready) { nsCOMPtr<nsIPresShell> shell = GetShell(); if (shell) { shell->ScrollToAnchor(); } return; } if (mScrollToRef.IsEmpty()) { return; } char* tmpstr = ToNewCString(mScrollToRef); if (!tmpstr) { return; } nsUnescape(tmpstr); nsAutoCString unescapedRef; unescapedRef.Assign(tmpstr); free(tmpstr); nsresult rv = NS_ERROR_FAILURE; // We assume that the bytes are in UTF-8, as it says in the spec: // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1 NS_ConvertUTF8toUTF16 ref(unescapedRef); nsCOMPtr<nsIPresShell> shell = GetShell(); if (shell) { // Check an empty string which might be caused by the UTF-8 conversion if (!ref.IsEmpty()) { // Note that GoToAnchor will handle flushing layout as needed. rv = shell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef); } else { rv = NS_ERROR_FAILURE; } // If UTF-8 URI failed then try to assume the string as a // document's charset. if (NS_FAILED(rv)) { const nsACString &docCharset = GetDocumentCharacterSet(); rv = nsContentUtils::ConvertStringFromEncoding(docCharset, unescapedRef, ref); if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) { rv = shell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef); } } if (NS_SUCCEEDED(rv)) { mScrolledToRefAlready = true; } } } void nsDocument::ResetScrolledToRefAlready() { mScrolledToRefAlready = false; } void nsDocument::SetChangeScrollPosWhenScrollingToRef(bool aValue) { mChangeScrollPosWhenScrollingToRef = aValue; } void nsIDocument::RegisterActivityObserver(nsISupports* aSupports) { if (!mActivityObservers) { mActivityObservers = new nsTHashtable<nsPtrHashKey<nsISupports> >(); } mActivityObservers->PutEntry(aSupports); } bool nsIDocument::UnregisterActivityObserver(nsISupports* aSupports) { if (!mActivityObservers) { return false; } nsPtrHashKey<nsISupports>* entry = mActivityObservers->GetEntry(aSupports); if (!entry) { return false; } mActivityObservers->RemoveEntry(entry); return true; } void nsIDocument::EnumerateActivityObservers(ActivityObserverEnumerator aEnumerator, void* aData) { if (!mActivityObservers) return; for (auto iter = mActivityObservers->ConstIter(); !iter.Done(); iter.Next()) { aEnumerator(iter.Get()->GetKey(), aData); } } void nsIDocument::RegisterPendingLinkUpdate(Link* aLink) { MOZ_ASSERT(!mIsLinkUpdateRegistrationsForbidden); mLinksToUpdate.PutEntry(aLink); mHasLinksToUpdate = true; } void nsIDocument::UnregisterPendingLinkUpdate(Link* aLink) { MOZ_ASSERT(!mIsLinkUpdateRegistrationsForbidden); if (!mHasLinksToUpdate) return; mLinksToUpdate.RemoveEntry(aLink); } void nsIDocument::FlushPendingLinkUpdates() { MOZ_ASSERT(!mIsLinkUpdateRegistrationsForbidden); if (!mHasLinksToUpdate) return; #ifdef DEBUG AutoRestore<bool> saved(mIsLinkUpdateRegistrationsForbidden); mIsLinkUpdateRegistrationsForbidden = true; #endif for (auto iter = mLinksToUpdate.ConstIter(); !iter.Done(); iter.Next()) { Link* link = iter.Get()->GetKey(); link->GetElement()->UpdateLinkState(link->LinkState()); } mLinksToUpdate.Clear(); mHasLinksToUpdate = false; } already_AddRefed<nsIDocument> nsIDocument::CreateStaticClone(nsIDocShell* aCloneContainer) { nsDocument* thisAsDoc = static_cast<nsDocument*>(this); mCreatingStaticClone = true; // Make document use different container during cloning. RefPtr<nsDocShell> originalShell = mDocumentContainer.get(); SetContainer(static_cast<nsDocShell*>(aCloneContainer)); nsCOMPtr<nsIDOMNode> clonedNode; nsresult rv = thisAsDoc->CloneNode(true, 1, getter_AddRefs(clonedNode)); SetContainer(originalShell); RefPtr<nsDocument> clonedDoc; if (NS_SUCCEEDED(rv)) { nsCOMPtr<nsIDocument> tmp = do_QueryInterface(clonedNode); if (tmp) { clonedDoc = static_cast<nsDocument*>(tmp.get()); if (IsStaticDocument()) { clonedDoc->mOriginalDocument = mOriginalDocument; } else { clonedDoc->mOriginalDocument = this; } clonedDoc->mOriginalDocument->mStaticCloneCount++; int32_t sheetsCount = GetNumberOfStyleSheets(); for (int32_t i = 0; i < sheetsCount; ++i) { RefPtr<StyleSheet> sheet = GetStyleSheetAt(i); if (sheet) { if (sheet->IsApplicable()) { // XXXheycam Need to make ServoStyleSheet cloning work. if (sheet->IsGecko()) { RefPtr<CSSStyleSheet> clonedSheet = sheet->AsGecko()->Clone(nullptr, nullptr, clonedDoc, nullptr); NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!"); if (clonedSheet) { clonedDoc->AddStyleSheet(clonedSheet); } } else { NS_ERROR("stylo: ServoStyleSheet doesn't support cloning"); } } } } // Iterate backwards to maintain order for (StyleSheet* sheet : Reversed(thisAsDoc->mOnDemandBuiltInUASheets)) { if (sheet) { if (sheet->IsApplicable()) { // XXXheycam Need to make ServoStyleSheet cloning work. if (sheet->IsGecko()) { RefPtr<CSSStyleSheet> clonedSheet = sheet->AsGecko()->Clone(nullptr, nullptr, clonedDoc, nullptr); NS_WARNING_ASSERTION(clonedSheet, "Cloning a stylesheet didn't work!"); if (clonedSheet) { clonedDoc->AddOnDemandBuiltInUASheet(clonedSheet); } } else { NS_ERROR("stylo: ServoStyleSheet doesn't support cloning"); } } } } } } mCreatingStaticClone = false; return clonedDoc.forget(); } void nsIDocument::UnlinkOriginalDocumentIfStatic() { if (IsStaticDocument() && mOriginalDocument) { MOZ_ASSERT(mOriginalDocument->mStaticCloneCount > 0); mOriginalDocument->mStaticCloneCount--; mOriginalDocument = nullptr; } MOZ_ASSERT(!mOriginalDocument); } nsresult nsIDocument::ScheduleFrameRequestCallback(FrameRequestCallback& aCallback, int32_t *aHandle) { if (mFrameRequestCallbackCounter == INT32_MAX) { // Can't increment without overflowing; bail out return NS_ERROR_NOT_AVAILABLE; } int32_t newHandle = ++mFrameRequestCallbackCounter; DebugOnly<FrameRequest*> request = mFrameRequestCallbacks.AppendElement(FrameRequest(aCallback, newHandle)); NS_ASSERTION(request, "This is supposed to be infallible!"); UpdateFrameRequestCallbackSchedulingState(); *aHandle = newHandle; return NS_OK; } void nsIDocument::CancelFrameRequestCallback(int32_t aHandle) { // mFrameRequestCallbacks is stored sorted by handle if (mFrameRequestCallbacks.RemoveElementSorted(aHandle)) { UpdateFrameRequestCallbackSchedulingState(); } } nsresult nsDocument::GetStateObject(nsIVariant** aState) { // Get the document's current state object. This is the object backing both // history.state and popStateEvent.state. // // mStateObjectContainer may be null; this just means that there's no // current state object. if (!mStateObjectCached && mStateObjectContainer) { AutoJSContext cx; nsIGlobalObject* sgo = GetScopeObject(); NS_ENSURE_TRUE(sgo, NS_ERROR_UNEXPECTED); JS::Rooted<JSObject*> global(cx, sgo->GetGlobalJSObject()); NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED); JSAutoCompartment ac(cx, global); mStateObjectContainer-> DeserializeToVariant(cx, getter_AddRefs(mStateObjectCached)); } NS_IF_ADDREF(*aState = mStateObjectCached); return NS_OK; } nsDOMNavigationTiming* nsDocument::GetNavigationTiming() const { return mTiming; } nsresult nsDocument::SetNavigationTiming(nsDOMNavigationTiming* aTiming) { mTiming = aTiming; if (!mLoadingTimeStamp.IsNull() && mTiming) { mTiming->SetDOMLoadingTimeStamp(nsIDocument::GetDocumentURI(), mLoadingTimeStamp); } return NS_OK; } Element* nsDocument::FindImageMap(const nsAString& aUseMapValue) { if (aUseMapValue.IsEmpty()) { return nullptr; } nsAString::const_iterator start, end; aUseMapValue.BeginReading(start); aUseMapValue.EndReading(end); int32_t hash = aUseMapValue.FindChar('#'); if (hash < 0) { return nullptr; } // aUsemap contains a '#', set start to point right after the '#' start.advance(hash + 1); if (start == end) { return nullptr; // aUsemap == "#" } const nsAString& mapName = Substring(start, end); if (!mImageMaps) { mImageMaps = new nsContentList(this, kNameSpaceID_XHTML, nsGkAtoms::map, nsGkAtoms::map); } uint32_t i, n = mImageMaps->Length(true); nsString name; for (i = 0; i < n; ++i) { nsIContent* map = mImageMaps->Item(i); if (map->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id, mapName, eCaseMatters) || (map->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name) && mapName.Equals(name, nsCaseInsensitiveStringComparator()))) { return map->AsElement(); } } return nullptr; } #define DEPRECATED_OPERATION(_op) #_op "Warning", static const char* kDeprecationWarnings[] = { #include "nsDeprecatedOperationList.h" nullptr }; #undef DEPRECATED_OPERATION #define DOCUMENT_WARNING(_op) #_op "Warning", static const char* kDocumentWarnings[] = { #include "nsDocumentWarningList.h" nullptr }; #undef DOCUMENT_WARNING static UseCounter OperationToUseCounter(nsIDocument::DeprecatedOperations aOperation) { switch(aOperation) { #define DEPRECATED_OPERATION(_op) \ case nsIDocument::e##_op: return eUseCounter_##_op; #include "nsDeprecatedOperationList.h" #undef DEPRECATED_OPERATION default: MOZ_CRASH(); } } bool nsIDocument::HasWarnedAbout(DeprecatedOperations aOperation) const { return mDeprecationWarnedAbout[aOperation]; } void nsIDocument::WarnOnceAbout(DeprecatedOperations aOperation, bool asError /* = false */) const { MOZ_ASSERT(NS_IsMainThread()); if (HasWarnedAbout(aOperation)) { return; } mDeprecationWarnedAbout[aOperation] = true; const_cast<nsIDocument*>(this)->SetDocumentAndPageUseCounter(OperationToUseCounter(aOperation)); uint32_t flags = asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag; nsContentUtils::ReportToConsole(flags, NS_LITERAL_CSTRING("DOM Core"), this, nsContentUtils::eDOM_PROPERTIES, kDeprecationWarnings[aOperation]); } bool nsIDocument::HasWarnedAbout(DocumentWarnings aWarning) const { return mDocWarningWarnedAbout[aWarning]; } void nsIDocument::WarnOnceAbout(DocumentWarnings aWarning, bool asError /* = false */, const char16_t **aParams /* = nullptr */, uint32_t aParamsLength /* = 0 */) const { MOZ_ASSERT(NS_IsMainThread()); if (HasWarnedAbout(aWarning)) { return; } mDocWarningWarnedAbout[aWarning] = true; uint32_t flags = asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag; nsContentUtils::ReportToConsole(flags, NS_LITERAL_CSTRING("DOM Core"), this, nsContentUtils::eDOM_PROPERTIES, kDocumentWarnings[aWarning], aParams, aParamsLength); } mozilla::dom::ImageTracker* nsIDocument::ImageTracker() { if (!mImageTracker) { mImageTracker = new mozilla::dom::ImageTracker; } return mImageTracker; } nsresult nsDocument::AddPlugin(nsIObjectLoadingContent* aPlugin) { MOZ_ASSERT(aPlugin); if (!mPlugins.PutEntry(aPlugin)) { return NS_ERROR_OUT_OF_MEMORY; } return NS_OK; } void nsDocument::RemovePlugin(nsIObjectLoadingContent* aPlugin) { MOZ_ASSERT(aPlugin); mPlugins.RemoveEntry(aPlugin); } static bool AllSubDocumentPluginEnum(nsIDocument* aDocument, void* userArg) { nsTArray<nsIObjectLoadingContent*>* plugins = reinterpret_cast< nsTArray<nsIObjectLoadingContent*>* >(userArg); MOZ_ASSERT(plugins); aDocument->GetPlugins(*plugins); return true; } void nsDocument::GetPlugins(nsTArray<nsIObjectLoadingContent*>& aPlugins) { aPlugins.SetCapacity(aPlugins.Length() + mPlugins.Count()); for (auto iter = mPlugins.ConstIter(); !iter.Done(); iter.Next()) { aPlugins.AppendElement(iter.Get()->GetKey()); } EnumerateSubDocuments(AllSubDocumentPluginEnum, &aPlugins); } nsresult nsDocument::AddResponsiveContent(nsIContent* aContent) { MOZ_ASSERT(aContent); MOZ_ASSERT(aContent->IsHTMLElement(nsGkAtoms::img)); mResponsiveContent.PutEntry(aContent); return NS_OK; } void nsDocument::RemoveResponsiveContent(nsIContent* aContent) { MOZ_ASSERT(aContent); mResponsiveContent.RemoveEntry(aContent); } void nsDocument::NotifyMediaFeatureValuesChanged() { for (auto iter = mResponsiveContent.ConstIter(); !iter.Done(); iter.Next()) { nsCOMPtr<nsIContent> content = iter.Get()->GetKey(); if (content->IsHTMLElement(nsGkAtoms::img)) { auto* imageElement = static_cast<HTMLImageElement*>(content.get()); imageElement->MediaFeatureValuesChanged(); } } } already_AddRefed<Touch> nsIDocument::CreateTouch(nsGlobalWindow* aView, EventTarget* aTarget, int32_t aIdentifier, int32_t aPageX, int32_t aPageY, int32_t aScreenX, int32_t aScreenY, int32_t aClientX, int32_t aClientY, int32_t aRadiusX, int32_t aRadiusY, float aRotationAngle, float aForce) { MOZ_ASSERT_IF(aView, aView->IsInnerWindow()); RefPtr<Touch> touch = new Touch(aTarget, aIdentifier, aPageX, aPageY, aScreenX, aScreenY, aClientX, aClientY, aRadiusX, aRadiusY, aRotationAngle, aForce); return touch.forget(); } already_AddRefed<TouchList> nsIDocument::CreateTouchList() { RefPtr<TouchList> retval = new TouchList(ToSupports(this)); return retval.forget(); } already_AddRefed<TouchList> nsIDocument::CreateTouchList(Touch& aTouch, const Sequence<OwningNonNull<Touch> >& aTouches) { RefPtr<TouchList> retval = new TouchList(ToSupports(this)); retval->Append(&aTouch); for (uint32_t i = 0; i < aTouches.Length(); ++i) { retval->Append(aTouches[i].get()); } return retval.forget(); } already_AddRefed<TouchList> nsIDocument::CreateTouchList(const Sequence<OwningNonNull<Touch> >& aTouches) { RefPtr<TouchList> retval = new TouchList(ToSupports(this)); for (uint32_t i = 0; i < aTouches.Length(); ++i) { retval->Append(aTouches[i].get()); } return retval.forget(); } already_AddRefed<nsDOMCaretPosition> nsIDocument::CaretPositionFromPoint(float aX, float aY) { nscoord x = nsPresContext::CSSPixelsToAppUnits(aX); nscoord y = nsPresContext::CSSPixelsToAppUnits(aY); nsPoint pt(x, y); FlushPendingNotifications(Flush_Layout); nsIPresShell *ps = GetShell(); if (!ps) { return nullptr; } nsIFrame *rootFrame = ps->GetRootFrame(); // XUL docs, unlike HTML, have no frame tree until everything's done loading if (!rootFrame) { return nullptr; } nsIFrame *ptFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, pt, nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC); if (!ptFrame) { return nullptr; } // GetContentOffsetsFromPoint requires frame-relative coordinates, so we need // to adjust to frame-relative coordinates before we can perform this call. // It should also not take into account the padding of the frame. nsPoint adjustedPoint = pt - ptFrame->GetOffsetTo(rootFrame); nsFrame::ContentOffsets offsets = ptFrame->GetContentOffsetsFromPoint(adjustedPoint); nsCOMPtr<nsIContent> node = offsets.content; uint32_t offset = offsets.offset; nsCOMPtr<nsIContent> anonNode = node; bool nodeIsAnonymous = node && node->IsInNativeAnonymousSubtree(); if (nodeIsAnonymous) { node = ptFrame->GetContent(); nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent(); nsCOMPtr<nsIDOMHTMLTextAreaElement> textArea = do_QueryInterface(nonanon); nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame()); nsNumberControlFrame* numberFrame = do_QueryFrame(nonanon->GetPrimaryFrame()); if (textFrame || numberFrame) { // If the anonymous content node has a child, then we need to make sure // that we get the appropriate child, as otherwise the offset may not be // correct when we construct a range for it. nsCOMPtr<nsIContent> firstChild = anonNode->GetFirstChild(); if (firstChild) { anonNode = firstChild; } if (textArea) { offset = nsContentUtils::GetAdjustedOffsetInTextControl(ptFrame, offset); } node = nonanon; } else { node = nullptr; offset = 0; } } RefPtr<nsDOMCaretPosition> aCaretPos = new nsDOMCaretPosition(node, offset); if (nodeIsAnonymous) { aCaretPos->SetAnonymousContentNode(anonNode); } return aCaretPos.forget(); } NS_IMETHODIMP nsDocument::CaretPositionFromPoint(float aX, float aY, nsISupports** aCaretPos) { NS_ENSURE_ARG_POINTER(aCaretPos); *aCaretPos = nsIDocument::CaretPositionFromPoint(aX, aY).take(); return NS_OK; } static bool IsPotentiallyScrollable(HTMLBodyElement* aBody) { // An element is potentially scrollable if all of the following conditions are // true: // The element has an associated CSS layout box. nsIFrame* bodyFrame = aBody->GetPrimaryFrame(); if (!bodyFrame) { return false; } // The element is not the HTML body element, or it is and the root element's // used value of the overflow-x or overflow-y properties is not visible. MOZ_ASSERT(aBody->GetParent() == aBody->OwnerDoc()->GetRootElement()); nsIFrame* parentFrame = aBody->GetParent()->GetPrimaryFrame(); if (parentFrame && parentFrame->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE && parentFrame->StyleDisplay()->mOverflowY == NS_STYLE_OVERFLOW_VISIBLE) { return false; } // The element's used value of the overflow-x or overflow-y properties is not // visible. if (bodyFrame->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE && bodyFrame->StyleDisplay()->mOverflowY == NS_STYLE_OVERFLOW_VISIBLE) { return false; } return true; } Element* nsIDocument::GetScrollingElement() { if (GetCompatibilityMode() == eCompatibility_NavQuirks) { FlushPendingNotifications(Flush_Layout); HTMLBodyElement* body = GetBodyElement(); if (body && !IsPotentiallyScrollable(body)) { return body; } return nullptr; } return GetRootElement(); } void nsIDocument::ObsoleteSheet(nsIURI *aSheetURI, ErrorResult& rv) { nsresult res = CSSLoader()->ObsoleteSheet(aSheetURI); if (NS_FAILED(res)) { rv.Throw(res); } } void nsIDocument::ObsoleteSheet(const nsAString& aSheetURI, ErrorResult& rv) { nsCOMPtr<nsIURI> uri; nsresult res = NS_NewURI(getter_AddRefs(uri), aSheetURI); if (NS_FAILED(res)) { rv.Throw(res); return; } res = CSSLoader()->ObsoleteSheet(uri); if (NS_FAILED(res)) { rv.Throw(res); } } already_AddRefed<nsIURI> nsIDocument::GetMozDocumentURIIfNotForErrorPages() { if (mFailedChannel) { nsCOMPtr<nsIURI> failedURI; if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) { return failedURI.forget(); } } nsCOMPtr<nsIURI> uri = GetDocumentURIObject(); if (!uri) { return nullptr; } return uri.forget(); } nsIHTMLCollection* nsIDocument::Children() { if (!mChildrenCollection) { mChildrenCollection = new nsContentList(this, kNameSpaceID_Wildcard, nsGkAtoms::_asterisk, nsGkAtoms::_asterisk, false); } return mChildrenCollection; } uint32_t nsIDocument::ChildElementCount() { return Children()->Length(); } namespace mozilla { // Singleton class to manage the list of fullscreen documents which are the // root of a branch which contains fullscreen documents. We maintain this list // so that we can easily exit all windows from fullscreen when the user // presses the escape key. class FullscreenRoots { public: // Adds the root of given document to the manager. Calling this method // with a document whose root is already contained has no effect. static void Add(nsIDocument* aDoc); // Iterates over every root in the root list, and calls aFunction, passing // each root once to aFunction. It is safe to call Add() and Remove() while // iterating over the list (i.e. in aFunction). Documents that are removed // from the manager during traversal are not traversed, and documents that // are added to the manager during traversal are also not traversed. static void ForEach(void(*aFunction)(nsIDocument* aDoc)); // Removes the root of a specific document from the manager. static void Remove(nsIDocument* aDoc); // Returns true if all roots added to the list have been removed. static bool IsEmpty(); private: FullscreenRoots() { MOZ_COUNT_CTOR(FullscreenRoots); } ~FullscreenRoots() { MOZ_COUNT_DTOR(FullscreenRoots); } enum { NotFound = uint32_t(-1) }; // Looks in mRoots for aRoot. Returns the index if found, otherwise NotFound. static uint32_t Find(nsIDocument* aRoot); // Returns true if aRoot is in the list of fullscreen roots. static bool Contains(nsIDocument* aRoot); // Singleton instance of the FullscreenRoots. This is instantiated when a // root is added, and it is deleted when the last root is removed. static FullscreenRoots* sInstance; // List of weak pointers to roots. nsTArray<nsWeakPtr> mRoots; }; FullscreenRoots* FullscreenRoots::sInstance = nullptr; /* static */ void FullscreenRoots::ForEach(void(*aFunction)(nsIDocument* aDoc)) { if (!sInstance) { return; } // Create a copy of the roots array, and iterate over the copy. This is so // that if an element is removed from mRoots we don't mess up our iteration. nsTArray<nsWeakPtr> roots(sInstance->mRoots); // Call aFunction on all entries. for (uint32_t i = 0; i < roots.Length(); i++) { nsCOMPtr<nsIDocument> root = do_QueryReferent(roots[i]); // Check that the root isn't in the manager. This is so that new additions // while we were running don't get traversed. if (root && FullscreenRoots::Contains(root)) { aFunction(root); } } } /* static */ bool FullscreenRoots::Contains(nsIDocument* aRoot) { return FullscreenRoots::Find(aRoot) != NotFound; } /* static */ void FullscreenRoots::Add(nsIDocument* aDoc) { nsCOMPtr<nsIDocument> root = nsContentUtils::GetRootDocument(aDoc); if (!FullscreenRoots::Contains(root)) { if (!sInstance) { sInstance = new FullscreenRoots(); } sInstance->mRoots.AppendElement(do_GetWeakReference(root)); } } /* static */ uint32_t FullscreenRoots::Find(nsIDocument* aRoot) { if (!sInstance) { return NotFound; } nsTArray<nsWeakPtr>& roots = sInstance->mRoots; for (uint32_t i = 0; i < roots.Length(); i++) { nsCOMPtr<nsIDocument> otherRoot(do_QueryReferent(roots[i])); if (otherRoot == aRoot) { return i; } } return NotFound; } /* static */ void FullscreenRoots::Remove(nsIDocument* aDoc) { nsCOMPtr<nsIDocument> root = nsContentUtils::GetRootDocument(aDoc); uint32_t index = Find(root); NS_ASSERTION(index != NotFound, "Should only try to remove roots which are still added!"); if (index == NotFound || !sInstance) { return; } sInstance->mRoots.RemoveElementAt(index); if (sInstance->mRoots.IsEmpty()) { delete sInstance; sInstance = nullptr; } } /* static */ bool FullscreenRoots::IsEmpty() { return !sInstance; } } // end namespace mozilla. using mozilla::FullscreenRoots; nsIDocument* nsDocument::GetFullscreenRoot() { nsCOMPtr<nsIDocument> root = do_QueryReferent(mFullscreenRoot); return root; } void nsDocument::SetFullscreenRoot(nsIDocument* aRoot) { mFullscreenRoot = do_GetWeakReference(aRoot); } NS_IMETHODIMP nsDocument::MozCancelFullScreen() { nsIDocument::ExitFullscreen(); return NS_OK; } void nsIDocument::ExitFullscreen() { RestorePreviousFullScreenState(); } static void AskWindowToExitFullscreen(nsIDocument* aDoc) { if (XRE_GetProcessType() == GeckoProcessType_Content) { nsContentUtils::DispatchEventOnlyToChrome( aDoc, ToSupports(aDoc), NS_LITERAL_STRING("MozDOMFullscreen:Exit"), /* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr); } else { if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) { win->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, false); } } } class nsCallExitFullscreen : public Runnable { public: explicit nsCallExitFullscreen(nsIDocument* aDoc) : mDoc(aDoc) {} NS_IMETHOD Run() override final { if (!mDoc) { FullscreenRoots::ForEach(&AskWindowToExitFullscreen); } else { AskWindowToExitFullscreen(mDoc); } return NS_OK; } private: nsCOMPtr<nsIDocument> mDoc; }; /* static */ void nsIDocument::AsyncExitFullscreen(nsIDocument* aDoc) { NS_DispatchToCurrentThread(new nsCallExitFullscreen(aDoc)); } static bool CountFullscreenSubDocuments(nsIDocument* aDoc, void* aData) { if (aDoc->GetFullscreenElement()) { uint32_t* count = static_cast<uint32_t*>(aData); (*count)++; } return true; } static uint32_t CountFullscreenSubDocuments(nsIDocument* aDoc) { uint32_t count = 0; aDoc->EnumerateSubDocuments(CountFullscreenSubDocuments, &count); return count; } bool nsDocument::IsFullscreenLeaf() { // A fullscreen leaf document is fullscreen, and has no fullscreen // subdocuments. if (!GetFullscreenElement()) { return false; } return CountFullscreenSubDocuments(this) == 0; } static bool ResetFullScreen(nsIDocument* aDocument, void* aData) { if (aDocument->GetFullscreenElement()) { NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1, "Should have at most 1 fullscreen subdocument."); static_cast<nsDocument*>(aDocument)->CleanupFullscreenState(); NS_ASSERTION(!aDocument->GetFullscreenElement(), "Should reset full-screen"); auto changed = reinterpret_cast<nsCOMArray<nsIDocument>*>(aData); changed->AppendElement(aDocument); aDocument->EnumerateSubDocuments(ResetFullScreen, aData); } return true; } // Since nsIDocument::ExitFullscreenInDocTree() could be called from // Element::UnbindFromTree() where it is not safe to synchronously run // script. This runnable is the script part of that function. class ExitFullscreenScriptRunnable : public Runnable { public: explicit ExitFullscreenScriptRunnable(nsCOMArray<nsIDocument>&& aDocuments) : mDocuments(Move(aDocuments)) { } NS_IMETHOD Run() override { // Dispatch MozDOMFullscreen:Exited to the last document in // the list since we want this event to follow the same path // MozDOMFullscreen:Entered dispatched. nsIDocument* lastDocument = mDocuments[mDocuments.Length() - 1]; nsContentUtils::DispatchEventOnlyToChrome( lastDocument, ToSupports(lastDocument), NS_LITERAL_STRING("MozDOMFullscreen:Exited"), /* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr); // Ensure the window exits fullscreen. if (nsPIDOMWindowOuter* win = mDocuments[0]->GetWindow()) { win->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen, false); } return NS_OK; } private: nsCOMArray<nsIDocument> mDocuments; }; /* static */ void nsIDocument::ExitFullscreenInDocTree(nsIDocument* aMaybeNotARootDoc) { MOZ_ASSERT(aMaybeNotARootDoc); // Unlock the pointer UnlockPointer(); nsCOMPtr<nsIDocument> root = aMaybeNotARootDoc->GetFullscreenRoot(); if (!root || !root->GetFullscreenElement()) { // If a document was detached before exiting from fullscreen, it is // possible that the root had left fullscreen state. In this case, // we would not get anything from the ResetFullScreen() call. Root's // not being a fullscreen doc also means the widget should have // exited fullscreen state. It means even if we do not return here, // we would actually do nothing below except crashing ourselves via // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent // document. return; } // Stores a list of documents to which we must dispatch "fullscreenchange". // We're required by the spec to dispatch the events in leaf-to-root // order when exiting fullscreen, but we traverse the doctree in a // root-to-leaf order, so we save references to the documents we must // dispatch to so that we dispatch in the specified order. nsCOMArray<nsIDocument> changed; // Walk the tree of fullscreen documents, and reset their fullscreen state. ResetFullScreen(root, static_cast<void*>(&changed)); // Dispatch "fullscreenchange" events. Note this loop is in reverse // order so that the events for the leaf document arrives before the root // document, as required by the spec. for (uint32_t i = 0; i < changed.Length(); ++i) { DispatchFullScreenChange(changed[changed.Length() - i - 1]); } NS_ASSERTION(!root->GetFullscreenElement(), "Fullscreen root should no longer be a fullscreen doc..."); // Move the top-level window out of fullscreen mode. FullscreenRoots::Remove(root); nsContentUtils::AddScriptRunner( new ExitFullscreenScriptRunnable(Move(changed))); } bool GetFullscreenLeaf(nsIDocument* aDoc, void* aData) { if (aDoc->IsFullscreenLeaf()) { nsIDocument** result = static_cast<nsIDocument**>(aData); *result = aDoc; return false; } else if (aDoc->GetFullscreenElement()) { aDoc->EnumerateSubDocuments(GetFullscreenLeaf, aData); } return true; } static nsIDocument* GetFullscreenLeaf(nsIDocument* aDoc) { nsIDocument* leaf = nullptr; GetFullscreenLeaf(aDoc, &leaf); if (leaf) { return leaf; } // Otherwise we could be either in a non-fullscreen doc tree, or we're // below the fullscreen doc. Start the search from the root. nsIDocument* root = nsContentUtils::GetRootDocument(aDoc); // Check that the root is actually fullscreen so we don't waste time walking // around its descendants. if (!root->GetFullscreenElement()) { return nullptr; } GetFullscreenLeaf(root, &leaf); return leaf; } void nsDocument::RestorePreviousFullScreenState() { NS_ASSERTION(!GetFullscreenElement() || !FullscreenRoots::IsEmpty(), "Should have at least 1 fullscreen root when fullscreen!"); if (!GetFullscreenElement() || !GetWindow() || FullscreenRoots::IsEmpty()) { return; } nsCOMPtr<nsIDocument> fullScreenDoc = GetFullscreenLeaf(this); AutoTArray<nsDocument*, 8> exitDocs; nsIDocument* doc = fullScreenDoc; // Collect all subdocuments. for (; doc != this; doc = doc->GetParentDocument()) { exitDocs.AppendElement(static_cast<nsDocument*>(doc)); } MOZ_ASSERT(doc == this, "Must have reached this doc"); // Collect all ancestor documents which we are going to change. for (; doc; doc = doc->GetParentDocument()) { nsDocument* theDoc = static_cast<nsDocument*>(doc); MOZ_ASSERT(!theDoc->mFullScreenStack.IsEmpty(), "Ancestor of fullscreen document must also be in fullscreen"); if (doc != this) { Element* top = theDoc->FullScreenStackTop(); if (top->IsHTMLElement(nsGkAtoms::iframe)) { if (static_cast<HTMLIFrameElement*>(top)->FullscreenFlag()) { // If this is an iframe, and it explicitly requested // fullscreen, don't rollback it automatically. break; } } } exitDocs.AppendElement(theDoc); if (theDoc->mFullScreenStack.Length() > 1) { break; } } nsDocument* lastDoc = exitDocs.LastElement(); if (!lastDoc->GetParentDocument() && lastDoc->mFullScreenStack.Length() == 1) { // If we are fully exiting fullscreen, don't touch anything here, // just wait for the window to get out from fullscreen first. AskWindowToExitFullscreen(this); return; } // If fullscreen mode is updated the pointer should be unlocked UnlockPointer(); // All documents listed in the array except the last one are going to // completely exit from the fullscreen state. for (auto i : MakeRange(exitDocs.Length() - 1)) { exitDocs[i]->CleanupFullscreenState(); } // The last document will either rollback one fullscreen element, or // completely exit from the fullscreen state as well. nsIDocument* newFullscreenDoc; if (lastDoc->mFullScreenStack.Length() > 1) { lastDoc->FullScreenStackPop(); newFullscreenDoc = lastDoc; } else { lastDoc->CleanupFullscreenState(); newFullscreenDoc = lastDoc->GetParentDocument(); } // Dispatch the fullscreenchange event to all document listed. for (nsDocument* d : exitDocs) { DispatchFullScreenChange(d); } MOZ_ASSERT(newFullscreenDoc, "If we were going to exit from fullscreen on " "all documents in this doctree, we should've asked the window to " "exit first instead of reaching here."); if (fullScreenDoc != newFullscreenDoc && !nsContentUtils::HaveEqualPrincipals(fullScreenDoc, newFullscreenDoc)) { // We've popped so enough off the stack that we've rolled back to // a fullscreen element in a parent document. If this document is // cross origin, dispatch an event to chrome so it knows to show // the warning UI. DispatchCustomEventWithFlush( newFullscreenDoc, NS_LITERAL_STRING("MozDOMFullscreen:NewOrigin"), /* Bubbles */ true, /* ChromeOnly */ true); } } class nsCallRequestFullScreen : public Runnable { public: explicit nsCallRequestFullScreen(UniquePtr<FullscreenRequest>&& aRequest) : mRequest(Move(aRequest)) { } NS_IMETHOD Run() override { mRequest->GetDocument()->RequestFullScreen(Move(mRequest)); return NS_OK; } UniquePtr<FullscreenRequest> mRequest; }; void nsDocument::AsyncRequestFullScreen(UniquePtr<FullscreenRequest>&& aRequest) { if (!aRequest->GetElement()) { MOZ_ASSERT_UNREACHABLE( "Must pass non-null element to nsDocument::AsyncRequestFullScreen"); return; } // Request full-screen asynchronously. nsCOMPtr<nsIRunnable> event(new nsCallRequestFullScreen(Move(aRequest))); NS_DispatchToCurrentThread(event); } void nsIDocument::DispatchFullscreenError(const char* aMessage) { RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(this, NS_LITERAL_STRING("fullscreenerror"), true, false); asyncDispatcher->PostDOMEvent(); nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM"), this, nsContentUtils::eDOM_PROPERTIES, aMessage); } static void UpdateViewportScrollbarOverrideForFullscreen(nsIDocument* aDoc) { if (nsIPresShell* presShell = aDoc->GetShell()) { if (nsPresContext* presContext = presShell->GetPresContext()) { presContext->UpdateViewportScrollbarStylesOverride(); } } } static void ClearFullscreenStateOnElement(Element* aElement) { // Remove styles from existing top element. EventStateManager::SetFullScreenState(aElement, false); // Reset iframe fullscreen flag. if (aElement->IsHTMLElement(nsGkAtoms::iframe)) { static_cast<HTMLIFrameElement*>(aElement)->SetFullscreenFlag(false); } } void nsDocument::CleanupFullscreenState() { // Iterate the fullscreen stack and clear the fullscreen states. // Since we also need to clear the fullscreen-ancestor state, and // currently fullscreen elements can only be placed in hierarchy // order in the stack, reversely iterating the stack could be more // efficient. NOTE that fullscreen-ancestor state would be removed // in bug 1199529, and the elements may not in hierarchy order // after bug 1195213. for (nsWeakPtr& weakPtr : Reversed(mFullScreenStack)) { if (nsCOMPtr<Element> element = do_QueryReferent(weakPtr)) { ClearFullscreenStateOnElement(element); } } mFullScreenStack.Clear(); mFullscreenRoot = nullptr; UpdateViewportScrollbarOverrideForFullscreen(this); } bool nsDocument::FullScreenStackPush(Element* aElement) { NS_ASSERTION(aElement, "Must pass non-null to FullScreenStackPush()"); Element* top = FullScreenStackTop(); if (top == aElement || !aElement) { return false; } EventStateManager::SetFullScreenState(aElement, true); mFullScreenStack.AppendElement(do_GetWeakReference(aElement)); NS_ASSERTION(GetFullscreenElement() == aElement, "Should match"); UpdateViewportScrollbarOverrideForFullscreen(this); return true; } void nsDocument::FullScreenStackPop() { if (mFullScreenStack.IsEmpty()) { return; } ClearFullscreenStateOnElement(FullScreenStackTop()); // Remove top element. Note the remaining top element in the stack // will not have full-screen style bits set, so we will need to restore // them on the new top element before returning. uint32_t last = mFullScreenStack.Length() - 1; mFullScreenStack.RemoveElementAt(last); // Pop from the stack null elements (references to elements which have // been GC'd since they were added to the stack) and elements which are // no longer in this document. while (!mFullScreenStack.IsEmpty()) { Element* element = FullScreenStackTop(); if (!element || !element->IsInUncomposedDoc() || element->OwnerDoc() != this) { NS_ASSERTION(!element->State().HasState(NS_EVENT_STATE_FULL_SCREEN), "Should have already removed full-screen styles"); uint32_t last = mFullScreenStack.Length() - 1; mFullScreenStack.RemoveElementAt(last); } else { // The top element of the stack is now an in-doc element. Return here. break; } } UpdateViewportScrollbarOverrideForFullscreen(this); } Element* nsDocument::FullScreenStackTop() { if (mFullScreenStack.IsEmpty()) { return nullptr; } uint32_t last = mFullScreenStack.Length() - 1; nsCOMPtr<Element> element(do_QueryReferent(mFullScreenStack[last])); NS_ASSERTION(element, "Should have full-screen element!"); NS_ASSERTION(element->IsInUncomposedDoc(), "Full-screen element should be in doc"); NS_ASSERTION(element->OwnerDoc() == this, "Full-screen element should be in this doc"); return element; } /* virtual */ nsTArray<Element*> nsDocument::GetFullscreenStack() const { nsTArray<Element*> elements; for (const nsWeakPtr& ptr : mFullScreenStack) { if (nsCOMPtr<Element> elem = do_QueryReferent(ptr)) { MOZ_ASSERT(elem->State().HasState(NS_EVENT_STATE_FULL_SCREEN)); elements.AppendElement(elem); } } return elements; } // Returns true if aDoc is in the focused tab in the active window. static bool IsInActiveTab(nsIDocument* aDoc) { nsCOMPtr<nsIDocShell> docshell = aDoc->GetDocShell(); if (!docshell) { return false; } bool isActive = false; docshell->GetIsActive(&isActive); if (!isActive) { return false; } nsCOMPtr<nsIDocShellTreeItem> rootItem; docshell->GetRootTreeItem(getter_AddRefs(rootItem)); if (!rootItem) { return false; } nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow(); if (!rootWin) { return false; } nsIFocusManager* fm = nsFocusManager::GetFocusManager(); if (!fm) { return false; } nsCOMPtr<mozIDOMWindowProxy> activeWindow; fm->GetActiveWindow(getter_AddRefs(activeWindow)); if (!activeWindow) { return false; } return activeWindow == rootWin; } nsresult nsDocument::RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement) { // Ensure the frame element is the fullscreen element in this document. // If the frame element is already the fullscreen element in this document, // this has no effect. nsCOMPtr<nsIContent> content(do_QueryInterface(aFrameElement)); auto request = MakeUnique<FullscreenRequest>(content->AsElement()); request->mIsCallerChrome = false; request->mShouldNotifyNewOrigin = false; RequestFullScreen(Move(request)); return NS_OK; } nsresult nsDocument::RemoteFrameFullscreenReverted() { RestorePreviousFullScreenState(); return NS_OK; } /* static */ bool nsDocument::IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject) { MOZ_ASSERT(NS_IsMainThread()); return nsContentUtils::IsCallerChrome() || nsContentUtils::IsUnprefixedFullscreenApiEnabled(); } static bool HasFullScreenSubDocument(nsIDocument* aDoc) { uint32_t count = CountFullscreenSubDocuments(aDoc); NS_ASSERTION(count <= 1, "Fullscreen docs should have at most 1 fullscreen child!"); return count >= 1; } // Returns nullptr if a request for Fullscreen API is currently enabled // in the given document. Returns a static string indicates the reason // why it is not enabled otherwise. static const char* GetFullscreenError(nsIDocument* aDoc, bool aCallerIsChrome) { if (nsContentUtils::IsFullScreenApiEnabled() && aCallerIsChrome) { // Chrome code can always use the full-screen API, provided it's not // explicitly disabled. Note IsCallerChrome() returns true when running // in a Runnable, so don't use GetMozFullScreenEnabled() from a // Runnable! return nullptr; } if (!nsContentUtils::IsFullScreenApiEnabled()) { return "FullscreenDeniedDisabled"; } // Ensure that all containing elements are <iframe> and have // allowfullscreen attribute set. nsCOMPtr<nsIDocShell> docShell(aDoc->GetDocShell()); if (!docShell || !docShell->GetFullscreenAllowed()) { return "FullscreenDeniedContainerNotAllowed"; } return nullptr; } bool nsDocument::FullscreenElementReadyCheck(Element* aElement, bool aWasCallerChrome) { NS_ASSERTION(aElement, "Must pass non-null element to nsDocument::RequestFullScreen"); if (!aElement || aElement == GetFullscreenElement()) { return false; } if (!aElement->IsInUncomposedDoc()) { DispatchFullscreenError("FullscreenDeniedNotInDocument"); return false; } if (aElement->OwnerDoc() != this) { DispatchFullscreenError("FullscreenDeniedMovedDocument"); return false; } if (!GetWindow()) { DispatchFullscreenError("FullscreenDeniedLostWindow"); return false; } if (const char* msg = GetFullscreenError(this, aWasCallerChrome)) { DispatchFullscreenError(msg); return false; } if (!IsVisible()) { DispatchFullscreenError("FullscreenDeniedHidden"); return false; } if (HasFullScreenSubDocument(this)) { DispatchFullscreenError("FullscreenDeniedSubDocFullScreen"); return false; } if (GetFullscreenElement() && !nsContentUtils::ContentIsDescendantOf(aElement, GetFullscreenElement())) { // If this document is full-screen, only grant full-screen requests from // a descendant of the current full-screen element. DispatchFullscreenError("FullscreenDeniedNotDescendant"); return false; } if (!nsContentUtils::IsChromeDoc(this) && !IsInActiveTab(this)) { DispatchFullscreenError("FullscreenDeniedNotFocusedTab"); return false; } // Deny requests when a windowed plugin is focused. nsIFocusManager* fm = nsFocusManager::GetFocusManager(); if (!fm) { NS_WARNING("Failed to retrieve focus manager in full-screen request."); return false; } nsCOMPtr<nsIDOMElement> focusedElement; fm->GetFocusedElement(getter_AddRefs(focusedElement)); if (focusedElement) { nsCOMPtr<nsIContent> content = do_QueryInterface(focusedElement); if (nsContentUtils::HasPluginWithUncontrolledEventDispatch(content)) { DispatchFullscreenError("FullscreenDeniedFocusedPlugin"); return false; } } return true; } FullscreenRequest::FullscreenRequest(Element* aElement) : mElement(aElement) , mDocument(static_cast<nsDocument*>(aElement->OwnerDoc())) { MOZ_COUNT_CTOR(FullscreenRequest); } FullscreenRequest::~FullscreenRequest() { MOZ_COUNT_DTOR(FullscreenRequest); } // Any fullscreen request waiting for the widget to finish being full- // screen is queued here. This is declared static instead of a member // of nsDocument because in the majority of time, there would be at most // one document requesting fullscreen. We shouldn't waste the space to // hold for it in every document. class PendingFullscreenRequestList { public: static void Add(UniquePtr<FullscreenRequest>&& aRequest) { sList.insertBack(aRequest.release()); } static const FullscreenRequest* GetLast() { return sList.getLast(); } enum IteratorOption { // When we are committing fullscreen changes or preparing for // that, we generally want to iterate all requests in the same // window with eDocumentsWithSameRoot option. eDocumentsWithSameRoot, // If we are removing a document from the tree, we would only // want to remove the requests from the given document and its // descendants. For that case, use eInclusiveDescendants. eInclusiveDescendants }; class Iterator { public: explicit Iterator(nsIDocument* aDoc, IteratorOption aOption) : mCurrent(PendingFullscreenRequestList::sList.getFirst()) , mRootShellForIteration(aDoc->GetDocShell()) { if (mCurrent) { if (mRootShellForIteration && aOption == eDocumentsWithSameRoot) { mRootShellForIteration-> GetRootTreeItem(getter_AddRefs(mRootShellForIteration)); } SkipToNextMatch(); } } void DeleteAndNext() { DeleteAndNextInternal(); SkipToNextMatch(); } bool AtEnd() const { return mCurrent == nullptr; } const FullscreenRequest& Get() const { return *mCurrent; } private: void DeleteAndNextInternal() { FullscreenRequest* thisRequest = mCurrent; mCurrent = mCurrent->getNext(); delete thisRequest; } void SkipToNextMatch() { while (mCurrent) { nsCOMPtr<nsIDocShellTreeItem> docShell = mCurrent->GetDocument()->GetDocShell(); if (!docShell) { // Always automatically drop documents which has been // detached from the doc shell. DeleteAndNextInternal(); } else { while (docShell && docShell != mRootShellForIteration) { docShell->GetParent(getter_AddRefs(docShell)); } if (!docShell) { // We've gone over the root, but haven't find the target // ancestor, so skip this item. mCurrent = mCurrent->getNext(); } else { break; } } } } FullscreenRequest* mCurrent; nsCOMPtr<nsIDocShellTreeItem> mRootShellForIteration; }; private: PendingFullscreenRequestList() = delete; static LinkedList<FullscreenRequest> sList; }; /* static */ LinkedList<FullscreenRequest> PendingFullscreenRequestList::sList; static nsCOMPtr<nsPIDOMWindowOuter> GetRootWindow(nsIDocument* aDoc) { nsIDocShell* docShell = aDoc->GetDocShell(); if (!docShell) { return nullptr; } nsCOMPtr<nsIDocShellTreeItem> rootItem; docShell->GetRootTreeItem(getter_AddRefs(rootItem)); return rootItem ? rootItem->GetWindow() : nullptr; } static bool ShouldApplyFullscreenDirectly(nsIDocument* aDoc, nsPIDOMWindowOuter* aRootWin) { if (XRE_GetProcessType() == GeckoProcessType_Content) { // If we are in the content process, we can apply the fullscreen // state directly only if we have been in DOM fullscreen, because // otherwise we always need to notify the chrome. return !!nsContentUtils::GetRootDocument(aDoc)->GetFullscreenElement(); } else { // If we are in the chrome process, and the window has not been in // fullscreen, we certainly need to make that fullscreen first. if (!aRootWin->GetFullScreen()) { return false; } // The iterator not being at end indicates there is still some // pending fullscreen request relates to this document. We have to // push the request to the pending queue so requests are handled // in the correct order. PendingFullscreenRequestList::Iterator iter(aDoc, PendingFullscreenRequestList::eDocumentsWithSameRoot); if (!iter.AtEnd()) { return false; } // We have to apply the fullscreen state directly in this case, // because nsGlobalWindow::SetFullscreenInternal() will do nothing // if it is already in fullscreen. If we do not apply the state but // instead add it to the queue and wait for the window as normal, // we would get stuck. return true; } } void nsDocument::RequestFullScreen(UniquePtr<FullscreenRequest>&& aRequest) { nsCOMPtr<nsPIDOMWindowOuter> rootWin = GetRootWindow(this); if (!rootWin) { return; } if (ShouldApplyFullscreenDirectly(this, rootWin)) { ApplyFullscreen(*aRequest); return; } // Per spec only HTML, <svg>, and <math> should be allowed, but // we also need to allow XUL elements right now. Element* elem = aRequest->GetElement(); if (!elem->IsHTMLElement() && !elem->IsXULElement() && !elem->IsSVGElement(nsGkAtoms::svg) && !elem->IsMathMLElement(nsGkAtoms::math)) { DispatchFullscreenError("FullscreenDeniedNotHTMLSVGOrMathML"); return; } // We don't need to check element ready before this point, because // if we called ApplyFullscreen, it would check that for us. if (!FullscreenElementReadyCheck(elem, aRequest->mIsCallerChrome)) { return; } PendingFullscreenRequestList::Add(Move(aRequest)); if (XRE_GetProcessType() == GeckoProcessType_Content) { // If we are not the top level process, dispatch an event to make // our parent process go fullscreen first. nsContentUtils::DispatchEventOnlyToChrome( this, ToSupports(this), NS_LITERAL_STRING("MozDOMFullscreen:Request"), /* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr); } else { // Make the window fullscreen. rootWin->SetFullscreenInternal(FullscreenReason::ForFullscreenAPI, true); } } /* static */ bool nsIDocument::HandlePendingFullscreenRequests(nsIDocument* aDoc) { bool handled = false; PendingFullscreenRequestList::Iterator iter( aDoc, PendingFullscreenRequestList::eDocumentsWithSameRoot); while (!iter.AtEnd()) { const FullscreenRequest& request = iter.Get(); if (request.GetDocument()->ApplyFullscreen(request)) { handled = true; } iter.DeleteAndNext(); } return handled; } static void ClearPendingFullscreenRequests(nsIDocument* aDoc) { PendingFullscreenRequestList::Iterator iter( aDoc, PendingFullscreenRequestList::eInclusiveDescendants); while (!iter.AtEnd()) { iter.DeleteAndNext(); } } bool nsDocument::ApplyFullscreen(const FullscreenRequest& aRequest) { Element* elem = aRequest.GetElement(); if (!FullscreenElementReadyCheck(elem, aRequest.mIsCallerChrome)) { return false; } // Stash a reference to any existing fullscreen doc, we'll use this later // to detect if the origin which is fullscreen has changed. nsCOMPtr<nsIDocument> previousFullscreenDoc = GetFullscreenLeaf(this); // Stores a list of documents which we must dispatch "fullscreenchange" // too. We're required by the spec to dispatch the events in root-to-leaf // order, but we traverse the doctree in a leaf-to-root order, so we save // references to the documents we must dispatch to so that we get the order // as specified. AutoTArray<nsIDocument*, 8> changed; // Remember the root document, so that if a full-screen document is hidden // we can reset full-screen state in the remaining visible full-screen documents. nsIDocument* fullScreenRootDoc = nsContentUtils::GetRootDocument(this); // If a document is already in fullscreen, then unlock the mouse pointer // before setting a new document to fullscreen UnlockPointer(); // Set the full-screen element. This sets the full-screen style on the // element, and the full-screen-ancestor styles on ancestors of the element // in this document. DebugOnly<bool> x = FullScreenStackPush(elem); NS_ASSERTION(x, "Full-screen state of requesting doc should always change!"); // Set the iframe fullscreen flag. if (elem->IsHTMLElement(nsGkAtoms::iframe)) { static_cast<HTMLIFrameElement*>(elem)->SetFullscreenFlag(true); } changed.AppendElement(this); // Propagate up the document hierarchy, setting the full-screen element as // the element's container in ancestor documents. This also sets the // appropriate css styles as well. Note we don't propagate down the // document hierarchy, the full-screen element (or its container) is not // visible there. Stop when we reach the root document. nsIDocument* child = this; while (true) { child->SetFullscreenRoot(fullScreenRootDoc); NS_ASSERTION(child->GetFullscreenRoot() == fullScreenRootDoc, "Fullscreen root should be set!"); if (child == fullScreenRootDoc) { break; } nsIDocument* parent = child->GetParentDocument(); Element* element = parent->FindContentForSubDocument(child)->AsElement(); if (static_cast<nsDocument*>(parent)->FullScreenStackPush(element)) { changed.AppendElement(parent); child = parent; } else { // We've reached either the root, or a point in the doctree where the // new full-screen element container is the same as the previous // full-screen element's container. No more changes need to be made // to the full-screen stacks of documents further up the tree. break; } } FullscreenRoots::Add(this); // If it is the first entry of the fullscreen, trigger an event so // that the UI can response to this change, e.g. hide chrome, or // notifying parent process to enter fullscreen. Note that chrome // code may also want to listen to MozDOMFullscreen:NewOrigin event // to pop up warning UI. if (!previousFullscreenDoc) { nsContentUtils::DispatchEventOnlyToChrome( this, ToSupports(elem), NS_LITERAL_STRING("MozDOMFullscreen:Entered"), /* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr); } // The origin which is fullscreen gets changed. Trigger an event so // that the chrome knows to pop up a warning UI. Note that // previousFullscreenDoc == nullptr upon first entry, so we always // take this path on the first entry. Also note that, in a multi- // process browser, the code in content process is responsible for // sending message with the origin to its parent, and the parent // shouldn't rely on this event itself. if (aRequest.mShouldNotifyNewOrigin && !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) { DispatchCustomEventWithFlush( this, NS_LITERAL_STRING("MozDOMFullscreen:NewOrigin"), /* Bubbles */ true, /* ChromeOnly */ true); } // Dispatch "fullscreenchange" events. Note this loop is in reverse // order so that the events for the root document arrives before the leaf // document, as required by the spec. for (uint32_t i = 0; i < changed.Length(); ++i) { DispatchFullScreenChange(changed[changed.Length() - i - 1]); } return true; } NS_IMETHODIMP nsDocument::GetMozFullScreenElement(nsIDOMElement **aFullScreenElement) { Element* el = GetFullscreenElement(); nsCOMPtr<nsIDOMElement> retval = do_QueryInterface(el); retval.forget(aFullScreenElement); return NS_OK; } Element* nsDocument::GetFullscreenElement() { Element* element = FullScreenStackTop(); NS_ASSERTION(!element || element->State().HasState(NS_EVENT_STATE_FULL_SCREEN), "Fullscreen element should have fullscreen styles applied"); return element; } NS_IMETHODIMP nsDocument::GetMozFullScreen(bool *aFullScreen) { *aFullScreen = Fullscreen(); return NS_OK; } NS_IMETHODIMP nsDocument::GetMozFullScreenEnabled(bool *aFullScreen) { NS_ENSURE_ARG_POINTER(aFullScreen); *aFullScreen = FullscreenEnabled(); return NS_OK; } bool nsDocument::FullscreenEnabled() { return !GetFullscreenError(this, nsContentUtils::IsCallerChrome()); } uint16_t nsDocument::CurrentOrientationAngle() const { return mCurrentOrientationAngle; } OrientationType nsDocument::CurrentOrientationType() const { return mCurrentOrientationType; } void nsDocument::SetCurrentOrientation(mozilla::dom::OrientationType aType, uint16_t aAngle) { mCurrentOrientationType = aType; mCurrentOrientationAngle = aAngle; } Promise* nsDocument::GetOrientationPendingPromise() const { return mOrientationPendingPromise; } void nsDocument::SetOrientationPendingPromise(Promise* aPromise) { mOrientationPendingPromise = aPromise; } static void DispatchPointerLockChange(nsIDocument* aTarget) { if (!aTarget) { return; } RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(aTarget, NS_LITERAL_STRING("pointerlockchange"), true, false); asyncDispatcher->PostDOMEvent(); } static void DispatchPointerLockError(nsIDocument* aTarget, const char* aMessage) { if (!aTarget) { return; } RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(aTarget, NS_LITERAL_STRING("pointerlockerror"), true, false); asyncDispatcher->PostDOMEvent(); nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM"), aTarget, nsContentUtils::eDOM_PROPERTIES, aMessage); } class PointerLockRequest final : public Runnable { public: PointerLockRequest(Element* aElement, bool aUserInputOrChromeCaller) : mElement(do_GetWeakReference(aElement)) , mDocument(do_GetWeakReference(aElement->OwnerDoc())) , mUserInputOrChromeCaller(aUserInputOrChromeCaller) {} NS_IMETHOD Run() final; private: nsWeakPtr mElement; nsWeakPtr mDocument; bool mUserInputOrChromeCaller; }; static const char* GetPointerLockError(Element* aElement, Element* aCurrentLock, bool aNoFocusCheck = false) { // Check if pointer lock pref is enabled if (!Preferences::GetBool("full-screen-api.pointer-lock.enabled")) { return "PointerLockDeniedDisabled"; } nsCOMPtr<nsIDocument> ownerDoc = aElement->OwnerDoc(); if (aCurrentLock && aCurrentLock->OwnerDoc() != ownerDoc) { return "PointerLockDeniedInUse"; } if (!aElement->IsInUncomposedDoc()) { return "PointerLockDeniedNotInDocument"; } if (ownerDoc->GetSandboxFlags() & SANDBOXED_POINTER_LOCK) { return "PointerLockDeniedSandboxed"; } // Check if the element is in a document with a docshell. if (!ownerDoc->GetContainer()) { return "PointerLockDeniedHidden"; } nsCOMPtr<nsPIDOMWindowOuter> ownerWindow = ownerDoc->GetWindow(); if (!ownerWindow) { return "PointerLockDeniedHidden"; } nsCOMPtr<nsPIDOMWindowInner> ownerInnerWindow = ownerDoc->GetInnerWindow(); if (!ownerInnerWindow) { return "PointerLockDeniedHidden"; } if (ownerWindow->GetCurrentInnerWindow() != ownerInnerWindow) { return "PointerLockDeniedHidden"; } nsCOMPtr<nsPIDOMWindowOuter> top = ownerWindow->GetScriptableTop(); if (!top || !top->GetExtantDoc() || top->GetExtantDoc()->Hidden()) { return "PointerLockDeniedHidden"; } if (!aNoFocusCheck) { mozilla::ErrorResult rv; if (!top->GetExtantDoc()->HasFocus(rv)) { return "PointerLockDeniedNotFocused"; } } return nullptr; } static void ChangePointerLockedElement(Element* aElement, nsIDocument* aDocument, Element* aPointerLockedElement) { // aDocument here is not really necessary, as it is the uncomposed // document of both aElement and aPointerLockedElement as far as one // is not nullptr, and they wouldn't both be nullptr in any case. // But since the caller of this function should have known what the // document is, we just don't try to figure out what it should be. MOZ_ASSERT(aDocument); MOZ_ASSERT(aElement != aPointerLockedElement); if (aPointerLockedElement) { MOZ_ASSERT(aPointerLockedElement->GetUncomposedDoc() == aDocument); aPointerLockedElement->ClearPointerLock(); } if (aElement) { MOZ_ASSERT(aElement->GetUncomposedDoc() == aDocument); aElement->SetPointerLock(); EventStateManager::sPointerLockedElement = do_GetWeakReference(aElement); EventStateManager::sPointerLockedDoc = do_GetWeakReference(aDocument); NS_ASSERTION(EventStateManager::sPointerLockedElement && EventStateManager::sPointerLockedDoc, "aElement and this should support weak references!"); } else { EventStateManager::sPointerLockedElement = nullptr; EventStateManager::sPointerLockedDoc = nullptr; } // Retarget all events to aElement via capture or // stop retargeting if aElement is nullptr. nsIPresShell::SetCapturingContent(aElement, CAPTURE_POINTERLOCK); DispatchPointerLockChange(aDocument); } NS_IMETHODIMP PointerLockRequest::Run() { nsCOMPtr<Element> e = do_QueryReferent(mElement); nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument); nsDocument* d = static_cast<nsDocument*>(doc.get()); const char* error = nullptr; if (!e || !d || !e->GetUncomposedDoc()) { error = "PointerLockDeniedNotInDocument"; } else if (e->GetUncomposedDoc() != d) { error = "PointerLockDeniedMovedDocument"; } if (!error) { nsCOMPtr<Element> pointerLockedElement = do_QueryReferent(EventStateManager::sPointerLockedElement); if (e == pointerLockedElement) { DispatchPointerLockChange(d); return NS_OK; } // Note, we must bypass focus change, so pass true as the last parameter! error = GetPointerLockError(e, pointerLockedElement, true); // Another element in the same document is requesting pointer lock, // just grant it without user input check. if (!error && pointerLockedElement) { ChangePointerLockedElement(e, d, pointerLockedElement); return NS_OK; } } // If it is neither user input initiated, nor requested in fullscreen, // it should be rejected. if (!error && !mUserInputOrChromeCaller && !doc->GetFullscreenElement()) { error = "PointerLockDeniedNotInputDriven"; } if (!error && !d->SetPointerLock(e, NS_STYLE_CURSOR_NONE)) { error = "PointerLockDeniedFailedToLock"; } if (error) { DispatchPointerLockError(d, error); return NS_OK; } ChangePointerLockedElement(e, d, nullptr); nsContentUtils::DispatchEventOnlyToChrome( doc, ToSupports(e), NS_LITERAL_STRING("MozDOMPointerLock:Entered"), /* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr); return NS_OK; } void nsDocument::RequestPointerLock(Element* aElement) { NS_ASSERTION(aElement, "Must pass non-null element to nsDocument::RequestPointerLock"); nsCOMPtr<Element> pointerLockedElement = do_QueryReferent(EventStateManager::sPointerLockedElement); if (aElement == pointerLockedElement) { DispatchPointerLockChange(this); return; } if (const char* msg = GetPointerLockError(aElement, pointerLockedElement)) { DispatchPointerLockError(this, msg); return; } bool userInputOrChromeCaller = EventStateManager::IsHandlingUserInput() || nsContentUtils::IsCallerChrome(); NS_DispatchToMainThread(new PointerLockRequest(aElement, userInputOrChromeCaller)); } bool nsDocument::SetPointerLock(Element* aElement, int aCursorStyle) { MOZ_ASSERT(!aElement || aElement->OwnerDoc() == this, "We should be either unlocking pointer (aElement is nullptr), " "or locking pointer to an element in this document"); #ifdef DEBUG if (!aElement) { nsCOMPtr<nsIDocument> pointerLockedDoc = do_QueryReferent(EventStateManager::sPointerLockedDoc); MOZ_ASSERT(pointerLockedDoc == this); } #endif nsIPresShell* shell = GetShell(); if (!shell) { NS_WARNING("SetPointerLock(): No PresShell"); if (!aElement) { // If we are unlocking pointer lock, but for some reason the doc // has already detached from the presshell, just ask the event // state manager to release the pointer. EventStateManager::SetPointerLock(nullptr, nullptr); return true; } return false; } nsPresContext* presContext = shell->GetPresContext(); if (!presContext) { NS_WARNING("SetPointerLock(): Unable to get PresContext"); return false; } nsCOMPtr<nsIWidget> widget; nsIFrame* rootFrame = shell->GetRootFrame(); if (!NS_WARN_IF(!rootFrame)) { widget = rootFrame->GetNearestWidget(); NS_WARNING_ASSERTION( widget, "SetPointerLock(): Unable to find widget in " "shell->GetRootFrame()->GetNearestWidget();"); if (aElement && !widget) { return false; } } // Hide the cursor and set pointer lock for future mouse events RefPtr<EventStateManager> esm = presContext->EventStateManager(); esm->SetCursor(aCursorStyle, nullptr, false, 0.0f, 0.0f, widget, true); EventStateManager::SetPointerLock(widget, aElement); return true; } void nsDocument::UnlockPointer(nsIDocument* aDoc) { if (!EventStateManager::sIsPointerLocked) { return; } nsCOMPtr<nsIDocument> pointerLockedDoc = do_QueryReferent(EventStateManager::sPointerLockedDoc); if (!pointerLockedDoc || (aDoc && aDoc != pointerLockedDoc)) { return; } nsDocument* doc = static_cast<nsDocument*>(pointerLockedDoc.get()); if (!doc->SetPointerLock(nullptr, NS_STYLE_CURSOR_AUTO)) { return; } nsCOMPtr<Element> pointerLockedElement = do_QueryReferent(EventStateManager::sPointerLockedElement); ChangePointerLockedElement(nullptr, doc, pointerLockedElement); nsContentUtils::DispatchEventOnlyToChrome( doc, ToSupports(pointerLockedElement), NS_LITERAL_STRING("MozDOMPointerLock:Exited"), /* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr); } void nsIDocument::UnlockPointer(nsIDocument* aDoc) { nsDocument::UnlockPointer(aDoc); } NS_IMETHODIMP nsDocument::MozExitPointerLock() { nsIDocument::ExitPointerLock(); return NS_OK; } NS_IMETHODIMP nsDocument::GetMozPointerLockElement(nsIDOMElement** aPointerLockedElement) { Element* el = nsIDocument::GetPointerLockElement(); nsCOMPtr<nsIDOMElement> retval = do_QueryInterface(el); retval.forget(aPointerLockedElement); return NS_OK; } Element* nsIDocument::GetPointerLockElement() { nsCOMPtr<Element> pointerLockedElement = do_QueryReferent(EventStateManager::sPointerLockedElement); if (!pointerLockedElement) { return nullptr; } // Make sure pointer locked element is in the same document. nsCOMPtr<nsIDocument> pointerLockedDoc = do_QueryReferent(EventStateManager::sPointerLockedDoc); if (pointerLockedDoc != this) { return nullptr; } return pointerLockedElement; } nsresult nsDocument::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) { if (strcmp("service-worker-get-client", aTopic) == 0) { // No need to generate the ID if it doesn't exist here. The ID being // requested must already be generated in order to passed in as // aSubject. nsString clientId = GetId(); if (!clientId.IsEmpty() && clientId.Equals(aData)) { nsCOMPtr<nsISupportsInterfacePointer> ifptr = do_QueryInterface(aSubject); if (ifptr) { #ifdef DEBUG nsCOMPtr<nsISupports> value; MOZ_ALWAYS_SUCCEEDS(ifptr->GetData(getter_AddRefs(value))); MOZ_ASSERT(!value); #endif ifptr->SetData(static_cast<nsIDocument*>(this)); ifptr->SetDataIID(&NS_GET_IID(nsIDocument)); } } } return NS_OK; } void nsDocument::UpdateVisibilityState() { dom::VisibilityState oldState = mVisibilityState; mVisibilityState = GetVisibilityState(); if (oldState != mVisibilityState) { nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this), NS_LITERAL_STRING("visibilitychange"), /* bubbles = */ true, /* cancelable = */ false); EnumerateActivityObservers(NotifyActivityChanged, nullptr); } if (mVisibilityState == dom::VisibilityState::Visible) { MaybeActiveMediaComponents(); } } VisibilityState nsDocument::GetVisibilityState() const { // We have to check a few pieces of information here: // 1) Are we in bfcache (!IsVisible())? If so, nothing else matters. // 2) Do we have an outer window? If not, we're hidden. Note that we don't // want to use GetWindow here because it does weird groveling for windows // in some cases. // 3) Is our outer window background? If so, we're hidden. // Otherwise, we're visible. if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() || mWindow->GetOuterWindow()->IsBackground()) { // Check if the document is in prerender state. nsCOMPtr<nsIDocShell> docshell = GetDocShell(); if (docshell && docshell->GetIsPrerendered()) { return dom::VisibilityState::Prerender; } return dom::VisibilityState::Hidden; } return dom::VisibilityState::Visible; } /* virtual */ void nsDocument::PostVisibilityUpdateEvent() { nsCOMPtr<nsIRunnable> event = NewRunnableMethod(this, &nsDocument::UpdateVisibilityState); NS_DispatchToMainThread(event); } void nsDocument::MaybeActiveMediaComponents() { if (mEverInForeground) { return; } if (!mWindow) { return; } mEverInForeground = true; if (GetWindow()->GetMediaSuspend() == nsISuspendedTypes::SUSPENDED_BLOCK) { GetWindow()->SetMediaSuspend(nsISuspendedTypes::NONE_SUSPENDED); } } NS_IMETHODIMP nsDocument::GetHidden(bool* aHidden) { *aHidden = Hidden(); return NS_OK; } NS_IMETHODIMP nsDocument::GetVisibilityState(nsAString& aState) { const EnumEntry& entry = VisibilityStateValues::strings[static_cast<int>(mVisibilityState)]; aState.AssignASCII(entry.value, entry.length); return NS_OK; } /* virtual */ void nsIDocument::DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const { aWindowSizes->mDOMOtherSize += nsINode::SizeOfExcludingThis(aWindowSizes->mMallocSizeOf); if (mPresShell) { mPresShell->AddSizeOfIncludingThis(aWindowSizes->mMallocSizeOf, &aWindowSizes->mArenaStats, &aWindowSizes->mLayoutPresShellSize, &aWindowSizes->mLayoutStyleSetsSize, &aWindowSizes->mLayoutTextRunsSize, &aWindowSizes->mLayoutPresContextSize, &aWindowSizes->mLayoutFramePropertiesSize); } aWindowSizes->mPropertyTablesSize += mPropertyTable.SizeOfExcludingThis(aWindowSizes->mMallocSizeOf); for (uint32_t i = 0, count = mExtraPropertyTables.Length(); i < count; ++i) { aWindowSizes->mPropertyTablesSize += mExtraPropertyTables[i]->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf); } if (EventListenerManager* elm = GetExistingListenerManager()) { aWindowSizes->mDOMEventListenersCount += elm->ListenerCount(); } // Measurement of the following members may be added later if DMD finds it // is worthwhile: // - many! } void nsIDocument::DocAddSizeOfIncludingThis(nsWindowSizes* aWindowSizes) const { aWindowSizes->mDOMOtherSize += aWindowSizes->mMallocSizeOf(this); DocAddSizeOfExcludingThis(aWindowSizes); } static size_t SizeOfOwnedSheetArrayExcludingThis(const nsTArray<RefPtr<StyleSheet>>& aSheets, MallocSizeOf aMallocSizeOf) { size_t n = 0; n += aSheets.ShallowSizeOfExcludingThis(aMallocSizeOf); for (StyleSheet* sheet : aSheets) { if (!sheet->GetOwningDocument()) { // Avoid over-reporting shared sheets. continue; } n += sheet->SizeOfIncludingThis(aMallocSizeOf); } return n; } size_t nsDocument::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { // This SizeOfExcludingThis() overrides the one from nsINode. But // nsDocuments can only appear at the top of the DOM tree, and we use the // specialized DocAddSizeOfExcludingThis() in that case. So this should never // be called. MOZ_CRASH(); } void nsDocument::DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const { nsIDocument::DocAddSizeOfExcludingThis(aWindowSizes); for (nsIContent* node = nsINode::GetFirstChild(); node; node = node->GetNextNode(this)) { size_t nodeSize = node->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf); size_t* p; switch (node->NodeType()) { case nsIDOMNode::ELEMENT_NODE: p = &aWindowSizes->mDOMElementNodesSize; break; case nsIDOMNode::TEXT_NODE: p = &aWindowSizes->mDOMTextNodesSize; break; case nsIDOMNode::CDATA_SECTION_NODE: p = &aWindowSizes->mDOMCDATANodesSize; break; case nsIDOMNode::COMMENT_NODE: p = &aWindowSizes->mDOMCommentNodesSize; break; default: p = &aWindowSizes->mDOMOtherSize; break; } *p += nodeSize; if (EventListenerManager* elm = node->GetExistingListenerManager()) { aWindowSizes->mDOMEventListenersCount += elm->ListenerCount(); } } aWindowSizes->mStyleSheetsSize += SizeOfOwnedSheetArrayExcludingThis(mStyleSheets, aWindowSizes->mMallocSizeOf); // Note that we do not own the sheets pointed to by mOnDemandBuiltInUASheets // (the nsLayoutStyleSheetCache singleton does). aWindowSizes->mStyleSheetsSize += mOnDemandBuiltInUASheets.ShallowSizeOfExcludingThis( aWindowSizes->mMallocSizeOf); for (auto& sheetArray : mAdditionalSheets) { aWindowSizes->mStyleSheetsSize += SizeOfOwnedSheetArrayExcludingThis(sheetArray, aWindowSizes->mMallocSizeOf); } // Lumping in the loader with the style-sheets size is not ideal, // but most of the things in there are in fact stylesheets, so it // doesn't seem worthwhile to separate it out. aWindowSizes->mStyleSheetsSize += CSSLoader()->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf); aWindowSizes->mDOMOtherSize += mAttrStyleSheet ? mAttrStyleSheet->DOMSizeOfIncludingThis(aWindowSizes->mMallocSizeOf) : 0; aWindowSizes->mDOMOtherSize += mSVGAttrAnimationRuleProcessor ? mSVGAttrAnimationRuleProcessor->DOMSizeOfIncludingThis( aWindowSizes->mMallocSizeOf) : 0; aWindowSizes->mDOMOtherSize += mStyledLinks.ShallowSizeOfExcludingThis(aWindowSizes->mMallocSizeOf); aWindowSizes->mDOMOtherSize += mIdentifierMap.SizeOfExcludingThis(aWindowSizes->mMallocSizeOf); // Measurement of the following members may be added later if DMD finds it // is worthwhile: // - many! } NS_IMETHODIMP nsDocument::QuerySelector(const nsAString& aSelector, nsIDOMElement **aReturn) { return nsINode::QuerySelector(aSelector, aReturn); } NS_IMETHODIMP nsDocument::QuerySelectorAll(const nsAString& aSelector, nsIDOMNodeList **aReturn) { return nsINode::QuerySelectorAll(aSelector, aReturn); } already_AddRefed<nsIDocument> nsIDocument::Constructor(const GlobalObject& aGlobal, ErrorResult& rv) { nsCOMPtr<nsIScriptGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); if (!global) { rv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsCOMPtr<nsIScriptObjectPrincipal> prin = do_QueryInterface(aGlobal.GetAsSupports()); if (!prin) { rv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsCOMPtr<nsIURI> uri; NS_NewURI(getter_AddRefs(uri), "about:blank"); if (!uri) { rv.Throw(NS_ERROR_OUT_OF_MEMORY); return nullptr; } nsCOMPtr<nsIDOMDocument> document; nsresult res = NS_NewDOMDocument(getter_AddRefs(document), NullString(), EmptyString(), nullptr, uri, uri, prin->GetPrincipal(), true, global, DocumentFlavorPlain); if (NS_FAILED(res)) { rv.Throw(res); return nullptr; } nsCOMPtr<nsIDocument> doc = do_QueryInterface(document); doc->SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE); return doc.forget(); } XPathExpression* nsIDocument::CreateExpression(const nsAString& aExpression, XPathNSResolver* aResolver, ErrorResult& rv) { return XPathEvaluator()->CreateExpression(aExpression, aResolver, rv); } nsINode* nsIDocument::CreateNSResolver(nsINode& aNodeResolver) { return XPathEvaluator()->CreateNSResolver(aNodeResolver); } already_AddRefed<XPathResult> nsIDocument::Evaluate(JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode, XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult, ErrorResult& rv) { return XPathEvaluator()->Evaluate(aCx, aExpression, aContextNode, aResolver, aType, aResult, rv); } NS_IMETHODIMP nsDocument::Evaluate(const nsAString& aExpression, nsIDOMNode* aContextNode, nsIDOMNode* aResolver, uint16_t aType, nsISupports* aInResult, nsISupports** aResult) { return XPathEvaluator()->Evaluate(aExpression, aContextNode, aResolver, aType, aInResult, aResult); } nsIDocument* nsIDocument::GetTopLevelContentDocument() { nsDocument* parent; if (!mLoadedAsData) { parent = static_cast<nsDocument*>(this); } else { nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject()); if (!window) { return nullptr; } parent = static_cast<nsDocument*>(window->GetExtantDoc()); if (!parent) { return nullptr; } } do { if (parent->IsTopLevelContentDocument()) { break; } // If we ever have a non-content parent before we hit a toplevel content // parent, then we're never going to find one. Just bail. if (!parent->IsContentDocument()) { return nullptr; } nsIDocument* candidate = parent->GetParentDocument(); parent = static_cast<nsDocument*>(candidate); } while (parent); return parent; } void nsIDocument::PropagateUseCounters(nsIDocument* aParentDocument) { MOZ_ASSERT(this != aParentDocument); // What really matters here is that our use counters get propagated as // high up in the content document hierarchy as possible. So, // starting with aParentDocument, we need to find the toplevel content // document, and propagate our use counters into its // mChildDocumentUseCounters. nsIDocument* contentParent = aParentDocument->GetTopLevelContentDocument(); if (!contentParent) { return; } contentParent->mChildDocumentUseCounters |= mUseCounters; contentParent->mChildDocumentUseCounters |= mChildDocumentUseCounters; } void nsIDocument::SetPageUseCounter(UseCounter aUseCounter) { // We want to set the use counter on the "page" that owns us; the definition // of "page" depends on what kind of document we are. See the comments below // for details. In any event, checking all the conditions below is // reasonably expensive, so we cache whether we've notified our owning page. if (mNotifiedPageForUseCounter[aUseCounter]) { return; } mNotifiedPageForUseCounter[aUseCounter] = true; if (mDisplayDocument) { // If we are a resource document, we won't have a docshell and so we won't // record any page use counters on this document. Instead, we should // forward it up to the document that loaded us. MOZ_ASSERT(!mDocumentContainer); mDisplayDocument->SetChildDocumentUseCounter(aUseCounter); return; } if (IsBeingUsedAsImage()) { // If this is an SVG image document, we also won't have a docshell. MOZ_ASSERT(!mDocumentContainer); return; } // We only care about use counters in content. If we're already a toplevel // content document, then we should have already set the use counter on // ourselves, and we are done. nsIDocument* contentParent = GetTopLevelContentDocument(); if (!contentParent) { return; } if (this == contentParent) { MOZ_ASSERT(GetUseCounter(aUseCounter)); return; } contentParent->SetChildDocumentUseCounter(aUseCounter); } bool nsIDocument::HasScriptsBlockedBySandbox() { return mSandboxFlags & SANDBOXED_SCRIPTS; } bool nsIDocument::InlineScriptAllowedByCSP() { // this function assumes the inline script is parser created // (e.g., before setting attribute(!) event handlers) nsCOMPtr<nsIContentSecurityPolicy> csp; nsresult rv = NodePrincipal()->GetCsp(getter_AddRefs(csp)); NS_ENSURE_SUCCESS(rv, true); bool allowsInlineScript = true; if (csp) { nsresult rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT, EmptyString(), // aNonce true, // aParserCreated EmptyString(), // FIXME get script sample (bug 1314567) 0, // aLineNumber &allowsInlineScript); NS_ENSURE_SUCCESS(rv, true); } return allowsInlineScript; } void nsDocument::AddIntersectionObserver(DOMIntersectionObserver* aObserver) { MOZ_ASSERT(!mIntersectionObservers.Contains(aObserver), "Intersection observer already in the list"); mIntersectionObservers.PutEntry(aObserver); } void nsDocument::RemoveIntersectionObserver(DOMIntersectionObserver* aObserver) { mIntersectionObservers.RemoveEntry(aObserver); } void nsDocument::UpdateIntersectionObservations() { if (mIntersectionObservers.IsEmpty()) { return; } DOMHighResTimeStamp time = 0; if (nsPIDOMWindowInner* window = GetInnerWindow()) { Performance* perf = window->GetPerformance(); if (perf) { time = perf->Now(); } } nsTArray<RefPtr<DOMIntersectionObserver>> observers(mIntersectionObservers.Count()); for (auto iter = mIntersectionObservers.Iter(); !iter.Done(); iter.Next()) { DOMIntersectionObserver* observer = iter.Get()->GetKey(); observers.AppendElement(observer); } for (const auto& observer : observers) { if (observer) { observer->Update(this, time); } } } void nsDocument::ScheduleIntersectionObserverNotification() { if (mIntersectionObservers.IsEmpty()) { return; } nsCOMPtr<nsIRunnable> notification = NewRunnableMethod(this, &nsDocument::NotifyIntersectionObservers); NS_DispatchToCurrentThread(notification.forget()); } void nsDocument::NotifyIntersectionObservers() { nsTArray<RefPtr<DOMIntersectionObserver>> observers(mIntersectionObservers.Count()); for (auto iter = mIntersectionObservers.Iter(); !iter.Done(); iter.Next()) { DOMIntersectionObserver* observer = iter.Get()->GetKey(); observers.AppendElement(observer); } for (const auto& observer : observers) { if (observer) { observer->Notify(); } } } static bool NotifyLayerManagerRecreatedCallback(nsIDocument* aDocument, void* aData) { aDocument->NotifyLayerManagerRecreated(); return true; } void nsDocument::NotifyLayerManagerRecreated() { EnumerateActivityObservers(NotifyActivityChanged, nullptr); EnumerateSubDocuments(NotifyLayerManagerRecreatedCallback, nullptr); } XPathEvaluator* nsIDocument::XPathEvaluator() { if (!mXPathEvaluator) { mXPathEvaluator = new dom::XPathEvaluator(this); } return mXPathEvaluator; } already_AddRefed<nsIDocumentEncoder> nsIDocument::GetCachedEncoder() { return mCachedEncoder.forget(); } void nsIDocument::SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder) { mCachedEncoder = aEncoder; } void nsIDocument::SetContentTypeInternal(const nsACString& aType) { if (!IsHTMLOrXHTML() && mDefaultElementType == kNameSpaceID_None && aType.EqualsLiteral("application/xhtml+xml")) { mDefaultElementType = kNameSpaceID_XHTML; } mCachedEncoder = nullptr; mContentType = aType; } nsILoadContext* nsIDocument::GetLoadContext() const { return mDocumentContainer; } nsIDocShell* nsIDocument::GetDocShell() const { return mDocumentContainer; } void nsIDocument::SetStateObject(nsIStructuredCloneContainer *scContainer) { mStateObjectContainer = scContainer; mStateObjectCached = nullptr; } already_AddRefed<Element> nsIDocument::CreateHTMLElement(nsIAtom* aTag) { RefPtr<mozilla::dom::NodeInfo> nodeInfo; nodeInfo = mNodeInfoManager->GetNodeInfo(aTag, nullptr, kNameSpaceID_XHTML, nsIDOMNode::ELEMENT_NODE); MOZ_ASSERT(nodeInfo, "GetNodeInfo should never fail"); nsCOMPtr<Element> element; DebugOnly<nsresult> rv = NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(), mozilla::dom::NOT_FROM_PARSER); MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail"); return element.forget(); } /* static */ nsresult nsIDocument::GenerateDocumentId(nsAString& aId) { nsID id; nsresult rv = nsContentUtils::GenerateUUIDInPlace(id); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format char buffer[NSID_LENGTH]; id.ToProvidedString(buffer); NS_ConvertASCIItoUTF16 uuid(buffer); // Remove {} and the null terminator aId.Assign(Substring(uuid, 1, NSID_LENGTH - 3)); return NS_OK; } nsresult nsIDocument::GetOrCreateId(nsAString& aId) { if (mId.IsEmpty()) { nsresult rv = GenerateDocumentId(mId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } aId = mId; return NS_OK; } void nsIDocument::SetId(const nsAString& aId) { // The ID should only be set one time, but we may get the same value // more than once if the document is controlled coming out of bfcache. MOZ_ASSERT_IF(mId != aId, mId.IsEmpty()); mId = aId; } bool MarkDocumentTreeToBeInSyncOperation(nsIDocument* aDoc, void* aData) { nsCOMArray<nsIDocument>* documents = static_cast<nsCOMArray<nsIDocument>*>(aData); if (aDoc) { aDoc->SetIsInSyncOperation(true); documents->AppendObject(aDoc); aDoc->EnumerateSubDocuments(MarkDocumentTreeToBeInSyncOperation, aData); } return true; } nsAutoSyncOperation::nsAutoSyncOperation(nsIDocument* aDoc) { mMicroTaskLevel = nsContentUtils::MicroTaskLevel(); nsContentUtils::SetMicroTaskLevel(0); if (aDoc) { if (nsPIDOMWindowOuter* win = aDoc->GetWindow()) { if (nsCOMPtr<nsPIDOMWindowOuter> top = win->GetTop()) { nsCOMPtr<nsIDocument> doc = top->GetExtantDoc(); MarkDocumentTreeToBeInSyncOperation(doc, &mDocuments); } } } } nsAutoSyncOperation::~nsAutoSyncOperation() { for (int32_t i = 0; i < mDocuments.Count(); ++i) { mDocuments[i]->SetIsInSyncOperation(false); } nsContentUtils::SetMicroTaskLevel(mMicroTaskLevel); } gfxUserFontSet* nsIDocument::GetUserFontSet(bool aFlushUserFontSet) { // We want to initialize the user font set lazily the first time the // user asks for it, rather than building it too early and forcing // rule cascade creation. Thus we try to enforce the invariant that // we *never* build the user font set until the first call to // GetUserFontSet. However, once it's been requested, we can't wait // for somebody to call GetUserFontSet in order to rebuild it (see // comments below in RebuildUserFontSet for why). #ifdef DEBUG bool userFontSetGottenBefore = mGetUserFontSetCalled; #endif // Set mGetUserFontSetCalled up front, so that FlushUserFontSet will actually // flush. mGetUserFontSetCalled = true; if (mFontFaceSetDirty && aFlushUserFontSet) { // If this assertion fails, and there have actually been changes to // @font-face rules, then we will call StyleChangeReflow in // FlushUserFontSet. If we're in the middle of reflow, // that's a bad thing to do, and the caller was responsible for // flushing first. If we're not (e.g., in frame construction), it's // ok. NS_ASSERTION(!userFontSetGottenBefore || !GetShell() || !GetShell()->IsReflowLocked(), "FlushUserFontSet should have been called first"); FlushUserFontSet(); } if (!mFontFaceSet) { return nullptr; } return mFontFaceSet->GetUserFontSet(); } void nsIDocument::FlushUserFontSet() { if (!mGetUserFontSetCalled) { return; // No one cares about this font set yet, but we want to be careful // to not unset our mFontFaceSetDirty bit, so when someone really // does we'll create it. } if (mFontFaceSetDirty) { if (gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) { nsTArray<nsFontFaceRuleContainer> rules; nsIPresShell* shell = GetShell(); if (shell) { // XXXheycam ServoStyleSets don't support exposing @font-face rules yet. if (shell->StyleSet()->IsGecko()) { if (!shell->StyleSet()->AsGecko()->AppendFontFaceRules(rules)) { return; } } else { NS_WARNING("stylo: ServoStyleSets cannot handle @font-face rules yet. " "See bug 1290237."); } } bool changed = false; if (!mFontFaceSet && !rules.IsEmpty()) { nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject()); mFontFaceSet = new FontFaceSet(window, this); } if (mFontFaceSet) { changed = mFontFaceSet->UpdateRules(rules); } // We need to enqueue a style change reflow (for later) to // reflect that we're modifying @font-face rules. (However, // without a reflow, nothing will happen to start any downloads // that are needed.) if (changed && shell) { nsPresContext* presContext = shell->GetPresContext(); if (presContext) { presContext->UserFontSetUpdated(); } } } mFontFaceSetDirty = false; } } void nsIDocument::RebuildUserFontSet() { if (!mGetUserFontSetCalled) { // We want to lazily build the user font set the first time it's // requested (so we don't force creation of rule cascades too // early), so don't do anything now. return; } mFontFaceSetDirty = true; SetNeedStyleFlush(); // Somebody has already asked for the user font set, so we need to // post an event to rebuild it. Setting the user font set to be dirty // and lazily rebuilding it isn't sufficient, since it is only the act // of rebuilding it that will trigger the style change reflow that // calls GetUserFontSet. (This reflow causes rebuilding of text runs, // which starts font loads, whose completion causes another style // change reflow). if (!mPostedFlushUserFontSet) { nsCOMPtr<nsIRunnable> ev = NewRunnableMethod(this, &nsIDocument::HandleRebuildUserFontSet); if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) { mPostedFlushUserFontSet = true; } } } FontFaceSet* nsIDocument::Fonts() { if (!mFontFaceSet) { nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject()); mFontFaceSet = new FontFaceSet(window, this); GetUserFontSet(); // this will cause the user font set to be created/updated } return mFontFaceSet; } void nsIDocument::ReportHasScrollLinkedEffect() { if (mHasScrollLinkedEffect) { // We already did this once for this document, don't do it again. return; } mHasScrollLinkedEffect = true; nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Async Pan/Zoom"), this, nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound2"); } void nsIDocument::UpdateStyleBackendType() { MOZ_ASSERT(mStyleBackendType == StyleBackendType(0), "no need to call UpdateStyleBackendType now"); // Assume Gecko by default. mStyleBackendType = StyleBackendType::Gecko; #ifdef MOZ_STYLO // XXX For now we use a Servo-backed style set only for (X)HTML documents // in content docshells. This should let us avoid implementing XUL-specific // CSS features. And apart from not supporting SVG properties in Servo // yet, the root SVG element likes to create a style sheet for an SVG // document before we have a pres shell (i.e. before we make the decision // here about whether to use a Gecko- or Servo-backed style system), so // we avoid Servo-backed style sets for SVG documents. if (!mDocumentContainer) { NS_WARNING("stylo: No docshell yet, assuming Gecko style system"); } else if (nsLayoutUtils::SupportsServoStyleBackend(this)) { mStyleBackendType = StyleBackendType::Servo; } #endif } const nsString* nsDocument::CheckCustomElementName(const ElementCreationOptions& aOptions, const nsAString& aLocalName, uint32_t aNamespaceID, ErrorResult& rv) { // only check aOptions if 'is' is passed and the webcomponents preference // is enabled if (!aOptions.mIs.WasPassed() || !CustomElementRegistry::IsCustomElementEnabled()) { return nullptr; } const nsString* is = &aOptions.mIs.Value(); // Throw NotFoundError if 'is' is not-null and definition is null if (!nsContentUtils::LookupCustomElementDefinition(this, aLocalName, aNamespaceID, is)) { rv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); } return is; } Selection* nsIDocument::GetSelection(ErrorResult& aRv) { nsCOMPtr<nsPIDOMWindowInner> window = GetInnerWindow(); if (!window) { return nullptr; } NS_ASSERTION(window->IsInnerWindow(), "Should have inner window here!"); if (!window->IsCurrentInnerWindow()) { return nullptr; } return nsGlobalWindow::Cast(window)->GetSelection(aRv); }