Detect if stylesheets fail to load (not working on Firefox) - javascript

I have cobbled together a script from different sources, that helps me to put some fallbacks in place when a stylesheet fails to load (specifically for me, Pictos server is not always reliable).
This works great, but fails on Firefox for some reason, it doesn't process anything within the if statement. I've tried running it through JSHint and nothing serious is coming up.
Any ideas?
$(document).ready(function(){
$.each(document.styleSheets, function(i,sheet){
if(sheet.href==='http://get.pictos.cc/fonts/357/9') {
var rules = sheet.rules ? sheet.rules : sheet.cssRules; // Assign the stylesheet rules to a variable for testing
$('body').addClass('pictos-working');
$('.pictos-fallback').hide(); // Hide fallbacks
// If the stylesheet fails to load...
if (rules.length === 0) {
$('.pictos').hide(); // Hide Pictos tags so we don't get random letters
$('body').removeClass('pictos-working'); // Remove 'working' class
$('.pictos-fallback').show(); // Show fallbacks
}
}
});
});​

Your style sheet detection method is not reliable. cssRules is null when the style sheet originates from a different domain, because of the Same origin policy.
Instead of detecting the existence of a css rule through the cssRules object, check if a rule from the style sheet is being applied:
if ($('selector').css('property') === 'expectedvalue') {
// Loaded
} else {
// Not loaded.
}

Just some suggestions for improvement that might help:
why a nested if? since you only have two scenarios, use if then else..
are you sure that cssRules is zero when the file doesn't load? maybe there are some headers or metadata that load.. Where did you find this property?
something I just found: http://www.quirksmode.org/dom/w3c_css.html

Related

In newer versions of Firefox, is it still possible to override a web page's JS function?

I am writing an extension to override a web page's JS function, and started from this question, but the answer does not appear to work in Firefox 42 on Linux.
Next, I tried to use exportFunction as described in the documentation, but that also silently failed.
Inside package.json, I have added the following sesction.
"permissions": {
"unsafe-content-script": true
}
Here is my index.js file.
var self = require('sdk/self');
require("sdk/tabs").on("ready", fixGoogle);
function fixGoogle(tab) {
if (tab.url.indexOf("google.com") > -1) {
tab.attach({
contentScriptFile: self.data.url("google-script.js")
});
}
}
Here is my current data/google-script.js.
unsafeWindow.rwt=function(){};
Note that manually typing in rwt=function(){}; to the browser's console achieves the desired effect, as does using a bookmarklet (which requires clicking) but I am writing the plugin to get this automatically every time I use Google.
Is it possible to override the rwt page function using a Firefox extension? If so, what is the correct API to use?
read the documentation you've linked to, specifically the chapter titled Expose functions to page scripts - which links to exportFunction
function blah() {}
exportFunction(blah, unsafeWindow, {defineAs: 'rwt'});
It turns out that the issue is that the redefinition of the function rwt is racing against the original definition and winning. The original runs after and overrides the function I defined, thereby making it look like my redefinition had silently failed.
Once I realized that this was the problem, the easiest hack around it was to add a timeout to the redefinition inside data/google-script.js.
setTimeout(function() {
unsafeWindow.rwt=function(){};
}, 1000);
Thus, the orignal answer is still correct but simply failed to address the race condition.
Even though content scripts share the DOM, they are otherwise isolated from page scripts. As you correctly surmised, one can use unsafeWindow in Firefox to bypass this isolation.
Personally, I don't like the name of unsafeWindow for some reason ;)
Therefore I propose another way to do this: make use of the thing that's shared between these scopes, i. e. DOM.
You can create a page script from a content script:
var script = 'rwt=function()();';
document.addEventListener('DOMContentLoaded', function() {
var scriptEl = document.createElement('script');
scriptEl.textContent = script;
document.head.appendChild(scriptEl);
});
The benefit of this approach is that you can use it in environments without unsafeWindow, e. g. chrome extensions.

Adding StyleSheets to Firefox Bootstrapped Addon

Accordion to Using the Stylesheet Service
Above mentioned document also states:
loadAndRegisterSheet fails if CSS contains #id. '#' must be percent-encoded, details see bug 659650.
The bag report was made on 2011-05-25. Is it still a bug or has it been resolved?
There is another way of adding CSS but that is per window and I prefer to get this one sorted.
Update:
Here is the content of the style-sheet
#rpnethelper-separator2:last-child { display: none; }
#rpnethelper-menuitem {
list-style-image: url('icon16.png');
}
This is the actual code (plus added console calls)
register: function(css) {
let sss = Components.classes['#mozilla.org/content/style-sheet-service;1']
.getService(Components.interfaces.nsIStyleSheetService);
let cssURI = Services.io.newURI(css, null, null);
sss.loadAndRegisterSheet(cssURI, sss.USER_SHEET);
},
I tried it with try{} catch{} and I dont get any errors.
How/where can USER_SHEET be viewed?
For now, I am going to use an inline style (which doesn't support the pseudo classes) but I would still like to resolve this issue.
Final Update:
For some reason, the code that wasn't working with USER_SHEET, works fine with AUTHOR_SHEET
Funny thing is, after all that, I decided it is not worth the extra processing just for one pseudo class, so I opted for the (simple) inline style
You forgot to specify the correct namespace. Add the following as the first line to your sheet.
#namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
The docs you already linked state:
Stylesheets added using this service get applied to both chrome and content documents. Remember to declare the correct namespace if you want to apply stylesheets to XUL documents.
Also, if you're targeting Firefox 18 and later (and really, supporting earlier versions has no merit as those are unsupported and contain known security vulnerabilities, so users shouldn't be using them), you should consider using nsIDOMWindowUtils.loadSheet instead. This will only load the sheet into the actual window, instead of applying it globally to all windows incl. websites.
if (window instanceof Ci.nsIInterfaceRequestor) {
let winUtils = window.getInterface(Ci.nsIDOMWindowUtils);
let uri = Services.io.newURI(..., null, null);
winUtils.loadSheet(uri, Ci.nsIDOMWindowUtils.AUTHOR_SHEET);
// Remove with winUtils.removeSheet() again on shutdown
}
Edit You'll want to use AUTHOR_SHEET most of the time (be it with the style sheet service or window utils). This is more equivalent to xml-stylesheet in overlays.
loadAndRegisterSheet fails if CSS contains #id. '#' must be percent-encoded, details see bug 659650.
The bag report was made on 2011-05-25. Is it still a bug or has it been resolved?
That bug report only applies data: URIs. Also, that bug report is invalid, # has special meaning in URIs and therefore you'll have to encode it when it is part of the URI directly (as is the case with data: URIs). If you're registering a regular chrome:/resource:/file:/http: URI, you don't need special encoding.

How to only wait for document.readyState in IE and fire instantly for all other browsers?

I have written a CSS and Javascript lazyloader to dynamically load resources for seperate pagelets (in the way that Facebook renders a page with it's BigPipe technology).
In short an HTML frame is rendered first, then separate parts of the page are all generated asynchronously by the server. When each pagelet arrives the pagelets css is loaded first, then its innerHTML is set, then finally we load any required javascript for this pagelet and initialise it.
Everything works perfectly and perceived load time is pretty much instantaneous for any given page.
However in IE, I occasional I get Method does not support method or property when initialising the scripts.
I have solved this by checking for document.readyState before loading the scripts.
Now this isn't a huge issue but it adds on average 170ms to a pageload in chrome or firefox. Which is not needed.
function loadScripts(init){
// ensure document readystate is complete before loading scripts
if( doc.readyState !== 'complete'){
setTimeout(function(){
loadScripts(init);
}, 1 );
}
else{
complete++;
if(complete == instance.length){
var scripts = checkJS(javascript);
if(scripts.length) {
LazyLoad.js(scripts, function(){
runPageletScript();
for (var i = 0; i < scripts.length; ++i) {
TC.loadedJS.push(scripts[i]);
}
});
}
else{
runPageletScript();
}
}
}
}
What I am looking for is a modification to this script which will only implement the 'wait' in IE, if it is any other browser it will just fire straight away. I cannot use a jQuery utility like $.Browser and need it to be the tiniest possible method. I hate to use any form of browser detection but it appears as though its my only solution. That said if anyone can come up with another way, that would be fantastic.
Any suggestions would be gratefully received.
You could use JScript conditional compilation, which is only available in IE browsers (up to IE10).
Because it's a comment, it's best to place it inside new Function as minifiers might remove it, changing your code. Though in general you should avoid using new Function, in this case there's not really any other way to prevent minifiers from removing it.
Example:
var isIE = !(new Function('return 1//#cc_on &0')());
However, it seems that your main issue is that the DOM hasn't loaded yet -- make sure that it has loaded before running any loader using the DOMContentLoaded event (IE9+):
document.addEventListener('DOMContentLoaded', function () {
// perform logic here
});
Here is just another solution as the solution from Qantas might not always work. For instance on UMTS connections it could happen that providers remove comments to save bandwith (maybe they preserve conditional comments):
if(navigator.appName == 'Microsoft Internet Explorer'
&& doc.readyState !== 'complete'){
...
}

Using multiple jQuery plugins on multiple pages?

I have site which is using a few JavaScript/jQuery plugins, I then have a default.js file which uses the plugin functionality for each of the different pages.
E.g. some plugins I have include:
a custom scrollbar plugin
a slideshow plugin
a cookie plugin
etc.
Then in the default.js file, I'll do something as follows (pseudocode):
var scrolling = findScrollbarDiv;
scrolling.ScrollFunction({ options });
(and then the same for the slideshow + other plugins I have)
However, if there is a plage where findScrollbarDiv returns null, I get an error something like the following:
ScrollFunction is not a function
This is not because the elements are returning null, but because I haven't included the plugin file for this page. My reasoning behind this is that I don't want to include every file on every page (even if it's not needed) as this could cause unnecessary HTTP requests (especially on the homepage, which only needs one plugin)
This error in turn messes up the rest of the JavaScript.
What is the best way to overcome this? Should I just include every plugin file on every page regardless of whether it is needed or not? Or is there some JavaScript like the following that I can use:
var scrolling = findScrollbarDiv;
if(scrolling != null) {
scrolling.ScrollFunction({ options });
}
(this feels a bit clunky to me, but if this is the best solution let me know)
Or is using one default js file to launch all plugins a bad idea?
If you're using the same header file and you don't mind having the file included, you can always check if the element exists before attaching an event to it.
Heres an example:
if(jQuery('#someElement').length > 0){
jQuery('#someElement').ScrollFunction({ options });
}
Either that, or have a JavaScript file for each function.
So you'd have one for the gallery and one for the cookies etc.. And include only the ones necessary for each page.
You can just do it as a conditional statement and avoid the clunky if:
scrolling && scrolling.ScrollFunction({ options });
This works great for any method you want to call and want to check if the parent object exists before you do.
You can go further (if you need to) and check if the method itself exists before execution:
scrolling && scrolling.ScrollFunction && scrolling.ScrollFunction({ options });
if scrolling is a collection, just check it's length (if length == 0 the statement will fail):
scrolling.length && scrolling.ScrollFunction({ options });

The CSS Rule object

According this site externalcss3 The CSS Rule object of the styleSheet object lets you access the individual rules of a stylesheet.
So If I try to run this piece of code(*) on stackoverflow page, using the javascript console, I expect to see some CSS Rules written in this page http://cdn.sstatic.net/stackoverflow/all.css?v=04e0337352b3.
(*)
var mysheet=document.styleSheets[0]
var myrules=mysheet.cssRules? mysheet.cssRules: mysheet.rules
for (i=0; i<myrules.length; i++) {
console.log(myrules[i].selectorText.toLowerCase());
}
Actually the result of console.log is something different from what I would expect:
object[type="application/x-shockwave-flash"], object[type="application/futuresplash"], object[data*=".swf"], object[src*=".swf"], embed[type="application/x-shockwave-flash"], embed[type="application/futuresplash"], embed[src*=".swf"]
What have I Missed? Sorry for my ignorance and your time.
It seems you can only access the rules in the CSS file if they are from the same domain.
Read this thread: Reading the rules of a cross domain CSS file in DOM

Categories

Resources