Search code examples
javascriptdomselectiontextselection

Selection object could not be cloned


I've tried 4 ways to clone the Selection object but it does not work.

The first 3 methods return a empty Object.

document.onselectionchange = function() {
  let selection = document.getSelection();

  if( !selection.isCollapsed ) {
      var clone1 = Object.assign({}, selection);
      var clone2 = {
        ...selection
      };
      var clone3 = JSON.stringify(selection);
      // This one trows a Error
      // Uncaught DOMException: Failed to execute 'structuredClone' on 'Window': Selection object could not be cloned.
      var clone4 = structuredClone(selection);

  }

  let {anchorNode, anchorOffset, focusNode, focusOffset} = selection;

  // anchorNode and focusNode are text nodes usually
  console.log("from.value", `${anchorNode["data"]}, offset ${anchorOffset}`)
  console.log("fto.value", `${focusNode["data"]}, offset ${focusOffset}`)

}

It only works this way:

document.onselectionchange = function() {
  let selection = document.getSelection();

  if( !selection.isCollapsed ) {
      var clone5 = {};
      clone5.anchorNode = selection.anchorNode;
      clone5.anchorOffset = selection.anchorOffset;
      clone5.focusOffset = selection.focusOffset;
      clone5.focusNode = selection.focusNode;
      console.log("clone5", clone5);
  }

  let {anchorNode, anchorOffset, focusNode, focusOffset} = selection;

  // anchorNode and focusNode are text nodes usually
  console.log("from.value", `${anchorNode["data"]}, offset ${anchorOffset}`)
  console.log("fto.value", `${focusNode["data"]}, offset ${focusOffset}`)

}

Solution

  • The reason why none of them work is, that the object returned by getSelection() doesn't have any of this properties assigned on its own. All methods you used rely on the fact, that the object has the properties on it's own.

    The properties are all inherited via prototype inheritance from the Selection.prototype and have a function defined as getter, which results in a read only value.

    Even Object.getOwnPropertyDescriptors(getSelection()) returns an empty object.

    But with a for-in loop you can copy it, as it iterates through every enumerable property, including inherited props from the prototype.

    const sel = getSelection(), clone = {};
    for (const prop in sel) {
    
        const desc = Object.getOwnPropertyDescriptor(sel.constructor.prototype, prop);
        console.log(
          "prop", prop ,
          "own:", sel.hasOwnProperty(prop),
          "enumerable", sel.propertyIsEnumerable(prop),
          "proto?", sel.constructor.prototype.hasOwnProperty(prop)
        );
        // functions should be avoided
        if (typeof sel[prop] == "function")
            continue;
        console.log("getter?", desc.hasOwnProperty('get'));
        clone[prop] = sel[prop];
    }
    console.log(sel); // orig
    console.log(clone); // clone without functions