1 /*
2 * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3 *
4 * See the LICENSE file for terms of use.
5 */
6
7 #include <boost/pool/pool.hpp>
8
9 #include "Wt/WSignal.h"
10 #include "Wt/WApplication.h"
11 #include "Wt/WStatelessSlot.h"
12 #include "Wt/WJavaScriptSlot.h"
13
14 #include "WebUtils.h"
15 #include "WebSession.h"
16
17 namespace Wt {
18
SignalBase()19 SignalBase::SignalBase()
20 { }
21
~SignalBase()22 SignalBase::~SignalBase()
23 { }
24
EventSignalBase(const char * name,WObject * owner,bool autoLearn)25 EventSignalBase::EventSignalBase(const char *name, WObject *owner,
26 bool autoLearn)
27 : name_(name), owner_(owner), id_(nextId_++)
28 {
29 if (!name_)
30 flags_.set(BIT_SIGNAL_SERVER_ANYWAY);
31
32 if (autoLearn)
33 flags_.set(BIT_CAN_AUTOLEARN); // requires sender is a WWidget !
34 }
35
alloc()36 void *EventSignalBase::alloc()
37 {
38 return WApplication::instance()->eventSignalPool_->malloc();
39 }
40
free(void * s)41 void EventSignalBase::free(void *s)
42 {
43 if (s)
44 WApplication::instance()->eventSignalPool_->free(s);
45 }
46
47 EventSignalBase
StatelessConnection(const Wt::Signals::connection & c,WObject * t,WStatelessSlot * s)48 ::StatelessConnection::StatelessConnection(const Wt::Signals::connection& c,
49 WObject *t,
50 WStatelessSlot *s)
51 : connection(c),
52 target(t),
53 slot(s)
54 { }
55
ok()56 bool EventSignalBase::StatelessConnection::ok() const
57 {
58 return target == nullptr || connection.isConnected();
59 }
60
61 #ifdef WT_THREADED
62 std::atomic<unsigned> EventSignalBase::nextId_(0);
63 #else
64 unsigned EventSignalBase::nextId_ = 0;
65 #endif // WT_THREADED
66
67
needsUpdate(bool all)68 bool EventSignalBase::needsUpdate(bool all) const
69 {
70 return (!all && flags_.test(BIT_NEED_UPDATE))
71 || (all &&
72 (isConnected() || defaultActionPrevented() || propagationPrevented()));
73 }
74
updateOk()75 void EventSignalBase::updateOk()
76 {
77 flags_.set(BIT_NEED_UPDATE, false);
78 }
79
removeSlot(WStatelessSlot * s)80 void EventSignalBase::removeSlot(WStatelessSlot *s)
81 {
82 for (unsigned i = 0; i < connections_.size(); ++i) {
83 if (connections_[i].slot == s) {
84 connections_.erase(connections_.begin() + i);
85 ownerRepaint();
86 return;
87 }
88 }
89
90 assert(false);
91 }
92
encodeCmd()93 const std::string EventSignalBase::encodeCmd() const
94 {
95 char buf[20];
96 buf[0] = 's';
97 Utils::utoa(id_, buf + 1, 16);
98 return std::string(buf);
99 }
100
101 const std::string
createUserEventCall(const std::string & jsObject,const std::string & jsEvent,const std::string & eventName,std::initializer_list<std::string> args)102 EventSignalBase::createUserEventCall(const std::string& jsObject,
103 const std::string& jsEvent,
104 const std::string& eventName,
105 std::initializer_list<std::string> args)
106 const
107 {
108 /*
109 * If we aren't connected yet to anything, assume we will be later to
110 * a server-side signal, and expose the signal now.
111 */
112 if (!this->isExposedSignal() && !isConnected())
113 const_cast<EventSignalBase*>(this)->exposeSignal();
114
115 WStringStream result;
116 int i = 0;
117
118 for (const std::string& a : args) {
119 if (++i == 1)
120 result << "var a";
121 else
122 result << ",a";
123 result << i << "=" << a;
124 }
125
126 if (i != 0)
127 result << ";";
128
129 result << javaScript();
130
131 if (flags_.test(BIT_SERVER_EVENT)) {
132 WApplication *app = WApplication::instance();
133
134 std::string senderId = encodeCmd();
135 senderId = senderId.substr(0, senderId.length() - eventName.length() - 1);
136
137 result << app->javaScriptClass() << ".emit('"
138 << senderId;
139
140 if (!jsObject.empty())
141 result << "', { name:'" << eventName << "', eventObject:" << jsObject
142 << ", event:" << jsEvent << "}";
143 else
144 result << "','" << eventName << "'";
145
146 for (const std::string& a : args)
147 result << "," << a;
148
149 result << ");";
150 }
151
152 return result.str();
153 }
154
javaScript()155 const std::string EventSignalBase::javaScript() const
156 {
157 std::string result = "";
158
159 for (unsigned i = 0; i < connections_.size(); ++i) {
160 if (connections_[i].ok()) {
161 if (connections_[i].slot->learned())
162 result += connections_[i].slot->javaScript();
163 }
164 }
165
166 if (defaultActionPrevented() || propagationPrevented()) {
167 result += WT_CLASS ".cancelEvent(e";
168 if (defaultActionPrevented() && propagationPrevented())
169 result += ");";
170 else if (defaultActionPrevented())
171 result += ",0x2);";
172 else
173 result += ",0x1);";
174 }
175
176 return result;
177 }
178
setNotExposed()179 void EventSignalBase::setNotExposed()
180 {
181 flags_.reset(BIT_SERVER_EVENT);
182 flags_.reset(BIT_SIGNAL_SERVER_ANYWAY);
183 }
184
185 #ifndef WT_TARGET_JAVA
disconnect(JSlot & slot)186 void EventSignalBase::disconnect(JSlot &slot)
187 {
188 slot.disconnectFrom(this);
189 }
190 #endif
191
disconnect(Wt::Signals::connection & conn)192 void EventSignalBase::disconnect(Wt::Signals::connection& conn)
193 {
194 conn.disconnect();
195
196 if (flags_.test(BIT_EXPOSED))
197 if (!isConnected()) {
198 WApplication *app = WApplication::instance();
199 app->removeExposedSignal(this);
200 flags_.reset(BIT_EXPOSED);
201 setNotExposed();
202 }
203
204 ownerRepaint();
205 }
206
isExposedSignal()207 bool EventSignalBase::isExposedSignal() const
208 {
209 return flags_.test(BIT_SERVER_EVENT);
210 }
211
canAutoLearn()212 bool EventSignalBase::canAutoLearn() const
213 {
214 return flags_.test(BIT_CAN_AUTOLEARN);
215 }
216
preventDefaultAction(bool prevent)217 void EventSignalBase::preventDefaultAction(bool prevent)
218 {
219 if (defaultActionPrevented() != prevent) {
220 flags_.set(BIT_PREVENT_DEFAULT, prevent);
221 ownerRepaint();
222 }
223 }
224
defaultActionPrevented()225 bool EventSignalBase::defaultActionPrevented() const
226 {
227 return flags_.test(BIT_PREVENT_DEFAULT);
228 }
229
preventPropagation(bool prevent)230 void EventSignalBase::preventPropagation(bool prevent)
231 {
232 if (propagationPrevented() != prevent) {
233 flags_.set(BIT_PREVENT_PROPAGATION, prevent);
234 ownerRepaint();
235 }
236 }
237
propagationPrevented()238 bool EventSignalBase::propagationPrevented() const
239 {
240 return flags_.test(BIT_PREVENT_PROPAGATION);
241 }
242
prepareDestruct()243 void EventSignalBase::prepareDestruct()
244 {
245 // uses virtual method encodeCmd()
246 if (flags_.test(BIT_EXPOSED)) {
247 WApplication *app = WApplication::instance();
248 if (app)
249 app->removeExposedSignal(this);
250 flags_.reset(BIT_EXPOSED);
251 }
252 }
253
~EventSignalBase()254 EventSignalBase::~EventSignalBase()
255 {
256 prepareDestruct();
257
258 for (unsigned i = 0; i < connections_.size(); ++i) {
259 if (connections_[i].ok())
260 if (!connections_[i].slot->removeConnection(this))
261 delete connections_[i].slot;
262 }
263 }
264
265 #ifndef WT_CNOR
266 Wt::Signals::connection
connectStateless(WObject::Method method,WObject * target,WStatelessSlot * slot)267 EventSignalBase::connectStateless(WObject::Method method,
268 WObject *target,
269 WStatelessSlot *slot)
270 {
271 Wt::Signals::connection c
272 = dummy_.connect(std::bind(method, target), target);
273 if (slot->addConnection(this))
274 connections_.push_back(StatelessConnection(c, target, slot));
275
276 ownerRepaint();
277
278 return c;
279 }
280 #endif // WT_CNOR
281
connect(JSlot & slot)282 void EventSignalBase::connect(JSlot& slot)
283 {
284 WStatelessSlot *s = slot.slotimp();
285
286 if (s->addConnection(this)) {
287 Wt::Signals::connection c;
288 connections_.push_back(StatelessConnection(c, nullptr, s));
289
290 ownerRepaint();
291 }
292 }
293
connect(const std::string & javaScript)294 void EventSignalBase::connect(const std::string& javaScript)
295 {
296 Wt::Signals::connection c;
297
298 int argc = argumentCount(); // user arguments, excluding 'e'
299
300 WStringStream ss;
301 ss << "(" << javaScript << ")(o,e";
302 for (int i = 0; i < argc; ++i)
303 ss << ",a" << (i+1);
304 ss << ");";
305
306 connections_.push_back
307 (StatelessConnection(c, nullptr, new WStatelessSlot(ss.str())));
308
309 ownerRepaint();
310 }
311
312 #ifndef WT_CNOR
isConnected()313 bool EventSignalBase::isConnected() const
314 {
315 bool result = dummy_.isConnected();
316
317 if (!result) {
318 for (unsigned i = 0; i < connections_.size(); ++i) {
319 if (connections_[i].target == nullptr)
320 return true;
321 }
322 }
323
324 return result;
325 }
326 #endif // WT_CNOR
327
exposeSignal()328 void EventSignalBase::exposeSignal()
329 {
330 /*
331 * - BIT_SERVER_EVENT indicates whether the signal invokes a server-side event
332 * - BIT_EXPOSED indicates whether the signal is in the WApplication's
333 * exposed signals list, which is used as a list of signals that require
334 * stateless slot learning (if BIT_AUTOLEARN).
335 *
336 * The difference is only signals in those widgets that are used to
337 * render a WViewWidget: they are not exposed but need learning
338 */
339
340 // cheap catch: if it generates a server event, for sure it is also exposed
341 if (flags_.test(BIT_SERVER_EVENT)) {
342 ownerRepaint();
343 return;
344 }
345
346 WApplication *app = WApplication::instance();
347
348 app->addExposedSignal(this);
349
350 flags_.set(BIT_EXPOSED);
351
352 if (app->exposeSignals())
353 flags_.set(BIT_SERVER_EVENT);
354
355 ownerRepaint();
356 }
357
ownerRepaint()358 void EventSignalBase::ownerRepaint()
359 {
360 flags_.set(BIT_NEED_UPDATE, true);
361 owner()->signalConnectionsChanged();
362 }
363
processNonLearnedStateless()364 void EventSignalBase::processNonLearnedStateless() const
365 {
366 std::vector<StatelessConnection> copy = connections_;
367
368 for (unsigned i = 0; i < copy.size(); ++i) {
369 StatelessConnection& c = copy[i];
370
371 if (c.ok() && !c.slot->learned())
372 c.slot->trigger();
373 }
374 }
375
processLearnedStateless()376 void EventSignalBase::processLearnedStateless() const
377 {
378 std::vector<StatelessConnection> copy = connections_;
379
380 for (unsigned i = 0; i < copy.size(); ++i) {
381 StatelessConnection& c = copy[i];
382
383 if (c.ok() && c.slot->learned())
384 c.slot->trigger();
385 }
386 }
387
processPreLearnStateless(SlotLearnerInterface * learner)388 void EventSignalBase::processPreLearnStateless(SlotLearnerInterface *learner)
389 {
390 std::vector<StatelessConnection> copy = connections_;
391
392 for (unsigned i = 0; i < copy.size(); ++i) {
393 StatelessConnection& c = copy[i];
394
395 if (c.ok()
396 && !c.slot->learned()
397 && c.slot->type() == WStatelessSlot::SlotType::PreLearnStateless) {
398 learner->learn(c.slot);
399 }
400 }
401 }
402
processAutoLearnStateless(SlotLearnerInterface * learner)403 void EventSignalBase::processAutoLearnStateless(SlotLearnerInterface *learner)
404 {
405 bool changed = false;
406
407 std::vector<StatelessConnection> copy = connections_;
408
409 for (unsigned i = 0; i < copy.size(); ++i) {
410 StatelessConnection& c = copy[i];
411
412 if (c.ok()
413 && !c.slot->learned()
414 && c.slot->type() == WStatelessSlot::SlotType::AutoLearnStateless) {
415 learner->learn(c.slot);
416 changed = true;
417 }
418 }
419
420 if (changed)
421 ownerRepaint();
422 }
423
424 }
425