1 | /* |
2 | * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
3 | * (C) 1999 Antti Koivisto (koivisto@kde.org) |
4 | * (C) 2001 Dirk Mueller (mueller@kde.org) |
5 | * Copyright (C) 2003-2017 Apple Inc. All rights reserved. |
6 | * Copyright (C) 2009 Rob Buis (rwlbuis@gmail.com) |
7 | * Copyright (C) 2011 Google Inc. All rights reserved. |
8 | * |
9 | * This library is free software; you can redistribute it and/or |
10 | * modify it under the terms of the GNU Library General Public |
11 | * License as published by the Free Software Foundation; either |
12 | * version 2 of the License, or (at your option) any later version. |
13 | * |
14 | * This library is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
17 | * Library General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU Library General Public License |
20 | * along with this library; see the file COPYING.LIB. If not, write to |
21 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
22 | * Boston, MA 02110-1301, USA. |
23 | */ |
24 | |
25 | #include "config.h" |
26 | #include "HTMLLinkElement.h" |
27 | |
28 | #include "Attribute.h" |
29 | #include "CachedCSSStyleSheet.h" |
30 | #include "CachedResource.h" |
31 | #include "CachedResourceLoader.h" |
32 | #include "CachedResourceRequest.h" |
33 | #include "ContentSecurityPolicy.h" |
34 | #include "CrossOriginAccessControl.h" |
35 | #include "DOMTokenList.h" |
36 | #include "Document.h" |
37 | #include "Event.h" |
38 | #include "EventNames.h" |
39 | #include "EventSender.h" |
40 | #include "Frame.h" |
41 | #include "FrameLoader.h" |
42 | #include "FrameLoaderClient.h" |
43 | #include "FrameTree.h" |
44 | #include "FrameView.h" |
45 | #include "HTMLAnchorElement.h" |
46 | #include "HTMLNames.h" |
47 | #include "HTMLParserIdioms.h" |
48 | #include "Logging.h" |
49 | #include "MediaList.h" |
50 | #include "MediaQueryEvaluator.h" |
51 | #include "MediaQueryParser.h" |
52 | #include "MouseEvent.h" |
53 | #include "RenderStyle.h" |
54 | #include "RuntimeEnabledFeatures.h" |
55 | #include "SecurityOrigin.h" |
56 | #include "Settings.h" |
57 | #include "StyleInheritedData.h" |
58 | #include "StyleResolveForDocument.h" |
59 | #include "StyleScope.h" |
60 | #include "StyleSheetContents.h" |
61 | #include "SubresourceIntegrity.h" |
62 | #include <wtf/IsoMallocInlines.h> |
63 | #include <wtf/Ref.h> |
64 | #include <wtf/SetForScope.h> |
65 | #include <wtf/StdLibExtras.h> |
66 | |
67 | namespace WebCore { |
68 | |
69 | WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLLinkElement); |
70 | |
71 | using namespace HTMLNames; |
72 | |
73 | static LinkEventSender& linkLoadEventSender() |
74 | { |
75 | static NeverDestroyed<LinkEventSender> sharedLoadEventSender(eventNames().loadEvent); |
76 | return sharedLoadEventSender; |
77 | } |
78 | |
79 | static LinkEventSender& linkErrorEventSender() |
80 | { |
81 | static NeverDestroyed<LinkEventSender> sharedErrorEventSender(eventNames().errorEvent); |
82 | return sharedErrorEventSender; |
83 | } |
84 | |
85 | inline HTMLLinkElement::HTMLLinkElement(const QualifiedName& tagName, Document& document, bool createdByParser) |
86 | : HTMLElement(tagName, document) |
87 | , m_linkLoader(*this) |
88 | , m_disabledState(Unset) |
89 | , m_loading(false) |
90 | , m_createdByParser(createdByParser) |
91 | , m_firedLoad(false) |
92 | , m_loadedResource(false) |
93 | , m_pendingSheetType(Unknown) |
94 | { |
95 | ASSERT(hasTagName(linkTag)); |
96 | } |
97 | |
98 | Ref<HTMLLinkElement> HTMLLinkElement::create(const QualifiedName& tagName, Document& document, bool createdByParser) |
99 | { |
100 | return adoptRef(*new HTMLLinkElement(tagName, document, createdByParser)); |
101 | } |
102 | |
103 | HTMLLinkElement::~HTMLLinkElement() |
104 | { |
105 | if (m_sheet) |
106 | m_sheet->clearOwnerNode(); |
107 | |
108 | if (m_cachedSheet) |
109 | m_cachedSheet->removeClient(*this); |
110 | |
111 | if (m_styleScope) |
112 | m_styleScope->removeStyleSheetCandidateNode(*this); |
113 | |
114 | linkLoadEventSender().cancelEvent(*this); |
115 | linkErrorEventSender().cancelEvent(*this); |
116 | } |
117 | |
118 | void HTMLLinkElement::setDisabledState(bool disabled) |
119 | { |
120 | DisabledState oldDisabledState = m_disabledState; |
121 | m_disabledState = disabled ? Disabled : EnabledViaScript; |
122 | if (oldDisabledState == m_disabledState) |
123 | return; |
124 | |
125 | ASSERT(isConnected() || !styleSheetIsLoading()); |
126 | if (!isConnected()) |
127 | return; |
128 | |
129 | // If we change the disabled state while the sheet is still loading, then we have to |
130 | // perform three checks: |
131 | if (styleSheetIsLoading()) { |
132 | // Check #1: The sheet becomes disabled while loading. |
133 | if (m_disabledState == Disabled) |
134 | removePendingSheet(); |
135 | |
136 | // Check #2: An alternate sheet becomes enabled while it is still loading. |
137 | if (m_relAttribute.isAlternate && m_disabledState == EnabledViaScript) |
138 | addPendingSheet(ActiveSheet); |
139 | |
140 | // Check #3: A main sheet becomes enabled while it was still loading and |
141 | // after it was disabled via script. It takes really terrible code to make this |
142 | // happen (a double toggle for no reason essentially). This happens on |
143 | // virtualplastic.net, which manages to do about 12 enable/disables on only 3 |
144 | // sheets. :) |
145 | if (!m_relAttribute.isAlternate && m_disabledState == EnabledViaScript && oldDisabledState == Disabled) |
146 | addPendingSheet(ActiveSheet); |
147 | |
148 | // If the sheet is already loading just bail. |
149 | return; |
150 | } |
151 | |
152 | // Load the sheet, since it's never been loaded before. |
153 | if (!m_sheet && m_disabledState == EnabledViaScript) |
154 | process(); |
155 | else { |
156 | ASSERT(m_styleScope); |
157 | m_styleScope->didChangeActiveStyleSheetCandidates(); |
158 | } |
159 | } |
160 | |
161 | void HTMLLinkElement::parseAttribute(const QualifiedName& name, const AtomicString& value) |
162 | { |
163 | if (name == relAttr) { |
164 | m_relAttribute = LinkRelAttribute(document(), value); |
165 | if (m_relList) |
166 | m_relList->associatedAttributeValueChanged(value); |
167 | process(); |
168 | return; |
169 | } |
170 | if (name == hrefAttr) { |
171 | bool wasLink = isLink(); |
172 | setIsLink(!value.isNull() && !shouldProhibitLinks(this)); |
173 | if (wasLink != isLink()) |
174 | invalidateStyleForSubtree(); |
175 | process(); |
176 | return; |
177 | } |
178 | if (name == typeAttr) { |
179 | m_type = value; |
180 | process(); |
181 | return; |
182 | } |
183 | if (name == sizesAttr) { |
184 | if (m_sizes) |
185 | m_sizes->associatedAttributeValueChanged(value); |
186 | process(); |
187 | return; |
188 | } |
189 | if (name == mediaAttr) { |
190 | m_media = value.string().convertToASCIILowercase(); |
191 | process(); |
192 | if (m_sheet && !isDisabled()) |
193 | m_styleScope->didChangeActiveStyleSheetCandidates(); |
194 | return; |
195 | } |
196 | if (name == disabledAttr) { |
197 | setDisabledState(!value.isNull()); |
198 | return; |
199 | } |
200 | if (name == titleAttr) { |
201 | if (m_sheet && !isInShadowTree()) |
202 | m_sheet->setTitle(value); |
203 | return; |
204 | } |
205 | HTMLElement::parseAttribute(name, value); |
206 | } |
207 | |
208 | bool HTMLLinkElement::shouldLoadLink() |
209 | { |
210 | Ref<Document> originalDocument = document(); |
211 | if (!dispatchBeforeLoadEvent(getNonEmptyURLAttribute(hrefAttr))) |
212 | return false; |
213 | // A beforeload handler might have removed us from the document or changed the document. |
214 | if (!isConnected() || &document() != originalDocument.ptr()) |
215 | return false; |
216 | return true; |
217 | } |
218 | |
219 | void HTMLLinkElement::setCrossOrigin(const AtomicString& value) |
220 | { |
221 | setAttributeWithoutSynchronization(crossoriginAttr, value); |
222 | } |
223 | |
224 | String HTMLLinkElement::crossOrigin() const |
225 | { |
226 | return parseCORSSettingsAttribute(attributeWithoutSynchronization(crossoriginAttr)); |
227 | } |
228 | |
229 | void HTMLLinkElement::setAs(const AtomicString& value) |
230 | { |
231 | setAttributeWithoutSynchronization(asAttr, value); |
232 | } |
233 | |
234 | String HTMLLinkElement::as() const |
235 | { |
236 | String as = attributeWithoutSynchronization(asAttr); |
237 | if (equalLettersIgnoringASCIICase(as, "fetch" ) |
238 | || equalLettersIgnoringASCIICase(as, "image" ) |
239 | || equalLettersIgnoringASCIICase(as, "script" ) |
240 | || equalLettersIgnoringASCIICase(as, "style" ) |
241 | || (RuntimeEnabledFeatures::sharedFeatures().mediaPreloadingEnabled() |
242 | && (equalLettersIgnoringASCIICase(as, "video" ) |
243 | || equalLettersIgnoringASCIICase(as, "audio" ))) |
244 | #if ENABLE(VIDEO_TRACK) |
245 | || equalLettersIgnoringASCIICase(as, "track" ) |
246 | #endif |
247 | || equalLettersIgnoringASCIICase(as, "font" )) |
248 | return as.convertToASCIILowercase(); |
249 | return String(); |
250 | } |
251 | |
252 | void HTMLLinkElement::process() |
253 | { |
254 | if (!isConnected()) { |
255 | ASSERT(!m_sheet); |
256 | return; |
257 | } |
258 | |
259 | // Prevent recursive loading of link. |
260 | if (m_isHandlingBeforeLoad) |
261 | return; |
262 | |
263 | URL url = getNonEmptyURLAttribute(hrefAttr); |
264 | |
265 | if (!m_linkLoader.loadLink(m_relAttribute, url, attributeWithoutSynchronization(asAttr), attributeWithoutSynchronization(mediaAttr), attributeWithoutSynchronization(typeAttr), attributeWithoutSynchronization(crossoriginAttr), attributeWithoutSynchronization(imagesrcsetAttr), attributeWithoutSynchronization(imagesizesAttr), document())) |
266 | return; |
267 | |
268 | bool treatAsStyleSheet = m_relAttribute.isStyleSheet |
269 | || (document().settings().treatsAnyTextCSSLinkAsStylesheet() && m_type.containsIgnoringASCIICase("text/css" )); |
270 | |
271 | if (m_disabledState != Disabled && treatAsStyleSheet && document().frame() && url.isValid()) { |
272 | String charset = attributeWithoutSynchronization(charsetAttr); |
273 | if (charset.isEmpty()) |
274 | charset = document().charset(); |
275 | |
276 | if (m_cachedSheet) { |
277 | removePendingSheet(); |
278 | m_cachedSheet->removeClient(*this); |
279 | m_cachedSheet = nullptr; |
280 | } |
281 | |
282 | { |
283 | SetForScope<bool> change(m_isHandlingBeforeLoad, true); |
284 | if (!shouldLoadLink()) |
285 | return; |
286 | } |
287 | |
288 | m_loading = true; |
289 | |
290 | bool mediaQueryMatches = true; |
291 | if (!m_media.isEmpty()) { |
292 | Optional<RenderStyle> documentStyle; |
293 | if (document().hasLivingRenderTree()) |
294 | documentStyle = Style::resolveForDocument(document()); |
295 | auto media = MediaQuerySet::create(m_media, MediaQueryParserContext(document())); |
296 | LOG(MediaQueries, "HTMLLinkElement::process evaluating queries" ); |
297 | mediaQueryMatches = MediaQueryEvaluator { document().frame()->view()->mediaType(), document(), documentStyle ? &*documentStyle : nullptr }.evaluate(media.get()); |
298 | } |
299 | |
300 | // Don't hold up render tree construction and script execution on stylesheets |
301 | // that are not needed for the rendering at the moment. |
302 | bool isActive = mediaQueryMatches && !isAlternate(); |
303 | addPendingSheet(isActive ? ActiveSheet : InactiveSheet); |
304 | |
305 | // Load stylesheets that are not needed for the rendering immediately with low priority. |
306 | Optional<ResourceLoadPriority> priority; |
307 | if (!isActive) |
308 | priority = ResourceLoadPriority::VeryLow; |
309 | |
310 | if (document().settings().subresourceIntegrityEnabled()) |
311 | m_integrityMetadataForPendingSheetRequest = attributeWithoutSynchronization(HTMLNames::integrityAttr); |
312 | |
313 | ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions(); |
314 | options.sameOriginDataURLFlag = SameOriginDataURLFlag::Set; |
315 | if (document().contentSecurityPolicy()->allowStyleWithNonce(attributeWithoutSynchronization(HTMLNames::nonceAttr))) |
316 | options.contentSecurityPolicyImposition = ContentSecurityPolicyImposition::SkipPolicyCheck; |
317 | options.integrity = m_integrityMetadataForPendingSheetRequest; |
318 | |
319 | auto request = createPotentialAccessControlRequest(WTFMove(url), document(), crossOrigin(), WTFMove(options)); |
320 | request.setPriority(WTFMove(priority)); |
321 | request.setCharset(WTFMove(charset)); |
322 | request.setInitiator(*this); |
323 | |
324 | ASSERT_WITH_SECURITY_IMPLICATION(!m_cachedSheet); |
325 | m_cachedSheet = document().cachedResourceLoader().requestCSSStyleSheet(WTFMove(request)).value_or(nullptr); |
326 | |
327 | if (m_cachedSheet) |
328 | m_cachedSheet->addClient(*this); |
329 | else { |
330 | // The request may have been denied if (for example) the stylesheet is local and the document is remote. |
331 | m_loading = false; |
332 | sheetLoaded(); |
333 | notifyLoadedSheetAndAllCriticalSubresources(false); |
334 | } |
335 | } else if (m_sheet) { |
336 | // we no longer contain a stylesheet, e.g. perhaps rel or type was changed |
337 | clearSheet(); |
338 | m_styleScope->didChangeActiveStyleSheetCandidates(); |
339 | } |
340 | } |
341 | |
342 | void HTMLLinkElement::clearSheet() |
343 | { |
344 | ASSERT(m_sheet); |
345 | ASSERT(m_sheet->ownerNode() == this); |
346 | m_sheet->clearOwnerNode(); |
347 | m_sheet = nullptr; |
348 | } |
349 | |
350 | Node::InsertedIntoAncestorResult HTMLLinkElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree) |
351 | { |
352 | HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
353 | if (!insertionType.connectedToDocument) |
354 | return InsertedIntoAncestorResult::Done; |
355 | |
356 | m_styleScope = &Style::Scope::forNode(*this); |
357 | m_styleScope->addStyleSheetCandidateNode(*this, m_createdByParser); |
358 | |
359 | return InsertedIntoAncestorResult::NeedsPostInsertionCallback; |
360 | } |
361 | |
362 | void HTMLLinkElement::didFinishInsertingNode() |
363 | { |
364 | process(); |
365 | } |
366 | |
367 | void HTMLLinkElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree) |
368 | { |
369 | HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree); |
370 | if (!removalType.disconnectedFromDocument) |
371 | return; |
372 | |
373 | m_linkLoader.cancelLoad(); |
374 | |
375 | bool wasLoading = styleSheetIsLoading(); |
376 | |
377 | if (m_sheet) |
378 | clearSheet(); |
379 | |
380 | if (wasLoading) |
381 | removePendingSheet(); |
382 | |
383 | if (m_styleScope) { |
384 | m_styleScope->removeStyleSheetCandidateNode(*this); |
385 | m_styleScope = nullptr; |
386 | } |
387 | } |
388 | |
389 | void HTMLLinkElement::finishParsingChildren() |
390 | { |
391 | m_createdByParser = false; |
392 | HTMLElement::finishParsingChildren(); |
393 | } |
394 | |
395 | void HTMLLinkElement::initializeStyleSheet(Ref<StyleSheetContents>&& styleSheet, const CachedCSSStyleSheet& cachedStyleSheet, MediaQueryParserContext context) |
396 | { |
397 | // FIXME: originClean should be turned to false except if fetch mode is CORS. |
398 | Optional<bool> originClean; |
399 | if (cachedStyleSheet.options().mode == FetchOptions::Mode::Cors) |
400 | originClean = cachedStyleSheet.isCORSSameOrigin(); |
401 | |
402 | m_sheet = CSSStyleSheet::create(WTFMove(styleSheet), *this, originClean); |
403 | m_sheet->setMediaQueries(MediaQuerySet::create(m_media, context)); |
404 | if (!isInShadowTree()) |
405 | m_sheet->setTitle(title()); |
406 | |
407 | if (!m_sheet->canAccessRules()) |
408 | m_sheet->contents().setAsOpaque(); |
409 | } |
410 | |
411 | void HTMLLinkElement::setCSSStyleSheet(const String& href, const URL& baseURL, const String& charset, const CachedCSSStyleSheet* cachedStyleSheet) |
412 | { |
413 | if (!isConnected()) { |
414 | ASSERT(!m_sheet); |
415 | return; |
416 | } |
417 | auto frame = makeRefPtr(document().frame()); |
418 | if (!frame) |
419 | return; |
420 | |
421 | // Completing the sheet load may cause scripts to execute. |
422 | Ref<HTMLLinkElement> protectedThis(*this); |
423 | |
424 | if (!cachedStyleSheet->errorOccurred() && !matchIntegrityMetadata(*cachedStyleSheet, m_integrityMetadataForPendingSheetRequest)) { |
425 | document().addConsoleMessage(MessageSource::Security, MessageLevel::Error, makeString("Cannot load stylesheet " , cachedStyleSheet->url().stringCenterEllipsizedToLength(), ". Failed integrity metadata check." )); |
426 | |
427 | m_loading = false; |
428 | sheetLoaded(); |
429 | notifyLoadedSheetAndAllCriticalSubresources(true); |
430 | return; |
431 | } |
432 | |
433 | CSSParserContext parserContext(document(), baseURL, charset); |
434 | auto cachePolicy = frame->loader().subresourceCachePolicy(baseURL); |
435 | |
436 | if (auto restoredSheet = const_cast<CachedCSSStyleSheet*>(cachedStyleSheet)->restoreParsedStyleSheet(parserContext, cachePolicy, frame->loader())) { |
437 | ASSERT(restoredSheet->isCacheable()); |
438 | ASSERT(!restoredSheet->isLoading()); |
439 | initializeStyleSheet(restoredSheet.releaseNonNull(), *cachedStyleSheet, MediaQueryParserContext(document())); |
440 | |
441 | m_loading = false; |
442 | sheetLoaded(); |
443 | notifyLoadedSheetAndAllCriticalSubresources(false); |
444 | return; |
445 | } |
446 | |
447 | auto styleSheet = StyleSheetContents::create(href, parserContext); |
448 | initializeStyleSheet(styleSheet.copyRef(), *cachedStyleSheet, MediaQueryParserContext(document())); |
449 | |
450 | // FIXME: Set the visibility option based on m_sheet being clean or not. |
451 | // Best approach might be to set it on the style sheet content itself or its context parser otherwise. |
452 | styleSheet.get().parseAuthorStyleSheet(cachedStyleSheet, &document().securityOrigin()); |
453 | |
454 | m_loading = false; |
455 | styleSheet.get().notifyLoadedSheet(cachedStyleSheet); |
456 | styleSheet.get().checkLoaded(); |
457 | |
458 | if (styleSheet.get().isCacheable()) |
459 | const_cast<CachedCSSStyleSheet*>(cachedStyleSheet)->saveParsedStyleSheet(WTFMove(styleSheet)); |
460 | } |
461 | |
462 | bool HTMLLinkElement::styleSheetIsLoading() const |
463 | { |
464 | if (m_loading) |
465 | return true; |
466 | if (!m_sheet) |
467 | return false; |
468 | return m_sheet->contents().isLoading(); |
469 | } |
470 | |
471 | DOMTokenList& HTMLLinkElement::sizes() |
472 | { |
473 | if (!m_sizes) |
474 | m_sizes = std::make_unique<DOMTokenList>(*this, sizesAttr); |
475 | return *m_sizes; |
476 | } |
477 | |
478 | void HTMLLinkElement::linkLoaded() |
479 | { |
480 | m_loadedResource = true; |
481 | linkLoadEventSender().dispatchEventSoon(*this); |
482 | } |
483 | |
484 | void HTMLLinkElement::linkLoadingErrored() |
485 | { |
486 | linkErrorEventSender().dispatchEventSoon(*this); |
487 | } |
488 | |
489 | bool HTMLLinkElement::sheetLoaded() |
490 | { |
491 | if (!styleSheetIsLoading()) { |
492 | removePendingSheet(); |
493 | return true; |
494 | } |
495 | return false; |
496 | } |
497 | |
498 | void HTMLLinkElement::dispatchPendingLoadEvents() |
499 | { |
500 | linkLoadEventSender().dispatchPendingEvents(); |
501 | } |
502 | |
503 | void HTMLLinkElement::dispatchPendingEvent(LinkEventSender* eventSender) |
504 | { |
505 | ASSERT_UNUSED(eventSender, eventSender == &linkLoadEventSender() || eventSender == &linkErrorEventSender()); |
506 | if (m_loadedResource) |
507 | dispatchEvent(Event::create(eventNames().loadEvent, Event::CanBubble::No, Event::IsCancelable::No)); |
508 | else |
509 | dispatchEvent(Event::create(eventNames().errorEvent, Event::CanBubble::No, Event::IsCancelable::No)); |
510 | } |
511 | |
512 | DOMTokenList& HTMLLinkElement::relList() |
513 | { |
514 | if (!m_relList) |
515 | m_relList = std::make_unique<DOMTokenList>(*this, HTMLNames::relAttr, [](Document& document, StringView token) { |
516 | return LinkRelAttribute::isSupported(document, token); |
517 | }); |
518 | return *m_relList; |
519 | } |
520 | |
521 | void HTMLLinkElement::notifyLoadedSheetAndAllCriticalSubresources(bool errorOccurred) |
522 | { |
523 | if (m_firedLoad) |
524 | return; |
525 | m_loadedResource = !errorOccurred; |
526 | linkLoadEventSender().dispatchEventSoon(*this); |
527 | m_firedLoad = true; |
528 | } |
529 | |
530 | void HTMLLinkElement::startLoadingDynamicSheet() |
531 | { |
532 | // We don't support multiple active sheets. |
533 | ASSERT(m_pendingSheetType < ActiveSheet); |
534 | addPendingSheet(ActiveSheet); |
535 | } |
536 | |
537 | bool HTMLLinkElement::isURLAttribute(const Attribute& attribute) const |
538 | { |
539 | return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute); |
540 | } |
541 | |
542 | void HTMLLinkElement::defaultEventHandler(Event& event) |
543 | { |
544 | if (MouseEvent::canTriggerActivationBehavior(event)) { |
545 | handleClick(event); |
546 | return; |
547 | } |
548 | HTMLElement::defaultEventHandler(event); |
549 | } |
550 | |
551 | void HTMLLinkElement::handleClick(Event& event) |
552 | { |
553 | event.setDefaultHandled(); |
554 | URL url = href(); |
555 | if (url.isNull()) |
556 | return; |
557 | RefPtr<Frame> frame = document().frame(); |
558 | if (!frame) |
559 | return; |
560 | frame->loader().urlSelected(url, target(), &event, LockHistory::No, LockBackForwardList::No, MaybeSendReferrer, document().shouldOpenExternalURLsPolicyToPropagate()); |
561 | } |
562 | |
563 | URL HTMLLinkElement::href() const |
564 | { |
565 | return document().completeURL(attributeWithoutSynchronization(hrefAttr)); |
566 | } |
567 | |
568 | const AtomicString& HTMLLinkElement::rel() const |
569 | { |
570 | return attributeWithoutSynchronization(relAttr); |
571 | } |
572 | |
573 | String HTMLLinkElement::target() const |
574 | { |
575 | return attributeWithoutSynchronization(targetAttr); |
576 | } |
577 | |
578 | const AtomicString& HTMLLinkElement::type() const |
579 | { |
580 | return attributeWithoutSynchronization(typeAttr); |
581 | } |
582 | |
583 | Optional<LinkIconType> HTMLLinkElement::iconType() const |
584 | { |
585 | return m_relAttribute.iconType; |
586 | } |
587 | |
588 | void HTMLLinkElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const |
589 | { |
590 | HTMLElement::addSubresourceAttributeURLs(urls); |
591 | |
592 | // Favicons are handled by a special case in LegacyWebArchive::create() |
593 | if (m_relAttribute.iconType) |
594 | return; |
595 | |
596 | if (!m_relAttribute.isStyleSheet) |
597 | return; |
598 | |
599 | // Append the URL of this link element. |
600 | addSubresourceURL(urls, href()); |
601 | |
602 | if (auto styleSheet = makeRefPtr(this->sheet())) { |
603 | styleSheet->contents().traverseSubresources([&] (auto& resource) { |
604 | urls.add(resource.url()); |
605 | return false; |
606 | }); |
607 | } |
608 | } |
609 | |
610 | void HTMLLinkElement::addPendingSheet(PendingSheetType type) |
611 | { |
612 | if (type <= m_pendingSheetType) |
613 | return; |
614 | m_pendingSheetType = type; |
615 | |
616 | if (m_pendingSheetType == InactiveSheet) |
617 | return; |
618 | ASSERT(m_styleScope); |
619 | m_styleScope->addPendingSheet(*this); |
620 | } |
621 | |
622 | void HTMLLinkElement::removePendingSheet() |
623 | { |
624 | PendingSheetType type = m_pendingSheetType; |
625 | m_pendingSheetType = Unknown; |
626 | |
627 | if (type == Unknown) |
628 | return; |
629 | |
630 | ASSERT(m_styleScope); |
631 | if (type == InactiveSheet) { |
632 | // Document just needs to know about the sheet for exposure through document.styleSheets |
633 | m_styleScope->didChangeActiveStyleSheetCandidates(); |
634 | return; |
635 | } |
636 | |
637 | m_styleScope->removePendingSheet(*this); |
638 | } |
639 | |
640 | } // namespace WebCore |
641 | |