View Javadoc
1   /*
2    * Copyright (c) 2002-2017 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package com.gargoylesoftware.htmlunit.activex.javascript.msxml;
16  
17  import java.lang.reflect.Method;
18  import java.util.HashMap;
19  import java.util.Map;
20  import java.util.Map.Entry;
21  
22  import org.apache.commons.lang3.StringUtils;
23  
24  import com.gargoylesoftware.htmlunit.BrowserVersion;
25  import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
26  import com.gargoylesoftware.htmlunit.javascript.configuration.ClassConfiguration;
27  import com.gargoylesoftware.htmlunit.javascript.configuration.ClassConfiguration.ConstantInfo;
28  import com.gargoylesoftware.htmlunit.javascript.configuration.ClassConfiguration.PropertyInfo;
29  
30  import net.sourceforge.htmlunit.corejs.javascript.FunctionObject;
31  import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
32  import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
33  
34  /**
35   * JavaScript environment for the MSXML ActiveX library.
36   *
37   * @author Frank Danek
38   */
39  public class MSXMLJavaScriptEnvironment {
40  
41      private final MSXMLConfiguration config_;
42  
43      private Map<Class<? extends MSXMLScriptable>, Scriptable> prototypes_;
44  
45      /**
46       * Creates an instance for the given {@link BrowserVersion}.
47       *
48       * @param browserVersion the browser version to use
49       * @throws Exception if something goes wrong
50       */
51      @SuppressWarnings("unchecked")
52      public MSXMLJavaScriptEnvironment(final BrowserVersion browserVersion) throws Exception {
53          config_ = MSXMLConfiguration.getInstance(browserVersion);
54  
55          final Map<String, ScriptableObject> prototypesPerJSName = new HashMap<>();
56          final Map<Class<? extends MSXMLScriptable>, Scriptable> prototypes = new HashMap<>();
57  
58          // put custom object to be called as very last prototype to call the fallback getter (if any)
59  
60          for (final ClassConfiguration config : config_.getAll()) {
61              final ScriptableObject prototype = configureClass(config);
62              if (config.isJsObject()) {
63                  prototypes.put((Class<? extends MSXMLScriptable>) config.getHostClass(), prototype);
64              }
65              prototypesPerJSName.put(config.getHostClass().getSimpleName(), prototype);
66          }
67  
68          // once all prototypes have been build, it's possible to configure the chains
69  //        final Scriptable objectPrototype = ScriptableObject.getObjectPrototype(window);
70          for (final Map.Entry<String, ScriptableObject> entry : prototypesPerJSName.entrySet()) {
71              final String name = entry.getKey();
72              final ClassConfiguration config = config_.getClassConfiguration(name);
73              Scriptable prototype = entry.getValue();
74              if (prototype.getPrototype() != null) {
75                  prototype = prototype.getPrototype(); // "double prototype" hack for FF
76              }
77              if (!StringUtils.isEmpty(config.getExtendedClassName())) {
78                  final Scriptable parentPrototype = prototypesPerJSName.get(config.getExtendedClassName());
79                  prototype.setPrototype(parentPrototype);
80              }
81              else {
82  //                prototype.setPrototype(objectPrototype);
83  //                prototype.setPrototype(fallbackCaller);
84              }
85          }
86  
87          prototypes_ = prototypes;
88      }
89  
90      /**
91       * Configures the specified class for access via JavaScript.
92       * @param config the configuration settings for the class to be configured
93       * @param window the scope within which to configure the class
94       * @throws InstantiationException if the new class cannot be instantiated
95       * @throws IllegalAccessException if we don't have access to create the new instance
96       * @return the created prototype
97       */
98      private static ScriptableObject configureClass(final ClassConfiguration config/*, final Scriptable window*/)
99          throws InstantiationException, IllegalAccessException {
100 
101         final Class<?> jsHostClass = config.getHostClass();
102         final ScriptableObject prototype = (ScriptableObject) jsHostClass.newInstance();
103 //        prototype.setParentScope(window);
104 
105         configureConstantsPropertiesAndFunctions(config, prototype);
106 
107         return prototype;
108     }
109 
110     /**
111      * Configures constants, properties and functions on the object.
112      * @param config the configuration for the object
113      * @param scriptable the object to configure
114      */
115     private static void configureConstantsPropertiesAndFunctions(final ClassConfiguration config,
116             final ScriptableObject scriptable) {
117 
118         // the constants
119         configureConstants(config, scriptable);
120 
121         // the properties
122         final Map<String, PropertyInfo> propertyMap = config.getPropertyMap();
123         for (final String propertyName : propertyMap.keySet()) {
124             final PropertyInfo info = propertyMap.get(propertyName);
125             final Method readMethod = info.getReadMethod();
126             final Method writeMethod = info.getWriteMethod();
127             scriptable.defineProperty(propertyName, null, readMethod, writeMethod, ScriptableObject.EMPTY);
128         }
129 
130         final int attributes = ScriptableObject.DONTENUM;
131         // the functions
132         for (final Entry<String, Method> functionInfo : config.getFunctionEntries()) {
133             final String functionName = functionInfo.getKey();
134             final Method method = functionInfo.getValue();
135             final FunctionObject functionObject = new FunctionObject(functionName, method, scriptable);
136             scriptable.defineProperty(functionName, functionObject, attributes);
137         }
138     }
139 
140     private static void configureConstants(final ClassConfiguration config,
141             final ScriptableObject scriptable) {
142         for (final ConstantInfo constantInfo : config.getConstants()) {
143             scriptable.defineProperty(constantInfo.getName(), constantInfo.getValue(),
144                     ScriptableObject.READONLY | ScriptableObject.PERMANENT);
145         }
146     }
147 
148     /**
149      * Gets the class of the JavaScript object for the node class.
150      * @param c the node class <code>DomNode</code> or some subclass.
151      * @return {@code null} if none found
152      */
153     @SuppressWarnings("unchecked")
154     public Class<? extends MSXMLScriptable> getJavaScriptClass(final Class<?> c) {
155         return (Class<? extends MSXMLScriptable>) config_.getDomJavaScriptMapping().get(c);
156     }
157 
158     /**
159      * Returns the prototype object corresponding to the specified HtmlUnit class inside the window scope.
160      * @param jsClass the class whose prototype is to be returned
161      * @return the prototype object corresponding to the specified class inside the specified scope
162      */
163     public Scriptable getPrototype(final Class<? extends SimpleScriptable> jsClass) {
164         return prototypes_.get(jsClass);
165     }
166 }