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