Google chrome extension race condition - javascript

I have a google chrome extension. In the background.js file, it contains this listener:
chrome.webRequest.onBeforeRequest.addListener(function(info)
{
console.log("On Before request info", info);
},
{
urls:
[
"<all_urls>"
]
},
[
"blocking"
]
);
I tell google chrome to open using the "--app" command line parameter and the html page it opens contains this:
<script type="text/javascript">
let url = new URL(window.location.href);
let targetUrl = url.searchParams.get("targetUrl");
if(targetUrl != null)
{
targetUrl = unescape(targetUrl);
console.log("The target url is:", targetUrl);
window.location.href = targetUrl;
}
</script>
It gets a url from the from the params and then navigates to it.
The issue is that my google chrome extension's listener is not being fired.
I found that if I set it to do the redirect with a 1 second timeout.
Then it fires as its suppose to.
This makes me believe that the background.js file of my extension is not executed before the first page is loaded.
How can I make google chrome assure that all the extensions are loaded and initialized before the first page is ever loaded?

Related

Firefox extension: Open window and write dynamic content

I have developed a Chrome Extension and it's mostly compatible to firefox web-extensions API. Just one problem:
In Chrome Extension i have popup.js and background.js. User click's a button, popup.js does chrome.sendMessage to background.js where data is received and afterwards (popup.html may be closed meanwhile) i just call in background.js:
newWin = window.open("about:blank", "Document Query", "width=800,height=500");
newWin.document.open();
newWin.document.write('<html><body><pre>' + documentJson + '</pre></body></html>');
// newWin.document.close();
so that works fine in Chrome extension but not in firefox. I read here (https://javascript.info/popup-windows) that for safety reasons firefox will only open with a "button click event". And if i move above code to popup.js, inside button-click-evenListener, it will open this way (but i dont have the data prepared yet, thats really not what i want)
So i tried everything i found but i dont get the chrome.tabs.executeScript running. Here is my code with comments:
popup.js
// working in firefox and chrome (popup.js)
const newWin = window.open("about:blank", "hello", "width=200,height=200");
newWin.document.write("Hello, world!");
// not working firefox: id's match, he enters function (newWindow) but document.write doing nothing (but no error in log)
// not working chrome: doesnt even enter "function (newWindow)""
chrome.windows.create({
type: 'popup',
url: "output.html"
}, function (newWindow) {
console.log(newWindow);
console.log(newWindow.id);
chrome.tabs.executeScript(newWindow.tabs[0].id, {
code: 'document.write("hello world");'
});
});
background.js
(created local output.html and gave several permissions in Manifest.json - tabs, activeTab, output.html, , about:blank)
// opening but executeScript not working in firefox: Unchecked lastError value: Error: The operation is insecure.
// opening but executeScript not working in chrome: Unchecked runtime.lastError: Cannot access contents of url "chrome-extension://plhphckppghaijagdmghdnjpilpdidkh/output.html". Extension manifest must request permission to access this host
chrome.tabs.create({
// type: 'popup',
url: "output.html"
}, function (newWindow) {
console.log(newWindow);
console.log(newWindow.id);
chrome.tabs.executeScript(newWindow.id, {
code: 'document.write("hello world");'
});
});
How can I get the data into the new window/popup from background.js - i can open an empty page from there, so it's only about getting executeScript() running
Thanks to #wOxxOm for pointing me to a data URI to transport the json document into the browser from background.js.
While searching for a javascript method to build a data URI i found this thread, with the suggestion to create a Blob :
https://stackoverflow.com/a/57243399/13292573
So my solution is this:
background.js
var documentJson = JSON.stringify(documents, null, 2)
let a = URL.createObjectURL(new Blob([documentJson]))
chrome.windows.create({
type: 'popup',
url: a
});

Message Passing Between Web Application and Chrome Extension - How to GET the message?

I am developing a chrome extension.
In my web application, there is a JavaScript embedded in a HTML page which tries to interact with my extension.
let extId = "extension id";
var extPort = chrome.runtime.connect(extId);
extPort.postMessage({from: "WebServer", fn: "greeting"});
In my extension - background script, event listener is set to handle the message.
chrome.runtime.onConnectExternal.addListener(function(port) {
port.onMessage.addListener(function(message, sender) {
if (message.from == "WebServer") {
if (message.fn == "greeting") {
console.log("Message from Web Server");
}
}
});
});
Besides, there is a function in the background script to use XHR to GET the HTML page. The function works fine.
Question: The message (which is shown in console log) can be got only when I visit the HTML page in browser but not when the page got by XHR. Why? (Sorry I am new to JavaScript and Chrome Extension)

Firefox extensions: How to run function on every video change on YouTube

I have an extension that injects js code into YouTube pages. I've used the following declaration in manifest.json:
"content_scripts": [
{
"matches": [
"*://*.youtube.com/*"
],
"js": [
"background.js"
]
}
]
I would like to define the function that prints the name of the video, number of likes and dislikes to console when I move to another video.
I've written this in background.js:
window.onhashchange = function () {
console.log(
document.querySelector("h1.title > yt-formatted-string:nth-child(1)").innerHTML, "\n",
document.querySelector("ytd-toggle-button-renderer.ytd-menu-renderer:nth-child(1) > a:nth-child(1) > yt-formatted-string:nth-child(2)").getAttribute("aria-label"), "\n",
document.querySelector("ytd-toggle-button-renderer.style-scope:nth-child(2) > a:nth-child(1) > yt-formatted-string:nth-child(2)").getAttribute("aria-label"), "\n",
)
}
But it runs only once. If I select new video from "Recommended" it does not work. I also tried .onload, .onunload, etc.
UPD: Now the only way I found is to use .setInterval.
Several possible solutions using the WebExtensions API, all require a background script that will send messages to your content script. Modify your manifest.json to include:
"background": {
"scripts": ["background.js"]
}
I've named the background script background.js here, that would collide with what you currently have - you might want to consider to rename your content script to something like contentscript.js, so you don't get the two confused.
In the contentscript.js you have the message listener
browser.runtime.onMessage.addListener(message => {
if (message.videoChanged) {
// do stuff
}
});
Using tabs.onUpdated
Needed permission in the manifest.json
"permissions": [
"tabs"
]
In the background.js
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (!changeInfo.url) {
// url didn't change
return;
}
const url = new URL(changeInfo.url);
if (!url.href.startsWith('https://www.youtube.com/watch?') ||
!url.searchParams.get('v')) {
// not a youtube video
return;
}
browser.tabs.sendMessage(tabId, {videoChanged: true});
});
This method will message the content script on first visits, while on-site navigation or auto play.
Using webNavigation.onHistoryStateUpdated
Needed permission in the manifest.json
"permissions": [
"webNavigation"
]
In the background.js
browser.webNavigation.onHistoryStateUpdated.addListener(history => {
const url = new URL(history.url);
if (!url.searchParams.get('v')) {
// not a video
return;
}
browser.tabs.sendMessage(history.tabId, {videoChanged: true});
},
{url: [{urlMatches: '^https://www.youtube.com/watch\?'}]}
);
This method messages the content script while on-site navigation or auto play.
Using webRequest.onBeforeRequest or webRequest.onCompleted
YouTube makes a xmlhttrequest when the video changes. You can see the requests by opening the Developer Tools (Ctrl+Shift+I), selecting the Network tab, select XHR, filter by watch? and then let YT switch to the next video. You'll see that two requests occur for the next video, one with a prefetch parameter in the URL shortly before the video changes, and one when the video actually changes without the prefetch parameter.
Needed permissions in the manifest.json
"permissions": [
"https://www.youtube.com/watch?*",
"webRequest"
]
In the background.js
browser.webRequest.onBeforeRequest.addListener(request => {
const url = new URL(request.url);
if (!url.searchParams.get('v') || url.searchParams.get('prefetch')) {
// not a video or it's prefetch
return;
}
browser.tabs.sendMessage(request.tabId, {videoChanged: true});
},
{urls: ['https://www.youtube.com/watch?*'], types: ['xmlhttprequest']}
);
onBeforeRequest might be a bit too fast and send the message to the content script before the new video actually finished loading. In this case you could just replace it with onCompleted.
This method messages the content script while on-site navigation or auto play.
Well the idea is to find a way to periodically check for URL change so the trick I use is to take advantage of the user's need to click on things like the play/pause button and of course on other videos to watch.
So inside your page onload event... (W being your iframe ID)
if(W.contentWindow.document.URL.indexOf('www.youtube.com/watch?v=')>-1){ // You may want to add some more permissable URL types here
W.contentWindow.document.body.addEventListener('click',function(e){ CheckForURLChange(W.contentWindow.document.title,W.contentWindow.document.location); },false);
}
And further down with the rest of your functions...
function CheckForURLChange(Title,URL){
// Your logic to test for URL change and take any required steps
if(StoredURL!==URL){}
}
It's not the best solution but it does work.

Can we download a webpage completely with chrome.downloads.download? (Google Chrome Extension)

I want to save a wabpage completely from my Google Chrome extension.
I added "downloads", "<all_urls>" permissions and confirmed that the following code save the Google page to google.html.
chrome.downloads.download(
{ url: "http://www.google.com",
filename: "google.html" },
function (x) { console.log(x); })
However, this code only saves the html file.
Stylesheets, scripts and images are not be saved.
I want to save the webpage completely, as if I save the page with the dialog, selecting Format: Webpage, Complete.
I looked into the document but I couldn't find a way.
So my question is: how can I download a webpage completely from an extension using the api(s) of Google Chrome?
The downloads API downloads a single resource only. If you want to save a complete web page, then you can first open the web page, then export it as MHTML using chrome.pageCapture.saveAsMHTML, create a blob:-URL for the exported Blob using URL.createObjectURL and finally save this URL using the chrome.downloads.download API.
The pageCapture API requires a valid tabId. For instance:
// Create new tab, wait until it is loaded and save the page
chrome.tabs.create({
url: 'http://example.com'
}, function(tab) {
chrome.tabs.onUpdated.addListener(function func(tabId, changeInfo) {
if (tabId == tab.id && changeInfo.status == 'complete') {
chrome.tabs.onUpdated.removeListener(func);
savePage(tabId);
}
});
});
function savePage(tabId) {
chrome.pageCapture.saveAsMHTML({
tabId: tabId
}, function(blob) {
var url = URL.createObjectURL(blob);
// Optional: chrome.tabs.remove(tabId); // to close the tab
chrome.downloads.download({
url: url,
filename: 'whatever.mhtml'
});
});
}
To try out, put the previous code in background.js,
add the permissions to manifest.json (as shown below) and reload the extension. Then example.com will be opened, and the web page will be saved as a self-contained MHTML file.
{
"name": "Save full web page",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"]
},
"permissions": [
"pageCapture",
"downloads"
]
}
No, it does not download for you all files: images, js, css etc.
You should use tools like HTTRACK.

Unknown error on chrome.tabs.executeScript

I need to run a script on an external page.
I'm trying to consume the Dropbox API (JavaScript and HTML only).
I'm using JsOAuth to work with OAuth.
Code
This application is a pair of type Packaged Apps to Google Chrome.
Authorise
//Request token
chrome.windows.create({url: url, type:"popup"}, function(win){
chrome.tabs.executeScript(win.id, { file: "contentScript.js" }, function(){
console.log("Callback executeScript!!");
});
});
url = Request token url
contentScript.js
$(document).ready(function() {
console.log("Script injected!!!");
})
Error in console
Error during tabs.executeScript: Unknown error.
chromeHidden.handleResponseextensions/schema_generated_bindings.js:94
openAuthoriseWindowscripts.js:297
g.fetchRequestTokenjsOAuth-1.3.3.min.js:1
g.init.request.q.onreadystatechange
Attempts
As the external page can not jQuery, an effort was to remove the reference to jQuery
contentScript.js
console.log("Script injected!!!");
Error in console
Error during tabs.executeScript: Unknown error.
chromeHidden.handleResponse
Another attempt was to inject the script via code:
//Request token
chrome.windows.create({url: url, type:"popup"}, function(win){
chrome.tabs.executeScript(win.id, { code: "console.log('Script injected!!')" }, function(){
console.log("Callback executeScript!!");
});
});
But the error was the same as above
I'm not sure whether you are wanting to inject the script into the tab opening the window, or the new tab you just opened. In any event, I made an effort to answer both questions below. First, please note that you should not attempt to load the script into the window object. The window can contain multiple tabs, and each tab has their own scripting environment. Inject your script into a tab of the newly opened window.
Outcome 1: Injecting the Script into the tab you Just Opened
The code below should load the script into all of the tabs of a window since win.tabs gives an array of tabs. For a newly opened window, there is usually only one tab.
chrome.windows.create({url: "https://google.com", type:"popup"}, function(win){
chrome.tabs.executeScript(win.id.tabs,
{ code: "console.log('new tab context');" });
});
Outcome 2: Injecting the Script into the tab opening the window
Record the id of the tab opening the new window, then inject the script on the callback
var openingTabId = ASSIGN_THE_TAB_ID;
chrome.windows.create({url: "https://google.com", type:"popup"}, function(win){
chrome.tabs.executeScript(openingTabId,
{ code: "console.log('opening tab context');" });
});
Notice that I used the code object to pass code without using a script file.

Categories

Resources