modifs traces new-model
authorveltr
Tue, 22 May 2012 16:49:48 +0200
branchnew-model
changeset 906 4b6e154ae8de
parent 905 029a4efe9e24
child 908 f56199193fad
modifs traces
src/js/iframe_embed/embed-mini.js
src/js/iframe_embed/embedder.js
src/js/libs/tracemanager.js
src/js/utils.js
src/widgets/Annotation.js
src/widgets/AnnotationsList.js
src/widgets/Mediafragment.js
src/widgets/Polemic.js
src/widgets/Segments.js
src/widgets/Tagcloud.js
src/widgets/Trace.js
src/widgets/Tweet.js
test/test-config.js
--- a/src/js/iframe_embed/embed-mini.js	Mon May 21 13:17:47 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-(function(a) {var b=document.location,c=b.getElementById(a),d=window,e="hashchange";
-d.onhashchange=function(){c.contentWindow.postMessage({type:e,hash:b.hash},"*");};
-d.addEventListener('message',function(f){g=f.data;if(g.type===e){b.hash=g.hash;}});    
-})("metadataplayer_embed");
--- a/src/js/iframe_embed/embedder.js	Mon May 21 13:17:47 2012 +0200
+++ b/src/js/iframe_embed/embedder.js	Tue May 22 16:49:48 2012 +0200
@@ -3,17 +3,30 @@
    to the iframe url in the page url.   
 */
 
-(function(_frameId) {
-    var _frame = document.getElementById(_frameId);
+if (typeof IriSP === "undefined") {
+    IriSP = {};
+}
+
+IriSP.iFrameUpdater = function(_frameId) {
     
-    window.onhashchange = function() {
-        frame.contentWindow.postMessage({type: "hashchange", hash: hashvalue}, "*");
-    };
-
+    var _frame = document.getElementById(_frameId),
+        _blocked = false,
+        _updater = function() {
+            _blocked = true;
+            window.setTimeout(function() {
+                _blocked = false;
+            }, 1000);
+            _frame.contentWindow.postMessage(document.location.hash, "*");
+        };
+    
+    window.onhashchange = _updater;
+    
     window.addEventListener('message', function(_e) {
-        if (e.data.type === "hashchange") {
-            document.location.hash = e.data.hash;
+        if (/^#/.test(_e.data) && !_blocked) {
+            document.location.hash = _e.data;
         }
     });
     
-})("metadataplayer_embed");
+    window.setTimeout(_updater, 2000);
+    
+};
--- a/src/js/libs/tracemanager.js	Mon May 21 13:17:47 2012 +0200
+++ b/src/js/libs/tracemanager.js	Tue May 22 16:49:48 2012 +0200
@@ -1,540 +1,540 @@
-/*
- * Modelled Trace API
- *
- * This file is part of ktbs4js.
- *
- * ktbs4js is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * ktbs4js is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with ktbs4js.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-/* FIXME: properly use require.js feature. This will do for debugging in the meantime */
-window.tracemanager = (function($) {
-     // If there are more than MAX_FAILURE_COUNT synchronisation
-     // failures, then disable synchronisation
-     MAX_FAILURE_COUNT = 20;
-
-     // If there are more than MAX_BUFFER_SIZE obsels in the buffer,
-     // then "compress" them as a single "ktbsFullBuffer"
-     MAX_BUFFER_SIZE = 500;
-
-     var BufferedService_prototype = {
-         /*
-          *  Buffered service for traces
-          */
-         // url: "",
-         // buffer: [],
-         // isReady: false,
-         // timer: null,
-         // failureCount: 0,
-
-         /* Flush buffer */
-         flush: function() {
-             // FIXME: add mutex on this.buffer
-             if (! this.isReady)
-             {
-                 if (window.console) window.console.log("Sync service not ready");
-             } else if (this.failureCount > MAX_FAILURE_COUNT)
-             {
-                 // Disable synchronisation
-                 this.set_sync_mode('none');
-             } else if (this.buffer.length) {
-                 var temp = this.buffer;
-                 this.buffer = [];
-
-                 if (this.mode == 'GET')
-                 {
-                     // GET mode: do some data mangline. We mark the
-                     // "compressed" nature of the generated JSON by
-                     // prefixing it with c
-                     var data = 'c' + JSON.stringify(temp.map(function (o) { return o.toCompactJSON(); }));
-                     // Swap " (very frequent, which will be
-                     // serialized into %22) and ; (rather rare), this
-                     // saves some bytes
-                     data = data.replace(/[;"#]/g, function(s){ return s == ';' ? '"' : ( s == '"' ? ';' : '%23'); });
-                     // FIXME: check data length (< 2K is safe)
-                     var request=$('<img />').error( function() { this.failureCount += 1; })
-                         .load( function() { this.failureCount = 0; })
-                         .attr('src', this.url + 'trace/?data=' + data);
-                 }
-                 else
-                 {
-                     $.ajax({ url: this.url + 'trace/',
-                              type: 'POST',
-                              contentType: 'application/json',
-                              data: JSON.stringify(temp.map(function (o) { return o.toJSON(); })),
-                              processData: false,
-                              // Type of the returned data.
-                              dataType: "html",
-                              error: function(jqXHR, textStatus, errorThrown) {
-                                  if (window.console) window.console.log("Error when sending buffer:", textStatus);
-                                  this.failureCount += 1;
-                              },
-                              success: function(data, textStatus, jqXHR) {
-                                  // Reset failureCount to 0 as soon as there is 1 valid answer
-                                  this.failureCount = 0;
-                              }
-                            });
-                 }
-             }
-         },
-
-         /* Sync mode: delayed, sync (immediate sync), none (no
-          * synchronisation with server, the trace has to be explicitly saved
-          * if needed */
-         set_sync_mode: function(mode, default_subject) {
-             this.sync_mode = mode;
-             if (! this.isReady && mode !== "none")
-                 this.init(default_subject);
-             if (mode == 'delayed') {
-                 this.start_timer();
-             } else {
-                 this.stop_timer();
-             }
-         },
-
-         /* Enqueue an obsel */
-         enqueue: function(obsel) {
-             if (this.buffer.length > MAX_BUFFER_SIZE)
-             {
-                 obsel = new Obsel('ktbsFullBuffer', this.buffer[0].begin,
-                                   this.buffer[this.buffer.length - 1].end, this.buffer[0].subject);
-                 obsel.trace = this.buffer[0].trace;
-                 this.buffer = [];
-             }
-             this.buffer.push(obsel);
-             if (this.sync_mode === 'sync') {
-                 // Immediate sync of the obsel.
-                 this.flush();
-             }
-         },
-
-         start_timer: function() {
-             var self = this;
-             if (this.timer === null) {
-                 this.timer = window.setInterval(function() {
-                                                     self.flush();
-                                                 }, this.timeOut);
-             }
-         },
-
-         stop_timer: function() {
-             if (this.timer !== null) {
-                 window.clearInterval(this.timer);
-                 this.timer = null;
-             }
-         },
-
-         /*
-          * Initialize the sync service
-          */
-         init: function(default_subject) {
-             var self = this;
-             if (this.isReady)
-                 /* Already initialized */
-                 return;
-             if (typeof default_subject === 'undefined')
-                 default_subject = 'anonymous';
-             if (this.mode == 'GET')
-             {
-                 var request=$('<img/>').attr('src', this.url + 'login?userinfo={"default_subject": "' + default_subject + '"}');
-                 // Do not wait for the return, assume it is
-                 // initialized. This assumption will not work anymore
-                 // if login returns some necessary information
-                 this.isReady = true;
-             }
-             else
-             {
-                 $.ajax({ url: this.url + 'login',
-                          type: 'POST',
-                          data: 'userinfo={"default_subject":"' + default_subject + '"}',
-                          success: function(data, textStatus, jqXHR) {
-                              self.isReady = true;
-                              if (self.buffer.length) {
-                                  self.flush();
-                              }
-                          }
-                        });
-             }
-         }
-     };
-     var BufferedService = function(url, mode) {
-         this.url = url;
-         this.buffer = [];
-         this.isReady = false;
-         this.timer = null;
-         this.failureCount = 0;
-         // sync_mode is either "none", "sync" or "buffered"
-         this.sync_mode = "none";
-         /* mode can be either POST or GET */
-         if (mode == 'POST' || mode == 'GET')
-             this.mode = mode;
-         else
-             this.mode = 'POST';
-         /* Flush buffer every timeOut ms if the sync_mode is delayed */
-         this.timeOut = 2000;
-     };
-     BufferedService.prototype = BufferedService_prototype;
-
-     var Trace_prototype = {
-         /* FIXME: We could/should use a sorted list such as
-          http://closure-library.googlecode.com/svn/docs/class_goog_structs_AvlTree.html
-          to speed up queries based on time */
-         obsels: [],
-         /* Trace URI */
-         uri: "",
-         default_subject: "",
-         /* baseuri is used as the base URI to resolve relative
-          * attribute-type names in obsels. Strictly speaking, this
-          * should rather be expressed as a reference to model, or
-          * more generically, as a qname/URI dict */
-         baseuri: "",
-         /* Mapping of obsel type or property name to a compact
-          * representation (shorthands).
-          */
-         shorthands: null,
-         syncservice: null,
-
-         /* Define the trace URI */
-         set_uri: function(uri) {
-             this.uri = uri;
-         },
-
-         /* Sync mode: delayed, sync (immediate sync), none (no
-          * synchronisation with server, the trace has to be explicitly saved
-          * if needed */
-         set_sync_mode: function(mode) {
-             if (this.syncservice !== null) {
-                 this.syncservice.set_sync_mode(mode, this.default_subject);
-             }
-         },
-
-         /*
-          * Return a list of the obsels of this trace matching the parameters
-          */
-         list_obsels: function(_begin, _end, _reverse) {
-             var res;
-             if (typeof _begin !== 'undefined' || typeof _end !== 'undefined') {
-                 /*
-                  * Not optimized yet.
-                  */
-                 res = [];
-                 var l = this.obsels.length;
-                 for (var i = 0; i < l; i++) {
-                     var o = this.obsels[i];
-                     if ((typeof _begin !== 'undefined' && o.begin > _begin) && (typeof _end !== 'undefined' && o.end < _end)) {
-                         res.push(o);
-                     }
-                 }
-             }
-
-             if (typeof _reverse !== 'undefined') {
-                 if (res !== undefined) {
-                     /* Should reverse the whole list. Make a copy. */
-                     res = this.obsels.slice(0);
-                 }
-                 res.sort(function(a, b) { return b.begin - a.begin; });
-                 return res;
-             }
-
-             if (res === undefined) {
-                 res = this.obsels;
-             }
-             return res;
-
-         },
-
-         /*
-          * Return the obsel of this trace identified by the URI, or undefined
-          */
-         get_obsel: function(id) {
-             for (var i = 0; i < this.obsels.length; i++) {
-                 /* FIXME: should check against variations of id/uri, take this.baseuri into account */
-                 if (this.obsels[i].uri === id) {
-                     return this.obsels[i];
-                 }
-             }
-             return undefined;
-         },
-
-         set_default_subject: function(subject) {
-             // FIXME: if we call this method after the sync_service
-             // init method, then the default_subject will not be
-             // consistent anymore. Maybe we should then call init() again?
-             this.default_subject = subject;
-         },
-
-         get_default_subject: function() {
-             return this.default_subject;
-         },
-
-         /* (type:ObselType, begin:int, end:int?, subject:str?, attributes:[AttributeType=>any]?) */
-         /* Create a new obsel and add it to the trace */
-         create_obsel: function(type, begin, end, subject, _attributes) {
-             var o = new Obsel(type, begin, end, subject);
-             if (typeof _attributes !== 'undefined') {
-                 o.attributes = _attributes;
-             }
-             o.trace = this;
-             this.obsels.push(o);
-             if (this.syncservice !== null)
-                 this.syncservice.enqueue(o);
-         },
-
-         /* Helper methods */
-
-         /* Create a new obsel with the given attributes */
-         trace: function(type, _attributes, _begin, _end, _subject) {
-             var t = (new Date()).getTime();
-             if (typeof begin === 'undefined') {
-                 _begin = t;
-             }
-             if (typeof end === 'undefined') {
-                 _end = _begin;
-             }
-             if (typeof subject === 'undefined') {
-                 _subject = this.default_subject;
-             }
-             if (typeof _attributes === 'undefined') {
-                 _attributes = {};
-             }
-             return this.create_obsel(type, _begin, _end, _subject, _attributes);
-         }
-     };
-
-     var Trace = function(uri, requestmode) {
-         /* FIXME: We could/should use a sorted list such as
-          http://closure-library.googlecode.com/svn/docs/class_goog_structs_AvlTree.html
-          to speed up queries based on time */
-         this.obsels = [];
-         /* Trace URI */
-         if (uri === undefined)
-             uri = "";
-         this.uri = uri;
-         this.sync_mode = "none";
-         this.default_subject = "";
-         this.shorthands = {};
-         /* baseuri is used a the base URI to resolve relative attribute names in obsels */
-         this.baseuri = "";
-
-         this.syncservice = new BufferedService(uri, requestmode);
-         $(window).unload( function () {
-                               if (this.syncservice && this.sync_mode !== 'none') {
-                                   this.syncservice.flush();
-                                   this.syncservice.stop_timer();
-                               }
-                           });
-     };
-     Trace.prototype = Trace_prototype;
-
-     var Obsel_prototype = {
-         /* The following attributes are here for documentation
-          * purposes. They MUST be defined in the constructor
-          * function. */
-         trace: undefined,
-         type: undefined,
-         begin: undefined,
-         end: undefined,
-         subject: undefined,
-         /* Dictionary indexed by ObselType URIs */
-         attributes: {},
-
-         /* Method definitions */
-         get_trace: function() {
-             return this.trace;
-         },
-
-         get_obsel_type: function() {
-             return this.type;
-         },
-         get_begin: function() {
-             return this.begin;
-         },
-         get_end: function() {
-             return this.end;
-         },
-         get_subject: function() {
-             return this.subject;
-         },
-
-         list_attribute_types: function() {
-             var result = [];
-             for (var prop in this.attributes) {
-                 if (this.attributes.hasOwnProperty(prop))
-                     result.push(prop);
-             }
-             /* FIXME: we return URIs here instead of AttributeType elements */
-             return result;
-         },
-
-         list_relation_types: function() {
-             /* FIXME: not implemented yet */
-         },
-
-         list_related_obsels: function (rt) {
-             /* FIXME: not implemented yet */
-         },
-         list_inverse_relation_types: function () {
-             /* FIXME: not implemented yet */
-         },
-         list_relating_obsels: function (rt) {
-             /* FIXME: not implemented yet */
-         },
-         /*
-          * Return the value of the given attribute type for this obsel
-          */
-         get_attribute_value: function(at) {
-             if (typeof at === "string")
-                 /* It is a URI */
-                 return this.attributes[at];
-             else
-                 /* FIXME: check that at is instance of AttributeType */
-                 return this.attributes[at.uri];
-         },
-
-
-         /* obsel modification (trace amendment) */
-
-         set_attribute_value: function(at, value) {
-             if (typeof at === "string")
-                 /* It is a URI */
-                 this.attributes[at] = value;
-             /* FIXME: check that at is instance of AttributeType */
-             else
-                 this.attributes[at.uri] = value;
-         },
-
-         del_attribute_value: function(at) {
-             if (typeof at === "string")
-                 /* It is a URI */
-                 delete this.attributes[at];
-             /* FIXME: check that at is instance of AttributeType */
-             else
-                 delete this.attributes[at.uri];
-         },
-
-         add_related_obsel: function(rt, value) {
-             /* FIXME: not implemented yet */
-         },
-
-         del_related_obsel: function(rt, value) {
-             /* FIXME: not implemented yet */
-         },
-
-         /*
-          * Return a JSON representation of the obsel
-          */
-         toJSON: function() {
-             var r = {
-                 "@id": this.id,
-                 "@type": this.type,
-                 "begin": this.begin,
-                 "end": this.end,
-                 "subject": this.subject
-             };
-             for (var prop in this.attributes) {
-                 if (this.attributes.hasOwnProperty(prop))
-                     r[prop] = this.attributes[prop];
-             }
-             return r;
-         },
-
-         /*
-          * Return a compact JSON representation of the obsel.
-          * Use predefined + custom shorthands for types/properties
-          */
-         toCompactJSON: function() {
-             var r = {
-                 "@t": (this.trace.shorthands.hasOwnProperty(this.type) ? this.trace.shorthands[this.type] : this.type),
-                 "@b": this.begin,
-             };
-             // Transmit subject only if different from default_subject
-             if (this.subject !== this.trace.default_subject)
-                 r["@s"] = this.subject;
-
-             // Store duration (to save some bytes) and only if it is non-null
-             if (this.begin !== this.end)
-                 r["@d"] = this.end - this.begin;
-
-             // Store id only if != ""
-             if (this.id !== "")
-                 r["@i"] = this.id;
-
-             for (var prop in this.attributes) {
-                 if (this.attributes.hasOwnProperty(prop))
-                 {
-                     var v = this.attributes[prop];
-                     r[prop] = this.trace.shorthands.hasOwnProperty(v) ? this.trace.shorthands[v] : v;
-                 }
-             }
-             return r;
-         },
-
-         toJSONstring: function() {
-             return JSON.stringify(this.toJSON());
-         }
-     };
-
-     var Obsel = function(type, begin, end, subject, attributes) {
-         this.trace = undefined;
-         this.uri = "";
-         this.id = "";
-         this.type = type;
-         this.begin = begin;
-         this.end = end;
-         this.subject = subject;
-         /* Is the obsel synched with the server ? */
-         this.sync_status = false;
-         /* Dictionary indexed by ObselType URIs */
-         this.attributes = {};
-     };
-     Obsel.prototype = Obsel_prototype;
-
-     var TraceManager_prototype = {
-         traces: [],
-         /*
-          * Return the trace with id name
-          * If it was not registered, return undefined.
-          */
-         get_trace: function(name) {
-             return this.traces[name];
-         },
-
-         /*
-          * Explicitly create and initialize a new trace with the given name.
-          * The optional uri parameter allows to initialize the trace URI.
-          *
-          * If another existed with the same name before, then it is replaced by a new one.
-          */
-         init_trace: function(name, params)
-         {
-             if (window.console) window.console.log("init_trace", params);
-             url = params.url ? params.url : "";
-             requestmode = params.requestmode ? params.requestmode : "POST";
-             syncmode = params.syncmode ? params.syncmode : "none";
-             default_subject = params.default_subject ? params.default_subject : "default";
-             var t = new Trace(url, requestmode);
-             t.set_default_subject(default_subject);
-             t.set_sync_mode(syncmode);
-             this.traces[name] = t;
-             return t;
-         }
-     };
-
-     var TraceManager = function() {
-         this.traces = {};
-     };
-     TraceManager.prototype = TraceManager_prototype;
-
-     var tracemanager  = new TraceManager();
-     return tracemanager;
- })(jQuery);
+/*
+ * Modelled Trace API
+ *
+ * This file is part of ktbs4js.
+ *
+ * ktbs4js is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * ktbs4js is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with ktbs4js.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+/* FIXME: properly use require.js feature. This will do for debugging in the meantime */
+window.tracemanager = (function($) {
+     // If there are more than MAX_FAILURE_COUNT synchronisation
+     // failures, then disable synchronisation
+     MAX_FAILURE_COUNT = 20;
+
+     // If there are more than MAX_BUFFER_SIZE obsels in the buffer,
+     // then "compress" them as a single "ktbsFullBuffer"
+     MAX_BUFFER_SIZE = 500;
+
+     var BufferedService_prototype = {
+         /*
+          *  Buffered service for traces
+          */
+         // url: "",
+         // buffer: [],
+         // isReady: false,
+         // timer: null,
+         // failureCount: 0,
+
+         /* Flush buffer */
+         flush: function() {
+             // FIXME: add mutex on this.buffer
+             if (! this.isReady)
+             {
+                 if (window.console) window.console.log("Sync service not ready");
+             } else if (this.failureCount > MAX_FAILURE_COUNT)
+             {
+                 // Disable synchronisation
+                 this.set_sync_mode('none');
+             } else if (this.buffer.length) {
+                 var temp = this.buffer;
+                 this.buffer = [];
+
+                 if (this.mode == 'GET')
+                 {
+                     // GET mode: do some data mangline. We mark the
+                     // "compressed" nature of the generated JSON by
+                     // prefixing it with c
+                     var data = 'c' + JSON.stringify(temp.map(function (o) { return o.toCompactJSON(); }));
+                     // Swap " (very frequent, which will be
+                     // serialized into %22) and ; (rather rare), this
+                     // saves some bytes
+                     data = data.replace(/[;"#]/g, function(s){ return s == ';' ? '"' : ( s == '"' ? ';' : '%23'); });
+                     // FIXME: check data length (< 2K is safe)
+                     var request=$('<img />').error( function() { this.failureCount += 1; })
+                         .load( function() { this.failureCount = 0; })
+                         .attr('src', this.url + 'trace/?data=' + data);
+                 }
+                 else
+                 {
+                     $.ajax({ url: this.url + 'trace/',
+                              type: 'POST',
+                              contentType: 'application/json',
+                              data: JSON.stringify(temp.map(function (o) { return o.toJSON(); })),
+                              processData: false,
+                              // Type of the returned data.
+                              dataType: "html",
+                              error: function(jqXHR, textStatus, errorThrown) {
+                                  if (window.console) window.console.log("Error when sending buffer:", textStatus);
+                                  this.failureCount += 1;
+                              },
+                              success: function(data, textStatus, jqXHR) {
+                                  // Reset failureCount to 0 as soon as there is 1 valid answer
+                                  this.failureCount = 0;
+                              }
+                            });
+                 }
+             }
+         },
+
+         /* Sync mode: delayed, sync (immediate sync), none (no
+          * synchronisation with server, the trace has to be explicitly saved
+          * if needed */
+         set_sync_mode: function(mode, default_subject) {
+             this.sync_mode = mode;
+             if (! this.isReady && mode !== "none")
+                 this.init(default_subject);
+             if (mode == 'delayed') {
+                 this.start_timer();
+             } else {
+                 this.stop_timer();
+             }
+         },
+
+         /* Enqueue an obsel */
+         enqueue: function(obsel) {
+             if (this.buffer.length > MAX_BUFFER_SIZE)
+             {
+                 obsel = new Obsel('ktbsFullBuffer', this.buffer[0].begin,
+                                   this.buffer[this.buffer.length - 1].end, this.buffer[0].subject);
+                 obsel.trace = this.buffer[0].trace;
+                 this.buffer = [];
+             }
+             this.buffer.push(obsel);
+             if (this.sync_mode === 'sync') {
+                 // Immediate sync of the obsel.
+                 this.flush();
+             }
+         },
+
+         start_timer: function() {
+             var self = this;
+             if (this.timer === null) {
+                 this.timer = window.setInterval(function() {
+                                                     self.flush();
+                                                 }, this.timeOut);
+             }
+         },
+
+         stop_timer: function() {
+             if (this.timer !== null) {
+                 window.clearInterval(this.timer);
+                 this.timer = null;
+             }
+         },
+
+         /*
+          * Initialize the sync service
+          */
+         init: function(default_subject) {
+             var self = this;
+             if (this.isReady)
+                 /* Already initialized */
+                 return;
+             if (typeof default_subject === 'undefined')
+                 default_subject = 'anonymous';
+             if (this.mode == 'GET')
+             {
+                 var request=$('<img/>').attr('src', this.url + 'login?userinfo={"default_subject": "' + default_subject + '"}');
+                 // Do not wait for the return, assume it is
+                 // initialized. This assumption will not work anymore
+                 // if login returns some necessary information
+                 this.isReady = true;
+             }
+             else
+             {
+                 $.ajax({ url: this.url + 'login',
+                          type: 'POST',
+                          data: 'userinfo={"default_subject":"' + default_subject + '"}',
+                          success: function(data, textStatus, jqXHR) {
+                              self.isReady = true;
+                              if (self.buffer.length) {
+                                  self.flush();
+                              }
+                          }
+                        });
+             }
+         }
+     };
+     var BufferedService = function(url, mode) {
+         this.url = url;
+         this.buffer = [];
+         this.isReady = false;
+         this.timer = null;
+         this.failureCount = 0;
+         // sync_mode is either "none", "sync" or "buffered"
+         this.sync_mode = "none";
+         /* mode can be either POST or GET */
+         if (mode == 'POST' || mode == 'GET')
+             this.mode = mode;
+         else
+             this.mode = 'POST';
+         /* Flush buffer every timeOut ms if the sync_mode is delayed */
+         this.timeOut = 2000;
+     };
+     BufferedService.prototype = BufferedService_prototype;
+
+     var Trace_prototype = {
+         /* FIXME: We could/should use a sorted list such as
+          http://closure-library.googlecode.com/svn/docs/class_goog_structs_AvlTree.html
+          to speed up queries based on time */
+         obsels: [],
+         /* Trace URI */
+         uri: "",
+         default_subject: "",
+         /* baseuri is used as the base URI to resolve relative
+          * attribute-type names in obsels. Strictly speaking, this
+          * should rather be expressed as a reference to model, or
+          * more generically, as a qname/URI dict */
+         baseuri: "",
+         /* Mapping of obsel type or property name to a compact
+          * representation (shorthands).
+          */
+         shorthands: null,
+         syncservice: null,
+
+         /* Define the trace URI */
+         set_uri: function(uri) {
+             this.uri = uri;
+         },
+
+         /* Sync mode: delayed, sync (immediate sync), none (no
+          * synchronisation with server, the trace has to be explicitly saved
+          * if needed */
+         set_sync_mode: function(mode) {
+             if (this.syncservice !== null) {
+                 this.syncservice.set_sync_mode(mode, this.default_subject);
+             }
+         },
+
+         /*
+          * Return a list of the obsels of this trace matching the parameters
+          */
+         list_obsels: function(_begin, _end, _reverse) {
+             var res;
+             if (typeof _begin !== 'undefined' || typeof _end !== 'undefined') {
+                 /*
+                  * Not optimized yet.
+                  */
+                 res = [];
+                 var l = this.obsels.length;
+                 for (var i = 0; i < l; i++) {
+                     var o = this.obsels[i];
+                     if ((typeof _begin !== 'undefined' && o.begin > _begin) && (typeof _end !== 'undefined' && o.end < _end)) {
+                         res.push(o);
+                     }
+                 }
+             }
+
+             if (typeof _reverse !== 'undefined') {
+                 if (res !== undefined) {
+                     /* Should reverse the whole list. Make a copy. */
+                     res = this.obsels.slice(0);
+                 }
+                 res.sort(function(a, b) { return b.begin - a.begin; });
+                 return res;
+             }
+
+             if (res === undefined) {
+                 res = this.obsels;
+             }
+             return res;
+
+         },
+
+         /*
+          * Return the obsel of this trace identified by the URI, or undefined
+          */
+         get_obsel: function(id) {
+             for (var i = 0; i < this.obsels.length; i++) {
+                 /* FIXME: should check against variations of id/uri, take this.baseuri into account */
+                 if (this.obsels[i].uri === id) {
+                     return this.obsels[i];
+                 }
+             }
+             return undefined;
+         },
+
+         set_default_subject: function(subject) {
+             // FIXME: if we call this method after the sync_service
+             // init method, then the default_subject will not be
+             // consistent anymore. Maybe we should then call init() again?
+             this.default_subject = subject;
+         },
+
+         get_default_subject: function() {
+             return this.default_subject;
+         },
+
+         /* (type:ObselType, begin:int, end:int?, subject:str?, attributes:[AttributeType=>any]?) */
+         /* Create a new obsel and add it to the trace */
+         create_obsel: function(type, begin, end, subject, _attributes) {
+             var o = new Obsel(type, begin, end, subject);
+             if (typeof _attributes !== 'undefined') {
+                 o.attributes = _attributes;
+             }
+             o.trace = this;
+             this.obsels.push(o);
+             if (this.syncservice !== null)
+                 this.syncservice.enqueue(o);
+         },
+
+         /* Helper methods */
+
+         /* Create a new obsel with the given attributes */
+         trace: function(type, _attributes, _begin, _end, _subject) {
+             var t = (new Date()).getTime();
+             if (typeof begin === 'undefined') {
+                 _begin = t;
+             }
+             if (typeof end === 'undefined') {
+                 _end = _begin;
+             }
+             if (typeof subject === 'undefined') {
+                 _subject = this.default_subject;
+             }
+             if (typeof _attributes === 'undefined') {
+                 _attributes = {};
+             }
+             return this.create_obsel(type, _begin, _end, _subject, _attributes);
+         }
+     };
+
+     var Trace = function(uri, requestmode) {
+         /* FIXME: We could/should use a sorted list such as
+          http://closure-library.googlecode.com/svn/docs/class_goog_structs_AvlTree.html
+          to speed up queries based on time */
+         this.obsels = [];
+         /* Trace URI */
+         if (uri === undefined)
+             uri = "";
+         this.uri = uri;
+         this.sync_mode = "none";
+         this.default_subject = "";
+         this.shorthands = {};
+         /* baseuri is used a the base URI to resolve relative attribute names in obsels */
+         this.baseuri = "";
+
+         this.syncservice = new BufferedService(uri, requestmode);
+         $(window).unload( function () {
+                               if (this.syncservice && this.sync_mode !== 'none') {
+                                   this.syncservice.flush();
+                                   this.syncservice.stop_timer();
+                               }
+                           });
+     };
+     Trace.prototype = Trace_prototype;
+
+     var Obsel_prototype = {
+         /* The following attributes are here for documentation
+          * purposes. They MUST be defined in the constructor
+          * function. */
+         trace: undefined,
+         type: undefined,
+         begin: undefined,
+         end: undefined,
+         subject: undefined,
+         /* Dictionary indexed by ObselType URIs */
+         attributes: {},
+
+         /* Method definitions */
+         get_trace: function() {
+             return this.trace;
+         },
+
+         get_obsel_type: function() {
+             return this.type;
+         },
+         get_begin: function() {
+             return this.begin;
+         },
+         get_end: function() {
+             return this.end;
+         },
+         get_subject: function() {
+             return this.subject;
+         },
+
+         list_attribute_types: function() {
+             var result = [];
+             for (var prop in this.attributes) {
+                 if (this.attributes.hasOwnProperty(prop))
+                     result.push(prop);
+             }
+             /* FIXME: we return URIs here instead of AttributeType elements */
+             return result;
+         },
+
+         list_relation_types: function() {
+             /* FIXME: not implemented yet */
+         },
+
+         list_related_obsels: function (rt) {
+             /* FIXME: not implemented yet */
+         },
+         list_inverse_relation_types: function () {
+             /* FIXME: not implemented yet */
+         },
+         list_relating_obsels: function (rt) {
+             /* FIXME: not implemented yet */
+         },
+         /*
+          * Return the value of the given attribute type for this obsel
+          */
+         get_attribute_value: function(at) {
+             if (typeof at === "string")
+                 /* It is a URI */
+                 return this.attributes[at];
+             else
+                 /* FIXME: check that at is instance of AttributeType */
+                 return this.attributes[at.uri];
+         },
+
+
+         /* obsel modification (trace amendment) */
+
+         set_attribute_value: function(at, value) {
+             if (typeof at === "string")
+                 /* It is a URI */
+                 this.attributes[at] = value;
+             /* FIXME: check that at is instance of AttributeType */
+             else
+                 this.attributes[at.uri] = value;
+         },
+
+         del_attribute_value: function(at) {
+             if (typeof at === "string")
+                 /* It is a URI */
+                 delete this.attributes[at];
+             /* FIXME: check that at is instance of AttributeType */
+             else
+                 delete this.attributes[at.uri];
+         },
+
+         add_related_obsel: function(rt, value) {
+             /* FIXME: not implemented yet */
+         },
+
+         del_related_obsel: function(rt, value) {
+             /* FIXME: not implemented yet */
+         },
+
+         /*
+          * Return a JSON representation of the obsel
+          */
+         toJSON: function() {
+             var r = {
+                 "@id": this.id,
+                 "@type": this.type,
+                 "begin": this.begin,
+                 "end": this.end,
+                 "subject": this.subject
+             };
+             for (var prop in this.attributes) {
+                 if (this.attributes.hasOwnProperty(prop))
+                     r[prop] = this.attributes[prop];
+             }
+             return r;
+         },
+
+         /*
+          * Return a compact JSON representation of the obsel.
+          * Use predefined + custom shorthands for types/properties
+          */
+         toCompactJSON: function() {
+             var r = {
+                 "@t": (this.trace.shorthands.hasOwnProperty(this.type) ? this.trace.shorthands[this.type] : this.type),
+                 "@b": this.begin,
+             };
+             // Transmit subject only if different from default_subject
+             if (this.subject !== this.trace.default_subject)
+                 r["@s"] = this.subject;
+
+             // Store duration (to save some bytes) and only if it is non-null
+             if (this.begin !== this.end)
+                 r["@d"] = this.end - this.begin;
+
+             // Store id only if != ""
+             if (this.id !== "")
+                 r["@i"] = this.id;
+
+             for (var prop in this.attributes) {
+                 if (this.attributes.hasOwnProperty(prop))
+                 {
+                     var v = this.attributes[prop];
+                     r[prop] = this.trace.shorthands.hasOwnProperty(v) ? this.trace.shorthands[v] : v;
+                 }
+             }
+             return r;
+         },
+
+         toJSONstring: function() {
+             return JSON.stringify(this.toJSON());
+         }
+     };
+
+     var Obsel = function(type, begin, end, subject, attributes) {
+         this.trace = undefined;
+         this.uri = "";
+         this.id = "";
+         this.type = type;
+         this.begin = begin;
+         this.end = end;
+         this.subject = subject;
+         /* Is the obsel synched with the server ? */
+         this.sync_status = false;
+         /* Dictionary indexed by ObselType URIs */
+         this.attributes = {};
+     };
+     Obsel.prototype = Obsel_prototype;
+
+     var TraceManager_prototype = {
+         traces: [],
+         /*
+          * Return the trace with id name
+          * If it was not registered, return undefined.
+          */
+         get_trace: function(name) {
+             return this.traces[name];
+         },
+
+         /*
+          * Explicitly create and initialize a new trace with the given name.
+          * The optional uri parameter allows to initialize the trace URI.
+          *
+          * If another existed with the same name before, then it is replaced by a new one.
+          */
+         init_trace: function(name, params)
+         {
+             if (window.console) window.console.log("init_trace", params);
+             url = params.url ? params.url : "";
+             requestmode = params.requestmode ? params.requestmode : "POST";
+             syncmode = params.syncmode ? params.syncmode : "none";
+             default_subject = params.default_subject ? params.default_subject : "default";
+             var t = new Trace(url, requestmode);
+             t.set_default_subject(default_subject);
+             t.set_sync_mode(syncmode);
+             this.traces[name] = t;
+             return t;
+         }
+     };
+
+     var TraceManager = function() {
+         this.traces = {};
+     };
+     TraceManager.prototype = TraceManager_prototype;
+
+     var tracemanager  = new TraceManager();
+     return tracemanager;
+ })(jQuery);
--- a/src/js/utils.js	Mon May 21 13:17:47 2012 +0200
+++ b/src/js/utils.js	Tue May 22 16:49:48 2012 +0200
@@ -1,7 +1,7 @@
 /* utils.js - various utils that don't belong anywhere else */
 
 IriSP.jqEscape = function(_text) {
-    return text.replace(/(:|\.)/g,'\\$1');
+    return _text.replace(/(:|\.)/g,'\\$1');
 }
 
 IriSP.getLib = function(lib) {
--- a/src/widgets/Annotation.js	Mon May 21 13:17:47 2012 +0200
+++ b/src/widgets/Annotation.js	Tue May 22 16:49:48 2012 +0200
@@ -27,9 +27,9 @@
 IriSP.Widgets.Annotation.prototype.template =
     '<div class="Ldt-Annotation-Widget {{#show_top_border}}Ldt-Annotation-ShowTop{{/show_top_border}}">'
     + '<div class="Ldt-Annotation-Inner Ldt-Annotation-Empty"><div class="Ldt-Annotation-ShareIcons Ldt-Annotation-HiddenWhenMinimized Ldt-Annotation-HiddenWhenEmpty">'
-    + '<a href="#" target="_blank" class="Ldt-Annotation-Share Ldt-Annotation-Fb" title="{{l10n.share_on}} Facebook"></a>'
-    + '<a href="#" target="_blank" class="Ldt-Annotation-Share Ldt-Annotation-Twitter" title="{{l10n.share_on}} Twitter"></a>'
-    + '<a href="#" target="_blank" class="Ldt-Annotation-Share Ldt-Annotation-Gplus" title="{{l10n.share_on}} Google+"></a>'
+    + '<a href="#" target="_blank" class="Ldt-Annotation-Share Ldt-Annotation-Fb Ldt-TraceMe" title="{{l10n.share_on}} Facebook"></a>'
+    + '<a href="#" target="_blank" class="Ldt-Annotation-Share Ldt-Annotation-Twitter Ldt-TraceMe" title="{{l10n.share_on}} Twitter"></a>'
+    + '<a href="#" target="_blank" class="Ldt-Annotation-Share Ldt-Annotation-Gplus Ldt-TraceMe" title="{{l10n.share_on}} Google+"></a>'
     + '</div><h3 class="Ldt-Annotation-HiddenWhenEmpty"><span class="Ldt-Annotation-Title"></span> <span class="Ldt-Annotation-Time">'
     + '( <span class="Ldt-Annotation-Begin"></span> - <span class="Ldt-Annotation-End"></span> )</span></h3>'
     + '<h3 class="Ldt-Annotation-MashupOrigin Ldt-Annotation-HiddenWhenEmpty">{{l10n.excerpt_from}} <span class="Ldt-Annotation-MashupMedia"></span> <span class="Ldt-Annotation-Time">'
--- a/src/widgets/AnnotationsList.js	Mon May 21 13:17:47 2012 +0200
+++ b/src/widgets/AnnotationsList.js	Tue May 22 16:49:48 2012 +0200
@@ -34,7 +34,7 @@
     '<div class="Ldt-AnnotationsListWidget">'
     + '<ul class="Ldt-AnnotationsList-ul">'
     + '{{#annotations}}'
-    + '<li id="Ldt-Annotation-li-{{id}}" class="Ldt-AnnotationsList-li Ldt-TraceMe">'
+    + '<li class="Ldt-AnnotationsList-li Ldt-TraceMe" trace-info="annotation-id:{{id}}">'
     + '<div class="Ldt-AnnotationsList-ThumbContainer">'
     + '<a href="{{url}}">'
     + '<img class="Ldt-AnnotationsList-Thumbnail" src="{{thumbnail}}" />'
@@ -177,7 +177,7 @@
                             )
                     );
                     var _res = {
-                        id : _annotation.id,
+                        id : _annotation.namespacedId.name,
                         title : _annotation.title.replace(_annotation.description,''),
                         description : _annotation.description,
                         begin : _annotation.begin.toString(),
--- a/src/widgets/Mediafragment.js	Mon May 21 13:17:47 2012 +0200
+++ b/src/widgets/Mediafragment.js	Tue May 22 16:49:48 2012 +0200
@@ -5,8 +5,8 @@
     window.onhashchange = this.functionWrapper("goToHash");
     if (typeof window.addEventListener !== "undefined") {
         window.addEventListener('message', function(_msg) {
-            if (_msg.data.type === "hashchange") {
-                document.location.hash = _msg.data.hash;
+            if (/^#/.test(_msg.data)) {
+                document.location.hash = _msg.data;
             }
         })
     };
@@ -73,10 +73,7 @@
         var _hash = this.getLastHash();
         document.location.hash = _hash;
         if (window.parent !== window) {
-            window.parent.postMessage({
-                type: "hashchange",
-                hash: _hash
-            })
+            window.parent.postMessage(_hash,"*")
         }
         this.block();
     }
--- a/src/widgets/Polemic.js	Mon May 21 13:17:47 2012 +0200
+++ b/src/widgets/Polemic.js	Tue May 22 16:49:48 2012 +0200
@@ -155,7 +155,7 @@
             
             function displayElement(_x, _y, _color, _id, _title) {
                 _html += Mustache.to_html(
-                    '<div class="Ldt-Polemic-TweetDiv" annotation-id="{{id}}" tweet-title="{{title}}" pos-x="{{posx}}" pos-y="{{top}}" polemic-color="{{color}}"'
+                    '<div class="Ldt-Polemic-TweetDiv Ldt-TraceMe" trace-info="annotation-id:{{id}}" annotation-id="{{id}}" tweet-title="{{title}}" pos-x="{{posx}}" pos-y="{{top}}" polemic-color="{{color}}"'
                     + ' style="width: {{width}}px; height: {{height}}px; top: {{top}}px; left: {{left}}px; background: {{color}}"></div>',
                 {
                     id: _id,
@@ -173,13 +173,13 @@
                 var _y = _this.height;
                 _slice.annotations.forEach(function(_annotation) {
                     _y -= _this.element_height;
-                    displayElement(_x, _y, _this.defaultcolor, _annotation.id, _annotation.title);
+                    displayElement(_x, _y, _this.defaultcolor, _annotation.namespacedId.name, _annotation.title);
                 });
                 IriSP._(_slice.polemicStacks).forEach(function(_annotations, _j) {
                     var _color = _this.polemics[_j].color;
                     _annotations.forEach(function(_annotation) {
                         _y -= _this.element_height;
-                        displayElement(_x, _y, _color, _annotation.id, _annotation.title);
+                        displayElement(_x, _y, _color, _annotation.namespacedId.name, _annotation.title);
                     });
                 });
                 _x += _this.element_width;
@@ -226,7 +226,7 @@
             
         function displayStackElement(_x, _y, _h, _color, _nums, _begin, _end) {
             _html += Mustache.to_html(
-                '<div class="Ldt-Polemic-TweetDiv" pos-x="{{posx}}" pos-y="{{top}}" annotation-counts="{{nums}}" begin-time="{{begin}}" end-time="{{end}}"'
+                '<div class="Ldt-Polemic-TweetDiv Ldt-TraceMe" trace-info="annotation-block,time:{{begin}}" pos-x="{{posx}}" pos-y="{{top}}" annotation-counts="{{nums}}" begin-time="{{begin}}" end-time="{{end}}"'
                 + ' style="width: {{width}}px; height: {{height}}px; top: {{top}}px; left: {{left}}px; background: {{color}}"></div>',
             {
                 nums: _nums,
--- a/src/widgets/Segments.js	Mon May 21 13:17:47 2012 +0200
+++ b/src/widgets/Segments.js	Tue May 22 16:49:48 2012 +0200
@@ -19,7 +19,7 @@
 
 IriSP.Widgets.Segments.prototype.template =
     '<div class="Ldt-Segments-List">{{#segments}}'
-    + '<div class="Ldt-Segments-Segment" segment-id="{{id}}" segment-text="{{text}}" segment-color="{{color}}" center-pos="{{center}}" begin-seconds="{{beginseconds}}"'
+    + '<div class="Ldt-Segments-Segment Ldt-TraceMe" trace-info="segment-id:{{id}}" segment-id="{{id}}" segment-text="{{text}}" segment-color="{{color}}" center-pos="{{center}}" begin-seconds="{{beginseconds}}"'
     + 'style="left:{{left}}px; width:{{width}}px; background:{{color}}"></div>'
     + '{{/segments}}</div>'
     + '<div class="Ldt-Segments-Position"></div>';
--- a/src/widgets/Tagcloud.js	Mon May 21 13:17:47 2012 +0200
+++ b/src/widgets/Tagcloud.js	Tue May 22 16:49:48 2012 +0200
@@ -7,7 +7,7 @@
 
 IriSP.Widgets.Tagcloud.prototype.template =
     '<div class="Ldt-Tagcloud-Container"><ul class="Ldt-Tagcloud-List">'
-    + '{{#words}}<li class="Ldt-Tagcloud-item" content="{{word}}" style="font-size: {{size}}px">{{word}}</li>{{/words}}'
+    + '{{#words}}<li class="Ldt-Tagcloud-item Ldt-TraceMe" trace-info="tag:{{word}}" content="{{word}}" style="font-size: {{size}}px">{{word}}</li>{{/words}}'
     + '</ul></div>';
 
 IriSP.Widgets.Tagcloud.prototype.defaults = {
--- a/src/widgets/Trace.js	Mon May 21 13:17:47 2012 +0200
+++ b/src/widgets/Trace.js	Tue May 22 16:49:48 2012 +0200
@@ -20,7 +20,6 @@
   }
   var _this = this,
     _listeners = {
-        "IriSP.createAnnotationWidget.addedAnnotation" : 0,
         "IriSP.search.open" : 0,
         "IriSP.search.closed" : 0,
         "IriSP.search" : 0,
@@ -55,59 +54,60 @@
     this.tracer.trace("StartTracing", {});
     
     this.mouseLocation = '';
-    IriSP.jQuery(".Ldt-Widget").bind("click mouseover mouseout dragstart dragstop", function(_e) {
-        var _widget = IriSP.jQuery(this).attr("widget-type"),
-            _class = _e.target.className;
-        var _data = {
-            "type": _e.type,
-            "x": _e.clientX,
-            "y": _e.clientY,
-            "widget": _widget
+    IriSP.jQuery(".Ldt-Widget").bind("click mouseover mouseout", function(_e) {
+        var _target = IriSP.jQuery(_e.target);
+
+        while (!_target.hasClass("Ldt-TraceMe") && !_target.hasClass("Ldt-Widget") && _target.length) {
+            _target = _target.parent();
         }
-        if (typeof _class == "string" && _class.indexOf('Ldt-TraceMe') != -1) {
-            var _name = _e.target.localName,
-                _id = _e.target.id,
-                _text = _e.target.textContent.trim(),
-                _title = _e.target.title,
-                _value = _e.target.value;
-            _data.target = _name + (_id.length ? '#' + IriSP.jqEscape(_id) : '') + (_class.length ? ('.' + IriSP.jqEscape(_class).replace(/\s/g,'.')).replace(/\.Ldt-(Widget|TraceMe)/g,'') : '');
-            if (typeof _title == "string" && _title.length && _title.length < 140) {
-                _data.title = _title;
-            }
-            if (typeof _text == "string" && _text.length && _text.length < 140) {
-                _data.text = _text;
-            }
-            if (typeof _value == "string" && _value.length) {
-                _data.value = _value;
-            }
-            this.player.popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data);
-        } else {
-            //console.log(_e.type+','+_this.mouseLocation+','+_widget);
-            if (_e.type == "mouseover") {
-                if (_this.mouseLocation != _widget) {
+        
+        var _widget = IriSP.jQuery(this).attr("widget-type"),
+            _data = {
+                "type": _e.type,
+                "x": _e.clientX,
+                "y": _e.clientY,
+                "widget": _widget
+            },
+            _targetEl = _target[0],
+            _class = _targetEl.className,
+            _name = _targetEl.localName,
+            _id = _targetEl.id,
+            _value = _targetEl.value,
+            _traceInfo = _target.attr("trace-info"),
+            _lastTarget = _name + (_id && _id.length ? '#' + IriSP.jqEscape(_id) : '') + (_class && _class.length ? ('.' + IriSP.jqEscape(_class).replace(/\s/g,'.')).replace(/\.Ldt-(Widget|TraceMe)/g,'') : '');
+        _data.target = _lastTarget
+        if (typeof _traceInfo == "string" && _traceInfo.length && _traceInfo.length < 140) {
+            _data.traceInfo = _traceInfo;
+            _lastTarget += ( ";" + _traceInfo );
+        }
+        if (typeof _value == "string" && _value.length) {
+            _data.value = _value;
+        }
+        switch(_e.type) {
+            case "mouseover":
+                if (_this.lastTarget != _lastTarget) {
                     _this.player.popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data);
                 } else {
                     if (typeof _this.moTimeout != "undefined") {
                         clearTimeout(_this.moTimeout);
-                        delete _this.moTimeout;
+                        _this.moTimeout = undefined;
                     }
                 }
-            }
-            if (_e.type == "click") {
-                _this.player.popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data);
-            }
-            if (_e.type == "mouseout") {
+            break;
+            case "mouseout":
                 if (typeof _this.moTimeout != "undefined") {
                     clearTimeout(_this.moTimeout);
                 }
                 _this.moTimeout = setTimeout(function() {
-                   if (_data.widget != _this.mouseLocation) {
+                   if (_lastTarget != _this.lastTarget) {
                        _this.player.popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data);
                    }
                 },100);
-            }
+            break;
+            default:
+                _this.player.popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data);
         }
-        _this.mouseLocation = _widget;
+        _this.lastTarget = _lastTarget;
     });
 }
 
--- a/src/widgets/Tweet.js	Mon May 21 13:17:47 2012 +0200
+++ b/src/widgets/Tweet.js	Tue May 22 16:49:48 2012 +0200
@@ -54,8 +54,8 @@
 
 IriSP.Widgets.Tweet.prototype.template =
     '<div class="Ldt-Tweet-Widget"><div class="Ldt-Tweet-Inner"><div class="Ldt-Tweet-PinClose-Buttons">'
-    + '<a href="#" class="Ldt-Tweet-Pin" title="{{l10n.keep_visible}}"></a>'
-    + '<a href="#" class="Ldt-Tweet-Close" title="{{l10n.close_widget}}"></a>'
+    + '<a href="#" class="Ldt-Tweet-Pin Ldt-TraceMe" title="{{l10n.keep_visible}}"></a>'
+    + '<a href="#" class="Ldt-Tweet-Close Ldt-TraceMe" title="{{l10n.close_widget}}"></a>'
     + '</div><div class="Ldt-Tweet-AvatarContainer"><a href="#" class="Ldt-Tweet-ProfileLink" target="_blank">'
     + '<img src="" class="Ldt-Tweet-Avatar"/></a></div><h3><a href="#" class="Ldt-Tweet-ProfileLink Ldt-Tweet-ScreenName" target="_blank">'
     + '</a> (<span class="Ldt-Tweet-FullName"></span>)</h3><p class="Ldt-Tweet-Contents"></p><div class="Ldt-Tweet-Bottom">'
--- a/test/test-config.js	Mon May 21 13:17:47 2012 +0200
+++ b/test/test-config.js	Tue May 22 16:49:48 2012 +0200
@@ -31,7 +31,12 @@
                     type: "AnnotationsList",
                     container: "AnnotationsListContainer"
                 },
-                { type: "Mediafragment"}
+                { type: "Mediafragment"},
+                {
+                    type: "Trace",
+                    default_subject: "tests-iri",
+                    js_console: true
+                }
             ]
         },
         player:{