| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (C) 2013 Google Inc. All rights reserved. | |
| 3 * | |
| 4 * Redistribution and use in source and binary forms, with or without | |
| 5 * modification, are permitted provided that the following conditions are met: | |
| 6 * | |
| 7 * 1. Redistributions of source code must retain the above copyright | |
| 8 * notice, this list of conditions and the following disclaimer. | |
| 9 * 2. Redistributions in binary form must reproduce the above copyright | |
| 10 * notice, this list of conditions and the following disclaimer in the | |
| 11 * documentation and/or other materials provided with the distribution. | |
| 12 * | |
| 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND | |
| 14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| 16 * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE | |
| 17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| 18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| 19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| 20 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
| 21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
| 22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH | |
| 23 * DAMAGE. | |
| 24 */ | |
| 25 | |
| 26 #include "sky/engine/core/css/FontFaceSet.h" | |
| 27 | |
| 28 #include "sky/engine/core/css/CSSFontSelector.h" | |
| 29 #include "sky/engine/core/css/CSSSegmentedFontFace.h" | |
| 30 #include "sky/engine/core/css/FontFaceCache.h" | |
| 31 #include "sky/engine/core/css/FontFaceSetLoadEvent.h" | |
| 32 #include "sky/engine/core/css/StylePropertySet.h" | |
| 33 #include "sky/engine/core/css/parser/BisonCSSParser.h" | |
| 34 #include "sky/engine/core/css/resolver/StyleResolver.h" | |
| 35 #include "sky/engine/core/dom/Document.h" | |
| 36 #include "sky/engine/core/dom/StyleEngine.h" | |
| 37 #include "sky/engine/core/frame/FrameView.h" | |
| 38 #include "sky/engine/core/frame/LocalFrame.h" | |
| 39 #include "sky/engine/core/rendering/style/RenderStyle.h" | |
| 40 #include "sky/engine/core/rendering/style/StyleInheritedData.h" | |
| 41 #include "sky/engine/public/platform/Platform.h" | |
| 42 | |
| 43 namespace blink { | |
| 44 | |
| 45 static const int defaultFontSize = 10; | |
| 46 static const char defaultFontFamily[] = "sans-serif"; | |
| 47 | |
| 48 FontFaceSet::FontFaceSet(Document& document) | |
| 49 : ActiveDOMObject(&document) | |
| 50 , m_shouldFireLoadingEvent(false) | |
| 51 , m_asyncRunner(this, &FontFaceSet::handlePendingEventsAndPromises) | |
| 52 { | |
| 53 suspendIfNeeded(); | |
| 54 } | |
| 55 | |
| 56 FontFaceSet::~FontFaceSet() | |
| 57 { | |
| 58 } | |
| 59 | |
| 60 Document* FontFaceSet::document() const | |
| 61 { | |
| 62 return toDocument(executionContext()); | |
| 63 } | |
| 64 | |
| 65 bool FontFaceSet::inActiveDocumentContext() const | |
| 66 { | |
| 67 ExecutionContext* context = executionContext(); | |
| 68 return context && toDocument(context)->isActive(); | |
| 69 } | |
| 70 | |
| 71 void FontFaceSet::addFontFacesToFontFaceCache(FontFaceCache* fontFaceCache, CSSF
ontSelector* fontSelector) | |
| 72 { | |
| 73 for (ListHashSet<RefPtr<FontFace> >::iterator it = m_nonCSSConnectedFaces.be
gin(); it != m_nonCSSConnectedFaces.end(); ++it) | |
| 74 fontFaceCache->addFontFace(fontSelector, *it, false); | |
| 75 } | |
| 76 | |
| 77 const AtomicString& FontFaceSet::interfaceName() const | |
| 78 { | |
| 79 return EventTargetNames::FontFaceSet; | |
| 80 } | |
| 81 | |
| 82 ExecutionContext* FontFaceSet::executionContext() const | |
| 83 { | |
| 84 return ActiveDOMObject::executionContext(); | |
| 85 } | |
| 86 | |
| 87 AtomicString FontFaceSet::status() const | |
| 88 { | |
| 89 DEFINE_STATIC_LOCAL(AtomicString, loading, ("loading", AtomicString::Constru
ctFromLiteral)); | |
| 90 DEFINE_STATIC_LOCAL(AtomicString, loaded, ("loaded", AtomicString::Construct
FromLiteral)); | |
| 91 return (!m_loadingFonts.isEmpty() || hasLoadedFonts()) ? loading : loaded; | |
| 92 } | |
| 93 | |
| 94 void FontFaceSet::handlePendingEventsAndPromisesSoon() | |
| 95 { | |
| 96 // m_asyncRunner will be automatically stopped on destruction. | |
| 97 m_asyncRunner.runAsync(); | |
| 98 } | |
| 99 | |
| 100 void FontFaceSet::didLayout() | |
| 101 { | |
| 102 if (m_loadingFonts.isEmpty()) | |
| 103 m_histogram.record(); | |
| 104 if (!m_loadingFonts.isEmpty() || !hasLoadedFonts()) | |
| 105 return; | |
| 106 handlePendingEventsAndPromisesSoon(); | |
| 107 } | |
| 108 | |
| 109 void FontFaceSet::handlePendingEventsAndPromises() | |
| 110 { | |
| 111 fireLoadingEvent(); | |
| 112 fireDoneEventIfPossible(); | |
| 113 } | |
| 114 | |
| 115 void FontFaceSet::fireLoadingEvent() | |
| 116 { | |
| 117 if (m_shouldFireLoadingEvent) { | |
| 118 m_shouldFireLoadingEvent = false; | |
| 119 dispatchEvent(FontFaceSetLoadEvent::createForFontFaces(EventTypeNames::l
oading)); | |
| 120 } | |
| 121 } | |
| 122 | |
| 123 void FontFaceSet::suspend() | |
| 124 { | |
| 125 m_asyncRunner.suspend(); | |
| 126 } | |
| 127 | |
| 128 void FontFaceSet::resume() | |
| 129 { | |
| 130 m_asyncRunner.resume(); | |
| 131 } | |
| 132 | |
| 133 void FontFaceSet::stop() | |
| 134 { | |
| 135 m_asyncRunner.stop(); | |
| 136 } | |
| 137 | |
| 138 void FontFaceSet::beginFontLoading(FontFace* fontFace) | |
| 139 { | |
| 140 m_histogram.incrementCount(); | |
| 141 addToLoadingFonts(fontFace); | |
| 142 } | |
| 143 | |
| 144 void FontFaceSet::fontLoaded(FontFace* fontFace) | |
| 145 { | |
| 146 m_histogram.updateStatus(fontFace); | |
| 147 m_loadedFonts.append(fontFace); | |
| 148 removeFromLoadingFonts(fontFace); | |
| 149 } | |
| 150 | |
| 151 void FontFaceSet::loadError(FontFace* fontFace) | |
| 152 { | |
| 153 m_histogram.updateStatus(fontFace); | |
| 154 m_failedFonts.append(fontFace); | |
| 155 removeFromLoadingFonts(fontFace); | |
| 156 } | |
| 157 | |
| 158 void FontFaceSet::addToLoadingFonts(PassRefPtr<FontFace> fontFace) | |
| 159 { | |
| 160 if (m_loadingFonts.isEmpty() && !hasLoadedFonts()) { | |
| 161 m_shouldFireLoadingEvent = true; | |
| 162 handlePendingEventsAndPromisesSoon(); | |
| 163 } | |
| 164 m_loadingFonts.add(fontFace); | |
| 165 } | |
| 166 | |
| 167 void FontFaceSet::removeFromLoadingFonts(PassRefPtr<FontFace> fontFace) | |
| 168 { | |
| 169 m_loadingFonts.remove(fontFace); | |
| 170 if (m_loadingFonts.isEmpty()) | |
| 171 handlePendingEventsAndPromisesSoon(); | |
| 172 } | |
| 173 | |
| 174 void FontFaceSet::add(FontFace* fontFace, ExceptionState& exceptionState) | |
| 175 { | |
| 176 if (!inActiveDocumentContext()) | |
| 177 return; | |
| 178 if (!fontFace) { | |
| 179 exceptionState.ThrowTypeError("The argument is not a FontFace."); | |
| 180 return; | |
| 181 } | |
| 182 if (m_nonCSSConnectedFaces.contains(fontFace)) | |
| 183 return; | |
| 184 if (isCSSConnectedFontFace(fontFace)) { | |
| 185 exceptionState.ThrowDOMException(InvalidModificationError, "Cannot add a
CSS-connected FontFace."); | |
| 186 return; | |
| 187 } | |
| 188 CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector(); | |
| 189 m_nonCSSConnectedFaces.add(fontFace); | |
| 190 fontSelector->fontFaceCache()->addFontFace(fontSelector, fontFace, false); | |
| 191 if (fontFace->loadStatus() == FontFace::Loading) | |
| 192 addToLoadingFonts(fontFace); | |
| 193 fontSelector->fontFaceInvalidated(); | |
| 194 } | |
| 195 | |
| 196 void FontFaceSet::clear() | |
| 197 { | |
| 198 if (!inActiveDocumentContext() || m_nonCSSConnectedFaces.isEmpty()) | |
| 199 return; | |
| 200 CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector(); | |
| 201 FontFaceCache* fontFaceCache = fontSelector->fontFaceCache(); | |
| 202 for (ListHashSet<RefPtr<FontFace> >::iterator it = m_nonCSSConnectedFaces.be
gin(); it != m_nonCSSConnectedFaces.end(); ++it) { | |
| 203 fontFaceCache->removeFontFace(it->get(), false); | |
| 204 if ((*it)->loadStatus() == FontFace::Loading) | |
| 205 removeFromLoadingFonts(*it); | |
| 206 } | |
| 207 m_nonCSSConnectedFaces.clear(); | |
| 208 fontSelector->fontFaceInvalidated(); | |
| 209 } | |
| 210 | |
| 211 bool FontFaceSet::remove(FontFace* fontFace, ExceptionState& exceptionState) | |
| 212 { | |
| 213 if (!inActiveDocumentContext()) | |
| 214 return false; | |
| 215 if (!fontFace) { | |
| 216 exceptionState.ThrowTypeError("The argument is not a FontFace."); | |
| 217 return false; | |
| 218 } | |
| 219 ListHashSet<RefPtr<FontFace> >::iterator it = m_nonCSSConnectedFaces.find(fo
ntFace); | |
| 220 if (it != m_nonCSSConnectedFaces.end()) { | |
| 221 m_nonCSSConnectedFaces.remove(it); | |
| 222 CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector(
); | |
| 223 fontSelector->fontFaceCache()->removeFontFace(fontFace, false); | |
| 224 if (fontFace->loadStatus() == FontFace::Loading) | |
| 225 removeFromLoadingFonts(fontFace); | |
| 226 fontSelector->fontFaceInvalidated(); | |
| 227 return true; | |
| 228 } | |
| 229 if (isCSSConnectedFontFace(fontFace)) | |
| 230 exceptionState.ThrowDOMException(InvalidModificationError, "Cannot delet
e a CSS-connected FontFace."); | |
| 231 return false; | |
| 232 } | |
| 233 | |
| 234 bool FontFaceSet::has(FontFace* fontFace, ExceptionState& exceptionState) const | |
| 235 { | |
| 236 if (!inActiveDocumentContext()) | |
| 237 return false; | |
| 238 if (!fontFace) { | |
| 239 exceptionState.ThrowTypeError("The argument is not a FontFace."); | |
| 240 return false; | |
| 241 } | |
| 242 return m_nonCSSConnectedFaces.contains(fontFace) || isCSSConnectedFontFace(f
ontFace); | |
| 243 } | |
| 244 | |
| 245 const ListHashSet<RefPtr<FontFace> >& FontFaceSet::cssConnectedFontFaceList() co
nst | |
| 246 { | |
| 247 Document* d = document(); | |
| 248 return d->styleEngine()->fontSelector()->fontFaceCache()->cssConnectedFontFa
ces(); | |
| 249 } | |
| 250 | |
| 251 bool FontFaceSet::isCSSConnectedFontFace(FontFace* fontFace) const | |
| 252 { | |
| 253 return cssConnectedFontFaceList().contains(fontFace); | |
| 254 } | |
| 255 | |
| 256 unsigned long FontFaceSet::size() const | |
| 257 { | |
| 258 if (!inActiveDocumentContext()) | |
| 259 return m_nonCSSConnectedFaces.size(); | |
| 260 return cssConnectedFontFaceList().size() + m_nonCSSConnectedFaces.size(); | |
| 261 } | |
| 262 | |
| 263 void FontFaceSet::fireDoneEventIfPossible() | |
| 264 { | |
| 265 if (m_shouldFireLoadingEvent) | |
| 266 return; | |
| 267 if (!m_loadingFonts.isEmpty() || !hasLoadedFonts()) | |
| 268 return; | |
| 269 | |
| 270 // If the layout was invalidated in between when we thought layout | |
| 271 // was updated and when we're ready to fire the event, just wait | |
| 272 // until after the next layout before firing events. | |
| 273 Document* d = document(); | |
| 274 if (!d->view() || d->view()->needsLayout()) | |
| 275 return; | |
| 276 | |
| 277 if (hasLoadedFonts()) { | |
| 278 RefPtr<FontFaceSetLoadEvent> doneEvent = nullptr; | |
| 279 RefPtr<FontFaceSetLoadEvent> errorEvent = nullptr; | |
| 280 doneEvent = FontFaceSetLoadEvent::createForFontFaces(EventTypeNames::loa
dingdone, m_loadedFonts); | |
| 281 m_loadedFonts.clear(); | |
| 282 if (!m_failedFonts.isEmpty()) { | |
| 283 errorEvent = FontFaceSetLoadEvent::createForFontFaces(EventTypeNames
::loadingerror, m_failedFonts); | |
| 284 m_failedFonts.clear(); | |
| 285 } | |
| 286 dispatchEvent(doneEvent); | |
| 287 if (errorEvent) | |
| 288 dispatchEvent(errorEvent); | |
| 289 } | |
| 290 } | |
| 291 | |
| 292 static const String& nullToSpace(const String& s) | |
| 293 { | |
| 294 DEFINE_STATIC_LOCAL(String, space, (" ")); | |
| 295 return s.isNull() ? space : s; | |
| 296 } | |
| 297 | |
| 298 bool FontFaceSet::check(const String& fontString, const String& text, ExceptionS
tate& exceptionState) | |
| 299 { | |
| 300 if (!inActiveDocumentContext()) | |
| 301 return false; | |
| 302 | |
| 303 Font font; | |
| 304 if (!resolveFontStyle(fontString, font)) { | |
| 305 exceptionState.ThrowDOMException(SyntaxError, "Could not resolve '" + fo
ntString + "' as a font."); | |
| 306 return false; | |
| 307 } | |
| 308 | |
| 309 CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector(); | |
| 310 FontFaceCache* fontFaceCache = fontSelector->fontFaceCache(); | |
| 311 | |
| 312 bool hasLoadedFaces = false; | |
| 313 for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next(
)) { | |
| 314 CSSSegmentedFontFace* face = fontFaceCache->get(font.fontDescription(),
f->family()); | |
| 315 if (face) { | |
| 316 if (!face->checkFont(nullToSpace(text))) | |
| 317 return false; | |
| 318 hasLoadedFaces = true; | |
| 319 } | |
| 320 } | |
| 321 if (hasLoadedFaces) | |
| 322 return true; | |
| 323 for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next(
)) { | |
| 324 if (fontSelector->isPlatformFontAvailable(font.fontDescription(), f->fam
ily())) | |
| 325 return true; | |
| 326 } | |
| 327 return false; | |
| 328 } | |
| 329 | |
| 330 bool FontFaceSet::resolveFontStyle(const String& fontString, Font& font) | |
| 331 { | |
| 332 if (fontString.isEmpty()) | |
| 333 return false; | |
| 334 | |
| 335 // Interpret fontString in the same way as the 'font' attribute of CanvasRen
deringContext2D. | |
| 336 RefPtr<MutableStylePropertySet> parsedStyle = MutableStylePropertySet::creat
e(); | |
| 337 BisonCSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, fontString, H
TMLStandardMode, 0); | |
| 338 if (parsedStyle->isEmpty()) | |
| 339 return false; | |
| 340 | |
| 341 String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont); | |
| 342 if (fontValue == "inherit" || fontValue == "initial") | |
| 343 return false; | |
| 344 | |
| 345 RefPtr<RenderStyle> style = RenderStyle::create(); | |
| 346 | |
| 347 FontFamily fontFamily; | |
| 348 fontFamily.setFamily(defaultFontFamily); | |
| 349 | |
| 350 FontDescription defaultFontDescription; | |
| 351 defaultFontDescription.setFamily(fontFamily); | |
| 352 defaultFontDescription.setSpecifiedSize(defaultFontSize); | |
| 353 defaultFontDescription.setComputedSize(defaultFontSize); | |
| 354 | |
| 355 style->setFontDescription(defaultFontDescription); | |
| 356 | |
| 357 style->font().update(style->font().fontSelector()); | |
| 358 | |
| 359 // Now map the font property longhands into the style. | |
| 360 CSSPropertyValue properties[] = { | |
| 361 CSSPropertyValue(CSSPropertyFontFamily, *parsedStyle), | |
| 362 CSSPropertyValue(CSSPropertyFontStretch, *parsedStyle), | |
| 363 CSSPropertyValue(CSSPropertyFontStyle, *parsedStyle), | |
| 364 CSSPropertyValue(CSSPropertyFontVariant, *parsedStyle), | |
| 365 CSSPropertyValue(CSSPropertyFontWeight, *parsedStyle), | |
| 366 CSSPropertyValue(CSSPropertyFontSize, *parsedStyle), | |
| 367 CSSPropertyValue(CSSPropertyLineHeight, *parsedStyle), | |
| 368 }; | |
| 369 StyleResolver& styleResolver = document()->styleResolver(); | |
| 370 styleResolver.applyPropertiesToStyle(properties, WTF_ARRAY_LENGTH(properties
), style.get()); | |
| 371 | |
| 372 font = style->font(); | |
| 373 font.update(document()->styleEngine()->fontSelector()); | |
| 374 return true; | |
| 375 } | |
| 376 | |
| 377 void FontFaceSet::FontLoadHistogram::updateStatus(FontFace* fontFace) | |
| 378 { | |
| 379 if (m_status == Reported) | |
| 380 return; | |
| 381 if (fontFace->hadBlankText()) | |
| 382 m_status = HadBlankText; | |
| 383 else if (m_status == NoWebFonts) | |
| 384 m_status = DidNotHaveBlankText; | |
| 385 } | |
| 386 | |
| 387 void FontFaceSet::FontLoadHistogram::record() | |
| 388 { | |
| 389 if (!m_recorded) { | |
| 390 m_recorded = true; | |
| 391 blink::Platform::current()->histogramCustomCounts("WebFont.WebFontsInPag
e", m_count, 1, 100, 50); | |
| 392 } | |
| 393 if (m_status == HadBlankText || m_status == DidNotHaveBlankText) { | |
| 394 blink::Platform::current()->histogramEnumeration("WebFont.HadBlankText",
m_status == HadBlankText ? 1 : 0, 2); | |
| 395 m_status = Reported; | |
| 396 } | |
| 397 } | |
| 398 | |
| 399 static const char* supplementName() | |
| 400 { | |
| 401 return "FontFaceSet"; | |
| 402 } | |
| 403 | |
| 404 PassRefPtr<FontFaceSet> FontFaceSet::from(Document& document) | |
| 405 { | |
| 406 RefPtr<FontFaceSet> fonts = static_cast<FontFaceSet*>(SupplementType::from(d
ocument, supplementName())); | |
| 407 if (!fonts) { | |
| 408 fonts = FontFaceSet::create(document); | |
| 409 SupplementType::provideTo(document, supplementName(), fonts); | |
| 410 } | |
| 411 | |
| 412 return fonts.release(); | |
| 413 } | |
| 414 | |
| 415 void FontFaceSet::didLayout(Document& document) | |
| 416 { | |
| 417 if (FontFaceSet* fonts = static_cast<FontFaceSet*>(SupplementType::from(docu
ment, supplementName()))) | |
| 418 fonts->didLayout(); | |
| 419 } | |
| 420 | |
| 421 } // namespace blink | |
| OLD | NEW |