Page MenuHomePhabricator

Sticky header: sticky header "gap" appearing on diffs of pages with other sticky elements
Closed, ResolvedPublic3 Estimated Story Points

Description

NOTE: Blocked on T309055

Steps to reproduce

  1. Go to https://de.wikipedia.org/w/index.php?title=Elvis_Presley/Diskografie&diff=219599787&oldid=219263831
  2. Scroll down the page

Expected:

  • No empty space appears between the sticky element and the top of the page

Observed:
There is a gap where the sticky header would go:

Screen Shot 2022-02-01 at 10.04.19 AM.png (1×1 px, 481 KB)

Developer Notes

We currently have logic in stickyHeader.js that is responsible for determining whether the sticky header should show on a given page:

/**
 * Determines if sticky header should be visible for a given namespace.
 *
 * @param {number} namespaceNumber
 * @return {boolean}
 */
function isAllowedNamespace( namespaceNumber ) {
	// Corresponds to Main, User, Wikipedia, Template, Help, Category, Portal, Module.
	const allowedNamespaceNumbers = [ 0, 2, 4, 10, 12, 14, 100, 828 ];
	return allowedNamespaceNumbers.indexOf( namespaceNumber ) > -1;
}

/**
 * Determines if sticky header should be visible for a given action.
 *
 * @param {string} action
 * @return {boolean}
 */
function isAllowedAction( action ) {
	const disallowedActions = [ 'history', 'edit' ],
		hasDiffId = mw.config.get( 'wgDiffOldId' );
	return disallowedActions.indexOf( action ) < 0 && !hasDiffId;
}

However, we also have render blocking styles that control scroll padding and the offset of other sticky elements:

@media ( min-width: @width-breakpoint-tablet ) {
	.client-js.vector-sticky-header-enabled {
		// T290518: When the sticky header is enabled (feature flag is on, js is
		// enabled, and viewport is at higher resolutions), add scroll padding to the
		// root element. This is needed so that the sticky header does not overlap the
		// top of an element when the URI has a hash fragment (e.g. when the user clicks
		// a jump link) and when the user tabs through elements in reverse order.
		//
		// Please note that this class must be independent of the
		// .vector-sticky-header-visible class to correctly handle situations where the
		// sticky header isn't visible yet but we still need scroll padding applied
		// (e.g. when the user navigates to a page with a hash fragment in the URI).
		scroll-padding-top: @height-sticky-header;

		// T289817 Override other sticky element offsets to ensure that other
		// sticky elements (i.e. table headers) appear below the sticky header.
		.mw-sticky-header-element,
		.charts-stickyhead th {
			/* stylelint-disable-next-line declaration-no-important */
			top: @height-sticky-header !important;
		}
	}
}

Currently, these styles apply to every page, but the JS prevents the sticky header from showing on certain pages which results in the gap. To prevent a shift in the position of sticky elements as the page loads and the JS kicks in, we should consider moving the JS logic to the backend where it controls whether the sticky element's HTML is rendered and also whether the vector-sticky-header-enabled class should be added to the root element (this is necessary for scroll padding to work in all scenarios).

Leverage VectorMaxWidthOptions when moving logic to backend.

Event Timeline

ovasileva triaged this task as Medium priority.Feb 1 2022, 9:05 AM
ovasileva created this task.
nray updated the task description. (Show Details)
cjming removed cjming as the assignee of this task.Feb 9 2022, 9:47 PM
cjming subscribed.

I'd suggest refactoring the logic in VectorMaxWidthOptions to make it more reusable so it can be applied here.

Another situation in which I have noticed the behaviour: all pages on screen widths between 720 and 1000px. The sticky header only appears at 1000px while the additional top width is already triggered at 720px.

Change 782677 had a related patch set uploaded (by Clare Ming; author: Clare Ming):

[mediawiki/skins/Vector@master] Move sticky header namespace logic in js to server

https://gerrit.wikimedia.org/r/782677

Another situation in which I have noticed the behaviour: all pages on screen widths between 720 and 1000px. The sticky header only appears at 1000px while the additional top width is already triggered at 720px.

Hopefully this will be resolved by latest patch which moves the media query to desktop instead of tablet for setting sticky header offsets.

Posting some notes from @phuedx here to help decide next steps.

First some context:
The original ticket was scoped as moving the sticky header js logic into the backend. There is an existing method called shouldDisableMaxWidth() in Vector hooks that uses config to determine whether to disable max width on specified pages. It seems logical to leverage this functionality to extend to the sticky header feature as well and use config to determine whether the sticky header should be shown on a given page. The current state of the associated patch posits this functionality into a new method in the FeatureManager class which determines requirements for a feature. This new method (aka FeatureManager::isAvailableOnPage()) would replace the Vector hooks method and accept config variables for either disabling max width or showing sticky header.

  1. In response to @Jdlrobson's Q

Do you think there's a more logical place to put this method, or some way to tie it into the feature registration?

Looking at this method in terms of Vector's FeatureManager, it has two responsibilities:

1) Providing a shorthand to construct several implicit types of Requirement; and
2) Evaluating those requirements

To expand on the first responsibility, I think the following Requirement implementations are implicit in the method body:

NotOnMainPageRequirement
OnPageRequirement/NotOnPageRequirement
InNamespaceRequirement/NotInNamespaceRequirement
NotInQueryStringRequirement

The second responsibility is the reason I would recommend against integrating this method with FeatureManager. This method is inconsistent with the way that FeatureManager evaluates requirements. This inconsistency is passed onto the consumers of the method (yourselves, currently).

All of the above said, I see the need for a shorthand to construct several types of Requirement at once, which we've spoken about elsewhere IIRC. I would recommend that you first create the requirements listed above and then create a builder class that provides the shorthand for creating those requirements, e.g.

 $builder = new Builder();
 $inclusionRequirements = $builder->include( [
   'titles' => [ 'Bertrand_Russell' ],
 ] );
 $exclusionRequirements = $builder->exclude( [
   'namespaces' => [ NS_SCHEMA, NS_SCHEMA_TALK ],
 ] );
(You'll likely find a cleaner API :) )

This means refactoring the patch again to pursue the approach outlined above.

  1. During the course of trying to refactor tests for the new FeatureManager::isAvailableOnPage(), I ran into difficulty trying to test the method in isolation because how it is currently written (lifted directly from the former Vector hooks method - its associated test itself an integration test along with tests for other hook methods) relies heavily on the Title class and the cascade of its methods invoked in isAvailableOnPage(). The less-than-optimal result was splitting from the FeatureManager unit test because mocking the Title object kept returning errors. I ended up spinning off a new FeatureManager integration test instead which passes CI. Patchset 13 is where I give up on the unit test and switch to the integration test in subsequent patch sets.

Here are Sam's recommendations around refactoring the method (which in the suggested approach above would still need to be done - i.e. refactor the original Vector hooks method):


$canonicalTitle = $title->getRootTitle();
if ( $title->isMainPage() ) {
    // only one check to make
    return $exclusions['mainpage'] ?? false;
} elseif ( $canonicalTitle->isSpecialPage() ) {
    $canonicalTitle->fixSpecialName();
}

Here you need to mock Title::getRootTitle().

I would recommend against calling Title::isMainPage(). Instead, I would get a Title object representing the main page by calling TitleFactory::newMainPage() (injecting an instance of TitleFactory at construction time so that it can be mocked) and passing it to Title::equals().

I would recommend against calling Title::isSpecialPage() and instead recommend calling Title::inNamespace() as it's a method defined on the TitleValue class.

Title::fixSpecialName() gets the root title and then uses SpecialPageFactory::getLocalNameFor() to get the default (American English) name for the special page. Here I would recommend against calling Title::fixSpecialName() and using SpecialPageFactory::getLocalNameFor(), injecting an instance of SpecialPageFactory at construction time so that it can be mocked.

foreach ( $includeTitles as $titleText ) {
    $includeTitle = Title::newFromText( $titleText );

    if ( $canonicalTitle->equals( $includeTitle ) ) {
        return false;
    }
}

Here I would recommend against calling Title::newFromText() and instead using TitleFactory::newFromText().

All recomendations above, bar the first, will leave you with a method that works with TitleValue instances, which are value objects that that can be constructed cheaply/without side effects and are immutable, i.e. you will be able to test the method in isolation far more easily.


TL;DR the suggestions above indicate a refactor of both the FeatureManager class and the Vector hooks method (which would be moved into one of the new requirements that would need to be generated by a new builder class). This can/should be undertaken but does significantly increase the scope of the original ticket.

I'm not able to reproduce this anymore. @Jdlrobson, @cjming - did we fix this?

@ovasileva i can't replicate either - and I can't explain what exactly fixed it. A lot of the recent CSS changes and optimizations related to grid and TOC might have something to do with it ¯\_(ツ)_/¯

@ovasileva just saw a patch that enables sticky header on talk pages come through so it must've gotten disabled recently which would explain why this issue was seemingly fixed. I expect the issue to persist after that aforementioned patch is merged.

The sticky header no longer shows up for me on diff pages. I think this is due to a bug introduced by T290347 (here https://github.com/wikimedia/Vector/commit/bdad84a7c958181c789039b7308d86a09584bcb8)

The sticky header no longer shows up for me on diff pages. I think this is due to a bug introduced by T290347 (here https://github.com/wikimedia/Vector/commit/bdad84a7c958181c789039b7308d86a09584bcb8)

I think the expectation is that it wouldn't appear on diff pages. What was happening here is that the space where it would have taken was still showing up, despite the sticky header not being available.

Ignore me then - sounds like this is definitely fixed then! :)

Change 782677 abandoned by Clare Ming:

[mediawiki/skins/Vector@master] Move sticky header namespace logic in js to server

Reason:

https://gerrit.wikimedia.org/r/782677

@Jdlrobson @ovasileva It looks like this is still an issue when logged-and visiting https://de.wikipedia.org/w/index.php?title=Elvis_Presley/Diskografie&diff=219599787&oldid=219263831:

2023-02-10_12-03-12.png (1×3 px, 849 KB)

Currently, these styles apply to every page, but the JS prevents the sticky header from showing on certain pages which results in the gap. To prevent a shift in the position of sticky elements as the page loads and the JS kicks in, we should consider moving the JS logic to the backend where it controls whether the sticky element's HTML is rendered and also whether the vector-sticky-header-enabled class should be added to the root element (this is necessary for scroll padding to work in all scenarios).

This is still important to do and currently affects the sticky TOC on diff pages when logged-in and will also affect T318169 by creating too large of a gap on history, diff pages, etc because the logic that determines whether the sticky header will show on a page is downstream from the CSS that sets the sticky offset for TOC and page tools.

2023-02-10_12-09-44 (1).png (1×3 px, 1 MB)

Thank you — it looks like that ticket talks about complex tables, but I think this issue is more prevalent than that. It currently affects the TOC on diff pages (and potentially others) and will also affect the page tools on diff pages, history pages, etc

After talking with @Jdlrobson, I created a bug to track the issue I'm talking about. It is related to the unresolved issue in this ticket, though: T329397