WordPress / performance

Performance plugin from the WordPress Performance Group, which is a collection of standalone performance modules.
https://wordpress.org/plugins/performance-lab/
GNU General Public License v2.0
347 stars 94 forks source link

Products added to cart in WooCommerce without clicking on Add to Cart button when Speculative Loading active #1140

Closed westonruter closed 4 months ago

westonruter commented 4 months ago

Bug Description

As reported on the support forum, users browsing a site with WooCommerce and the Speculative Loading plugin can end up getting products added to their shopping cart by just hovering over the Add to Cart button when in moderate eagerness (the default setting). What's worse is when the eager eagerness setting is enabled, all the products on the page can be added to the cart just by visiting the page.

Steps to reproduce

  1. Activate and configure WooCommerce (and install the Storefront theme, perhaps)
  2. Activate the Speculative Loading plugin
  3. Access the shop page and hover over an Add to Cart button

Screenshots

Screen recording 2024-04-15 11.49.08.webm

Additional Context

When not using a block theme, this is the code to create the add-to-cart button: https://github.com/woocommerce/woocommerce/blob/a8e84d8c8b20558f66ed33dcbee6c1dec300752f/plugins/woocommerce/includes/wc-template-functions.php#L1363-L1403 https://github.com/woocommerce/woocommerce/blob/a8e84d8c8b20558f66ed33dcbee6c1dec300752f/plugins/woocommerce/templates/loop/add-to-cart.php#L27-L32

Note an a tag is always used. Contrast with the block code: https://github.com/woocommerce/woocommerce/blob/a8e84d8c8b20558f66ed33dcbee6c1dec300752f/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php#L74-L231

Here a button is used when AJAX is enabled.

In both cases, however, rel=nofollow is present (although it can be filtered out by the woocommerce_loop_add_to_cart_args filter). It's unfortunate that since these add-to-cart links aren't idempotent (i.e. they have an action) that they don't also have _wpnonce query parameters.

masvil commented 4 months ago

I reported this problem on the support forum. The 1.2.1 fixed the problem as it was. Then I added speculation rules using this script:

<script type="speculationrules">
{
  "prerender": [{
    "where": {
      "and": [
        { "href_matches": "/product/*" },
        { "not": {"selector_matches": ".do-not-prerender"}}
      ]
    },
    "eagerness": "eager"
  }]
}
</script>

The intention was to give product pages higher priority in prerendering while still having two available on hover "slot" provided by the "Moderate" setting. I used the template found here.

That's what happened: video.

Some products are still automatically added to cart. It looks like the culprits are the add to cart links /?add-to-cart= in the related products boxes, like I highlighted in the video.

I can't share the URL because it's a staging I'm developing for a client, but I can privately grant admin access.

westonruter commented 4 months ago

@masvil Thanks. Can you share the HTML for the related products boxes? Do the links not have rel=nofollow like the WooCommerce's product add-to-cart template includes by default?

tunetheweb commented 4 months ago

This is basically what 1.2.1 does now:

<script type="speculationrules">
{
    "prerender": [
        {
            "where": {
                "and": [
                    {"href_matches": "/*"},
                    {"not": {
                        "selector_matches": [
                            ".do-not-prerender",
                            "[rel=nofollow]"
                        ]
                    }},
                    {"not": {
                        "href_matches": [
                            "\/wp-login.php",
                            "\/wp-admin\/*",
                            "/*?*(^|&)_wpnonce=*"
                        ]
                    }}
                ]
            },
            "eagerness": "moderate"
        }
    ]
}
</script>

That is it excludes some WordPress specific stuff:

If you want to add your own rules on top, then you need to be aware of these types of specifics. Rather than copying from a generic article, it would be better to have looked at the rendered DOM in the Elements panel in DevTools to see what the plugin added and modified that.

Saying that, earlier today I added the [rel=nofollow] to the example in the article you referenced (refresh to see it) as that seemed a sensible one to add. However the intent is not to list all WordPress or WooCommerce-specific things (or any other platform).

BTW, IMHO WooCommerce should not be performing changing requests in links (GET—HTTP requests as links are—are supposed to be "safe" by HTTP standards) but such is the varied web we live in. It's incredibly forgiving when people do things that aren't really correct, until it's not.

tunetheweb commented 4 months ago

Also note that Chrome is limited to 10 prerenders per page. So really wouldn't recommend eagerly prerendering many links on the page. You can see in that video that only the first 10 succeeded anyway.

westonruter commented 4 months ago

I've opened https://github.com/WordPress/performance/issues/1156 to extend the plugin with the ability to add additional rules to the speculationrules. So with that, you would be able to do the following to add the rule in https://github.com/WordPress/performance/issues/1140#issuecomment-2060034357 to the existing ruleset:

add_filter(
    'plsr_speculation_ruleset',
    static function ( array $ruleset ): array {
        $ruleset['prerender'][] = array(
            "where"     => array(
                "and" => array(
                    array( "href_matches" => "/product/*" ),
                    array( "not" => array( "selector_matches" => array( 
                        ".do-not-prerender", 
                        "[rel=nofollow]", //  👈 added
                    ) ) )
                )
            ),
            "eagerness" => "eager"
        );

        return $ruleset;
    }
);

~It's important to remember that only one speculationrules script can be on the page at a time.~ Actually, this is not true. See Multiple speculation rules. h/t @tunetheweb

westonruter commented 4 months ago

@masvil Oh, and to @tunetheweb's point, it seems your rule was lacking the rel=nofollow exclusion (presuming the template includes rel=nofollow on the add-to-cart buttons). So that should be included as well.

masvil commented 4 months ago

Do the links not have rel=nofollow like the WooCommerce's product add-to-cart template includes by default?

Those links have rel=nofollow.

and to @tunetheweb's point, it seems your rule was lacking the rel=nofollow exclusion (presuming the template includes rel=nofollow on the add-to-cart buttons). So that should be included as well.

Done. Indeed, the following script works good:

<script type="speculationrules">
{
  "prerender": [{
    "where": {
      "and": [
        { "href_matches": "/product/*" },
        { "not": {"selector_matches": ".do-not-prerender"}},
        { "not": {"selector_matches": "[rel=nofollow]"}}
      ]
    },
    "eagerness": "eager"
  }]
}
</script>

it would be better to have looked at the rendered DOM in the Elements panel in DevTools to see what the plugin added and modified that.

Lesson learned, thank you.

Saying that, earlier today I added the [rel=nofollow] to the example in the article you referenced (refresh to see it) as that seemed a sensible one to add.

Thank you!