Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
/nw.jsPublic

Differences of JavaScript contexts

Victor Shih edited this pageJul 31, 2016 ·25 revisions

NOTE: some content in this wiki applies only to 0.12 and earlier versions. For official documentation on 0.13 and later, seehttp://docs.nwjs.io

Different windows of a node-webkit-based application have different JavaScript contexts, i.e. each window has its own global object and its own set of global constructors (such asArray orObject).

That's common practice among web browsers. It's a good thing because, for example:

  • when an object's prototype is replaced or augmented by some library (such asPrototype) or a simpler script, the analogous objects in other windows are unaffected nevertheless;

  • when a programmer makes a mistake (such asmissingnew before a poorly written constructor) and the bug affects (pollutes) the global scope, it still cannot affect larger areas (several windows);

  • malicious applications cannot access confidential data structures in other windows.

Node modules in node-webkit run in their own shared Node context. (Shared by default; however, you may explicitly add'new-instance': true to the options ofWindow.open if you need your new window to have a separate Node.js context.)

Determining the context of a script

If therequire() method (of Node.jsmodules API) is used, then the required module runs in the Node's context. (When you call therequire() function or a function from some required module, the JS engine enters the Node's context and leaves it after the function returns.)

If HTML<script> element (or jQuery's$.getScript(), or any other similar method) is used in some window, then the script runs in the context of that window.

If the module is given as the value of the"node-main" property of the application'smanifest file, then the module runs in the Node's context but later has access to thewindow object. (See the “node-main” article for details.)

Features and limitations of the Node's context

Scripts running in the Node's context may use__dirname variable to read the path of their file's directory.

The Node.jsglobal object is the global object in the Node's context. Any WebKit window'swindow object is not the global object and even is not implicitly available in the Node's context (the special case ofnode-main is the only exception), i.e. you have to (explicitly) pass thewindow object to your module's function if you need to access it.

That also means you cannot rely onalert() (which is actuallywindow.alert()) for debugging. You may, however, useconsole.log(); its output (and the output of other similar methods such asconsole.warn() andconsole.error()) is redirected to WebKit's console. You may see it in your “Developer Tools” window (on its “Console” tab).

You cannot userequire('nw.gui') (to access the node-webkit'sGUI API) from the Node's context, because there's no GUI outside of a window.

Some other browser features (such asWorker andWebSocket interfaces) are also unavailable in the Node's context.

Resolving relative paths to other scripts

Relative paths in webkit context are resolved according to path of main HTML file (like all browsers do). Relative paths in node modules are resolved according to path of that module (like node.js always do). Just remember in which context you are.

For example if we have file/myApp/main.html:

<html><head><!-- will be resolved according to this html file path --><scriptsrc="components/myComponent.js"></script></head><body><script>// will be resolved according to this html file pathvarhello=require('./libs/myLib');// __dirname is not defined in webkit context, this is only node.js thingconsole.log(__dirname);// undefined</script></body></html>

In file/myApp/components/myComponent.js we can do this:

// we are still in webkit context, so paths are still resolved according to main.htmlvarsomething=require('./util.js');// will look for file /myApp/util.js NOT for /myApp/components/util.js// __dirname still not definedconsole.log(__dirname);// undefined

In file/myApp/libs/myLib.js we can do this:

// here we are in node.js context, so paths are resolved according to this filevarsomething=require('./otherUtil.js');// will look for file /myApp/libs/otherUtil.js// __dirname is definedconsole.log(__dirname);// '/myApp/libs'

Working around differences of contexts

While differences of contexts are generally beneficial, sometimes they may constitute a problem in your (or some other person's) code, and a need for a workaround arises.

The most common cause for such problems is the behaviour of theinstanceof operator in JavaScript. As you maysee in MDN, the operationsomeValue instanceof someConstructor tests whether an object has in its prototype chain theprototype property of the given constructor. However, ifsomeValue is passed from a different JavaScript context, then it has its own line of ancestor objects, and thesomeValue instanceof someConstructor check fails inevitably.

For example, a simple checksomeValue instanceof Array cannot determine if a variable's value is an array's if it's passed from another context (see “Determining with absolute accuracy whether or not a JavaScript object is an array” for details).

The same problem arises when the.constructor property is checked directly (for example, whensomeValue.constructor === Array is used instead ofsomeValue instanceof Array).

The following constructors are children of the context-dependent global object, and thus their instances are affected:

  • Standard object types:Array,Boolean,Date,Function,Number,Object,RegExp,String.

  • Typed array types:ArrayBuffer,DataView,Float32Array,Float64Array,Int16Array,Int32Array,Int8Array,Uint16Array,Uint32Array,Uint8Array.

  • Error types:Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError.

There are several ways to work around this problem.

Avoiding instanceof

The easiest way to prevent context-related problems is to avoid usinginstanceof when a value may come from another JavaScript context. For example, you may useArray.isArray method to check whether a value is an array, and that method works reliably across contexts.

However, if such a convenient alternate method is not readily available, or when you face a problem in someone other's (not your own) code and patching that would need a hassle, then another workaround is necessary.

Using a constructor from the other context

When you foresee passing a value to some other context, you may providently use a constructor from that context in order to construct your value. The value then would easily pass anyinstanceof checks in that context.

For example, the well-knownasync module used (inits code dated 2013-05-20; it was fixed later) numerous.constructor checks (in lines472,505,545,675,752) and thus failed whenever it encountered an array from another context. For example, if you run the following code from a node-webkit's window context,

require('async').waterfall([function(callback){console.log('1.');callback(null,'one','two');},function(arg1,arg2,callback){console.log('2.');callback(null,'three');},function(arg1,callback){console.log('3.');callback(null,'done');}],function(err,result){console.log('Fin.');if(err)throwerr;console.log(result);});

it throws theError: First argument to waterfall must be an array of functions (erroneously thinking it's not an array).

Using thenwglobal module, you may access the Node's context'sArray constructor and rewrite the above code:

require('async').waterfall(require('nwglobal').Array(function(callback){console.log('1.');callback(null,'one','two');},function(arg1,arg2,callback){console.log('2.');callback(null,'three');},function(arg1,callback){console.log('3.');callback(null,'done');}),function(err,result){console.log('Fin.');if(err)throwerr;console.log(result);});

It makes theasync module happy.

However, in some cases you cannot (or won't) use the constructor directly to create your value. (For example, as you maysee in MDN, using theFunction constructor is less efficient than declaring a function, and it also does not create a closure.) In such cases another workaround is necessary.

Replacing__proto__

The non-standard (but widely implemented)__proto__ property of an object can be used (as you maysee in MDN) to change the object's internal “Prototype” property (initially containing the prototype of its constructor).

When you foresee passing a value to some other context, you may providently replace the value's__proto__ property with a constructor from that context. The value then would easily pass anyinstanceof checks in that context.

For example, the well-knownasync module used (inits code dated 2013-05-20; it was fixed later) aninstanceof Function check (on line 428) and thus it failed whenever it encountered a function from another context. For example, if you run the following code from a node-webkit's window context,

vargetData=function(callback){setTimeout(function(){console.log('1.1: got data');callback();},300);}varmakeFolder=function(callback){setTimeout(function(){console.log('1.1: made folder');callback();},200);}varwriteFile=function(callback){setTimeout(function(){console.log('1.1: wrote file');callback(null,'myfile');},300);}varemailFiles=function(callback,results){console.log('1.1: emailed file: '+results.writeFile);callback(null,results.writeFile);}require('async').auto({getData:getData,makeFolder:makeFolder,writeFile:['getData','makeFolder',writeFile],emailFiles:['writeFile',emailFiles]},function(err,results){console.log('1.1: err: '+err);console.log('1.1: results: '+results);});

it throws thehas no method slice error (erroneously thinking that the given value is not a function and thus it has to be an array, and then attempting to slice that “array”).

Using thenwglobal module, you may access the Node's context'sFunction constructor and rewrite the above code:

vargetData=function(callback){setTimeout(function(){console.log('1.1: got data');callback();},300);}getData.__proto__=require('nwglobal').Function;varmakeFolder=function(callback){setTimeout(function(){console.log('1.1: made folder');callback();},200);}makeFolder.__proto__=require('nwglobal').Function;varwriteFile=function(callback){setTimeout(function(){console.log('1.1: wrote file');callback(null,'myfile');},300);}writeFile.__proto__=require('nwglobal').Function;varemailFiles=function(callback,results){console.log('1.1: emailed file: '+results.writeFile);callback(null,results.writeFile);}emailFiles.__proto__=require('nwglobal').Function;require('async').auto({getData:getData,makeFolder:makeFolder,writeFile:['getData','makeFolder',writeFile],emailFiles:['writeFile',emailFiles]},function(err,results){console.log('1.1: err: '+err);console.log('1.1: results: '+results);});

It makes theasync module happy.

Avoiding Node's setImmediate

Switching between WebKit's and Node's contexts takes some time.

In most cases this delay does not constitute a serious problem, but if you are in a WebKit's context and you are simply deferring some function's execution (in somehot spot where the same function is likely to be deferred hundreds of times; for example, when you draw some 500 objects giving the WebKit its chance to redraw the window after each object's appearance rather than forcing the user to stare on a blank screen mindlessly), then using Node'ssetImmediate (exported from some Node.js module) can actually become less “immediate” than you would be happy to experience.

To work around this problem it's usually enough to define (in WebKit's context) and use David Baron'ssetZeroTimeout function instead of Node'ssetImmediate.

// Only add setZeroTimeout to the window object, and hide// everything else in a closure.(function(){vartimeouts=[];varmessageName="zero-timeout-message";// Like setTimeout, but only takes a function argument.// There's no time argument (always 0) and no function's arguments// (you have to use a closure if such arguments are necessary).functionsetZeroTimeout(fn){timeouts.push(fn);window.postMessage(messageName,"*");}functionhandleMessage(event){if(event.source==window&&event.data==messageName){event.stopPropagation();if(timeouts.length>0){varfn=timeouts.shift();fn();}}}window.addEventListener("message",handleMessage,true);// Add the one thing we want added to the window object.window.setZeroTimeout=setZeroTimeout;})();

Using standardwindow.setTimeout(yourFunction, 0) is less efficient becauseHTML5 standard defines its minimal timeout as 4 milliseconds even if0 is given (i.e. even if you usesetTimeout only 250 times, you already get a whole second of extra delay in your application).

Clone this wiki locally

[8]ページ先頭

©2009-2025 Movatter.jp