1

I'm experiencing Lightning Web Components Range proxy behavior that is not expected, on API 55.

Creating a new Javascript Range object in a Locker Component:

// <div class="container" lwc:dom="manual"></div>

renderedCallback() {
    const container = this.template.querySelector('.container');
    container.innerHTML = '<div>Hello World</div>';
    const div = container.querySelector('div');

    const range1 = document.createRange();
    range1.setStart(div, 0);
    range1.setEnd(div, 1);
    
    console.log('div: ', div); // div: Proxy {} 
    console.log('range1: ', range1); // range1: n {}
    console.log('range1: ', range1.toString()); // range1:  [object Object]
    console.log('range1: ', JSON.stringify(range1)); // range1:  {}
}

Instead of returning the string of the text range, it inherits the default Object.toString method and returns [object Object]

Another piece of range functionality that I want to use is checking if 2 ranges intersect.

export function firstRangeIntersect(rangeOne, rangeTwo) {
  const {
    startContainer,
    startOffset,
    endContainer,
    endOffset,
  } = rangeTwo;
  return rangeOne.isPointInRange(startContainer, startOffset) ||
      rangeOne.isPointInRange(endContainer, endOffset);
}

TypeError: Failed to execute 'isPointInRange' on 'Range': parameter 1 is not of type 'Node'.
at Proxy.c (aura_prod.js:65:119975)

I believe the error here is that range.startContainer and range.endContainer return proxy objects instead of DOM Nodes, but isPointInRange handler for the range proxy still expects a Node.

[1] https://developer.salesforce.com/docs/component-library/tools/locker-service-viewer
[2] https://developer.mozilla.org/en-US/docs/Web/API/Range

If there is a suggested way to use ranges or a workaround please let me know!


Update: There seems to be 2 representations for range objects:

Type 1.

  • prints to console as Proxy {}
  • does not implement range.isPointInRange
  • implements range.toString

Type 2.

  • prints to console as n {}
  • implements range.isPointInRange
  • does not implement range.toString (returns [object Object])
  • document.createRange mostly creates this kind of range for me, after a while they seem to turn into Type 1 ranges somehow.

Theres a simple path forward for me to recreate a range with document.createRange to use isPointInRange, I just need to figure out what deterministic operation turns a freshly create range into a "Proxy".

2
  • Are you using Locker Service or Lightning Web Security?
    – sfdcfox
    Commented Aug 8, 2022 at 20:25
  • I believe this is in a Locker Service environment
    – James
    Commented Aug 9, 2022 at 4:49

1 Answer 1

1

I did some research on this, and it's mildly complicated, to say the least. In the Aura runtime, there is a class called Range, that is constructed by a method called SecureRange, and created in a SecureRangeWrapper. The original Range object is still in memory, but is not available to your script, since it's neatly tucked away somewhere.

In other words, what you're working with inside of Salesforce is actually a custom class Range that is not equal to the standard Range object as written in the MDN. This class doesn't forward toString to the raw Range object inside the Proxy, so that's why you're getting [object Object] from toString.

I tested this with this code:

import { LightningElement } from "lwc";
function firstRangeIntersect(rangeOne, rangeTwo) {
  const { startContainer, startOffset, endContainer, endOffset } = rangeTwo;
  return (
    rangeOne.isPointInRange(startContainer, startOffset) ||
    rangeOne.isPointInRange(endContainer, endOffset)
  );
}

export default class Q382551 extends LightningElement {
  renderedCallback() {
    const range1 = document.createRange();
    const div1 = this.template.querySelector("div");
    range1.setStart(div1, 0);
    range1.setEnd(div1, 1);
    console.log(range1.toString()); // [object Object]
    console.log(firstRangeIntersect(range1, range1)); // true
  }
}

And it did work as expected (I'm also in v55.0), except for the [object Object] issue. I was able to create a range and get a true value from firstRangeIntersect.

I wrote some code to test out Range itself, and found that it will not work if Range is in a Proxy:

let range = new Proxy(document.createRange(), {});
range.setStart(document.querySelector('div')); // TypeError

So, a lot of extra work was necessary to provide Range at all, because it won't work when placed in a Proxy.

Moving the method to another component:

import { LightningElement } from "lwc";
import { firstRangeIntersect } from "c/utilsRange";

export default class Q382551 extends LightningElement {
  renderedCallback() {
    const range1 = document.createRange();
    const div1 = this.template.querySelector("div");
    range1.setStart(div1, 0);
    range1.setEnd(div1, 1);
    console.log(range1.toString());
    console.log(firstRangeIntersect(range1, range1));
  }
}

Still worked, except console.log(range1.toString()); now seems to return "" (empty string). I wasn't able to replicate your issue, but I think it's entirely possible that you're tripping across some bug related to how the Proxy is being handled in SecureRange.

If you can recreate the bug in some concise code, I'd recommend that you contact salesforce.com and get a bug logged for this, or, I would be happy to report it for you if you don't have Developer Support. I just need a reproduction that I can put into an org of my own.

8
  • Thanks sfdcfox, I'm working on a repro case for the firstRangeIntersect issue, I'll update the question when I'm done. It may take me a bit, I'm blocked on this weird quirk where half of my ranges print to the console as "Proxy {}" (when cached) and the other half as "n {}" (immediately after creation). I think the cached ranges are the ones that do not work well. Let me know if this is something you've seen before.
    – James
    Commented Aug 11, 2022 at 7:05
  • @James I've seen kind of a similar issue where LWS was causing Uint8Array to not work after being cached. This problem seems similar, but with a different mechanism in play. I created a demo component that shows how to properly proxy a Range, so I'm going to compare that to SecureRange and see if I can spot the difference, maybe come up with one of my own.
    – sfdcfox
    Commented Aug 11, 2022 at 9:35
  • sfdcfox, do you mind helping me submit the toString issue to Developer Support first, and I'll try to get a simple repro for the isPointInRange separately?
    – James
    Commented Aug 11, 2022 at 21:36
  • following up on this. I've gotten as far as I think I can investigating lightning range objects, updated the post with what I've found.
    – James
    Commented Aug 19, 2022 at 18:43
  • @sfdcfox following up on this
    – James
    Commented Sep 2, 2022 at 17:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .