Separate top level scope and globalThis.#2161
Conversation
|
Okay, I've almost got this sorted. There remain a small set of failing tests due to nested scopes and associated values, which think I can tackle. This has required an API change in how we create nested scopes from a common sealed parent. I'm going to try and distill that down to a simple API, maybe something like |
2fecd46 to
10c5f88
Compare
|
One concern I have is that it's not great how in so many places you have to do: if (scope instanceof TopLevel) {
scope = ((TopLevel) scope).getGlobalThis();
}I am unsure on how we could improve it, but it's really diffused basically everywhere we have I am not sure why you are doing all the delegation in |
|
@andreabergia I agree that I don't really like the tests for top level that are now scattered through several bits of initialisation code. The original reason to not force the top scope to be a I've prototyped this by doing it in stages
I'm currently on step 2. and have had make small adjustments to four tests, and have another four which seem to need similar fixes. @gbrail how would you feel about this API change? |
|
Okay, I've moved all the initialisation stuff over to requiring a There are still couple of explicit checks for |
|
Well, that went well. 😆 so it turns out that even if we force the creation of all top level scopes to be actual
The serialisation problem is similar in that the relationship between the top level scope and the global this has been fully set up at the point when something is trying to rely on that. I’m setting this back to draft until I can track down those problems. |
|
Had a burst of inspiration this morning and got this down to about 30 failing test cases. I think all the remaining cases are actual bugs where we ended up with The serialisation problem is occurring because of the way I handled associated values on the top level scope. To enable the creation of isolates I put the values on the global object rather than the scope, and relied on being able to reach that object before it has been associated with top level scope, so the lookup breaks. I think the thing to do here is to delegate associated values explicitly when creating isolates. I think two top level scopes created from a sealed shared scope should be guaranteed to share the same type cache. |
|
I feel like I'm falling behind here, and apologies -- but it's a testament to the great work you're all doing. I want to think more carefully about what we're doing to backward compatibility with all this stuff. I've never been super happy about the whole "parent scope" thing, but it's a fundamental part of Rhino and creating a "sealed shared scope" is used by a lot of examples and docs and probably real projects. Can we document somewhere what we're proposing that will require people to change their current code, and then make a plan to stage it in upcoming releases? Between this and other things we're working on, as well as the increased velocity of work on the project, it may indeed be time for a 1.9.0 or even a 2.0 with some reasonable changes to backward compatibility in favor of spec compliance. |
|
Just a possible heads-up when doing stuff in the area of sealed/shared scopes: #1085 |
|
@gbrail Strongly agree. I think we're approaching the point where we want to do a release prior to any significant API change, and then work to get all those API changes into a 2.0.0 release. I think it might be worth it to create a 2.0 feature branch which we use for this and regularly rebase it on top of I understand why people want to create a sealed parent object as it is a reasonably simple way to avoid repeated initialisation and prevent accidental modification, and we should absolutely ensure there is an API for doing so. I think that can be done, ad we can make it make it easier to use than the current |
890f88d to
49f3a22
Compare
|
Okay. Looks like there is a small change required for some Android tests and a little tidy up required. I'll try and do that over the weekend. |
c161013 to
7ee1037
Compare
|
Puh, this is huge (and i have to attend a workshop the next days). Will try it next week - hope that is ok And a question - my top level scope is the window (or the globalworker)
Do i have to make Window extending TopLevel? |
|
Your |
7ee1037 to
82bcce9
Compare
82bcce9 to
2d051f2
Compare
|
While preparing a refactoring of where we resolve |
|
Okay. The test failures I saw appear to be long term bugs in our code base which I've fixed as part of my refactoring and nothing introduced by this change. |
2d051f2 to
195618b
Compare
|
So, reviewing my own change… I don't like the way we build isolates with a custom Java class, but I didn't like the old way either. They both involved messing with the prototype chain making that custom class unusable as a normal object (or at least really weird since it exposed all of global this in its prototype chain) and it required special handling in I think I have a better model of how to do this, which works by using bound functions and avoids any messing with prototype chains. |
195618b to
b0cbdc0
Compare
|
After some experimentation I've gone for cleaning up |
03d7ed4 to
3b0db1c
Compare
|
Still think we have a issue with NativeConsole.init(Scriptable scope, boolean sealed, ConsolePrinter printer) Using the provided scope for all the calls looks good to me, but the last line should define the property on the globalThis. Therefore i expect to see also the destinationObject for the new property in the method parameter list. |
|
During the last weeks i tried to apply this change to HtmlUnit. It was a hard fight, because there was no clear separation between the globalThis and the TopScope. I think i'm nearly done and fine with this - means: happy to see this merged! But we have to have in mind, how big this change might be for other applications using Rhino. Maybe it will be helpfuly to have a clear design sample document including some pictures and sample code that shows how to use Rhino as scripting language for an application. Including
And maybe more (hope the AI can help us a bit to make a shiny document here). Maybe we can start small and improve this step by step What do you think? |
|
So, any property defined on TopLevel is simply defined on the associated globalThis object. That should not remain true in the long term (because consts, classes, and some other things shouldn't end up there, but for now it's true, and is why it does not matter that scope is being passed in that property definition call rather than globalThis. Clearly I need to find a good way to communicate that. @rbri would you be okay with the architectural docs and examples being a follow up PR so we can merge this and start to work through the PR stack? I think I agree with your list, but I think it's also going to need a little bit of a primer on what realms are, what intrinsic are, and a couple of other spec related things. P.s. Sorry, that came over grumpier than I intended. My jet lagged brain clearly isn't doing tone well today.
🎉 Ship it @gbrail! 😄 |
|
Oh wait, I need to rebase this before we can merge it. |
63e71ec to
974f703
Compare
That was exactly the reason for my last post, i think we have to go this way and i no longer like to be something like the blocker for this and all the other cool things on top. |
@aardvark179 at first, my english is not good enought to even notice this ;-) And i fear sometimes my writings have the same 'problem' And second: we are 'fighting' for the best solution - thats at least for me the fun part of all this |
gbrail
left a comment
There was a problem hiding this comment.
Let's get this thing moving! It's an important next step in our evolution, and since it will affect every Rhino embedder it's time to rip the band aid off and work on this.
With that said, I have a few small suggestions but if other's are generally OK I think that we should move forward soon. Thanks!
| } | ||
|
|
||
| @Override | ||
| public Object get(String name, Scriptable start) { |
There was a problem hiding this comment.
What breaks if this class overrides get, put, etc, but does not override getIds? Are there cases in which some code will need to enumerate the properties of the global object?
Also is there a reason to override get(int) and put(int)?
There was a problem hiding this comment.
There's enough annoying edge cases with things like __parent__ that I thought it best to override everything.
There was a problem hiding this comment.
Sorry I was going to fast - why NOT override those two as well? Same with getIds?
| public FunctionObject(String name, Member methodOrConstructor, Scriptable scope) { | ||
| if (methodOrConstructor instanceof Constructor) { | ||
| member = new MemberBox((Constructor<?>) methodOrConstructor); | ||
| member = new MemberBox(getDeclarationScope(), (Constructor<?>) methodOrConstructor); |
There was a problem hiding this comment.
Have not verified this myself but by AI friend tells me that at this point in the constructor, getDeclarationScope() will always return null. That looks accurate to me. Is that going to cause a problem later with the Java embedding?
There was a problem hiding this comment.
Good catch. This was an artefact of re-ordering a bunch of commits a while ago.
|
|
||
| Function f = getCallbackArg(cx, callbackArg); | ||
| Scriptable parent = ScriptableObject.getTopLevelScope(f); | ||
| TopLevel parent = ScriptableObject.getTopLevelScope(f); |
There was a problem hiding this comment.
Just a minor thing but in my new Java code I have been getting more judicious about using "var" in cases like this and if we had done so before then perhaps this would not have been necessary to make this particular change.
There was a problem hiding this comment.
Yes, I've been using var a lot and it would have reduced a bunch of these changes, but I've had enough push back on changing explicit types to var that I'm wary of doing it as part of a complex behavioural change.
974f703 to
c6cbf52
Compare
|
@rbri this is the first of many that will now start to cause backward compatibility to break, but we've all been talking about this for long enough and we agree it's time. Are you OK? I want to start moving faster on Duncan's big rewrite! |
@gbrail all fine for me - let'S move on |
|
OK -- let's do it and move to the next one. Thanks, all! |
|
Cool. Next in the queue should be the changes to function this resolution ( #2328 ), I'll get that prepped when I'm get back home tomorrow. Oh, wait, I have WiFi. I've done the rebase and made that PR ready for review. |
|
|
||
| var obj = (Scriptable) scope.get("Object", scope); | ||
| var objProto = (Scriptable) obj.get("prototype", obj); | ||
| globalThis.setPrototype(objProto); |
There was a problem hiding this comment.
Why is this wiring done here, and not somewhere else like in the initialization of TopLevel or at the end of initStandardObject?
| globalThis = customGlobal; | ||
| } | ||
|
|
||
| public static TopLevel createIsolate(TopLevel parent) { |
There was a problem hiding this comment.
Why the name? I assume it has something to do with v8 isolates (which I don't know well), but since it actually creates a relationship with the existing toplevel via the prototype and builtins, it's a bit confusing.
Another draft PR on the road to separating scopes and scriptable objects (#2163). I've put the rationale for doing this in a separate issue(#2164)
The global scope in EcmaScript is a a little subtle in that it consists of a declaration environment record and an object environment record for the global object (
globalThis). The global object contains variable declarations, function declarations, generator function declarations, async function declarations, and async generator function declarations. Everything else (constdeclarations,letdeclarations,classdeclarations, etc.) goes on declaration environment and is not added toglobalThis. This change could make that full separation, but currently there is one test inMozillaSuiteTestwhich depends on aconstdeclaration being added to the globalthis. I wonder if we should gate that on a language version.This is working well apart from a couple of edge cases that I think it's worth talking through:
ImporterTopLevelis mostly working, but I don't think I have a great model of how it should work. Currently The behaviour for package lookup is living on the scope half, but I'm wondering ifImporterTopLevelshould really put all the behaviour on a customGlobalThis.globalThis, but maybe like java imports it should live on that half.FunctionObject(which is currently the only way the existingcustomGlobaltest works). The existingModuleScopemodel also doesn't matchrequirein node or common-js.Let me expand on that last point slightly. If I have a file
blah.jscontainingand from node REPL I do
Then we find
thing2 === globalThisandthing2 === thing4.requiresimply introduces a new scope (just like a function does) rather than what we do with a module scope (a new top level scope that happens to have another top level scope as the prototype). What we have doesn't match that, but it also doesn't match the behaviour ofimportwhich should have an entirely separate realm (top level scope).I think I can see two possible ways to resolve this:
globalThis, and the module scope becomes a normal child scope. This would be closest to node as far as I can tell. We might well want to keep theModuleScopeas a special class to mark that we are in a module for relative requires etc.globalThisof whatever custom type provided. To avoid mutating the mainglobalThiswhen executing code in a module we'd need to ensure all modules execute on child scopes of this main module scope (but those need not themselves beModuleScopes, and we'd need to find allinstanceof ModuleScopechecks which currently look at thethisObjand change them to appropriate top scope checks.I don't know if @youngj is still around, still has their use case, and has an opinion on this. I can't tell how well covered this area really is because our Jacoco reports in
rhinoonly cover the tests inrhino, while the coverage report intestsdoesn't seem to be configured to show the core runtime classes.