2011-04-06 Chang Shu <cshu@webkit.org>
[WebKit-https.git] / Tools / WebKitTestRunner / InjectedBundle / LayoutTestController.cpp
1 /*
2  * Copyright (C) 2010 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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "LayoutTestController.h"
28
29 #include "InjectedBundle.h"
30 #include "InjectedBundlePage.h"
31 #include "JSLayoutTestController.h"
32 #include "PlatformWebView.h"
33 #include "StringFunctions.h"
34 #include "TestController.h"
35 #include <WebKit2/WKBundleBackForwardList.h>
36 #include <WebKit2/WKBundleFrame.h>
37 #include <WebKit2/WKBundleFramePrivate.h>
38 #include <WebKit2/WKBundleInspector.h>
39 #include <WebKit2/WKBundleNodeHandlePrivate.h>
40 #include <WebKit2/WKBundlePagePrivate.h>
41 #include <WebKit2/WKBundlePrivate.h>
42 #include <WebKit2/WKBundleScriptWorld.h>
43 #include <WebKit2/WKRetainPtr.h>
44 #include <WebKit2/WebKit2.h>
45 #include <wtf/HashMap.h>
46
47 namespace WTR {
48
49 // This is lower than DumpRenderTree's timeout, to make it easier to work through the failures
50 // Eventually it should be changed to match.
51 const double LayoutTestController::waitToDumpWatchdogTimerInterval = 6;
52
53 static JSValueRef propertyValue(JSContextRef context, JSObjectRef object, const char* propertyName)
54 {
55     if (!object)
56         return 0;
57     JSRetainPtr<JSStringRef> propertyNameString(Adopt, JSStringCreateWithUTF8CString(propertyName));
58     JSValueRef exception;
59     return JSObjectGetProperty(context, object, propertyNameString.get(), &exception);
60 }
61
62 static JSObjectRef propertyObject(JSContextRef context, JSObjectRef object, const char* propertyName)
63 {
64     JSValueRef value = propertyValue(context, object, propertyName);
65     if (!value || !JSValueIsObject(context, value))
66         return 0;
67     return const_cast<JSObjectRef>(value);
68 }
69
70 static JSObjectRef getElementById(WKBundleFrameRef frame, JSStringRef elementId)
71 {
72     JSContextRef context = WKBundleFrameGetJavaScriptContext(frame);
73     JSObjectRef document = propertyObject(context, JSContextGetGlobalObject(context), "document");
74     if (!document)
75         return 0;
76     JSValueRef getElementById = propertyObject(context, document, "getElementById");
77     if (!getElementById || !JSValueIsObject(context, getElementById))
78         return 0;
79     JSValueRef elementIdValue = JSValueMakeString(context, elementId);
80     JSValueRef exception;
81     JSValueRef element = JSObjectCallAsFunction(context, const_cast<JSObjectRef>(getElementById), document, 1, &elementIdValue, &exception);
82     if (!element || !JSValueIsObject(context, element))
83         return 0;
84     return const_cast<JSObjectRef>(element);
85 }
86
87 PassRefPtr<LayoutTestController> LayoutTestController::create()
88 {
89     return adoptRef(new LayoutTestController);
90 }
91
92 LayoutTestController::LayoutTestController()
93     : m_whatToDump(RenderTree)
94     , m_shouldDumpAllFrameScrollPositions(false)
95     , m_shouldDumpBackForwardListsForAllWindows(false)
96     , m_shouldAllowEditing(true)
97     , m_shouldCloseExtraWindows(false)
98     , m_dumpEditingCallbacks(false)
99     , m_dumpStatusCallbacks(false)
100     , m_dumpTitleChanges(false)
101     , m_dumpPixels(true)
102     , m_waitToDump(false)
103     , m_testRepaint(false)
104     , m_testRepaintSweepHorizontally(false)
105     , m_willSendRequestReturnsNull(false)
106 {
107     platformInitialize();
108 }
109
110 LayoutTestController::~LayoutTestController()
111 {
112 }
113
114 JSClassRef LayoutTestController::wrapperClass()
115 {
116     return JSLayoutTestController::layoutTestControllerClass();
117 }
118
119 void LayoutTestController::display()
120 {
121     // FIXME: actually implement, once we want pixel tests
122 }
123
124 void LayoutTestController::dumpAsText()
125 {
126     m_whatToDump = MainFrameText;
127     m_dumpPixels = false;
128 }
129     
130 void LayoutTestController::waitUntilDone()
131 {
132     m_waitToDump = true;
133     initializeWaitToDumpWatchdogTimerIfNeeded();
134 }
135
136 void LayoutTestController::waitToDumpWatchdogTimerFired()
137 {
138     invalidateWaitToDumpWatchdogTimer();
139     const char* message = "FAIL: Timed out waiting for notifyDone to be called\n";
140     InjectedBundle::shared().os() << message << "\n";
141     InjectedBundle::shared().done();
142 }
143
144 void LayoutTestController::notifyDone()
145 {
146     if (!InjectedBundle::shared().isTestRunning())
147         return;
148
149     if (m_waitToDump && !InjectedBundle::shared().topLoadingFrame())
150         InjectedBundle::shared().page()->dump();
151
152     m_waitToDump = false;
153 }
154
155 unsigned LayoutTestController::numberOfActiveAnimations() const
156 {
157     // FIXME: Is it OK this works only for the main frame?
158     // FIXME: If this is needed only for the main frame, then why is the function on WKBundleFrame instead of WKBundlePage?
159     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
160     return WKBundleFrameGetNumberOfActiveAnimations(mainFrame);
161 }
162
163 bool LayoutTestController::pauseAnimationAtTimeOnElementWithId(JSStringRef animationName, double time, JSStringRef elementId)
164 {
165     // FIXME: Is it OK this works only for the main frame?
166     // FIXME: If this is needed only for the main frame, then why is the function on WKBundleFrame instead of WKBundlePage?
167     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
168     return WKBundleFramePauseAnimationOnElementWithId(mainFrame, toWK(animationName).get(), toWK(elementId).get(), time);
169 }
170
171 void LayoutTestController::suspendAnimations()
172 {
173     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
174     WKBundleFrameSuspendAnimations(mainFrame);
175 }
176
177 void LayoutTestController::resumeAnimations()
178 {
179     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
180     WKBundleFrameResumeAnimations(mainFrame);
181 }
182
183 JSRetainPtr<JSStringRef> LayoutTestController::layerTreeAsText() const
184 {
185     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
186     WKRetainPtr<WKStringRef> text(AdoptWK, WKBundleFrameCopyLayerTreeAsText(mainFrame));
187     return toJS(text);
188 }
189
190 void LayoutTestController::addUserScript(JSStringRef source, bool runAtStart, bool allFrames)
191 {
192     WKRetainPtr<WKStringRef> sourceWK = toWK(source);
193     WKRetainPtr<WKBundleScriptWorldRef> scriptWorld(AdoptWK, WKBundleScriptWorldCreateWorld());
194
195     WKBundleAddUserScript(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), scriptWorld.get(), sourceWK.get(), 0, 0, 0,
196         (runAtStart ? kWKInjectAtDocumentStart : kWKInjectAtDocumentEnd),
197         (allFrames ? kWKInjectInAllFrames : kWKInjectInTopFrameOnly));
198 }
199
200 void LayoutTestController::addUserStyleSheet(JSStringRef source, bool allFrames)
201 {
202     WKRetainPtr<WKStringRef> sourceWK = toWK(source);
203     WKRetainPtr<WKBundleScriptWorldRef> scriptWorld(AdoptWK, WKBundleScriptWorldCreateWorld());
204
205     WKBundleAddUserStyleSheet(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), scriptWorld.get(), sourceWK.get(), 0, 0, 0,
206         (allFrames ? kWKInjectInAllFrames : kWKInjectInTopFrameOnly));
207 }
208
209 void LayoutTestController::keepWebHistory()
210 {
211     WKBundleSetShouldTrackVisitedLinks(InjectedBundle::shared().bundle(), true);
212 }
213
214 JSValueRef LayoutTestController::computedStyleIncludingVisitedInfo(JSValueRef element)
215 {
216     // FIXME: Is it OK this works only for the main frame?
217     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
218     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
219     if (!JSValueIsObject(context, element))
220         return JSValueMakeUndefined(context);
221     JSValueRef value = WKBundleFrameGetComputedStyleIncludingVisitedInfo(mainFrame, const_cast<JSObjectRef>(element));
222     if (!value)
223         return JSValueMakeUndefined(context);
224     return value;
225 }
226
227 JSRetainPtr<JSStringRef> LayoutTestController::counterValueForElementById(JSStringRef elementId)
228 {
229     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
230     JSObjectRef element = getElementById(mainFrame, elementId);
231     if (!element)
232         return 0;
233     WKRetainPtr<WKStringRef> value(AdoptWK, WKBundleFrameCopyCounterValue(mainFrame, const_cast<JSObjectRef>(element)));
234     return toJS(value);
235 }
236
237 JSRetainPtr<JSStringRef> LayoutTestController::markerTextForListItem(JSValueRef element)
238 {
239     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
240     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
241     if (!element || !JSValueIsObject(context, element))
242         return 0;
243     WKRetainPtr<WKStringRef> text(AdoptWK, WKBundleFrameCopyMarkerText(mainFrame, const_cast<JSObjectRef>(element)));
244     if (WKStringIsEmpty(text.get()))
245         return 0;
246     return toJS(text);
247 }
248
249 void LayoutTestController::execCommand(JSStringRef name, JSStringRef argument)
250 {
251     WKBundlePageExecuteEditingCommand(InjectedBundle::shared().page()->page(), toWK(name).get(), toWK(argument).get());
252 }
253
254 bool LayoutTestController::findString(JSStringRef target, JSValueRef optionsArrayAsValue)
255 {
256     WKFindOptions options = 0;
257
258     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
259     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
260     JSRetainPtr<JSStringRef> lengthPropertyName(Adopt, JSStringCreateWithUTF8CString("length"));
261     JSObjectRef optionsArray = JSValueToObject(context, optionsArrayAsValue, 0);
262     JSValueRef lengthValue = JSObjectGetProperty(context, optionsArray, lengthPropertyName.get(), 0);
263     if (!JSValueIsNumber(context, lengthValue))
264         return false;
265
266     size_t length = static_cast<size_t>(JSValueToNumber(context, lengthValue, 0));
267     for (size_t i = 0; i < length; ++i) {
268         JSValueRef value = JSObjectGetPropertyAtIndex(context, optionsArray, i, 0);
269         if (!JSValueIsString(context, value))
270             continue;
271
272         JSRetainPtr<JSStringRef> optionName(Adopt, JSValueToStringCopy(context, value, 0));
273
274         if (JSStringIsEqualToUTF8CString(optionName.get(), "CaseInsensitive"))
275             options |= kWKFindOptionsCaseInsensitive;
276         else if (JSStringIsEqualToUTF8CString(optionName.get(), "AtWordStarts"))
277             options |= kWKFindOptionsAtWordStarts;
278         else if (JSStringIsEqualToUTF8CString(optionName.get(), "TreatMedialCapitalAsWordStart"))
279             options |= kWKFindOptionsTreatMedialCapitalAsWordStart;
280         else if (JSStringIsEqualToUTF8CString(optionName.get(), "Backwards"))
281             options |= kWKFindOptionsBackwards;
282         else if (JSStringIsEqualToUTF8CString(optionName.get(), "WrapAround"))
283             options |= kWKFindOptionsWrapAround;
284         else if (JSStringIsEqualToUTF8CString(optionName.get(), "StartInSelection")) {
285             // FIXME: No kWKFindOptionsStartInSelection.
286         }
287     }
288
289     return WKBundlePageFindString(InjectedBundle::shared().page()->page(), toWK(target).get(), options);
290 }
291
292 void LayoutTestController::clearAllDatabases()
293 {
294     WKBundleClearAllDatabases(InjectedBundle::shared().bundle());
295 }
296
297 void LayoutTestController::setDatabaseQuota(uint64_t quota)
298 {
299     return WKBundleSetDatabaseQuota(InjectedBundle::shared().bundle(), quota);
300 }
301
302 bool LayoutTestController::isCommandEnabled(JSStringRef name)
303 {
304     return WKBundlePageIsEditingCommandEnabled(InjectedBundle::shared().page()->page(), toWK(name).get());
305 }
306
307 void LayoutTestController::setCanOpenWindows(bool)
308 {
309     // It's not clear if or why any tests require opening windows be forbidden.
310     // For now, just ignore this setting, and if we find later it's needed we can add it.
311 }
312
313 void LayoutTestController::setXSSAuditorEnabled(bool enabled)
314 {
315     WKBundleOverrideXSSAuditorEnabledForTestRunner(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), true);
316 }
317
318 void LayoutTestController::setAllowUniversalAccessFromFileURLs(bool enabled)
319 {
320     WKBundleOverrideAllowUniversalAccessFromFileURLsForTestRunner(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
321 }
322
323 void LayoutTestController::setAllowFileAccessFromFileURLs(bool enabled)
324 {
325     WKBundleSetAllowFileAccessFromFileURLs(InjectedBundle::shared().bundle(), InjectedBundle::shared().pageGroup(), enabled);
326 }
327
328 int LayoutTestController::numberOfPages(double pageWidthInPixels, double pageHeightInPixels)
329 {
330     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
331     return WKBundleNumberOfPages(InjectedBundle::shared().bundle(), mainFrame, pageWidthInPixels, pageHeightInPixels);
332 }
333
334 int LayoutTestController::pageNumberForElementById(JSStringRef id, double pageWidthInPixels, double pageHeightInPixels)
335 {
336     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
337     return WKBundlePageNumberForElementById(InjectedBundle::shared().bundle(), mainFrame, toWK(id).get(), pageWidthInPixels, pageHeightInPixels);
338 }
339
340 JSRetainPtr<JSStringRef> LayoutTestController::pageSizeAndMarginsInPixels(int pageIndex, int width, int height, int marginTop, int marginRight, int marginBottom, int marginLeft)
341 {
342     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
343     return toJS(WKBundlePageSizeAndMarginsInPixels(InjectedBundle::shared().bundle(), mainFrame, pageIndex, width, height, marginTop, marginRight, marginBottom, marginLeft));
344 }
345
346 unsigned LayoutTestController::windowCount()
347 {
348     return InjectedBundle::shared().pageCount();
349 }
350
351 JSValueRef LayoutTestController::shadowRoot(JSValueRef element)
352 {
353     WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
354     JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
355
356     if (!element || !JSValueIsObject(context, element))
357         return JSValueMakeNull(context);
358
359     WKRetainPtr<WKBundleNodeHandleRef> domElement = adoptWK(WKBundleNodeHandleCreate(context, const_cast<JSObjectRef>(element)));
360     if (!domElement)
361         return JSValueMakeNull(context);
362
363     WKRetainPtr<WKBundleNodeHandleRef> shadowRootDOMElement = adoptWK(WKBundleNodeHandleCopyElementShadowRoot(domElement.get()));
364     if (!shadowRootDOMElement)
365         return JSValueMakeNull(context);
366
367     return WKBundleFrameGetJavaScriptWrapperForNodeForWorld(mainFrame, shadowRootDOMElement.get(), WKBundleScriptWorldNormalWorld());
368 }
369
370 void LayoutTestController::clearBackForwardList()
371 {
372     WKBundleBackForwardListClear(WKBundlePageGetBackForwardList(InjectedBundle::shared().page()->page()));
373 }
374
375 // Object Creation
376
377 void LayoutTestController::makeWindowObject(JSContextRef context, JSObjectRef windowObject, JSValueRef* exception)
378 {
379     setProperty(context, windowObject, "layoutTestController", this, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, exception);
380 }
381
382 void LayoutTestController::showWebInspector()
383 {
384     WKBundleInspectorShow(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()));
385 }
386
387 void LayoutTestController::closeWebInspector()
388 {
389     WKBundleInspectorClose(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()));
390 }
391
392 void LayoutTestController::evaluateInWebInspector(long callID, JSStringRef script)
393 {
394     WKRetainPtr<WKStringRef> scriptWK = toWK(script);
395     WKBundleInspectorEvaluateScriptForTest(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()), callID, scriptWK.get());
396 }
397
398 void LayoutTestController::setTimelineProfilingEnabled(bool enabled)
399 {
400     WKBundleInspectorSetPageProfilingEnabled(WKBundlePageGetInspector(InjectedBundle::shared().page()->page()), enabled);
401 }
402
403 typedef WTF::HashMap<unsigned, WKRetainPtr<WKBundleScriptWorldRef> > WorldMap;
404 static WorldMap& worldMap()
405 {
406     static WorldMap& map = *new WorldMap;
407     return map;
408 }
409
410 unsigned LayoutTestController::worldIDForWorld(WKBundleScriptWorldRef world)
411 {
412     WorldMap::const_iterator end = worldMap().end();
413     for (WorldMap::const_iterator it = worldMap().begin(); it != end; ++it) {
414         if (it->second == world)
415             return it->first;
416     }
417
418     return 0;
419 }
420
421 void LayoutTestController::evaluateScriptInIsolatedWorld(JSContextRef context, unsigned worldID, JSStringRef script)
422 {
423     // A worldID of 0 always corresponds to a new world. Any other worldID corresponds to a world
424     // that is created once and cached forever.
425     WKRetainPtr<WKBundleScriptWorldRef> world;
426     if (!worldID)
427         world.adopt(WKBundleScriptWorldCreateWorld());
428     else {
429         WKRetainPtr<WKBundleScriptWorldRef>& worldSlot = worldMap().add(worldID, 0).first->second;
430         if (!worldSlot)
431             worldSlot.adopt(WKBundleScriptWorldCreateWorld());
432         world = worldSlot;
433     }
434
435     WKBundleFrameRef frame = WKBundleFrameForJavaScriptContext(context);
436     if (!frame)
437         frame = WKBundlePageGetMainFrame(InjectedBundle::shared().page()->page());
438
439     JSGlobalContextRef jsContext = WKBundleFrameGetJavaScriptContextForWorld(frame, world.get());
440     JSEvaluateScript(jsContext, script, 0, 0, 0, 0); 
441 }
442
443 void LayoutTestController::setPOSIXLocale(JSStringRef locale)
444 {
445     char localeBuf[32];
446     JSStringGetUTF8CString(locale, localeBuf, sizeof(localeBuf));
447     setlocale(LC_ALL, localeBuf);
448 }
449
450 } // namespace WTR