Discussions » Development

Single-Page Application warning, how can I launch my userscript ?

§
Posted: 07-06-2024
Edited: 07-06-2024

Hi, how can I launch my userscript? "window.onload = function()" not working in this situation.

Violentmonkey is showing me a warning that this webpage is a Single-Page Application: "The script didn't exist or didn't match the URL when the page loaded, which happens in Single-Page Application sites like fb or instagram that are using fake navigation. You can reload the tab to run the script To fix the script use @match for the entire site and then detect changes using MutationObserver or window.navigation API"

My userscript calculates the total cost (by including this platform cost), so the orange color number can only be higher than the original one in grey color.

Here is a webpage example: https://www.leboncoin.fr/ad/collection/2656725226 If I click on another article (for example, the projector pointed with the yellow arrow on my screenshot https://i.imgur.com/6BjA5be.png), the total price is not updated, only the original price. So sometimes, the total price is even lower than the original price, which is incorrect.

// ==UserScript==
// @name        leboncoin.fr - Calcul
// @match       *://www.leboncoin.fr/*/*.htm
// @match       *://www.leboncoin.fr/offre/*
// @match       *://www.leboncoin.fr/ad/*/*
// @grant       none
// @version     1.1
// @author      faso
// ==/UserScript==


'use strict';

window.onload = function() {
  createMeaningfulBookmarkName();
}


function createMeaningfulBookmarkName() {
  const priceElement = document.querySelectorAll('[data-qa-id="adview_price"]');
  const priceText = priceElement[1].textContent;
  const priceRegex = new RegExp(/\b(\d+,\d+|\d+)\b/);
  const priceMatch = priceText.match(priceRegex);
  const price = parseFloat(priceMatch[0].replace(',', '.'));
  const priceOnlyElement = priceElement[1].querySelector('p.text-headline-2');
    priceOnlyElement.style.color = 'gray';
    priceOnlyElement.style.fontSize = '14px';
    priceOnlyElement.style.fontWeight = 'normal';
  let charges = price * 0.04;
  if (charges < 0.99) charges = 0.99;
  const total = price + charges;
  let priceForMeStr;
  priceForMeStr = total.toLocaleString('fr-FR', {minimumFractionDigits: 0, maximumFractionDigits: 2}) + ' €';
  const totalElement = document.createElement('span');
  totalElement.innerHTML = '&nbsp;&nbsp;&nbsp;' + priceForMeStr;
    totalElement.style.color = 'orange';
    totalElement.style.fontSize = '18px';
    totalElement.style.fontWeight = 'bold';
  priceElement[1].appendChild(totalElement);
}
woxxomMod
§
Posted: 09-06-2024

As the warning says match the entire site and then monitor the URL changes or DOM changes:

// @match *://www.leboncoin.fr/*
// ==/UserScript==

if (createMeaningfulBookmarkName() === false) {
  new MutationObserver((mutations, observer) => {
    if (createMeaningfulBookmarkName() !== false) observer.disconnect();
  }).observe(document.body, {subtree: true, childList: true});
}

function createMeaningfulBookmarkName() {
  const priceElement = document.querySelectorAll('[data-qa-id="adview_price"]');
  if (!priceElement.length) return false;
.........
..........
§
Posted: 10-06-2024

I have tested this suggestion (https://i.imgur.com/jy1gyiZ.png) but it is still not working:
- when the page has first loaded the page, most of the time, the userscript does not start
- when I refresh the screen, the calculation is displayed and immediately hidden after

woxxomMod
§
Posted: 10-06-2024

No, you forgot to add if (!priceElement.length) return false;

§
Posted: 10-06-2024

Thanks for your feedback, I have corrected this line (https://i.imgur.com/dtNSE6n.png) but the behavior is still the same:
- when the page has first loaded the page, most of the time, the userscript does not start
- when I refresh the screen, the calculation is displayed and immediately hidden after

woxxomMod
§
Posted: 10-06-2024
Edited: 10-06-2024

The script should always run, otherwise it's a bug in the browser. What happens probably is that the site overwrites your changes. Use devtools to debug the script by adding a breakpoint at const priceElement and see what happens when it runs.

woxxomMod
§
Posted: 10-06-2024

Also try replacing that line with if (!priceElement[1]) return false;

§
Posted: 10-06-2024
Edited: 10-06-2024

The script should always run, otherwise it's a bug in the browser. What happens probably is that the site overwrites your changes. Use devtools to debug the script by adding a breakpoint at const priceElement and see what happens when it runs.

When I add a breakpoint at that place I can see that everything is ok (https://i.imgur.com/VJaCoWE.png). The calculation result is even displayed as you can see, but when I press F5 at that point, I am coming again to the breakpoint (like if the webpage was relaoded) and this seems indefinite as I had already pressed F5 several times and it is always the same. When I remove the breakpoint, the webpage is first displaying the calculation then it reloads and does not display any calculation.

§
Posted: 10-06-2024
if (!priceElement[1]) return false;

same result with this

woxxomMod
§
Posted: 10-06-2024

It means the site overwrites your changes. You can remove observer.disconnect(); because apparently you want to process all prices while browsing multiple pages anyway.

§
Posted: 10-06-2024

It is like the site overwrites my changes. When I use the breakpoint and press F5, I am each time coming back to my breakpoint. If I do F10 I can see the proper calculation is being made and even added to the webpage.
When I disable the breakpoint, the page loads (surprisingly there is no look like it is the case when using F5. I don't understand why) but without the calculation display.

woxxomMod
§
Posted: 10-06-2024

It means the site overwrites your changes. You can remove observer.disconnect(); because apparently you want to process all prices while browsing multiple pages anyway.

§
Posted: 10-06-2024

It means the site overwrites your changes. You can remove observer.disconnect(); because apparently you want to process all prices while browsing multiple pages anyway.

I have remove that `observer.disconnect();` but I still have the same annoying overwrite behavior.

woxxomMod
§
Posted: 10-06-2024
Edited: 10-06-2024

Well, now your code runs every time there's a change, including those your code makes, so you need to check for that too. Also, start the script earlier, e.g. on document-body.

// ==UserScript==
// @name        leboncoin.fr - Calcul
// @match       *://www.leboncoin.fr/*
// @grant       none
// @version     1.1
// @author      faso
// @run-at      document-body
// ==/UserScript==


'use strict';

const observer = new MutationObserver(createMeaningfulBookmarkName);
const observe = () => observer.observe(document.body, {subtree: true, childList: true});
createMeaningfulBookmarkName();
observe();

function createMeaningfulBookmarkName() {
  const priceElement = document.querySelectorAll('[data-qa-id="adview_price"]');
  if (!priceElement[1] || priceElement[1].querySelector('span[style*="orange"]')) return;
  const priceText = priceElement[1].textContent;
  const priceRegex = new RegExp(/\b(\d+,\d+|\d+)\b/);
  const priceMatch = priceText.match(priceRegex);
  if (!priceMatch) return;
  const price = parseFloat(priceMatch[0].replace(',', '.'));
  const priceOnlyElement = priceElement[1].querySelector('p.text-headline-2');
    priceOnlyElement.style.color = 'gray';
    priceOnlyElement.style.fontSize = '14px';
    priceOnlyElement.style.fontWeight = 'normal';
  let charges = price * 0.04;
  if (charges < 0.99) charges = 0.99;
  const total = price + charges;
  let priceForMeStr;
  priceForMeStr = total.toLocaleString('fr-FR', {minimumFractionDigits: 0, maximumFractionDigits: 2}) + ' €';
  const totalElement = document.createElement('span');
  totalElement.innerHTML = '&nbsp;&nbsp;&nbsp;' + priceForMeStr;
    totalElement.style.color = 'orange';
    totalElement.style.fontSize = '18px';
    totalElement.style.fontWeight = 'bold';
  observer.disconnect();
  priceElement[1].appendChild(totalElement);
  observe();
}
§
Posted: 10-06-2024
Edited: 10-06-2024

Thank you. Great proposition and example, yes now it is running every time I open a webpage :)


When I click on an ad at the webpage (https://i.imgur.com/AexCqml.png), the calculation is not being updated (https://i.imgur.com/b0CA31b.png). So the calculation result is still the one of the previous watched ad. Is there a way to update it?

woxxomMod
§
Posted: 10-06-2024

If you set a DOM breakpoint on the price element you'll see the site updates nodeValue. To observe these changes you can add characterData: true to the config parameter in MutationObserver.

A more efficient approach though would be to observe the price element, not the entire document:

let priceElement;
let totalElement;

if (!waitForPrice())
  new MutationObserver(waitForPrice)
    .observe(document.body, {subtree: true, childList: true});

function waitForPrice(mutations, observer) {
  priceElement = document.querySelector('article [data-qa-id="adview_price"] p');
  if (!priceElement) return;
  observer?.disconnect();
  patchPrice();
  new MutationObserver(patchPrice).observe(priceElement, {characterData: true});
  return true;
}

function patchPrice() {
  const priceText = priceElement.textContent;
  const priceRegex = new RegExp(/\b(\d+,\d+|\d+)\b/);
  const priceMatch = priceText.match(priceRegex);
  const price = parseFloat(priceMatch[0].replace(',', '.'));
  if (priceElement.style.color !== 'gray') {
    priceElement.style.color = 'gray';
    priceElement.style.fontSize = '14px';
    priceElement.style.fontWeight = 'normal';
  }
  let charges = price * 0.04;
  if (charges < 0.99) charges = 0.99;
  const total = price + charges;
  const priceForMeStr = total.toLocaleString('fr-FR', {minimumFractionDigits: 0, maximumFractionDigits: 2}) + ' €';
  if (!totalElement) {
    totalElement = document.createElement('span');
    totalElement.style.color = 'orange';
    totalElement.style.fontSize = '18px';
    totalElement.style.fontWeight = 'bold';
    priceElement.parentElement.appendChild(totalElement);
  }
  totalElement.textContent = '\xA0\xA0\xA0' + priceForMeStr;
}

I didn't test this code, because this paranoid site blocked my IP after a couple of page reloads.

§
Posted: 18-06-2024

Sorry I was full with my kids to take care. I have tested the last userscript code you have given: it is not working, the orange calculation result is displayed when loading and then immediately removed by the webpage.

With the userscript you provided before it is working but when I click on another ad inside the webpage (https://i.imgur.com/afTBEaC.png) the orange calculation is not being refreshed unfortunately, so the calculation result is incorrect. Is there a way to have it recalculate?

>I didn't test this code, because this paranoid site blocked my IP after a couple of page reloads.
I have tested, you're right, this website is checking the IP is coming from France :(

Post reply

Sign in to post a reply.