Sfoglia il codice sorgente

Catch all exceptions occurring in the Smalltalk stack. JS exceptions are converted to instances of JavaScriptException

Nicolas Petton 11 anni fa
parent
commit
5976556d2e

+ 82 - 0
js/Kernel-Exceptions.deploy.js

@@ -119,6 +119,88 @@ return $1;
 smalltalk.Error.klass);
 
 
+smalltalk.addClass('JavaScriptException', smalltalk.Error, ['exception'], 'Kernel-Exceptions');
+smalltalk.addMethod(
+"_context_",
+smalltalk.method({
+selector: "context:",
+fn: function (aMethodContext){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
self.context = aMethodContext;
+return self}, function($ctx1) {$ctx1.fill(self,"context:",{aMethodContext:aMethodContext}, smalltalk.JavaScriptException)})}
+}),
+smalltalk.JavaScriptException);
+
+smalltalk.addMethod(
+"_exception",
+smalltalk.method({
+selector: "exception",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
var $1;
+$1=self["@exception"];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"exception",{}, smalltalk.JavaScriptException)})}
+}),
+smalltalk.JavaScriptException);
+
+smalltalk.addMethod(
+"_exception_",
+smalltalk.method({
+selector: "exception:",
+fn: function (anException){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
self["@exception"]=anException;
+return self}, function($ctx1) {$ctx1.fill(self,"exception:",{anException:anException}, smalltalk.JavaScriptException)})}
+}),
+smalltalk.JavaScriptException);
+
+smalltalk.addMethod(
+"_messageText",
+smalltalk.method({
+selector: "messageText",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
return "A JavaScript exception occured while in the Smalltalk stack!";
+}, function($ctx1) {$ctx1.fill(self,"messageText",{}, smalltalk.JavaScriptException)})}
+}),
+smalltalk.JavaScriptException);
+
+
+smalltalk.addMethod(
+"_on_",
+smalltalk.method({
+selector: "on:",
+fn: function (anException){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
var $2,$3,$1;
+$2=_st(self)._new();
+_st($2)._exception_(anException);
+$3=_st($2)._yourself();
+$1=$3;
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"on:",{anException:anException}, smalltalk.JavaScriptException.klass)})}
+}),
+smalltalk.JavaScriptException.klass);
+
+smalltalk.addMethod(
+"_on_context_",
+smalltalk.method({
+selector: "on:context:",
+fn: function (anException,aMethodContext){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
var $2,$3,$1;
+$2=_st(self)._new();
+_st($2)._exception_(anException);
+_st($2)._context_(aMethodContext);
+$3=_st($2)._yourself();
+$1=$3;
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"on:context:",{anException:anException,aMethodContext:aMethodContext}, smalltalk.JavaScriptException.klass)})}
+}),
+smalltalk.JavaScriptException.klass);
+
+
 smalltalk.addClass('MessageNotUnderstood', smalltalk.Error, ['message', 'receiver'], 'Kernel-Exceptions');
 smalltalk.addMethod(
 "_message",

+ 113 - 0
js/Kernel-Exceptions.js

@@ -170,6 +170,119 @@ referencedClasses: []
 smalltalk.Error.klass);
 
 
+smalltalk.addClass('JavaScriptException', smalltalk.Error, ['exception'], 'Kernel-Exceptions');
+smalltalk.JavaScriptException.comment="A JavaScriptException is thrown when a non-Smalltalk exception occurs while in the Smalltalk stack.\x0aSee `boot.js` `inContext()` and `BlockClosure >> on:do:`"
+smalltalk.addMethod(
+"_context_",
+smalltalk.method({
+selector: "context:",
+category: 'accessing',
+fn: function (aMethodContext){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
self.context = aMethodContext;
+return self}, function($ctx1) {$ctx1.fill(self,"context:",{aMethodContext:aMethodContext}, smalltalk.JavaScriptException)})},
+args: ["aMethodContext"],
+source: "context: aMethodContext\x0a\x09\x22Set the context from the outside.\x0a    See boot.js `inContext()` exception handling\x22\x0a    \x0a    <self.context = aMethodContext>",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.JavaScriptException);
+
+smalltalk.addMethod(
+"_exception",
+smalltalk.method({
+selector: "exception",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
var $1;
+$1=self["@exception"];
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"exception",{}, smalltalk.JavaScriptException)})},
+args: [],
+source: "exception\x0a\x09^ exception",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.JavaScriptException);
+
+smalltalk.addMethod(
+"_exception_",
+smalltalk.method({
+selector: "exception:",
+category: 'accessing',
+fn: function (anException){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
self["@exception"]=anException;
+return self}, function($ctx1) {$ctx1.fill(self,"exception:",{anException:anException}, smalltalk.JavaScriptException)})},
+args: ["anException"],
+source: "exception: anException\x0a\x09exception := anException",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.JavaScriptException);
+
+smalltalk.addMethod(
+"_messageText",
+smalltalk.method({
+selector: "messageText",
+category: 'accessing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
return "A JavaScript exception occured while in the Smalltalk stack!";
+}, function($ctx1) {$ctx1.fill(self,"messageText",{}, smalltalk.JavaScriptException)})},
+args: [],
+source: "messageText\x0a\x09^ 'A JavaScript exception occured while in the Smalltalk stack!'",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.JavaScriptException);
+
+
+smalltalk.addMethod(
+"_on_",
+smalltalk.method({
+selector: "on:",
+category: 'instance creation',
+fn: function (anException){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
var $2,$3,$1;
+$2=_st(self)._new();
+_st($2)._exception_(anException);
+$3=_st($2)._yourself();
+$1=$3;
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"on:",{anException:anException}, smalltalk.JavaScriptException.klass)})},
+args: ["anException"],
+source: "on: anException\x0a\x09^ self new\x0a    \x09exception: anException;\x0a        yourself",
+messageSends: ["exception:", "new", "yourself"],
+referencedClasses: []
+}),
+smalltalk.JavaScriptException.klass);
+
+smalltalk.addMethod(
+"_on_context_",
+smalltalk.method({
+selector: "on:context:",
+category: 'instance creation',
+fn: function (anException,aMethodContext){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
var $2,$3,$1;
+$2=_st(self)._new();
+_st($2)._exception_(anException);
+_st($2)._context_(aMethodContext);
+$3=_st($2)._yourself();
+$1=$3;
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"on:context:",{anException:anException,aMethodContext:aMethodContext}, smalltalk.JavaScriptException.klass)})},
+args: ["anException", "aMethodContext"],
+source: "on: anException context: aMethodContext\x0a\x09^ self new\x0a    \x09exception: anException;\x0a        context: aMethodContext;\x0a        yourself",
+messageSends: ["exception:", "new", "context:", "yourself"],
+referencedClasses: []
+}),
+smalltalk.JavaScriptException.klass);
+
+
 smalltalk.addClass('MessageNotUnderstood', smalltalk.Error, ['message', 'receiver'], 'Kernel-Exceptions');
 smalltalk.MessageNotUnderstood.comment="This exception is provided to support `Object>>doesNotUnderstand:`."
 smalltalk.addMethod(

+ 10 - 10
js/Kernel-Methods.deploy.js

@@ -105,18 +105,18 @@ smalltalk.method({
 selector: "on:do:",
 fn: function (anErrorClass,aBlock){
 var self=this;
-return smalltalk.withContext(function($ctx1) { 
var $2,$3,$5,$4,$1;
-$2=self;
-$3=self;
-$4=(function(error){
-return smalltalk.withContext(function($ctx2) {
$5=_st(error)._isKindOf_(anErrorClass);
-if(smalltalk.assert($5)){
-return _st(aBlock)._value_(error);
+return smalltalk.withContext(function($ctx1) { 
var $2,$1;
+$1=_st(self)._try_catch_(self,(function(error){
+var smalltalkError;
+return smalltalk.withContext(function($ctx2) {
smalltalkError=_st(_st((smalltalk.Smalltalk || Smalltalk))._current())._asSmalltalkError_(error);
+smalltalkError;
+$2=_st(smalltalkError)._isKindOf_(anErrorClass);
+if(smalltalk.assert($2)){
+return _st(aBlock)._value_(smalltalkError);
 } else {
-return _st(error)._signal();
+return _st(smalltalkError)._signal();
 };
-}, function($ctx2) {$ctx2.fillBlock({error:error},$ctx1)})});
-$1=_st($2)._try_catch_($3,$4);
+}, function($ctx2) {$ctx2.fillBlock({error:error,smalltalkError:smalltalkError},$ctx1)})}));
 return $1;
 }, function($ctx1) {$ctx1.fill(self,"on:do:",{anErrorClass:anErrorClass,aBlock:aBlock}, smalltalk.BlockClosure)})}
 }),

+ 13 - 13
js/Kernel-Methods.js

@@ -152,24 +152,24 @@ selector: "on:do:",
 category: 'error handling',
 fn: function (anErrorClass,aBlock){
 var self=this;
-return smalltalk.withContext(function($ctx1) { 
var $2,$3,$5,$4,$1;
-$2=self;
-$3=self;
-$4=(function(error){
-return smalltalk.withContext(function($ctx2) {
$5=_st(error)._isKindOf_(anErrorClass);
-if(smalltalk.assert($5)){
-return _st(aBlock)._value_(error);
+return smalltalk.withContext(function($ctx1) { 
var $2,$1;
+$1=_st(self)._try_catch_(self,(function(error){
+var smalltalkError;
+return smalltalk.withContext(function($ctx2) {
smalltalkError=_st(_st((smalltalk.Smalltalk || Smalltalk))._current())._asSmalltalkError_(error);
+smalltalkError;
+$2=_st(smalltalkError)._isKindOf_(anErrorClass);
+if(smalltalk.assert($2)){
+return _st(aBlock)._value_(smalltalkError);
 } else {
-return _st(error)._signal();
+return _st(smalltalkError)._signal();
 };
-}, function($ctx2) {$ctx2.fillBlock({error:error},$ctx1)})});
-$1=_st($2)._try_catch_($3,$4);
+}, function($ctx2) {$ctx2.fillBlock({error:error,smalltalkError:smalltalkError},$ctx1)})}));
 return $1;
 }, function($ctx1) {$ctx1.fill(self,"on:do:",{anErrorClass:anErrorClass,aBlock:aBlock}, smalltalk.BlockClosure)})},
 args: ["anErrorClass", "aBlock"],
-source: "on: anErrorClass do: aBlock\x0a\x09^self try: self catch: [:error |\x0a\x09    (error isKindOf: anErrorClass) \x0a\x09     ifTrue: [aBlock value: error]\x0a\x09     ifFalse: [error signal]]",
-messageSends: ["try:catch:", "ifTrue:ifFalse:", "value:", "signal", "isKindOf:"],
-referencedClasses: []
+source: "on: anErrorClass do: aBlock\x0a\x09\x22All exceptions thrown in the Smalltalk stack are cought.\x0a    Convert all JS exceptions to JavaScriptException instances.\x22\x0a    \x0a\x09^self try: self catch: [ :error | | smalltalkError |\x0a    \x09smalltalkError := Smalltalk current asSmalltalkError: error.\x0a\x09    (smalltalkError isKindOf: anErrorClass) \x0a\x09     ifTrue: [ aBlock value: smalltalkError ]\x0a\x09     ifFalse: [ smalltalkError signal ] ]",
+messageSends: ["try:catch:", "asSmalltalkError:", "current", "ifTrue:ifFalse:", "value:", "signal", "isKindOf:"],
+referencedClasses: ["Smalltalk"]
 }),
 smalltalk.BlockClosure);
 

+ 31 - 0
js/Kernel-Objects.deploy.js

@@ -2939,6 +2939,26 @@ smalltalk.Random);
 
 
 smalltalk.addClass('Smalltalk', smalltalk.Object, [], 'Kernel-Objects');
+smalltalk.addMethod(
+"_asSmalltalkError_",
+smalltalk.method({
+selector: "asSmalltalkError:",
+fn: function (anError){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
var $2,$1;
+$2=_st(_st(self)._isSmalltalkObject_(anError))._and_((function(){
+return smalltalk.withContext(function($ctx2) {
return _st(anError)._isKindOf_((smalltalk.Error || Error));
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+if(smalltalk.assert($2)){
+$1=anError;
+} else {
+$1=_st((smalltalk.JavaScriptException || JavaScriptException))._on_(anError);
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"asSmalltalkError:",{anError:anError}, smalltalk.Smalltalk)})}
+}),
+smalltalk.Smalltalk);
+
 smalltalk.addMethod(
 "_at_",
 smalltalk.method({
@@ -3023,6 +3043,17 @@ return self}, function($ctx1) {$ctx1.fill(self,"deletePackage:",{packageName:pac
 }),
 smalltalk.Smalltalk);
 
+smalltalk.addMethod(
+"_isSmalltalkObject_",
+smalltalk.method({
+selector: "isSmalltalkObject:",
+fn: function (anObject){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
return typeof anObject.klass !== 'undefined';
+return self}, function($ctx1) {$ctx1.fill(self,"isSmalltalkObject:",{anObject:anObject}, smalltalk.Smalltalk)})}
+}),
+smalltalk.Smalltalk);
+
 smalltalk.addMethod(
 "_packageAt_",
 smalltalk.method({

+ 41 - 0
js/Kernel-Objects.js

@@ -4053,6 +4053,31 @@ smalltalk.Random);
 
 smalltalk.addClass('Smalltalk', smalltalk.Object, [], 'Kernel-Objects');
 smalltalk.Smalltalk.comment="Smalltalk has only one instance, accessed with `Smalltalk current`. \x0aIt represents the global JavaScript variable `smalltalk` declared in `js/boot.js`.\x0a\x0aThe `smalltalk` object holds all class and packages defined in the system.\x0a\x0a## Classes\x0a\x0aClasses can be accessed using the following methods:\x0a\x0a- `#classes` answers the full list of Smalltalk classes in the system\x0a- `#at:` answers a specific class of `nil`\x0a\x0a## Packages\x0a\x0aPackages can be accessed using the following methods:\x0a\x0a- `#packages` answers the full list of packages\x0a- `#packageAt:` answers a specific class of `nil`\x0a\x0a__note:__ classes and packages are accessed using strings, not symbols\x0a\x0a## Parsing\x0a\x0aThe `#parse:` method is used to parse Smalltalk source code. \x0aIt requires the `Compiler` package and the `js/parser.js` parser file in order to work"
+smalltalk.addMethod(
+"_asSmalltalkError_",
+smalltalk.method({
+selector: "asSmalltalkError:",
+category: 'error handling',
+fn: function (anError){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
var $2,$1;
+$2=_st(_st(self)._isSmalltalkObject_(anError))._and_((function(){
+return smalltalk.withContext(function($ctx2) {
return _st(anError)._isKindOf_((smalltalk.Error || Error));
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}));
+if(smalltalk.assert($2)){
+$1=anError;
+} else {
+$1=_st((smalltalk.JavaScriptException || JavaScriptException))._on_(anError);
+};
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"asSmalltalkError:",{anError:anError}, smalltalk.Smalltalk)})},
+args: ["anError"],
+source: "asSmalltalkError: anError\x0a\x09\x22A JavaScript error may be thrown.\x0a    We then need to convert it back to a Smalltalk object\x22\x0a    \x0a    ^ ((self isSmalltalkObject: anError) and: [ anError isKindOf: Error ])\x0a    \x09ifTrue: [ anError ]\x0a      \x09ifFalse: [ JavaScriptException on: anError ]",
+messageSends: ["ifTrue:ifFalse:", "on:", "and:", "isKindOf:", "isSmalltalkObject:"],
+referencedClasses: ["JavaScriptException", "Error"]
+}),
+smalltalk.Smalltalk);
+
 smalltalk.addMethod(
 "_at_",
 smalltalk.method({
@@ -4172,6 +4197,22 @@ referencedClasses: []
 }),
 smalltalk.Smalltalk);
 
+smalltalk.addMethod(
+"_isSmalltalkObject_",
+smalltalk.method({
+selector: "isSmalltalkObject:",
+category: 'testing',
+fn: function (anObject){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
return typeof anObject.klass !== 'undefined';
+return self}, function($ctx1) {$ctx1.fill(self,"isSmalltalkObject:",{anObject:anObject}, smalltalk.Smalltalk)})},
+args: ["anObject"],
+source: "isSmalltalkObject: anObject\x0a\x09\x22Consider anObject a Smalltalk object if it has a 'klass' property.\x0a    Note that this may be unaccurate\x22\x0a    \x0a    <return typeof anObject.klass !== 'undefined'>",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.Smalltalk);
+
 smalltalk.addMethod(
 "_packageAt_",
 smalltalk.method({

+ 42 - 0
js/Kernel-Tests.deploy.js

@@ -1915,6 +1915,48 @@ smalltalk.JSObjectProxyTest);
 
 
 
+smalltalk.addClass('JavaScriptExceptionTest', smalltalk.TestCase, [], 'Kernel-Tests');
+smalltalk.addMethod(
+"_testCatchingException",
+smalltalk.method({
+selector: "testCatchingException",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
_st((function(){
+return smalltalk.withContext(function($ctx2) {
return _st(self)._throwException();
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}))._on_do_((smalltalk.Error || Error),(function(error){
+return smalltalk.withContext(function($ctx2) {
return _st(self)._assert_(_st(_st(error)._exception()).__eq("test"));
+}, function($ctx2) {$ctx2.fillBlock({error:error},$ctx1)})}));
+return self}, function($ctx1) {$ctx1.fill(self,"testCatchingException",{}, smalltalk.JavaScriptExceptionTest)})}
+}),
+smalltalk.JavaScriptExceptionTest);
+
+smalltalk.addMethod(
+"_testRaisingException",
+smalltalk.method({
+selector: "testRaisingException",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
_st(self)._should_raise_((function(){
+return smalltalk.withContext(function($ctx2) {
return _st(self)._throwException();
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}),(smalltalk.JavaScriptException || JavaScriptException));
+return self}, function($ctx1) {$ctx1.fill(self,"testRaisingException",{}, smalltalk.JavaScriptExceptionTest)})}
+}),
+smalltalk.JavaScriptExceptionTest);
+
+smalltalk.addMethod(
+"_throwException",
+smalltalk.method({
+selector: "throwException",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
throw 'test';
+return self}, function($ctx1) {$ctx1.fill(self,"throwException",{}, smalltalk.JavaScriptExceptionTest)})}
+}),
+smalltalk.JavaScriptExceptionTest);
+
+
+
 smalltalk.addClass('NumberTest', smalltalk.TestCase, [], 'Kernel-Tests');
 smalltalk.addMethod(
 "_testAbs",

+ 57 - 0
js/Kernel-Tests.js

@@ -2465,6 +2465,63 @@ smalltalk.JSObjectProxyTest);
 
 
 
+smalltalk.addClass('JavaScriptExceptionTest', smalltalk.TestCase, [], 'Kernel-Tests');
+smalltalk.addMethod(
+"_testCatchingException",
+smalltalk.method({
+selector: "testCatchingException",
+category: 'testing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
_st((function(){
+return smalltalk.withContext(function($ctx2) {
return _st(self)._throwException();
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}))._on_do_((smalltalk.Error || Error),(function(error){
+return smalltalk.withContext(function($ctx2) {
return _st(self)._assert_(_st(_st(error)._exception()).__eq("test"));
+}, function($ctx2) {$ctx2.fillBlock({error:error},$ctx1)})}));
+return self}, function($ctx1) {$ctx1.fill(self,"testCatchingException",{}, smalltalk.JavaScriptExceptionTest)})},
+args: [],
+source: "testCatchingException\x0a\x09[ self throwException ]\x0a  \x09\x09on: Error\x0a        do: [ :error | \x0a\x09\x09\x09self assert: error exception = 'test' ]",
+messageSends: ["on:do:", "assert:", "=", "exception", "throwException"],
+referencedClasses: ["Error"]
+}),
+smalltalk.JavaScriptExceptionTest);
+
+smalltalk.addMethod(
+"_testRaisingException",
+smalltalk.method({
+selector: "testRaisingException",
+category: 'testing',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
_st(self)._should_raise_((function(){
+return smalltalk.withContext(function($ctx2) {
return _st(self)._throwException();
+}, function($ctx2) {$ctx2.fillBlock({},$ctx1)})}),(smalltalk.JavaScriptException || JavaScriptException));
+return self}, function($ctx1) {$ctx1.fill(self,"testRaisingException",{}, smalltalk.JavaScriptExceptionTest)})},
+args: [],
+source: "testRaisingException\x0a\x09self should: [ self throwException ] raise: JavaScriptException",
+messageSends: ["should:raise:", "throwException"],
+referencedClasses: ["JavaScriptException"]
+}),
+smalltalk.JavaScriptExceptionTest);
+
+smalltalk.addMethod(
+"_throwException",
+smalltalk.method({
+selector: "throwException",
+category: 'helpers',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
throw 'test';
+return self}, function($ctx1) {$ctx1.fill(self,"throwException",{}, smalltalk.JavaScriptExceptionTest)})},
+args: [],
+source: "throwException\x0a\x09<throw 'test'>",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.JavaScriptExceptionTest);
+
+
+
 smalltalk.addClass('NumberTest', smalltalk.TestCase, [], 'Kernel-Tests');
 smalltalk.addMethod(
 "_testAbs",

+ 5 - 2
js/boot.js

@@ -526,11 +526,14 @@ function Smalltalk() {
 		} else {
 			try {return inContext(worker, setup)}
 			catch(error) {
-				// Reset the context stack in any case
-				st.thisContext = undefined;
 				if(error.smalltalkError) {
 					handleError(error);
+                } else {
+
+                    handleError(smalltalk.JavaScriptException._on_context_(error, smalltalk.thisContext));
                 }
+				// Reset the context stack in any case
+				st.thisContext = undefined;
                 // Throw the exception anyway, as we want to stop
                 // the execution to avoid infinite loops
 				throw(error);

+ 43 - 0
st/Kernel-Exceptions.st

@@ -62,6 +62,49 @@ signal: aString
 		signal: aString
 ! !
 
+Error subclass: #JavaScriptException
+	instanceVariableNames: 'exception'
+	package: 'Kernel-Exceptions'!
+!JavaScriptException commentStamp!
+A JavaScriptException is thrown when a non-Smalltalk exception occurs while in the Smalltalk stack.
+See `boot.js` `inContext()` and `BlockClosure >> on:do:`!
+
+!JavaScriptException methodsFor: 'accessing'!
+
+context: aMethodContext
+	"Set the context from the outside.
+    See boot.js `inContext()` exception handling"
+    
+    <self.context = aMethodContext>
+!
+
+exception
+	^ exception
+!
+
+exception: anException
+	exception := anException
+!
+
+messageText
+	^ 'A JavaScript exception occured while in the Smalltalk stack!!'
+! !
+
+!JavaScriptException class methodsFor: 'instance creation'!
+
+on: anException
+	^ self new
+    	exception: anException;
+        yourself
+!
+
+on: anException context: aMethodContext
+	^ self new
+    	exception: anException;
+        context: aMethodContext;
+        yourself
+! !
+
 Error subclass: #MessageNotUnderstood
 	instanceVariableNames: 'message receiver'
 	package: 'Kernel-Exceptions'!

+ 8 - 4
st/Kernel-Methods.st

@@ -43,10 +43,14 @@ whileTrue: aBlock
 !BlockClosure methodsFor: 'error handling'!
 
 on: anErrorClass do: aBlock
-	^self try: self catch: [:error |
-	    (error isKindOf: anErrorClass) 
-	     ifTrue: [aBlock value: error]
-	     ifFalse: [error signal]]
+	"All exceptions thrown in the Smalltalk stack are cought.
+    Convert all JS exceptions to JavaScriptException instances."
+    
+	^self try: self catch: [ :error | | smalltalkError |
+    	smalltalkError := Smalltalk current asSmalltalkError: error.
+	    (smalltalkError isKindOf: anErrorClass) 
+	     ifTrue: [ aBlock value: smalltalkError ]
+	     ifFalse: [ smalltalkError signal ] ]
 ! !
 
 !BlockClosure methodsFor: 'evaluating'!

+ 18 - 0
st/Kernel-Objects.st

@@ -1476,6 +1476,15 @@ removeClass: aClass
 
 !Smalltalk methodsFor: 'error handling'!
 
+asSmalltalkError: anError
+	"A JavaScript error may be thrown.
+    We then need to convert it back to a Smalltalk object"
+    
+    ^ ((self isSmalltalkObject: anError) and: [ anError isKindOf: Error ])
+    	ifTrue: [ anError ]
+      	ifFalse: [ JavaScriptException on: anError ]
+!
+
 parseError: anException parsing: aString
 	^ ParseError new messageText: 'Parse error on line ', (anException basicAt: 'line') ,' column ' , (anException basicAt: 'column') ,' : Unexpected character ', (anException basicAt: 'found')
 ! !
@@ -1547,6 +1556,15 @@ createPackage: packageName properties: aDict
     ^ self createPackage: packageName
 ! !
 
+!Smalltalk methodsFor: 'testing'!
+
+isSmalltalkObject: anObject
+	"Consider anObject a Smalltalk object if it has a 'klass' property.
+    Note that this may be unaccurate"
+    
+    <return typeof anObject.klass !!== 'undefined'>
+! !
+
 Smalltalk class instanceVariableNames: 'current'!
 
 !Smalltalk class methodsFor: 'accessing'!

+ 23 - 0
st/Kernel-Tests.st

@@ -973,6 +973,29 @@ testYourself
 	self assert: object d equals: 'test'
 ! !
 
+TestCase subclass: #JavaScriptExceptionTest
+	instanceVariableNames: ''
+	package: 'Kernel-Tests'!
+
+!JavaScriptExceptionTest methodsFor: 'helpers'!
+
+throwException
+	<throw 'test'>
+! !
+
+!JavaScriptExceptionTest methodsFor: 'testing'!
+
+testCatchingException
+	[ self throwException ]
+  		on: Error
+        do: [ :error | 
+			self assert: error exception = 'test' ]
+!
+
+testRaisingException
+	self should: [ self throwException ] raise: JavaScriptException
+! !
+
 TestCase subclass: #NumberTest
 	instanceVariableNames: ''
 	package: 'Kernel-Tests'!