diff options
Diffstat (limited to 'dom/html/HTMLLinkElement.cpp')
-rw-r--r-- | dom/html/HTMLLinkElement.cpp | 584 |
1 files changed, 584 insertions, 0 deletions
diff --git a/dom/html/HTMLLinkElement.cpp b/dom/html/HTMLLinkElement.cpp new file mode 100644 index 000000000..8afe767bd --- /dev/null +++ b/dom/html/HTMLLinkElement.cpp @@ -0,0 +1,584 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/HTMLLinkElement.h" + +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventStates.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/HTMLLinkElementBinding.h" +#include "nsContentUtils.h" +#include "nsGenericHTMLElement.h" +#include "nsGkAtoms.h" +#include "nsDOMTokenList.h" +#include "nsIContentInlines.h" +#include "nsIDocument.h" +#include "nsIDOMEvent.h" +#include "nsIDOMStyleSheet.h" +#include "nsINode.h" +#include "nsIStyleSheetLinkingElement.h" +#include "nsIURL.h" +#include "nsPIDOMWindow.h" +#include "nsReadableUtils.h" +#include "nsStyleConsts.h" +#include "nsStyleLinkElement.h" +#include "nsUnicharUtils.h" + +#define LINK_ELEMENT_FLAG_BIT(n_) \ + NODE_FLAG_BIT(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + (n_)) + +// Link element specific bits +enum { + // Indicates that a DNS Prefetch has been requested from this Link element. + HTML_LINK_DNS_PREFETCH_REQUESTED = LINK_ELEMENT_FLAG_BIT(0), + + // Indicates that a DNS Prefetch was added to the deferral queue + HTML_LINK_DNS_PREFETCH_DEFERRED = LINK_ELEMENT_FLAG_BIT(1) +}; + +#undef LINK_ELEMENT_FLAG_BIT + +ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 2); + +NS_IMPL_NS_NEW_HTML_ELEMENT(Link) + +namespace mozilla { +namespace dom { + +HTMLLinkElement::HTMLLinkElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo) + : nsGenericHTMLElement(aNodeInfo) + , Link(this) +{ +} + +HTMLLinkElement::~HTMLLinkElement() +{ +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLLinkElement) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLLinkElement, + nsGenericHTMLElement) + tmp->nsStyleLinkElement::Traverse(cb); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImportLoader) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLLinkElement, + nsGenericHTMLElement) + tmp->nsStyleLinkElement::Unlink(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mImportLoader) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_ADDREF_INHERITED(HTMLLinkElement, Element) +NS_IMPL_RELEASE_INHERITED(HTMLLinkElement, Element) + + +// QueryInterface implementation for HTMLLinkElement +NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLLinkElement) + NS_INTERFACE_TABLE_INHERITED(HTMLLinkElement, + nsIDOMHTMLLinkElement, + nsIStyleSheetLinkingElement, + Link) +NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement) + + +NS_IMPL_ELEMENT_CLONE(HTMLLinkElement) + +bool +HTMLLinkElement::Disabled() +{ + StyleSheet* ss = GetSheet(); + return ss && ss->Disabled(); +} + +NS_IMETHODIMP +HTMLLinkElement::GetMozDisabled(bool* aDisabled) +{ + *aDisabled = Disabled(); + return NS_OK; +} + +void +HTMLLinkElement::SetDisabled(bool aDisabled) +{ + if (StyleSheet* ss = GetSheet()) { + ss->SetDisabled(aDisabled); + } +} + +NS_IMETHODIMP +HTMLLinkElement::SetMozDisabled(bool aDisabled) +{ + SetDisabled(aDisabled); + return NS_OK; +} + + +NS_IMPL_STRING_ATTR(HTMLLinkElement, Charset, charset) +NS_IMPL_URI_ATTR(HTMLLinkElement, Href, href) +NS_IMPL_STRING_ATTR(HTMLLinkElement, Hreflang, hreflang) +NS_IMPL_STRING_ATTR(HTMLLinkElement, Media, media) +NS_IMPL_STRING_ATTR(HTMLLinkElement, Rel, rel) +NS_IMPL_STRING_ATTR(HTMLLinkElement, Rev, rev) +NS_IMPL_STRING_ATTR(HTMLLinkElement, Target, target) +NS_IMPL_STRING_ATTR(HTMLLinkElement, Type, type) + +void +HTMLLinkElement::OnDNSPrefetchRequested() +{ + UnsetFlags(HTML_LINK_DNS_PREFETCH_DEFERRED); + SetFlags(HTML_LINK_DNS_PREFETCH_REQUESTED); +} + +void +HTMLLinkElement::OnDNSPrefetchDeferred() +{ + UnsetFlags(HTML_LINK_DNS_PREFETCH_REQUESTED); + SetFlags(HTML_LINK_DNS_PREFETCH_DEFERRED); +} + +bool +HTMLLinkElement::HasDeferredDNSPrefetchRequest() +{ + return HasFlag(HTML_LINK_DNS_PREFETCH_DEFERRED); +} + +nsresult +HTMLLinkElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, + nsIContent* aBindingParent, + bool aCompileEventHandlers) +{ + Link::ResetLinkState(false, Link::ElementHasHref()); + + nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent, + aBindingParent, + aCompileEventHandlers); + NS_ENSURE_SUCCESS(rv, rv); + + // Link must be inert in ShadowRoot. + if (aDocument && !GetContainingShadow()) { + aDocument->RegisterPendingLinkUpdate(this); + } + + if (IsInComposedDoc()) { + TryDNSPrefetchPreconnectOrPrefetch(); + } + + void (HTMLLinkElement::*update)() = &HTMLLinkElement::UpdateStyleSheetInternal; + nsContentUtils::AddScriptRunner(NewRunnableMethod(this, update)); + + void (HTMLLinkElement::*updateImport)() = &HTMLLinkElement::UpdateImport; + nsContentUtils::AddScriptRunner(NewRunnableMethod(this, updateImport)); + + CreateAndDispatchEvent(aDocument, NS_LITERAL_STRING("DOMLinkAdded")); + + return rv; +} + +void +HTMLLinkElement::LinkAdded() +{ + CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkAdded")); +} + +void +HTMLLinkElement::LinkRemoved() +{ + CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkRemoved")); +} + +void +HTMLLinkElement::UnbindFromTree(bool aDeep, bool aNullParent) +{ + // Cancel any DNS prefetches + // Note: Must come before ResetLinkState. If called after, it will recreate + // mCachedURI based on data that is invalid - due to a call to GetHostname. + CancelDNSPrefetch(HTML_LINK_DNS_PREFETCH_DEFERRED, + HTML_LINK_DNS_PREFETCH_REQUESTED); + CancelPrefetch(); + + // If this link is ever reinserted into a document, it might + // be under a different xml:base, so forget the cached state now. + Link::ResetLinkState(false, Link::ElementHasHref()); + + // If this is reinserted back into the document it will not be + // from the parser. + nsCOMPtr<nsIDocument> oldDoc = GetUncomposedDoc(); + + // Check for a ShadowRoot because link elements are inert in a + // ShadowRoot. + ShadowRoot* oldShadowRoot = GetBindingParent() ? + GetBindingParent()->GetShadowRoot() : nullptr; + + OwnerDoc()->UnregisterPendingLinkUpdate(this); + + CreateAndDispatchEvent(oldDoc, NS_LITERAL_STRING("DOMLinkRemoved")); + nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent); + + UpdateStyleSheetInternal(oldDoc, oldShadowRoot); + UpdateImport(); +} + +bool +HTMLLinkElement::ParseAttribute(int32_t aNamespaceID, + nsIAtom* aAttribute, + const nsAString& aValue, + nsAttrValue& aResult) +{ + if (aNamespaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::crossorigin) { + ParseCORSValue(aValue, aResult); + return true; + } + + if (aAttribute == nsGkAtoms::sizes) { + aResult.ParseAtomArray(aValue); + return true; + } + + if (aAttribute == nsGkAtoms::integrity) { + aResult.ParseStringOrAtom(aValue); + return true; + } + } + + return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, + aResult); +} + +void +HTMLLinkElement::CreateAndDispatchEvent(nsIDocument* aDoc, + const nsAString& aEventName) +{ + if (!aDoc) + return; + + // In the unlikely case that both rev is specified *and* rel=stylesheet, + // this code will cause the event to fire, on the principle that maybe the + // page really does want to specify that its author is a stylesheet. Since + // this should never actually happen and the performance hit is minimal, + // doing the "right" thing costs virtually nothing here, even if it doesn't + // make much sense. + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::_empty, &nsGkAtoms::stylesheet, nullptr}; + + if (!nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None, + nsGkAtoms::rev) && + FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::rel, + strings, eIgnoreCase) != ATTR_VALUE_NO_MATCH) + return; + + RefPtr<AsyncEventDispatcher> asyncDispatcher = + new AsyncEventDispatcher(this, aEventName, true, true); + // Always run async in order to avoid running script when the content + // sink isn't expecting it. + asyncDispatcher->PostDOMEvent(); +} + +void +HTMLLinkElement::UpdateImport() +{ + // 1. link node should be attached to the document. + nsCOMPtr<nsIDocument> doc = GetUncomposedDoc(); + if (!doc) { + // We might have been just removed from the document, so + // let's remove ourself from the list of link nodes of + // the import and reset mImportLoader. + if (mImportLoader) { + mImportLoader->RemoveLinkElement(this); + mImportLoader = nullptr; + } + return; + } + + // 2. rel type should be import. + nsAutoString rel; + GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel); + uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(rel, NodePrincipal()); + if (!(linkTypes & eHTMLIMPORT)) { + mImportLoader = nullptr; + return; + } + + nsCOMPtr<nsIURI> uri = GetHrefURI(); + if (!uri) { + mImportLoader = nullptr; + return; + } + + if (!nsStyleLinkElement::IsImportEnabled()) { + // For now imports are hidden behind a pref... + return; + } + + RefPtr<ImportManager> manager = doc->ImportManager(); + MOZ_ASSERT(manager, "ImportManager should be created lazily when needed"); + + { + // The load even might fire sooner than we could set mImportLoader so + // we must use async event and a scriptBlocker here. + nsAutoScriptBlocker scriptBlocker; + // CORS check will happen at the start of the load. + mImportLoader = manager->Get(uri, this, doc); + } +} + +nsresult +HTMLLinkElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName, + nsAttrValueOrString* aValue, bool aNotify) +{ + if (aNameSpaceID == kNameSpaceID_None && + (aName == nsGkAtoms::href || aName == nsGkAtoms::rel)) { + CancelDNSPrefetch(HTML_LINK_DNS_PREFETCH_DEFERRED, + HTML_LINK_DNS_PREFETCH_REQUESTED); + CancelPrefetch(); + } + + return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, + aValue, aNotify); +} + +nsresult +HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, + const nsAttrValue* aValue, bool aNotify) +{ + // It's safe to call ResetLinkState here because our new attr value has + // already been set or unset. ResetLinkState needs the updated attribute + // value because notifying the document that content states have changed will + // call IntrinsicState, which will try to get updated information about the + // visitedness from Link. + if (aName == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) { + bool hasHref = aValue; + Link::ResetLinkState(!!aNotify, hasHref); + if (IsInUncomposedDoc()) { + CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkChanged")); + } + } + + if (aValue) { + if (aNameSpaceID == kNameSpaceID_None && + (aName == nsGkAtoms::href || + aName == nsGkAtoms::rel || + aName == nsGkAtoms::title || + aName == nsGkAtoms::media || + aName == nsGkAtoms::type)) { + bool dropSheet = false; + if (aName == nsGkAtoms::rel) { + nsAutoString value; + aValue->ToString(value); + uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(value, + NodePrincipal()); + if (GetSheet()) { + dropSheet = !(linkTypes & nsStyleLinkElement::eSTYLESHEET); + } else if (linkTypes & eHTMLIMPORT) { + UpdateImport(); + } + } + + if (aName == nsGkAtoms::href) { + UpdateImport(); + } + + if ((aName == nsGkAtoms::rel || aName == nsGkAtoms::href) && + IsInComposedDoc()) { + TryDNSPrefetchPreconnectOrPrefetch(); + } + + UpdateStyleSheetInternal(nullptr, nullptr, + dropSheet || + (aName == nsGkAtoms::title || + aName == nsGkAtoms::media || + aName == nsGkAtoms::type)); + } + } else { + // Since removing href or rel makes us no longer link to a + // stylesheet, force updates for those too. + if (aNameSpaceID == kNameSpaceID_None) { + if (aName == nsGkAtoms::href || + aName == nsGkAtoms::rel || + aName == nsGkAtoms::title || + aName == nsGkAtoms::media || + aName == nsGkAtoms::type) { + UpdateStyleSheetInternal(nullptr, nullptr, true); + } + if (aName == nsGkAtoms::href || + aName == nsGkAtoms::rel) { + UpdateImport(); + } + } + } + + return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, + aNotify); +} + +nsresult +HTMLLinkElement::PreHandleEvent(EventChainPreVisitor& aVisitor) +{ + return PreHandleEventForAnchors(aVisitor); +} + +nsresult +HTMLLinkElement::PostHandleEvent(EventChainPostVisitor& aVisitor) +{ + return PostHandleEventForAnchors(aVisitor); +} + +bool +HTMLLinkElement::IsLink(nsIURI** aURI) const +{ + return IsHTMLLink(aURI); +} + +void +HTMLLinkElement::GetLinkTarget(nsAString& aTarget) +{ + GetAttr(kNameSpaceID_None, nsGkAtoms::target, aTarget); + if (aTarget.IsEmpty()) { + GetBaseTarget(aTarget); + } +} + +static const DOMTokenListSupportedToken sSupportedRelValues[] = { + // Keep this in sync with ToLinkMask in nsStyleLinkElement.cpp. + // "import" must come first because it's conditional. + "import", + "prefetch", + "dns-prefetch", + "stylesheet", + "next", + "alternate", + "preconnect", + "icon", + "search", + nullptr +}; + +nsDOMTokenList* +HTMLLinkElement::RelList() +{ + if (!mRelList) { + const DOMTokenListSupportedTokenArray relValues = + nsStyleLinkElement::IsImportEnabled() ? + sSupportedRelValues : &sSupportedRelValues[1]; + + mRelList = new nsDOMTokenList(this, nsGkAtoms::rel, relValues); + } + return mRelList; +} + +already_AddRefed<nsIURI> +HTMLLinkElement::GetHrefURI() const +{ + return GetHrefURIForAnchors(); +} + +already_AddRefed<nsIURI> +HTMLLinkElement::GetStyleSheetURL(bool* aIsInline) +{ + *aIsInline = false; + nsAutoString href; + GetAttr(kNameSpaceID_None, nsGkAtoms::href, href); + if (href.IsEmpty()) { + return nullptr; + } + nsCOMPtr<nsIURI> uri = Link::GetURI(); + return uri.forget(); +} + +void +HTMLLinkElement::GetStyleSheetInfo(nsAString& aTitle, + nsAString& aType, + nsAString& aMedia, + bool* aIsScoped, + bool* aIsAlternate) +{ + aTitle.Truncate(); + aType.Truncate(); + aMedia.Truncate(); + *aIsScoped = false; + *aIsAlternate = false; + + nsAutoString rel; + GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel); + uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(rel, NodePrincipal()); + // Is it a stylesheet link? + if (!(linkTypes & nsStyleLinkElement::eSTYLESHEET)) { + return; + } + + nsAutoString title; + GetAttr(kNameSpaceID_None, nsGkAtoms::title, title); + title.CompressWhitespace(); + aTitle.Assign(title); + + // If alternate, does it have title? + if (linkTypes & nsStyleLinkElement::eALTERNATE) { + if (aTitle.IsEmpty()) { // alternates must have title + return; + } else { + *aIsAlternate = true; + } + } + + GetAttr(kNameSpaceID_None, nsGkAtoms::media, aMedia); + // The HTML5 spec is formulated in terms of the CSSOM spec, which specifies + // that media queries should be ASCII lowercased during serialization. + nsContentUtils::ASCIIToLower(aMedia); + + nsAutoString mimeType; + nsAutoString notUsed; + GetAttr(kNameSpaceID_None, nsGkAtoms::type, aType); + nsContentUtils::SplitMimeType(aType, mimeType, notUsed); + if (!mimeType.IsEmpty() && !mimeType.LowerCaseEqualsLiteral("text/css")) { + return; + } + + // If we get here we assume that we're loading a css file, so set the + // type to 'text/css' + aType.AssignLiteral("text/css"); + + return; +} + +CORSMode +HTMLLinkElement::GetCORSMode() const +{ + return AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin)); +} + +EventStates +HTMLLinkElement::IntrinsicState() const +{ + return Link::LinkState() | nsGenericHTMLElement::IntrinsicState(); +} + +size_t +HTMLLinkElement::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + return nsGenericHTMLElement::SizeOfExcludingThis(aMallocSizeOf) + + Link::SizeOfExcludingThis(aMallocSizeOf); +} + +JSObject* +HTMLLinkElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return HTMLLinkElementBinding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<nsIDocument> +HTMLLinkElement::GetImport() +{ + return mImportLoader ? RefPtr<nsIDocument>(mImportLoader->GetImport()).forget() : nullptr; +} + +} // namespace dom +} // namespace mozilla |