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 "RuntimeEnabledFeatures.h" 30 #include "bindings/v8/Dictionary.h" 31 #include "bindings/v8/ScriptPromiseResolver.h" 32 #include "bindings/v8/ScriptScope.h" 33 #include "bindings/v8/ScriptState.h" 34 #include "core/css/CSSFontFaceLoadEvent.h" 35 #include "core/css/CSSFontFaceSource.h" 36 #include "core/css/CSSFontSelector.h" 37 #include "core/css/CSSParser.h" 38 #include "core/css/CSSSegmentedFontFace.h" 39 #include "core/css/StylePropertySet.h" 40 #include "core/css/resolver/StyleResolver.h" 41 #include "core/dom/Document.h" 42 #include "core/frame/Frame.h" 43 #include "core/frame/FrameView.h" 44 #include "public/platform/Platform.h" 45 46 namespace WebCore { 47 48 static const int defaultFontSize = 10; 49 static const char defaultFontFamily[] = "sans-serif"; 50 51 class LoadFontPromiseResolver : public CSSSegmentedFontFace::LoadFontCallback { 52 public: 53 static PassRefPtr<LoadFontPromiseResolver> create(const FontFamily& family, ScriptPromise promise, ExecutionContext* context) 54 { 55 int numFamilies = 0; 56 for (const FontFamily* f = &family; f; f = f->next()) 57 numFamilies++; 58 return adoptRef<LoadFontPromiseResolver>(new LoadFontPromiseResolver(numFamilies, promise, context)); 59 } 60 61 virtual void notifyLoaded(CSSSegmentedFontFace*) OVERRIDE; 62 virtual void notifyError(CSSSegmentedFontFace*) OVERRIDE; 63 void loaded(Document*); 64 void error(Document*); 65 66 private: 67 LoadFontPromiseResolver(int numLoading, ScriptPromise promise, ExecutionContext* context) 68 : m_numLoading(numLoading) 69 , m_errorOccured(false) 70 , m_scriptState(ScriptState::current()) 71 , m_resolver(ScriptPromiseResolver::create(promise, context)) 72 { } 73 74 int m_numLoading; 75 bool m_errorOccured; 76 ScriptState* m_scriptState; 77 RefPtr<ScriptPromiseResolver> m_resolver; 78 }; 79 80 void LoadFontPromiseResolver::loaded(Document* document) 81 { 82 m_numLoading--; 83 if (m_numLoading || !document) 84 return; 85 86 ScriptScope scope(m_scriptState); 87 if (m_errorOccured) 88 m_resolver->reject(ScriptValue::createNull()); 89 else 90 m_resolver->resolve(ScriptValue::createNull()); 91 } 92 93 void LoadFontPromiseResolver::error(Document* document) 94 { 95 m_errorOccured = true; 96 loaded(document); 97 } 98 99 void LoadFontPromiseResolver::notifyLoaded(CSSSegmentedFontFace* face) 100 { 101 loaded(face->fontSelector()->document()); 102 } 103 104 void LoadFontPromiseResolver::notifyError(CSSSegmentedFontFace* face) 105 { 106 error(face->fontSelector()->document()); 107 } 108 109 class FontsReadyPromiseResolver { 110 public: 111 static PassOwnPtr<FontsReadyPromiseResolver> create(ScriptPromise promise, ExecutionContext* context) 112 { 113 return adoptPtr(new FontsReadyPromiseResolver(promise, context)); 114 } 115 116 void resolve(PassRefPtr<FontFaceSet> fontFaceSet) 117 { 118 ScriptScope scope(m_scriptState); 119 m_resolver->resolve(fontFaceSet); 120 } 121 122 private: 123 FontsReadyPromiseResolver(ScriptPromise promise, ExecutionContext* context) 124 : m_scriptState(ScriptState::current()) 125 , m_resolver(ScriptPromiseResolver::create(promise, context)) 126 { } 127 ScriptState* m_scriptState; 128 RefPtr<ScriptPromiseResolver> m_resolver; 129 }; 130 131 FontFaceSet::FontFaceSet(Document* document) 132 : ActiveDOMObject(document) 133 , m_loadingCount(0) 134 , m_shouldFireLoadingEvent(false) 135 , m_asyncRunner(this, &FontFaceSet::handlePendingEventsAndPromises) 136 { 137 suspendIfNeeded(); 138 } 139 140 FontFaceSet::~FontFaceSet() 141 { 142 } 143 144 Document* FontFaceSet::document() const 145 { 146 return toDocument(executionContext()); 147 } 148 149 const AtomicString& FontFaceSet::interfaceName() const 150 { 151 return EventTargetNames::FontFaceSet; 152 } 153 154 ExecutionContext* FontFaceSet::executionContext() const 155 { 156 return ActiveDOMObject::executionContext(); 157 } 158 159 AtomicString FontFaceSet::status() const 160 { 161 DEFINE_STATIC_LOCAL(AtomicString, loading, ("loading", AtomicString::ConstructFromLiteral)); 162 DEFINE_STATIC_LOCAL(AtomicString, loaded, ("loaded", AtomicString::ConstructFromLiteral)); 163 return (m_loadingCount > 0 || hasLoadedFonts()) ? loading : loaded; 164 } 165 166 void FontFaceSet::handlePendingEventsAndPromisesSoon() 167 { 168 // setPendingActivity() is unnecessary because m_asyncRunner will be 169 // automatically stopped on destruction. 170 m_asyncRunner.runAsync(); 171 } 172 173 void FontFaceSet::didLayout() 174 { 175 if (document()->frame()->isMainFrame()) 176 m_histogram.record(); 177 if (!RuntimeEnabledFeatures::fontLoadEventsEnabled()) 178 return; 179 if (m_loadingCount || (!hasLoadedFonts() && m_readyResolvers.isEmpty())) 180 return; 181 handlePendingEventsAndPromisesSoon(); 182 } 183 184 void FontFaceSet::handlePendingEventsAndPromises() 185 { 186 fireLoadingEvent(); 187 fireDoneEventIfPossible(); 188 } 189 190 void FontFaceSet::fireLoadingEvent() 191 { 192 if (m_shouldFireLoadingEvent) { 193 m_shouldFireLoadingEvent = false; 194 dispatchEvent(CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loading)); 195 } 196 } 197 198 void FontFaceSet::suspend() 199 { 200 m_asyncRunner.suspend(); 201 } 202 203 void FontFaceSet::resume() 204 { 205 m_asyncRunner.resume(); 206 } 207 208 void FontFaceSet::stop() 209 { 210 m_asyncRunner.stop(); 211 } 212 213 void FontFaceSet::beginFontLoading(FontFace* fontFace) 214 { 215 m_histogram.incrementCount(); 216 if (!RuntimeEnabledFeatures::fontLoadEventsEnabled()) 217 return; 218 219 if (!m_loadingCount && !hasLoadedFonts()) { 220 ASSERT(!m_shouldFireLoadingEvent); 221 m_shouldFireLoadingEvent = true; 222 handlePendingEventsAndPromisesSoon(); 223 } 224 ++m_loadingCount; 225 } 226 227 void FontFaceSet::fontLoaded(FontFace* fontFace) 228 { 229 if (!RuntimeEnabledFeatures::fontLoadEventsEnabled()) 230 return; 231 m_loadedFonts.append(fontFace); 232 queueDoneEvent(fontFace); 233 } 234 235 void FontFaceSet::loadError(FontFace* fontFace) 236 { 237 if (!RuntimeEnabledFeatures::fontLoadEventsEnabled()) 238 return; 239 m_failedFonts.append(fontFace); 240 queueDoneEvent(fontFace); 241 } 242 243 void FontFaceSet::queueDoneEvent(FontFace* fontFace) 244 { 245 ASSERT(m_loadingCount > 0); 246 --m_loadingCount; 247 if (!m_loadingCount) 248 handlePendingEventsAndPromisesSoon(); 249 } 250 251 ScriptPromise FontFaceSet::ready() 252 { 253 ScriptPromise promise = ScriptPromise::createPending(executionContext()); 254 OwnPtr<FontsReadyPromiseResolver> resolver = FontsReadyPromiseResolver::create(promise, executionContext()); 255 m_readyResolvers.append(resolver.release()); 256 handlePendingEventsAndPromisesSoon(); 257 return promise; 258 } 259 260 void FontFaceSet::fireDoneEventIfPossible() 261 { 262 if (m_shouldFireLoadingEvent) 263 return; 264 if (m_loadingCount || (!hasLoadedFonts() && m_readyResolvers.isEmpty())) 265 return; 266 267 // If the layout was invalidated in between when we thought layout 268 // was updated and when we're ready to fire the event, just wait 269 // until after the next layout before firing events. 270 Document* d = document(); 271 if (!d->view() || d->view()->needsLayout()) 272 return; 273 274 if (hasLoadedFonts()) { 275 RefPtr<CSSFontFaceLoadEvent> doneEvent; 276 RefPtr<CSSFontFaceLoadEvent> errorEvent; 277 doneEvent = CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loadingdone, m_loadedFonts); 278 m_loadedFonts.clear(); 279 if (!m_failedFonts.isEmpty()) { 280 errorEvent = CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loadingerror, m_failedFonts); 281 m_failedFonts.clear(); 282 } 283 dispatchEvent(doneEvent); 284 if (errorEvent) 285 dispatchEvent(errorEvent); 286 } 287 288 if (!m_readyResolvers.isEmpty()) { 289 Vector<OwnPtr<FontsReadyPromiseResolver> > resolvers; 290 m_readyResolvers.swap(resolvers); 291 for (size_t index = 0; index < resolvers.size(); ++index) 292 resolvers[index]->resolve(this); 293 } 294 } 295 296 static const String& nullToSpace(const String& s) 297 { 298 DEFINE_STATIC_LOCAL(String, space, (" ")); 299 return s.isNull() ? space : s; 300 } 301 302 Vector<RefPtr<FontFace> > FontFaceSet::match(const String& fontString, const String& text, ExceptionState& exceptionState) 303 { 304 Vector<RefPtr<FontFace> > matchedFonts; 305 306 Font font; 307 if (!resolveFontStyle(fontString, font)) { 308 exceptionState.throwUninformativeAndGenericDOMException(SyntaxError); 309 return matchedFonts; 310 } 311 312 for (const FontFamily* f = &font.family(); f; f = f->next()) { 313 CSSSegmentedFontFace* face = document()->styleEngine()->fontSelector()->getFontFace(font.fontDescription(), f->family()); 314 if (face) 315 matchedFonts.append(face->fontFaces(nullToSpace(text))); 316 } 317 return matchedFonts; 318 } 319 320 ScriptPromise FontFaceSet::load(const String& fontString, const String& text, ExceptionState& exceptionState) 321 { 322 Font font; 323 if (!resolveFontStyle(fontString, font)) { 324 exceptionState.throwUninformativeAndGenericDOMException(SyntaxError); 325 return ScriptPromise(); 326 } 327 328 Document* d = document(); 329 ScriptPromise promise = ScriptPromise::createPending(executionContext()); 330 RefPtr<LoadFontPromiseResolver> resolver = LoadFontPromiseResolver::create(font.family(), promise, executionContext()); 331 for (const FontFamily* f = &font.family(); f; f = f->next()) { 332 CSSSegmentedFontFace* face = d->styleEngine()->fontSelector()->getFontFace(font.fontDescription(), f->family()); 333 if (!face) { 334 resolver->error(d); 335 continue; 336 } 337 face->loadFont(font.fontDescription(), nullToSpace(text), resolver); 338 } 339 return promise; 340 } 341 342 bool FontFaceSet::check(const String& fontString, const String& text, ExceptionState& exceptionState) 343 { 344 Font font; 345 if (!resolveFontStyle(fontString, font)) { 346 exceptionState.throwUninformativeAndGenericDOMException(SyntaxError); 347 return false; 348 } 349 350 for (const FontFamily* f = &font.family(); f; f = f->next()) { 351 CSSSegmentedFontFace* face = document()->styleEngine()->fontSelector()->getFontFace(font.fontDescription(), f->family()); 352 if (!face || !face->checkFont(nullToSpace(text))) 353 return false; 354 } 355 return true; 356 } 357 358 bool FontFaceSet::resolveFontStyle(const String& fontString, Font& font) 359 { 360 if (fontString.isEmpty()) 361 return false; 362 363 // Interpret fontString in the same way as the 'font' attribute of CanvasRenderingContext2D. 364 RefPtr<MutableStylePropertySet> parsedStyle = MutableStylePropertySet::create(); 365 CSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, fontString, true, HTMLStandardMode, 0); 366 if (parsedStyle->isEmpty()) 367 return false; 368 369 String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont); 370 if (fontValue == "inherit" || fontValue == "initial") 371 return false; 372 373 RefPtr<RenderStyle> style = RenderStyle::create(); 374 375 FontFamily fontFamily; 376 fontFamily.setFamily(defaultFontFamily); 377 378 FontDescription defaultFontDescription; 379 defaultFontDescription.setFamily(fontFamily); 380 defaultFontDescription.setSpecifiedSize(defaultFontSize); 381 defaultFontDescription.setComputedSize(defaultFontSize); 382 383 style->setFontDescription(defaultFontDescription); 384 385 style->font().update(style->font().fontSelector()); 386 387 // Now map the font property longhands into the style. 388 CSSPropertyValue properties[] = { 389 CSSPropertyValue(CSSPropertyFontFamily, *parsedStyle), 390 CSSPropertyValue(CSSPropertyFontStyle, *parsedStyle), 391 CSSPropertyValue(CSSPropertyFontVariant, *parsedStyle), 392 CSSPropertyValue(CSSPropertyFontWeight, *parsedStyle), 393 CSSPropertyValue(CSSPropertyFontSize, *parsedStyle), 394 CSSPropertyValue(CSSPropertyLineHeight, *parsedStyle), 395 }; 396 StyleResolver& styleResolver = document()->ensureStyleResolver(); 397 styleResolver.applyPropertiesToStyle(properties, WTF_ARRAY_LENGTH(properties), style.get()); 398 399 font = style->font(); 400 font.update(document()->styleEngine()->fontSelector()); 401 return true; 402 } 403 404 void FontFaceSet::FontLoadHistogram::record() 405 { 406 if (m_recorded) 407 return; 408 m_recorded = true; 409 blink::Platform::current()->histogramCustomCounts("WebFont.WebFontsInPage", m_count, 1, 100, 50); 410 } 411 412 static const char* supplementName() 413 { 414 return "FontFaceSet"; 415 } 416 417 PassRefPtr<FontFaceSet> FontFaceSet::from(Document* document) 418 { 419 RefPtr<FontFaceSet> fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName())); 420 if (!fonts) { 421 fonts = FontFaceSet::create(document); 422 SupplementType::provideTo(document, supplementName(), fonts); 423 } 424 425 return fonts.release(); 426 } 427 428 void FontFaceSet::didLayout(Document* document) 429 { 430 if (FontFaceSet* fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName()))) 431 fonts->didLayout(); 432 } 433 434 435 } // namespace WebCore 436