First Web Extension | Stardew Wiki search box modification

This is the first web extension I made. I've made one for the minecraft wiki as well (same thing), but I haven't released either one. You might have to fix some bugs... I threw this together in a pretty short amount of time & I'm just leaving it as is. Hopefully this is a better experience than reading up on web-extensions to figure out how to do simple things.

The UI for this WebExtension is non-traditional. That means you're loading a popup into the document, instead of using the built-in GUI methods for web extensions. I'm not well-versed on Mozilla or Google's policies on WebExtensions, so I don't know if you can release such a thing, but... It's cool anyway.

We'll build a simple control panel overlay for Stardew Valley Wiki. The search box on the page will be added to a position:fixed div & that search box will be focused everytime a wiki page is loaded or focused. (alt+tab from one app back to the browser)

1. Create the skeleton

  1. Create a directory named rbear-stardew. Perhaps in Documents/Firefox Extensions or inside your usual code workspace, or any other directory of your choice.
  2. Create files as follows (content to follow):
     rbear-stardew
         - manifest.json  //extension settings
         | view
             - panel.html  //html for the control panel
         | script
             - init-panel.js //to set everything up
             - RB-Request.js //to help with sending requests
             - functions.js  //helper functions for adding files to the page
         | style
             - panel.css  //style the control panel
    

2. Add code to all the files

manifest.json

The manifest.json contains all the settings for the extension.
Pay special attention to content_scripts and web_accessible_resources.

{
    "manifest_version": 2,
    "name": "StardewValleyWiki Panel",
    "version": "1.0",
    "description": "Adds a (bad) control panel to StardewValleyWiki.com",
    "content_scripts": [
        {
            "matches": [
                "*://stardewvalleywiki.com/*"
            ],
            "js": [
                "/script/RB-Request.js",
                "/script/functions.js",
                "/script/init-panel.js"
            ]
        }
    ],
    "web_accessible_resources": ["style/panel.css","view/panel.html"]
}

The order of the scripts in content_scripts is very important. Those scripts are loaded in the order they're listed & init-panel.js needs the prior two scripts to be loaded already.

view/panel.html

This is a very simple panel which will float on top of the page, thanks to style/panel.css.

<div id="rbear_wiki_panel">
    <h6>Control Panel</h6>
</div>

style/panel.css

This makes your HTML panel float. & it's super ugly.

#rbear_wiki_panel {
    background:rgba(0,0,0,0.4);
    position:fixed;
    right:0;
    top:0;
    width:250px;
    height:200px;
}

script/init-panel.js

This will set everything up. Note that it depends upon RB-Request.js and functions.js (see below)

    function focusSearch(){
        const searchInput = document.getElementById("searchInput"); //pre-existing component on the StardewWiki
        searchInput.focus();
    };
    function doSetup(){
            const cssUrl = browser.runtime.getURL('/style/panel.css');
            loadStyle(cssUrl);

            const viewUrl = browser.runtime.getURL('/view/panel.html');
            const request = new RB.Request(viewUrl);
            request.handleText(function(rawHtml){
                loadHtmlString(rawHtml);
                const form = document.getElementById("searchform"); //pre-existing component on the StardewWiki
                    form.parentNode.removeChild(form);
                const wikiPanel = document.getElementById("rbear_wiki_panel"); //prefix, to avoid collisions with resources on the site.
                    wikiPanel.appendChild(form); //moved the searchform from the page as normal over to the floating panel
                focusSearch();
            });
    }
    window.addEventListener("focus",focusSearch); //so the search bar gets focus every time the page is focused
    document.body.addEventListener("load",doSetup); //in case the body isn't loaded yet... 
    doSetup(); //the body probably is already loaded, so 'load' won't be called. This is a bad approach.

You can see we call loadStyle(cssUrl) above. This is because, if we embed a <link> inside /view/panel.html, it will load stardewvalleywiki.com/view/panel.html, since the html is being added to the main document's body. There are probably better ways to handle this.

script/RB-Request.js

Get an up-to-date crummy Request class on github.

if (typeof RB === 'undefined')var RB = {};
RB.Request = class {
    constructor(url, method){
        this.params = {};
        this.url = url;
        this.method = method ?? 'POST';
    }
    put(key,value){
        if (key in this.params){
            this.params[key] = (typeof this.params[key]==typeof []) ? this.params[key] : [this.params[key]];
            this.params[key].push(value);
        } else {
            this.params[key] = value;
        }
    }

    handleJson(func){
        var formData = new FormData();
        for(var key in this.params){
            const param = this.params[key];
            if (typeof param == typeof []){
                for(const val of param){
                    formData.append(key,val);
                }
            } else formData.append(key,this.params[key]);
        }
        fetch(this.url, {
            method: this.method, 
            mode: "cors",
            body: formData
        }).then(res => {
            return res.json();
        }).then(json => {
            func(json);
        });
    }
    handleText(func){
        var formData = new FormData();
        for(var key in this.params){
            const param = this.params[key];
            if (typeof param == typeof []){
                for(const val of param){
                    formData.append(key,val);
                }
            } else formData.append(key,this.params[key]);
        }
        fetch(this.url, {
            method: this.method, 
            mode: "cors",
            body: formData
        }).then(res => {
            return res.text();
        }).then(text => {
            func(text);
        });
    }
}

script/functions.js

function loadStyle(href){
    // avoid duplicates
    for(var i = 0; i < document.styleSheets.length; i++){
        if(document.styleSheets[i].href == href){
            return;
        }
    }
    var head  = document.getElementsByTagName('head')[0];
    var link  = document.createElement('link');
    link.rel  = 'stylesheet';
    link.type = 'text/css';
    link.href = href;
    head.appendChild(link);
}
function loadHtmlString(html,parentNode = 'document.body'){
    if (parentNode==='document.body')parentNode = document.body;
    var wrapper  = document.createElement('div'); //you could use a span here, if you want inline-styling, or customize styles through js. 
    wrapper.innerHTML = html;
    parentNode.appendChild(wrapper);
}

3. Run the extension

  1. Open https://stardewvalleywiki.com in Firefox & look at the search bar in the top-right
  2. Go to about:debugging in a new tab (type that into the url bar)
  3. click 'This Firefox'
  4. click 'Load Temporary Addon'
  5. Choose the 'manifest.json' of your extension
  6. Refresh the Stardew wiki page.
  7. Click on the page (so search bar not focused).
  8. Alt+tab to a different program, then alt+tab back & type "tractor" (or whatever)