0

I'm trying to write a simple Chrome extension to grab info on flats from a website but I can't even get the on/click functions from JQuery to work.

manifest.json

{
  "manifest_version": 2,

  "name": "Flat",
  "description": "Adds flats to app",
  "version": "1.0",

  "browser_action": {
    "default_icon": "icon.png",
    "default_popup": "popup.html"
  },
  "permissions": [
    "activeTab",
  ],
  "content_scripts": [
    {
      "matches": [
        "https://www.spareroom.co.uk/flatshare/flatshare_detail.pl*",
      ],
      "js": ["lib/jquery.min.js", "lib/bootstrap.min.js", "content.js"]
    }
  ]
}

content.js

$(document).ready(function () {
    console.log('Viewing flat');
    $("#hello").on("click", function () {
        console.log("Hello");
    });
    $("#hello").click(function () {
        console.log("hello");
    });
});

popup.html

<html>
<head>
    <title>Popup</title>
    <link rel="stylesheet" href="lib/bootstrap.min.css">
    <link rel="stylesheet" href="style.css">
    <script src="lib/jquery.min.js"></script>
    <script src="lib/bootstrap.min.js"></script>
    <script src="content.js"></script>
</head>
<body>
    <div class="container">
        <button id="hello" class="btn btn-default">Add Flat</button>
    </div>
</body>
</html>

I'm not sure what the issue here is. JQuery is 3.2.1 and Bootstrap is 3.3.7. There is not a lot of JQuery related Chrome Extensions questions on here. I found this one Chrome extensions with jQuery but I already have the document ready function encapsulating the other code. Also looked at this one $(document).click() not working in chrome extension but that solution doesn't take advantage of JQuery click/on functions.

9
  • 1
    The site probably adds its content dynamically after the document ready event is fired. You can verify this by adding console.log($("#hello")[0]) in the content script. If it's null/undefined you need to wait via setTimeout/setInterval or window.onload or MutationObserver. Commented Jul 6, 2017 at 17:24
  • That does return undefined which to me doesn't make sense. My assumption is that $(document) is referring to popup.html and not the actual webpage. But that appears incorrect. What am I not understanding? Commented Jul 6, 2017 at 17:29
  • 1
    Make sure to read the extension architecture overview. Neither content script nor popup can refer to each other directly, they run in different parts of the browser. Commented Jul 6, 2017 at 17:33
  • Alright I guess I will have to reread that but it looks like I am gonna have to move some stuff to a background page. Commented Jul 6, 2017 at 17:36
  • 1
    I see you're including content.js inside the popup too. You need to use a different script for the popup. The content script runs in web pages. Commented Jul 6, 2017 at 17:44

1 Answer 1

2

Alright so the issue here was my misunderstanding of the context each of the JS files are ran and the messaging system in Chrome. So I'll outline the changes to my code below. There might be a better way of doing this but this is the first time I've developed a Chrome extension.

I modified manifest.json as shown below

manifest.json

...
"background": {
    "scripts": ["background.js"]
},
"content_scripts": {
  {
    ...
    "js": ["lib/jquery.min.js"]
  }
}

I added the background script as well as changed the script used in popup.html to be popup.js instead of content.js

popup.js is shown below. This is ran in the context of the extension on popup.html and sends a message to Chrome that kicks off a handler in background.js

popup.js

$(document).ready(function () {
    console.log('Viewing flat');
    $("#hello").click(clickHandler);
});

function clickHandler(e) {
     console.log('Adding flat');
     chrome.runtime.sendMessage({directive: "get-flat"}, function (response) {
        //
        console.log("handler", response);
    });
}

In background.js I created a listener using the onMessage event API of chrome.runtime. This then will call chrome.tabs.executeScript that executes a script (content.js) in the context of the actual webpage. The messenger listener is a similar patter to Redux w/ React so that was pretty interesting. I found this pattern on this question Detect a button click in the browser_action form of a Google Chrome Extension

background.js

chrome.runtime.onMessage.addListener(
    function (request, sender, sendResponse) {
       switch (request.directive) {
           case "get-flat":
               chrome.tabs.executeScript(null, { file: "content.js" });
               sendResponse("in get-flat directive");
               break;
           case "process":
               console.log("process", request.data);
               break;
            default:
               break;
        }
    }
);

The next part is content.js which runs in the context of the webpage and grabs the data I care about. It then passes that message back to background.js for further actions. I simplified the code for brevity's sake.

content.js

function getFlat() {
    console.log("in getFlat");
    // Do stuff
    var val = $("#something").text();
    return val;
}
chrome.runtime.sendMessage({directive: "process", data: getFlat()});

I should mention that I think the only reason JQuery worked in content.js was because the site I am grabbing info from also uses JQuery. I would of had to do something along the lines of tabs.executeScript - passing parameters and using libraries? in background.js to inject JQuery into the context so content.js could use it if that wasn't the case.

Edit

^^ This was incorrect in that I am injecting JQuery via the content_scripts part of manifest.json

end edit

Either way I was able to resolve the issue. I also used this for reference Pass a parameter to a content script injected using chrome.tabs.executeScript()

If there is a better way of doing this feel free to comment. Like I said this was my first attempt at a Chrome extension.

Sign up to request clarification or add additional context in comments.

4 Comments

Your manifest.json appears to imply that you including popup.js as both a content script and in your popup.html. Doing so is almost always a Bad Idea™. In general, you should only load a single script into multiple contexts which is a library (e.g. like jQuery, etc.). Scripts written specifically to work in one context will usually have problems if you try to use them in a different context. Doing so is the source of many questions in these tags.
Ok so I should remove popup.js as well as the bootstrap script since it injects them into the context of the webpage. I thought I had to do that so it would work in popup.html. I will go ahead and remove it. So since I am injecting JQuery as a content script that is why I am able to use it in content.js?
Yes. Just FYI: The popup and content script contexts are completely separate. You do not/should not be injecting the same scripts (except libraries) into multiple different contexts. You can, but if you don't intentionally program it that way, which is usually more effort, it just creates problems.
Just FYI: There are 3 primary context types: the background (one context shared by your extension's scripts for the background, popup, option page, and any other HTML page loaded as a top level document in a tab or frame), the content script context (a different context for every page/frame which is not a parent/child in the same domain), and the page context (where the web page's scripts run; a separate context per page/frame w/ same parent/child caveat). The for each page/frame (same caveat) the content scripts and page scripts share access to the DOM (and some other special stuff).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.