Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / css / FontFaceSet.cpp
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/ScriptPromiseResolverWithContext.h"
32 #include "bindings/v8/ScriptState.h"
33 #include "core/css/CSSFontFaceLoadEvent.h"
34 #include "core/css/CSSFontSelector.h"
35 #include "core/css/parser/BisonCSSParser.h"
36 #include "core/css/CSSSegmentedFontFace.h"
37 #include "core/css/FontFaceCache.h"
38 #include "core/css/StylePropertySet.h"
39 #include "core/css/resolver/StyleResolver.h"
40 #include "core/dom/Document.h"
41 #include "core/dom/StyleEngine.h"
42 #include "core/frame/FrameView.h"
43 #include "core/frame/LocalFrame.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 FINAL : public FontFace::LoadFontCallback {
52 public:
53     static PassRefPtrWillBeRawPtr<LoadFontPromiseResolver> create(FontFaceArray faces, ExecutionContext* context)
54     {
55         return adoptRefWillBeNoop(new LoadFontPromiseResolver(faces, context));
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
66 private:
67     LoadFontPromiseResolver(FontFaceArray faces, ExecutionContext* context)
68         : m_numLoading(faces.size())
69         , m_errorOccured(false)
70         , m_resolver(ScriptPromiseResolverWithContext::create(ScriptState::current(toIsolate(context))))
71     {
72         m_fontFaces.swap(faces);
73     }
74
75     WillBeHeapVector<RefPtrWillBeMember<FontFace> > m_fontFaces;
76     int m_numLoading;
77     bool m_errorOccured;
78     RefPtr<ScriptPromiseResolverWithContext> m_resolver;
79 };
80
81 void 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
92 void 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
101 void 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
110 void LoadFontPromiseResolver::trace(Visitor* visitor)
111 {
112     visitor->trace(m_fontFaces);
113     LoadFontCallback::trace(visitor);
114 }
115
116 class FontsReadyPromiseResolver {
117 public:
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
130 private:
131     explicit FontsReadyPromiseResolver(ScriptState* scriptState)
132         : m_resolver(ScriptPromiseResolverWithContext::create(scriptState))
133     {
134     }
135
136     RefPtr<ScriptPromiseResolverWithContext> m_resolver;
137 };
138
139 FontFaceSet::FontFaceSet(Document& document)
140     : ActiveDOMObject(&document)
141     , m_shouldFireLoadingEvent(false)
142     , m_asyncRunner(this, &FontFaceSet::handlePendingEventsAndPromises)
143 {
144     suspendIfNeeded();
145 }
146
147 FontFaceSet::~FontFaceSet()
148 {
149 }
150
151 Document* FontFaceSet::document() const
152 {
153     return toDocument(executionContext());
154 }
155
156 bool FontFaceSet::inActiveDocumentContext() const
157 {
158     ExecutionContext* context = executionContext();
159     return context && toDocument(context)->isActive();
160 }
161
162 void FontFaceSet::addFontFacesToFontFaceCache(FontFaceCache* fontFaceCache, CSSFontSelector* fontSelector)
163 {
164     for (ListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it)
165         fontFaceCache->addFontFace(fontSelector, *it, false);
166 }
167
168 const AtomicString& FontFaceSet::interfaceName() const
169 {
170     return EventTargetNames::FontFaceSet;
171 }
172
173 ExecutionContext* FontFaceSet::executionContext() const
174 {
175     return ActiveDOMObject::executionContext();
176 }
177
178 AtomicString 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
185 void FontFaceSet::handlePendingEventsAndPromisesSoon()
186 {
187     // setPendingActivity() is unnecessary because m_asyncRunner will be
188     // automatically stopped on destruction.
189     m_asyncRunner.runAsync();
190 }
191
192 void FontFaceSet::didLayout()
193 {
194     if (document()->frame()->isMainFrame() && m_loadingFonts.isEmpty())
195         m_histogram.record();
196     if (!RuntimeEnabledFeatures::fontLoadEventsEnabled())
197         return;
198     if (!m_loadingFonts.isEmpty() || (!hasLoadedFonts() && m_readyResolvers.isEmpty()))
199         return;
200     handlePendingEventsAndPromisesSoon();
201 }
202
203 void FontFaceSet::handlePendingEventsAndPromises()
204 {
205     fireLoadingEvent();
206     fireDoneEventIfPossible();
207 }
208
209 void FontFaceSet::fireLoadingEvent()
210 {
211     if (m_shouldFireLoadingEvent) {
212         m_shouldFireLoadingEvent = false;
213         dispatchEvent(CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loading));
214     }
215 }
216
217 void FontFaceSet::suspend()
218 {
219     m_asyncRunner.suspend();
220 }
221
222 void FontFaceSet::resume()
223 {
224     m_asyncRunner.resume();
225 }
226
227 void FontFaceSet::stop()
228 {
229     m_asyncRunner.stop();
230 }
231
232 void FontFaceSet::beginFontLoading(FontFace* fontFace)
233 {
234     m_histogram.incrementCount();
235     addToLoadingFonts(fontFace);
236 }
237
238 void FontFaceSet::fontLoaded(FontFace* fontFace)
239 {
240     m_histogram.updateStatus(fontFace);
241     if (RuntimeEnabledFeatures::fontLoadEventsEnabled())
242         m_loadedFonts.append(fontFace);
243     removeFromLoadingFonts(fontFace);
244 }
245
246 void FontFaceSet::loadError(FontFace* fontFace)
247 {
248     m_histogram.updateStatus(fontFace);
249     if (RuntimeEnabledFeatures::fontLoadEventsEnabled())
250         m_failedFonts.append(fontFace);
251     removeFromLoadingFonts(fontFace);
252 }
253
254 void FontFaceSet::addToLoadingFonts(PassRefPtrWillBeRawPtr<FontFace> fontFace)
255 {
256     if (RuntimeEnabledFeatures::fontLoadEventsEnabled() && m_loadingFonts.isEmpty() && !hasLoadedFonts()) {
257         m_shouldFireLoadingEvent = true;
258         handlePendingEventsAndPromisesSoon();
259     }
260     m_loadingFonts.add(fontFace);
261 }
262
263 void FontFaceSet::removeFromLoadingFonts(PassRefPtrWillBeRawPtr<FontFace> fontFace)
264 {
265     m_loadingFonts.remove(fontFace);
266     if (RuntimeEnabledFeatures::fontLoadEventsEnabled() && m_loadingFonts.isEmpty())
267         handlePendingEventsAndPromisesSoon();
268 }
269
270 ScriptPromise FontFaceSet::ready(ScriptState* scriptState)
271 {
272     if (!inActiveDocumentContext())
273         return ScriptPromise();
274     OwnPtr<FontsReadyPromiseResolver> resolver = FontsReadyPromiseResolver::create(scriptState);
275     ScriptPromise promise = resolver->promise();
276     m_readyResolvers.append(resolver.release());
277     handlePendingEventsAndPromisesSoon();
278     return promise;
279 }
280
281 void FontFaceSet::add(FontFace* fontFace, ExceptionState& exceptionState)
282 {
283     if (!inActiveDocumentContext())
284         return;
285     if (!fontFace) {
286         exceptionState.throwTypeError("The argument is not a FontFace.");
287         return;
288     }
289     if (m_nonCSSConnectedFaces.contains(fontFace))
290         return;
291     if (isCSSConnectedFontFace(fontFace)) {
292         exceptionState.throwDOMException(InvalidModificationError, "Cannot add a CSS-connected FontFace.");
293         return;
294     }
295     CSSFontSelector* fontSelector = document()->styleEngine()->fontSelector();
296     m_nonCSSConnectedFaces.add(fontFace);
297     fontSelector->fontFaceCache()->addFontFace(fontSelector, fontFace, false);
298     if (fontFace->loadStatus() == FontFace::Loading)
299         addToLoadingFonts(fontFace);
300 }
301
302 void FontFaceSet::clear()
303 {
304     if (!inActiveDocumentContext())
305         return;
306     FontFaceCache* fontFaceCache = document()->styleEngine()->fontSelector()->fontFaceCache();
307     for (ListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.begin(); it != m_nonCSSConnectedFaces.end(); ++it) {
308         fontFaceCache->removeFontFace(it->get(), false);
309         if ((*it)->loadStatus() == FontFace::Loading)
310             removeFromLoadingFonts(*it);
311     }
312     m_nonCSSConnectedFaces.clear();
313 }
314
315 bool FontFaceSet::remove(FontFace* fontFace, ExceptionState& exceptionState)
316 {
317     if (!inActiveDocumentContext())
318         return false;
319     if (!fontFace) {
320         exceptionState.throwTypeError("The argument is not a FontFace.");
321         return false;
322     }
323     ListHashSet<RefPtrWillBeMember<FontFace> >::iterator it = m_nonCSSConnectedFaces.find(fontFace);
324     if (it != m_nonCSSConnectedFaces.end()) {
325         m_nonCSSConnectedFaces.remove(it);
326         document()->styleEngine()->fontSelector()->fontFaceCache()->removeFontFace(fontFace, false);
327         if (fontFace->loadStatus() == FontFace::Loading)
328             removeFromLoadingFonts(fontFace);
329         return true;
330     }
331     if (isCSSConnectedFontFace(fontFace))
332         exceptionState.throwDOMException(InvalidModificationError, "Cannot delete a CSS-connected FontFace.");
333     return false;
334 }
335
336 bool 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
347 const ListHashSet<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
354 bool FontFaceSet::isCSSConnectedFontFace(FontFace* fontFace) const
355 {
356     return cssConnectedFontFaceList().contains(fontFace);
357 }
358
359 void FontFaceSet::forEach(PassOwnPtr<FontFaceSetForEachCallback> callback, ScriptValue& thisArg) const
360 {
361     forEachInternal(callback, &thisArg);
362 }
363
364 void FontFaceSet::forEach(PassOwnPtr<FontFaceSetForEachCallback> callback) const
365 {
366     forEachInternal(callback, 0);
367 }
368
369 void FontFaceSet::forEachInternal(PassOwnPtr<FontFaceSetForEachCallback> callback, ScriptValue* thisArg) const
370 {
371     if (!inActiveDocumentContext())
372         return;
373     const ListHashSet<RefPtrWillBeMember<FontFace> >& cssConnectedFaces = cssConnectedFontFaceList();
374     WillBeHeapVector<RefPtrWillBeMember<FontFace> > fontFaces;
375     fontFaces.reserveInitialCapacity(cssConnectedFaces.size() + m_nonCSSConnectedFaces.size());
376     for (ListHashSet<RefPtrWillBeMember<FontFace> >::const_iterator it = cssConnectedFaces.begin(); it != cssConnectedFaces.end(); ++it)
377         fontFaces.append(*it);
378     for (ListHashSet<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
390 unsigned long FontFaceSet::size() const
391 {
392     if (!inActiveDocumentContext())
393         return m_nonCSSConnectedFaces.size();
394     return cssConnectedFontFaceList().size() + m_nonCSSConnectedFaces.size();
395 }
396
397 void 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<CSSFontFaceLoadEvent> doneEvent = nullptr;
413         RefPtrWillBeRawPtr<CSSFontFaceLoadEvent> errorEvent = nullptr;
414         doneEvent = CSSFontFaceLoadEvent::createForFontFaces(EventTypeNames::loadingdone, m_loadedFonts);
415         m_loadedFonts.clear();
416         if (!m_failedFonts.isEmpty()) {
417             errorEvent = CSSFontFaceLoadEvent::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
433 static const String& nullToSpace(const String& s)
434 {
435     DEFINE_STATIC_LOCAL(String, space, (" "));
436     return s.isNull() ? space : s;
437 }
438
439 ScriptPromise FontFaceSet::load(ScriptState* scriptState, const String& fontString, const String& text)
440 {
441     if (!inActiveDocumentContext())
442         return ScriptPromise();
443
444     Font font;
445     if (!resolveFontStyle(fontString, font)) {
446         RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState);
447         ScriptPromise promise = resolver->promise();
448         resolver->reject(DOMError::create(SyntaxError, "Could not resolve '" + fontString + "' as a font."));
449         return promise;
450     }
451
452     FontFaceCache* fontFaceCache = document()->styleEngine()->fontSelector()->fontFaceCache();
453     FontFaceArray faces;
454     for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) {
455         CSSSegmentedFontFace* segmentedFontFace = fontFaceCache->get(font.fontDescription(), f->family());
456         if (segmentedFontFace)
457             segmentedFontFace->match(nullToSpace(text), faces);
458     }
459
460     RefPtrWillBeRawPtr<LoadFontPromiseResolver> resolver = LoadFontPromiseResolver::create(faces, executionContext());
461     ScriptPromise promise = resolver->promise();
462     resolver->loadFonts(executionContext()); // After this, resolver->promise() may return null.
463     return promise;
464 }
465
466 bool FontFaceSet::check(const String& fontString, const String& text, ExceptionState& exceptionState)
467 {
468     if (!inActiveDocumentContext())
469         return false;
470
471     Font font;
472     if (!resolveFontStyle(fontString, font)) {
473         exceptionState.throwDOMException(SyntaxError, "Could not resolve '" + fontString + "' as a font.");
474         return false;
475     }
476
477     FontFaceCache* fontFaceCache = document()->styleEngine()->fontSelector()->fontFaceCache();
478     for (const FontFamily* f = &font.fontDescription().family(); f; f = f->next()) {
479         CSSSegmentedFontFace* face = fontFaceCache->get(font.fontDescription(), f->family());
480         if (face && !face->checkFont(nullToSpace(text)))
481             return false;
482     }
483     return true;
484 }
485
486 bool FontFaceSet::resolveFontStyle(const String& fontString, Font& font)
487 {
488     if (fontString.isEmpty())
489         return false;
490
491     // Interpret fontString in the same way as the 'font' attribute of CanvasRenderingContext2D.
492     RefPtrWillBeRawPtr<MutableStylePropertySet> parsedStyle = MutableStylePropertySet::create();
493     BisonCSSParser::parseValue(parsedStyle.get(), CSSPropertyFont, fontString, true, HTMLStandardMode, 0);
494     if (parsedStyle->isEmpty())
495         return false;
496
497     String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont);
498     if (fontValue == "inherit" || fontValue == "initial")
499         return false;
500
501     RefPtr<RenderStyle> style = RenderStyle::create();
502
503     FontFamily fontFamily;
504     fontFamily.setFamily(defaultFontFamily);
505
506     FontDescription defaultFontDescription;
507     defaultFontDescription.setFamily(fontFamily);
508     defaultFontDescription.setSpecifiedSize(defaultFontSize);
509     defaultFontDescription.setComputedSize(defaultFontSize);
510
511     style->setFontDescription(defaultFontDescription);
512
513     style->font().update(style->font().fontSelector());
514
515     // Now map the font property longhands into the style.
516     CSSPropertyValue properties[] = {
517         CSSPropertyValue(CSSPropertyFontFamily, *parsedStyle),
518         CSSPropertyValue(CSSPropertyFontStyle, *parsedStyle),
519         CSSPropertyValue(CSSPropertyFontVariant, *parsedStyle),
520         CSSPropertyValue(CSSPropertyFontWeight, *parsedStyle),
521         CSSPropertyValue(CSSPropertyFontSize, *parsedStyle),
522         CSSPropertyValue(CSSPropertyLineHeight, *parsedStyle),
523     };
524     StyleResolver& styleResolver = document()->ensureStyleResolver();
525     styleResolver.applyPropertiesToStyle(properties, WTF_ARRAY_LENGTH(properties), style.get());
526
527     font = style->font();
528     font.update(document()->styleEngine()->fontSelector());
529     return true;
530 }
531
532 void FontFaceSet::FontLoadHistogram::updateStatus(FontFace* fontFace)
533 {
534     if (m_status == Reported)
535         return;
536     if (fontFace->hadBlankText())
537         m_status = HadBlankText;
538     else if (m_status == NoWebFonts)
539         m_status = DidNotHaveBlankText;
540 }
541
542 void FontFaceSet::FontLoadHistogram::record()
543 {
544     if (!m_recorded) {
545         m_recorded = true;
546         blink::Platform::current()->histogramCustomCounts("WebFont.WebFontsInPage", m_count, 1, 100, 50);
547     }
548     if (m_status == HadBlankText || m_status == DidNotHaveBlankText) {
549         blink::Platform::current()->histogramEnumeration("WebFont.HadBlankText", m_status == HadBlankText ? 1 : 0, 2);
550         m_status = Reported;
551     }
552 }
553
554 static const char* supplementName()
555 {
556     return "FontFaceSet";
557 }
558
559 PassRefPtrWillBeRawPtr<FontFaceSet> FontFaceSet::from(Document& document)
560 {
561     RefPtrWillBeRawPtr<FontFaceSet> fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName()));
562     if (!fonts) {
563         fonts = FontFaceSet::create(document);
564         SupplementType::provideTo(document, supplementName(), fonts);
565     }
566
567     return fonts.release();
568 }
569
570 void FontFaceSet::didLayout(Document& document)
571 {
572     if (FontFaceSet* fonts = static_cast<FontFaceSet*>(SupplementType::from(document, supplementName())))
573         fonts->didLayout();
574 }
575
576 #if ENABLE(OILPAN)
577 void FontFaceSet::trace(Visitor* visitor)
578 {
579     visitor->trace(m_loadingFonts);
580     visitor->trace(m_loadedFonts);
581     visitor->trace(m_failedFonts);
582     visitor->trace(m_nonCSSConnectedFaces);
583     DocumentSupplement::trace(visitor);
584 }
585 #endif
586
587 } // namespace WebCore