W3C home > Mailing lists > Public > www-dom@w3.org > January to March 2014

Re: MutationObserver - a better interface

From: Axel Dahmen <brille1@hotmail.com>
Date: Sat, 15 Feb 2014 12:27:26 +0100
To: www-dom@w3.org
Message-ID: <ldniun$1vh$1@ger.gmane.org>
Just for the records, as a step forward in design, a read-only property might as well have been introduced to the Node interface – something similar to the “location” property to the “Window” object.

This pseudo property, having no public properties but just implementing the (current) MutationObserver interface, would gain the following edges over implementing MutationObserver interface in a Node object directly:

* encapsulation of MutationObserver functionality into a single property
* enabling test for MutationObserver implementation availability

So the interfaces would look like this:

    dictionary MutationObserverInit
    {
        function callback;   // *** new ***
        boolean childList = false;
        boolean attributes;
        boolean characterData;
        boolean subtree = false;
        boolean attributeOldValue;
        boolean characterDataOldValue;
        sequence<DOMString> attributeFilter;
    };


    interface MutationObserverHandler
    {
        void observe(MutationObserverInit options);
        void disconnect(MutationObserverInit options);
        sequence<MutationRecord> takeRecords();
        void suspend();   // *** new ***
        void continue();  // *** new ***
    };


    interface Node : EventTarget
    {
        ...
        [SameObject] readonly MutationObserverHandler observer;
    };



The internal implementation of this new design would stay similar to the one I suggested in my last message. i. e. the MutationObserver interface would have been implemented by an application implementation of Node. However, since the MutationObserver functionality would have got implemented internally, there’d be only the handler interface left, which I now call MutationObserverHandler in my example to emphasize on the change in design.

So still there wouldn’t be orphans, closures or any other malicious obstacles if my suggestion would have been specified by the W3C – which is in contrast to the current, rather confusing, specification.



Here’s an example of using the amended MutationObserver with the updated design suggestion:


    if (document.documentElement.observer)    // if this is true, it’s true for all nodes in the document
    {
        var myNodes = document.getElementsByClassName("MyClass");
        var i;
        var observerInit = { callback: myObserveFunc; childList: true; attributes: true; };

        // A node can store a list of unique MutationObserverInit objects.
        // observe() adds a new object to the node’s list if that object doesn’t already exist in the list.
        // After adding the MutationObserverInit object to the list, observe() adds an internal (hidden)
        // MutationObserver object to the new MutationObserverInit object if not already attached.
        // So, within the following loop, while every node is getting the MutationObserverInit object attached
        // the (internal) MutationObserver object only gets attached at the first iteration because
        // all other nodes get the same MutationObserverInit object attached which already got that
        // internal MutationObserver object attached to it at the first iteration.

        for (i = 0;i < myNodes.length;++i)
            myNodes[i].observer.observe( observerInit );

        // suspend(), continue() and takeRecords() act on the internal (hidden) observerInit object of a node.
        // Calling them affects all nodes with the same observerInit object
        var observerObj = myNodes[0].observer;  // yields the observer object for the first node

        // **** Two new helpful functions, added to MutationObserver interface ****
        observerObj.suspend(); // pause observation of all nodes with the same observerInit object
        observerObj.continue(); // continue observation of all nodes with the same observerInit object

        // pop mutation records of all nodes with the same observerInit object
        observerObj.takeRecords();

        // stop observation and remove MutationObserverInit object from each node separately
        for (i = 0;i < myNodes.length;++i)
            myNodes[i].observer.disconnect( observerInit );
    }







"Axel Dahmen" <brille1@hotmail.com> schrieb im Newsbeitrag news:ldi6a6$3bs$1@ger.gmane.org...
Exactly that’s my argumentation.

It doesn’t matter whether MutationObserver is specified as a public or a private kind of element. Raising the MutationCallback event must *always* be prefixed with a check whether the node(s) it’s firing on are all still members of the same origin.

If MutationObserver was some private implementation of Node, no JavaScript attack could possibly harm its functionality.

So, apart from my first suggestion - which was just a first step in thoughts – I would even have suggested to *hide* MutationObserver by implementing it into the Node object:


    interface Node : EventTarget, MutationObserver
    {
    };

    dictionary MutationObserverInit
    {
        function callback;   // *** new ***
        boolean childList = false;
        boolean attributes;
        boolean characterData;
        boolean subtree = false;
        boolean attributeOldValue;
        boolean characterDataOldValue;
        sequence<DOMString> attributeFilter;
    };


Thus, a sample implementation would look like this:

    {
        var myNodes = document.getElementsByClassName("MyClass");
        var i;
        var observerInit = { callback: myObserveFunc; childList: true; attributes: true; };

        // A node can store a list of unique MutationObserverInit objects.
        // observe() adds a new object to the node’s list if that object doesn’t already exist in the list.
        // After adding the MutationObserverInit object to the list, observe() adds an internal (hidden)
        // MutationObserver object to the new MutationObserverInit object if not already attached.
        // So, within the following loop, while every node is getting the MutationObserverInit object attached
        // the (internal) MutationObserver object only gets attached at the first iteration because
        // all other nodes get the same MutationObserverInit object attached which already got that
        // internal MutationObserver object attached to it at the first iteration.

        for (i = 0;i < myNodes.length;++i)
            myNodes[i].observe( observerInit );

        // **** Two new helpful functions, added to MutationObserver interface ****
        myNodes[0].suspend(); // pause observation of all nodes with the same observerInit object
        myNodes[0].continue(); // continue observation of all nodes with the same observerInit object

        // pop mutation records of all nodes with the same observerInit object
        myNodes[0].takeRecords();

        // stop observation and remove MutationObserverInit object from each node separately
        for (i = 0;i < myNodes.length;++i)
            myNodes[i].disconnect( observerInit );


   }


If - internally - the MutationObserver object would be created by the application and attached to the above observerInit object (without being accessible to the client script), then this would keep the whole application from keeping closure orphans, and thus it'd keep the application from being attacked by a malicious (or poor) programmer who'd simply be allocating a huge number of orphans that couldn’t be accessed through the current MutationObserver design.

Since all nodes in the above example use the same MutationObserverInit object (amended according to my suggestion by adding the callback function to the MutationObserverInit class), the application can attach the - internally created - MutationObserver object to that MutationObserverInit object. And the application can even examine whether a MutationObserver object has already been attached to the MutationObserverInit object, and re-use that same MutationObserver object internally. So all nodes in the above example would use the same - internal - MutationObserver object.

This way, the notion of "MutationObserver" would be obsolete. And prefixing each call of callback with a same-origin check for all nodes it’s been attached to then would make the function safe.


"Bradley Meck" <bradley.meck@gmail.com> schrieb im Newsbeitrag news:CANnEKUZELQGYHEATvEpCTEWJRt2wM=0JxgcV1Pr1C-3rfM4Djg@mail.gmail.com...
forgot to send to list


---------- Forwarded message ----------
From: Bradley Meck <bradley.meck@gmail.com>
Date: Tue, Feb 11, 2014 at 8:21 PM
Subject: Re: MutationObserver - a better interface
To: Axel Dahmen <brille1@hotmail.com>



They are used while they observe least 1 Node (sometimes you observe more than 1); I still have uses where I have multiple views and need to keep them in sync (darn contenteditable). Having multiple Nodes observed at once is a use case why you don't want this on Node (but you can always abstract this away, but then you are talking about multiple aggregations for the MutationRecords... which is sad times). 

Also, if the MutationObserver is exposed as a means to provide behavior based upon a closure you are talking about passing around multiple references to a function which a user script could easily invoke for nefarious means. For example if we have a contenteditable that we use to see if someone has somehow poisoned our offline app's security:

m=new MutationObserver(function (records) {
  // testAppStateNotHijacked should be in a closure
  if (records.some(recordCanHijackApp)) {
    logout();
  }
  else {
    saveHTMLState();
  }
});

As an internal field to MutationObserver, the function is still safe (not talking about debuggers) to the user. If we are passing that function around to each Node, can we make that guarantee that someone won't send in an object that uses a fake records.some?



On Tue, Feb 11, 2014 at 7:58 PM, Axel Dahmen <brille1@hotmail.com> wrote:

  Sure, but that's no contradiction to the fact that a MutationObserver is always attached to a Node object.

  Even if the code you mentioned removes the node from the object tree and re-adds it again, it still stays the same Node object. So if the MutationObserver interface would have been added to a Node object, all references would still be valid and in place.

  And please consider the case when a node simply gets removed:

     var myNode = document.getElementById("myNode");
     myNode.parentNode.removeChild(myNode);

  If the MutationObserver interface would have been implemented at the Node class, there was a natural correlation between the node and events being tracked for children of that node.

  My assumption is that every current DOM application implementation distinguishes between hard and soft references. When a node is neither attached to any document nor referenced by any JavaScript variable it won't fire any more mutation events, without doubt.

  It doesn't make sense to provide a reference to a separate MutationObserver object, being isolated from the Node object it observes. It most easily may become orphaned and invalid.

  Regards,
  Axel Dahmen
  www.axeldahmen.de



  ----------
  "Anne van Kesteren"  schrieb im Newsbeitrag news:CADnb78jBbR+Wd5f3ZamnASXVmSQTaYat4bSAzROuktRpQs9OUg@mail.gmail.com... 


  On Tue, Feb 11, 2014 at 6:34 PM, Axel Dahmen <brille1@hotmail.com> wrote:

    But, well, that's, too, even one more point for having had MutationObserver
    being a Node's member as it doesn't make any sense to observe a Node you
    don't have any more references to in code.


  As I said you often do keep a reference to the node.

  E.g. document.head.appendChild(document.body) will first remove
  document.body and then insert it again. It would be bad if the
  observer was destroyed while this operation took place as subsequent
  document.body.appendChild(...) invocations would go unrecorded.


  -- 
  http://annevankesteren.nl/
Received on Saturday, 15 February 2014 11:28:09 UTC

This archive was generated by hypermail 2.3.1 : Tuesday, 20 October 2015 10:46:22 UTC