1 /* 2 * Copyright (C) 2007 Apple 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 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "config.h" 30 #include "EditingDelegate.h" 31 32 #include "DumpRenderTree.h" 33 #include "LayoutTestController.h" 34 #include <WebCore/COMPtr.h> 35 #include <wtf/Platform.h> 36 #include <JavaScriptCore/Assertions.h> 37 #include <string> 38 #include <tchar.h> 39 40 using std::wstring; 41 42 EditingDelegate::EditingDelegate() 43 : m_refCount(1) 44 , m_acceptsEditing(true) 45 { 46 } 47 48 // IUnknown 49 HRESULT STDMETHODCALLTYPE EditingDelegate::QueryInterface(REFIID riid, void** ppvObject) 50 { 51 *ppvObject = 0; 52 if (IsEqualGUID(riid, IID_IUnknown)) 53 *ppvObject = static_cast<IWebEditingDelegate*>(this); 54 else if (IsEqualGUID(riid, IID_IWebEditingDelegate)) 55 *ppvObject = static_cast<IWebEditingDelegate*>(this); 56 else 57 return E_NOINTERFACE; 58 59 AddRef(); 60 return S_OK; 61 } 62 63 ULONG STDMETHODCALLTYPE EditingDelegate::AddRef(void) 64 { 65 return ++m_refCount; 66 } 67 68 ULONG STDMETHODCALLTYPE EditingDelegate::Release(void) 69 { 70 ULONG newRef = --m_refCount; 71 if (!newRef) 72 delete this; 73 74 return newRef; 75 } 76 77 static wstring dumpPath(IDOMNode* node) 78 { 79 ASSERT(node); 80 81 wstring result; 82 83 BSTR name; 84 if (FAILED(node->nodeName(&name))) 85 return result; 86 result.assign(name, SysStringLen(name)); 87 SysFreeString(name); 88 89 COMPtr<IDOMNode> parent; 90 if (SUCCEEDED(node->parentNode(&parent))) 91 result += TEXT(" > ") + dumpPath(parent.get()); 92 93 return result; 94 } 95 96 static wstring dump(IDOMRange* range) 97 { 98 ASSERT(range); 99 100 int startOffset; 101 if (FAILED(range->startOffset(&startOffset))) 102 return 0; 103 104 int endOffset; 105 if (FAILED(range->endOffset(&endOffset))) 106 return 0; 107 108 COMPtr<IDOMNode> startContainer; 109 if (FAILED(range->startContainer(&startContainer))) 110 return 0; 111 112 COMPtr<IDOMNode> endContainer; 113 if (FAILED(range->endContainer(&endContainer))) 114 return 0; 115 116 wchar_t buffer[1024]; 117 _snwprintf(buffer, ARRAYSIZE(buffer), L"range from %ld of %s to %ld of %s", startOffset, dumpPath(startContainer.get()), endOffset, dumpPath(endContainer.get())); 118 return buffer; 119 } 120 121 HRESULT STDMETHODCALLTYPE EditingDelegate::shouldBeginEditingInDOMRange( 122 /* [in] */ IWebView* webView, 123 /* [in] */ IDOMRange* range, 124 /* [retval][out] */ BOOL* result) 125 { 126 if (!result) { 127 ASSERT_NOT_REACHED(); 128 return E_POINTER; 129 } 130 131 if (::gLayoutTestController->dumpEditingCallbacks() && !done) 132 _tprintf(TEXT("EDITING DELEGATE: shouldBeginEditingInDOMRange:%s\n"), dump(range)); 133 134 *result = m_acceptsEditing; 135 return S_OK; 136 } 137 138 HRESULT STDMETHODCALLTYPE EditingDelegate::shouldEndEditingInDOMRange( 139 /* [in] */ IWebView* webView, 140 /* [in] */ IDOMRange* range, 141 /* [retval][out] */ BOOL* result) 142 { 143 if (!result) { 144 ASSERT_NOT_REACHED(); 145 return E_POINTER; 146 } 147 148 if (::gLayoutTestController->dumpEditingCallbacks() && !done) 149 _tprintf(TEXT("EDITING DELEGATE: shouldEndEditingInDOMRange:%s\n"), dump(range)); 150 151 *result = m_acceptsEditing; 152 return S_OK; 153 } 154 155 HRESULT STDMETHODCALLTYPE EditingDelegate::shouldInsertNode( 156 /* [in] */ IWebView* webView, 157 /* [in] */ IDOMNode* node, 158 /* [in] */ IDOMRange* range, 159 /* [in] */ WebViewInsertAction action) 160 { 161 static LPCTSTR insertactionstring[] = { 162 TEXT("WebViewInsertActionTyped"), 163 TEXT("WebViewInsertActionPasted"), 164 TEXT("WebViewInsertActionDropped"), 165 }; 166 167 if (::gLayoutTestController->dumpEditingCallbacks() && !done) 168 _tprintf(TEXT("EDITING DELEGATE: shouldInsertNode:%s replacingDOMRange:%s givenAction:%s\n"), dumpPath(node), dump(range), insertactionstring[action]); 169 170 return S_OK; 171 } 172 173 HRESULT STDMETHODCALLTYPE EditingDelegate::shouldInsertText( 174 /* [in] */ IWebView* webView, 175 /* [in] */ BSTR text, 176 /* [in] */ IDOMRange* range, 177 /* [in] */ WebViewInsertAction action, 178 /* [retval][out] */ BOOL* result) 179 { 180 if (!result) { 181 ASSERT_NOT_REACHED(); 182 return E_POINTER; 183 } 184 185 static LPCTSTR insertactionstring[] = { 186 TEXT("WebViewInsertActionTyped"), 187 TEXT("WebViewInsertActionPasted"), 188 TEXT("WebViewInsertActionDropped"), 189 }; 190 191 if (::gLayoutTestController->dumpEditingCallbacks() && !done) 192 _tprintf(TEXT("EDITING DELEGATE: shouldInsertText:%s replacingDOMRange:%s givenAction:%s\n"), text ? text : TEXT(""), dump(range), insertactionstring[action]); 193 194 *result = m_acceptsEditing; 195 return S_OK; 196 } 197 198 HRESULT STDMETHODCALLTYPE EditingDelegate::shouldDeleteDOMRange( 199 /* [in] */ IWebView* webView, 200 /* [in] */ IDOMRange* range, 201 /* [retval][out] */ BOOL* result) 202 { 203 if (!result) { 204 ASSERT_NOT_REACHED(); 205 return E_POINTER; 206 } 207 208 if (::gLayoutTestController->dumpEditingCallbacks() && !done) 209 _tprintf(TEXT("EDITING DELEGATE: shouldDeleteDOMRange:%s\n"), dump(range)); 210 211 *result = m_acceptsEditing; 212 return S_OK; 213 } 214 215 HRESULT STDMETHODCALLTYPE EditingDelegate::shouldChangeSelectedDOMRange( 216 /* [in] */ IWebView* webView, 217 /* [in] */ IDOMRange* currentRange, 218 /* [in] */ IDOMRange* proposedRange, 219 /* [in] */ WebSelectionAffinity selectionAffinity, 220 /* [in] */ BOOL stillSelecting, 221 /* [retval][out] */ BOOL* result) 222 { 223 if (!result) { 224 ASSERT_NOT_REACHED(); 225 return E_POINTER; 226 } 227 228 static LPCTSTR affinitystring[] = { 229 TEXT("NSSelectionAffinityUpstream"), 230 TEXT("NSSelectionAffinityDownstream") 231 }; 232 static LPCTSTR boolstring[] = { 233 TEXT("FALSE"), 234 TEXT("TRUE") 235 }; 236 237 if (::gLayoutTestController->dumpEditingCallbacks() && !done) 238 _tprintf(TEXT("EDITING DELEGATE: shouldChangeSelectedDOMRange:%s toDOMRange:%s affinity:%s stillSelecting:%s\n"), dump(currentRange), dump(proposedRange), affinitystring[selectionAffinity], boolstring[stillSelecting]); 239 240 *result = m_acceptsEditing; 241 return S_OK; 242 } 243 244 HRESULT STDMETHODCALLTYPE EditingDelegate::shouldApplyStyle( 245 /* [in] */ IWebView* webView, 246 /* [in] */ IDOMCSSStyleDeclaration* style, 247 /* [in] */ IDOMRange* range, 248 /* [retval][out] */ BOOL* result) 249 { 250 if (!result) { 251 ASSERT_NOT_REACHED(); 252 return E_POINTER; 253 } 254 255 if (::gLayoutTestController->dumpEditingCallbacks() && !done) 256 _tprintf(TEXT("EDITING DELEGATE: shouldApplyStyle:%s toElementsInDOMRange:%s\n"), TEXT("'style description'")/*[[style description] UTF8String]*/, dump(range)); 257 258 *result = m_acceptsEditing; 259 return S_OK; 260 } 261 262 HRESULT STDMETHODCALLTYPE EditingDelegate::shouldChangeTypingStyle( 263 /* [in] */ IWebView* webView, 264 /* [in] */ IDOMCSSStyleDeclaration* currentStyle, 265 /* [in] */ IDOMCSSStyleDeclaration* proposedStyle, 266 /* [retval][out] */ BOOL* result) 267 { 268 if (!result) { 269 ASSERT_NOT_REACHED(); 270 return E_POINTER; 271 } 272 273 if (::gLayoutTestController->dumpEditingCallbacks() && !done) 274 _tprintf(TEXT("EDITING DELEGATE: shouldChangeTypingStyle:%s toStyle:%s\n"), TEXT("'currentStyle description'"), TEXT("'proposedStyle description'")); 275 276 *result = m_acceptsEditing; 277 return S_OK; 278 } 279 280 HRESULT STDMETHODCALLTYPE EditingDelegate::doPlatformCommand( 281 /* [in] */ IWebView *webView, 282 /* [in] */ BSTR command, 283 /* [retval][out] */ BOOL *result) 284 { 285 if (!result) { 286 ASSERT_NOT_REACHED(); 287 return E_POINTER; 288 } 289 290 if (::gLayoutTestController->dumpEditingCallbacks() && !done) 291 _tprintf(TEXT("EDITING DELEGATE: doPlatformCommand:%s\n"), command ? command : TEXT("")); 292 293 *result = m_acceptsEditing; 294 return S_OK; 295 } 296 297 HRESULT STDMETHODCALLTYPE EditingDelegate::webViewDidBeginEditing( 298 /* [in] */ IWebNotification* notification) 299 { 300 if (::gLayoutTestController->dumpEditingCallbacks() && !done) { 301 BSTR name; 302 notification->name(&name); 303 _tprintf(TEXT("EDITING DELEGATE: webViewDidBeginEditing:%s\n"), name ? name : TEXT("")); 304 SysFreeString(name); 305 } 306 return S_OK; 307 } 308 309 HRESULT STDMETHODCALLTYPE EditingDelegate::webViewDidChange( 310 /* [in] */ IWebNotification *notification) 311 { 312 if (::gLayoutTestController->dumpEditingCallbacks() && !done) { 313 BSTR name; 314 notification->name(&name); 315 _tprintf(TEXT("EDITING DELEGATE: webViewDidBeginEditing:%s\n"), name ? name : TEXT("")); 316 SysFreeString(name); 317 } 318 return S_OK; 319 } 320 321 HRESULT STDMETHODCALLTYPE EditingDelegate::webViewDidEndEditing( 322 /* [in] */ IWebNotification *notification) 323 { 324 if (::gLayoutTestController->dumpEditingCallbacks() && !done) { 325 BSTR name; 326 notification->name(&name); 327 _tprintf(TEXT("EDITING DELEGATE: webViewDidEndEditing:%s\n"), name ? name : TEXT("")); 328 SysFreeString(name); 329 } 330 return S_OK; 331 } 332 333 HRESULT STDMETHODCALLTYPE EditingDelegate::webViewDidChangeTypingStyle( 334 /* [in] */ IWebNotification *notification) 335 { 336 if (::gLayoutTestController->dumpEditingCallbacks() && !done) { 337 BSTR name; 338 notification->name(&name); 339 _tprintf(TEXT("EDITING DELEGATE: webViewDidChangeTypingStyle:%s\n"), name ? name : TEXT("")); 340 SysFreeString(name); 341 } 342 return S_OK; 343 } 344 345 HRESULT STDMETHODCALLTYPE EditingDelegate::webViewDidChangeSelection( 346 /* [in] */ IWebNotification *notification) 347 { 348 if (::gLayoutTestController->dumpEditingCallbacks() && !done) { 349 BSTR name; 350 notification->name(&name); 351 _tprintf(TEXT("EDITING DELEGATE: webViewDidChangeSelection:%s\n"), name ? name : TEXT("")); 352 SysFreeString(name); 353 } 354 return S_OK; 355 } 356 357 static int indexOfFirstWordCharacter(const TCHAR* text) 358 { 359 const TCHAR* cursor = text; 360 while (*cursor && !iswalpha(*cursor)) 361 ++cursor; 362 return *cursor ? (cursor - text) : -1; 363 }; 364 365 static int wordLength(const TCHAR* text) 366 { 367 const TCHAR* cursor = text; 368 while (*cursor && iswalpha(*cursor)) 369 ++cursor; 370 return cursor - text; 371 }; 372 373 HRESULT STDMETHODCALLTYPE EditingDelegate::checkSpellingOfString( 374 /* [in] */ IWebView* view, 375 /* [in] */ LPCTSTR text, 376 /* [in] */ int length, 377 /* [out] */ int* misspellingLocation, 378 /* [out] */ int* misspellingLength) 379 { 380 static const TCHAR* misspelledWords[] = { 381 // These words are known misspelled words in webkit tests. 382 // If there are other misspelled words in webkit tests, please add them in 383 // this array. 384 TEXT("foo"), 385 TEXT("Foo"), 386 TEXT("baz"), 387 TEXT("fo"), 388 TEXT("LibertyF"), 389 TEXT("chello"), 390 TEXT("xxxtestxxx"), 391 TEXT("XXxxx"), 392 TEXT("Textx"), 393 TEXT("blockquoted"), 394 TEXT("asd"), 395 TEXT("Lorem"), 396 TEXT("Nunc"), 397 TEXT("Curabitur"), 398 TEXT("eu"), 399 TEXT("adlj"), 400 TEXT("adaasj"), 401 TEXT("sdklj"), 402 TEXT("jlkds"), 403 TEXT("jsaada"), 404 TEXT("jlda"), 405 TEXT("zz"), 406 TEXT("contentEditable"), 407 0, 408 }; 409 410 wstring textString(text, length); 411 int wordStart = indexOfFirstWordCharacter(textString.c_str()); 412 if (-1 == wordStart) 413 return S_OK; 414 wstring word = textString.substr(wordStart, wordLength(textString.c_str() + wordStart)); 415 for (size_t i = 0; misspelledWords[i]; ++i) { 416 if (word == misspelledWords[i]) { 417 *misspellingLocation = wordStart; 418 *misspellingLength = word.size(); 419 break; 420 } 421 } 422 423 return S_OK; 424 } 425