1 /* 2 * Copyright (C) 2010 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 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "AutoFillPopupMenuClient.h" 33 34 #include "CSSStyleSelector.h" 35 #include "CSSValueKeywords.h" 36 #include "Chrome.h" 37 #include "FrameView.h" 38 #include "HTMLInputElement.h" 39 #include "RenderTheme.h" 40 #include "WebAutoFillClient.h" 41 #include "WebNode.h" 42 #include "WebString.h" 43 #include "WebVector.h" 44 #include "WebViewClient.h" 45 #include "WebViewImpl.h" 46 47 using namespace WebCore; 48 49 namespace WebKit { 50 51 AutoFillPopupMenuClient::AutoFillPopupMenuClient() 52 : m_separatorIndex(-1) 53 , m_selectedIndex(-1) 54 , m_textField(0) 55 { 56 } 57 58 AutoFillPopupMenuClient::~AutoFillPopupMenuClient() 59 { 60 } 61 62 unsigned AutoFillPopupMenuClient::getSuggestionsCount() const 63 { 64 return m_names.size() + ((m_separatorIndex == -1) ? 0 : 1); 65 } 66 67 WebString AutoFillPopupMenuClient::getSuggestion(unsigned listIndex) const 68 { 69 int index = convertListIndexToInternalIndex(listIndex); 70 if (index == -1) 71 return WebString(); 72 73 ASSERT(index >= 0 && static_cast<size_t>(index) < m_names.size()); 74 return m_names[index]; 75 } 76 77 WebString AutoFillPopupMenuClient::getLabel(unsigned listIndex) const 78 { 79 int index = convertListIndexToInternalIndex(listIndex); 80 if (index == -1) 81 return WebString(); 82 83 ASSERT(index >= 0 && static_cast<size_t>(index) < m_labels.size()); 84 return m_labels[index]; 85 } 86 87 WebString AutoFillPopupMenuClient::getIcon(unsigned listIndex) const 88 { 89 int index = convertListIndexToInternalIndex(listIndex); 90 if (index == -1) 91 return WebString(); 92 93 ASSERT(index >= 0 && static_cast<size_t>(index) < m_icons.size()); 94 return m_icons[index]; 95 } 96 97 void AutoFillPopupMenuClient::removeSuggestionAtIndex(unsigned listIndex) 98 { 99 if (!canRemoveSuggestionAtIndex(listIndex)) 100 return; 101 102 int index = convertListIndexToInternalIndex(listIndex); 103 104 ASSERT(static_cast<unsigned>(index) < m_names.size()); 105 106 m_names.remove(index); 107 m_labels.remove(index); 108 m_icons.remove(index); 109 m_uniqueIDs.remove(index); 110 111 // Shift the separator index if necessary. 112 if (m_separatorIndex != -1) 113 m_separatorIndex--; 114 } 115 116 bool AutoFillPopupMenuClient::canRemoveSuggestionAtIndex(unsigned listIndex) 117 { 118 // Only allow deletion of items before the separator that have unique id 0 119 // (i.e. are autocomplete rather than autofill items). 120 int index = convertListIndexToInternalIndex(listIndex); 121 return !m_uniqueIDs[index] && (m_separatorIndex == -1 || listIndex < static_cast<unsigned>(m_separatorIndex)); 122 } 123 124 void AutoFillPopupMenuClient::valueChanged(unsigned listIndex, bool fireEvents) 125 { 126 WebViewImpl* webView = getWebView(); 127 if (!webView) 128 return; 129 130 if (m_separatorIndex != -1 && listIndex > static_cast<unsigned>(m_separatorIndex)) 131 --listIndex; 132 133 ASSERT(listIndex < m_names.size()); 134 135 webView->autoFillClient()->didAcceptAutoFillSuggestion(WebNode(getTextField()), 136 m_names[listIndex], 137 m_labels[listIndex], 138 m_uniqueIDs[listIndex], 139 listIndex); 140 } 141 142 void AutoFillPopupMenuClient::selectionChanged(unsigned listIndex, bool fireEvents) 143 { 144 WebViewImpl* webView = getWebView(); 145 if (!webView) 146 return; 147 148 if (m_separatorIndex != -1 && listIndex > static_cast<unsigned>(m_separatorIndex)) 149 --listIndex; 150 151 ASSERT(listIndex < m_names.size()); 152 153 webView->autoFillClient()->didSelectAutoFillSuggestion(WebNode(getTextField()), 154 m_names[listIndex], 155 m_labels[listIndex], 156 m_uniqueIDs[listIndex]); 157 } 158 159 void AutoFillPopupMenuClient::selectionCleared() 160 { 161 WebViewImpl* webView = getWebView(); 162 if (webView) 163 webView->autoFillClient()->didClearAutoFillSelection(WebNode(getTextField())); 164 } 165 166 String AutoFillPopupMenuClient::itemText(unsigned listIndex) const 167 { 168 return getSuggestion(listIndex); 169 } 170 171 String AutoFillPopupMenuClient::itemLabel(unsigned listIndex) const 172 { 173 return getLabel(listIndex); 174 } 175 176 String AutoFillPopupMenuClient::itemIcon(unsigned listIndex) const 177 { 178 return getIcon(listIndex); 179 } 180 181 bool AutoFillPopupMenuClient::itemIsEnabled(unsigned listIndex) const 182 { 183 return !itemIsWarning(listIndex); 184 } 185 186 PopupMenuStyle AutoFillPopupMenuClient::itemStyle(unsigned listIndex) const 187 { 188 return itemIsWarning(listIndex) ? *m_warningStyle : *m_regularStyle; 189 } 190 191 PopupMenuStyle AutoFillPopupMenuClient::menuStyle() const 192 { 193 return *m_regularStyle; 194 } 195 196 int AutoFillPopupMenuClient::clientPaddingLeft() const 197 { 198 // Bug http://crbug.com/7708 seems to indicate the style can be 0. 199 RenderStyle* style = textFieldStyle(); 200 if (!style) 201 return 0; 202 203 return RenderTheme::defaultTheme()->popupInternalPaddingLeft(style); 204 } 205 206 int AutoFillPopupMenuClient::clientPaddingRight() const 207 { 208 // Bug http://crbug.com/7708 seems to indicate the style can be 0. 209 RenderStyle* style = textFieldStyle(); 210 if (!style) 211 return 0; 212 213 return RenderTheme::defaultTheme()->popupInternalPaddingRight(style); 214 } 215 216 void AutoFillPopupMenuClient::popupDidHide() 217 { 218 WebViewImpl* webView = getWebView(); 219 if (!webView) 220 return; 221 222 webView->autoFillPopupDidHide(); 223 webView->autoFillClient()->didClearAutoFillSelection(WebNode(getTextField())); 224 } 225 226 bool AutoFillPopupMenuClient::itemIsSeparator(unsigned listIndex) const 227 { 228 return (m_separatorIndex != -1 && static_cast<unsigned>(m_separatorIndex) == listIndex); 229 } 230 231 bool AutoFillPopupMenuClient::itemIsWarning(unsigned listIndex) const 232 { 233 int index = convertListIndexToInternalIndex(listIndex); 234 if (index == -1) 235 return false; 236 237 ASSERT(index >= 0 && static_cast<size_t>(index) < m_uniqueIDs.size()); 238 return m_uniqueIDs[index] < 0; 239 } 240 241 void AutoFillPopupMenuClient::setTextFromItem(unsigned listIndex) 242 { 243 m_textField->setValue(getSuggestion(listIndex)); 244 } 245 246 FontSelector* AutoFillPopupMenuClient::fontSelector() const 247 { 248 return m_textField->document()->styleSelector()->fontSelector(); 249 } 250 251 HostWindow* AutoFillPopupMenuClient::hostWindow() const 252 { 253 return m_textField->document()->view()->hostWindow(); 254 } 255 256 PassRefPtr<Scrollbar> AutoFillPopupMenuClient::createScrollbar( 257 ScrollableArea* scrollableArea, 258 ScrollbarOrientation orientation, 259 ScrollbarControlSize size) 260 { 261 return Scrollbar::createNativeScrollbar(scrollableArea, orientation, size); 262 } 263 264 void AutoFillPopupMenuClient::initialize( 265 HTMLInputElement* textField, 266 const WebVector<WebString>& names, 267 const WebVector<WebString>& labels, 268 const WebVector<WebString>& icons, 269 const WebVector<int>& uniqueIDs, 270 int separatorIndex) 271 { 272 ASSERT(names.size() == labels.size()); 273 ASSERT(names.size() == icons.size()); 274 ASSERT(names.size() == uniqueIDs.size()); 275 ASSERT(separatorIndex < static_cast<int>(names.size())); 276 277 m_selectedIndex = -1; 278 m_textField = textField; 279 280 // The suggestions must be set before initializing the 281 // AutoFillPopupMenuClient. 282 setSuggestions(names, labels, icons, uniqueIDs, separatorIndex); 283 284 FontDescription regularFontDescription; 285 RenderTheme::defaultTheme()->systemFont(CSSValueWebkitControl, 286 regularFontDescription); 287 RenderStyle* style = m_textField->computedStyle(); 288 regularFontDescription.setComputedSize(style->fontDescription().computedSize()); 289 290 Font regularFont(regularFontDescription, 0, 0); 291 regularFont.update(textField->document()->styleSelector()->fontSelector()); 292 // The direction of text in popup menu is set the same as the direction of 293 // the input element: textField. 294 m_regularStyle.set(new PopupMenuStyle(Color::black, Color::white, regularFont, 295 true, false, Length(WebCore::Fixed), 296 textField->renderer()->style()->direction(), textField->renderer()->style()->unicodeBidi() == Override)); 297 298 FontDescription warningFontDescription = regularFont.fontDescription(); 299 warningFontDescription.setItalic(true); 300 Font warningFont(warningFontDescription, regularFont.letterSpacing(), regularFont.wordSpacing()); 301 warningFont.update(regularFont.fontSelector()); 302 m_warningStyle.set(new PopupMenuStyle(Color::darkGray, 303 m_regularStyle->backgroundColor(), 304 warningFont, 305 m_regularStyle->isVisible(), 306 m_regularStyle->isDisplayNone(), 307 m_regularStyle->textIndent(), 308 m_regularStyle->textDirection(), 309 m_regularStyle->hasTextDirectionOverride())); 310 } 311 312 void AutoFillPopupMenuClient::setSuggestions(const WebVector<WebString>& names, 313 const WebVector<WebString>& labels, 314 const WebVector<WebString>& icons, 315 const WebVector<int>& uniqueIDs, 316 int separatorIndex) 317 { 318 ASSERT(names.size() == labels.size()); 319 ASSERT(names.size() == icons.size()); 320 ASSERT(names.size() == uniqueIDs.size()); 321 ASSERT(separatorIndex < static_cast<int>(names.size())); 322 323 m_names.clear(); 324 m_labels.clear(); 325 m_icons.clear(); 326 m_uniqueIDs.clear(); 327 for (size_t i = 0; i < names.size(); ++i) { 328 m_names.append(names[i]); 329 m_labels.append(labels[i]); 330 m_icons.append(icons[i]); 331 m_uniqueIDs.append(uniqueIDs[i]); 332 } 333 334 m_separatorIndex = separatorIndex; 335 336 // Try to preserve selection if possible. 337 if (getSelectedIndex() >= static_cast<int>(names.size())) 338 setSelectedIndex(-1); 339 } 340 341 int AutoFillPopupMenuClient::convertListIndexToInternalIndex(unsigned listIndex) const 342 { 343 if (listIndex == static_cast<unsigned>(m_separatorIndex)) 344 return -1; 345 346 if (m_separatorIndex == -1 || listIndex < static_cast<unsigned>(m_separatorIndex)) 347 return listIndex; 348 return listIndex - 1; 349 } 350 351 WebViewImpl* AutoFillPopupMenuClient::getWebView() const 352 { 353 Frame* frame = m_textField->document()->frame(); 354 if (!frame) 355 return 0; 356 357 Page* page = frame->page(); 358 if (!page) 359 return 0; 360 361 return static_cast<WebViewImpl*>(page->chrome()->client()->webView()); 362 } 363 364 RenderStyle* AutoFillPopupMenuClient::textFieldStyle() const 365 { 366 RenderStyle* style = m_textField->computedStyle(); 367 if (!style) { 368 // It seems we can only have a 0 style in a TextField if the 369 // node is detached, in which case we the popup should not be 370 // showing. Please report this in http://crbug.com/7708 and 371 // include the page you were visiting. 372 ASSERT_NOT_REACHED(); 373 } 374 return style; 375 } 376 377 } // namespace WebKit 378