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.

wOxxOm
  • 65,848
  • 11
  • 132
  • 136
Adam
  • 3,992
  • 2
  • 19
  • 39
  • 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. – wOxxOm Jul 06 '17 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? – Adam Jul 06 '17 at 17:29
  • 1
    Make sure to read the [extension architecture overview](https://developer.chrome.com/extensions/overview#arch). Neither content script nor popup can refer to each other directly, they run in different parts of the browser. – wOxxOm Jul 06 '17 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. – Adam Jul 06 '17 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. – wOxxOm Jul 06 '17 at 17:44
  • Also, to see the devtools and console of the popup you need to rightclick it and choose Inspect. – wOxxOm Jul 06 '17 at 17:46
  • Yeah I knew I could get to the console for the extension that way but forgot to check that when clicking the button. Now I am seeing the output of the console.log function. – Adam Jul 06 '17 at 17:50
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/148537/discussion-between-adam-and-woxxom). – Adam Jul 06 '17 at 18:02
  • See also: [Is there a JavaScript/jQuery DOM change listener?](https://stackoverflow.com/q/2844565) – Makyen Jul 07 '17 at 15:49

1 Answers1

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.

Adam
  • 3,992
  • 2
  • 19
  • 39
  • 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. – Makyen Jul 07 '17 at 15:47
  • 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`? – Adam Jul 07 '17 at 16:26
  • 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. – Makyen Jul 07 '17 at 16:41
  • 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). – Makyen Jul 07 '17 at 16:41