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 | auto 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 | auto 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(reinterpret_cast<const UChar*>(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 | |
212 | |