1 // AsBroadcaster.cpp - AsBroadcaster AS interface
2 //
3 //   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 //   Free Software Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19 //
20 
21 #include "AsBroadcaster.h"
22 
23 #include "Array_as.h" // for _listeners construction
24 #include "log.h"
25 #include "fn_call.h"
26 #include "NativeFunction.h"
27 #include "Global_as.h"
28 #include "namedStrings.h"
29 #include "ObjectURI.h"
30 
31 //#define GNASH_DEBUG_BROADCASTER 1
32 
33 #ifdef GNASH_DEBUG_BROADCASTER
34 # include "Stats.h"
35 #endif
36 
37 namespace gnash {
38 
39 // Forward declarations.
40 namespace {
41     as_value asbroadcaster_addListener(const fn_call& fn);
42     as_value asbroadcaster_removeListener(const fn_call& fn);
43     as_value asbroadcaster_broadcastMessage(const fn_call& fn);
44     as_value asbroadcaster_initialize(const fn_call& fn);
45 }
46 
47 
48 /// Helper for notifying listeners
49 namespace {
50 
51 #ifdef GNASH_DEBUG_BROADCASTER
52 struct BroadcasterStats {
53     typedef std::map<ObjectURI&, unsigned long int> Stat;
54     Stat stat;
55     const VM& _st;
BroadcasterStatsgnash::__anon25c706e80211::BroadcasterStats56     BroadcasterStats(const VM& vm) : _st(st) {}
checkgnash::__anon25c706e80211::BroadcasterStats57     void check(ObjectURI& k) {
58         if ( ! (++stat[k] % 100) ) dump();
59     }
dumpgnash::__anon25c706e80211::BroadcasterStats60     void dump() {
61         using namespace std;
62         typedef std::map<unsigned long int, ObjectURI&> Sorted;
63         Sorted sorted;
64         for (Stat::iterator i=stat.begin(), e=stat.end(); i!=e; ++i)
65             sorted[i->second] = i->first;
66         cerr << "Broadcaster stats follow:" << endl;
67         for (Sorted::reverse_iterator i=sorted.rbegin(), e=sorted.rend();
68                 i!=e; ++i)
69             std::cerr
70                       << std::setw(10)
71                       << i->first
72                       << ":"
73                       << _st.value(i->second) << "("
74                       << i->second << ")"
75                       << std::endl;
76 
77     }
78 };
79 #endif // GNASH_DEBUG_BROADCASTER
80 
81 class BroadcasterVisitor
82 {
83 public:
84 
85     /// @param eName name of event, will be converted to lowercase if needed
86     ///
87     /// @param env Environment to use for functions invocation.
88     ///
BroadcasterVisitor(const fn_call & fn)89     BroadcasterVisitor(const fn_call& fn)
90         :
91         _eventURI(getURI(getVM(fn),fn.arg(0).to_string())),
92         _dispatched(0),
93         _fn(fn)
94     {
95         _fn.drop_bottom();
96     }
97 
98     /// Call a method on the given value
operator ()(const as_value & v)99     void operator()(const as_value& v)
100     {
101 
102         as_object* o = toObject(v, getVM(_fn));
103         if (!o) return;
104 
105 #ifdef GNASH_DEBUG_BROADCASTER
106         static stats::KeyLookup stats("BroadcasterVisitor call operator",
107             getVM(_fn), 1);
108         stats.check(_eventURI.name);
109 #endif
110         as_value method;
111         o->get_member(_eventURI, &method);
112 
113         if (method.is_function()) {
114             _fn.super = o->get_super(_eventURI);
115             _fn.this_ptr = o;
116             method.to_function()->call(_fn);
117         }
118 
119         ++_dispatched;
120     }
121 
122     /// Return number of events dispatched.
eventsDispatched() const123     size_t eventsDispatched() const { return _dispatched; }
124 
125 private:
126 
127     /// Name of the event being broadcasted
128     /// appropriately cased based on SWF version
129     /// of the current VM
130     const ObjectURI _eventURI;
131 
132     /// Number of event dispatches
133     size_t _dispatched;
134 
135     fn_call _fn;
136 
137 };
138 
139 }
140 
141 /// AsBroadcaster class
142 
143 
144 void
initialize(as_object & o)145 AsBroadcaster::initialize(as_object& o)
146 {
147     Global_as& gl = getGlobal(o);
148 
149     // Find _global.AsBroadcaster.
150     as_object* asb = toObject(
151             getMember(gl, NSV::CLASS_AS_BROADCASTER), getVM(o));
152 
153     // If it's not an object, these are left undefined, but they are
154     // always attached to the initialized object.
155     as_value al, rl;
156 
157     const int flags = as_object::DefaultFlags;
158 
159     if (asb) {
160         al = getMember(*asb, NSV::PROP_ADD_LISTENER);
161         rl = getMember(*asb, NSV::PROP_REMOVE_LISTENER);
162     }
163 
164     o.set_member(NSV::PROP_ADD_LISTENER, al);
165     o.set_member(NSV::PROP_REMOVE_LISTENER, rl);
166 
167     // The function returned by ASnative(101, 12) is attached, even though
168     // this may not exist (e.g. if _global.ASnative is altered)
169     const as_value& asn = callMethod(&gl, NSV::PROP_AS_NATIVE, 101, 12);
170     o.set_member(NSV::PROP_BROADCAST_MESSAGE, asn);
171 
172     // This corresponds to  "_listeners = [];", which is different from
173     // _listeners = new Array();
174     o.set_member(NSV::PROP_uLISTENERS, gl.createArray());
175 
176     // This function should call ASSetPropFlags on these four properties.
177     o.set_member_flags(NSV::PROP_BROADCAST_MESSAGE, flags);
178     o.set_member_flags(NSV::PROP_ADD_LISTENER, flags);
179     o.set_member_flags(NSV::PROP_REMOVE_LISTENER, flags);
180     o.set_member_flags(NSV::PROP_uLISTENERS, flags);
181 
182 }
183 
184 void
attachAsBroadcasterStaticInterface(as_object & o)185 attachAsBroadcasterStaticInterface(as_object& o)
186 {
187     const int flags = PropFlags::dontEnum |
188                       PropFlags::dontDelete |
189                       PropFlags::onlySWF6Up;
190 
191     Global_as& gl = getGlobal(o);
192 
193     o.init_member("initialize",
194             gl.createFunction(asbroadcaster_initialize), flags);
195     o.init_member(NSV::PROP_ADD_LISTENER,
196             gl.createFunction(asbroadcaster_addListener), flags);
197     o.init_member(NSV::PROP_REMOVE_LISTENER,
198             gl.createFunction(asbroadcaster_removeListener), flags);
199 
200     VM& vm = getVM(o);
201     o.init_member(NSV::PROP_BROADCAST_MESSAGE, vm.getNative(101, 12),
202             flags);
203 
204 }
205 
206 
207 void
registerNative(as_object & global)208 AsBroadcaster::registerNative(as_object& global)
209 {
210     VM& vm = getVM(global);
211     vm.registerNative(asbroadcaster_broadcastMessage, 101, 12);
212 }
213 
214 
215 void
init(as_object & where,const ObjectURI & uri)216 AsBroadcaster::init(as_object& where, const ObjectURI& uri)
217 {
218     // AsBroadcaster is a class, even though it doesn't look much like one.
219     // Its prototype has no properties.
220     registerBuiltinClass(where, emptyFunction, nullptr,
221             attachAsBroadcasterStaticInterface, uri);
222 }
223 
224 
225 namespace {
226 
227 as_value
asbroadcaster_initialize(const fn_call & fn)228 asbroadcaster_initialize(const fn_call& fn)
229 {
230     if ( fn.nargs < 1 )
231     {
232         IF_VERBOSE_ASCODING_ERRORS(
233         log_aserror(_("AsBroadcaster.initialize() requires one argument, "
234                 "none given"));
235         );
236         return as_value();
237     }
238 
239     // TODO: check if automatic primitive to object conversion apply here
240     const as_value& tgtval = fn.arg(0);
241     if (!tgtval.is_object()) {
242         IF_VERBOSE_ASCODING_ERRORS(
243         log_aserror(_("AsBroadcaster.initialize(%s): first arg is "
244                 "not an object"), tgtval);
245         );
246         return as_value();
247     }
248 
249     as_object* tgt = toObject(tgtval, getVM(fn));
250     if (!tgt) {
251         IF_VERBOSE_ASCODING_ERRORS(
252         log_aserror(_("AsBroadcaster.initialize(%s): first arg is an object"
253             " but doesn't cast to one (dangling DisplayObject ref?)"), tgtval);
254         );
255         return as_value();
256     }
257 
258     AsBroadcaster::initialize(*tgt);
259 
260     return as_value();
261 }
262 as_value
asbroadcaster_addListener(const fn_call & fn)263 asbroadcaster_addListener(const fn_call& fn)
264 {
265 
266     as_object* obj = ensure<ValidThis>(fn);
267 
268     as_value newListener; assert(newListener.is_undefined());
269     if ( fn.nargs ) newListener = fn.arg(0);
270 
271     callMethod(obj, NSV::PROP_REMOVE_LISTENER, newListener);
272 
273     as_value listenersValue;
274 
275     // TODO: test if we're supposed to crawl the target object's
276     //       inheritance chain in case it's own property _listeners
277     //       has been deleted while another one is found in any base
278     //       class.
279     if (!obj->get_member(NSV::PROP_uLISTENERS, &listenersValue)) {
280         IF_VERBOSE_ASCODING_ERRORS(
281             std::ostringstream ss; fn.dump_args(ss);
282             log_aserror(_("%p.addListener(%s): this object has no "
283                     "_listeners member"), (void*)fn.this_ptr, ss.str());
284         );
285         return as_value(true); // odd, but seems the case..
286     }
287 
288     // assuming no automatic primitive-to-object cast will return an array...
289     if (!listenersValue.is_object())
290     {
291         IF_VERBOSE_ASCODING_ERRORS(
292             std::ostringstream ss; fn.dump_args(ss);
293             log_aserror(_("%p.addListener(%s): this object's _listener isn't "
294                 "an object: %s"), (void*)fn.this_ptr, ss.str(),
295                 listenersValue);
296         );
297         // TODO: check this
298         return as_value(false);
299     }
300 
301     as_object* listeners = toObject(listenersValue, getVM(fn));
302 
303     // We checked is_object() above.
304     assert(listeners);
305 
306     callMethod(listeners, NSV::PROP_PUSH, newListener);
307 
308     return as_value(true);
309 
310 }
311 
312 
313 as_value
asbroadcaster_removeListener(const fn_call & fn)314 asbroadcaster_removeListener(const fn_call& fn)
315 {
316     as_object* obj = ensure<ValidThis>(fn);
317 
318     as_value listenersValue;
319 
320     // TODO: test if we're supposed to crawl the target object's
321     //       inheritance chain in case it's own property _listeners
322     //       has been deleted while another one is found in any base
323     //       class.
324     if (!obj->get_member(NSV::PROP_uLISTENERS, &listenersValue)) {
325         IF_VERBOSE_ASCODING_ERRORS(
326             std::ostringstream ss; fn.dump_args(ss);
327             log_aserror(_("%p.addListener(%s): this object has no _listeners "
328                 "member"), (void*)fn.this_ptr, ss.str());
329         );
330         return as_value(false); // TODO: check this
331     }
332 
333     // assuming no automatic primitive-to-object cast will return an array...
334     if ( ! listenersValue.is_object() )
335     {
336         IF_VERBOSE_ASCODING_ERRORS(
337             std::ostringstream ss; fn.dump_args(ss);
338             log_aserror(_("%p.addListener(%s): this object's _listener isn't "
339                 "an object: %s"), (void*)fn.this_ptr, ss.str(),
340                 listenersValue);
341         );
342         return as_value(false); // TODO: check this
343     }
344 
345     as_object* listeners = toObject(listenersValue, getVM(fn));
346     assert(listeners);
347 
348     as_value listenerToRemove;
349     if (fn.nargs) listenerToRemove = fn.arg(0);
350 
351     // Remove the first listener matching the new value
352     // See http://www.senocular.com/flash/tutorials/
353     // listenersasbroadcaster/?page=2
354 
355     // This is an ActionScript-like implementation, which is why it looks
356     // like poor C++.
357     const int length = toInt(getMember(*listeners, NSV::PROP_LENGTH),
358             getVM(fn));
359     int i = 0;
360 
361     VM& vm = getVM(fn);
362     const ObjectURI& propSplice = getURI(vm, NSV::PROP_SPLICE);
363 
364     while (i < length) {
365         std::ostringstream s;
366         s << i;
367         as_value el = getMember(*listeners, getURI(vm, s.str()));
368         if (equals(el, listenerToRemove, getVM(fn))) {
369             callMethod(listeners, propSplice, s.str(), 1);
370             return as_value(true);
371         }
372         ++i;
373     }
374     return as_value(false);
375 
376 }
377 
378 
379 as_value
asbroadcaster_broadcastMessage(const fn_call & fn)380 asbroadcaster_broadcastMessage(const fn_call& fn)
381 {
382     as_object* obj = ensure<ValidThis>(fn);
383 
384     as_value listenersValue;
385 
386     // TODO: test if we're supposed to crawl the target object's
387     //       inheritance chain in case its own property _listeners
388     //       has been deleted while another one is found in any base
389     //       class.
390     if (!obj->get_member(NSV::PROP_uLISTENERS, &listenersValue)) {
391         IF_VERBOSE_ASCODING_ERRORS(
392             std::ostringstream ss; fn.dump_args(ss);
393             log_aserror(_("%p.addListener(%s): this object has no "
394                     "_listeners member"), obj, ss.str());
395         );
396         return as_value(); // TODO: check this
397     }
398 
399     // assuming no automatic primitive-to-object cast will return an array...
400     if ( ! listenersValue.is_object() )
401     {
402         IF_VERBOSE_ASCODING_ERRORS(
403             std::ostringstream ss; fn.dump_args(ss);
404             log_aserror(_("%p.addListener(%s): this object's _listener "
405                 "isn't an object: %s"), (void*)fn.this_ptr,
406                 ss.str(), listenersValue);
407         );
408         return as_value(); // TODO: check this
409     }
410 
411     as_object* listeners = toObject(listenersValue, getVM(fn));
412 
413     if (!fn.nargs) {
414         IF_VERBOSE_ASCODING_ERRORS(
415             log_aserror(_("%p.broadcastMessage() needs an argument"),
416             (void*)fn.this_ptr);
417         );
418         return as_value();
419     }
420 
421     BroadcasterVisitor visitor(fn);
422     foreachArray(*listeners, visitor);
423 
424     const size_t dispatched = visitor.eventsDispatched();
425 
426     if (dispatched) return as_value(true);
427 
428     return as_value();
429 
430 }
431 
432 } // anonymous namespace
433 
434 } // end of gnash namespace
435