{ "id": "82370", "key": "TIMOB-6033", "fields": { "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false }, "project": { "id": "10153", "key": "TIMOB", "name": "Titanium SDK/CLI", "projectCategory": { "id": "10100", "description": "Titanium and related SDKs used in application development", "name": "Client" } }, "fixVersions": [], "resolution": { "id": "10100", "description": "This issue won't be actioned.", "name": "Won't Do" }, "resolutiondate": "2020-01-09T19:27:59.000+0000", "created": "2011-11-05T02:07:09.000+0000", "priority": { "name": "Low", "id": "4" }, "labels": [ "dr-list", "parity" ], "versions": [ { "id": "12570", "name": "Release 1.7.5", "archived": true, "released": true, "releaseDate": "2011-11-02" } ], "issuelinks": [ { "id": "15849", "type": { "id": "10003", "name": "Relates", "inward": "relates to", "outward": "relates to" }, "inwardIssue": { "id": "88167", "key": "TIMOB-8082", "fields": { "summary": "iOS: Global variables are accessible in CommonJS module's scope", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "priority": { "name": "Medium", "id": "3" }, "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false } } } } ], "assignee": { "name": "ingo", "key": "ingo", "displayName": "Ingo Muschenetz", "active": true, "timeZone": "America/Los_Angeles" }, "updated": "2020-01-09T19:27:59.000+0000", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "components": [ { "id": "10202", "name": "Android", "description": "Android Platform" } ], "attachment": [], "flagged": false, "summary": "Android: Heavyweight root window causes incorrect CommonJS global variable behavior", "creator": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "subtasks": [], "reporter": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "environment": "* Samsung Galaxy Tab v7: 1.7.5 (11/02/11 16:30 ab20af7...) + Android 2.2\r\n* Emulator: Titanium 1.7.5 (2011/11/02 17:00 ab20af7) + Google APIs Android 2.2\r\n* Windows XP, OSX 10.6, Ubuntu 10.04", "comment": { "comments": [ { "id": "171696", "author": { "name": "pdowsett", "key": "pdowsett", "displayName": "Paul Dowsett", "active": true, "timeZone": "Europe/London" }, "body": "Well spotted, Ivan! Thank you for raising this issue, as there is certainly inconsistent behavior when using a heavyweight root window.\r\n\r\nI have modified the ticket to make the test case clearer. Please note that I have changed the way you created a heavyweight window, from using the modal to fullscreen property, because the root window by its nature is modal.\r\n\r\nCheers", "updateAuthor": { "name": "pdowsett", "key": "pdowsett", "displayName": "Paul Dowsett", "active": true, "timeZone": "Europe/London" }, "created": "2011-11-06T07:14:13.000+0000", "updated": "2011-11-06T07:20:19.000+0000" }, { "id": "171721", "author": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "body": "Thanks for accepting my ticket, Paul. \r\n\r\n\r\nBut I do mind some of your modifications:\r\n\r\n* \"win2\" seems unnecessary here (bug is present without it)\r\n* events should be attached to the window before it opens. In theory, window could open before event listener is attached to it (maybe it's not a problem for \"click\" event, but could be for \"open\" event)\r\n* it's not clear how global variables function with CommonJS approach in Titanium. What if variable is declared without \"var\" keyword in module scope? Will it become global variable or local variable in module scope? Is there a mechanism how to introduce new global variables from module scope?\r\n\r\n\r\nAlso, I do need to note that this is like forth ticket that I reported related to the inconsistency between lightweight and heavyweight windows in Android, so I'd liked to bring attention to that to devs working on Android.", "updateAuthor": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "created": "2011-11-07T01:30:10.000+0000", "updated": "2011-11-07T01:30:10.000+0000" }, { "id": "171724", "author": { "name": "pdowsett", "key": "pdowsett", "displayName": "Paul Dowsett", "active": true, "timeZone": "Europe/London" }, "body": "Ivan\r\n\r\nJust as you pointed out, the test case was indeed unnecessarily long, so I have improved it.\r\n\r\nPoints to note:\r\n* the root window, by its nature, is a modal window, and so the modal property is rarely used for it in real-world applications. Therefore, I have removed this property and added fullscreen, which produces the same result as your original code\r\n* to avoid any ambiguity about when the event is invoked, I have changed the open event listener to a click event listener. Again, this produces the same result\r\n* eventListener code should be placed after the window is opened, to improve the user experience by reducing the delay until the screen is rendered\r\n\r\nBe assured that we are aware that there needs to be more information about the use of CommonJS with Titanium, and are working on documentation and video tutorials to address it\r\n\r\n\r\nThanks for the ticket.", "updateAuthor": { "name": "pdowsett", "key": "pdowsett", "displayName": "Paul Dowsett", "active": true, "timeZone": "Europe/London" }, "created": "2011-11-07T05:54:28.000+0000", "updated": "2011-11-07T05:54:28.000+0000" }, { "id": "171725", "author": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "body": "Paul,\r\n\r\n\r\n* I don't know for others, but I use modal property for all windows in my (Android) application. OK, maybe it's not necessary for root window, because it's modal by it's nature, but I find that strange because setting modal property to different values has affect as you can see in my example (if root window is modal by it's nature, setting modal property to it should have affect, right?).\r\n* hmmm, okay :)\r\n* delay shouldn't be present if UI is on separate thread, right? Even if that is not so, \"addEventListener\" function is extremely fast. On the other hand (if I understand correctly, and please correct me if I'm wrong), window opening is asynchronous, but execution of JS statements is synchronous. That means that window would \"open\" (maybe not visually because of \"asynchronous\" opening, but it would \"open\" from JS statement point of view) and \"open\" event listener at that time would not be present, therefore event would not be fired to that listener.\r\n\r\nSo ...\r\n\r\n{code}\r\nwin.open(); //window opens right here from statement point of view, no event listeners attached\r\n\r\n//at this time window is already opened from statement point of view, so it would not be fired if window opening would be super-fast\r\nwin.addEventListener('open', function() {\r\n\r\n});\r\n{code}\r\n\r\n\r\nSometimes your approach will work because window opening is slow (and of course, asynchronous) and JS execution is fast. To be precise, if time necessary for window to open is greater than time needed to execute all statements including \"addEventListener\", then your approach will work. If that is no so, it will not work. Check this example:\r\n\r\n\r\n{code}\r\nvar rootWin = Ti.UI.createWindow({\r\n\tfullscreen: true,\r\n\tbackgroundColor: '#0f0'\r\n});\r\n\r\nrootWin.open();\r\n\r\nrootWin.addEventListener('open', function() {\r\n\t\r\n\tTi.API.info('Root window opened ');\r\n\t\r\n\tvar win = Ti.UI.createWindow({\r\n\t\t//modal: true,\r\n\t\tbackgroundColor: 'magenta'\r\n\t});\r\n\t\r\n\twin.open();\r\n\t\r\n\tvar doNothing = [];\r\n\tfor (var i = 0; i < 100; ++i) {\r\n\t\tdoNothing.push('Doing nothing ...' + i);\r\n\t}\r\n\t\r\n\twin.addEventListener('open', function() {\r\n\t\tTi.API.info('Window opened ');\r\n\t});\r\n\t\r\n});\r\n{code}\r\n\r\n(I had to use \"magenta\" as background color to illustrate the disaster, sorry about that)\r\n\r\nFor some reason example doesn't work for root window (only dear God and Don Thorp knows why), but you can see the potential disaster for non-root window if there is some delay introduced before adding of event listener (beside \"magenta\" background color, there is no console output \"Window opened\").\r\n\r\n\r\n\r\nI hope you understand the problem now and that you won't correct my code that way (again!). :)\r\n(if you need more explanation, please let me know)", "updateAuthor": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "created": "2011-11-07T07:42:10.000+0000", "updated": "2011-11-07T07:42:10.000+0000" }, { "id": "173145", "author": { "name": "rblalock", "key": "rblalock", "displayName": "Rick Blalock", "active": false, "timeZone": "America/Havana" }, "body": "To add to this discussion:\r\n\r\nIn general, globals in a CommonJS setting are considered bad. In fact NodeJS completely disallows them. You get a few like an object called 'global' that you can append things to but NodeJS kills anything else in a global context. This is common across many CommonJS implementations.\r\n\r\nI'm not sure what will be implemented in our V8 version of Android but here are some notes:\r\n\r\n1. Don't use globals. If you need to pass things (like an an app object to manage state, etc.) pass that in to your module. This is common practice amongst the Node community as well. On iOS you don't have to and globals will be present. This isn't guaranteed to always work though. Safest bet is to always pass in to your module what the module will use.\r\n\r\n2. As far as I can tell this is how it works on iOS currently: Globals can be brought in to the module's scope. If you overwrite the global in the module scope - it doesn't overwrite it globally IF you overwrite it using 'var'. e.g.\r\n{code}\r\n// Global\r\nvar myapp = {};\r\nvar mymodule = require('somemodule');\r\n\r\n// somemodule.js\r\nvar myapp = 'cool beans';\r\n// Now myapp inside 'somemodule.js' is 'cool beans' but it is not changed in the global context (and there's no way to access it in the module now\r\n{code}\r\n\r\nI've seen this change throughout releases but I'm pretty sure that's how it works currently.\r\n\r\nAt the end of the day: DONT USE GLOBALS inside a commonjs module. Pass things in to it.\r\n\r\n3. I think Android is following a similar path to NodeJS for the V8 implementation. So you'll want to do what I'm recommending above for long term support. Marshall am I correct on that?", "updateAuthor": { "name": "rblalock", "key": "rblalock", "displayName": "Rick Blalock", "active": false, "timeZone": "America/Havana" }, "created": "2011-11-17T12:26:30.000+0000", "updated": "2011-11-17T12:26:30.000+0000" }, { "id": "173235", "author": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "body": "Hi Rick, thanks for your comment.\r\n\r\n\r\nI agree that, in general, global variables should be avoided, but sometimes they are useful. For example, if there is some common functionality that you don't want to re-include in every module (because it's against DRY principle).\r\nIf CommonJS specification allows global variables, then I don't see a reason why Titanium should disallow them.\r\n\r\nI didn't find any decent explanation about global variables in CommonJS (although, many CommonJS implementations allows them in different ways), anyone can share some knowledge?\r\n\r\n\r\n\r\nI'm not sure do I understand you correctly regarding point 2., but ... I think that's the way it should work. If variable is declared using \"var\" keyword in some particular scope, then it's local variable to that scope and not global scope, even if variable with same name is declared in global scope. See this example:\r\n\r\n{code}\r\n//this.c === window.c === global object\r\nvar c = 5;\r\n\t\r\nwindow.onload = function() {\r\n\tvar c = 2;\r\n\talert(c);\r\n\talert(window.c);\r\n};\r\n{code}\r\n\r\nThis alerts \"2\" and \"5\".\r\n\r\nI think module scope is pretty similar. If you define variable in module scope using \"var\" keyword, then it's local variable in that module, even if it is defined in global scope. If it's defined without \"var\" keyword, JS engine will try to find it in super-scope (I don't know right word :) ).\r\n\r\n\r\nSo ...\r\n\r\n{code}\r\n// Global scope\r\nvar myapp1 = {};\r\nvar myapp2 = {};\r\nvar mymodule = require('somemodule');\r\n\r\n// somemodule.js\r\n//global module scope\r\nvar myapp1 = 'cool beans'; //doesn't change global scope variable because it's local variable\r\nmyapp2 = 'cool beans'; // affects global scope variable\r\n{code}\r\n\r\nThis is how it works on Android when there are no heavyweight windows involved (I don't know how iOS side functions).\r\nThat approach is pretty JavaScript-ish, but the problem is that the problem of polluting global scope is not solved because (accidentally) declaring variable without \"var\" keyword would implicitly create new global variable. I think some implementations use \"global\" as global variable to avoid that. In them global variables must be set explicitly using \"global.variableName = ...\". IMHO, the problem is not implementation, it's programmer's bad practice (declaration of variable without \"var\" keyword). Using \"global\" variable does not solve that problem, lousy practice is actually encouraged (because now programmer can declare variable without \"var\" keyword without consequences).", "updateAuthor": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "created": "2011-11-18T02:39:56.000+0000", "updated": "2011-11-18T02:39:56.000+0000" }, { "id": "174546", "author": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "body": "Kevin seems to like globals. :)\r\nhttps://github.com/appcelerator-developer-relations/Forging-Titanium/blob/master/ep-006/SCTabGroup/Resources/ui/AppWindow.js#L14\r\n\r\n\r\nAnyway, just to note that this issue does not exist when lightweight windows are used, I think that behaviors in LW & HW windows should be the same. ", "updateAuthor": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "created": "2011-12-02T05:25:16.000+0000", "updated": "2011-12-02T05:25:16.000+0000" }, { "id": "174566", "author": { "name": "kwhinnery", "key": "kwhinnery", "displayName": "Kevin Whinnery", "active": true, "timeZone": "America/Chicago" }, "body": "Most rich client app technologies I've worked with (Flex/WPF/Browser) allow for a global scope. This is a useful tools for doing things like caching and binding to data. I think we should provide a consistent means for using globals, regardless of whether Ti.include or require is used.", "updateAuthor": { "name": "kwhinnery", "key": "kwhinnery", "displayName": "Kevin Whinnery", "active": true, "timeZone": "America/Chicago" }, "created": "2011-12-02T07:59:14.000+0000", "updated": "2011-12-02T07:59:14.000+0000" }, { "id": "174615", "author": { "name": "mculpepper", "key": "mculpepper", "displayName": "Marshall Culpepper", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Moving forward, Window URLs and CommonJS modules should be seen as functionally equivalent in their access to global scope. (We actually implement Window URLs as CommonJS modules in our new V8 implementation).\r\n\r\nThat being said, objects on the global scope should in principle never be used (or be allowed to be set) by a CommonJS module. It's true that some CommonJS implementations allow this, but it is not specifically spelled out by the spec, and in my opinion goes against the spirit of CommonJS. In Android, starting with our new V8 implementation, each module is purposefully sandboxed into a new, *private* context which has it's own private global.\r\n\r\nAs Rick stated above, there is never a technical reason to clobber globals from within the private scope of a module -- if you need to reuse components, then those components can be passed in as part of an initialization process, i.e...\r\n\r\n{code}\r\n// app.js\r\nvar myExpensiveThing = //...\r\n\r\nvar myModule = require(\"myModule\");\r\nmyModule.init(myExpensiveThing);\r\n\r\n// myModule.js\r\nexports.init = function(expensiveThing) {\r\n // ...\r\n}\r\n{code}\r\n\r\n\r\nThe fact that clobbering globals works currently in Window URLs / CommonJS modules should be considered a bug, as this is not desired behavior moving forward.", "updateAuthor": { "name": "mculpepper", "key": "mculpepper", "displayName": "Marshall Culpepper", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2011-12-02T11:37:47.000+0000", "updated": "2011-12-02T11:37:47.000+0000" }, { "id": "174646", "author": { "name": "kwhinnery", "key": "kwhinnery", "displayName": "Kevin Whinnery", "active": true, "timeZone": "America/Chicago" }, "body": "Just had a conversation with Opie - it turns out that in the new CommonJS implementation (and possibly before, I don't know), modules themselves are stateful, which is a departure from how I had been thinking about modules. If modules can have persistent global object, then the need for an app-wide global state is removed. Let's say I had some global config options I needed to store. I could have a module to store my app's state which looked like this:\r\n\r\n{code:javascript}\r\nvar STATE = {\r\n\tusername:'kwhinnery' //some default value\r\n};\r\n\r\nexports.get = function(key) {\r\n\treturn STATE[key];\r\n};\r\n\r\nexports.set = function(key, value) {\r\n\tSTATE[key] = value;\r\n};\r\n{code}\r\n\r\nWhich could be consumed across my entire app like so:\r\n\r\n{code:javascript}\r\nvar appstate = require('module/defined/above');\r\nvar username = appstate.get('username');\r\n{code}\r\n\r\nIf this is uniformly implemented on the iOS side as well, then I can't think of a legitimate use case for having global variables.", "updateAuthor": { "name": "kwhinnery", "key": "kwhinnery", "displayName": "Kevin Whinnery", "active": true, "timeZone": "America/Chicago" }, "created": "2011-12-02T15:53:25.000+0000", "updated": "2011-12-02T15:53:25.000+0000" }, { "id": "178833", "author": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "body": "Kevin's example does not show that module is stateful (since \"username\" is hardcoded, so it will always return same value), to show that module is stateful, \"username\" has to be changed, and that change should be reflected on re-required module.\r\n\r\n\r\n\r\nAnyway, just to add more info if someone bumps to this issue ... module scope creates new JS context, similar to what \"iframe\" is in browser. That means - clear JS environment. So, if you change JS native prototype (which are by default global objects), that won't be reflected in module's scope:\r\n\r\n\r\n{code}\r\n//app.js\r\nString.prototype.say = function() {};\r\nvar someString = 'Meh';\r\nTi.API.info(typeof someString.say); // function\r\n\r\n//module.js\r\nvar modulesString = 'Meh';\r\nTi.API.info(typeof modulesString.say); // undefined\r\n{code}\r\n\r\n\r\n\r\nOne more reason not to mess with native prototypes. :)", "updateAuthor": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "created": "2012-01-11T04:23:12.000+0000", "updated": "2012-01-11T04:23:12.000+0000" }, { "id": "178834", "author": { "name": "kwhinnery", "key": "kwhinnery", "displayName": "Kevin Whinnery", "active": true, "timeZone": "America/Chicago" }, "body": "Good point on the prototypes, but the module objects are indeed stateful. If you use the \"set\" function in my example, as of 1.8, the object will be updated for all users of the module.", "updateAuthor": { "name": "kwhinnery", "key": "kwhinnery", "displayName": "Kevin Whinnery", "active": true, "timeZone": "America/Chicago" }, "created": "2012-01-11T04:28:11.000+0000", "updated": "2012-01-11T04:28:11.000+0000" }, { "id": "178835", "author": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "body": "Yes, I agree that modules are stateful, but (IMHO) your example missed to show that.\r\n\r\n\r\n{code}\r\n//somewhere\r\nvar appstate1 = require('module/defined/above');\r\nvar username1 = appstate1.get('username'); //kwhinnery\r\nappstate1.set('username', 'iskugor');\r\n\r\n\r\n//somewhere else\r\nvar appstate2 = require('module/defined/above');\r\nvar username2 = appstate2.get('username'); //iskugor\r\n{code}\r\n\r\nDidn't mean to say it's wrong, only that it may not be clear enough at first glance.\r\n\r\n\r\n\r\nAlso, (just for info) this module's \"statefulness\" brings some potential problems. If module's API has some method and someone overrides it, then it will be reflected on all module's instances (it's actually one instance because modules are singletons). Sometimes that's not what we want.", "updateAuthor": { "name": "ivan.skugor", "key": "ivan.skugor", "displayName": "Ivan Skugor", "active": true, "timeZone": "Europe/Amsterdam" }, "created": "2012-01-11T05:37:53.000+0000", "updated": "2012-01-11T05:37:53.000+0000" }, { "id": "453569", "author": { "name": "ahutton", "key": "ahutton", "displayName": "Alan Hutton", "active": true, "timeZone": "America/Los_Angeles" }, "body": "It has been decided that this issue should be closed as “Won’t do.” This issue is out of date with our current supported SDK release (7.5.2.GA as of the date of closure), and out of date with mobile OS versions. Updating, or creating code may not reproduce the issue reported, or be a valid test case.\r\n\r\nIf community members feel that the issue is still valid, please create a new ticket. Please reference this closed ticket number, include SDK used, comments, and code that demonstrates/reproduces the issue.", "updateAuthor": { "name": "ahutton", "key": "ahutton", "displayName": "Alan Hutton", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2020-01-09T19:27:59.000+0000", "updated": "2020-01-09T19:27:59.000+0000" } ], "maxResults": 14, "total": 14, "startAt": 0 } } }