package textbender.o.rhinohide; // Copyright 2006-2007, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Textbender Software"), to deal in the Textbender Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Textbender Software, and to permit persons to whom the Textbender Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Textbender Software. THE TEXTBENDER SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE TEXTBENDER SOFTWARE OR THE USE OR OTHER DEALINGS IN THE TEXTBENDER SOFTWARE. import netscape.javascript.*; import org.w3c.dom.*; import textbender.g.lang.*; import textbender.g.util.logging.*; import textbender.o.rhinohide._.*; /** An overlay/wrapper of a JavaScript object, * based on an intermediate JSObject bridge. * *

BUG: lebensRAM

*
* http://reluk.ca/var/cache/textbender-javadoc/textbender/o/rhinohide/Rhinohide.html#lebensRAM *
*

* Was intermittent on * system/host/tinman/windows.xht, both IE and Firefox. * Different failure modes, including crash of browser. * Probable concurrency bug in the underlying JSObject bridge. *

* Moved to Java 1.6, and added memory to that * Windows box. * Bug has not recurred. *

* * @see LiveConnect documentation * @see JSObject in the LiveConnect API * @see JSObject as implmented for the Java Plug-In */ public @ThreadSafe class Rhinohide { // Thread-safety really depends on the JSObject implementation. // JavaScript itself is naturally single threaded. // The doubt therefore falls on the Java side. But neither LiveConnect docs, // nor Plug-In's JSObject docs say anything about thread-safety/concurrency. /** Constructs a Rhinohide. * * @param window global object * @param jsObject bridge to underlying JavaScript object, per {@linkplain #jsObject() jsObject}() */ public Rhinohide( RhiWindow window, JSObject jsObject ) { if( window == null || jsObject == null ) throw new NullPointerException(); this.window = window; this.jsObject = jsObject; } protected Rhinohide( JSObject jsObject ) // for RhiWindow { if( jsObject == null ) throw new NullPointerException(); this.jsObject = jsObject; } // ------------------------------------------------------------------------------------ /** @return the same object * @throws StunnedRhinoException if the object is null * * @see #callV(String,Object...) * @see #evalV(String) * @see #getMemberV(String) */ public static Object checkV( Object object ) { if( object == null ) throw new StunnedRhinoException(); return object; } /** Bridge to underlying JavaScript object. It is never null. *

* Ordinary clients ought not to call methods * of the underlying JavaScript object directly. * Instead, they ought to call the equivalent methods of this Rhinohide wrapper. *

*/ public final JSObject jsObject() { return jsObject; } private final JSObject jsObject; /** @return rhinohide.jsObject(); or null if rhinohide is null */ public static JSObject toJSObject( Rhinohide rhinohide ) { final JSObject jsObject; if( rhinohide == null ) return null; else jsObject = rhinohide.jsObject(); return jsObject; } /** The global object. */ public RhiWindow window() { return window; } protected RhiWindow window; // final after subclass init // - J - S - O b j e c t -------------------------------------------------------------- /** Calls a method on the underlying JavaScript object, via the bridge. * Equivalent to {@linkplain #jsObject() jsObject}().call(). * Equivalent to JavaScript: *
      *     object.methodName( arguments );
      *     
*/ public Object call( String methodName, Object... arguments ) { try { return jsObject.call( methodName, arguments ); } catch( JSException xJS ) { throw convertedException( xJS ); } } /** Same as {@linkplain #checkV(Object) checkV}({@linkplain #call(String,Object...) call}(methodName, arguments)). * * @throws StunnedRhinoException if the result is null */ public Object callV( String methodName, Object... arguments ) { return checkV( call( methodName, arguments )); } /** Evaluates an expression in the context of the underlying JavaScript object, via the bridge. * Equivalent to {@linkplain #jsObject() jsObject}().eval(). * Similar to JavaScript: *
      *     object.eval( expression );
* *

BUG: stubborn-rhino

*
* http://reluk.ca/var/cache/textbender-javadoc/textbender/o/rhinohide/Rhinohide.html#stubborn-rhino *
*

* Repeatable on * system/host/tinman/windows.xht#firefox. * JSObject.eval() and call() fail, after a time. * Failure may occur some time into applet initialization; * or after interruption by an alert box. * It consistently occurs on that host for all post-init access, * e.g. via RMI worker threads, or the AWT event dispatch thread. *

*

* On failure, JSObject.eval() calls no longer have effect, and always return null. * And JSObject.call(method) always returns null for custom methods; * those defined in page content scripts, * as well as those defined in previous calls to eval(). *

*/ public Object eval( String expression ) { try { return jsObject.eval( expression ); } catch( JSException xJS ) { throw convertedException( xJS ); } } /** Same as {@linkplain #checkV(Object) checkV}({@linkplain #eval(String) eval}(expression)). * * @throws StunnedRhinoException if the result is null */ public Object evalV( String expression ) { return checkV( eval( expression )); } /** Returns a property of the underlying JavaScript object, via the bridge. * Equivalent to {@linkplain #jsObject() jsObject}().getMember(). * Similar to JavaScript: *
      *     object.name;
      *     
*/ public Object getMember( String name ) { try { return jsObject.getMember( name ); } catch( JSException xJS ) { throw convertedException( xJS ); } } /** Same as {@linkplain #checkV(Object) checkV}({@linkplain #getMember(String) getMember}(name)). * * @throws StunnedRhinoException if the result is null */ public Object getMemberV( String name ) { return checkV( getMember( name )); } /** Sets a property of the underlying JavaScript object, via the bridge. * Equivalent to {@linkplain #jsObject() jsObject}().setMember(). * Equivalent to JavaScript: *
          *     object.name = value;
          *     
*/ public void setMember( String name, Object value ) { try { jsObject.setMember( name, value ); } catch( JSException xJS ) { throw convertedException( xJS ); } } // /** Removes a property of the underlying JavaScript object, via the bridge. // * Equivalent to {@linkplain #jsObject() jsObject}().removeMember(). // * Equivalent (presumeably) to JavaScript: // *
 //       *     delete object.name;
 //       *     
// */ // public void removeMember( String name ) { jsObject.removeMember( name ); } // // // // // public Object getSlot( int index ) { return jsObject.getSlot( index ); } // // // public void setSlot( int index, Object value ) { jsObject.setSlot( index, value ); } // // ///// but these methods disavowed by Sun, per warning in: http://java.sun.com/j2se/1.5.0/docs/guide/plugin/developer_guide/java_js.html // - O b j e c t ---------------------------------------------------------------------- /** Returns true iff o is a Rhinohide, * and has an 'equal' {@linkplain #jsObject() JSObject bridge}. */ public @Override boolean equals( Object o ) { if( !(o instanceof Rhinohide )) return false; Rhinohide that = (Rhinohide)o; return this.jsObject.equals( that.jsObject ); } /** Defers to the bridge, returning {@linkplain #jsObject() jsObject}.hashCode(). */ public @Override int hashCode() { return jsObject.hashCode(); } /** Defers to the bridge, returning {@linkplain #jsObject() jsObject}.toString(). */ public @Override String toString() { return jsObject.toString(); } // disavowed as for removeMember(), but how could it fail? //// P r i v a t e /////////////////////////////////////////////////////////////////////// /** @return same exception, or an unwrapped equivalent */ private static RuntimeException convertedException( final JSException xJS ) { // try // { // final Object xWrappedObject = xJS.getWrappedException(); //System.out.println( "R xWrappedObject=" + xWrappedObject ); // if( xWrappedObject instanceof JSObject ) // { // final JSObject xWrappedJSO = (JSObject)xWrappedObject; // Object codeObject = xWrappedJSO.getMember( "code" ); // FIX, this is an imprecise check. Do an eval instead, and use JS typeof and instanceof operators to ensure that it really is a DOMException. // if( codeObject instanceof Number ) // { // LoggerX.i(Rhinohide.class).info("untested code is running"); // Untested because neither Firefox nor IE behave as expected. See textbender.a.b.rhinohideDemo._.Rhinohide_1.domException(). But if that's ever fixed, then remember to add a note to javadocs of call, eval, etc. above, stating that DOMException etc. are auto-unwrapped. // return new DOMException // ( ((Number)codeObject).shortValue(), xWrappedJSO.toString() ); // } // } // } // catch( ThreadDeath d ) { throw d; } // always let it die // catch( Error e ) { throw e; } // catch( Throwable t ) // normal code does not catch Throwable, but this is not normal code // { // try // { // LoggerX.i(Rhinohide.class).log( LoggerX.WARNING, /*message*/"", t ); // } // catch( ThreadDeath d2 ) { throw d2; } // always let it die // catch( Error e2 ) { throw e2; } // catch( Throwable t2 ) {} // } ///// no point, per 'Untested' above return xJS; } }