3

I'm working on a project where I need to create an object in an iframe and then send said object to the parent window.

The problem is the postMessage is failing as the object cannot be cloned (DataCloneError) as it has a function (callback) property.

What further complicates this is there is a cyclic relationship whereby button lists contain buttons and each button has a reference to its parent list.

If this was using JSON.stringify rather than structured cloning, one could override toJSON on the button and avoid sending callback and replace list with listId to avoid the cyclic reference situation. Is there an equivalent to toJSON for structured cloning which would allow ignoring callback while maintaining a cyclic relationship, or another solution?

This is the rough gist of the situation with the error reproducable:

class ButtonList {
    constructor() {
        this.buttons = [];
    }

    addButton(button) {
        if (!this.buttons.includes(button)) {
            this.buttons.push(button);
            button.setList(this);
        }
        return this;
    }
}

class Button {
    setList(list) {
        if (!list) return this;
        if (this.list !== list) {
            this.list = list;
            list.addButton(this);
        }
        return this;
    }

    setCallback(callback) {
        this.callback = callback;
        return this;
    }

    getCallback() {
        return this.callback;
    }

    runCallback() {
        if (!this.callback) return this;
        this.callback();
        return this;
    }
}

const list = new ButtonList();
const button = new Button().setList(list).setCallback(() => console.log('Hello'));
window.postMessage(list, '*');

// DataCloneError: The object could not be cloned.

The parent window does not need to know the callback but needs to know any other properties.

shaneod
  • 49
  • 1
  • 7

1 Answers1

-1

Create a new object with overwritten properties with Object.assign and send that through postMessage.

const foo = {
  bar: 'bar',
  list: { bla: 'bla' },
  baz: function() {
    console.log('baz')
  }
}

const serializable = Object.assign({}, foo, { 
  list: 3,
  baz: undefined 
})

console.log(serializable)
nicholaswmin
  • 21,686
  • 15
  • 91
  • 167
  • Thanks, this works. Just wondering though, would there be performance concerns given this would require cloning the list and then iterating over all buttons in the list and cloning them with callback undefned? – shaneod Jan 31 '19 at 16:38
  • 1
    @shaneod Performance concerns are relative; Granted, `Object.assign()` would create a new object here but are you planning to do this for millions of large objects? Also, unrelated but probably more important, you should be careful as `Object.assign` does **not** deep clone. – nicholaswmin Jan 31 '19 at 16:39
  • The number would be small enough. This approach sounds good then. Thanks :) – shaneod Jan 31 '19 at 16:48