Firefox bootstrapped extension, race conditions when importing in top-level? - javascript

I'm trying to get myself familiar with Firefox bootstrapped add-ons. Consider following example:
// bootstrap.js
'use strict'
function alert(message) {
var prompts = Components.classes["#mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
prompts.alert(null, "from my extension", message);
}
try {
Components.utils.import('chrome://my-ext/content/foo.jsm');
alert('ok');
} catch(e) {
alert(e);
}
And chrome://my-ext/content/foo.jsm being just this.EXPORTED_SYMBOLS = [];.
The issue with an above code sample is that it does not work each time. It may fail with NS_ERROR_FILE_NOT_FOUND instead of importing, or may say OK — despite the fact later, when browser starts, I can access foo.jsm through location bar.
Does this mean I should not import anything in the top level, because chrome registration might not be done yet, or problem is in something else?

N.B. The following is my experience and may not be definite
bootstrap.js of bootstrapped addon run when browser starts and BEFORE any WINDOW or DOM is created.
bootstrap.js also has a order and specific format for execution.
Is above example the way your bootstrap.js is?
You can import Firefox modules anywhere (although I would always do so on top).
It is best to execute your Components.utils.import('chrome://my-ext/content/foo.jsm'); within function startup(data, reason) { ... }
Additional Notes/Suggestions:
Generally, I would assign var prompts outside a function so that it is not reassigned every time a function is run.
I would also use Services.jsm for ease of use (but it make no difference at all) eg:
Components.utils.import('resource://gre/modules/Services.jsm');
// then anywhere in the code
Services.prompt.alert(null, title, text);
Habitually, I do all Firefox built-in modules imports Components.utils.import() on top of the JS/JSM
Firefox built-in modules (not addon's modules) are cached by Firefox and there is no need to Components.utils.unload() them.

Related

Why does my feature support test not run before syntax error occurs?

I'm doing Javascript feature support testing on my site, but I'm running into an issue while testing on IE11 that is causing a syntax error (because of a default parameter) prior to executing my feature test.
My application builds a script tag to inject into the layout view. That script tag is built using a configuration that defines all of the JS dependencies. I've ensured that the resulting JS file that's delivered to the browser has my feature detection at the top of the combined script (right after jQuery). But what seems to be happening is that some function that's defined later on in the script is running (or parsed?) prior to running my feature detection expression.
For more clarity, this is an example of what my script tag looks like:
<script type="text/javascript" src="/asset/jscript?load=feature-detection,global,login&version=1820523616"></script>
Which results in a script file that first contains jQuery, then my feature-detection.js, then everything else. This is the line in one of the JS files after feature detection that causes the syntax error:
processMessages: function(problem, container, useMessage = true) {...}
EDIT:
To be clear, I'm unsure why the syntax error would occur before the feature detection logic, even though my feature detection occurs far earlier in the code. This is what my combined script looks like:
/* jQuery v3.2.0 ... */
// This is a placeholder for jQuery code
// Test browser support for 'for...of' statement.
// If this feature is lacking, ES6 support is lacking from the browser
try {
eval("for (var i of []);");
} catch(ex) {
// Show 'Unsupported Browser' banner
alert('GET BETTER BROWSER');
}
// The rest of my JS files, which would contain several non-supported features
processMessages: function(problem, container, useMessage = true) {...}
The "alert" is never triggered in IE11, instead I get "Expected ')'" pointing to the default parameter function
I'm pretty sure you can't use default parameter values in JS like that, that's probably what's breaking in IE.
You'll have to default it like this instead:
processMessages: function(problem, container, useMessage) {
if (typeof useMessage === 'undefined') useMessage = true;
}
Javascript is first compiled then executed, it's a multistage process.
Your syntax error will occur at compilation cycle(or interpret time) which is why you are seeing it before your feature detection executes.
I am not a deep js expert so my terminology might be wrong but that's the idea behind your issue

Is it possible to make and run changes in javascript files using chrome developer tools or firebug without refreshing?

I am working an issue and problem is code is huge and deployed on some remote location and takes a lot of time to go through this process. Now, I know that in chrome developer tools we can open javascript files and modify/save them but problem is, changes are not reflected in application. for example, hello.js has something like this,
sayHello : function() {
// some existing code here.
}
now, developer tools allows to modify this file to look like,
sayHello : function() {
// some existing code here.
// additional code added at runtime.
}
but problem I am facing is, this additional code is not reflected when I execute
sayHello()
function again.
Note: I am not trying to load any new script here.
It's hard to say without some more input on your action, but could be that you are changing the source code (in the browser cache) and you expect the object already in memory to change as well? In case put a break point before the object is instantiated and change the code at that moment and see what happens.

Angular Universal and browser feature checks

When developing a web app with jQuery or normal JavaScript, it is commonplace to check for feature availability first. So for example, if I want to use the document.oncopy event, I should first have something like this to ensure my code doesn't break for lesser browsers:
if ("oncopy" in document) {
// Feature is available
}
I'm a bit puzzled about how this would work in Angular2. I could still use the same if I expect to only run in the browser, but I'm specifically told to leave the DOM alone if I want to use Angular Universal and depend on templates or the DomRenderer instead. This allows the page to be pre-rendered on the server and provides a truly impressive performance gain.
But suppose I want a specific div to be invisible if the document.oncopy is unavailable. My understanding is that this is not recommended:
<div *ngIf="hasFeature()">...</div>
and
hasFeature() {
return 'oncopy' in document;
}
because then I'm still manipulating the DOM. Note that my example is about the document.oncopy but I could choose any feature whatsoever that doesn't have universal support.
I tested this using Chris Nwamba's tutorial on Scotch and added the following to the end of his Home template:
<div *ngIf="hasFeature()">Feature is supported</div>
<div *ngIf="!hasFeature()">Feature is NOT supported</div>
Update: Interestingly, it gave different results on different browsers. On Chrome 55, it executed as it would normally and showed the "Feature is supported" message. On IE11, I received the "not supported" message. In both instances the server log shows a EXCEPTION: document is not defined message, but the page still seems perfectly okay.
So what is the correct way to check for browser features if I want to use Angular Universal?
Update:
I also toyed around with using a field in the template and assigning that field from one of the life cycle hooks. ngAfterContentInit seemed like a fine candidate, but also causes an error on the server. It still runs fine in the browser with no weird effects (that I have noticed so far).
There are two ways to approach this:
Do the check only once the server is done rendering and the client is completely initialised (including the replay of user events done by preboot.js).
Return a reasonable default when the page is running on the server and perform the actual check only in the browser.
I started looking at the first option, but none of the Angular2 life cycle events will help with this. In fact, you can clearly see them all executing on the server and only then on the client.
I then started looking for something usable in preboot.js but quickly realised it was more complex than it needed to be.
So onto option 2 I went. It turns out checking for the browser is as easy as importing and checking isBrowser.
import { isBrowser } from "angular2-universal";
#Component({
// All the usual stuff
})
export class MyComponent {
// ...
hasFeature(): boolean {
return isBrowser && 'oncopy' in document;
}
// ...
}
And then use the template as I showed in the question.
To check if you're running on the server, import and use isNode in exactly the same way. There doesn't seem to be an obvious way to distinguish between Node and ASP.NET Core, but perhaps it's best not to write too much code that specific to platform.

Declaring class differently for different Dojo versions without duplicating code?

I have an iWidget designed for IBM Connections, and my javascript code depends on Dojo (which is included by default in Connections).
It currently works in Connections 4.0 and 4.5, but is broken in Connections 5.0 (released last week), as Dojo has been updated to v1.9 and complains about my use of dojo.require.
These messages appear in the browser console when my widget tries to load on Connections 5.0:
Avoid calling dojo.require() to load classes at runtime, use net.jazz.ajax.xdloader.load_async() instead. Function '(anonymous)' required class 'dojox.atom.io.model'.
Avoid calling dojo.require() to load classes at runtime, use net.jazz.ajax.xdloader.load_async() instead. Function '(anonymous)' required class 'dojox.atom.io.Connection'.
I want to make conditional code that uses different ways of defining my widget class and requiring other Dojo modules depending on the Dojo version.
The widget javascript currently looks like this:
dojo.provide('insightCommunityWidgetClass');
dojo.require('dojox.atom.io.model');
dojo.require('dojox.atom.io.Connection');
dojo.declare('insightCommunityWidgetClass',null,{
// Class fields and methods. Currently 680 lines uncompressed.
});
I haven't yet created a version that works with Dojo 1.9 / Connections 5.0, but I think it would look something like this (and I'll have to make my javascript file name match the desired class name):
define(['dojo/_base/declare','dojox.atom.io.model','dojox.atom.io.Connection'], function(declare){
return declare(null, {
// Class fields and methods.
});
});
How can I have both of these in one file and choose between them without duplicating the class body?
Update:
I've attempted some conditional code, checking (define && define.amd) as suggested by Dimitri, tested this on Connections 4.0 and 4.5, and am getting very weird behaviour.
Temporarily ignoring any attempt to not duplicate my class, here's some conditional code which I've used exactly as shown, with a severely reduced widget class:
if (define && define.amd) {
console.log('Declaring insightWidgetClass with AMD (new method).');
define(['dojo/_base/declare','dojox/atom/io/model','dojox/atom/io/Connection'],
function(declare){
return declare(null,{
SVC_INV: 1,
onLoad: function() {
console.log('insightWidgetClass onLoad.');
}
});
}
);
} else {
console.log('Declaring insightWidgetClass with dojo.declare (old method).');
dojo.provide('insightWidgetClass');
dojo.require('dojox.atom.io.model');
dojo.require('dojox.atom.io.Connection');
dojo.declare('insightWidgetClass',null,{
SVC_INV: 1,
onLoad: function() {
console.log('insightWidgetClass onLoad.');
}
});
}
This seems not to run at all. None of my console.log messages appear in the browser console.
If I comment out the conditionals and make it so the only active code is the block after else, it runs. I get the "declaring ... (old method)" and the "insightWidgetClass onLoad" console messages.
I thought maybe enclosing the Dojo provide, require and declare calls in any kind of block might cause a problem, so I tested just putting the working code in an if (true) { block, and it still works.
The last things I've tried at this point are adding this one line before everything else, to see what define is:
console.log('dojo define',define);
... which breaks it. No console messages at all from my code.
Then I remove the define argument from that new line, so it's only sending a string to the console, and the code works again.
It seems like any mention of a define identifier silently stops the rest of the code from running.
There are no errors or warnings in the console indicating a problem. All I can say to that is: WTF?!
Now back to checking dojo.version instead.
Normally both should still work, dojo.provide() and dojo.require() are deprecated, but not entirely removed. Just make sure that your loading dojo in synchronous mode.
Besides that, the AMD way of coding is introduced in Dojo 1.7, which means that it should be supported on IBM Connections 4.5 as well (though I don't know about IBM Connections 4).
But if you really want to use both code bases, you can simply refer to the same object in stead of duplicating it, for example:
var myModule = {
// Class fields and methods.
};
if (dojo.version.major == 1 && dojo.version.minor == 9) {
define(['dojo/_base/declare','dojox.atom.io.model','dojox.atom.io.Connection'], function(declare){
return declare(null, myModule);
});
} else {
dojo.provide('insightCommunityWidgetClass');
dojo.require('dojox.atom.io.model');
dojo.require('dojox.atom.io.Connection');
dojo.declare('insightCommunityWidgetClass',null, myModule);
}
Or you could use the following check:
if (typeof define === 'function' && define.amd) {
// AMD style code
} else {
// Non-AMD style code
}
This is the approach most cross-loader libraries use. Libraries that work both on AMD loaders (Dojo, Require.js), but also on Node.js or simply by using global namespacing use a similar piece of code to determine how they load their module.
This is not your code, it should work as it is. We recently faced the same problem and identified the cause.
Connections 5 is using an AMD version of the Jazz framework which provides its own dojo loader. This framework is used to aggregate the needed dojo modules into a single JS file, which limits the number of requests to the server. Unfortunately, this loader no longer handles synchronous modules loading. It fails with the warning you reported when dojo.require() requests a module that is not yet loaded by the aggregator. If the module was already loaded, because it was part of the Jazz aggregated file, then it works. It explains why you can dojo.require() some modules, but not all of them.
-> A workaround is to deploy a server side OSGi bundle to get the modules you need part of the aggregated JS file. There is a documented extension point for this. This can unblock you while enhancing the performance of your page.
Now, we opened a PMR to IBM support. The development team is working on a resolution. We hope that they will be able to deliver a fix soon.
We reported the following issues:
dojo.require()
dojo.requireLocalization()
dojo.registerModulePath()/require({paths:})
If you think about something else, please let me know.

using requirejs within a firefox xul extension

I would like to use requirejs to manage my code within a firefox xul plugin, and I can't get it to find my modules.
I know that xul doesn't play nice with the data-main attribute, so I have my main.js script as a second script:
<script src="chrome://myPackage/content/require.js" type="application/x-javascript"></script>
<script src="chrome://myPackage/content/main.js" type="application/x-javascript"></script>
This successfully calls the script, and the require function is available within main.js, but when I run
require(['lib1'], function(lib1){
alert(lib1.val1);
})
the alert never gets popped (lib1 is in the same directory as main.js).
I have tried this within and without setting the baseUrl as
require.config({
baseUrl: "chrome://myPackage/content/"
})
and it does not work either way.
Does anyone know how I can get require.js to look in the right place for my modules?
Addendum **
I added an error handling function and the error code returned is
http://requirejs.org/docs/errors.html#timeout
I have loaded the test module into a normal web page successfully. This seems to confirm that the issue is path configuration (it also takes the 15 second timeout before failing)
Firebug seems to have a working requirejs version. But more importantly, they have a far better mini-require.js that will not pollute the shared global scope when used in overlays (if used correctly :p)
https://github.com/firebug/firebug/blob/master/extension/modules/require.js
https://github.com/firebug/firebug/blob/master/extension/modules/mini-require.js
I suggest you have a look at these implementations and also the code using it.
Proactive warning:
Please note, that if your add-on uses code that defines lots of new properties on the scope in overlays (window) either by defining global functions or variables or implicitly declaring variables within functions, then this may interfere with other code running in the same scope (the browser code itself and other add-ons). Besides, should you want to submit your add-on to addons.mozilla.org, then a reviewer might not give it public status if your add-on "pollutes" the global scope/namespace in the main overlay.

Categories

Resources