Open Bug 2018543 Opened 1 month ago Updated 7 days ago

lacunacoil.com - Buttons are not working

Categories

(Web Compatibility :: Site Reports, defect, P2)

Desktop
Windows 10

Tracking

(Webcompat Priority:P3, Webcompat Score:1, firefox148 affected, firefox149 affected)

ASSIGNED
Webcompat Priority P3
Webcompat Score 1
Tracking Status
firefox148 --- affected
firefox149 --- affected

People

(Reporter: rbucata, Assigned: twisniewski)

References

()

Details

(4 keywords, Whiteboard: [webcompat-source:web-bugs])

User Story

platform:windows,mac,linux,android
impact:site-broken
configuration:general
affects:all
branch:release
diagnosis-team:dom
user-impact-score:0

Attachments

(2 files)

Environment:
Operating system: Android 16 and Windows 10
Firefox version: Firefox Mobile 147.0

Steps to reproduce:

  1. Navigate to: https://lacunacoil.com/
  2. Click on any buttons and observe

Expected Behavior:
Buttons are working

Actual Behavior:
No buttons or links work on site

Notes:

  • Reproduces regardless of the status of ETP
  • Reproduces in firefox-nightly, and firefox-release
  • Does not reproduce in chrome

Created from https://github.com/webcompat/web-bugs/issues/209108

Attached video Chr vs ff
Severity: -- → S2
User Story: (updated)
Webcompat Priority: --- → P2
Webcompat Score: --- → 6
Priority: -- → P2
Webcompat Score: 6 → 5

There's a global click handler that calls preventDefault(). But there's also a click handler in each anchor element. Firefox somehow pass through the dom hierarchy and only calls the global listener...

Somehow all jQuery-added listeners are being ignored? Breakpoints don't run either from devtools.

Huge thanks for Nicolas Chevobbe and Julian Descottes from devtools for helping the investigation.

They noticed that wp-rocket is involved - https://docs.wp-rocket.me/article/1349-delay-javascript-execution, and that overrides on* event handler attributes and addEventListener methods. Per the doc it converts the page to load the scripts asynchronously and run things later.

All scripts that are present in the HTML of the page, will be delayed. Any scripts which are inserted after the page loads, or fetched indirectly from another script, cannot be delayed.

The page has a relevant inline script block:

            ( () => {
                class RocketLazyLoadScripts {
                    constructor() {
                        this.v = "2.0.3",
                        this.userEvents = ["keydown", "keyup", "mousedown", "mouseup", "mousemove", "mouseover", "mouseenter", "mouseout", "mouseleave", "touchmove", "touchstart", "touchend", "touchcancel", "wheel", "click", "dblclick", "input", "visibilitychange"],
                        this.attributeEvents = ["onblur", "onclick", "oncontextmenu", "ondblclick", "onfocus", "onmousedown", "onmouseenter", "onmouseleave", "onmousemove", "onmouseout", "onmouseover", "onmouseup", "onmousewheel", "onscroll", "onsubmit"]
                    }
                    async t() {
                        this.i(),
                        this.o(),
                        /iP(ad|hone)/.test(navigator.userAgent) && this.h(),
                        this.u(),
                        this.l(this),
                        this.m(),
                        this.k(this),
                        this.p(this),
                        this._(),
                        await Promise.all([this.R(), this.L()]),
                        this.lastBreath = Date.now(),
                        this.S(this),
                        this.P(),
                        this.D(),
                        this.O(),
                        this.M(),
                        await this.C(this.delayedScripts.normal),
                        await this.C(this.delayedScripts.defer),
                        await this.C(this.delayedScripts.async),
                        this.F("domReady"),
                        await this.T(),
                        await this.j(),
                        await this.I(),
                        this.F("windowLoad"),
                        await this.A(),
                        window.dispatchEvent(new Event("rocket-allScriptsLoaded")),
                        this.everythingLoaded = !0,
                        this.lastTouchEnd && await new Promise((t => setTimeout(t, 500 - Date.now() + this.lastTouchEnd))),
                        this.H(),
                        this.F("all"),
                        this.U(),
                        this.W()
                    }

Chrome hits this.everythingLoaded = !0, which is checked in the overridden event listener stuffs. Somehow not on Firefox.

(And it seems devtools is confused and thinks listeners are added via jQuery when they are actually not)

Looks like we are stuck here:

        await this.C(this.delayedScripts.normal),
        await this.C(this.delayedScripts.defer),
        await this.C(this.delayedScripts.async),

The await expression for normal never resolves.

Wait, Chrome Mask makes it work.

There's a documented behavior difference for wp-rocket:

In Chrome and Safari, inline scripts will have a src="data:text/javascript;base64,... attribute, used to speed up the processing of the script after it was delayed.

And there's this block in the inline script (inserted by wp-rocket)

                if (
                  navigator.userAgent.includes('Firefox/') ||
                  '' === navigator.vendor ||
                  this.CSPIssue
                ) i = document.createElement('script'),
                [
                  ...t.attributes
                ].forEach(
                  (
                    t => {
                      let e = t.nodeName;
                      'type' !== e &&
                      (
                        'data-rocket-type' === e &&
                        (e = 'type'),
                        'data-rocket-src' === e &&
                        (e = 'src'),
                        i.setAttribute(e, t.nodeValue)
                      )
                    }
                  )
                ),
See Also: → 2020756
  async $(t) {
    if (
      (await this.G(),
      !0 !== t.noModule || !("noModule" in HTMLScriptElement.prototype))
    )
      return new Promise((e) => {
        let i;
        function o() {
          ((i || t).setAttribute("data-rocket-status", "executed"), e());
        }
        try {
          if (
            navigator.userAgent.includes("Firefox/") ||
            "" === navigator.vendor ||
            this.CSPIssue
          )
            ((i = document.createElement("script")),
              [...t.attributes].forEach((t) => {
                let e = t.nodeName;
                "type" !== e &&
                  ("data-rocket-type" === e && (e = "type"),
                  "data-rocket-src" === e && (e = "src"),
                  i.setAttribute(e, t.nodeValue));
              }),
              t.text && (i.text = t.text),
              t.nonce && (i.nonce = t.nonce),
              i.hasAttribute("src")
                ? (i.addEventListener("load", o, {
                    isRocket: !0,
                  }),
                  i.addEventListener(
                    "error",
                    () => {
                      (i.setAttribute("data-rocket-status", "failed-network"),
                        e());
                    },
                    {
                      isRocket: !0,
                    },
                  ),
                  setTimeout(() => {
                    i.isConnected || e();
                  }, 1))
                : ((i.text = t.text), o()),
              (i.isWPRocket = !0),
              t.parentNode.replaceChild(i, t));
          else {
            const i = t.getAttribute("data-rocket-type"),
              s = t.getAttribute("data-rocket-src");
            (i
              ? ((t.type = i), t.removeAttribute("data-rocket-type"))
              : t.removeAttribute("type"),
              t.addEventListener("load", o, {
                isRocket: !0,
              }),
              t.addEventListener(
                "error",
                (i) => {
                  this.CSPIssue && i.target.src.startsWith("data:")
                    ? (console.log("WPRocket: CSP fallback activated"),
                      t.removeAttribute("src"),
                      this.$(t).then(e))
                    : (t.setAttribute("data-rocket-status", "failed-network"),
                      e());
                },
                {
                  isRocket: !0,
                },
              ),
              s
                ? ((t.fetchPriority = "high"),
                  t.removeAttribute("data-rocket-src"),
                  (t.src = s))
                : (t.src =
                    "data:text/javascript;base64," +
                    window.btoa(unescape(encodeURIComponent(t.text)))));
          }
        } catch (i) {
          (t.setAttribute("data-rocket-status", "failed-transform"), e());
        }
      });
    t.setAttribute("data-rocket-status", "skipped");
  }

This whole function goes very different route when it detects Firefox.

The core breakage comes from this part:

              [...t.attributes].forEach((t) => {
                let e = t.nodeName;
                "type" !== e &&
                  ("data-rocket-type" === e && (e = "type"),
                  "data-rocket-src" === e && (e = "src"),
                  i.setAttribute(e, t.nodeValue));
              }),

It tries to copy <script type="rocketlazyloadscript" data-rocket-src="https://www.google.com/recaptcha/api.js?render=explicit&amp;ver=3.35.1" id="elementor-recaptcha_v3-api-js"></script> into a new script element with data-rocket- prefix removed. But somehow it doesn't work:

i2 = document.createElement("script")
i2.src = "https://www.google.com/recaptcha/api.js?render=explicit&ver=3.35.1"
i2.src // ... 'https://lacunacoil.com/'. What?

I'm not sure what's happening in the src setter?

https://lacunacoil.b-cdn.net/wp-content/plugins/myagileprivacy/local-cache/my-agile-privacy/cookie-shield-1.4.01.js

      document[_0x3bf812(436) + _0x3bf812(272) + _0x3bf812(503) + 't'] = function (..._0x3d2064) {
        var _0x1d86f9 = _0x3bf812;
        let _0xff708a = _0x3d2064[ - 67 * - 39 + - 271 * 25 + 4162][_0x1d86f9(208) + _0x1d86f9(630) + _0x1d86f9(844)]();
        if (_0xff708a !== _0x1d86f9(1660) + 'pt') return _0x2254af[_0x1d86f9(400)](document) (..._0x3d2064);
        const _0x821b6e = _0x2254af[_0x1d86f9(400)](document) (..._0x3d2064);
        try {
          Object[_0x1d86f9(939) + _0x1d86f9(174) + _0x1d86f9(1749) + _0x1d86f9(1061)](
            _0x821b6e,
            {
              'src': {
                ..._0x415b3a[_0x1d86f9(442)],
                'set'(_0x1c8ff7) {
                  var _0xc878fe = _0x1d86f9;
                  if (typeof _0x1c8ff7 !== _0xc878fe(177) + _0xc878fe(1212) + 'd') {
                    var _0x2962ce = _0x1c8ff7[_0xc878fe(1650) + _0xc878fe(304)]();
                    var _0x25d570 = ![];
                    var {
                      api_key: _0x16d2a9,
                      found_item: _0x16dc28
                    }

Oh... god.

So that cryptic script ends up making script.src setter, through a dark magic, to set type=text/plain. A plain text script element is not going to fire any load/error events, thus breaking wp-rocket's expectation.

The recommended intervention is to ignore all the details and override the user agent string to not include Firefox, to make wp-rocket not enter Firefox specific branch.

And probably contact wp-rocket to stop Firefox specific behavior?

Keywords: leave-open
Assignee: nobody → twisniewski
Status: NEW → ASSIGNED
User Story: (updated)
Webcompat Priority: P2 → P3
Webcompat Score: 5 → 1
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: