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