Plug some leaks in TestController and UIScriptContext.
[WebKit-https.git] / Tools / TestRunnerShared / UIScriptContext / UIScriptContext.cpp
1 /*
2  * Copyright (C) 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  * 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 "UIScriptContext.h"
28
29 #include "UIScriptController.h"
30 #include <JavaScriptCore/JSContextRef.h>
31 #include <JavaScriptCore/JSValueRef.h>
32 #include <WebCore/FloatRect.h>
33
34 using namespace WTR;
35
36 static inline bool isPersistentCallbackID(unsigned callbackID)
37 {
38     return callbackID < firstNonPersistentCallbackID;
39 }
40
41 UIScriptContext::UIScriptContext(UIScriptContextDelegate& delegate)
42     : m_context(Adopt, JSGlobalContextCreate(nullptr))
43     , m_delegate(delegate)
44 {
45     m_controller = UIScriptController::create(*this);
46
47     JSObjectRef globalObject = JSContextGetGlobalObject(m_context.get());
48
49     JSValueRef exception = nullptr;
50     m_controller->makeWindowObject(m_context.get(), globalObject, &exception);
51 }
52
53 UIScriptContext::~UIScriptContext()
54 {
55     m_controller->checkForOutstandingCallbacks();
56     m_controller->contextDestroyed();
57 }
58
59 void UIScriptContext::runUIScript(const String& script, unsigned scriptCallbackID)
60 {
61     m_currentScriptCallbackID = scriptCallbackID;
62
63     JSRetainPtr<JSStringRef> stringRef(Adopt, JSStringCreateWithUTF8CString(script.utf8().data()));
64
65     JSValueRef exception = nullptr;
66     JSValueRef result = JSEvaluateScript(m_context.get(), stringRef.get(), 0, 0, 1, &exception);
67     
68     if (!hasOutstandingAsyncTasks()) {
69         JSValueRef stringifyException = nullptr;
70         JSRetainPtr<JSStringRef> stringified(Adopt, JSValueToStringCopy(m_context.get(), result, &stringifyException));
71         requestUIScriptCompletion(stringified.get());
72         tryToCompleteUIScriptForCurrentParentCallback();
73     }
74 }
75
76 unsigned UIScriptContext::nextTaskCallbackID(CallbackType type)
77 {
78     if (type == CallbackTypeNonPersistent)
79         return ++m_nextTaskCallbackID + firstNonPersistentCallbackID;
80
81     return type;
82 }
83
84 unsigned UIScriptContext::prepareForAsyncTask(JSValueRef callback, CallbackType type)
85 {
86     unsigned callbackID = nextTaskCallbackID(type);
87     
88     JSValueProtect(m_context.get(), callback);
89     Task task;
90     task.parentScriptCallbackID = m_currentScriptCallbackID;
91     task.callback = callback;
92
93     ASSERT(!m_callbacks.contains(callbackID));
94     m_callbacks.add(callbackID, task);
95     
96     return callbackID;
97 }
98
99 void UIScriptContext::asyncTaskComplete(unsigned callbackID)
100 {
101     Task task = m_callbacks.take(callbackID);
102     ASSERT(task.callback);
103
104     JSValueRef exception = nullptr;
105     JSObjectRef callbackObject = JSValueToObject(m_context.get(), task.callback, &exception);
106
107     m_currentScriptCallbackID = task.parentScriptCallbackID;
108
109     exception = nullptr;
110     JSObjectCallAsFunction(m_context.get(), callbackObject, JSContextGetGlobalObject(m_context.get()), 0, nullptr, &exception);
111     JSValueUnprotect(m_context.get(), task.callback);
112     
113     tryToCompleteUIScriptForCurrentParentCallback();
114     m_currentScriptCallbackID = 0;
115 }
116
117 unsigned UIScriptContext::registerCallback(JSValueRef taskCallback, CallbackType type)
118 {
119     if (m_callbacks.contains(type))
120         unregisterCallback(type);
121
122     if (JSValueIsUndefined(m_context.get(), taskCallback))
123         return 0;
124
125     return prepareForAsyncTask(taskCallback, type);
126 }
127
128 void UIScriptContext::unregisterCallback(unsigned callbackID)
129 {
130     Task task = m_callbacks.take(callbackID);
131     ASSERT(task.callback);
132     JSValueUnprotect(m_context.get(), task.callback);
133 }
134
135 JSValueRef UIScriptContext::callbackWithID(unsigned callbackID)
136 {
137     Task task = m_callbacks.get(callbackID);
138     return task.callback;
139 }
140
141 void UIScriptContext::fireCallback(unsigned callbackID)
142 {
143     Task task = m_callbacks.get(callbackID);
144     ASSERT(task.callback);
145
146     JSValueRef exception = nullptr;
147     JSObjectRef callbackObject = JSValueToObject(m_context.get(), task.callback, &exception);
148
149     m_currentScriptCallbackID = task.parentScriptCallbackID;
150
151     exception = nullptr;
152     JSObjectCallAsFunction(m_context.get(), callbackObject, JSContextGetGlobalObject(m_context.get()), 0, nullptr, &exception);
153     
154     tryToCompleteUIScriptForCurrentParentCallback();
155     m_currentScriptCallbackID = 0;
156 }
157
158 void UIScriptContext::requestUIScriptCompletion(JSStringRef result)
159 {
160     ASSERT(m_currentScriptCallbackID);
161     if (currentParentCallbackIsPendingCompletion())
162         return;
163
164     // This request for the UI script to complete is not fulfilled until the last non-persistent task for the parent callback is finished.
165     m_uiScriptResultsPendingCompletion.add(m_currentScriptCallbackID, result ? JSStringRetain(result) : nullptr);
166 }
167
168 void UIScriptContext::tryToCompleteUIScriptForCurrentParentCallback()
169 {
170     if (!currentParentCallbackIsPendingCompletion() || currentParentCallbackHasOutstandingAsyncTasks())
171         return;
172
173     JSStringRef result = m_uiScriptResultsPendingCompletion.take(m_currentScriptCallbackID);
174     String scriptResult(JSStringGetCharactersPtr(result), JSStringGetLength(result));
175
176     m_delegate.uiScriptDidComplete(scriptResult, m_currentScriptCallbackID);
177     
178     // Unregister tasks associated with this callback
179     m_callbacks.removeIf([&](auto& keyAndValue) {
180         return keyAndValue.value.parentScriptCallbackID == m_currentScriptCallbackID;
181     });
182     
183     m_currentScriptCallbackID = 0;
184     if (result)
185         JSStringRelease(result);
186 }
187
188 JSObjectRef UIScriptContext::objectFromRect(const WebCore::FloatRect& rect) const
189 {
190     JSObjectRef object = JSObjectMake(m_context.get(), nullptr, nullptr);
191
192     JSObjectSetProperty(m_context.get(), object, adopt(JSStringCreateWithUTF8CString("left")).get(), JSValueMakeNumber(m_context.get(), rect.x()), kJSPropertyAttributeNone, nullptr);
193     JSObjectSetProperty(m_context.get(), object, adopt(JSStringCreateWithUTF8CString("top")).get(), JSValueMakeNumber(m_context.get(), rect.y()), kJSPropertyAttributeNone, nullptr);
194     JSObjectSetProperty(m_context.get(), object, adopt(JSStringCreateWithUTF8CString("width")).get(), JSValueMakeNumber(m_context.get(), rect.width()), kJSPropertyAttributeNone, nullptr);
195     JSObjectSetProperty(m_context.get(), object, adopt(JSStringCreateWithUTF8CString("height")).get(), JSValueMakeNumber(m_context.get(), rect.height()), kJSPropertyAttributeNone, nullptr);
196     
197     return object;
198 }
199
200 bool UIScriptContext::currentParentCallbackHasOutstandingAsyncTasks() const
201 {
202     for (auto entry : m_callbacks) {
203         unsigned callbackID = entry.key;
204         Task task = entry.value;
205         if (task.parentScriptCallbackID == m_currentScriptCallbackID && !isPersistentCallbackID(callbackID))
206             return true;
207     }
208
209     return false;
210 }
211