Open Bug 1788530 Opened 2 years ago Updated 1 month ago

Fenix hangs and memory usage jumps on theguardian.com when opening headlines in new tab

Categories

(Fenix :: Performance, defect, P2)

Tracking

(Performance Impact:high)

Performance Impact high

People

(Reporter: mcomella, Unassigned)

References

(Depends on 1 open bug, Blocks 1 open bug)

Details

(Keywords: perf:resource-use, perf:responsiveness, reproducible, Whiteboard: [fxdroid] [foundation]perf-android)

Attachments

(2 files)

Moved from https://github.com/mozilla-mobile/fenix/issues/26554:

Steps to reproduce

  1. Go to https theguardian.com/uk, an English language newspaper website with multiple clickable headlines. I have not been able to reproduce the problem on any other website.
  2. Long press any headline or article to open it in a new tab.
  3. Repeat by opening three more headlines or articles, each in a new tab.
  4. Firefox will stop responding for (at best) a minute or two.

Expected behavior

  1. The page should allow me to continue selecting items for opening in new tabs, so that I have a set of pages ready to read.
  2. When I review these other pages in their respective tabs, rendering should be near instantaneous exactly as usual.

Actual behavior

  • Firefox stops accepting long press - or indeed any press. On a good day I can scroll up and down the part of the page that has already been rendered, but unrendered visuals "below the visible page" will not be rendered and I end up scrolling a blank page.
  • New tabs and those I might have had open before this test will not render - I get a blank page for each.
  • After a minute or so, Firefox might start to respond again, sluggishly recognising some earlier presses, but rendering is gone.

The workaround is to press Home on the Android device and swipe out Firefox. Wait a minute. Restart Firefox. At this point rendering will work fine again until I open more than two or three tabs from this particular website.

Looking in the phone's Settings / System / Developer options / Memory, I see that during this event Firefox memory usage has suddenly escalated from around 500MB way up to multiple GB - often near the limit of the phone. This can cause even the phone's app launcher to struggle to run.

Device information

  • Android device: OnePlus 5T (Android 10) with 8GB RAM

  • Fenix version: ?

  • Firefox 103.2.0 (Build #2015895963)

┆Issue is synchronized with this Jira Task

Our QA team was able to reproduce on a OnePlus 6T on Nightly and was unable to reproduce on Chrome. They and provided profiles:

During fenix performance triage, we found specific issues in the profiles and decided to move it to Bugzilla:

Triage: we suspect the root cause is a platform issue because, in the first profile, there are large jank markers in the child process w/ near 100% CPU use over 30 seconds and we see a lengthy GC calls when the original reporter mentioned unexpectedly high memory usage. By contrast, the JVM profile has nothing out of the ordinary.

And gave it a priority:

The Performance Priority Calculator has determined this bug's performance priority to be P1. If you'd like to request re-triage, you can reset the Performance flag to "?" or needinfo the triage sheriff.

Platforms: Android
Impact on browser UI: Renders browser effectively unusable
Impact on site: Renders site effectively unusable
[x] Causes severe resource usage
[x] Able to reproduce locally


However, we lacked the expertise to put it in an appropriate component: please find the proper component to put this in.

The second profile has a part where a script appears to get stuck in a loop: https://share.firefox.dev/3RVGWBw

Performance Impact: high → ---
Component: Performance → Mobile
Product: Core → Web Compatibility

Oh, in the first profile we can see that it's not actually stuck, it does end up finishing eventually, after about 30 seconds: https://share.firefox.dev/3dbS0eP

Performance Impact: --- → high

This GitHub issue says the loading performance is "fixed" by using uBlock Origin to block some scripts.

https://github.com/mozilla-mobile/fenix/issues/27300#issuecomment-1359110027

Component: Mobile → Performance
Product: Web Compatibility → Fenix
Summary: Fenix hangs and memory usage jumps on theguardian when opening headlines in new tab → Fenix hangs and memory usage jumps on theguardian.com when opening headlines in new tab

QA, can you still reproduce this bug?

Severity: -- → S3
Flags: qe-verify+

The severity field for this bug is set to S3. However, the Performance Impact field flags this bug as having a high impact on the performance.
:cpeterson, could you consider increasing the severity of this performance-impacting bug? Alternatively, if you think the performance impact is lower than previously assessed, could you request a re-triage from the performance team by setting the Performance Impact flag to ??

For more information, please visit auto_nag documentation.

Flags: needinfo?(cpeterson)
Severity: S3 → S2
Flags: needinfo?(cpeterson)

Yes, this is reproducible with the latest Nightly 111.0a1 (2023-01-31) build.
The described actual result is pretty accurate. The websites opened in a new tab continued to remain blank even after refresh.
Device used: Oppo Find X5 (Android 12). - 8Gb RAM.

Flags: qe-verify+
See Also: → 1832103
Attached file index.html
Attached file page2.html

TLDR: The most simple mitigation: ** If you do "Reject All“ in "Manage or reject cookies" , the site becomes normal. **


A bit deeper:

The stucked function could be tracked to the code in https://assets.guim.co.uk/javascripts/commercial/3dabdb74146e378e768c/graun.standalone.commercial.js?http3=true. The function will be executed if you accept the consent to to track scroll depth.

        var Qc = function() {
            var n, r = (n = function*() {
                var n, r = yield(0, e.B4)();
                "tcfv2" == r.framework && null !== (n = r.tcfv2) && void 0 !== n && n.consents[8] || r.canTarget ? ((() => {
                    var e = document.body.offsetHeight,
                        n = window.innerHeight,
                        r = Math.floor(e / n),
                        i = p.get();
                    i.setProperty("pageHeightVH", r);
                    for (var o = new IntersectionObserver((e => {
                            e.forEach((e => {
                                if (e.isIntersecting) {
                                    var n = String(e.target.getAttribute("data-depth"));
                                    (0, t.c)("commercial", "current scroll depth ".concat(n)), i.mark("scroll-depth-vh-".concat(n)), o.unobserve(e.target)
                                }
                            }))
                        })), a = 1; a <= r; a++) {
                        var s = document.createElement("div");
                        s.dataset.depth = String(a), s.style.top = String(100 * a) + "%", s.style.position = "absolute", s.className = "scroll-depth-marker", document.body.appendChild(s), o.observe(s)
                    }
                })(), (0, t.c)("commercial", "tracking scroll depth")) : (0, t.c)("commercial", "No consent to track scroll depth")
            }, function() {
                var e = this,
                    t = arguments;
                return new Promise((function(r, i) {
                    var o = n.apply(e, t);

                    function a(e) {
                        Xc(o, r, i, a, s, "next", e)
                    }

                    function s(e) {
                        Xc(o, r, i, a, s, "throw", e)
                    }
                    a(void 0)
                }))
            });
            return function() {
                return r.apply(this, arguments)
            }
        }();

Since theguardian provides source map. Above function resolves to

webpack://guardian/commercial/src/core/track-scroll-depth.ts

import { log } from '@guardian/libs';
import { EventTimer } from './event-timer';
/**
 * Collect commercial metrics on scroll depth
 * Insert hidden elements at intervals of 1 viewport height
 * then use an intersection observer to mark the time when the viewport intersects with these elements.
 * Approach inspired by https://gist.github.com/bgreater/2412517f5a3f9c6fc4cafeb1ca71384f
 */
const initTrackScrollDepth = () => {
    const pageHeight = document.body.offsetHeight;
    const intViewportHeight = window.innerHeight;
    // how many viewports tall is the page?
    const pageHeightVH = Math.floor(pageHeight / intViewportHeight);
    const eventTimer = EventTimer.get();
    eventTimer.setProperty('pageHeightVH', pageHeightVH);
    const observer = new IntersectionObserver(
    /* istanbul ignore next */
    (entries) => {
        entries.forEach((entry) => {
            if (entry.isIntersecting) {
                const currentDepthVH = String(entry.target.getAttribute('data-depth'));
                log('commercial', `current scroll depth ${currentDepthVH}`);
                eventTimer.mark(`scroll-depth-vh-${currentDepthVH}`);
                observer.unobserve(entry.target);
            }
        });
    });
    for (let depth = 1; depth <= pageHeightVH; depth++) {
        const div = document.createElement('div');
        div.dataset.depth = String(depth);
        div.style.top = String(100 * depth) + '%';
        div.style.position = 'absolute';
        div.className = 'scroll-depth-marker';
        document.body.appendChild(div);
        observer.observe(div);
    }
};
export { initTrackScrollDepth };

and

webpack://guardian/commercial/src/lib/track-scroll-depth.ts
import { onConsent } from '@guardian/consent-management-platform';
import { log } from '@guardian/libs';
import { initTrackScrollDepth } from 'core/track-scroll-depth';
/**
 * Initialise scroll depth / velocity tracking if user has consented to relevant purposes.
 * @returns Promise
 */
export const init = async () => {
    const state = await onConsent();
    if (
    // Purpose 8 - Measure content performance
    (state.framework == 'tcfv2' && state.tcfv2?.consents[8]) ||
        state.canTarget) {
        initTrackScrollDepth();
        log('commercial', 'tracking scroll depth');
    }
    else {
        log('commercial', 'No consent to track scroll depth');
    }
};


So if there's an infinite loop , pageHeightVH (aka `document.body.offsetHeight / window.innerHeight) is the most suspicious value.

I made a simple test html. See attachment index.html , page2.html .

The result is ,

  1. if you click the link directly , windows.innerHeight have value , 770 on my device.
  2. if you long click the link and then choose "open link in new tab" and then switch to the new opened tab , Oooops windows.innerHeight is 0 ,which will definitely cause infinite loop

I'm not familiar with GeckoView 's internal mechanism. So i could only dig to here.


Besides , document.body.offsetHeight is different too , in 1) is 1245 and in 2) is 4112 . (Based on my device)

Thank you jackyzy823 for the excellent sleuthing!

Botond, is this something you could advise on? I don't think this is something that falls in the GeckoView side.

Flags: needinfo?(botond)
Depends on: 1853078

Thanks Jon for the heads up, and thank you Jacky for the reduced testcase!

It looks like the issue described in comment 9 is a compat issue where Firefox and Chrome have different behaviour in their handling of innerHeight in a background tab. I filed bug 1853078 for this, with a hosted version of the reduced testcase so the issue can be tested live.

I marked this bug as depending on bug 1853078, so that once bug 1853078 is fixed, we can confirm that the fix resolves the original issue on the Guardian website.

Flags: needinfo?(botond)
Assignee: nobody → kkaya
Priority: -- → P2
Whiteboard: [fxdroid] [foundation]
Assignee: kkaya → nobody
See Also: → 1813807
See Also: → 1877093
Blocks: perf-android
Whiteboard: [fxdroid] [foundation] → [fxdroid] [foundation]perf-android
See Also: → 1891190
Duplicate of this bug: 1891190
Duplicate of this bug: 1832103
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: