Testing functionality based on HTML5 DOM-methods - javascript

I'm wondering how to test functionality based on HTML5 DOM-methods. Like the new dialog-API for example.
Consider the following function;
function show(dialog)
{
if(typeof(dialog.showModal) !== "function")
throw new Error("Update your browser");
dialog.showModal();
}
I want to make sure that
an error is thrown when the method is not available
dialog.showModal is called if available
I'm currently running karma + jasmine with the firefox and chrome launchers but none of them seems to support the dialog-API.
Can I shim this in some kind of way? Can I create a function on all dialog DOM nodes?
I could do the following
var modal = document.createElement("dialog");
Object.defineProperty(modal, "showModal", {
value: function()
{
console.log("hello world!");
}
});
however, that isn't carried over to other instances of a dialog.
This is a very simplified example of what I'm trying to achieve.

This is what polyfills are: an attempt to shim support for newer specs into browsers that don't support them. What you're talking about is essentially creating a polyfill for the <dialog> element.
Some older ECMAScript 6 specs specified a function to test if something existed and was a function, but this seems to have been scrapped in more recent specs. This is a "polyfill" for that (in whatever way that term can apply to a scrapped spec), both as an example of polyfilling and as a tool we'll use to explore the concept further:
(function () {
"use strict";
var ops = Object.prototype.toString;
Function.isFunction = function(o) {
return ops.call(o) === "[object Function]";
};
}());
Note that we have to cache the original Object.prototype.toString: most JavaScript runtimes override it in Function.prototype.toString, and we really need to be calling the original. This should give you an idea of how fiddly and error-prone polyfilling can be: even in a case like this where the code is short and simple, nuances like using Object.prototype.toString.call(o) instead of o.toString() are still critical.
Anyway, once you've got this function, it becomes much easier to test for things like modal support:
(function () {
"use strict";
var test = document.createElement("dialog");
if (!Function.isFunction(test.showModal)) {
throw new Error("HTMLDialogElement.showModal() is not supported");
};
}());
To modify all dialog element instances, you would extend the prototype that most immediately pertains to that object. In the case of the HTML dialog spec, that's HTMLDialogElement. You could do something like this:
(function () {
"use strict";
var HTMLDialogElement = HTMLDialogElement; //undefined if unsupported
if (!Function.isFunction(HTMLDialogElement)) {
// No native dialog support
throw new Error("No support for <dialog> elements");
}
if (!Function.isFunction(HTMLDialogElement.prototype.showModal)) {
// The interface exists, but showModal isn't supported
HTMLDialogElement.prototype.showModal = function () {
console.log("Hello world!");
};
}
}());
If there's no support for the prototype either, then things get trickier. An unknown element doesn't get a distinctive prototype; it's just an HTMLElement (like all other elements, eventually). So to make a function work for <dialog> elements in an unsupported environment, you'd have to add it to all elements. Still, you can sort of make it work:
(function () {
"use strict";
HTMLElement.prototype.showModal = function () {
if (this.tagName !== "DIALOG") {
throw new TypeError("showModal() called on non-dialog");
}
console.log("Hello world!");
};
}());
Most polyfills work based on compromises like this: they don't support the spec perfectly, but do well enough to support common use cases.
Be warned: polyfilling extends native objects, and this is commonly considered bad practice. Most people who call it bad practice will make an exception for polyfills, but the rule is still considered good even though polyfills are so useful. That should give you an idea of how complex and error-prone the task can be. This is black magic: not a task to be undertaken lightly.

Related

Preserve prototypes in ADVANCED mode

I need to compile my code with closure compiler in ADVANCED mode. I also need to keep prototypes of my objects in my application because I'm looping on Javascript objects prototypes. Trying to get both results in some ReferenceError when starting the application.
When compiling with ADVANCED mode, some prototypes are removed and replaced by a function that is using an object parameter in order to recover "this" keyword. This is due to crossModuleCodeMotionNoStubMethods attribute of CompilerOptions.java.
Example of code before compilation :
function MyClass() = { // Some code }
MyClass.prototype.someFunc = function() { // Some code calling someOtherFunc };
MyClass.prototype.someOtherFunc = function(someParam) { // Some code };
Example of code after compilation :
function MyCompiledClass = { // Some code }
MyCompiledClass.prototype.someCompiledFunc = function() { // Some code calling someOtherFunc }
function someOtherCompiledFunc(that, someParam) = { // Some code }
I first tried to use #this and #preserve JSDoc tags to solve the problem, without success. Using #export is not a solution, because functions will then keep their original names.
I've found two options to solve my problem for now :
Refactor the code as seen here
Build a custom version of Closure Compiler as seen here
Option 1 will need to much modifications in my code and will make it less readable, if it's the only solution, I will have a go for this one.
Option 2 seems to be a nice workaround, but I've read that some changes on CompilationLevel.java may violate some core assumptions of the compiler. Can someone tell me if by modifying setCrossModuleMethodMotion from true to false, will it still respect all core assumptions of the compiler ?
I'm currently building a custom version of the compiler to check if the code is compiling properly, but even if the code is usable, I need to be sure it will be properly obfuscated.
Thank you !
The specific optimization pass you are referring to is DevirtualizePrototypeMethods. The best way to block the optimization would be to use the #nocollapse annotation. It will allow your method to be renamed but not allow it to be removed from the prototype.
I'm not 100% sure it will work for this case, but if it doesn't it should and you can file an issue to have that fixed: https://github.com/google/closure-compiler/issues
You can export constructors and prototype properties in the same way.
For example:
MyClass = function(name) {
this.myName = name;
};
MyClass.prototype.myMethod = function() {
alert(this.myName);
};
window['MyClass'] = MyClass; // <-- Constructor
MyClass.prototype['myMethod'] = MyClass.prototype.myMethod;
As in https://developers.google.com/closure/compiler/docs/api-tutorial3

Javascript ES6 cross-browser detection

How can I find out the browser's Javascript engine version and support to ECMAScript 6?
I'm using navigator.appVersion just to know the browser's version, but not the engine's version.
Feature detection
I suggest you to use feature detection instead of detecting the browser's engine with heuristic methods. To do this you can simply wrap some code inside a try {..} catch (e) {...} statement, or use some if (...) statements.
For example:
function check() {
if (typeof SpecialObject == "undefined") return false;
try { specialFunction(); }
catch (e) { return false; }
return true;
}
if (check()) {
// Use SpecialObject and specialFunction
} else {
// You cannot use them :(
}
Why is feature detection better than browser/engine detection?
There are multiple reasons that make, in most of the cases, feature detection the best option:
You don't have to rely on browser's version, engine or specifics, nor detect them using heuristic methods which are hard and pretty crafty to implement.
You will not fall into errors regarding browser/engine specifications detection.
You don't have to worry about browser-specific features: for example WebKit browsers have different specifications than other ones.
You can be sure that, once a feature is detected, you'll be able to use it.
These are the main reasons that IMHO make feature detection the best approach.
Feature detection + fallback
When using feature detection, a pretty smart way to work when you aren't sure which features you can/cannot use consists in several feature detections and consequent fallbacks to more basic methods (or even creation of these methods from scratch) in case the features you want to use are not supported.
A simple example of feature detection with fallback may be applied to the window.requestAnimationFrame feature, which is not supported by all the browsers, and has several different prefixes depending on the browser you're working on. In this case, you can easily detect and fallback like this:
requestAnimationFrame =
window.requestAnimationFrame // Standard name
|| window.webkitRequestAnimationFrame // Fallback to webkit- (old versions of Chrome or Safari)
|| window.mozRequestAnimationFrame // Fallback to moz- (Mozilla Firefox)
|| false; // Feature not supported :(
// Same goes for cancelAnimationFrame
cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || false;
if (!requestAnimationFrame) {
// Not supported? Build it by yourself!
requestAnimationFrame = function(callback) {
return setTimeout(callback, 0);
}
// No requestAnim. means no cancelAnim. Built that too.
cancelAnimationFrame = function(id) {
clearTimeout(id);
}
}
// Now you can use requestAnimationFrame
// No matter which browser you're running
var animationID = requestAnimationFrame(myBeautifulFunction);
ECMAScript 6 (Harmony) features detection
Now, coming to the real problem: if you want to detect the support to ES6, you'll not be able to behave like I said above, because a relevant range of ES6 features is based on new syntaxes and private words, and will throw a SyntaxError if used in ES5, which means that writing a script which contains both ES5 and ES6 is impossible!
Here is an example to demonstrate this issue; the below snippet won't work, and it will be blocked before execution because contains illegal syntax.
function check() {
"use strict";
try { eval("var foo = (x)=>x+1"); }
catch (e) { return false; }
return true;
}
if (check()) {
var bar = (arg) => { return arg; }
// THIS LINE will always throw a SyntaxError in ES5
// even before checking for ES6
// because it contains illegal syntax.
} else {
var bar = function(arg) { return arg; }
}
Now, since that you cannot both check and execute ES6 conditionally in the same script, you'll have to write two different scripts: one which only uses ES5, and another one which includes ES6 features. With two different scripts you'll be able to import the ES6 one only if it is supported, and without causing SyntaxErrors to be thrown.
ES6 detection and conditional execution example
Now let's make a more relatable example, and let's say you want to use these features in your ES6 script:
The new Symbol objects
Classes built with the class keyword
Arrow ((...)=>{...}) functions
NOTE: feature detection of newly introduced syntaxes (like arrow functions) can only be done using the eval() function or other equivalents (e.g. Function()), because writing invalid syntax will stop the script before its execution. This is also the reason why you cannot use if statements to detect classes and arrow functions: these features are regarding keywords and syntax, so an eval(...) wrapped inside a try {...} catch (e) {...} block will work fine.
So, coming to the real code:
HTML Markup:
<html>
<head>
<script src="es5script.js"></script>
</head>
<body>
<!-- ... -->
</body>
</html>
Code in your es5script.js script:
function check() {
"use strict";
if (typeof Symbol == "undefined") return false;
try {
eval("class Foo {}");
eval("var bar = (x) => x+1");
} catch (e) { return false; }
return true;
}
if (check()) {
// The engine supports ES6 features you want to use
var s = document.createElement('script');
s.src = "es6script.js";
document.head.appendChild(s);
} else {
// The engine doesn't support those ES6 features
// Use the boring ES5 :(
}
Code in your es6script.js:
// Just for example...
"use strict";
class Car { // yay!
constructor(speed) {
this.speed = speed;
}
}
var foo = Symbol('foo'); // wohoo!
var bar = new Car(320); // blaze it!
var baz = (name) => { alert('Hello ' + name + '!'); }; // so cool!
Browser/engine detection
Like I said above, browser and engine detection are not the best practices when programming some JavaScript script. I'm gonna give you some background on this topic, just not to leave my words as a "random personal opinion".
Quoting from the MDN Documentation [link]:
When considering using the user agent string to detect which browser is being used, your first step is to try to avoid it if possible. Start by trying to identify why you want to do it.
[...] Are you trying to check for the existence of a specific feature?
Your site needs to use a specific Web feature that some browsers don't yet support, and you want to send those users to an older Web site with fewer features but that you know will work. This is the worst reason to use user agent detection, because odds are eventually all the other browsers will catch up. You should do your best to avoid using user agent sniffing in this scenario, and do feature detection instead.
Also, you're saying you use navigator.appVersion, but consider using another approach, because that one, together with many other navigator properties, is deprecated, and doesn't always behave like you think.
So, quoting from the MDN Documentation [link] again:
Deprecated: this feature has been removed from the Web standards. Though some browsers may still support it, it is in the process of being dropped. Do not use it in old or new projects. Pages or Web apps using it may break at any time.
Note: Do not rely on this property to return the correct browser version. In Gecko-based browsers (like Firefox) and WebKit-based browsers (like Chrome and Safari) the returned value starts with "5.0" followed by platform information. In Opera 10 and newer the returned version does not match the actual browser version, either.
Browser vendors that support ES6 modules now provide an easy way to do feature detection:
...
<head>
<script nomodule>window.nomodules = true;</script>
<script>console.log(window.nomodules)</script>
</head>
...
The script with the nomodule attribute will not be excuted by browsers which support <script type="module" ...>
You can also inject the script like this:
const script = document.createElement('script');
script.setAttribute('nomodule', '');
script.innerHTML = 'window.nomodules = true;';
document.head.insertBefore(script, document.head.firstChild);
script.remove();
As Marco Bonelli said, the best way to detect ECMAScript 6 language syntax is to use eval();. If the call does not throw an error, "all other" features are supported, but I recommend Function();.
function isES6()
{
try
{
Function("() => {};"); return true;
}
catch(exception)
{
return false;
}
}
demo: https://jsfiddle.net/uma4Loq7/
No eval ES6 feature detection
You can do it without using eval - just insert the detection code in its own script block and make a global variable assignment at the end. The variable assignment will not run if any error occurs in the script block.
<script>
window.isES6 = false;
</script>
<script>
// Arrow functions support
() => { };
// Class support
class __ES6FeatureDetectionTest { };
// Object initializer property and method shorthands
let a = true;
let b = {
a,
c() { return true; },
d: [1,2,3],
};
// Object destructuring
let { c, d } = b;
// Spread operator
let e = [...d, 4];
window.isES6 = true;
</script>
<script>
document.body.innerHTML += 'isES6: ' + window.isES6;
</script>
https://jsfiddle.net/s5tqow91/2/
Please note that there are many ES6 features, and checking only one does not guarantee you are covered. (The above code doesn't cover everything either, it's just what I think are the features that I use most often).
Why no eval?
The main reason is security and it's not that calling eval for feature detection is insecure by itself. It's that ideally you should disallow eval with Content Security Policy so it cannot be used at all - which greatly decreases attack surface. But if your own code uses eval, you cannot do that.
Detect devicePixelRatio which is a special property in WebKit.
Detect javaEnabled function's implement.
(function() {
var v8string = 'function%20javaEnabled%28%29%20%7B%20%5Bnative%20code%5D%20%7D';
var es6string = 'function%20javaEnabled%28%29%20%7B%0A%20%20%20%20%5Bnative%20code%5D%0A%7D';
if (window.devicePixelRatio) //If WebKit browser
{
var s = escape(navigator.javaEnabled.toString());
if (s === v8string) {
alert('V099787 detected');
} else if (s === es6string) {
alert('ES6 detected')
} else {
alert('JSC detected');
}
} else {
display("Not a WebKit browser");
}
function display(msg) {
var p = document.createElement('p');
p.innerHTML = msg;
document.body.appendChild(p);
}
})()
For now there's not a exact way to detect ES6, but if you test its features in the current browser, you can determine if the engine is ES6. My esx library detects the ECMAScript version by doing syntax tests and methods check. For know it can detect ECMAScript 3, 5, 6 and 7 (ES7 not tested, but should work), if no ECMAScript test matched, it gives null as result.
Example using my library:
if (esx.detectVersion() >= 6) {
/* We're in ES6 or above */
}
As Damian Yerrick has mentioned, the use of eval() or Function() is incompatible with a Content Security Policy that does not specify 'unsafe-eval'.
If the browser supports Worker then you can detect support for any ES6 syntax by implementing that syntax in a worker and checking for error or successs eg to detect support for arrow functions:
worker.js
// If ES6 arrow functions are supported then the worker listener will receive true, otherwise it will receive an error message
(() => {
postMessage(true);
})();
index.js
if (typeof (Worker) !== "undefined") {
var myWorker = new Worker('worker.js');
myWorker.onmessage = function (e) {
// arrow functions must be supported since we received message from the worker arrow function
}
myWorker.onerror = function (e) {
// the worker triggered an error so arrow function not supported (could explicitly check message for syntax error)
}
}
This function returns true in Chrome 98.0.4758.80 and Firefox 97.0.2 (just tested). It may not work on other browsers and previous versions of Chrome/Firefox (false negative results)
function hasAsyncSupport () {
return Object.getPrototypeOf(async function() {}).constructor.toString().includes('Async')
}
The best way, I think, is to simply write all ES6 scripts as modules, and use nomodule script tags for any fallbacks. Trying to code inline detection is not needed.
Of course, this begs the question of how to detect new syntax from post-ES6 versions, such as the "??" and "?." operators.
Put the incompatible syntax code, such as containing arrow functions, in it's own script block and polyfill it with compatible syntax code.
<script>
// This script block should not compile on incompatible browsers,
// leaving the function name undefined.
// It can then be polyfilled with a function containing compatible syntax code.
function fame() {
/* incompatible syntax code such as arrow functions */
}
</script>
<script>
if (typeof fame !== "function") {
// alert("polyfill: fame");
function fame() {
/* compatible syntax code */
}
}
</script>
<script>
// main code
fame();
</script>

Javascript Intellisense in Microsoft Visual Studio objects defined by custom code define / derive

Situation: using functions to declare your Classes
If you are using and declaring classes with some custom (or framework function) as WinJs does (check their open source git directory), you are certainly familiar with this kind of code:
function define(constructor, instanceMembers, staticMembers) { }
function derive(baseClass, constructor, instanceMembers, staticMembers) { }
define(function constructor(){
this.yourProperty = 1;
}, {
// Prototype object
somePrototypeFunction: function(){
// When you type "this." here, it will not show up "yourProperty" declared
// in the constructor, because you have not instanciated the class,
// intellisense does not know that everything is linked
}
}
Common problem on these "custom" functions
Intellisense does not show up the values declared within the constructor when you try to reach them from the prototype functions.
I found something that have helped me: http://social.msdn.microsoft.com/forums/windowsapps/en-US/3eee400a-fefd-4f5e-9109-68df03fef006/javascript-intellisense-with-this-inside-gettersetter
This leaded me to the solution that I share to you below, it was a pain to make it work, and actually I was about to ** AGAIN ** let go with that problem which was something really disapointing especially with big team projects.
I find it weird that there are not many complaints about this on the web, maybe it's a configuration problem? However I had that problem on all VSD installations I saw.
So I hope the following solution will help you too if you run into the same situation.
After a few hours I finally have a solution which is not perfect (I have handled .base like in C# in my javascript library, but with the following code I can't say to intellisense that this ".base(...) " exists in the context of the prototype functions and constructor). If you have any tip on how to do that let me know, I'm interested.
Tested on Visual Studio 2013.
Simply change window.define / window.derive to the namespace and name you actually use (for WinJs it would be WinJS.Class.define and WinJS.Class.derive).
Add in _references.js the relative path of the file where you will put the following code, just after your library
And that's all! You'll have intellisense inside your
(function (window) {
"use strict";
/*
* Goal: make intellisense understand that the constructor of your define/derive functions are linked to the prototype object you have supplied.
* Tested on WinJs library and other custom libraries.
* Save this in a file, and reference it only in _references.js, insert it after your library containing the define/derive functions
*/
function initIntellisenseFor(constructor, baseClass) {
var inst = new constructor();
// Force intellisense to run function
for (var key in inst) {
if (typeof inst[key] == 'function') {
try {
inst[key]();
} catch (e) {
// Silent fail if wrong arguments (not sure if needed)
}
}
}
// Force intellisense to discover constructor
inst.constructor = constructor;
// Missing: .base() implementation for each method with redirection to the appropriate parent class method
}
var oldDefine = window.define;
window.define = function (constructor, instanceMembers, staticMembers) {
var result = oldDefine.call(this, constructor, instanceMembers, staticMembers);
initIntellisenseFor(result);
return result;
};
var oldDerive = window.derive;
window.derive = function (baseClass, constructor, instanceMembers, staticMembers) {
var result = oldDerive.call(this, baseClass, constructor, instanceMembers, staticMembers);
initIntellisenseFor(result, baseClass);
return result;
};
})(this);

require.js for non-browser platform or the right way to use Function constructor

I am trying to use requirejs in an Apple TV project. We have a lot of requirejs modules written for web, would be cool if we could re-use them.
Apple TV platform has certain limitations and it's sorta impossible to use requirejs "as is". There's no DOM in common sense.
One possible way I found to overcome the problem is: first to load require.js itself and then override its .load() method, so whenever require('foo') gets called it would load foo.js via a simple XHR call:
requirejs.load = (context, moduleName, moduleUrl) ->
reqModule = new XMLHttpRequest()
reqModule.open('GET', appRoot+moduleUrl, true)
reqModule.send(null)
reqModule.onreadystatechange = ->
if reqModule.readyState is 4 and reqModule.status is 200
fn = (new Function(reqModule.responseText))() # parse module
context[moduleName] = fn
context.completeLoad(moduleName)
So this works for normally defined modules like this:
define [], ->
someField: 'empty field'
Even works for self executing functions like this (with shim configured):
(myFoo = ->
someField:"empty field"
)()
for example Undercore.js contains itself in a self executing wrapper
However, that doesn't work with modules defined like this:
myFoo = ->
someField:"empty field"
Question: how can I make it work for all 3 cases? When used in browser, requirejs successfully loads all of them.
One solution I found is to wrap the function in define block for non-wrapped modules like in the last example, so instead of doing fn = (new Function(reqModule.responseText))() I would do:
fn = define [], (new Function("return "+reqModule.responseText))()
But then that would break load for both first and second cases. Is there a way to find out if a function wrapped in a self-executing block or not? How can I distinguish first two cases from the last one?
Using the code in the question as a starting point, I was able to get the following code to work. I don't have Apple TV so I cannot test it on Apple TV. I've tested it in a browser. It is able to load all 3 types of modules you've shown in your question, provided that the 2nd and 3rd modules have appropriate shims. So the logic is sound. The missing piece is what needs to stand in for window in eval.call(window, ...). In Node.js, it would be global. I don't know the equivalent in Apple TV.
requirejs.load = function(context, moduleName, moduleUrl) {
var reqModule = new XMLHttpRequest();
reqModule.open('GET', moduleUrl, true);
reqModule.send(null);
return reqModule.onreadystatechange = function() {
if (reqModule.readyState === 4 && reqModule.status === 200) {
eval.call(window, reqModule.responseText);
return context.completeLoad(moduleName);
}
};
};
If I were you, I would use Browserify
Write your browser code with node.js-style requires.

(Seemingly) redundant naming of functions in classes

I've seen various patterns in object creation, but upon diving into some Firefox framework Javascript, I'd noticed that they're using a convention that I haven't seen before and I'm hoping that someone here can either confirm my assumption or correct me:
When building a class, I've done this (and variations of) in the past:
function MyClass() {
this.myPublicFunction = function() {
};
var myPrivateFunction = function() {
};
}
Bound anonymous functions. Cool. This is what I've seen throughout most frameworks and such.
However, it seem that Mozilla does this:
function MyClass() {
this.myPublicFunction = function MYCLASS_myPublicFunction() {
};
var myPrivateFunction = function MYCLASS_myPrivateFunction() {
};
}
My assumption is that in using the latter convention, JS JIT compiles the functions, but wouldn't pick up the first two as they'd be defined at run-time rather than parse time.
Is this assumption correct and if so, does this also apply to other JS engines or just Spidermonkey?
Reasons to use NFE:
a function with a name gives more debugging information in your stack trace
NFE are self documenting. (if you put useful information in the name).
Note that function declarations are better then NFE. (From a personal style choice)
function MyClass() {
this.myPublicFunction = myPublicFunction;
function myPrivateFunction() {
}
function myPublicFunction() {
}
}
Also note that the word "private" is misleading, but that's a tangent.

Categories

Resources