HTMLLinkElement.cpp 15.2 KB
Newer Older
darin@apple.com's avatar
darin@apple.com committed
1
/*
2 3 4
 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
 *           (C) 2001 Dirk Mueller (mueller@kde.org)
5
 * Copyright (C) 2003, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
levin@chromium.org's avatar
levin@chromium.org committed
6
 * Copyright (C) 2009 Rob Buis (rwlbuis@gmail.com)
7
 * Copyright (C) 2011 Google Inc. All rights reserved.
8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
ddkilzer's avatar
ddkilzer committed
21 22
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
23
 */
darin@apple.com's avatar
darin@apple.com committed
24

25 26 27
#include "config.h"
#include "HTMLLinkElement.h"

28
#include "Attribute.h"
29
#include "CachedCSSStyleSheet.h"
30
#include "CachedResource.h"
31
#include "CachedResourceLoader.h"
32
#include "CachedResourceRequest.h"
33
#include "Document.h"
34
#include "DocumentStyleSheetCollection.h"
35
#include "Event.h"
36
#include "EventSender.h"
37
#include "Frame.h"
darin's avatar
darin committed
38
#include "FrameLoader.h"
collinj@webkit.org's avatar
 
collinj@webkit.org committed
39
#include "FrameLoaderClient.h"
40
#include "FrameTree.h"
41
#include "FrameView.h"
42
#include "HTMLNames.h"
43
#include "HTMLParserIdioms.h"
44
#include "MediaList.h"
45
#include "MediaQueryEvaluator.h"
beidson@apple.com's avatar
beidson@apple.com committed
46
#include "Page.h"
47
#include "ScriptEventListener.h"
48
#include "SecurityOrigin.h"
beidson@apple.com's avatar
beidson@apple.com committed
49
#include "Settings.h"
50
#include "StyleResolver.h"
51
#include "StyleSheetContents.h"
aroben@apple.com's avatar
aroben@apple.com committed
52
#include <wtf/StdLibExtras.h>
53 54 55 56 57

namespace WebCore {

using namespace HTMLNames;

58
static LinkEventSender& linkLoadEventSender()
59 60 61 62 63
{
    DEFINE_STATIC_LOCAL(LinkEventSender, sharedLoadEventSender, (eventNames().loadEvent));
    return sharedLoadEventSender;
}

64 65
inline HTMLLinkElement::HTMLLinkElement(const QualifiedName& tagName, Document* document, bool createdByParser)
    : HTMLElement(tagName, document)
66
    , m_linkLoader(this)
67
    , m_sizes(DOMSettableTokenList::create())
68
    , m_disabledState(Unset)
69
    , m_loading(false)
70
    , m_createdByParser(createdByParser)
71
    , m_isInShadowTree(false)
72 73
    , m_firedLoad(false)
    , m_loadedSheet(false)
74
    , m_pendingSheetType(None)
75
{
76
    ASSERT(hasTagName(linkTag));
77 78
}

79 80
PassRefPtr<HTMLLinkElement> HTMLLinkElement::create(const QualifiedName& tagName, Document* document, bool createdByParser)
{
81
    return adoptRef(new HTMLLinkElement(tagName, document, createdByParser));
82 83
}

84 85
HTMLLinkElement::~HTMLLinkElement()
{
ap@apple.com's avatar
ap@apple.com committed
86 87 88
    if (m_sheet)
        m_sheet->clearOwnerNode();

89
    if (m_cachedSheet)
90
        m_cachedSheet->removeClient(this);
91 92

    if (inDocument())
93
        document()->styleSheetCollection()->removeStyleSheetCandidateNode(this);
94

95
    linkLoadEventSender().cancelEvent(this);
96 97
}

98
void HTMLLinkElement::setDisabledState(bool disabled)
99
{
100 101 102 103 104
    DisabledState oldDisabledState = m_disabledState;
    m_disabledState = disabled ? Disabled : EnabledViaScript;
    if (oldDisabledState != m_disabledState) {
        // If we change the disabled state while the sheet is still loading, then we have to
        // perform three checks:
105
        if (styleSheetIsLoading()) {
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
            // Check #1: The sheet becomes disabled while loading.
            if (m_disabledState == Disabled)
                removePendingSheet();

            // Check #2: An alternate sheet becomes enabled while it is still loading.
            if (m_relAttribute.m_isAlternate && m_disabledState == EnabledViaScript)
                addPendingSheet(Blocking);

            // Check #3: A main sheet becomes enabled while it was still loading and
            // after it was disabled via script. It takes really terrible code to make this
            // happen (a double toggle for no reason essentially). This happens on
            // virtualplastic.net, which manages to do about 12 enable/disables on only 3
            // sheets. :)
            if (!m_relAttribute.m_isAlternate && m_disabledState == EnabledViaScript && oldDisabledState == Disabled)
                addPendingSheet(Blocking);

            // If the sheet is already loading just bail.
            return;
        }
125

126 127 128 129
        // Load the sheet, since it's never been loaded before.
        if (!m_sheet && m_disabledState == EnabledViaScript)
            process();
        else
130
            document()->styleResolverChanged(DeferRecalcStyle); // Update the style selector.
131 132 133
    }
}

134
void HTMLLinkElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
135
{
136 137
    if (name == relAttr) {
        m_relAttribute = LinkRelAttribute(value);
138
        process();
139
    } else if (name == hrefAttr) {
140
        process();
141 142
    } else if (name == typeAttr) {
        m_type = value;
143
        process();
144 145
    } else if (name == sizesAttr) {
        setSizes(value);
146
        process();
147 148
    } else if (name == mediaAttr) {
        m_media = value.string().lower();
149
        process();
150 151 152 153
    } else if (name == disabledAttr)
        setDisabledState(!value.isNull());
    else if (name == onbeforeloadAttr)
        setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, name, value));
154
    else {
155 156 157
        if (name == titleAttr && m_sheet)
            m_sheet->setTitle(value);
        HTMLElement::parseAttribute(name, value);
rwlbuis's avatar
rwlbuis committed
158
    }
159 160
}

161
bool HTMLLinkElement::shouldLoadLink()
162 163
{
    RefPtr<Document> originalDocument = document();
164
    if (!dispatchBeforeLoadEvent(getNonEmptyURLAttribute(hrefAttr)))
165 166 167 168 169 170 171
        return false;
    // A beforeload handler might have removed us from the document or changed the document.
    if (!inDocument() || document() != originalDocument)
        return false;
    return true;
}

172 173
void HTMLLinkElement::process()
{
174
    if (!inDocument() || m_isInShadowTree) {
ap@apple.com's avatar
ap@apple.com committed
175
        ASSERT(!m_sheet);
176
        return;
ap@apple.com's avatar
ap@apple.com committed
177
    }
178 179

    String type = m_type.lower();
180
    KURL url = getNonEmptyURLAttribute(hrefAttr);
181

182
    if (!m_linkLoader.loadLink(m_relAttribute, type, m_sizes->toString(), url, document()))
183
        return;
184

mitz@apple.com's avatar
mitz@apple.com committed
185
    bool acceptIfTypeContainsTextCSS = document()->page() && document()->page()->settings() && document()->page()->settings()->treatsAnyTextCSSLinkAsStylesheet();
186

187
    if (m_disabledState != Disabled && (m_relAttribute.m_isStyleSheet || (acceptIfTypeContainsTextCSS && type.contains("text/css")))
188
        && document()->frame() && url.isValid()) {
189
        
190
        String charset = getAttribute(charsetAttr);
191
        if (charset.isEmpty() && document()->frame())
192
            charset = document()->charset();
193
        
levin@chromium.org's avatar
levin@chromium.org committed
194
        if (m_cachedSheet) {
195
            removePendingSheet();
levin@chromium.org's avatar
levin@chromium.org committed
196
            m_cachedSheet->removeClient(this);
197
            m_cachedSheet = 0;
levin@chromium.org's avatar
levin@chromium.org committed
198
        }
199

200
        if (!shouldLoadLink())
201 202
            return;

levin@chromium.org's avatar
levin@chromium.org committed
203
        m_loading = true;
204

205 206
        bool mediaQueryMatches = true;
        if (!m_media.isEmpty()) {
207
            RefPtr<RenderStyle> documentStyle = StyleResolver::styleForDocument(document());
208
            RefPtr<MediaQuerySet> media = MediaQuerySet::createAllowingDescriptionSyntax(m_media);
209 210 211 212 213 214 215 216 217 218 219
            MediaQueryEvaluator evaluator(document()->frame()->view()->mediaType(), document()->frame(), documentStyle.get());
            mediaQueryMatches = evaluator.eval(media.get());
        }

        // Don't hold up render tree construction and script execution on stylesheets
        // that are not needed for the rendering at the moment.
        bool blocking = mediaQueryMatches && !isAlternate();
        addPendingSheet(blocking ? Blocking : NonBlocking);

        // Load stylesheets that are not needed for the rendering immediately with low priority.
        ResourceLoadPriority priority = blocking ? ResourceLoadPriorityUnresolved : ResourceLoadPriorityVeryLow;
220
        CachedResourceRequest request(ResourceRequest(document()->completeURL(url)), charset, priority);
221
        request.setInitiator(this);
222
        m_cachedSheet = document()->cachedResourceLoader()->requestCSSStyleSheet(request);
223
        
levin@chromium.org's avatar
levin@chromium.org committed
224 225
        if (m_cachedSheet)
            m_cachedSheet->addClient(this);
226 227
        else {
            // The request may have been denied if (for example) the stylesheet is local and the document is remote.
levin@chromium.org's avatar
levin@chromium.org committed
228
            m_loading = false;
229
            removePendingSheet();
230 231 232
        }
    } else if (m_sheet) {
        // we no longer contain a stylesheet, e.g. perhaps rel or type was changed
233
        clearSheet();
234
        document()->styleResolverChanged(DeferRecalcStyle);
235 236 237
    }
}

238 239 240 241 242 243 244 245
void HTMLLinkElement::clearSheet()
{
    ASSERT(m_sheet);
    ASSERT(m_sheet->ownerNode() == this);
    m_sheet->clearOwnerNode();
    m_sheet = 0;
}

246
Node::InsertionNotificationRequest HTMLLinkElement::insertedInto(ContainerNode* insertionPoint)
247
{
248 249 250
    HTMLElement::insertedInto(insertionPoint);
    if (!insertionPoint->inDocument())
        return InsertionDone;
251 252 253

    m_isInShadowTree = isInShadowTree();
    if (m_isInShadowTree)
254
        return InsertionDone;
255

256
    document()->styleSheetCollection()->addStyleSheetCandidateNode(this, m_createdByParser);
257

258
    process();
259
    return InsertionDone;
260 261
}

262
void HTMLLinkElement::removedFrom(ContainerNode* insertionPoint)
263
{
264 265 266
    HTMLElement::removedFrom(insertionPoint);
    if (!insertionPoint->inDocument())
        return;
267

268 269
    m_linkLoader.released();

270 271 272 273
    if (m_isInShadowTree) {
        ASSERT(!m_sheet);
        return;
    }
274
    document()->styleSheetCollection()->removeStyleSheetCandidateNode(this);
mitz@apple.com's avatar
mitz@apple.com committed
275

276 277
    if (m_sheet)
        clearSheet();
ap@apple.com's avatar
ap@apple.com committed
278

279
    if (styleSheetIsLoading())
280
        removePendingSheet(RemovePendingSheetNotifyLater);
281

mitz@apple.com's avatar
mitz@apple.com committed
282
    if (document()->renderer())
283
        document()->styleResolverChanged(DeferRecalcStyle);
284 285 286 287 288 289
}

void HTMLLinkElement::finishParsingChildren()
{
    m_createdByParser = false;
    HTMLElement::finishParsingChildren();
290 291
}

292
void HTMLLinkElement::setCSSStyleSheet(const String& href, const KURL& baseURL, const String& charset, const CachedCSSStyleSheet* cachedStyleSheet)
293
{
ap@apple.com's avatar
ap@apple.com committed
294 295 296 297
    if (!inDocument()) {
        ASSERT(!m_sheet);
        return;
    }
298 299
    // Completing the sheet load may cause scripts to execute.
    RefPtr<Node> protector(this);
ap@apple.com's avatar
ap@apple.com committed
300

301
    CSSParserContext parserContext(document(), baseURL, charset);
antti@apple.com's avatar
antti@apple.com committed
302

303
    if (RefPtr<StyleSheetContents> restoredSheet = const_cast<CachedCSSStyleSheet*>(cachedStyleSheet)->restoreParsedStyleSheet(parserContext)) {
antti@apple.com's avatar
antti@apple.com committed
304 305 306 307
        ASSERT(restoredSheet->isCacheable());
        ASSERT(!restoredSheet->isLoading());

        m_sheet = CSSStyleSheet::create(restoredSheet, this);
308 309 310
        m_sheet->setMediaQueries(MediaQuerySet::createAllowingDescriptionSyntax(m_media));
        m_sheet->setTitle(title());

antti@apple.com's avatar
antti@apple.com committed
311 312 313 314 315 316
        m_loading = false;
        sheetLoaded();
        notifyLoadedSheetAndAllCriticalSubresources(false);
        return;
    }

317
    RefPtr<StyleSheetContents> styleSheet = StyleSheetContents::create(href, parserContext);
318

319
    m_sheet = CSSStyleSheet::create(styleSheet, this);
320 321
    m_sheet->setMediaQueries(MediaQuerySet::createAllowingDescriptionSyntax(m_media));
    m_sheet->setTitle(title());
beidson@apple.com's avatar
beidson@apple.com committed
322

323
    styleSheet->parseAuthorStyleSheet(cachedStyleSheet, document()->securityOrigin());
324 325

    m_loading = false;
326 327
    styleSheet->notifyLoadedSheet(cachedStyleSheet);
    styleSheet->checkLoaded();
antti@apple.com's avatar
antti@apple.com committed
328 329 330

    if (styleSheet->isCacheable())
        const_cast<CachedCSSStyleSheet*>(cachedStyleSheet)->saveParsedStyleSheet(styleSheet);
331 332
}

333
bool HTMLLinkElement::styleSheetIsLoading() const
334 335 336 337 338
{
    if (m_loading)
        return true;
    if (!m_sheet)
        return false;
339
    return m_sheet->contents()->isLoading();
340 341
}

342
void HTMLLinkElement::linkLoaded()
343
{
344
    dispatchEvent(Event::create(eventNames().loadEvent, false, false));
345 346
}

347
void HTMLLinkElement::linkLoadingErrored()
348
{
349
    dispatchEvent(Event::create(eventNames().errorEvent, false, false));
350 351
}

beidson's avatar
beidson committed
352
bool HTMLLinkElement::sheetLoaded()
353
{
354
    if (!styleSheetIsLoading()) {
355
        removePendingSheet();
beidson's avatar
beidson committed
356 357 358
        return true;
    }
    return false;
359 360
}

361 362
void HTMLLinkElement::dispatchPendingLoadEvents()
{
363
    linkLoadEventSender().dispatchPendingEvents();
364 365 366 367
}

void HTMLLinkElement::dispatchPendingEvent(LinkEventSender* eventSender)
{
368
    ASSERT_UNUSED(eventSender, eventSender == &linkLoadEventSender());
369 370 371 372 373 374 375 376 377 378 379
    if (m_loadedSheet)
        linkLoaded();
    else
        linkLoadingErrored();
}

void HTMLLinkElement::notifyLoadedSheetAndAllCriticalSubresources(bool errorOccurred)
{
    if (m_firedLoad)
        return;
    m_loadedSheet = !errorOccurred;
380
    linkLoadEventSender().dispatchEventSoon(this);
381 382 383
    m_firedLoad = true;
}

384 385 386 387 388 389 390
void HTMLLinkElement::startLoadingDynamicSheet()
{
    // We don't support multiple blocking sheets.
    ASSERT(m_pendingSheetType < Blocking);
    addPendingSheet(Blocking);
}

391
bool HTMLLinkElement::isURLAttribute(const Attribute& attribute) const
392
{
393
    return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
394 395
}

darin@apple.com's avatar
darin@apple.com committed
396
KURL HTMLLinkElement::href() const
397
{
398
    return document()->completeURL(getAttribute(hrefAttr));
399 400 401 402
}

String HTMLLinkElement::rel() const
{
403
    return getAttribute(relAttr);
404 405 406 407
}

String HTMLLinkElement::target() const
{
408
    return getAttribute(targetAttr);
409 410 411 412
}

String HTMLLinkElement::type() const
{
413
    return getAttribute(typeAttr);
414 415
}

416 417 418 419 420 421 422 423 424 425
IconType HTMLLinkElement::iconType() const
{
    return m_relAttribute.m_iconType;
}

String HTMLLinkElement::iconSizes() const
{
    return m_sizes->toString();
}

426
void HTMLLinkElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
427
{
428
    HTMLElement::addSubresourceAttributeURLs(urls);
429

430
    // Favicons are handled by a special case in LegacyWebArchive::create()
431
    if (m_relAttribute.m_iconType != InvalidIcon)
beidson@apple.com's avatar
beidson@apple.com committed
432
        return;
433

434
    if (!m_relAttribute.m_isStyleSheet)
beidson@apple.com's avatar
beidson@apple.com committed
435
        return;
436
    
beidson@apple.com's avatar
beidson@apple.com committed
437
    // Append the URL of this link element.
438
    addSubresourceURL(urls, href());
beidson@apple.com's avatar
beidson@apple.com committed
439 440
    
    // Walk the URLs linked by the linked-to stylesheet.
441
    if (CSSStyleSheet* styleSheet = const_cast<HTMLLinkElement*>(this)->sheet())
442
        styleSheet->contents()->addSubresourceStyleURLs(urls);
beidson@apple.com's avatar
beidson@apple.com committed
443 444
}

445 446 447 448 449 450 451 452
void HTMLLinkElement::addPendingSheet(PendingSheetType type)
{
    if (type <= m_pendingSheetType)
        return;
    m_pendingSheetType = type;

    if (m_pendingSheetType == NonBlocking)
        return;
453
    document()->styleSheetCollection()->addPendingSheet();
454 455
}

456
void HTMLLinkElement::removePendingSheet(RemovePendingSheetNotificationType notification)
457 458 459 460 461 462 463 464
{
    PendingSheetType type = m_pendingSheetType;
    m_pendingSheetType = None;

    if (type == None)
        return;
    if (type == NonBlocking) {
        // Document::removePendingSheet() triggers the style selector recalc for blocking sheets.
465
        document()->styleResolverChanged(RecalcStyleImmediately);
466 467
        return;
    }
468

469
    document()->styleSheetCollection()->removePendingSheet(
470
        notification == RemovePendingSheetNotifyImmediately
471 472
        ? DocumentStyleSheetCollection::RemovePendingSheetNotifyImmediately
        : DocumentStyleSheetCollection::RemovePendingSheetNotifyLater);
473 474
}

475 476 477 478 479 480 481 482
DOMSettableTokenList* HTMLLinkElement::sizes() const
{
    return m_sizes.get();
}

void HTMLLinkElement::setSizes(const String& value)
{
    m_sizes->setValue(value);
483
}
484

485 486 487 488 489 490
#if ENABLE(MICRODATA)
String HTMLLinkElement::itemValueText() const
{
    return getURLAttribute(hrefAttr);
}

491
void HTMLLinkElement::setItemValueText(const String& value, ExceptionCode&)
492
{
493
    setAttribute(hrefAttr, value);
494 495 496
}
#endif

497
} // namespace WebCore