View Javadoc
1   /*
2    * Copyright (c) 2002-2025 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    * https://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 org.htmlunit;
16  
17  import static org.htmlunit.BrowserVersionFeatures.JS_WINDOW_COMPUTED_STYLE_PSEUDO_ACCEPT_WITHOUT_COLON;
18  import static org.htmlunit.BrowserVersionFeatures.JS_WINDOW_OUTER_INNER_HEIGHT_DIFF_138;
19  import static org.htmlunit.BrowserVersionFeatures.JS_WINDOW_OUTER_INNER_HEIGHT_DIFF_147;
20  import static org.htmlunit.BrowserVersionFeatures.JS_WINDOW_OUTER_INNER_HEIGHT_DIFF_94;
21  
22  import java.io.IOException;
23  import java.io.ObjectInputStream;
24  import java.io.ObjectOutputStream;
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.htmlunit.css.ComputedCssStyleDeclaration;
31  import org.htmlunit.css.CssStyleSheet;
32  import org.htmlunit.css.ElementCssStyleDeclaration;
33  import org.htmlunit.html.DomElement;
34  import org.htmlunit.html.HtmlPage;
35  import org.htmlunit.javascript.HtmlUnitScriptable;
36  import org.htmlunit.javascript.background.BackgroundJavaScriptFactory;
37  import org.htmlunit.javascript.background.JavaScriptJobManager;
38  import org.htmlunit.javascript.host.Window;
39  import org.w3c.dom.Document;
40  
41  /**
42   * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
43   *
44   * Base class for common WebWindow functionality. While public, this class is not
45   * exposed in any other places of the API. Internally we can cast to this class
46   * when we need access to functionality that is not present in {@link WebWindow}
47   *
48   * @author Brad Clarke
49   * @author David K. Taylor
50   * @author Ahmed Ashour
51   * @author Ronald Brill
52   * @author Sven Strickroth
53   */
54  public abstract class WebWindowImpl implements WebWindow {
55  
56      private static final Log LOG = LogFactory.getLog(WebWindowImpl.class);
57  
58      private final WebClient webClient_;
59      private final Screen screen_;
60      private Page enclosedPage_;
61      private transient HtmlUnitScriptable scriptObject_;
62      private JavaScriptJobManager jobManager_;
63      private final List<WebWindowImpl> childWindows_ = new ArrayList<>();
64      private String name_ = "";
65      private final History history_ = new History(this);
66      private boolean closed_;
67  
68      private int innerHeight_;
69      private int outerHeight_;
70      private int innerWidth_;
71      private int outerWidth_;
72  
73      /**
74       * Creates a window and associates it with the client.
75       *
76       * @param webClient the web client that "owns" this window
77       */
78      public WebWindowImpl(final WebClient webClient) {
79          WebAssert.notNull("webClient", webClient);
80          webClient_ = webClient;
81          jobManager_ = BackgroundJavaScriptFactory.theFactory().createJavaScriptJobManager(this);
82  
83          screen_ = new Screen(webClient);
84  
85          innerHeight_ = 605;
86          innerWidth_ = 1256;
87          if (webClient.getBrowserVersion().hasFeature(JS_WINDOW_OUTER_INNER_HEIGHT_DIFF_94)) {
88              outerHeight_ = innerHeight_ + 94;
89              outerWidth_ = innerWidth_ + 16;
90          }
91          else if (webClient.getBrowserVersion().hasFeature(JS_WINDOW_OUTER_INNER_HEIGHT_DIFF_138)) {
92              outerHeight_ = innerHeight_ + 138;
93              outerWidth_ = innerWidth_ + 24;
94          }
95          else if (webClient.getBrowserVersion().hasFeature(JS_WINDOW_OUTER_INNER_HEIGHT_DIFF_147)) {
96              outerHeight_ = innerHeight_ + 147;
97              outerWidth_ = innerWidth_ + 16;
98          }
99          else {
100             outerHeight_ = innerHeight_ + 115;
101             outerWidth_ = innerWidth_ + 14;
102         }
103     }
104 
105     /**
106      * Registers the window with the client.
107      */
108     protected void performRegistration() {
109         webClient_.registerWebWindow(this);
110     }
111 
112     /**
113      * {@inheritDoc}
114      */
115     @Override
116     public WebClient getWebClient() {
117         return webClient_;
118     }
119 
120     /**
121      * {@inheritDoc}
122      */
123     @Override
124     public Screen getScreen() {
125         return screen_;
126     }
127 
128     /**
129      * {@inheritDoc}
130      */
131     @Override
132     public Page getEnclosedPage() {
133         return enclosedPage_;
134     }
135 
136     /**
137      * {@inheritDoc}
138      */
139     @Override
140     public void setEnclosedPage(final Page page) {
141         if (LOG.isDebugEnabled()) {
142             LOG.debug("setEnclosedPage: " + page);
143         }
144         if (page == enclosedPage_) {
145             return;
146         }
147         destroyChildren();
148 
149         if (isJavaScriptInitializationNeeded(page)) {
150             webClient_.initialize(this, page);
151         }
152         if (webClient_.isJavaScriptEngineEnabled()) {
153             final Window window = getScriptableObject();
154             window.initialize(page);
155         }
156 
157         // has to be done after page initialization to make sure
158         // the enclosedPage has a scriptable object
159         enclosedPage_ = page;
160         history_.addPage(page);
161     }
162 
163     /**
164      * Returns {@code true} if this window needs JavaScript initialization to occur when the enclosed page is set.
165      * @param page the page that will become the enclosing page
166      * @return {@code true} if this window needs JavaScript initialization to occur when the enclosed page is set
167      */
168     protected abstract boolean isJavaScriptInitializationNeeded(Page page);
169 
170     /**
171      * {@inheritDoc}
172      */
173     @Override
174     public <T extends HtmlUnitScriptable> void setScriptableObject(final T scriptObject) {
175         scriptObject_ = scriptObject;
176     }
177 
178     /**
179      * {@inheritDoc}
180      */
181     @Override
182     @SuppressWarnings("unchecked")
183     public <T> T getScriptableObject() {
184         return (T) scriptObject_;
185     }
186 
187     /**
188      * {@inheritDoc}
189      */
190     @Override
191     public JavaScriptJobManager getJobManager() {
192         return jobManager_;
193     }
194 
195     /**
196      * <p><span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span></p>
197      *
198      * <p>Sets the JavaScript job manager for this window.</p>
199      *
200      * @param jobManager the JavaScript job manager to use
201      */
202     public void setJobManager(final JavaScriptJobManager jobManager) {
203         jobManager_ = jobManager;
204     }
205 
206     /**
207      * <p><span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span></p>
208      *
209      * <p>Adds a child to this window, for shutdown purposes.</p>
210      *
211      * @param child the child window to associate with this window
212      */
213     public void addChildWindow(final WebWindowImpl child) {
214         synchronized (childWindows_) {
215             childWindows_.add(child);
216         }
217     }
218 
219     /**
220      * Destroy our children.
221      */
222     protected void destroyChildren() {
223         LOG.debug("destroyChildren");
224         getJobManager().removeAllJobs();
225 
226         // try to deal with js thread adding a new window in between
227         while (!childWindows_.isEmpty()) {
228             final WebWindowImpl window = childWindows_.get(0);
229             removeChildWindow(window);
230         }
231     }
232 
233     /**
234      * <p><span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span></p>
235      *
236      * Destroy the child window.
237      * @param window the child to destroy
238      */
239     public void removeChildWindow(final WebWindowImpl window) {
240         if (LOG.isDebugEnabled()) {
241             LOG.debug("closing child window: " + window);
242         }
243         window.setClosed();
244         window.getJobManager().shutdown();
245         final Page page = window.getEnclosedPage();
246         if (page != null) {
247             page.cleanUp();
248         }
249         window.destroyChildren();
250 
251         synchronized (childWindows_) {
252             childWindows_.remove(window);
253         }
254         webClient_.deregisterWebWindow(window);
255     }
256 
257     /**
258      * {@inheritDoc}
259      */
260     @Override
261     public String getName() {
262         return name_;
263     }
264 
265     /**
266      * {@inheritDoc}
267      */
268     @Override
269     public void setName(final String name) {
270         name_ = name;
271     }
272 
273     /**
274      * Returns this window's navigation history.
275      * @return this window's navigation history
276      */
277     @Override
278     public History getHistory() {
279         return history_;
280     }
281 
282     /**
283      * {@inheritDoc}
284      */
285     @Override
286     public boolean isClosed() {
287         return closed_;
288     }
289 
290     /**
291      * Sets this window as closed.
292      */
293     protected void setClosed() {
294         closed_ = true;
295     }
296 
297     /**
298      * {@inheritDoc}
299      */
300     @Override
301     public int getInnerWidth() {
302         return innerWidth_;
303     }
304 
305     /**
306      * {@inheritDoc}
307      */
308     @Override
309     public void setInnerWidth(final int innerWidth) {
310         innerWidth_ = innerWidth;
311     }
312 
313     /**
314      * {@inheritDoc}
315      */
316     @Override
317     public int getOuterWidth() {
318         return outerWidth_;
319     }
320 
321     /**
322      * {@inheritDoc}
323      */
324     @Override
325     public void setOuterWidth(final int outerWidth) {
326         outerWidth_ = outerWidth;
327     }
328 
329     /**
330      * {@inheritDoc}
331      */
332     @Override
333     public int getInnerHeight() {
334         return innerHeight_;
335     }
336 
337     /**
338      * {@inheritDoc}
339      */
340     @Override
341     public void setInnerHeight(final int innerHeight) {
342         innerHeight_ = innerHeight;
343     }
344 
345     /**
346      * {@inheritDoc}
347      */
348     @Override
349     public int getOuterHeight() {
350         return outerHeight_;
351     }
352 
353     /**
354      * {@inheritDoc}
355      */
356     @Override
357     public void setOuterHeight(final int outerHeight) {
358         outerHeight_ = outerHeight;
359     }
360 
361     private void writeObject(final ObjectOutputStream oos) throws IOException {
362         oos.defaultWriteObject();
363         oos.writeObject(scriptObject_);
364     }
365 
366     private void readObject(final ObjectInputStream ois) throws ClassNotFoundException, IOException {
367         ois.defaultReadObject();
368         scriptObject_ = (HtmlUnitScriptable) ois.readObject();
369     }
370 
371     /**
372      * {@inheritDoc}
373      */
374     @Override
375     public ComputedCssStyleDeclaration getComputedStyle(final DomElement element, final String pseudoElement) {
376         String normalizedPseudo = pseudoElement;
377         if (normalizedPseudo != null) {
378             if (normalizedPseudo.startsWith("::")) {
379                 normalizedPseudo = normalizedPseudo.substring(1);
380             }
381             else if (getWebClient().getBrowserVersion().hasFeature(JS_WINDOW_COMPUTED_STYLE_PSEUDO_ACCEPT_WITHOUT_COLON)
382                     && normalizedPseudo.length() > 0 && normalizedPseudo.charAt(0) != ':') {
383                 normalizedPseudo = ":" + normalizedPseudo;
384             }
385         }
386 
387         final SgmlPage sgmlPage = element.getPage();
388         if (sgmlPage instanceof HtmlPage) {
389             final ComputedCssStyleDeclaration styleFromCache =
390                     ((HtmlPage) sgmlPage).getStyleFromCache(element, normalizedPseudo);
391             if (styleFromCache != null) {
392                 return styleFromCache;
393             }
394         }
395 
396         final ComputedCssStyleDeclaration computedsStyleDeclaration =
397                 new ComputedCssStyleDeclaration(new ElementCssStyleDeclaration(element));
398 
399         final Document ownerDocument = element.getOwnerDocument();
400         if (ownerDocument instanceof HtmlPage) {
401             final HtmlPage htmlPage = (HtmlPage) ownerDocument;
402 
403             final WebClient webClient = getWebClient();
404 
405             if (webClient.getOptions().isCssEnabled()) {
406                 final boolean trace = LOG.isTraceEnabled();
407                 for (final CssStyleSheet cssStyleSheet : htmlPage.getStyleSheets()) {
408                     if (cssStyleSheet != null
409                             && cssStyleSheet.isEnabled()
410                             && cssStyleSheet.isActive()) {
411                         if (trace) {
412                             LOG.trace("modifyIfNecessary: " + cssStyleSheet
413                                         + ", " + computedsStyleDeclaration + ", " + element);
414                         }
415                         cssStyleSheet.modifyIfNecessary(computedsStyleDeclaration, element, normalizedPseudo);
416                     }
417                 }
418             }
419 
420             ((HtmlPage) element.getPage()).putStyleIntoCache(element, normalizedPseudo, computedsStyleDeclaration);
421         }
422 
423         return computedsStyleDeclaration;
424     }
425 }