1 /* 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. 3 * Copyright (C) 2009 Torch Mobile, Inc. http://www.torchmobile.com/ 4 * Copyright (C) 2010 Google Inc. All Rights Reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include "config.h" 29 #include "HTMLPreloadScanner.h" 30 31 #include "CachedResourceLoader.h" 32 #include "Document.h" 33 #include "InputType.h" 34 #include "HTMLDocumentParser.h" 35 #include "HTMLTokenizer.h" 36 #include "HTMLLinkElement.h" 37 #include "HTMLNames.h" 38 #include "HTMLParserIdioms.h" 39 #include "MediaList.h" 40 #include "MediaQueryEvaluator.h" 41 42 namespace WebCore { 43 44 using namespace HTMLNames; 45 46 namespace { 47 48 class PreloadTask { 49 public: 50 PreloadTask(const HTMLToken& token) 51 : m_tagName(token.name().data(), token.name().size()) 52 , m_linkIsStyleSheet(false) 53 , m_linkMediaAttributeIsScreen(true) 54 , m_inputIsImage(false) 55 { 56 processAttributes(token.attributes()); 57 } 58 59 void processAttributes(const HTMLToken::AttributeList& attributes) 60 { 61 if (m_tagName != imgTag 62 && m_tagName != inputTag 63 && m_tagName != linkTag 64 && m_tagName != scriptTag) 65 return; 66 67 for (HTMLToken::AttributeList::const_iterator iter = attributes.begin(); 68 iter != attributes.end(); ++iter) { 69 AtomicString attributeName(iter->m_name.data(), iter->m_name.size()); 70 String attributeValue(iter->m_value.data(), iter->m_value.size()); 71 72 if (attributeName == charsetAttr) 73 m_charset = attributeValue; 74 75 if (m_tagName == scriptTag || m_tagName == imgTag) { 76 if (attributeName == srcAttr) 77 setUrlToLoad(attributeValue); 78 } else if (m_tagName == linkTag) { 79 if (attributeName == hrefAttr) 80 setUrlToLoad(attributeValue); 81 else if (attributeName == relAttr) 82 m_linkIsStyleSheet = relAttributeIsStyleSheet(attributeValue); 83 else if (attributeName == mediaAttr) 84 m_linkMediaAttributeIsScreen = linkMediaAttributeIsScreen(attributeValue); 85 } else if (m_tagName == inputTag) { 86 if (attributeName == srcAttr) 87 setUrlToLoad(attributeValue); 88 else if (attributeName == typeAttr) 89 m_inputIsImage = equalIgnoringCase(attributeValue, InputTypeNames::image()); 90 } 91 } 92 } 93 94 static bool relAttributeIsStyleSheet(const String& attributeValue) 95 { 96 HTMLLinkElement::RelAttribute rel; 97 HTMLLinkElement::tokenizeRelAttribute(attributeValue, rel); 98 return rel.m_isStyleSheet && !rel.m_isAlternate && !rel.m_isIcon && !rel.m_isDNSPrefetch; 99 } 100 101 static bool linkMediaAttributeIsScreen(const String& attributeValue) 102 { 103 if (attributeValue.isEmpty()) 104 return true; 105 RefPtr<MediaList> mediaList = MediaList::createAllowingDescriptionSyntax(attributeValue); 106 107 // Only preload screen media stylesheets. Used this way, the evaluator evaluates to true for any 108 // rules containing complex queries (full evaluation is possible but it requires a frame and a style selector which 109 // may be problematic here). 110 MediaQueryEvaluator mediaQueryEvaluator("screen"); 111 return mediaQueryEvaluator.eval(mediaList.get()); 112 } 113 114 void setUrlToLoad(const String& attributeValue) 115 { 116 // We only respect the first src/href, per HTML5: 117 // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#attribute-name-state 118 if (!m_urlToLoad.isEmpty()) 119 return; 120 m_urlToLoad = stripLeadingAndTrailingHTMLSpaces(attributeValue); 121 } 122 123 void preload(Document* document, bool scanningBody) 124 { 125 if (m_urlToLoad.isEmpty()) 126 return; 127 128 CachedResourceLoader* cachedResourceLoader = document->cachedResourceLoader(); 129 if (m_tagName == scriptTag) 130 cachedResourceLoader->preload(CachedResource::Script, m_urlToLoad, m_charset, scanningBody); 131 else if (m_tagName == imgTag || (m_tagName == inputTag && m_inputIsImage)) 132 cachedResourceLoader->preload(CachedResource::ImageResource, m_urlToLoad, String(), scanningBody); 133 else if (m_tagName == linkTag && m_linkIsStyleSheet && m_linkMediaAttributeIsScreen) 134 cachedResourceLoader->preload(CachedResource::CSSStyleSheet, m_urlToLoad, m_charset, scanningBody); 135 } 136 137 const AtomicString& tagName() const { return m_tagName; } 138 139 private: 140 AtomicString m_tagName; 141 String m_urlToLoad; 142 String m_charset; 143 bool m_linkIsStyleSheet; 144 bool m_linkMediaAttributeIsScreen; 145 bool m_inputIsImage; 146 }; 147 148 } // namespace 149 150 HTMLPreloadScanner::HTMLPreloadScanner(Document* document) 151 : m_document(document) 152 , m_cssScanner(document) 153 , m_tokenizer(HTMLTokenizer::create(HTMLDocumentParser::usePreHTML5ParserQuirks(document))) 154 , m_bodySeen(false) 155 , m_inStyle(false) 156 { 157 } 158 159 void HTMLPreloadScanner::appendToEnd(const SegmentedString& source) 160 { 161 m_source.append(source); 162 } 163 164 void HTMLPreloadScanner::scan() 165 { 166 // FIXME: We should save and re-use these tokens in HTMLDocumentParser if 167 // the pending script doesn't end up calling document.write. 168 while (m_tokenizer->nextToken(m_source, m_token)) { 169 processToken(); 170 m_token.clear(); 171 } 172 } 173 174 void HTMLPreloadScanner::processToken() 175 { 176 if (m_inStyle) { 177 if (m_token.type() == HTMLToken::Character) 178 m_cssScanner.scan(m_token, scanningBody()); 179 else if (m_token.type() == HTMLToken::EndTag) { 180 m_inStyle = false; 181 m_cssScanner.reset(); 182 } 183 } 184 185 if (m_token.type() != HTMLToken::StartTag) 186 return; 187 188 PreloadTask task(m_token); 189 m_tokenizer->updateStateFor(task.tagName(), m_document->frame()); 190 191 if (task.tagName() == bodyTag) 192 m_bodySeen = true; 193 194 if (task.tagName() == styleTag) 195 m_inStyle = true; 196 197 task.preload(m_document, scanningBody()); 198 } 199 200 bool HTMLPreloadScanner::scanningBody() const 201 { 202 return m_document->body() || m_bodySeen; 203 } 204 205 } 206