WebCore:
[WebKit-https.git] / WebKitTools / DumpRenderTree / win / LayoutTestControllerWin.cpp
1 /*
2  * Copyright (C) 2006, 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 "DumpRenderTree.h"
30 #include "LayoutTestController.h"
31
32 #include "EditingDelegate.h"
33 #include "PolicyDelegate.h"
34 #include "WorkQueue.h"
35 #include "WorkQueueItem.h"
36 #include <WebCore/COMPtr.h>
37 #include <wtf/Platform.h>
38 #include <wtf/RetainPtr.h>
39 #include <wtf/Vector.h>
40 #include <JavaScriptCore/Assertions.h>
41 #include <JavaScriptCore/JavaScriptCore.h>
42 #include <JavaScriptCore/JSRetainPtr.h>
43 #include <WebKit/WebKit.h>
44 #include <string>
45 #include <CoreFoundation/CoreFoundation.h>
46 #include <shlwapi.h>
47 #include <shlguid.h>
48 #include <shobjidl.h>
49
50 using std::string;
51 using std::wstring;
52
53 static bool resolveCygwinPath(const wstring& cygwinPath, wstring& windowsPath);
54
55 LayoutTestController::~LayoutTestController()
56 {
57     COMPtr<IWebView> webView;
58     if (FAILED(frame->webView(&webView)))
59         return;
60
61     // reset webview-related states back to default values in preparation for next test
62
63     COMPtr<IWebViewPrivate> viewPrivate;
64     if (SUCCEEDED(webView->QueryInterface(&viewPrivate)))
65         viewPrivate->setTabKeyCyclesThroughElements(TRUE);
66
67     COMPtr<IWebViewEditing> viewEditing;
68     if (FAILED(webView->QueryInterface(&viewEditing)))
69         return;
70     COMPtr<IWebEditingDelegate> delegate;
71     if (FAILED(viewEditing->editingDelegate(&delegate)))
72         return;
73     COMPtr<EditingDelegate> editingDelegate(Query, viewEditing.get());
74     if (editingDelegate)
75         editingDelegate->setAcceptsEditing(TRUE);
76 }
77
78 void LayoutTestController::addDisallowedURL(JSStringRef url)
79 {
80     // FIXME: Implement!
81 }
82
83 void LayoutTestController::clearBackForwardList()
84 {
85     COMPtr<IWebView> webView;
86     if (FAILED(frame->webView(&webView)))
87         return;
88
89     COMPtr<IWebBackForwardList> backForwardList;
90     if (FAILED(webView->backForwardList(&backForwardList)))
91         return;
92
93     COMPtr<IWebHistoryItem> item;
94     if (FAILED(backForwardList->currentItem(&item)))
95         return;
96
97     // We clear the history by setting the back/forward list's capacity to 0
98     // then restoring it back and adding back the current item.
99     int capacity;
100     if (FAILED(backForwardList->capacity(&capacity)))
101         return;
102
103     backForwardList->setCapacity(0);
104     backForwardList->setCapacity(capacity);
105     backForwardList->addItem(item.get());
106     backForwardList->goToItem(item.get());
107 }
108
109 JSStringRef LayoutTestController::copyDecodedHostName(JSStringRef name)
110 {
111     // FIXME: Implement!
112     return 0;
113 }
114
115 JSStringRef LayoutTestController::copyEncodedHostName(JSStringRef name)
116 {
117     // FIXME: Implement!
118     return 0;
119 }
120
121 void LayoutTestController::display()
122 {
123     displayWebView();
124 }
125
126 void LayoutTestController::keepWebHistory()
127 {
128     COMPtr<IWebHistory> history(Create, CLSID_WebHistory);
129     if (!history)
130         return;
131
132     COMPtr<IWebHistory> sharedHistory(Create, CLSID_WebHistory);
133     if (!sharedHistory)
134         return;
135
136     history->setOptionalSharedHistory(sharedHistory.get());
137 }
138
139 void LayoutTestController::notifyDone()
140 {
141     // Same as on mac.  This can be shared.
142     if (m_waitToDump && !topLoadingFrame && !WorkQueue::shared()->count())
143         dump();
144     m_waitToDump = false;
145 }
146
147 JSStringRef LayoutTestController::pathToLocalResource(JSContextRef context, JSStringRef url)
148 {
149     wstring input(JSStringGetCharactersPtr(url), JSStringGetLength(url));
150
151     wstring localPath;
152     if (!resolveCygwinPath(input, localPath)) {
153         printf("ERROR: Failed to resolve Cygwin path %S\n", input.c_str());
154         return 0;
155     }
156
157     return JSStringCreateWithCharacters(localPath.c_str(), localPath.length());
158 }
159
160 void LayoutTestController::queueBackNavigation(int howFarBack)
161 {
162     // Same as on mac.  This can be shared.
163     WorkQueue::shared()->queue(new BackItem(howFarBack));
164 }
165
166 void LayoutTestController::queueForwardNavigation(int howFarForward)
167 {
168     // Same as on mac.  This can be shared.
169     WorkQueue::shared()->queue(new ForwardItem(howFarForward));
170 }
171
172 static wstring jsStringRefToWString(JSStringRef jsStr)
173 {
174     size_t length = JSStringGetLength(jsStr);
175     Vector<WCHAR> buffer(length + 1);
176     memcpy(buffer.data(), JSStringGetCharactersPtr(jsStr), length * sizeof(WCHAR));
177     buffer[length] = '\0';
178
179     return buffer.data();
180 }
181
182 void LayoutTestController::queueLoad(JSStringRef url, JSStringRef target)
183 {
184     COMPtr<IWebDataSource> dataSource;
185     if (FAILED(frame->dataSource(&dataSource)))
186         return;
187
188     COMPtr<IWebURLResponse> response;
189     if (FAILED(dataSource->response(&response)) || !response)
190         return;
191
192     BSTR responseURLBSTR;
193     if (FAILED(response->URL(&responseURLBSTR)))
194         return;
195     wstring responseURL(responseURLBSTR, SysStringLen(responseURLBSTR));
196     SysFreeString(responseURLBSTR);
197
198     // FIXME: We should do real relative URL resolution here.
199     int lastSlash = responseURL.rfind('/');
200     if (lastSlash != -1)
201         responseURL = responseURL.substr(0, lastSlash);
202
203     wstring wURL = jsStringRefToWString(url);
204     wstring wAbsoluteURL = responseURL + TEXT("/") + wURL;
205     JSRetainPtr<JSStringRef> jsAbsoluteURL(Adopt, JSStringCreateWithCharacters(wAbsoluteURL.data(), wAbsoluteURL.length()));
206
207     WorkQueue::shared()->queue(new LoadItem(jsAbsoluteURL.get(), target));
208 }
209
210 void LayoutTestController::queueReload()
211 {
212     WorkQueue::shared()->queue(new ReloadItem);
213 }
214
215 void LayoutTestController::queueScript(JSStringRef script)
216 {
217     WorkQueue::shared()->queue(new ScriptItem(script));
218 }
219
220 void LayoutTestController::setAcceptsEditing(bool acceptsEditing)
221 {
222     COMPtr<IWebView> webView;
223     if (FAILED(frame->webView(&webView)))
224         return;
225
226     COMPtr<IWebViewEditing> viewEditing;
227     if (FAILED(webView->QueryInterface(&viewEditing)))
228         return;
229
230     COMPtr<IWebEditingDelegate> delegate;
231     if (FAILED(viewEditing->editingDelegate(&delegate)))
232         return;
233
234     EditingDelegate* editingDelegate = (EditingDelegate*)(IWebEditingDelegate*)delegate.get();
235     editingDelegate->setAcceptsEditing(acceptsEditing);
236 }
237
238 void LayoutTestController::setAuthorAndUserStylesEnabled(bool flag)
239 {
240     COMPtr<IWebView> webView;
241     if (FAILED(frame->webView(&webView)))
242         return;
243
244     COMPtr<IWebPreferences> preferences;
245     if (FAILED(webView->preferences(&preferences)))
246         return;
247
248     COMPtr<IWebPreferencesPrivate> prefsPrivate(Query, preferences);
249     if (!prefsPrivate)
250         return;
251
252     prefsPrivate->setAuthorAndUserStylesEnabled(flag);
253 }
254
255 void LayoutTestController::setCustomPolicyDelegate(bool setDelegate)
256 {
257     COMPtr<IWebView> webView;
258     if (FAILED(frame->webView(&webView)))
259         return;
260
261     if (setDelegate)
262         webView->setPolicyDelegate(policyDelegate);
263     else
264         webView->setPolicyDelegate(NULL);
265 }
266
267 void LayoutTestController::setMainFrameIsFirstResponder(bool flag)
268 {
269     // FIXME: Implement!
270 }
271
272 void LayoutTestController::setPrivateBrowsingEnabled(bool privateBrowsingEnabled)
273 {
274     COMPtr<IWebView> webView;
275     if (FAILED(frame->webView(&webView)))
276         return;
277
278     COMPtr<IWebPreferences> preferences;
279     if (FAILED(webView->preferences(&preferences)))
280         return;
281
282     preferences->setPrivateBrowsingEnabled(privateBrowsingEnabled);
283 }
284
285 void LayoutTestController::setPopupBlockingEnabled(bool privateBrowsingEnabled)
286 {
287     COMPtr<IWebView> webView;
288     if (FAILED(frame->webView(&webView)))
289         return;
290
291     COMPtr<IWebPreferences> preferences;
292     if (FAILED(webView->preferences(&preferences)))
293         return;
294
295     preferences->setJavaScriptCanOpenWindowsAutomatically(!privateBrowsingEnabled);
296 }
297
298 void LayoutTestController::setTabKeyCyclesThroughElements(bool shouldCycle)
299 {
300     COMPtr<IWebView> webView;
301     if (FAILED(frame->webView(&webView)))
302         return;
303
304     COMPtr<IWebViewPrivate> viewPrivate;
305     if (FAILED(webView->QueryInterface(&viewPrivate)))
306         return;
307
308     viewPrivate->setTabKeyCyclesThroughElements(shouldCycle ? TRUE : FALSE);
309 }
310
311 void LayoutTestController::setUseDashboardCompatibilityMode(bool flag)
312 {
313     // FIXME: Implement!
314 }
315
316 void LayoutTestController::setUserStyleSheetEnabled(bool flag)
317 {
318     COMPtr<IWebView> webView;
319     if (FAILED(frame->webView(&webView)))
320         return;
321
322     COMPtr<IWebPreferences> preferences;
323     if (FAILED(webView->preferences(&preferences)))
324         return;
325
326    preferences->setUserStyleSheetEnabled(flag);
327 }
328
329 bool appendComponentToPath(wstring& path, const wstring& component)
330 {
331     WCHAR buffer[MAX_PATH];
332
333     if (path.size() + 1 > MAX_PATH)
334         return false;
335
336     memcpy(buffer, path.data(), path.size() * sizeof(WCHAR));
337     buffer[path.size()] = '\0';
338
339     if (!PathAppendW(buffer, component.c_str()))
340         return false;
341
342     path = wstring(buffer);
343     return true;
344 }
345
346 static bool followShortcuts(wstring& path)
347 {
348     if (PathFileExists(path.c_str()))
349         return true;
350
351     // Do we have a shortcut?
352     wstring linkPath = path;
353     linkPath.append(TEXT(".lnk"));
354     if (!PathFileExists(linkPath.c_str()))
355        return true;
356
357     // We have a shortcut, find its target.
358     COMPtr<IShellLink> shortcut(Create, CLSID_ShellLink);
359     if (!shortcut)
360        return false;
361     COMPtr<IPersistFile> persistFile(Query, shortcut);
362     if (!shortcut)
363         return false;
364     if (FAILED(persistFile->Load(linkPath.c_str(), STGM_READ)))
365         return false;
366     if (FAILED(shortcut->Resolve(0, 0)))
367         return false;
368     WCHAR targetPath[MAX_PATH];
369     DWORD targetPathLen = _countof(targetPath);
370     if (FAILED(shortcut->GetPath(targetPath, targetPathLen, 0, 0)))
371         return false;
372     if (!PathFileExists(targetPath))
373         return false;
374     // Use the target path as the result path instead.
375     path = wstring(targetPath);
376
377     return true;
378 }
379
380 static bool resolveCygwinPath(const wstring& cygwinPath, wstring& windowsPath)
381 {
382     wstring fileProtocol = L"file://";
383     bool isFileProtocol = cygwinPath.find(fileProtocol) != string::npos;
384     if (cygwinPath[isFileProtocol ? 7 : 0] != '/')  // ensure path is absolute
385         return false;
386
387     // Get the Root path.
388     WCHAR rootPath[MAX_PATH];
389     DWORD rootPathSize = _countof(rootPath);
390     DWORD keyType;
391     DWORD result = ::SHGetValueW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Cygnus Solutions\\Cygwin\\mounts v2\\/"), TEXT("native"), &keyType, &rootPath, &rootPathSize);
392     
393     if (result != ERROR_SUCCESS || keyType != REG_SZ)
394         return false;
395
396     windowsPath = wstring(rootPath, rootPathSize);
397
398     int oldPos = isFileProtocol ? 8 : 1;
399     while (1) {
400         int newPos = cygwinPath.find('/', oldPos);
401
402         if (newPos == -1) {
403             wstring pathComponent = cygwinPath.substr(oldPos);
404
405             if (!appendComponentToPath(windowsPath, pathComponent))
406                return false;
407
408             if (!followShortcuts(windowsPath))
409                 return false;
410
411             break;
412         }
413
414         wstring pathComponent = cygwinPath.substr(oldPos, newPos - oldPos);
415         if (!appendComponentToPath(windowsPath, pathComponent))
416             return false;
417
418         if (!followShortcuts(windowsPath))
419             return false;
420
421         oldPos = newPos + 1;
422     }
423
424     if (isFileProtocol)
425         windowsPath = fileProtocol + windowsPath;
426
427     return true;
428 }
429
430 static wstring cfStringRefToWString(CFStringRef cfStr)
431 {
432     Vector<wchar_t> v(CFStringGetLength(cfStr));
433     CFStringGetCharacters(cfStr, CFRangeMake(0, CFStringGetLength(cfStr)), (UniChar *)v.data());
434
435     return wstring(v.data(), v.size());
436 }
437
438 void LayoutTestController::setUserStyleSheetLocation(JSStringRef jsURL)
439 {
440     COMPtr<IWebView> webView;
441     if (FAILED(frame->webView(&webView)))
442         return;
443
444     COMPtr<IWebPreferences> preferences;
445     if (FAILED(webView->preferences(&preferences)))
446         return;
447
448     RetainPtr<CFStringRef> urlString(AdoptCF, JSStringCopyCFString(0, jsURL));
449     RetainPtr<CFURLRef> url(AdoptCF, CFURLCreateWithString(0, urlString.get(), 0));
450     if (!url)
451         return;
452
453     // Now copy the file system path, POSIX style.
454     RetainPtr<CFStringRef> pathCF(AdoptCF, CFURLCopyFileSystemPath(url.get(), kCFURLPOSIXPathStyle));
455     if (!pathCF)
456         return;
457
458     wstring path = cfStringRefToWString(pathCF.get());
459
460     wstring resultPath;
461     if (!resolveCygwinPath(path, resultPath))
462         return;
463
464     // The path has been resolved, now convert it back to a CFURL.
465     int result = WideCharToMultiByte(CP_UTF8, 0, resultPath.c_str(), resultPath.size() + 1, 0, 0, 0, 0);
466     Vector<char> utf8Vector(result);
467     result = WideCharToMultiByte(CP_UTF8, 0, resultPath.c_str(), resultPath.size() + 1, utf8Vector.data(), result, 0, 0);
468     if (!result)
469         return;
470
471     url = CFURLCreateFromFileSystemRepresentation(0, (const UInt8*)utf8Vector.data(), utf8Vector.size() - 1, false);
472     if (!url)
473         return;
474
475     resultPath = cfStringRefToWString(CFURLGetString(url.get()));
476
477     BSTR resultPathBSTR = SysAllocStringLen(resultPath.data(), resultPath.size());
478     preferences->setUserStyleSheetLocation(resultPathBSTR);
479     SysFreeString(resultPathBSTR);
480 }
481
482 void LayoutTestController::setPersistentUserStyleSheetLocation(JSStringRef jsURL)
483 {
484     RetainPtr<CFStringRef> urlString(AdoptCF, JSStringCopyCFString(0, jsURL));
485     ::setPersistentUserStyleSheetLocation(urlString.get());
486 }
487
488 void LayoutTestController::clearPersistentUserStyleSheet()
489 {
490     ::setPersistentUserStyleSheetLocation(0);
491 }
492
493 void LayoutTestController::setWindowIsKey(bool flag)
494 {
495     COMPtr<IWebView> webView;
496     if (FAILED(frame->webView(&webView)))
497         return;
498
499     COMPtr<IWebViewPrivate> viewPrivate;
500     if (FAILED(webView->QueryInterface(&viewPrivate)))
501         return;
502
503     HWND webViewWindow;
504     if (FAILED(viewPrivate->viewWindow((OLE_HANDLE*)&webViewWindow)))
505         return;
506
507     ::SendMessage(webViewWindow, flag ? WM_SETFOCUS : WM_KILLFOCUS, (WPARAM)::GetDesktopWindow(), 0);
508 }
509
510 static const CFTimeInterval waitToDumpWatchdogInterval = 10.0;
511
512 static void waitUntilDoneWatchdogFired(CFRunLoopTimerRef timer, void* info)
513 {
514     const char* message = "FAIL: Timed out waiting for notifyDone to be called\n";
515     fprintf(stderr, message);
516     fprintf(stdout, message);
517     dump();
518 }
519
520 void LayoutTestController::setWaitToDump(bool waitUntilDone)
521 {
522     // Same as on mac.  This can be shared.
523     m_waitToDump = waitUntilDone;
524     if (m_waitToDump && !waitToDumpWatchdog) {
525         waitToDumpWatchdog = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + waitToDumpWatchdogInterval, 0, 0, 0, waitUntilDoneWatchdogFired, NULL);
526         CFRunLoopAddTimer(CFRunLoopGetCurrent(), waitToDumpWatchdog, kCFRunLoopCommonModes);
527     }
528 }
529
530 int LayoutTestController::windowCount()
531 {
532     return openWindows().size();
533 }
534
535 bool LayoutTestController::elementDoesAutoCompleteForElementWithId(JSStringRef id)
536 {
537     // FIXME: Implement this almost exactly like the Mac version
538
539     return false;
540 }
541
542 void LayoutTestController::execCommand(JSStringRef name, JSStringRef value)
543 {
544     wstring wName = jsStringRefToWString(name);
545     wstring wValue = jsStringRefToWString(value);
546
547     COMPtr<IWebView> webView;
548     if (FAILED(frame->webView(&webView)))
549         return;
550
551     COMPtr<IWebViewPrivate> viewPrivate;
552     if (FAILED(webView->QueryInterface(&viewPrivate)))
553         return;
554
555     BSTR nameBSTR = SysAllocStringLen((OLECHAR*)wName.c_str(), wName.length());
556     BSTR valueBSTR = SysAllocStringLen((OLECHAR*)wValue.c_str(), wValue.length());
557     viewPrivate->executeCoreCommandByName(nameBSTR, valueBSTR);
558
559     SysFreeString(nameBSTR);
560     SysFreeString(valueBSTR);
561 }
562
563 void LayoutTestController::clearAllDatabases()
564 {
565     printf("ERROR: LayoutTestController::clearAllDatabases() not implemented\n");
566 }
567  
568 void LayoutTestController::setDatabaseQuota(unsigned long long quota)
569 {    
570     printf("ERROR: LayoutTestController::setDatabaseQuota() not implemented\n");
571 }