The injected code does not function properly unless the cache is cleared

Currently in the process of updating a Chrome extension to manifest v3 (curses upon you, Google monopoly!)

This extension is specifically designed for a particular website and includes numerous exciting features. To achieve this, I need to inject scripts that can access the page context. The website utilizes PageSpeed Insights (likely an older version as eval usage is questionable nowadays), which retrieves stringified code and evaluates it as shown below:

<script src="https://cdn.tanktrouble.com/RELEASE-2021-07-06-01/js/red_infiltration.js+messages.js.pagespeed.jc.vNQfCE2Wzx.js"></script>
<!-- ^ Contains the variables `mod_pagespeed_nEcHBi$9_H = 'function foo(){...}'` and `mod_pagespeed_gxZ81E5yX8 = 'function bar(){...}'` with subsequent evaluation -->
<script>eval(mod_pagespeed_nEcHBi$9_H);</script>
<script>eval(mod_pagespeed_gxZ81E5yX8);</script>

I devised a technique where I override the native eval function by gathering the evaluated string, creating a hash, and comparing it with stored hashes. If there's a match, I prevent the script from executing and insert my own instead. This method works well in theory and serves as a safety net in case the site updates its code.

However, the issue arises when approximately 50% of the time evaluation occurs before I override eval, often due to caching problems. A hard reset using Ctrl+Shift+R resolves the issue most of the time, allowing my scripts to take over as intended. But during normal reloads or site loads, the workaround fails to function.

manifest.json

    ...
    "content_scripts": [{
            "run_at": "document_start",
            "js": ["js/content.js"],
            "matches": [ "*://*.tanktrouble.com/*" ]
        }
    ], ...

content.js (content script)

class GameLoader {
    constructor() {
        // Preload scripts, just because
        this.hasherScript = Object.assign(document.createElement('script'), {
            'src': chrome.runtime.getURL('js/hasher.js'),
            'type': 'module' // Allows us to import utilities and data later
        });
        // Using custom elements as a workaround to incorporate my chrome runtime URL into the site via element datasets.
        this.extensionURL = document.createElement('tanktroubleaddons-url');
        this.extensionURL.dataset.url = chrome.runtime.getURL('');
    }

    observe() {
        return new Promise(resolve => {
            const observer = new MutationObserver((mutations, observer) => {
                for (const mutation of mutations) {
                    for (const node of mutation.addedNodes) {
                        if (node.tagName === 'HEAD') {
                            node.insertBefore(this.extensionURL, node.firstChild); // extensionURL used in hasherScript.js.
                            node.insertBefore(this.hasherScript, node.firstChild); // Injecting the hash module

                            resolve(), observer.disconnect();
                        }
                    }
                }
            })
            .observe(document.documentElement, { childList: true, subtree: true });
        });
    }
}

const loader = new GameLoader();
loader.observe();

hasher.js (this.hasherScript)

import ScriptHashes from '/config/ScriptHashes.js'; // Contains the earlier mentioned script hashes in the format { '7pqp95akl2s': 'game/gamemanager.js' ... }
import Hasher from '/js/utils/HashAlgorithm.js'; // Simple hash algorithm

/**
Implementation of the clever work-around for the extension URL.
 */
const nodeData = document.querySelector('tanktroubleaddons-url'),
extensionURL = nodeData.dataset.url;

window.t_url = function(url) {
    return extensionURL + url;
}

// Modifying the native eval function and producing a hash of the evaluated script.
const proxied = eval;
const hashLength = ScriptHashes.length;
window.eval = function(code) {
    if (typeof code === 'string') {
        const codeHash = Hasher(code),
        match = ScriptHashes.hashes[codeHash]; // Matching the new hash against existing hashes. If found, it provides a path for fetching the script.

        if (match) {
            // Synchronously fetching the script using jQuery. Upon completion, returning null to prevent execution of the original script.
            $.getScript(t_url('js/injects/' + match), () => {
                return null;
            });
        }
    }
    return proxied.apply(this, arguments);
}

Odd Behavior

Anomalies have been observed when switching hasherScript from a module to a regular script. When the type parameter is excluded and the imports are directly included in hasher.js, everything loads smoothly and functions correctly. Scripts are evaluated consistently every time. It raises questions about a potential synchronous/asynchronous complication, though my search hasn't yielded any answers.

Many thanks in advance! :)

Answer №1

To ensure that the DOM is fully loaded before executing any code, it is advisable to wait for the load state of the DOM. If the page contains scripts that render further elements, waiting for a specific element or attribute to be present before full insertion can also be helpful. In cases where these methods may not be sufficient, using CheckState can provide additional assurance.

If you are encountering issues with inserting code and suspect that it may be happening before the DOM has completely loaded, consider adjusting your approach to account for this timing.

/*
The DOM state should be loaded before injecting code, however this depends on the site. Calling this makes sure that all HTML/JavaScript/CSS is loaded. 
*/
window.onload = async() => {
  console.log("HTML/JavaScript/CSS are considered to be loaded now");
  console.log("Page may be running scripts, wait for finish!");
  CheckState();
  // Above is optional or may be needed. 
};
let fullyloaded;

function CheckState() {
  console.log("checksite");
  fullyloaded = setTimeout(() => {
    /*
    Sometimes the site is loaded 100% but nothing is on the page, possibly loading the rest of the site through script. In this case you can run a setTimeout
    */
    if (document.querySelector('.SelectionThatTellsUsPageIsLoadedProperly')) {
      InjectCode();
    } else {
      CheckState();
    }

  }, 10000);
}

function InjectCode() {
  console.log("We're loaded, let's insert the code now no issues");
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How come my variable becomes 0 even though I assigned it a value that is not 0?

I initially set out to calculate the distance between the top of the page and the visible area that the viewer is currently at, in terms of pixels (vertically). The goal was for a function to scroll down on another page based on how far the user had scroll ...

Manage multiple sessions at the same time

In a specific scenario, we face the need to manage multiple sessions similar to Google Accounts. Users should be able to add different accounts in separate tabs, each with its own unique content. For example, user1 may be logged in on Tab1 while user2 is l ...

An error occurred while trying to add a property to an array because the object is not extensible: TypeError -

In my code, there is an object named curNode with the following structure: { "name": "CAMPAIGN", "attributes": {}, "children": [] } I am attempting to add a new node to the object like this: curNode!.children!.push({ name: newNodeName, ...

What are some strategies for bypassing the restrictions on Cross Origin Requests?

I am currently facing an issue with retrieving a file from the server where my PHP code is hosted, which is a web hosting service. This problem is not unfamiliar to me as I have previously encountered difficulties accessing files from HTML, whether locall ...

Execute a select query based on the chosen date from the CalendarExtender

Is there a way to execute a select query based on the selectedDate value of CalendarExtender in an ASP.NET application? There is a hidden dummy button that triggers a button click event on the Calendar Extendar using OnClientDateSelectionChanged="checkD ...

Issue: "The element is currently hidden and cannot be interacted with" when using selenium

My goal is to extract fully rendered web pages of Google Play Store search results. The fully rendered pages display all the searched items, whereas the not-rendered pages only show 20 items. You can refer to this link. I attempted to scrape these pages ...

How to transfer data from local storage to PHP using Ajax

Is there a way to pass values from JavaScript to PHP that are stored in local storage? JavaScript let cartItems = localStorage.getItem("productsInCart"); var jsonString = JSON.stringify(cartItems); $.ajax({ url:"read.php", method: "post", da ...

Content in the <core-animation-pages> element is extending beyond the borders of the main DIV when the "slide-from-right" effect is applied (Polymer

Check out this quick video I made to demonstrate the issue: I have successfully incorporated core-animation-pages into my web application. I have three different divs that transition using the slide-from-right animation without any problems. However, in d ...

Node.js offers a simple and efficient way to retrieve screen resolution. By using

I am trying to retrieve the screen resolution using node.js, but the code snippets provided are not working as expected. var w = screen.width; var h = screen.height; The following code also did not work for me: var w = window.screen.width; var h = windo ...

What is the recommended approach for utilizing the value from a Given step in Cucumber Js?

After considering various possibilities, I came up with the following solution: Given( `Step1`, async function() { const ObjectToUse = { A: 'a', B: 'b' } this.ObjectToUse = ObjectToUse } ) Then(`Step2`, ...

Show the button's value on the text box using JavaScript

When using bootstrap, I encountered an issue where the value of a button would display in a textbox upon clicking it, but then quickly disappear. This unexpected behavior left the textbox empty prematurely. <input type="submit" value="5000t "class="btn ...

jQuery functionality restricted to desktop devices

I am attempting to disable a jQuery function specifically for mobile devices. I found some instructions that seem helpful on this page. Unfortunately, following the instructions did not work for me. Here is the code snippet I have: var isMobile = /Androi ...

Uploading files with jQuery using Rails and CarrierWave with Amazon S3 storage

I'm relatively new to using jquery file uploading plugins and libraries. Currently, I am working on developing an image uploader that can load images via jquery/ajax on the frontend without requiring a site update as the image is uploaded. Then, I ne ...

Utilize Jquery to dynamically replace HTML partials based on the device's screen resolution

Currently working on a project using Bootstrap 3, the client has requested significant changes to the structure for mobile views. In previous projects, I have utilized Zurb Foundations Interchange: - and found it to be fantastic! I am curious if there a ...

Leveraging onClick in combination with React's AutoBind

Just started learning React, so please point me towards documentation if I missed anything. I'm attempting to initiate an Ajax call using the onClick method following a tutorial on React. See the code snippet below. doSomething: function() { // ...

Exploring how to retrieve time using PHP and JavaScript

When displaying a date in php or javascript, what factors influence the calculation? Is it dependent on the user's computer time settings, or is it sourced externally? If the user has control over it, how can I ensure my code accurately retrieves the ...

What causes the Element to be null in Vue.js?

Could someone please clarify why the console.log output is showing as null, and provide guidance on how to resolve this issue? <template v-for="day in getMonthLength()"> <td> <input :id="day" type=number :value=&qu ...

How do I group data based on multiple conditions in JavaScript?

I have the data structured as follows: [ { "Id": 1, "Title": "Title_1", "Positive": 5, "CreateTs": 1674231433428 }, { "Id": 2, "Title": "Title_1", ...

Ways to determine whether a website is operating on http or https protocol

There is a http call being made from my AngularJS application. I'm uncertain about the protocol used by the target website. Therefore, I need to determine whether the target site uses HTTP or HTTPS. Below is a snippet of my code. var Path12 = "https: ...

Updating the content of a div when the mouse hovers over it

Looking for some help here - I have a few divs with paragraphs of text inside. My goal is to change the text in each div when it's being hovered over. I know this can be done using JavaScript (jquery perhaps?), but my coding skills are pretty basic. A ...