Loading libraries for use in a content script - javascript

I created a Firefox extension that works, for the most part. I am having a hard time importing jQuery. I have downloaded it locally. I am getting no errors. So, sometimes the extension will work and jQuery will load. Sometimes it won't. Other times I have to reload the page 5 or 6 times to get it to work.
I am not a JavaScript developer and this is my first time attempting an extension. I have Googled and tried a bunch of things with no luck.
Below is my manifest.json
"web_accessible_resources" : ["/jquery-3.2.1.min.js","/jquery.csv.min.js","/ui.js"],
"icons": {
"128": "icon_128px.png",
"48": "icon_48px.png"
},
"browser_action": {
"default_icon": "icon_48px.png"
},
"content_scripts": [
{
"matches": ["https://*****.com/*"],
"js": ["content.js"],
"run_at": "document_end"
}
],
"permissions":[
"activeTab"
],
"homepage_url": "https://*****.com"
}
content.js
function injectJs(link) {
var scr = document.createElement("script");
scr.type="text/javascript";
scr.src=link;
(document.head || document.body || document.documentElement).appendChild(scr);
}
injectJs(chrome.extension.getURL("/jquery-3.2.1.min.js"));
injectJs(chrome.extension.getURL("/jquery.csv.min.js"));
injectJs(chrome.extension.getURL("/ui.js"));

Normally, you would load jQuery by including it within the js key in your manifest.json content_scripts entry. For example:
"content_scripts": [
{
"matches": ["https://example.com/*"],
"js": ["jquery-3.2.1.min.js", "jquery.csv.min.js", "ui.js", "content.js"]
}
]
Scripts are loaded in the order listed. So, you need to list the libraries which depend on others after the ones they depend upon (e.g. "jquery-3.2.1.min.js" before "jquery.csv.min.js").
What you were doing
The way that you were doing it inserted the scripts into the page context using <script> tags. Such tags are loaded asynchronously. Thus, there was no guarantee that your scripts which depended on jQuery were actually loaded after jQuery. For what you are doing, you don't want to be loading the scripts into the page context, which is separate from the content script context where your content scripts normally run. If you want more information as to how you can do that successfully, you can see my answer to How to sequentially insert scripts into the page context using tags, which has fully functional code to insert multiple dependent libraries into the page context.
Use .tabs.executeScript() to dynamically load scripts when they are not used 100% of the time
However, if you are loading your content script into a large number of pages (e.g. matches being "<all_urls>", *://*/*, etc.), then you should use a manifest.json content_scripts entry to load only the bare minimum needed to show the initial portion of your user interface (i.e. just the static portion seen prior to the user interacting). Only once the user begins interacting with your user interface should you then send a message, using .runtime.sendMessage(), to your background script, received using .runtime.onMessage(), to instruct your background script to inject the rest of the files needed for your complete user interface. You background script would then use .tabs.executeScript() to load the additional scripts you need and, perhaps, tabs.insertCSS() to inject any additional CSS which you may need.
The point of doing the above is to minimize the impact your extension has on the user/browser during the time which the user is not actively using your extension, which is most of the time under most conditions.

Related

Chrome extension script not triggering on navigation but only on refresh on willhaben.at

I am currently learning js and dom manipulation and I am trying to figure this out since about 24 hours. By now I am completely clueless. I have the following (below) code where I try to DOM manipulate willhaben.at to inject it with some additional information.
The injection only works when I refresh the page or open it in new tab, I know this is because:
The Chrome extension content script will only load when a completely new webpage matching the URL specified in your manifest is loaded. In this era of single page web applications, many websites, including GitHub, use Javascript frameworks and Ajax calls to only update parts of the existing webpage content as the user navigates around the site. Even though the address bar is being updated, most of the time no actual page loads are being executed, so your chrome extension won't trigger.
My manifest:
{
"manifest_version": 3,
"name": "WH",
"version": "1.0",
"description": "...",
"icons": {
"32": "icon.png"
},
"content_scripts": [
{
"matches": ["*://www.willhaben.at/iad/kaufen-und-verkaufen/d/*"],
"js": ["wh.js"],
"run_at": "document_end"
}
]
}
An example script:
const changeThis= document.querySelector('[data-testid="ad-detail-header"]');
changeThis.innerHTML = 'THIS IS NOT WORKING WITHOUT REFRESHING :(';
I've tried to trigger my script if the url changes, I've tried mutationObserver and probably many other things I don't even remember. I can't even get a darn log in to the console after I navigate without a refresh.
I am pretty sure it is something banal but I can't seem to be able to figure it out. Sorry if you would need code snipes of what I've tried by now but I didn't save all the trials I did and it would be a freaking large amount of code snippets.
(before posting I went trough all of the "Review questions already on Stack Overflow to see if your question is a duplicate." but non of them solved my issue on this site. However I tried the below two after while going trough the list)
document.addEventListener("pjax:end", function() {
console.log('pjax:end');
});
Manifest:
"matches": ["*://*.willhaben.at/*"],
Does not work either.

Chrome content script not executing on Ember site

I've got a Google Chrome extension which has the following content script syntax in it's manifest.json:
"content_scripts": [
{
"matches": [
"https://example.com/*"
],
"js": ["js/jquery-2.1.1.js", "js/custom.js"],
"run_at": "document_end",
"all_frames": true
}
],
When testing this extension on an Ember site, it runs on initial page load but after changing the page, it does not get injected again.
For non-Ember users, Ember can update the URL and page content without performing an entire page reload which appears to be causing this issue.
Is anyone aware of a work-around for examples like this?
This was fixed by using chrome.WebNavigation.onHistoryStateChanged which fires every time the page updates.

Should I use location.href or chrome.tabs.query with message sending to get the tab URL?

I am writing a Chrome extension which recognizes certain URL pattern and perform further DOM manipulation. The content script has to get the current URL and matches with the predefined list of URL patterns.
There are two ways I could think of achieving the goal:
The first one would be using location.href
manifest.json
...
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_end"
}
],
...
content.js
console.log(location.href);
This method works fine. However, across other similar questions on StackOverflow, they usually suggests using chrome.tabs and message sending from background script to content script as follow:
manifest.json
...
"background" : {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_end"
}
],
...
background.js
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
chrome.tabs.get(tab.id, function(tabInfo) {
chrome.tabs.sendMessage(tab.id, {
url: tabInfo.url
}, function(response) {
});
})
});
content.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.url) {
console.log(request.url)
sendResponse(true);
}
return true;
});
Both methods can have the URL correctly. And for the background script, it requires extra memory to keep the background script running in background, compared with content script which is only injected when loading the page.
On the other hand, using background script has benefit as the background script is said to be privileged, it can execute privileged Chrome APIs like the chrome.tabs API.
So as far as I am not using any privileged APIs, should I use the location.href or is there any particular reason most developers suggest using the chrome.tabs and message sending?
Advantages of a background page script:
Complex checks using additional information from the Tab object (N.B. add "tabs" permissions to access url, title, favIconUrl), whereas manifest.json-based injection is limited to URL wildcards/globs.
chrome.declarativeContent API with RequestContentScript action is an example of advanced filtering, it injects the content script(s) based on URL checks and DOM content (only simple selectors as noted in the documentation).
chrome.tabs.executeScript inside chrome.tabs.onUpdated or chrome.webNavigation or chrome.browserAction.onClicked listener to inject the content script(s) only when needed.
Changing the toolbar icon or the browser context menu to reflect change of state; content scripts can't do that, the background page script can.
Accessing privileged API like most of chrome.*, extension's internal storage such as IndexedDB, WebSQL, HTML5 FileSystem, localStorage (the latter is not a good choice though as it's not available in a content script directly, moreover it's synchronous and thus blocks execution).
In all the above cases it makes sense to pass data to content script using messages if that data was used while checking the conditions or it's only available in the background script. Otherwise chrome.storage API inside the content script is as good or could be even better readability-wise.
"Backgroundless" content script is better when all these conditions are met:
URLs to process can be set entirely with wildcards/globs or it really really must be <all_urls>
all required parameters are accessible via chrome.storage API or no parameters needed
no privileged chrome.* APIs are used as those aren't available in content scripts, in other words when the background page is not actually needed.
As for memory consumption: either method may be better or worse depending on how it's used.
If the content script is injected on all pages then each instance will consume memory (some people open 100 tabs so beware!). The worst case case is obviously when both persistent background page and content script on all URLs are used. Non-persistent event page might help but in a limited fashion because chrome.tabs.onUpdated is likely to run pretty frequently forcing the event page to reload (which also takes some time).

Chrome extension content script won't load script from web accessible resources

I'm trying to create a chrome extension that interacts with youtube. It loads the content script, which is then supposed to inject the experiment.js script from web_accessible_resources. None of my code from experiment.js works.
I've followed this as reference: Insert code into the page context using a content script
manifest.json
{
"version": "1.0",
"manifest_version": 2,
"permissions": ["tabs", "https://*/*"],
"content_scripts": [{
"js": ["contentscript.js"],
"matches": [ "https://*.youtube.com/*", "http://*.youtube.com/*"]
}],
"web_accessible_resources": ["experiment.js"],
"browser_action": {
"default_icon": "icon.png"
}
}
contentscript.js
var s = document.createElement('script');
s.src = chrome.extension.getURL('experiment.js');
s.onload = function() {
this.parentNode.removeChild(this);
};
(document.head||document.documentElement).appendChild(s);
experiment.js
alert('loaded');
console.log('loaded');
EDIT: I just used another solution by including the code from experiment.js into contentscript.js in an array and joining each line. The process is referred to as "Method 2" in the reference post I added earlier.
Basically the problem was caused by content scripts limitations. For security reasons (I guess) content scripts can't use variables or functions defined by web pages or by other content scripts. It's called sandboxing.
When you add new script to the page by creating new <script> element you add this script to the page's sandbox. Therefore content script couldn't see the one added to the page, even if it is your extension's code.
Simplest solution is you quick fix. You just need to add the script to the manifest file or using programmatic injection.
Besides their limitations, content scripts can use shared DOM to communicate with the page. In this case you could add script to the page using <script> tag and communicate with content script using window.postMessage.

JQuery 1.11 not loading from manifest

I just started playing with Chrome extensions, trying to load JQuery from manifest file, and test if it's loaded. It is not loading, although I did specify it in content-scripts! Any idea why?
sample.js
if (typeof jQuery != 'undefined') {
alert("jQuery library is loaded!");
}
manifest.json
{
"manifest_version": 2,
"name": "__MSG_extName__",
"version": "0.1",
"default_locale": "en",
"background": { "scripts": ["sample.js"] },
"permissions": ["contextMenus"],
"minimum_chrome_version": "33",
"content_scripts": [{
"matches": ["<all_urls>"],
"css": ["style.css"],
"js": ["lib/jquery-1.11.0.js"]
}]
}
As noted in the comments to the question, you are doing things in two different contexts.
The "background" key defines what is loaded in a single invisible page where the "main" code resides.
"content_scripts" injects code into other pages instead, that can communicate with the background page via Chrome API.
A good overview is available here.
It's not immediately clear why <all_urls> match pattern does not inject the script into your background page, but fact is, it's not.
How to modify your example depends on what you try to achieve.
If you need sample.js to be executed on every page when it loads, you need to move it to the "content_scripts" key. Note that most of the Chrome API will be unavailable.
If you also need jQuery in your background page (doubtful, but possible), you can add it to the background.scripts list.
If you need some processing done that is not available to the content scripts, then you need to have two scripts, one background script that does the work and an injected content script that communicates with it via Messaging API.
To inject jQuery to every page you can do use this approach.
Add the following line to the manifest.json file. This specify that packaged resource (jQuery file inside extension) is expected to be usable in the context of a web page.
"web_accessible_resources": ["lib/jquery-1.11.0.js"]
Add the following code into new file located in your (file manifest.json) "content_scripts: [{ "js": new_file.js }]" to actually inject jQuery when onLoad event triggers:
The new_file.js file:
var s = document.createElement('script');
s.src = chrome.extension.getURL('lib/jquery-1.11.0.js');
(document.head||document.documentElement).appendChild(s);
s.onload = function() {
s.parentNode.removeChild(s);
};

Categories

Resources