Chrome is not defined


#1

Hi,

I am working on porting over a Chrome extension to Firefox. However with the temporary add on installed in Firefox, I keep getting “ReferenceError: chrome is not defined”. I am trying to send a message from my content script to my background.js file in the extension. Everything works perfectly in Chrome but that message that I’m trying to send allows the extension to know if I’m logged into the website and am verified to use the our API. Because of this, due to other code that processes, the extension pops up, it just can’t log me in. Is there any reason you all can think of why chrome would be undefined in Firefox if it should be compatible with Chrome? I tried browser.runtime as well and get the same issue “ReferenceError: browser is not defined. Any help would be greatly appreciated.

Thank you,
Daniel

If it helps here is my background.js

chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (request) {
if (request.message) {
if (request.message == “version”) {
sendResponse({version: 1.0});
}
}
if (request.token == “not added”) {
window.token_string = “”;
localStorage.setItem(“giftibly_token”, “”);
}
else{
window.token_string = request.token;
localStorage.setItem(“giftibly_token”, token_string);
}
}
return true;
}
);
var isChromium = window.chrome,
winNav = window.navigator,
vendorName = winNav.vendor;
if (isChromium !== null && typeof isChromium !== “undefined” && vendorName === “Google Inc.”) { //if Chrome
chrome.extension.onConnect.addListener(function(port) {
port.onMessage.addListener(function(msg) {
console.log(msg);
if(msg == “token not added”){
port.postMessage(token_string);
}
});
});
}
else{
browser.runtime.onConnect.addListener(function(port) { //if not Chrome
port.onMessage.addListener(function(msg) {
console.log(msg);
if(msg == “token not added”){
port.postMessage(token_string);
}
});
});
}

And my content script. The turbolinks:load function is due to my project using Ruby on Rails.


(Michel Gutierrez) #2

The chrome global variable is only available on Chrome, Chromium, Opera, Vivaldi and maybe a few others. On Firefox or Edge, the add-on API should be invoked with the browser global. The best way to deal with this is to use the library webextension-polyfill. Once you have loaded this library, you can use browser on any browser (well, on Edge it takes a patch that is available). In addition, you can use the promisified version of the API function (browser.method(...,callback) can now be called with browser.method(...).then(...)).

If you don’t want to use that library, you can still do something like const browser = window.browser || window.chrome; at the top of your source file.

I’m unsure why you get browser is not defined on Firefox but this is definitely what you’d get on Chrome.


(erosman) #3

It is available on Firefox as well


(Michel Gutierrez) #4

It is available on Firefox as well

Damned, i’ve been using browser for so long …


(erosman) #5

There is a difference. chrome returns a callback function and browser returns a promise.


(Michel Gutierrez) #6

I’m pretty sure you can also use browser with the callback mechanism, just like chrome.

browser.tabs.query({},(tabs)=>{
    ...
})

Anyway, can we say the best practice is to use webextension-polyfill, the browser global and Promises ?


#7

Thanks for the responses everyone. I added the browser-polyfill.js file to my site and now I’m getting ReferenceError: module is not defined on line 362 of the unminified version. Any ideas what this could be.

Also tried browser = window.browser || window.chrome; which mig suggested with no luck.

I should add too that browser and chrome seem to be defined in the extension just not on my site that needs to send the message.


(Michel Gutierrez) #8

You should not take browser-polyfill.js “as is” from the github repository as the code is written as a UMD wrapper (basically, defined as a module that requires some machinery to run), but instead you should do one of those:

  • clone the github repository and build the library according to the instructions
  • install a released version from npmjs using npm install webextension-polyfill, and the file you need is in ./node_module/webextension-polyfill/dist/browser-polyfill.min.js
  • or download the library from a CDN

This being said if globals browser and chrome are undefined for you, you will still have the problem. What version of Firefox are you running ?


#9

Well downloading the library fixed the module issue but you’re right. That didn’t fix chrome and browser not being defined.

My version is: 56.0.2

Everything says it is up to date. Would a fresh install have a possibility of helping?


(Michel Gutierrez) #10

What if you open URL about:debugging and click the debug link in your add-on’s section.
At the bottom of the console window that opens, you can enter commands that run in the context of your add-on. Try typing typeof chrome and typeof browser. In my case, i get object in both cases.


#11

Browser and Chrome also appear as object. But that makes sense, because they already seemed to be defined for whatever reason since the extension doesn’t throw errors. However typing that in the regular browser inspect element I get undefined for both browser and Chrome.

I also tried Object.keys(window); on both Chrome and Firefox. On Chrome, both browser and chrome globals are there and on Firefox they both aren’t and it’s almost like it skips them.


(Michel Gutierrez) #12

Is there any chance you are trying to call chrome.runtime (or browser.runtime) from the regular javascript code of a web page, and not from a content script the add-on injected into the page ? That would explain your behaviour.

If so, what you should do is modify the web site code to maintain a DOM element (like a hidden DIV), with an attribute telling whether you are logged or not. On the add-on side, you must inject a content script that will read this DOM element and report back to your background using the add-on API that will now be available.

You can inject this content code from your manifest.json file, using something like:

{
    ...
    "content_scripts": [{
        "matches": ["*://*.mysite.com/*"],
        "js": ["myscript.js"]
    }
    ...
]

#13

That is exactly my problem. However I don’t want to inject that script into every page my extension is used in if I don’t have to. Is there any way around this? If not, I will definitely try it and see if it works.

Also, is there any reason why chrome works fine on the chrome browser without doing this?

Here is my current manifest if it helps:

{
    "manifest_version": 2,

    "name": "Gift Assistant",
    "version": "1.0",
    "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArgWA5T0TOFDM4qUtjGycEgDR+0waox8l9Hc0FI5FJUgfi3a3Iw6uBJDsUtSyGFHHzV9XkifHmbnnNnRi0P6wg8/4PthPtj71qBynshusb3SzBIq0Uou17wj1GuXjSKgP18ttFOwDhL/BQAoixWAc9QU0HtRoEM+HoO9NM0K8bbAvfhcyn6LSM1/DScCOqKd6QqDv//1YHzgtTn0vZt2f7fnRIhOiiIM0J05QfUEgdLZblxHYUKoL+QSJwRD2r2KyQ/fdZmUl3VtLqvIKTcLEAOvjFNZr4Kn99KUrcotLRmC/8sXoYnccUvUlw06diq9zVvLdfv+eJFFLPzAWU+DSMwIDAQAB",

    "icons": {
        "16": "img/icon16.png",
        "48": "img/icon48.png",
        "128": "img/icon.png"
    },

    "browser_action": {
        "default_icon": {
            "16": "img/icon16.png",
            "48": "img/icon48.png",
            "128": "img/icon.png"
        },
        "default_popup": "popup.html"
    },
    
    "background": {
        "scripts": ["eventPage.js"],
        "persistent": false
    },
    
    "externally_connectable": {
      "matches": ["http://localhost:3000/*", "http://mywebsitename.com/*"]
    },
    
    "content_scripts": [{
        "matches": ["http://*/*", "https://*/*"],
        "js": [
            "scripts/jquery-3.2.1.min.js",
            "scripts/getitem/itemcolor.js",
            "scripts/getitem/itemsize.js",
            "scripts/getitem/itemname.js",
            "scripts/getitem/itemmerchantname.js",
            "scripts/getitem/itemimage.js",
            "scripts/getitem/itemprice.js",
            "scripts/getitem/itemapiid.js",
            "scripts/getitem/itemdescription.js",
            "scripts/item.js",
            "content.js"
        ]
    }],

    "permissions": [
        "tabs", "activeTab", "https://*/", "management"
    ],
    "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
}

(Michel Gutierrez) #14

The other way to inject code into content (apart from declaring the content_script pattern in the manifest.json), is to use browser.tabs.executeScript(). You then need to monitor tabs to choose when you want to inject your script.

Note that from your manifest.json, you already inject jquery and a bunch of other files into every page your browser loads. That’s probably not a good practice and in comparison, injecting a few lines script to check whether you are logged or not is not a big deal.


#15

For some reason, I can’t see my injected script on my website.

This also presents another issue because I have a dynamic token that I need that is displayed with Ruby in the Javascript as a variable. I need to have this variable be read by the injected script.

Also, the purpose of application is to parse other website’s product pages so I have to have all those js files there.

Is there any reason why chrome works fine on the chrome browser without doing all of this?


(Michel Gutierrez) #16

You cannot share variables between the regular page scripts and the addon-injected content script. Both components have to communicate through the DOM.

What you need to do is, in your page script, to create an element like an hidden DIV, and set an attribute (like data-mytoken) to the desired value, then have your addon content script search for this element and read the attribute.

For some reason, I can’t get my injected script to appear on my website.

Do not expect to see the addon content script to appear in the DOM like in a <script> tag, this won’t be the case. I don’t remember if using the console in your addon script works, but if it doesn’t, you can try still use alert() to display debug data.


#17

Awesome! Almost there. I’m getting all the data I need to be passed through however now my send message isn’t working. This is the same code I had. I’m just not getting a reply which probably means the message didn’t go through.

My error is : “reply is undefined” which is coming from the alert
I also get: Unchecked lastError value: Error: Could not establish connection. Receiving end does not exist.

var isChromium = window.chrome,
    winNav = window.navigator,
    vendorName = winNav.vendor;

if ((isChromium !== null && typeof isChromium !== "undefined" && vendorName === "Google Inc.") || (navigator.userAgent.toLowerCase().indexOf('firefox') > -1)) {  //detect if Chrome is the Browser
    if($('#ga_logged_in')[0]){
        var editorExtensionId = $('#ga_logged_in').data('gaid');
        var hasExtension = false;
        var token_string = $('#ga_logged_in').data('token');
        alert(token_string);
        chrome.runtime.sendMessage(editorExtensionId, { message: "version",  token: token_string},
            function (reply) {
                alert(reply);
                if (reply.version >= "1.0") {
                    hasExtension = true;
                    $('#gift_assistant_link').remove();
                    localStorage.setItem("token", "added");
                }
                else {
                  hasExtension = false;
                }
            }
        );
    }
    else if($('#ga_logged_out')[0]){
        var editorExtensionId = $('#ga_logged_in').data('gaid');
        var token_string = 'not added';
        chrome.runtime.sendMessage(editorExtensionId, {token: token_string});
        localStorage.setItem("token", "");
    }
}

Event Page:

var isChromium = window.chrome,
    winNav = window.navigator,
    vendorName = winNav.vendor;
if (isChromium !== null && typeof isChromium !== "undefined" && vendorName === "Google Inc.") { //if Chrome
    
    chrome.runtime.onMessageExternal.addListener(
        function(request, sender, sendResponse) {
            if (request) {
                if (request.message) {
                    if (request.message == "version") {
                        sendResponse({version: 1.0});
                    }
                }
                if (request.token == "not added") {
                    window.token_string = "";
                    localStorage.setItem("token", "");
                }
                else{
                    window.token_string = request.token;
                    localStorage.setItem("token", token_string);
                }
            }
            return true;
        }
    );
    
    
     chrome.extension.onConnect.addListener(function(port) {
          port.onMessage.addListener(function(msg) {
               console.log(msg);
               if(msg == "token not added"){
                   port.postMessage(token_string);
               }
          });
     });
}
else{
     browser.runtime.onMessageExternal.addListener(
        function(request, sender, sendResponse) {
            alert('test');
            if (request) {
                if (request.message) {
                    if (request.message == "version") {
                        sendResponse({version: 1.0});
                    }
                }
                if (request.token == "not added") {
                    window.token_string = "";
                    localStorage.setItem("token", "");
                }
                else{
                    window.token_string = request.token;
                    localStorage.setItem("token", token_string);
                }
            }
            return true;
        }
     );
     browser.runtime.onConnect.addListener(function(port) {
          port.onMessage.addListener(function(msg) {
               console.log(msg);
               if(msg == "token not added"){
                   port.postMessage(token_string);
               }
          });
     });
}

(Baptiste Thémine) #18

This is because you are using different messaging APIs. If you want to use ports, you must send message with port.postMessage. Notice also :

runtime.connect()
Establishes a connection from a content script to the main extension process, or from one extension to a different extension.

runtime.onConnect
Fired when a connection is made with either an extension process or a content script.

runtime.onMessageExternal
Fired when a message is sent from another extension. Cannot be used in a content script.

Source : https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime

Just a suggestion : Instead of trying to send messages from your website to the addon, I would rather send an XHR request from the addon to the website and retrieve data.


#19

Just got it working! Thank you so much everyone. There’s a security error so it isn’t functional yet but it’s a separate API issue which needs to be fixed anyways.