/* * Copyright (C) 2008 Apple Inc. All Rights Reserved. * Copyright (C) 2009 Torch Mobile, Inc. http://www.torchmobile.com/ * Copyright (C) 2010 Google Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "HTMLPreloadScanner.h" #include "CachedResourceLoader.h" #include "Document.h" #include "InputType.h" #include "HTMLDocumentParser.h" #include "HTMLTokenizer.h" #include "HTMLLinkElement.h" #include "HTMLNames.h" #include "HTMLParserIdioms.h" #include "MediaList.h" #include "MediaQueryEvaluator.h" namespace WebCore { using namespace HTMLNames; namespace { class PreloadTask { public: PreloadTask(const HTMLToken& token) : m_tagName(token.name().data(), token.name().size()) , m_linkIsStyleSheet(false) , m_linkMediaAttributeIsScreen(true) , m_inputIsImage(false) { processAttributes(token.attributes()); } void processAttributes(const HTMLToken::AttributeList& attributes) { if (m_tagName != imgTag && m_tagName != inputTag && m_tagName != linkTag && m_tagName != scriptTag) return; for (HTMLToken::AttributeList::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter) { AtomicString attributeName(iter->m_name.data(), iter->m_name.size()); String attributeValue(iter->m_value.data(), iter->m_value.size()); if (attributeName == charsetAttr) m_charset = attributeValue; if (m_tagName == scriptTag || m_tagName == imgTag) { if (attributeName == srcAttr) setUrlToLoad(attributeValue); } else if (m_tagName == linkTag) { if (attributeName == hrefAttr) setUrlToLoad(attributeValue); else if (attributeName == relAttr) m_linkIsStyleSheet = relAttributeIsStyleSheet(attributeValue); else if (attributeName == mediaAttr) m_linkMediaAttributeIsScreen = linkMediaAttributeIsScreen(attributeValue); } else if (m_tagName == inputTag) { if (attributeName == srcAttr) setUrlToLoad(attributeValue); else if (attributeName == typeAttr) m_inputIsImage = equalIgnoringCase(attributeValue, InputTypeNames::image()); } } } static bool relAttributeIsStyleSheet(const String& attributeValue) { HTMLLinkElement::RelAttribute rel; HTMLLinkElement::tokenizeRelAttribute(attributeValue, rel); return rel.m_isStyleSheet && !rel.m_isAlternate && !rel.m_isIcon && !rel.m_isDNSPrefetch; } static bool linkMediaAttributeIsScreen(const String& attributeValue) { if (attributeValue.isEmpty()) return true; RefPtr<MediaList> mediaList = MediaList::createAllowingDescriptionSyntax(attributeValue); // Only preload screen media stylesheets. Used this way, the evaluator evaluates to true for any // rules containing complex queries (full evaluation is possible but it requires a frame and a style selector which // may be problematic here). MediaQueryEvaluator mediaQueryEvaluator("screen"); return mediaQueryEvaluator.eval(mediaList.get()); } void setUrlToLoad(const String& attributeValue) { // We only respect the first src/href, per HTML5: // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#attribute-name-state if (!m_urlToLoad.isEmpty()) return; m_urlToLoad = stripLeadingAndTrailingHTMLSpaces(attributeValue); } void preload(Document* document, bool scanningBody) { if (m_urlToLoad.isEmpty()) return; CachedResourceLoader* cachedResourceLoader = document->cachedResourceLoader(); if (m_tagName == scriptTag) cachedResourceLoader->preload(CachedResource::Script, m_urlToLoad, m_charset, scanningBody); else if (m_tagName == imgTag || (m_tagName == inputTag && m_inputIsImage)) cachedResourceLoader->preload(CachedResource::ImageResource, m_urlToLoad, String(), scanningBody); else if (m_tagName == linkTag && m_linkIsStyleSheet && m_linkMediaAttributeIsScreen) cachedResourceLoader->preload(CachedResource::CSSStyleSheet, m_urlToLoad, m_charset, scanningBody); } const AtomicString& tagName() const { return m_tagName; } private: AtomicString m_tagName; String m_urlToLoad; String m_charset; bool m_linkIsStyleSheet; bool m_linkMediaAttributeIsScreen; bool m_inputIsImage; }; } // namespace HTMLPreloadScanner::HTMLPreloadScanner(Document* document) : m_document(document) , m_cssScanner(document) , m_tokenizer(HTMLTokenizer::create(HTMLDocumentParser::usePreHTML5ParserQuirks(document))) , m_bodySeen(false) , m_inStyle(false) { } void HTMLPreloadScanner::appendToEnd(const SegmentedString& source) { m_source.append(source); } void HTMLPreloadScanner::scan() { // FIXME: We should save and re-use these tokens in HTMLDocumentParser if // the pending script doesn't end up calling document.write. while (m_tokenizer->nextToken(m_source, m_token)) { processToken(); m_token.clear(); } } void HTMLPreloadScanner::processToken() { if (m_inStyle) { if (m_token.type() == HTMLToken::Character) m_cssScanner.scan(m_token, scanningBody()); else if (m_token.type() == HTMLToken::EndTag) { m_inStyle = false; m_cssScanner.reset(); } } if (m_token.type() != HTMLToken::StartTag) return; PreloadTask task(m_token); m_tokenizer->updateStateFor(task.tagName(), m_document->frame()); if (task.tagName() == bodyTag) m_bodySeen = true; if (task.tagName() == styleTag) m_inStyle = true; task.preload(m_document, scanningBody()); } bool HTMLPreloadScanner::scanningBody() const { return m_document->body() || m_bodySeen; } }