5

As a UX engineer, I try to stay current with best practices.

I noticed that Facebook recently changed their HTML to be scrambled for the datetime of a post.

I'm wondering why that would be a good practice.

The screenshot below shows all of the nested span elements.

What appears as "September 24 at 6:45 AM" is actually StfepltembretSopr 2oo4sacrnsg aut 6a:g4rl5t orefdmeASMsr ·

I.e. extra characters have been inserted in between certain characters of "September 24 at 6:45 AM".

After copying to clipboard and then pasting somewhere, the garbled text is what I see.

enter image description here

It seems like a deliberate attempt to prevent people from copying the datetime of a post. (But copying and pasting from the body of the post is still allowed.)

Why would this be a good practice?

P.S. I'm using Windows Chrome and am logged in to Facebook. When I try via Incognito (logged out), the datetime does not use nested span elements, so copying-and-pasting is not garbled.

4
  • facebook.com/FacebookforDevelopers/posts/10157725187978553 is a public example of a post. When logged out, its datetime copies as "September 29 at 10:08 AM · ". But when logged in, it's StugsSpeptlogemnhrbermero 29cs atre t1c:ort08r hghedanrsPM ·
    – Ryan
    Commented Oct 1, 2020 at 15:10
  • I don't think there's any way to answer this with any certainty without actually asking the team at Facebook. However, since it only appears when logged in, it could be a way of adding a security key to any interaction with that particular post. Commented Oct 3, 2020 at 7:44
  • I think this Q&A site is supposed to help people with best practices. I don't need to know what motivations Facebook in particular had, but if this practice is familiar to someone here, I am curious to know what possible reasons there are for using it. When is it appropriate / helpful?
    – Ryan
    Commented Oct 3, 2020 at 20:14
  • You're getting the same crap when typing a FB comment! Commented Nov 6, 2020 at 2:54

4 Answers 4

3

It's to try to prevent ad-blocking software from distinguishing between the word "Sponsored" on in-line advertisements and the posting date on non-advertisements. The date text is exactly where the word "Sponsored" is shown on ads, but the word "Sponsored" is obfuscated in the same way. (You can almost certainly find the letters S p o n s o r e d in that order scattered in every obfuscated date, and probably a plausible date in every obfuscated "Sponsored".) For a direct link to a post or perhaps for a non-logged in viewer this wouldn't matter, but if you're logged in and scrolling through, then FB really wants you to be able to see the ads. This has been effective in defeating uBlock Origin for several years now (uBlock still blocks the side-bar ads).

2
  • Wow, that's really interesting.
    – Ryan
    Commented Sep 8, 2022 at 18:12
  • 1
    Oh wow, I suppose for a company who's primary source of revenue is ad sales, this level of sophistication in beating ad blockers makes sense. Very interesting. @Ryan, I'd suggest you change the accepted answer to this one—this seems likely to be what's going on here. Commented Sep 9, 2022 at 12:52
2

Note: @Marty White's answer is more likely, in my opinion, to be the actual explanation.

Using the following clues, I'm fairly certain it's an unintended side effect of however they're implementing their internationalization. As you've noticed, this is not helpful, expected, or user-friendly in any way, so it almost certainly is an unpolished result of some implementation detail.

  • As you mention, it doesn't happen when you're logged out, which would make sense, as they probably use a different method for determining how to render dates once they look up your full profile details. Before you're logged in, the method is probably just based on the time zone set in your browser by your OS or checking the general location of your IP. After logging in, it probably uses a dictionary of sorts to present the date in the official format for your exact region set in your profile.

  • The example you provided is not actually the correct, localized date with extra characters mixed in, like you indicated in the question.
    StugsSpeptlogemnhrbermero 29cs atre t1c:ort08r hghedanrsPM
    S ept em ber 29 at 1 : 08 PM
    "September 29 at 1:08 PM"
    September 29 at 1:08 PM = September 29 at 10:08 AM + 3 hours
    The fact that the jumbled mess *mostly* contains the same date, but only offset by a whole number of hours implies that the date representation is being calculated and rendered by the browser, based on the time zone it thinks you're in.

I did not check prior to today, but it works as expected for me this morning. I'm not sure if they've made a change to it's behavior since you opened this question, but I'd be curious to see if you still experience the same issue.

1
1

As @Marty mentioned, this issue is related to ad blockers. They go to great lengths to make it difficult to detect sponsored posts easily.

If you want to reassemble the characters, one approach is to get the bounding box of the parent element, then find all the child elements within that bounding box. By ordering them by their x position, you can reconstruct the original text.

Here's an example of how this can be accomplished in JavaScript:

function unscrambleTextFromChildren(element) {
    if (!element) return [];

    const parentRect = element.getBoundingClientRect();
    const children = Array.from(element.children);
    const containedChildren = [];

    children.forEach(child => {
        const childRect = child.getBoundingClientRect();

        if (
            childRect.top >= parentRect.top &&
            childRect.left >= parentRect.left &&
            childRect.bottom <= parentRect.bottom &&
            childRect.right <= parentRect.right
        ) {
            containedChildren.push(child);
        }
    });

    containedChildren.sort((a, b) => {
        const rectA = a.getBoundingClientRect();
        const rectB = b.getBoundingClientRect();
        return rectA.left - rectB.left;
    });

    return containedChildren.map(item => item.innerText).join('');
}

They also seem to be using canvas elements in some areas of their platform to achieve the same effect. Interestingly, they create canvases with random widths containing the word "Sponsored." To counter this, you can trim the whitespace around the word and check it against the fixed "Sponsored" base64ed canvas.

All this effort likely slows down Facebook and was bypassed in about an hour of experimentation. It might be more beneficial to focus on improving Facebook itself rather than implementing features like this which make it harder for users.

function isSponsoredCanvas(canvas) {
    if (!(canvas instanceof HTMLCanvasElement)) {
        throw new Error("Invalid canvas element");
    }

    const context = canvas.getContext('2d');
    if (!context) {
        throw new Error("Could not get canvas context");
    }

    const width = canvas.width;
    const height = canvas.height;
    const imageData = context.getImageData(0, 0, width, height);
    const data = imageData.data;

    let top = height, left = width, bottom = 0, right = 0;

    // Find the bounding box of the non-transparent pixels
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const alpha = data[(y * width + x) * 4 + 3];
            if (alpha > 0) {
                if (x < left) left = x;
                if (x > right) right = x;
                if (y < top) top = y;
                if (y > bottom) bottom = y;
            }
        }
    }

    // If the canvas is completely empty
    if (right < left || bottom < top) {
        return canvas.toDataURL('image/png');
    }

    const trimmedWidth = right - left + 1;
    const trimmedHeight = bottom - top + 1;
    const trimmedCanvas = document.createElement('canvas');
    const trimmedContext = trimmedCanvas.getContext('2d');

    trimmedCanvas.width = trimmedWidth;
    trimmedCanvas.height = trimmedHeight;

    trimmedContext.putImageData(
        context.getImageData(left, top, trimmedWidth, trimmedHeight),
        0, 0
    );

    return trimmedCanvas.toDataURL('image/png') === 'data:image/png;base64,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';
}
0

The question is what would be a reason to scramble posts dates during copy and paste, not what is the reason, just what might be a possible reason. Not everyone understands what a hypothetical scenario is, but purely from a theoretical basis to answer the q it might be worth considering a case in which fb wasn't goldilocks. So, I am not saying any of below is true, just like considering like x=ai+b (imaginary solutions) to a question of what is the root of a quadratic equation where the solution is really known to be real. If that is confusing please stop reading now.

That out of the way, purely hypothetically speaking, what if

(1) FB wanted a semi-monopoly on downloading user's own data, so that you'd need to make a request to them and their software, or a human, could have a chance to edit it as they wished before hand? Hypothetically.

(2) FB wanted to alter dates without people knowing?

(3) FB wanted to de-index some posts they didn't like?

(4) FB feared article theft, where search engine optimization "authors" would use a scraper to copy and paste high quality (some posts can be long on fb and one could use likes and size to get an idea of quality) content from facebook?

(5) FB wanted to know who was copying what?

It's purely hypothetical, but let's say a bad event occurred, and the exact time of some post mattered. You know, like a court case where they ask these sorts of questions. Would #2 give fb more power? Hypothetically? What if the power is just to know that one requested a download of their data, as in having a list of all the users who don't really trust fb to store their data?

Is it really true that the view of someone logged in is the same as the view of someone not logged in, or are the number of visible posts drastically limited for those viewing anonymously?

What features would be helpful in the hypothetical goals #1-#5?

One feature for both might be a non-direct relation between what the user gets when copying text manually (after expanding posts, itself a potentially related "feature") and what the computer saves into say a word document upon a paste command. It would make it more difficult for people from just doing that simple method, and boost the role of making fb be the middle man. There are historical cases where an entity wishes to be the middle man, so it is not as insane as it seems. An exact example is the app called com.mhd.flasher.n54 which allows one to modify some engine parameters. (You have to go online and enter your vin number. You cannot just hack your car without going through the middle man.)

Is that the reason? No idea, but I did think the other answer is not the only possible answer so I wanted to post this one.

What if the other characters might be unique to the copy so that the time of the copy and who did the copy can be known. This is super easy to test of course but I'm lazy and just wanted to post other more creative answers than the "this is a dumb question" (the supreme brilliance of something as shocking as occam's razor, just a glitch) answer sitting here before us now.

1
  • I did not say "this is a dumb question." Commented Jun 23, 2021 at 15:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.