Closed Bug 1902897 Opened 11 months ago Closed 11 months ago

CanvasBlocker intercepts Element.prototype.innerHTML setter

Categories

(Core :: JavaScript Engine, defect)

Firefox 127
All
Windows
defect

Tracking

()

RESOLVED INVALID

People

(Reporter: theghostofinky, Unassigned)

Details

Attachments

(2 files)

Attached image broken.png

User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0

Steps to reproduce:

I am the main developer of an open source political flowchart quiz called ideosorter, 2 years ago a 3rd party (Github @aurium) provided a patch to my localization/internationalization code based on their own NPM module (https://www.npmjs.com/package/@aurium/i18n) and at some point in the last year firefox stopped correctly displaying the text strings required, displaying instead the javascript default value, undefined.

Actual results:

Through having debugged the code I discovered the bug only happens when a string returned from a proxy (which the i18n module uses) gets assigned to an HTMLElement's innerHTML property.

In my debugging process I added a function that grabs the string from the proxy, assigns it to a local constant and checks if it's undefined, throwing an error if it's not defined, through breakpoints placed on the debugger I found that it never triggers the exception and always returns the proper string, but yet when the return value of the function is assigned to .innerHTML it still displays as undefined.

My workaround for the bug was to replace innerHTML with innerText in all the instances that didn't contain HTML tags, and to break the string down into a character array and then rejoin them with an empty string to assign to the .innerHTML property.

It's also worth pointing out that this is a highly inconsistent bug, occurring on Firefox 127 stable (and several older versions) in normal mode, but not on private browsing mode, I tried turning off all extensions and the results were unchanged, discarding those as the potential issue.

Attached I include a screenshot of the pre-patched code I archived on cloudflare pages (https://ideosorter.pages.dev/), for an example of how it should look see the official (now patched) github pages (https://ideosorter.github.io/).

Expected results:

I expected the string returned from the proxy object in the i18n module to be assigned to the HTML elements' innerHTML property as normal, like what happens in private browsing mode and all chromium based browsers I tested

OS: Unspecified → Windows
Hardware: Unspecified → All
Summary: Firefox not properly assinging proxy strings to innerHTML → Firefox not properly assigning proxy strings to innerHTML

The Bugbug bot thinks this bug should belong to the 'Core::JavaScript Engine' component, and is moving the bug to that component. Please correct in case you think the bot is wrong.

Component: Untriaged → JavaScript Engine
Product: Firefox → Core

Thank you for reporting.

Unfortunately, It's not reproducible here on Firefox 127.0 (64-bit) on Windows 11.

Is it correct that the following is the reproduction steps?

  1. run Firefox 127.0 (64-bit) with clean profile, on Windows 11
  2. In normal window, type https://ideosorter.pages.dev/ to location bar and enter
  3. Select "New private window" from the menu
  4. In private window, type https://ideosorter.pages.dev/ to location bar and enter

For me, there's no difference between step 2 and step 4.
Neither of them show "undefined", but they look "expected" rendering (attached the screenshot), that looks identical to what I observe on https://ideosorter.github.io/

Can you please check the following?

  • (A) Does the issue reproduce on clean profile? (Just to make sure it's not caused by some settings)
  • (B) When you open the Network Monitor and reload the page, do you any difference between "good" case and "bad" case? is there any resource gets blocked etc?
  • (C) When you open the Web Console and reload the page, do you see any error message or warning message, or any difference between "good" case and "bad" case?

Also, if you've already located where the difference happens in the source, can you post the details, such as file's URL and line number, and what the types and values there are?
If you have minimal testcase, that's also helpful.

Flags: needinfo?(theghostofinky)

Looking into the code, it looks like the proxy returned by i18n library returns an instance of a subclass of String:

https://unpkg.com/@aurium/i18n@0.4.1/i18n.mjs

var i18nHandler = {
  get: function(target, prop) {
    if (prop[0] === '$' && target[prop]) return target[prop]()
    let l10n = getL10n(prop, target, [...target.langs])
    if (l10n.forEach) return mkL10nArray(l10n)
    else return mkL10nString(l10n)
  }
}

export default function i18nBuilder(l10nData, langList, defaultLang) {
  const clonedL10nData = { }
  for (let lang in l10nData) clonedL10nData[lang] = { ...l10nData[lang] }
  const target = {
...
  }
  return new Proxy(target, i18nHandler)
}

where the string subclass is

export class L10nString extends String {

  toString() {
    return toStr(this).split('\0')[0]
  }

  get str() {
    return this.toString()
  }

  plural(num) {
    let str = '' + this
    return new L10nString(str.replace(/\{${num}\}/g, num))
  }

  build(...args) {
    return stringBuilder(this, ...args)
  }

  category(c) {
    return new L10nString(toStr(this))
  }

}

Then, I assume the issue you've observed happens at the following line:

https://ideosorter.pages.dev/scripts/page-index.js

document.getElementById("title").innerHTML = i18n.ui_index_title

Can you rewrite the above line to the following and check the result?

{
  let rawResult = i18n.ui_index_title;
  console.log("rawResult", rawResult);
  let toStringResult = rawResult.toString();
  console.log("toStringResult", toStringResult);
  document.getElementById("title").innerHTML = toStringResult;
}

If the issue happens at the point of Proxy's get handler, rawResult would be undefined.
If the issue happens at the point of L10nString methods, rawResult would show the L10nString instance, and toStringResult would be undefined.

Thanks for replying, I added the code you indicated me to and both of the logs printed the correct string to the console, so I did create a new profile to see if it continued to happen, and it did not.

I went back to trying to see if one extension was causing it (I had already gone down this route before and hadn't noticed a difference, perhaps I missed one), and it turns out it's CanvasBlocker intercepting the call to .innerHTML, I have reported my findings in their github, you can close this issue now since it's not directly firefox related, sorry for the bother.

Flags: needinfo?(theghostofinky)

No problem! Good to hear that you've figured out the issue :)
Thank you for sharing your findings!

Status: UNCONFIRMED → RESOLVED
Closed: 11 months ago
Resolution: --- → INVALID
Summary: Firefox not properly assigning proxy strings to innerHTML → CanvasBlocker intercepts Element.prototype.innerHTML setter
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: