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 "config.h" 27#include "core/css/FontFaceSet.h" 28 29#include "bindings/core/v8/Dictionary.h" 30#include "bindings/core/v8/ScriptPromiseResolver.h" 31#include "bindings/core/v8/ScriptState.h" 32#include "core/css/CSSFontSelector.h" 33#include "core/css/CSSSegmentedFontFace.h" 34#include "core/css/FontFaceCache.h" 35#include "core/css/FontFaceSetLoadEvent.h" 36#include "core/css/StylePropertySet.h" 37#include "core/css/parser/CSSParser.h" 38#include "core/css/resolver/StyleResolver.h" 39#include "core/dom/Document.h" 40#include "core/dom/StyleEngine.h" 41#include "core/frame/FrameView.h" 42#include "core/frame/LocalFrame.h" 43#include "core/rendering/style/StyleInheritedData.h" 44#include "public/platform/Platform.h" 45 46namespace blink { 47 48static const int defaultFontSize = 10; 49static const char defaultFontFamily[] = "sans-serif"; 50 51class LoadFontPromiseResolver FINAL : public FontFace::LoadFontCallback { 52public: 53 static PassRefPtrWillBeRawPtr<LoadFontPromiseResolver> create(FontFaceArray faces, ScriptState* scriptState) 54 { 55 return adoptRefWillBeNoop(new LoadFontPromiseResolver(faces, scriptState)); 56 } 57 58 void loadFonts(ExecutionContext*); 59 ScriptPromise promise() { return m_resolver->promise(); } 60 61 virtual void notifyLoaded(FontFace*) OVERRIDE; 62 virtual void notifyError(FontFace*) OVERRIDE; 63 64 virtual void trace(Visitor*) OVERRIDE; 65 66private: 67 LoadFontPromiseResolver(FontFaceArray faces, ScriptState* scriptState) 68 : m_numLoading(faces.size()) 69 , m_errorOccured(false) 70 , m_resolver(ScriptPromiseResolver::create(scriptState)) 71 { 72 m_fontFaces.swap(faces); 73 } 74 75 WillBeHeapVector<RefPtrWillBeMember<FontFace> > m_fontFaces; 76 int m_numLoading; 77 bool m_errorOccured; 78 RefPtr<ScriptPromiseResolver> m_resolver; 79}; 80 81void LoadFontPromiseResolver::loadFonts(ExecutionContext* context) 82{ 83 if (!m_numLoading) { 84 m_resolver->resolve(m_fontFaces); 85 return; 86 } 87 88 for (size_t i = 0; i < m_fontFaces.size(); i++) 89 m_fontFaces[i]->loadWithCallback(this, context); 90} 91 92void LoadFontPromiseResolver::notifyLoaded(FontFace* fontFace) 93{ 94 m_numLoading--; 95 if (m_numLoading || m_errorOccured) 96 return; 97 98 m_resolver->resolve(m_fontFaces); 99} 100 101void LoadFontPromiseResolver::notifyError(FontFace* fontFace) 102{ 103 m_numLoading--; 104 if (!m_errorOccured) { 105 m_errorOccured = true; 106 m_resolver->reject(fontFace->error()); 107 } 108} 109 110void LoadFontPromiseResolver::trace(Visitor* visitor) 111{ 112 visitor->trace(m_fontFaces); 113 LoadFontCallback::trace(visitor); 114} 115 116class FontsReadyPromiseResolver { 117public: 118 static PassOwnPtr<FontsReadyPromiseResolver> create(ScriptState* scriptState) 119 { 120 return adoptPtr(new FontsReadyPromiseResolver(scriptState)); 121 } 122 123 void resolve(PassRefPtrWillBeRawPtr<FontFaceSet> fontFaceSet) 124 { 125 m_resolver->resolve(fontFaceSet); 126 } 127 128 ScriptPromise promise() { return m_resolver->promise(); } 129 130private: 131 explicit FontsReadyPromiseResolver(ScriptState* scriptState) 132 : m_resolver(ScriptPromiseResolver::create(scriptState)) 133 { 134 } 135 136 RefPtr<ScriptPromiseResolver> m_resolver; 137}; 138 139FontFaceSet::FontFaceSet(Document& document) 140 : ActiveDOMObject(&document) 141 , m_shouldFireLoadingEvent(false) 142 , m_asyncRunner(this, &FontFaceSet::handlePendingEventsAndPromises) 143{ 144 suspendIfNeeded(); 145} 146 147FontFaceSet::~FontFaceSet() 148{ 149} 150 151Document* FontFaceSet::document() const 152{ 153 return toDocument(executionContext()); 154} 155 156bool FontFaceSet::inActiveDocumentContext() const 157{ 158 ExecutionContext* context = executionContext(); 159 return context && toDocument(context)->isActive(); 160} 161 162void FontFaceSet::addFontFacesToFontFaceCache(FontFaceCache* fontFaceCache, CSSFontSelector* fontSelector) 163{ 164 for (WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it) 165 fontFaceCache->addFontFace(fontSelector, *it, false); 166} 167 168const AtomicString& FontFaceSet::interfaceName() const 169{ 170 return EventTargetNames::FontFaceSet; 171} 172 173ExecutionContext* FontFaceSet::executionContext() const 174{ 175 return ActiveDOMObject::executionContext(); 176} 177 178AtomicString FontFaceSet::status() const 179{ 180 DEFINE_STATIC_LOCAL(AtomicString, loading, ("loading", AtomicString::ConstructFromLiteral)); 181 DEFINE_STATIC_LOCAL(AtomicString, loaded, ("loaded", AtomicString::ConstructFromLiteral)); 182 return (!m_loadingFonts.isEmpty() || hasLoadedFonts()) ? loading : loaded; 183} 184 185void FontFaceSet::handlePendingEventsAndPromisesSoon() 186{ 187 // m_asyncRunner will be automatically stopped on destruction. 188 m_asyncRunner.runAsync(); 189} 190 191void FontFaceSet::didLayout() 192{ 193 if (document()->frame()->isMainFrame() && m_loadingFonts.isEmpty()) 194 m_histogram.record(); 195 if (!m_loadingFonts.isEmpty() || (!hasLoadedFonts() && m_readyResolvers.isEmpty())) 196 return; 197 handlePendingEventsAndPromisesSoon(); 198} 199 200void FontFaceSet::handlePendingEventsAndPromises() 201{ 202 fireLoadingEvent(); 203 fireDoneEventIfPossible(); 204} 205 206void FontFaceSet::fireLoadingEvent() 207{ 208 if (m_shouldFireLoadingEvent) { 209 m_shouldFireLoadingEvent = false; 210 dispatchEvent(FontFaceSetLoadEvent::createForFontFaces(EventTypeNames::loading)); 211 } 212} 213 214void FontFaceSet::suspend() 215{ 216 m_asyncRunner.suspend(); 217} 218 219void FontFaceSet::resume() 220{ 221 m_asyncRunner.resume(); 222} 223 224void FontFaceSet::stop() 225{ 226 m_asyncRunner.stop(); 227} 228 229void FontFaceSet::beginFontLoading(FontFace* fontFace) 230{ 231 m_histogram.incrementCount(); 232 addToLoadingFonts(fontFace); 233} 234 235void FontFaceSet::fontLoaded(FontFace* fontFace) 236{ 237 m_histogram.updateStatus(fontFace); 238 m_loadedFonts.append(fontFace); 239 removeFromLoadingFonts(fontFace); 240} 241 242void FontFaceSet::loadError(FontFace* fontFace) 243{ 244 m_histogram.updateStatus(fontFace); 245 m_failedFonts.append(fontFace); 246 removeFromLoadingFonts(fontFace); 247} 248 249void FontFaceSet::addToLoadingFonts(PassRefPtrWillBeRawPtr<FontFace> fontFace) 250{ 251 if (m_loadingFonts.isEmpty() && !hasLoadedFonts()) { 252 m_shouldFireLoadingEvent = true; 253 handlePendingEventsAndPromisesSoon(); 254 } 255 m_loadingFonts.add(fontFace); 256} 257 258void FontFaceSet::removeFromLoadingFonts(PassRefPtrWillBeRawPtr<FontFace> fontFace) 259{ 260 m_loadingFonts.remove(fontFace); 261 if (m_loadingFonts.isEmpty()) 262 handlePendingEventsAndPromisesSoon(); 263} 264 265ScriptPromise FontFaceSet::ready(ScriptState* scriptState) 266{ 267 if (!inActiveDocumentContext()) 268 return ScriptPromise(); 269 OwnPtr<FontsReadyPromiseResolver> resolver = FontsReadyPromiseResolver::create(scriptState); 270 ScriptPromise promise = resolver->promise(); 271 m_readyResolvers.append(resolver.release()); 272 handlePendingEventsAndPromisesSoon(); 273 return promise; 274} 275 276void FontFaceSet::add(FontFace* fontFace, ExceptionState& exceptionState) 277{ 278 if (!inActiveDocumentContext()) 279 return; 280 if (!fontFace) { 281 exceptionState.throwTypeError("The argument is not a FontFace."); 282 return; 283 } 284 if (m_nonCSSConnectedFaces.contains(fontFace)) 285 return; 286 if (isCSSConnectedFontFace(fontFace)) { 287 exceptionState.throwDOMException(InvalidModificationError, "Cannot add a CSS-connected FontFace."); 288 return; 289 } 290 CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector(); 291 m_nonCSSConnectedFaces.add(fontFace); 292 fontSelector->fontFaceCache()->addFontFace(fontSelector, fontFace, false); 293 if (fontFace->loadStatus() == FontFace::Loading) 294 addToLoadingFonts(fontFace); 295 fontSelector->fontFaceInvalidated(); 296} 297 298void FontFaceSet::clear() 299{ 300 if (!inActiveDocumentContext() || m_nonCSSConnectedFaces.isEmpty()) 301 return; 302 CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector(); 303 FontFaceCache* fontFaceCache = fontSelector->fontFaceCache(); 304 for (WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it) { 305 fontFaceCache->removeFontFace(it->get(), false); 306 if ((*it)->loadStatus() == FontFace::Loading) 307 removeFromLoadingFonts(*it); 308 } 309 m_nonCSSConnectedFaces.clear(); 310 fontSelector->fontFaceInvalidated(); 311} 312 313bool FontFaceSet::remove(FontFace* fontFace, ExceptionState& exceptionState) 314{ 315 if (!inActiveDocumentContext()) 316 return false; 317 if (!fontFace) { 318 exceptionState.throwTypeError("The argument is not a FontFace."); 319 return false; 320 } 321 WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.find(fontFace); 322 if (it != m_nonCSSConnectedFaces.end()) { 323 m_nonCSSConnectedFaces.remove(it); 324 CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector(); 325 fontSelector->fontFaceCache()->removeFontFace(fontFace, false); 326 if (fontFace->loadStatus() == FontFace::Loading) 327 removeFromLoadingFonts(fontFace); 328 fontSelector->fontFaceInvalidated(); 329 return true; 330 } 331 if (isCSSConnectedFontFace(fontFace)) 332 exceptionState.throwDOMException(InvalidModificationError, "Cannot delete a CSS-connected FontFace."); 333 return false; 334} 335 336bool FontFaceSet::has(FontFace* fontFace, ExceptionState& exceptionState) const 337{ 338 if (!inActiveDocumentContext()) 339 return false; 340 if (!fontFace) { 341 exceptionState.throwTypeError("The argument is not a FontFace."); 342 return false; 343 } 344 return m_nonCSSConnectedFaces.contains(fontFace) || isCSSConnectedFontFace(fontFace); 345} 346 347const WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >& FontFaceSet::cssConnectedFontFaceList() const 348{ 349 Document* d = document(); 350 d->ensureStyleResolver(); // Flush pending style changes. 351 return d->styleEngine()->fontSelector()->fontFaceCache()->cssConnectedFontFaces(); 352} 353 354bool FontFaceSet::isCSSConnectedFontFace(FontFace* fontFace) const 355{ 356 return cssConnectedFontFaceList().contains(fontFace); 357} 358 359void FontFaceSet::forEach(FontFaceSetForEachCallback* callback, const ScriptValue& thisArg) const 360{ 361 forEachInternal(callback, &thisArg); 362} 363 364void FontFaceSet::forEach(FontFaceSetForEachCallback* callback) const 365{ 366 forEachInternal(callback, 0); 367} 368 369void FontFaceSet::forEachInternal(FontFaceSetForEachCallback* callback, const ScriptValue* thisArg) const 370{ 371 if (!inActiveDocumentContext()) 372 return; 373 const WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >& cssConnectedFaces = cssConnectedFontFaceList(); 374 WillBeHeapVector<RefPtrWillBeMember<FontFace> > fontFaces; 375 fontFaces.reserveInitialCapacity(cssConnectedFaces.size() + m_nonCSSConnectedFaces.size()); 376 for (WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::const_iterator it = cssConnectedFaces.begin(); it != cssConnectedFaces.end(); ++it) 377 fontFaces.append(*it); 378 for (WillBeHeapListHashSet<RefPtrWillBeMember<FontFace> >::const_iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it) 379 fontFaces.append(*it); 380 381 for (size_t i = 0; i < fontFaces.size(); ++i) { 382 FontFace* face = fontFaces[i].get(); 383 if (thisArg) 384 callback->handleItem(*thisArg, face, face, const_cast<FontFaceSet*>(this)); 385 else 386 callback->handleItem(face, face, const_cast<FontFaceSet*>(this)); 387 } 388} 389 390unsigned long FontFaceSet::size() const 391{ 392 if (!inActiveDocumentContext()) 393 return m_nonCSSConnectedFaces.size(); 394 return cssConnectedFontFaceList().size() + m_nonCSSConnectedFaces.size(); 395} 396 397void FontFaceSet::fireDoneEventIfPossible() 398{ 399 if (m_shouldFireLoadingEvent) 400 return; 401 if (!m_loadingFonts.isEmpty() || (!hasLoadedFonts() && m_readyResolvers.isEmpty())) 402 return; 403 404 // If the layout was invalidated in between when we thought layout 405 // was updated and when we're ready to fire the event, just wait 406 // until after the next layout before firing events. 407 Document* d = document(); 408 if (!d->view() || d->view()->needsLayout()) 409 return; 410 411 if (hasLoadedFonts()) { 412 RefPtrWillBeRawPtr<FontFaceSetLoadEvent> doneEvent = nullptr; 413 RefPtrWillBeRawPtr<FontFaceSetLoadEvent> errorEvent = nullptr; 414 doneEvent = FontFaceSetLoadEvent::createForFontFaces(EventTypeNames::loadingdone, m_loadedFonts); 415 m_loadedFonts.clear(); 416 if (!m_failedFonts.isEmpty()) { 417 errorEvent = FontFaceSetLoadEvent::createForFontFaces(EventTypeNames::loadingerror, m_failedFonts); 418 m_failedFonts.clear(); 419 } 420 dispatchEvent(doneEvent); 421 if (errorEvent) 422 dispatchEvent(errorEvent); 423 } 424 425 if (!m_readyResolvers.isEmpty()) { 426 Vector<OwnPtr<FontsReadyPromiseResolver> > resolvers; 427 m_readyResolvers.swap(resolvers); 428 for (size_t index = 0; index < resolvers.size(); ++index) 429 resolvers[index]->resolve(this); 430 } 431} 432 433ScriptPromise FontFaceSet::load(ScriptState* scriptState, const String& fontString, const String& text) 434{ 435 if (!inActiveDocumentContext()) 436 return ScriptPromise(); 437 438 Font font; 439 if (!resolveFontStyle(fontString, font)) { 440 RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState); 441 ScriptPromise promise = resolver->promise(); 442 resolver->reject(DOMException::create(SyntaxError, "Could not resolve '" + fontString + "' as a font.")); 443 return promise; 444 } 445 446 FontFaceCache* fontFaceCache = document()->styleEngine()->fontSelector()->fontFaceCache(); 447 FontFaceArray faces; 448 for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) { 449 CSSSegmentedFontFace* segmentedFontFace = fontFaceCache->get(font.fontDescription(), f->family()); 450 if (segmentedFontFace) 451 segmentedFontFace->match(text, faces); 452 } 453 454 RefPtrWillBeRawPtr<LoadFontPromiseResolver> resolver = LoadFontPromiseResolver::create(faces, scriptState); 455 ScriptPromise promise = resolver->promise(); 456 resolver->loadFonts(executionContext()); // After this, resolver->promise() may return null. 457 return promise; 458} 459 460bool FontFaceSet::check(const String& fontString, const String& text, ExceptionState& exceptionState) 461{ 462 if (!inActiveDocumentContext()) 463 return false; 464 465 Font font; 466 if (!resolveFontStyle(fontString, font)) { 467 exceptionState.throwDOMException(SyntaxError, "Could not resolve '" + fontString + "' as a font."); 468 return false; 469 } 470 471 CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector(); 472 FontFaceCache* fontFaceCache = fontSelector->fontFaceCache(); 473 474 bool hasLoadedFaces = false; 475 for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) { 476 CSSSegmentedFontFace* face = fontFaceCache->get(font.fontDescription(), f->family()); 477 if (face) { 478 if (!face->checkFont(text)) 479 return false; 480 hasLoadedFaces = true; 481 } 482 } 483 if (hasLoadedFaces) 484 return true; 485 for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) { 486 if (fontSelector->isPlatformFontAvailable(font.fontDescription(), f->family())) 487 return true; 488 } 489 return false; 490} 491 492bool FontFaceSet::resolveFontStyle(const String& fontString, Font& font) 493{ 494 if (fontString.isEmpty()) 495 return false; 496 497 // Interpret fontString in the same way as the 'font' attribute of CanvasRenderingContext2D. 498 RefPtrWillBeRawPtr<MutableStylePropertySet> parsedStyle = MutableStylePropertySet::create(); 499 CSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, fontString, true, HTMLStandardMode, 0); 500 if (parsedStyle->isEmpty()) 501 return false; 502 503 String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont); 504 if (fontValue == "inherit" || fontValue == "initial") 505 return false; 506 507 RefPtr<RenderStyle> style = RenderStyle::create(); 508 509 FontFamily fontFamily; 510 fontFamily.setFamily(defaultFontFamily); 511 512 FontDescription defaultFontDescription; 513 defaultFontDescription.setFamily(fontFamily); 514 defaultFontDescription.setSpecifiedSize(defaultFontSize); 515 defaultFontDescription.setComputedSize(defaultFontSize); 516 517 style->setFontDescription(defaultFontDescription); 518 519 style->font().update(style->font().fontSelector()); 520 521 // Now map the font property longhands into the style. 522 CSSPropertyValue properties[] = { 523 CSSPropertyValue(CSSPropertyFontFamily, *parsedStyle), 524 CSSPropertyValue(CSSPropertyFontStretch, *parsedStyle), 525 CSSPropertyValue(CSSPropertyFontStyle, *parsedStyle), 526 CSSPropertyValue(CSSPropertyFontVariant, *parsedStyle), 527 CSSPropertyValue(CSSPropertyFontWeight, *parsedStyle), 528 CSSPropertyValue(CSSPropertyFontSize, *parsedStyle), 529 CSSPropertyValue(CSSPropertyLineHeight, *parsedStyle), 530 }; 531 StyleResolver& styleResolver = document()->ensureStyleResolver(); 532 styleResolver.applyPropertiesToStyle(properties, WTF_ARRAY_LENGTH(properties), style.get()); 533 534 font = style->font(); 535 font.update(document()->styleEngine()->fontSelector()); 536 return true; 537} 538 539void FontFaceSet::FontLoadHistogram::updateStatus(FontFace* fontFace) 540{ 541 if (m_status == Reported) 542 return; 543 if (fontFace->hadBlankText()) 544 m_status = HadBlankText; 545 else if (m_status == NoWebFonts) 546 m_status = DidNotHaveBlankText; 547} 548 549void FontFaceSet::FontLoadHistogram::record() 550{ 551 if (!m_recorded) { 552 m_recorded = true; 553 blink::Platform::current()->histogramCustomCounts("WebFont.WebFontsInPage", m_count, 1, 100, 50); 554 } 555 if (m_status == HadBlankText || m_status == DidNotHaveBlankText) { 556 blink::Platform::current()->histogramEnumeration("WebFont.HadBlankText", m_status == HadBlankText ? 1 : 0, 2); 557 m_status = Reported; 558 } 559} 560 561static const char* supplementName() 562{ 563 return "FontFaceSet"; 564} 565 566PassRefPtrWillBeRawPtr<FontFaceSet> FontFaceSet::from(Document& document) 567{ 568 RefPtrWillBeRawPtr<FontFaceSet> fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName())); 569 if (!fonts) { 570 fonts = FontFaceSet::create(document); 571 SupplementType::provideTo(document, supplementName(), fonts); 572 } 573 574 return fonts.release(); 575} 576 577void FontFaceSet::didLayout(Document& document) 578{ 579 if (FontFaceSet* fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName()))) 580 fonts->didLayout(); 581} 582 583#if ENABLE(OILPAN) 584void FontFaceSet::trace(Visitor* visitor) 585{ 586 visitor->trace(m_loadingFonts); 587 visitor->trace(m_loadedFonts); 588 visitor->trace(m_failedFonts); 589 visitor->trace(m_nonCSSConnectedFaces); 590 DocumentSupplement::trace(visitor); 591 EventTargetWithInlineData::trace(visitor); 592} 593#endif 594 595} // namespace blink 596