1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/common/font_cache_dispatcher_win.h"
6
7#include <map>
8#include <vector>
9
10#include "base/logging.h"
11#include "base/strings/string16.h"
12#include "content/common/child_process_messages.h"
13
14namespace content {
15namespace {
16typedef std::vector<base::string16> FontNameVector;
17typedef std::map<FontCacheDispatcher*, FontNameVector> DispatcherToFontNames;
18
19class FontCache {
20 public:
21  static FontCache* GetInstance() {
22    return Singleton<FontCache>::get();
23  }
24
25  void PreCacheFont(const LOGFONT& font, FontCacheDispatcher* dispatcher) {
26    typedef std::map<base::string16, FontCache::CacheElement> FontNameToElement;
27
28    base::AutoLock lock(mutex_);
29
30    // Fetch the font into memory.
31    // No matter the font is cached or not, we load it to avoid GDI swapping out
32    // that font file.
33    HDC hdc = GetDC(NULL);
34    HFONT font_handle = CreateFontIndirect(&font);
35    DCHECK(NULL != font_handle);
36
37    HGDIOBJ old_font = SelectObject(hdc, font_handle);
38    DCHECK(NULL != old_font);
39
40    TEXTMETRIC tm;
41    BOOL ret = GetTextMetrics(hdc, &tm);
42    DCHECK(ret);
43
44    base::string16 font_name = font.lfFaceName;
45    int ref_count_inc = 1;
46    FontNameVector::iterator it =
47        std::find(dispatcher_font_map_[dispatcher].begin(),
48                  dispatcher_font_map_[dispatcher].end(),
49                  font_name);
50    if (it == dispatcher_font_map_[dispatcher].end()) {
51      // Requested font is new to cache.
52      dispatcher_font_map_[dispatcher].push_back(font_name);
53    } else {
54      ref_count_inc = 0;
55    }
56
57    if (cache_[font_name].ref_count_ == 0) {  // Requested font is new to cache.
58      cache_[font_name].ref_count_ = 1;
59    } else {  // Requested font is already in cache, release old handles.
60      SelectObject(cache_[font_name].dc_, cache_[font_name].old_font_);
61      DeleteObject(cache_[font_name].font_);
62      ReleaseDC(NULL, cache_[font_name].dc_);
63    }
64    cache_[font_name].font_ = font_handle;
65    cache_[font_name].dc_ = hdc;
66    cache_[font_name].old_font_ = old_font;
67    cache_[font_name].ref_count_ += ref_count_inc;
68  }
69
70  void ReleaseCachedFonts(FontCacheDispatcher* dispatcher) {
71    typedef std::map<base::string16, FontCache::CacheElement> FontNameToElement;
72
73    base::AutoLock lock(mutex_);
74
75    DispatcherToFontNames::iterator it;
76    it = dispatcher_font_map_.find(dispatcher);
77    if (it == dispatcher_font_map_.end()) {
78      return;
79    }
80
81    for (FontNameVector::iterator i = it->second.begin(), e = it->second.end();
82                                  i != e; ++i) {
83      FontNameToElement::iterator element;
84      element = cache_.find(*i);
85      if (element != cache_.end()) {
86        --((*element).second.ref_count_);
87      }
88    }
89
90    dispatcher_font_map_.erase(it);
91    for (FontNameToElement::iterator i = cache_.begin(); i != cache_.end(); ) {
92      if (i->second.ref_count_ == 0) {
93        cache_.erase(i++);
94      } else {
95        ++i;
96      }
97    }
98  }
99
100 private:
101  struct CacheElement {
102    CacheElement()
103        : font_(NULL), old_font_(NULL), dc_(NULL), ref_count_(0) {
104    }
105
106    ~CacheElement() {
107      if (font_) {
108        if (dc_ && old_font_) {
109          SelectObject(dc_, old_font_);
110        }
111        DeleteObject(font_);
112      }
113      if (dc_) {
114        ReleaseDC(NULL, dc_);
115      }
116    }
117
118    HFONT font_;
119    HGDIOBJ old_font_;
120    HDC dc_;
121    int ref_count_;
122  };
123  friend struct DefaultSingletonTraits<FontCache>;
124
125  FontCache() {
126  }
127
128  std::map<base::string16, CacheElement> cache_;
129  DispatcherToFontNames dispatcher_font_map_;
130  base::Lock mutex_;
131
132  DISALLOW_COPY_AND_ASSIGN(FontCache);
133};
134
135}
136
137FontCacheDispatcher::FontCacheDispatcher()
138    : channel_(NULL) {
139}
140
141FontCacheDispatcher::~FontCacheDispatcher() {
142}
143
144void FontCacheDispatcher::OnFilterAdded(IPC::Channel* channel) {
145  channel_ = channel;
146}
147
148bool FontCacheDispatcher::OnMessageReceived(const IPC::Message& message) {
149  bool handled = true;
150  IPC_BEGIN_MESSAGE_MAP(FontCacheDispatcher, message)
151    IPC_MESSAGE_HANDLER(ChildProcessHostMsg_PreCacheFont, OnPreCacheFont)
152    IPC_MESSAGE_HANDLER(ChildProcessHostMsg_ReleaseCachedFonts,
153                        OnReleaseCachedFonts)
154    IPC_MESSAGE_UNHANDLED(handled = false)
155  IPC_END_MESSAGE_MAP()
156  return handled;
157}
158
159void FontCacheDispatcher::OnChannelClosing() {
160  channel_ = NULL;
161}
162
163bool FontCacheDispatcher::Send(IPC::Message* message) {
164  if (channel_)
165    return channel_->Send(message);
166
167  delete message;
168  return false;
169}
170
171void FontCacheDispatcher::OnPreCacheFont(const LOGFONT& font) {
172  // If a child process is running in a sandbox, GetTextMetrics()
173  // can sometimes fail. If a font has not been loaded
174  // previously, GetTextMetrics() will try to load the font
175  // from the font file. However, the sandboxed process does
176  // not have permissions to access any font files and
177  // the call fails. So we make the browser pre-load the
178  // font for us by using a dummy call to GetTextMetrics of
179  // the same font.
180  // This means the browser process just loads the font into memory so that
181  // when GDI attempt to query that font info in child process, it does not
182  // need to load that file, hence no permission issues there.  Therefore,
183  // when a font is asked to be cached, we always recreates the font object
184  // to avoid the case that an in-cache font is swapped out by GDI.
185  FontCache::GetInstance()->PreCacheFont(font, this);
186}
187
188void FontCacheDispatcher::OnReleaseCachedFonts() {
189  // Release cached fonts that requested from a pid by decrementing the ref
190  // count.  When ref count is zero, the handles are released.
191  FontCache::GetInstance()->ReleaseCachedFonts(this);
192}
193
194}  // namespace content
195