Accessing every document that a user currently views from an extension - javascript

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.

Related

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();

prevent ajax call if application cache is being updated

i am writing an offline web application.
i have a manifest files for my applicationCache. and i have handlers for appCache events.
i want to detect if the files being downloaded are being downloaded for the first time or being updated. because in case they are being updated, i would like prevent my application code from running, since i will refresh the browser after updating my app.
my specific problem here is that when the "checking" events gets fired, the applicationCache status is already "DOWNLOADING",
does anybody know how to get the applicationCache.status before any manifest or files gets downloaded?
thanks in advance for any answer
window.applicationCache.addEventListener('checking', function (event) {
console.log("Checking for updates.");
console.log("inside checking event, appcache status : %s", applicationCache.status);
}, false);
window.applicationCache.addEventListener('updateready', function (e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
// Browser downloaded a new version of manifest files
window.location.reload();
}
}, false);
window.applicationCache.addEventListener('downloading', function (event) {
appCommon.information("New version available", "Updating application files...", null, null);
}, false);
window.applicationCache.addEventListener('progress', function (event) {
$("#informationModal").find(".modal-body").html("Updating application files... " + event.loaded.toString() + " of " + event.total.toString());
}, false);
window.applicationCache.addEventListener('cached', function (event) {
$("#informationModal").find(".modal-body").html("Application up to date.");
setTimeout(function () { $("#informationModal").find(".close").click(); }, 1000);
}, false);
According to the specification the checking event is always the first event.
the user agent is checking for an update, or attempting to download the manifest for the first time. This is always the first event in the sequence.
I think it is a mistake to refresh the browser since that will not automatically fetch a new copy. If you look at the MDN guide for using the applicationCache you'll see that files are always loaded from the applicationCache first and then proceed to go through the cycle of events you attempted to avoid by refreshing.
Instead of refreshing you should simply make proper use of the applicationCache event life cycle in order to initialize and start your application.
i would like prevent my application code from running, since i will refresh the browser after updating my app.
You have a lot of control of when your application begins to run, if you really wanted to refresh the browser, just always start the app after updateready or noupdate events, but really instead of refreshing it seems like you should use window.applicationCache.swapCache instead.
I found this question and answer useful.

How to properly handle chrome extension updates from content scripts

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.

How to tell the difference between 'real' and 'virtual' onpopstate events

I'm building a tool that uses AJAX and pushState/replaceState on top of a non-javascript fallback (http://biologos.org/resources/find). Basically, it's a search tool that returns a list of real HTML links (clicking a link takes you out of the tool).
I am using onpopstate so the user can navigate through their query history created by pushState. This event also fires when navigating back from a real link (one NOT created with pushState but by actual browser navigation). I don't want it to fire here.
So here's my question: how can I tell the difference between a onpopstate event coming from a pushState history item, vs one that comes from real navigation?
I want to do something like this:
window.onpopstate = function(event){
if(event.realClick) return;
// otherwise do something
}
I've tried onpopstate handler - ajax back button but with no luck :(
Thanks in advance!
EDIT:
A problem here is the way different browsers handle the onpopstate event. Here's what seems to be happening:
Chrome
Fires onpopstate on both real and virtual events
Actually re-runs javascript (so setting loaded=false will actually test false)
The solution in the above link actually works!
Firefox
Only fires onpopstate on virtual events
Actually re-runs javascript (so setting loaded=false will actually test false)
For the linked solution to actually work, loaded needs to be set true on page load, which breaks Chrome!
Safari
Fires onpopstate on both real and virtual events
Seems to NOT re-run javascript before the event (so loaded will be true if previously set to be true!)
Hopefully I'm just missing something...
You may be able to use history.js. It should give you an API that behaves consistently across all major platforms (though it's possible that it does not address this specific issue; you'll have to try it to find out).
However, in my opinion, the best way to handle this (and other related issues too) is to design your application in such a way that these issues don't matter. Keep track of your application's state yourself, instead of relying exclusively on the state object in the history stack.
Keep track of what page your application is currently showing. Track it in a variable -- separate from window.location. When a navigation event (including popstate) arrives, compare your known current page to the requested next page. Start by figuring out whether or not a page change is actually required. If so, then render the requested page, and call pushState if necessary (only call pushState for "normal" navigation -- never in response to a popstate event).
The same code that handles popstate, should also handle your normal navigation. As far as your application is concerned, there should be no difference (except that normal nav includes a call to pushState, while popstate-driven nav does not).
Here's the basic idea in code (see the live example at jsBin)
// keep track of the current page.
var currentPage = null;
// This function will be called every time a navigation
// is requested, whether the navigation request is due to
// back/forward button, or whether it comes from calling
// the `goTo` function in response to a user's click...
// either way, this function will be called.
//
// The argument `pathToShow` will indicate the pathname of
// the page that is being requested. The var `currentPage`
// will contain the pathname of the currently visible page.
// `currentPage` will be `null` if we're coming in from
// some other site.
//
// Don't call `_renderPage(path)` directly. Instead call
// `goTo(path)` (eg. in response to the user clicking a link
// in your app).
//
function _renderPage(pathToShow) {
if (currentPage === pathToShow) {
// if we're already on the proper page, then do nothing.
// return false to indicate that no actual navigation
// happened.
//
return false;
}
// ...
// your data fetching and page-rendering
// logic goes here
// ...
console.log("renderPage");
console.log(" prev page : " + currentPage);
console.log(" next page : " + pathToShow);
// be sure to update `currentPage`
currentPage = pathToShow;
// return true to indicate that a real navigation
// happened, and should be stored in history stack
// (eg. via pushState - see `function goTo()` below).
return true;
}
// listen for popstate events, so we can handle
// fwd/back buttons...
//
window.addEventListener('popstate', function(evt) {
// ask the app to show the requested page
// this will be a no-op if we're already on the
// proper page.
_renderPage(window.location.pathname);
});
// call this function directly whenever you want to perform
// a navigation (eg. when the user clicks a link or button).
//
function goTo(path) {
// turn `path` into an absolute path, so it will compare
// with `window.location.pathname`. (you probably want
// something a bit more robust here... but this is just
// an example).
//
var basePath, absPath;
if (path[0] === '/') {
absPath = path;
} else {
basePath = window.location.pathname.split('/');
basePath.pop();
basePath = basePath.join('/');
absPath = basePath + '/' + path;
}
// now show that page, and push it onto the history stack.
var changedPages = _renderPage(absPath);
if (changedPages) {
// if renderPage says that a navigation happened, then
// store it on the history stack, so the back/fwd buttons
// will work.
history.pushState({}, document.title, absPath);
}
}
// whenever the javascript is executed (or "re-executed"),
// just render whatever page is indicated in the browser's
// address-bar at this time.
//
_renderPage(window.location.pathname);
If you check out the example on jsBin, you'll see that the _renderPage function is called every time the app requests a transition to a new page -- whether it's due to popstate (eg. back/fwd button), or it's due to calling goTo(page) (eg. a user action of some sort). It's even called when the page first loads.
Your logic, in the _renderPage function can use the value of currentPage to determine "where the request is coming from". If we're coming from an outside site then currentPage will be null, otherwise, it will contain the pathname of the currently visible page.

An event when document stops loading

I have a web page whose large parts are constructed on the front-end, in JavaScript. When the basic HTML is parsed and the DOM tree is constructed, the "ready" event triggers. At this event, a JavaScript code launches that continues with the construction of the web page.
$().ready(function() {
$(document.body).html('<img src = "img.png" />');
$.ajax({
url: 'text.txt'
});
});
As you can see in this tiny example,
new elements are added to DOM
more stuff, like images is being loaded
ajax requests are being sent
Only when all of this finishes, when the browser's loading progress bar disappears for the first time, I consider my document to be actually ready. Is there a reasonable way to handle this event?
Note that this:
$().ready(function() {
$(document.body).html('<img src = "img.png" />');
$.ajax({
url: 'text.txt'
});
alert('Document constructed!');
});
does not suffice, as HTTP requests are asynchronous. Maybe attaching handlers to every single request and then checking if all have finished is one way to do what I want, but I'm looking for something more elegant.
Appendix:
A more formal definition of the event I'm looking for:
The event when both the document is ready and the page's dynamic content finishes loading / executing for the first time, unless the user triggers another events with mouse moving, clicking etc.
As described here, you can sign up for load events for individual images when they finally load. However, as also described there, that may not be reliable across browsers or if the image is already in the browser's cache. So I think at least part of that is not 100% trustworthy. With that said, you can easily hook to a bunch of different asynchronous events and then only react when all of them have completed. For example:
$(document.body).html('<img src="img.png" id="porcupine"/>');
var ajaxPromise1 = $.get(...);
var ajaxPromise2 = $.get(...);
var imgLoadDeferred1 = $.Deferred();
$("#porcupine").load(
function() {
imgLoadDeferred1.resolve();
}
);
$.when(ajaxPromise1, ajaxPromise2, imgLoadDeferred1).done(
function () {
console.log("Everything's done.");
}
);
If you want to allow for the fact that the image load may never generate an event, you could also set a timer which would go off eventually and try to resolve the imgLoadDeferred1 if it is not already resolved. That way you don't get stuck waiting for an event that never happens.
You are looking for .ajaxStop().
Used like so:
$("#loading").ajaxStop(function(){
$(this).hide();
});
It does't really matter what DOM element you attach to unless you want to use this as is done in the above example.
Source: http://api.jquery.com/ajaxStop/
Add global counter to ajax calls in way that when ajax request is ready it calls addToReadyCounter(); function that will +1 to glogal var readyCount;.
Within same function check your readyCount and trigger event handler
var readyCount = 0, ajaxRequestCount = 5;
function addToReadyCount() {
++readyCount;
if (readyCount >= ajaxRequestCount) {
documentReadyEvent();
}
}
$.ajax(
...
success: function(data){
addToReadyCount();
}
...);
If you are only using success: ... for addToReaadyCount documentReadyEvent() only triggers if all call succeed, use other .ajax() event handlers to increment counter in case of error.

Categories

Resources