[Win] Expose missing editing features through WebView interface
[WebKit-https.git] / Tools / DumpRenderTree / win / EditingDelegate.cpp
1 /*
2  * Copyright (C) 2007, 2014-2015 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 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 <comutil.h>
33 #include "DumpRenderTree.h"
34 #include "TestRunner.h"
35 #include <WebCore/COMPtr.h>
36 #include <wtf/Assertions.h>
37 #include <wtf/Platform.h>
38 #include <wtf/StringExtras.h>
39 #include <string>
40 #include <tchar.h>
41
42 using std::wstring;
43
44 EditingDelegate::EditingDelegate()
45     : m_refCount(1)
46     , m_acceptsEditing(true)
47 {
48 }
49
50 // IUnknown
51 HRESULT EditingDelegate::QueryInterface(REFIID riid, void** ppvObject)
52 {
53     *ppvObject = 0;
54     if (IsEqualGUID(riid, IID_IUnknown))
55         *ppvObject = static_cast<IWebEditingDelegate*>(this);
56     else if (IsEqualGUID(riid, IID_IWebEditingDelegate))
57         *ppvObject = static_cast<IWebEditingDelegate*>(this);
58     else if (IsEqualGUID(riid, IID_IWebNotificationObserver))
59         *ppvObject = static_cast<IWebNotificationObserver*>(this);
60     else
61         return E_NOINTERFACE;
62
63     AddRef();
64     return S_OK;
65 }
66
67 ULONG EditingDelegate::AddRef(void)
68 {
69     return ++m_refCount;
70 }
71
72 ULONG EditingDelegate::Release(void)
73 {
74     ULONG newRef = --m_refCount;
75     if (!newRef)
76         delete this;
77
78     return newRef;
79 }
80
81 static std::string dumpPath(IDOMNode* node)
82 {
83     ASSERT(node);
84
85     std::string result;
86
87     _bstr_t name;
88     if (FAILED(node->nodeName(&name.GetBSTR())))
89         return result;
90     result.assign(static_cast<const char*>(name), name.length());
91
92     COMPtr<IDOMNode> parent;
93     if (SUCCEEDED(node->parentNode(&parent)))
94         result += " > " + dumpPath(parent.get());
95
96     return result;
97 }
98
99 static std::string dump(IDOMRange* range)
100 {
101     if (!range)
102         return std::string();
103
104     int startOffset;
105     if (FAILED(range->startOffset(&startOffset)))
106         return std::string();
107
108     int endOffset;
109     if (FAILED(range->endOffset(&endOffset)))
110         return std::string();
111
112     COMPtr<IDOMNode> startContainer;
113     if (FAILED(range->startContainer(&startContainer)))
114         return std::string();
115
116     COMPtr<IDOMNode> endContainer;
117     if (FAILED(range->endContainer(&endContainer)))
118         return std::string();
119
120     char buffer[1024];
121     snprintf(buffer, ARRAYSIZE(buffer), "range from %ld of %s to %ld of %s", startOffset, dumpPath(startContainer.get()).c_str(), endOffset, dumpPath(endContainer.get()).c_str());
122     return buffer;
123 }
124
125 HRESULT EditingDelegate::shouldBeginEditingInDOMRange(IWebView* /*webView*/, IDOMRange* range, BOOL* result)
126 {
127     if (!result) {
128         ASSERT_NOT_REACHED();
129         return E_POINTER;
130     }
131
132     if (::gTestRunner->dumpEditingCallbacks() && !done)
133         printf("EDITING DELEGATE: shouldBeginEditingInDOMRange:%s\n", dump(range).c_str());
134
135     *result = m_acceptsEditing;
136     return S_OK;
137 }
138
139 HRESULT EditingDelegate::shouldEndEditingInDOMRange(IWebView* /*webView*/, IDOMRange* range, BOOL* result)
140 {
141     if (!result) {
142         ASSERT_NOT_REACHED();
143         return E_POINTER;
144     }
145
146     if (::gTestRunner->dumpEditingCallbacks() && !done)
147         printf("EDITING DELEGATE: shouldEndEditingInDOMRange:%s\n", dump(range).c_str());
148
149     *result = m_acceptsEditing;
150     return S_OK;
151 }
152
153 HRESULT EditingDelegate::shouldInsertNode(IWebView* /*webView*/, IDOMNode* node, IDOMRange* range, WebViewInsertAction action, BOOL* result)
154 {
155     static const char* insertActionString[] = {
156         "WebViewInsertActionTyped",
157         "WebViewInsertActionPasted",
158         "WebViewInsertActionDropped",
159     };
160
161     if (::gTestRunner->dumpEditingCallbacks() && !done)
162         printf("EDITING DELEGATE: shouldInsertNode:%s replacingDOMRange:%s givenAction:%s\n", dumpPath(node).c_str(), dump(range).c_str(), insertActionString[action]);
163
164     *result = m_acceptsEditing;
165     return S_OK;
166 }
167
168 HRESULT EditingDelegate::shouldInsertText(IWebView* /*webView*/, BSTR text, IDOMRange* range, WebViewInsertAction action, BOOL* result)
169 {
170     if (!result) {
171         ASSERT_NOT_REACHED();
172         return E_POINTER;
173     }
174
175     static const char* insertactionstring[] = {
176         "WebViewInsertActionTyped",
177         "WebViewInsertActionPasted",
178         "WebViewInsertActionDropped",
179     };
180
181     if (::gTestRunner->dumpEditingCallbacks() && !done) {
182         _bstr_t textBstr(text);
183         printf("EDITING DELEGATE: shouldInsertText:%s replacingDOMRange:%s givenAction:%s\n", static_cast<const char*>(textBstr), dump(range).c_str(), insertactionstring[action]);
184     }
185
186     *result = m_acceptsEditing;
187     return S_OK;
188 }
189
190 HRESULT EditingDelegate::shouldDeleteDOMRange(IWebView* /*webView*/, IDOMRange* range, BOOL* result)
191 {
192     if (!result) {
193         ASSERT_NOT_REACHED();
194         return E_POINTER;
195     }
196
197     if (::gTestRunner->dumpEditingCallbacks() && !done)
198         printf("EDITING DELEGATE: shouldDeleteDOMRange:%s\n", dump(range).c_str());
199
200     *result = m_acceptsEditing;
201     return S_OK;
202 }
203
204 HRESULT EditingDelegate::shouldChangeSelectedDOMRange(IWebView* /*webView*/, IDOMRange* currentRange, IDOMRange* proposedRange,
205     WebSelectionAffinity selectionAffinity, BOOL stillSelecting, BOOL* result)
206 {
207     if (!result) {
208         ASSERT_NOT_REACHED();
209         return E_POINTER;
210     }
211
212     static const char* affinityString[] = {
213         "NSSelectionAffinityUpstream",
214         "NSSelectionAffinityDownstream"
215     };
216     static const char* boolstring[] = {
217         "FALSE",
218         "TRUE"
219     };
220
221     if (::gTestRunner->dumpEditingCallbacks() && !done)
222         printf("EDITING DELEGATE: shouldChangeSelectedDOMRange:%s toDOMRange:%s affinity:%s stillSelecting:%s\n", dump(currentRange).c_str(), dump(proposedRange).c_str(), affinityString[selectionAffinity], boolstring[stillSelecting]);
223
224     *result = m_acceptsEditing;
225     return S_OK;
226 }
227
228 HRESULT EditingDelegate::shouldApplyStyle(IWebView* /*webView*/, IDOMCSSStyleDeclaration* style, IDOMRange* range, BOOL* result)
229 {
230     if (!result) {
231         ASSERT_NOT_REACHED();
232         return E_POINTER;
233     }
234
235     if (::gTestRunner->dumpEditingCallbacks() && !done)
236         printf("EDITING DELEGATE: shouldApplyStyle:%s toElementsInDOMRange:%s\n", "'style description'"/*[[style description] UTF8String]*/, dump(range).c_str());
237
238     *result = m_acceptsEditing;
239     return S_OK;
240 }
241
242 HRESULT EditingDelegate::shouldChangeTypingStyle(IWebView* /*webView*/, IDOMCSSStyleDeclaration* currentStyle, IDOMCSSStyleDeclaration* proposedStyle, BOOL* result)
243 {
244     if (!result) {
245         ASSERT_NOT_REACHED();
246         return E_POINTER;
247     }
248
249     if (::gTestRunner->dumpEditingCallbacks() && !done)
250         printf("EDITING DELEGATE: shouldChangeTypingStyle:%s toStyle:%s\n", "'currentStyle description'", "'proposedStyle description'");
251
252     *result = m_acceptsEditing;
253     return S_OK;
254 }
255
256 HRESULT EditingDelegate::doPlatformCommand(IWebView* /*webView*/, BSTR command, BOOL* result)
257 {
258     if (!result) {
259         ASSERT_NOT_REACHED();
260         return E_POINTER;
261     }
262
263     if (::gTestRunner->dumpEditingCallbacks() && !done) {
264         _bstr_t commandBSTR(command);
265         printf("EDITING DELEGATE: doPlatformCommand:%s\n", static_cast<const char*>(commandBSTR));
266     }
267
268     *result = m_acceptsEditing;
269     return S_OK;
270 }
271
272 HRESULT EditingDelegate::webViewDidBeginEditing(IWebNotification* notification)
273 {
274     if (::gTestRunner->dumpEditingCallbacks() && !done) {
275         _bstr_t name;
276         notification->name(&name.GetBSTR());
277         printf("EDITING DELEGATE: webViewDidBeginEditing:%s\n", static_cast<const char*>(name));
278     }
279     return S_OK;
280 }
281
282 HRESULT EditingDelegate::webViewDidChange(IWebNotification* notification)
283 {
284     if (::gTestRunner->dumpEditingCallbacks() && !done) {
285         _bstr_t name;
286         notification->name(&name.GetBSTR());
287         printf("EDITING DELEGATE: webViewDidChange:%s\n", static_cast<const char*>(name));
288     }
289     return S_OK;
290 }
291
292 HRESULT EditingDelegate::webViewDidEndEditing(IWebNotification* notification)
293 {
294     if (::gTestRunner->dumpEditingCallbacks() && !done) {
295         _bstr_t name;
296         notification->name(&name.GetBSTR());
297         printf("EDITING DELEGATE: webViewDidEndEditing:%s\n", static_cast<const char*>(name));
298     }
299     return S_OK;
300 }
301
302 HRESULT EditingDelegate::webViewDidChangeTypingStyle(IWebNotification* notification)
303 {
304     if (::gTestRunner->dumpEditingCallbacks() && !done) {
305         _bstr_t name;
306         notification->name(&name.GetBSTR());
307         printf("EDITING DELEGATE: webViewDidChangeTypingStyle:%s\n", static_cast<const char*>(name));
308     }
309     return S_OK;
310 }
311
312 HRESULT EditingDelegate::webViewDidChangeSelection(IWebNotification* notification)
313 {
314     if (::gTestRunner->dumpEditingCallbacks() && !done) {
315         _bstr_t name;
316         notification->name(&name.GetBSTR());
317         printf("EDITING DELEGATE: webViewDidChangeSelection:%s\n", static_cast<const char*>(name));
318     }
319     return S_OK;
320 }
321
322 static int indexOfFirstWordCharacter(const TCHAR* text)
323 {
324     const TCHAR* cursor = text;
325     while (*cursor && !iswalpha(*cursor))
326         ++cursor;
327     return *cursor ? (cursor - text) : -1;
328 };
329
330 static int wordLength(const TCHAR* text)
331 {
332     const TCHAR* cursor = text;
333     while (*cursor && iswalpha(*cursor))
334         ++cursor;
335     return cursor - text;
336 };
337
338 HRESULT EditingDelegate::checkSpellingOfString(
339             /* [in] */ IWebView* view,
340             /* [in] */ LPCTSTR text,
341             /* [in] */ int length,
342             /* [out] */ int* misspellingLocation,
343             /* [out] */ int* misspellingLength)
344 {
345     static const TCHAR* misspelledWords[] = {
346         // These words are known misspelled words in webkit tests.
347         // If there are other misspelled words in webkit tests, please add them in
348         // this array.
349         TEXT("foo"),
350         TEXT("Foo"),
351         TEXT("baz"),
352         TEXT("fo"),
353         TEXT("LibertyF"),
354         TEXT("chello"),
355         TEXT("xxxtestxxx"),
356         TEXT("XXxxx"),
357         TEXT("Textx"),
358         TEXT("blockquoted"),
359         TEXT("asd"),
360         TEXT("Lorem"),
361         TEXT("Nunc"),
362         TEXT("Curabitur"),
363         TEXT("eu"),
364         TEXT("adlj"),
365         TEXT("adaasj"),
366         TEXT("sdklj"),
367         TEXT("jlkds"),
368         TEXT("jsaada"),
369         TEXT("jlda"),
370         TEXT("zz"),
371         TEXT("contentEditable"),
372         0,
373     };
374
375     wstring textString(text, length);
376     int wordStart = indexOfFirstWordCharacter(textString.c_str());
377     if (-1 == wordStart)
378         return S_OK;
379     wstring word = textString.substr(wordStart, wordLength(textString.c_str() + wordStart));
380     for (size_t i = 0; misspelledWords[i]; ++i) {
381         if (word == misspelledWords[i]) {
382             *misspellingLocation = wordStart;
383             *misspellingLength = word.size();
384             break;
385         }
386     }
387
388     return S_OK;
389 }
390
391 HRESULT EditingDelegate::onNotify(IWebNotification* notification)
392 {
393     _bstr_t notificationName;
394     HRESULT hr = notification->name(&notificationName.GetBSTR());
395     if (FAILED(hr))
396         return hr;
397
398     static _bstr_t webViewDidBeginEditingNotificationName(WebViewDidBeginEditingNotification);
399     static _bstr_t webViewDidChangeSelectionNotificationName(WebViewDidChangeSelectionNotification);
400     static _bstr_t webViewDidEndEditingNotificationName(WebViewDidEndEditingNotification);
401     static _bstr_t webViewDidChangeTypingStyleNotificationName(WebViewDidChangeTypingStyleNotification);
402     static _bstr_t webViewDidChangeNotificationName(WebViewDidChangeNotification);
403
404     if (!wcscmp(notificationName, webViewDidBeginEditingNotificationName))
405         return webViewDidBeginEditing(notification);
406
407     if (!wcscmp(notificationName, webViewDidChangeSelectionNotificationName))
408         return webViewDidChangeSelection(notification);
409
410     if (!wcscmp(notificationName, webViewDidEndEditingNotificationName))
411         return webViewDidEndEditing(notification);
412
413     if (!wcscmp(notificationName, webViewDidChangeTypingStyleNotificationName))
414         return webViewDidChangeTypingStyle(notification);
415
416     if (!wcscmp(notificationName, webViewDidChangeNotificationName))
417         return webViewDidChange(notification);
418
419     return S_OK;
420 }