How to properly handle chrome extension updates from content scripts - javascript

In background page we're able to detect extension updates using chrome.runtime.onInstalled.addListener.
But after extension has been updated all content scripts can't connect to the background page. And we get an error: Error connecting to extension ....
It's possible to re-inject content scripts using chrome.tabs.executeScript... But what if we have a sensitive data that should be saved before an update and used after update? What could we do?
Also if we re-inject all content scripts we should properly tear down previous content scripts.
What is the proper way to handle extension updates from content scripts without losing the user data?

If you've established a communication through var port = chrome.runtime.connect(...) (as described on
https://developer.chrome.com/extensions/messaging#connect), it should be possible to listen to the runtime.Port.onDisconnect event:
tport.onDisconnect.addListener(function(msg) {...})
There you can react and, e.g. apply some sort of memoization, let's say via localStorage. But in general, I would suggest to keep content scripts as tiny as possible and perform all the data manipulations in the background, letting content only to collect/pass data and render some state, if needed.

Once Chrome extension update happens, the "orphaned" content script is cut off from the extension completely. The only way it can still communicate is through shared DOM. If you're talking about really sensitive data, this is not secure from the page. More on that later.
First off, you can delay an update. In your background script, add a handler for the chrome.runtime.onUpdateAvailable event. As long as the listener is there, you have a chance to do cleanup.
// Background script
chrome.runtime.onUpdateAvailable.addListener(function(details) {
// Do your work, call the callback when done
syncRemainingData(function() {
chrome.runtime.reload();
});
});
Second, suppose the worst happens and you are cut off. You can still communicate using DOM events:
// Content script
// Get ready for data
window.addEventListener("SendRemainingData", function(evt) {
processData(evt.detail);
}, false);
// Request data
var event = new CustomEvent("RequestRemainingData");
window.dispatchEvent(event);
// Be ready to send data if asked later
window.addEventListener("RequestRemainingData", function(evt) {
var event = new CustomEvent("SendRemainingData", {detail: data});
window.dispatchEvent(event);
}, false);
However, this communication channel is potentially eavesdropped on by the host page. And, as said previously, that eavesdropping is not something you can bypass.
Yet, you can have some out-of-band pre-shared data. Suppose that you generate a random key on first install and keep it in chrome.storage - this is not accessible by web pages by any means. Of course, once orphaned you can't read it, but you can at the moment of injection.
var PSK;
chrome.storage.local.get("preSharedKey", function(data) {
PSK = data.preSharedKey;
// ...
window.addEventListener("SendRemainingData", function(evt) {
processData(decrypt(evt.detail, PSK));
}, false);
// ...
window.addEventListener("RequestRemainingData", function(evt) {
var event = new CustomEvent("SendRemainingData", {detail: encrypt(data, PSK)});
window.dispatchEvent(event);
}, false);
});
This is of course proof-of-concept code. I doubt that you will need more than an onUpdateAvailable listener.

Related

Chrome extension to refresh a page every minute and run a (javascript) script every time it refreshes

What I want:
My propose is to check if new content was added in a page (that I do not own), so I was thinking to make a script that save the last content added in a cookie and refresh the page every minute: If the cookie doesn't match the last content added, that would mean there is new content and I would receive a notification.
Let's try with pseudocode:
main_file:
include: functions.js;
cookie last_content_added= get_first_paragraph();
//Refresh script
do (every_minute){
page_reload();
}
when.page.reload.complete {
run script_check_content
}
functions.js
script_check_content{
var content_check = get_first_paragraph();
if (content_check == cookie[last_content_added])
{
//do nothing
}
else{
//new content was added
play.notification.mp3
cookie[last_content.added] = get_first_paragraph();
}
}
Am I not thinking in an easier solution for what I'm looking for?
I'm new to chrome extensions, if you could separate the code in different files like it was a real extension, I would appreciate very much.
I recommend to use 'chrome.tabs.query', use this to get all tabs that have the specified properties or all tabs if no properties are specified and 'chrome.tabs.executeScript' to inject the javascript code into a page that calls 'window.location.reload(). to refresh the page.
Here's a sample code to get the current tab and reload it using chrome.tab methods:
chrome.tabs.query({active: false, currentWindow: true}, function (arrayOfTabs) {
var code = 'window.location.reload();';
chrome.tabs.executeScript(arrayOfTabs[0].id, {code: code});
});
Also, include 'onCompleted' listener to listen when it is completely loaded and initialized.
chrome.webNavigation.onCompleted.addListener(function callback).
Take a look at MutationObserver, it provides a way to react to changes in a DOM. You can provide a callback to react to DOM changes and don't need to use a timer.

Is there any onResourceTimeout equivalent in CasperJS?

I would like to abort running casper when it takes long to open a page. In PhantomJS, you can set a page setting called resourceTimeout. This property defines the timeout after which any resource requested will stop trying and proceed with other parts of the page.
As I checked with CasperJS documentations, this property of a page is not supported in CasperJS. I know that we can use of stepTimeout option to have a control on the time spent in each step but I don't want to set a global value to affect all the steps. I want to limit just the page open step of the code.
Is there any equivalent setting in CasperJS to do that? or Any other suggestions to stop pages which take long to load?
Thanks,
CasperJS is built on top of PhantomJS, so you can simply use the underlying page instance to register to this event by accessing casper.page.
The page instance is not created until casper.start() is called, so you need to register the event as soon as the page is created in the page.created event:
casper.on("page.created", function(){
this.page.onResourceTimeout = function(request){
// do whatever you need to do
};
});
casper.start(url, then).run();
It's unlikely that you would need it (multiple different event handlers), but you can also use CasperJS' event system:
casper.on("page.created", function(){
casper.page.onResourceTimeout = function(request){
casper.emit("resource.timeout", request);
};
});
casper.on("resource.timeout", function(request){
// do whatever you need to do
});
casper.start(url, then).run();

How do I provide an extra function to javascript code through an extension?

I want to write an extension that does the following:
Defines a custom function
Allows Javascript code loaded from the Internet to run such a function
The function should take as a parameter an event listener. Basically, something like:
newApiFunctionDefinedInExtension( function( responseHeaders ){
console.log("Headers arrived!", responseHeaders );
} ;
Then using chrome.webRequest, my extension (which made newApiFunctionDefinedInExtension available in the first place) will call the listener (in the locally loaded page) every time response headers are received from the network.
I am new to Chrome extensions and cannot find a way to make that happen. It would be great to know:
How to make a function defined in a module available to the loaded page's scope
How to make such an EventEmitter -- is there a constructor class I can extend?
My goal is simple: the loaded page should define a function, and that function should be called every time there is a network connection.
Every webRequest event receives information about a request, including the ID of the originating tab.
So, assuming that the tab exists note 1, you can use the following flow:
// background.js
chrome.webRequest.onHeadersReceived.addListener(function(details) {
if (details.tabId == -1)
return; // Not related to any tab
chrome.tabs.sendMessage(details.tabId, {
responseHeaders: details.responseHeaders
});
}, {
urls: ['*://*/*'], // e.g. all http(s) URLs. See match patterns docs
// types: ['image'] // for example, defaults to **all** request types
}, ['responseHeaders']);
Then, in a content script (declared in the manifest file), you take the message and pass it to the web page:
// contentscript.js
chrome.runtime.onMessage.addListener(function(message) {
// Assuming that all messages from the background are meant for the page:
document.dispatchEvent(new CustomEvent('my-extension-event', {
detail: message
}));
});
After doing that, your web page can just receive these events as follows:
document.addEventListener('my-extension-event', function(event) {
var message = event.detail;
if (message.responseHeaders) {
// Do something with response headers
}
});
If you want to put an abstraction on top (e.g. implementing a custom EventEmitter), then you need to inject a script in the main execution environment, and declare your custom API over there.
note 1. For simplicity, I assumed that the tab existed. In reality, that is never true for type "main_frame" (and "sub_frame"), because the page has not yet been rendered. If you want to get response headers for the top-level/frame documents, then you need to temporarily store the response headers in some data structure (e.g. a queue / dictionary) in the background page, and send the data to the content script whenever the script is ready.
This can be implemented by using chrome.runtime.sendMessage in the content script to send a message to the background page. Then, whenever a page has loaded and the content script is ready, the background page can use sendResponse to deliver any queued messages.

Javascript code isn't getting into my document ready listener. (forge iOS)

This is my entire javascript file for the home page of my app. Any ideas as to why it never gets into the document ready listener?
var photos;
forge.request.ajax({
url: "http://photos-url.com/pics.json",
dataType: "json",
success: function(data) {
photos = data;
},
error: function(error) {
forge.logging.info("Couldn't fetch pics!");
}
});
//logging output works here
$(function() {
//logging output doesn't work here
//I'm trying to append to the html here, but it never gets into this code
});
Cross-domain requests are prohibited for security reasons (same as in desktop browsers). You must configure environment to allow requests to your domain. Look at https://trigger.io/docs/current/api/modules/request.html for details.
json files are usually allowed to be read from cross domain and even if this one would't be, I still doubt it could affect ready event. I'm not using document ready function on my page as I was having simillar issues (it fires few minutes after page is loaded, or doesn't fire at all). You could try window.onload or document.onload events. I'd also try to find out how document.readyState behaves and eventually check it manually with interval or try to bind event listener to it.

Accessing every document that a user currently views from an extension

I'm writing an extension that checks every document a user views on certain data structures, does some back-end server calls and displays the results as a dialog.The problem is starting and continuing the sequence properly with event listeners. My actual idea is:
Load: function()
{
var Listener = function(){ Fabogore.Start();};
var ListenerTab = window.gBrowser.selectedTab;
ListenerTab.addEventListener("load",Listener,true);
}
(...)
ListenerTab.removeEventListener("load", Listener, true);
Fabogore.Load();
The Fabogore.Load function is first initialized when the browser gets opened. It works only once I get these data structures, but not afterwards. But theoretically the script should initialize a new listener, so maybe it's the selectedTab. I also tried listening to focus events.
If someone has got an alternative solution how to access a page a user is currently viewing I would feel comfortable as well.
The common approach is using a progress listener. If I understand correctly, you want to get a notification whenever a browser tab finished loading. So the important method in your progress listener would be onStateChange (it needs to have all the other methods as well however):
onStateChange: function(aWebProgress, aRequest, aFlag, aStatus)
{
if ((aFlag & Components.interfaces.nsIWebProgressListener.STATE_STOP) &&
(aFlag & Components.interfaces.nsIWebProgressListener.STATE_IS_WINDOW) &&
aWebProgress.DOMWindow == aWebProgress.DOMWindow.top)
{
// A window finished loading and it is the top-level frame in its tab
Fabogore.Start(aWebProgress.DOMWindow);
}
},
Ok, I found a way which works from the MDN documentation, and achieves that every document a user opens can be accessed by your extension. Accessing every document a user focuses is too much, I want the code to be executed only once. So I start with initializing the Exentsion, and Listen to DOMcontentloaded Event
window.addEventListener("load", function() { Fabogore.init(); }, false);
var Fabogore = {
init: function() {
var appcontent = document.getElementById("appcontent"); // browser
if(appcontent)
appcontent.addEventListener("DOMContentLoaded", Fabogore.onPageLoad, true);
},
This executes the code every Time a page is loaded. Now what's important is, that you execute your code with the new loaded page, and not with the old one. You can acces this one with the variable aEvent:
onPageLoad: function(aEvent)
{
var doc = aEvent.originalTarget;//Document which initiated the event
With the variable "doc" you can check data structures using XPCNativeWrapper etc. Thanks Wladimir for bringing me in the right direction, I suppose if you need a more sophisticated event listening choose his way with the progress listeners.

Categories

Resources