LCOV - code coverage report
Current view: top level - modules/core - _signals.js (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 93.7 % 79 74
Test Date: 2024-03-26 02:45:07 Functions: 100.0 % 14 14
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 80.4 % 46 37

             Branch data     Line data    Source code
       1                 :          22 : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
       2                 :             : // SPDX-FileCopyrightText: 2008 litl, LLC
       3                 :             : // SPDX-FileCopyrightText: 2022 Canonical Ltd.
       4                 :             : // SPDX-FileContributor: Marco Trevisan <marco.trevisan@canonical.com>
       5                 :             : 
       6                 :             : /* exported addSignalMethods */
       7                 :             : 
       8                 :             : // A couple principals of this simple signal system:
       9                 :             : // 1) should look just like our GObject signal binding
      10                 :             : // 2) memory and safety matter more than speed of connect/disconnect/emit
      11                 :             : // 3) the expectation is that a given object will have a very small number of
      12                 :             : //    connections, but they may be to different signal names
      13                 :             : 
      14                 :          84 : function _connectFull(name, callback, after) {
      15                 :             :     // be paranoid about callback arg since we'd start to throw from emit()
      16                 :             :     // if it was messed up
      17         [ +  - ]:          84 :     if (typeof callback !== 'function')
      18                 :           0 :         throw new Error('When connecting signal must give a callback that is a function');
      19                 :             : 
      20                 :             :     // we instantiate the "signal machinery" only on-demand if anything
      21                 :             :     // gets connected.
      22         [ +  + ]:          84 :     if (this._signalConnections === undefined) {
      23                 :          42 :         this._signalConnections = Object.create(null);
      24                 :          42 :         this._signalConnectionsByName = Object.create(null);
      25                 :          42 :         this._nextConnectionId = 1;
      26                 :             :     }
      27                 :             : 
      28                 :          84 :     const id = this._nextConnectionId;
      29                 :          84 :     this._nextConnectionId += 1;
      30                 :             : 
      31                 :          84 :     this._signalConnections[id] = {
      32                 :          84 :         name,
      33                 :          84 :         callback,
      34                 :          84 :         after,
      35                 :             :     };
      36                 :             : 
      37         [ +  + ]:          84 :     const connectionsByName = this._signalConnectionsByName[name] ?? [];
      38                 :             : 
      39         [ +  + ]:          84 :     if (!connectionsByName.length)
      40                 :          48 :         this._signalConnectionsByName[name] = connectionsByName;
      41                 :          84 :     connectionsByName.push(id);
      42                 :             : 
      43                 :          84 :     return id;
      44                 :          84 : }
      45                 :             : 
      46                 :          44 : function _connect(name, callback) {
      47                 :          44 :     return _connectFull.call(this, name, callback, false);
      48                 :             : }
      49                 :             : 
      50                 :          40 : function _connectAfter(name, callback) {
      51                 :          40 :     return _connectFull.call(this, name, callback, true);
      52                 :             : }
      53                 :             : 
      54                 :          35 : function _disconnect(id) {
      55         [ -  + ]:          35 :     const connection = this._signalConnections?.[id];
      56                 :             : 
      57         [ +  + ]:          35 :     if (!connection)
      58                 :           4 :         throw new Error(`No signal connection ${id} found`);
      59                 :             : 
      60         [ +  - ]:          31 :     if (connection.disconnected)
      61                 :           0 :         throw new Error(`Signal handler id ${id} already disconnected`);
      62                 :             : 
      63                 :          31 :     connection.disconnected = true;
      64                 :          31 :     delete this._signalConnections[id];
      65                 :             : 
      66                 :          31 :     const ids = this._signalConnectionsByName[connection.name];
      67         [ +  - ]:          31 :     if (!ids)
      68                 :           0 :         return;
      69                 :             : 
      70                 :          31 :     const indexOfId = ids.indexOf(id);
      71         [ -  + ]:          31 :     if (indexOfId !== -1)
      72                 :          31 :         ids.splice(indexOfId, 1);
      73                 :             : 
      74         [ +  + ]:          31 :     if (ids.length === 0)
      75                 :          19 :         delete this._signalConnectionsByName[connection.name];
      76                 :           0 : }
      77                 :             : 
      78                 :           8 : function _signalHandlerIsConnected(id) {
      79         [ -  + ]:           8 :     const connection = this._signalConnections?.[id];
      80         [ +  + ]:           8 :     return !!connection && !connection.disconnected;
      81                 :           8 : }
      82                 :             : 
      83                 :           4 : function _disconnectAll() {
      84         [ +  - ]:          16 :     Object.values(this._signalConnections ?? {}).forEach(c => (c.disconnected = true));
      85                 :           4 :     delete this._signalConnections;
      86                 :           4 :     delete this._signalConnectionsByName;
      87                 :             : }
      88                 :             : 
      89                 :         380 : function _emit(name, ...args) {
      90         [ +  + ]:         380 :     const connections = this._signalConnectionsByName?.[name];
      91                 :             : 
      92                 :             :     // may not be any signal handlers at all, if not then return
      93         [ +  + ]:         380 :     if (!connections)
      94                 :          18 :         return;
      95                 :             : 
      96                 :             :     // To deal with re-entrancy (removal/addition while
      97                 :             :     // emitting), we copy out a list of what was connected
      98                 :             :     // at emission start; and just before invoking each
      99                 :             :     // handler we check its disconnected flag.
     100                 :         768 :     const handlers = connections.map(id => this._signalConnections[id]);
     101                 :             : 
     102                 :             :     // create arg array which is emitter + everything passed in except
     103                 :             :     // signal name. Would be more convenient not to pass emitter to
     104                 :             :     // the callback, but trying to be 100% consistent with GObject
     105                 :             :     // which does pass it in. Also if we pass in the emitter here,
     106                 :             :     // people don't create closures with the emitter in them,
     107                 :             :     // which would be a cycle.
     108         [ +  + ]:         362 :     const argArray = [this, ...args];
     109                 :             : 
     110                 :         362 :     const afterHandlers = [];
     111                 :         362 :     const beforeHandlers = handlers.filter(c => {
     112         [ +  + ]:         406 :         if (!c.after)
     113                 :         358 :             return true;
     114                 :             : 
     115                 :          48 :         afterHandlers.push(c);
     116                 :          48 :         return false;
     117                 :             :     });
     118                 :             : 
     119         [ +  + ]:         362 :     if (!_callHandlers(beforeHandlers, argArray))
     120                 :          50 :         _callHandlers(afterHandlers, argArray);
     121                 :          18 : }
     122                 :             : 
     123                 :         412 : function _callHandlers(handlers, argArray) {
     124         [ +  + ]:         494 :     for (const handler of handlers) {
     125         [ +  + ]:         396 :         if (handler.disconnected)
     126                 :             :             continue;
     127                 :             : 
     128                 :         388 :         try {
     129                 :             :             // since we pass "null" for this, the global object will be used.
     130                 :         388 :             const ret = handler.callback.apply(null, argArray);
     131                 :             : 
     132                 :             :             // if the callback returns true, we don't call the next
     133                 :             :             // signal handlers
     134         [ +  + ]:         376 :             if (ret === true)
     135                 :         314 :                 return true;
     136                 :          12 :         } catch (e) {
     137                 :             :             // just log any exceptions so that callbacks can't disrupt
     138                 :             :             // signal emission
     139                 :          12 :             logError(e, `Exception in callback for signal: ${handler.name}`);
     140                 :             :         }
     141                 :             :     }
     142                 :             : 
     143                 :          98 :     return false;
     144                 :         314 : }
     145                 :             : 
     146                 :          18 : function _addSignalMethod(proto, functionName, func) {
     147 [ +  - ][ +  - ]:          18 :     if (proto[functionName] && proto[functionName] !== func)
     148                 :           0 :         log(`WARNING: addSignalMethods is replacing existing ${proto} ${functionName} method`);
     149                 :             : 
     150                 :          18 :     proto[functionName] = func;
     151                 :             : }
     152                 :             : 
     153                 :           3 : function addSignalMethods(proto) {
     154                 :           3 :     _addSignalMethod(proto, 'connect', _connect);
     155                 :           3 :     _addSignalMethod(proto, 'connectAfter', _connectAfter);
     156                 :           3 :     _addSignalMethod(proto, 'disconnect', _disconnect);
     157                 :           3 :     _addSignalMethod(proto, 'emit', _emit);
     158                 :           3 :     _addSignalMethod(proto, 'signalHandlerIsConnected', _signalHandlerIsConnected);
     159                 :             :     // this one is not in GObject, but useful
     160                 :           3 :     _addSignalMethod(proto, 'disconnectAll', _disconnectAll);
     161                 :             : }
        

Generated by: LCOV version 2.0-1