Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "mozilla/dom/HTMLLinkElement.h"
8 :
9 : #include "mozilla/AsyncEventDispatcher.h"
10 : #include "mozilla/Attributes.h"
11 : #include "mozilla/EventDispatcher.h"
12 : #include "mozilla/EventStates.h"
13 : #include "mozilla/MemoryReporting.h"
14 : #include "mozilla/Preferences.h"
15 : #include "mozilla/dom/HTMLLinkElementBinding.h"
16 : #include "nsContentUtils.h"
17 : #include "nsGenericHTMLElement.h"
18 : #include "nsGkAtoms.h"
19 : #include "nsDOMTokenList.h"
20 : #include "nsIContentInlines.h"
21 : #include "nsIDocument.h"
22 : #include "nsIDOMEvent.h"
23 : #include "nsIDOMStyleSheet.h"
24 : #include "nsINode.h"
25 : #include "nsIStyleSheetLinkingElement.h"
26 : #include "nsIURL.h"
27 : #include "nsPIDOMWindow.h"
28 : #include "nsReadableUtils.h"
29 : #include "nsStyleConsts.h"
30 : #include "nsStyleLinkElement.h"
31 : #include "nsUnicharUtils.h"
32 :
33 : #define LINK_ELEMENT_FLAG_BIT(n_) \
34 : NODE_FLAG_BIT(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + (n_))
35 :
36 : // Link element specific bits
37 : enum {
38 : // Indicates that a DNS Prefetch has been requested from this Link element.
39 : HTML_LINK_DNS_PREFETCH_REQUESTED = LINK_ELEMENT_FLAG_BIT(0),
40 :
41 : // Indicates that a DNS Prefetch was added to the deferral queue
42 : HTML_LINK_DNS_PREFETCH_DEFERRED = LINK_ELEMENT_FLAG_BIT(1)
43 : };
44 :
45 : #undef LINK_ELEMENT_FLAG_BIT
46 :
47 : ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 2);
48 :
49 0 : NS_IMPL_NS_NEW_HTML_ELEMENT(Link)
50 :
51 : namespace mozilla {
52 : namespace dom {
53 :
54 0 : HTMLLinkElement::HTMLLinkElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
55 : : nsGenericHTMLElement(aNodeInfo)
56 0 : , Link(this)
57 : {
58 0 : }
59 :
60 0 : HTMLLinkElement::~HTMLLinkElement()
61 : {
62 0 : }
63 :
64 : NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLLinkElement)
65 :
66 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLLinkElement,
67 : nsGenericHTMLElement)
68 0 : tmp->nsStyleLinkElement::Traverse(cb);
69 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelList)
70 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
71 :
72 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLLinkElement,
73 : nsGenericHTMLElement)
74 0 : tmp->nsStyleLinkElement::Unlink();
75 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mRelList)
76 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
77 :
78 0 : NS_IMPL_ADDREF_INHERITED(HTMLLinkElement, Element)
79 0 : NS_IMPL_RELEASE_INHERITED(HTMLLinkElement, Element)
80 :
81 :
82 : // QueryInterface implementation for HTMLLinkElement
83 0 : NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLLinkElement)
84 0 : NS_INTERFACE_TABLE_INHERITED(HTMLLinkElement,
85 : nsIDOMHTMLLinkElement,
86 : nsIStyleSheetLinkingElement,
87 : Link)
88 0 : NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLElement)
89 :
90 :
91 0 : NS_IMPL_ELEMENT_CLONE(HTMLLinkElement)
92 :
93 : bool
94 0 : HTMLLinkElement::Disabled()
95 : {
96 0 : StyleSheet* ss = GetSheet();
97 0 : return ss && ss->Disabled();
98 : }
99 :
100 : NS_IMETHODIMP
101 0 : HTMLLinkElement::GetMozDisabled(bool* aDisabled)
102 : {
103 0 : *aDisabled = Disabled();
104 0 : return NS_OK;
105 : }
106 :
107 : void
108 0 : HTMLLinkElement::SetDisabled(bool aDisabled)
109 : {
110 0 : if (StyleSheet* ss = GetSheet()) {
111 0 : ss->SetDisabled(aDisabled);
112 : }
113 0 : }
114 :
115 : NS_IMETHODIMP
116 0 : HTMLLinkElement::SetMozDisabled(bool aDisabled)
117 : {
118 0 : SetDisabled(aDisabled);
119 0 : return NS_OK;
120 : }
121 :
122 :
123 0 : NS_IMPL_STRING_ATTR(HTMLLinkElement, Charset, charset)
124 0 : NS_IMPL_URI_ATTR(HTMLLinkElement, Href, href)
125 0 : NS_IMPL_STRING_ATTR(HTMLLinkElement, Hreflang, hreflang)
126 0 : NS_IMPL_STRING_ATTR(HTMLLinkElement, Media, media)
127 0 : NS_IMPL_STRING_ATTR(HTMLLinkElement, Rel, rel)
128 0 : NS_IMPL_STRING_ATTR(HTMLLinkElement, Rev, rev)
129 0 : NS_IMPL_STRING_ATTR(HTMLLinkElement, Target, target)
130 0 : NS_IMPL_STRING_ATTR(HTMLLinkElement, Type, type)
131 :
132 : void
133 0 : HTMLLinkElement::OnDNSPrefetchRequested()
134 : {
135 0 : UnsetFlags(HTML_LINK_DNS_PREFETCH_DEFERRED);
136 0 : SetFlags(HTML_LINK_DNS_PREFETCH_REQUESTED);
137 0 : }
138 :
139 : void
140 0 : HTMLLinkElement::OnDNSPrefetchDeferred()
141 : {
142 0 : UnsetFlags(HTML_LINK_DNS_PREFETCH_REQUESTED);
143 0 : SetFlags(HTML_LINK_DNS_PREFETCH_DEFERRED);
144 0 : }
145 :
146 : bool
147 0 : HTMLLinkElement::HasDeferredDNSPrefetchRequest()
148 : {
149 0 : return HasFlag(HTML_LINK_DNS_PREFETCH_DEFERRED);
150 : }
151 :
152 : nsresult
153 0 : HTMLLinkElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
154 : nsIContent* aBindingParent,
155 : bool aCompileEventHandlers)
156 : {
157 0 : Link::ResetLinkState(false, Link::ElementHasHref());
158 :
159 0 : nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
160 : aBindingParent,
161 0 : aCompileEventHandlers);
162 0 : NS_ENSURE_SUCCESS(rv, rv);
163 :
164 : // Link must be inert in ShadowRoot.
165 0 : if (aDocument && !GetContainingShadow()) {
166 0 : aDocument->RegisterPendingLinkUpdate(this);
167 : }
168 :
169 0 : if (IsInComposedDoc()) {
170 0 : TryDNSPrefetchOrPreconnectOrPrefetchOrPreloadOrPrerender();
171 : }
172 :
173 0 : void (HTMLLinkElement::*update)() = &HTMLLinkElement::UpdateStyleSheetInternal;
174 0 : nsContentUtils::AddScriptRunner(
175 0 : NewRunnableMethod("dom::HTMLLinkElement::BindToTree", this, update));
176 :
177 0 : CreateAndDispatchEvent(aDocument, NS_LITERAL_STRING("DOMLinkAdded"));
178 :
179 0 : return rv;
180 : }
181 :
182 : void
183 0 : HTMLLinkElement::LinkAdded()
184 : {
185 0 : CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkAdded"));
186 0 : }
187 :
188 : void
189 0 : HTMLLinkElement::LinkRemoved()
190 : {
191 0 : CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkRemoved"));
192 0 : }
193 :
194 : void
195 0 : HTMLLinkElement::UnbindFromTree(bool aDeep, bool aNullParent)
196 : {
197 : // Cancel any DNS prefetches
198 : // Note: Must come before ResetLinkState. If called after, it will recreate
199 : // mCachedURI based on data that is invalid - due to a call to GetHostname.
200 0 : CancelDNSPrefetch(HTML_LINK_DNS_PREFETCH_DEFERRED,
201 0 : HTML_LINK_DNS_PREFETCH_REQUESTED);
202 0 : CancelPrefetchOrPreload();
203 :
204 : // If this link is ever reinserted into a document, it might
205 : // be under a different xml:base, so forget the cached state now.
206 0 : Link::ResetLinkState(false, Link::ElementHasHref());
207 :
208 : // If this is reinserted back into the document it will not be
209 : // from the parser.
210 0 : nsCOMPtr<nsIDocument> oldDoc = GetUncomposedDoc();
211 :
212 : // Check for a ShadowRoot because link elements are inert in a
213 : // ShadowRoot.
214 0 : ShadowRoot* oldShadowRoot = GetBindingParent() ?
215 0 : GetBindingParent()->GetShadowRoot() : nullptr;
216 :
217 0 : CreateAndDispatchEvent(oldDoc, NS_LITERAL_STRING("DOMLinkRemoved"));
218 0 : nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
219 :
220 0 : UpdateStyleSheetInternal(oldDoc, oldShadowRoot);
221 0 : }
222 :
223 : bool
224 0 : HTMLLinkElement::ParseAttribute(int32_t aNamespaceID,
225 : nsIAtom* aAttribute,
226 : const nsAString& aValue,
227 : nsAttrValue& aResult)
228 : {
229 0 : if (aNamespaceID == kNameSpaceID_None) {
230 0 : if (aAttribute == nsGkAtoms::crossorigin) {
231 0 : ParseCORSValue(aValue, aResult);
232 0 : return true;
233 : }
234 :
235 0 : if (aAttribute == nsGkAtoms::as) {
236 0 : ParseAsValue(aValue, aResult);
237 0 : return true;
238 : }
239 :
240 0 : if (aAttribute == nsGkAtoms::sizes) {
241 0 : aResult.ParseAtomArray(aValue);
242 0 : return true;
243 : }
244 :
245 0 : if (aAttribute == nsGkAtoms::integrity) {
246 0 : aResult.ParseStringOrAtom(aValue);
247 0 : return true;
248 : }
249 : }
250 :
251 0 : return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
252 0 : aResult);
253 : }
254 :
255 : void
256 0 : HTMLLinkElement::CreateAndDispatchEvent(nsIDocument* aDoc,
257 : const nsAString& aEventName)
258 : {
259 0 : if (!aDoc)
260 0 : return;
261 :
262 : // In the unlikely case that both rev is specified *and* rel=stylesheet,
263 : // this code will cause the event to fire, on the principle that maybe the
264 : // page really does want to specify that its author is a stylesheet. Since
265 : // this should never actually happen and the performance hit is minimal,
266 : // doing the "right" thing costs virtually nothing here, even if it doesn't
267 : // make much sense.
268 : static nsIContent::AttrValuesArray strings[] =
269 : {&nsGkAtoms::_empty, &nsGkAtoms::stylesheet, nullptr};
270 :
271 0 : if (!nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
272 0 : nsGkAtoms::rev) &&
273 0 : FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::rel,
274 0 : strings, eIgnoreCase) != ATTR_VALUE_NO_MATCH)
275 0 : return;
276 :
277 : RefPtr<AsyncEventDispatcher> asyncDispatcher =
278 0 : new AsyncEventDispatcher(this, aEventName, true, true);
279 : // Always run async in order to avoid running script when the content
280 : // sink isn't expecting it.
281 0 : asyncDispatcher->PostDOMEvent();
282 : }
283 :
284 : nsresult
285 0 : HTMLLinkElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
286 : const nsAttrValueOrString* aValue, bool aNotify)
287 : {
288 0 : if (aNameSpaceID == kNameSpaceID_None &&
289 0 : (aName == nsGkAtoms::href || aName == nsGkAtoms::rel)) {
290 0 : CancelDNSPrefetch(HTML_LINK_DNS_PREFETCH_DEFERRED,
291 0 : HTML_LINK_DNS_PREFETCH_REQUESTED);
292 0 : CancelPrefetchOrPreload();
293 : }
294 :
295 0 : return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName,
296 0 : aValue, aNotify);
297 : }
298 :
299 : nsresult
300 0 : HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
301 : const nsAttrValue* aValue,
302 : const nsAttrValue* aOldValue, bool aNotify)
303 : {
304 : // It's safe to call ResetLinkState here because our new attr value has
305 : // already been set or unset. ResetLinkState needs the updated attribute
306 : // value because notifying the document that content states have changed will
307 : // call IntrinsicState, which will try to get updated information about the
308 : // visitedness from Link.
309 0 : if (aName == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
310 0 : bool hasHref = aValue;
311 0 : Link::ResetLinkState(!!aNotify, hasHref);
312 0 : if (IsInUncomposedDoc()) {
313 0 : CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkChanged"));
314 : }
315 : }
316 :
317 0 : if (aValue) {
318 0 : if (aNameSpaceID == kNameSpaceID_None &&
319 0 : (aName == nsGkAtoms::href ||
320 0 : aName == nsGkAtoms::rel ||
321 0 : aName == nsGkAtoms::title ||
322 0 : aName == nsGkAtoms::media ||
323 0 : aName == nsGkAtoms::type ||
324 0 : aName == nsGkAtoms::as ||
325 0 : aName == nsGkAtoms::crossorigin)) {
326 0 : bool dropSheet = false;
327 0 : if (aName == nsGkAtoms::rel) {
328 0 : nsAutoString value;
329 0 : aValue->ToString(value);
330 0 : uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(value);
331 0 : if (GetSheet()) {
332 0 : dropSheet = !(linkTypes & nsStyleLinkElement::eSTYLESHEET);
333 : }
334 : }
335 :
336 0 : if ((aName == nsGkAtoms::rel || aName == nsGkAtoms::href) &&
337 0 : IsInComposedDoc()) {
338 0 : TryDNSPrefetchOrPreconnectOrPrefetchOrPreloadOrPrerender();
339 : }
340 :
341 0 : if ((aName == nsGkAtoms::as || aName == nsGkAtoms::type ||
342 0 : aName == nsGkAtoms::crossorigin || aName == nsGkAtoms::media) &&
343 0 : IsInComposedDoc()) {
344 0 : UpdatePreload(aName, aValue, aOldValue);
345 : }
346 :
347 0 : UpdateStyleSheetInternal(nullptr, nullptr,
348 0 : dropSheet ||
349 0 : (aName == nsGkAtoms::title ||
350 0 : aName == nsGkAtoms::media ||
351 0 : aName == nsGkAtoms::type));
352 : }
353 : } else {
354 : // Since removing href or rel makes us no longer link to a
355 : // stylesheet, force updates for those too.
356 0 : if (aNameSpaceID == kNameSpaceID_None) {
357 0 : if (aName == nsGkAtoms::href ||
358 0 : aName == nsGkAtoms::rel ||
359 0 : aName == nsGkAtoms::title ||
360 0 : aName == nsGkAtoms::media ||
361 0 : aName == nsGkAtoms::type) {
362 0 : UpdateStyleSheetInternal(nullptr, nullptr, true);
363 : }
364 0 : if ((aName == nsGkAtoms::as || aName == nsGkAtoms::type ||
365 0 : aName == nsGkAtoms::crossorigin || aName == nsGkAtoms::media) &&
366 0 : IsInComposedDoc()) {
367 0 : UpdatePreload(aName, aValue, aOldValue);
368 : }
369 : }
370 : }
371 :
372 0 : return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
373 0 : aOldValue, aNotify);
374 : }
375 :
376 : nsresult
377 0 : HTMLLinkElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
378 : {
379 0 : return GetEventTargetParentForAnchors(aVisitor);
380 : }
381 :
382 : nsresult
383 0 : HTMLLinkElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
384 : {
385 0 : return PostHandleEventForAnchors(aVisitor);
386 : }
387 :
388 : bool
389 0 : HTMLLinkElement::IsLink(nsIURI** aURI) const
390 : {
391 0 : return IsHTMLLink(aURI);
392 : }
393 :
394 : void
395 0 : HTMLLinkElement::GetLinkTarget(nsAString& aTarget)
396 : {
397 0 : GetAttr(kNameSpaceID_None, nsGkAtoms::target, aTarget);
398 0 : if (aTarget.IsEmpty()) {
399 0 : GetBaseTarget(aTarget);
400 : }
401 0 : }
402 :
403 : static const DOMTokenListSupportedToken sSupportedRelValues[] = {
404 : // Keep this in sync with ToLinkMask in nsStyleLinkElement.cpp.
405 : // "import" must come first because it's conditional.
406 : "prefetch",
407 : "dns-prefetch",
408 : "stylesheet",
409 : "next",
410 : "alternate",
411 : "preconnect",
412 : "icon",
413 : "search",
414 : "preload",
415 : nullptr
416 : };
417 :
418 : nsDOMTokenList*
419 0 : HTMLLinkElement::RelList()
420 : {
421 0 : if (!mRelList) {
422 0 : mRelList = new nsDOMTokenList(this, nsGkAtoms::rel, sSupportedRelValues);
423 : }
424 0 : return mRelList;
425 : }
426 :
427 : already_AddRefed<nsIURI>
428 0 : HTMLLinkElement::GetHrefURI() const
429 : {
430 0 : return GetHrefURIForAnchors();
431 : }
432 :
433 : already_AddRefed<nsIURI>
434 0 : HTMLLinkElement::GetStyleSheetURL(bool* aIsInline)
435 : {
436 0 : *aIsInline = false;
437 0 : nsAutoString href;
438 0 : GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
439 0 : if (href.IsEmpty()) {
440 0 : return nullptr;
441 : }
442 0 : nsCOMPtr<nsIURI> uri = Link::GetURI();
443 0 : return uri.forget();
444 : }
445 :
446 : void
447 0 : HTMLLinkElement::GetStyleSheetInfo(nsAString& aTitle,
448 : nsAString& aType,
449 : nsAString& aMedia,
450 : bool* aIsScoped,
451 : bool* aIsAlternate)
452 : {
453 0 : aTitle.Truncate();
454 0 : aType.Truncate();
455 0 : aMedia.Truncate();
456 0 : *aIsScoped = false;
457 0 : *aIsAlternate = false;
458 :
459 0 : nsAutoString rel;
460 0 : GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel);
461 0 : uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(rel);
462 : // Is it a stylesheet link?
463 0 : if (!(linkTypes & nsStyleLinkElement::eSTYLESHEET)) {
464 0 : return;
465 : }
466 :
467 0 : nsAutoString title;
468 0 : GetAttr(kNameSpaceID_None, nsGkAtoms::title, title);
469 0 : title.CompressWhitespace();
470 0 : aTitle.Assign(title);
471 :
472 : // If alternate, does it have title?
473 0 : if (linkTypes & nsStyleLinkElement::eALTERNATE) {
474 0 : if (aTitle.IsEmpty()) { // alternates must have title
475 0 : return;
476 : } else {
477 0 : *aIsAlternate = true;
478 : }
479 : }
480 :
481 0 : GetAttr(kNameSpaceID_None, nsGkAtoms::media, aMedia);
482 : // The HTML5 spec is formulated in terms of the CSSOM spec, which specifies
483 : // that media queries should be ASCII lowercased during serialization.
484 0 : nsContentUtils::ASCIIToLower(aMedia);
485 :
486 0 : nsAutoString mimeType;
487 0 : nsAutoString notUsed;
488 0 : GetAttr(kNameSpaceID_None, nsGkAtoms::type, aType);
489 0 : nsContentUtils::SplitMimeType(aType, mimeType, notUsed);
490 0 : if (!mimeType.IsEmpty() && !mimeType.LowerCaseEqualsLiteral("text/css")) {
491 0 : return;
492 : }
493 :
494 : // If we get here we assume that we're loading a css file, so set the
495 : // type to 'text/css'
496 0 : aType.AssignLiteral("text/css");
497 :
498 0 : return;
499 : }
500 :
501 : CORSMode
502 0 : HTMLLinkElement::GetCORSMode() const
503 : {
504 0 : return AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin));
505 : }
506 :
507 : EventStates
508 0 : HTMLLinkElement::IntrinsicState() const
509 : {
510 0 : return Link::LinkState() | nsGenericHTMLElement::IntrinsicState();
511 : }
512 :
513 : size_t
514 0 : HTMLLinkElement::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
515 : {
516 0 : return nsGenericHTMLElement::SizeOfExcludingThis(aMallocSizeOf) +
517 0 : Link::SizeOfExcludingThis(aMallocSizeOf);
518 : }
519 :
520 : JSObject*
521 0 : HTMLLinkElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
522 : {
523 0 : return HTMLLinkElementBinding::Wrap(aCx, this, aGivenProto);
524 : }
525 :
526 : void
527 0 : HTMLLinkElement::GetAs(nsAString& aResult)
528 : {
529 0 : GetEnumAttr(nsGkAtoms::as, EmptyCString().get(), aResult);
530 0 : }
531 :
532 : } // namespace dom
533 : } // namespace mozilla
|