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.javascript.host;
16  
17  import java.util.Set;
18  
19  import org.htmlunit.Page;
20  import org.htmlunit.WebWindow;
21  import org.htmlunit.corejs.javascript.Context;
22  import org.htmlunit.corejs.javascript.Function;
23  import org.htmlunit.corejs.javascript.FunctionObject;
24  import org.htmlunit.corejs.javascript.Scriptable;
25  import org.htmlunit.javascript.AbstractJavaScriptEngine;
26  import org.htmlunit.javascript.HtmlUnitContextFactory;
27  import org.htmlunit.javascript.JavaScriptEngine;
28  import org.htmlunit.javascript.PostponedAction;
29  import org.htmlunit.javascript.configuration.JsxClass;
30  import org.htmlunit.javascript.configuration.JsxConstructor;
31  import org.htmlunit.javascript.configuration.JsxFunction;
32  import org.htmlunit.javascript.configuration.JsxGetter;
33  import org.htmlunit.javascript.configuration.JsxSetter;
34  import org.htmlunit.javascript.host.event.Event;
35  import org.htmlunit.javascript.host.event.EventTarget;
36  import org.htmlunit.javascript.host.event.MessageEvent;
37  import org.htmlunit.util.UrlUtils;
38  
39  /**
40   * A JavaScript object for {@code BroadcastChannel}.
41   *
42   * @author Ronald Brill
43   */
44  @JsxClass
45  public class BroadcastChannel extends EventTarget {
46  
47      private String name_;
48  
49      /**
50       * JavaScript constructor.
51       * @param cx the current context
52       * @param scope the scope
53       * @param args the arguments to the WebSocket constructor
54       * @param ctorObj the function object
55       * @param inNewExpr Is new or not
56       * @return the java object to allow JavaScript to access
57       */
58      @JsxConstructor
59      public static BroadcastChannel jsConstructor(final Context cx, final Scriptable scope,
60              final Object[] args, final Function ctorObj, final boolean inNewExpr) {
61          if (args.length < 1 || JavaScriptEngine.isUndefined(args[0])) {
62              throw JavaScriptEngine.typeError("BroadcastChannel constructor requires a channel name argument");
63          }
64  
65          final BroadcastChannel broadcastChannel = new BroadcastChannel();
66          final Window window = getWindow(ctorObj);
67          broadcastChannel.setParentScope(window);
68          broadcastChannel.setPrototype(((FunctionObject) ctorObj).getClassPrototype());
69  
70          broadcastChannel.name_ = JavaScriptEngine.toString(args[0]);
71  
72          final Set<BroadcastChannel> broadcastChannels = window.getWebWindow().getWebClient().getBroadcastChannels();
73          synchronized (broadcastChannels) {
74              broadcastChannels.add(broadcastChannel);
75          }
76  
77          return broadcastChannel;
78      }
79  
80      /**
81       * Returns the channel name.
82       * @return the channel name
83       */
84      @JsxGetter
85      public String getName() {
86          return name_;
87      }
88  
89      /**
90       * Returns the value of the {@code onmessage} property.
91       * @return the value of the {@code onmessage} property
92       */
93      @JsxGetter
94      public Function getOnmessage() {
95          return getEventListenersContainer().getEventHandler(Event.TYPE_MESSAGE);
96      }
97  
98      /**
99       * Sets the value of the {@code onmessage} property.
100      * @param onmessage the value of the {@code onmessage} property
101      */
102     @JsxSetter
103     public void setOnmessage(final Object onmessage) {
104         getEventListenersContainer().setEventHandler(Event.TYPE_MESSAGE, onmessage);
105     }
106 
107     /**
108      * Returns the value of the {@code onmessageerror} property.
109      * @return the value of the {@code onmessageerror} property
110      */
111     @JsxGetter
112     public Function getOnmessageerror() {
113         return getEventListenersContainer().getEventHandler("messageerror");
114     }
115 
116     /**
117      * Sets the value of the {@code onmessageerror} property.
118      * @param onmessageerror the value of the {@code onmessageerror} property
119      */
120     @JsxSetter
121     public void setOnmessageerror(final Object onmessageerror) {
122         getEventListenersContainer().setEventHandler("messageerror", onmessageerror);
123     }
124 
125     /**
126      * Posts a message to all other BroadcastChannel objects with the same name.
127      * @param message the message to send
128      * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel/postMessage">MDN documentation</a>
129      */
130     @JsxFunction
131     public void postMessage(final Object message) {
132         if (name_ == null) {
133             return;
134         }
135 
136         final Window w = getWindow();
137         final WebWindow webWindow = w.getWebWindow();
138         final Page page = webWindow.getEnclosedPage();
139         final java.net.URL currentURL = page.getUrl();
140         final String origin = currentURL.getProtocol() + "://" + currentURL.getHost() + ':' + currentURL.getPort();
141 
142         final AbstractJavaScriptEngine<?> jsEngine = webWindow.getWebClient().getJavaScriptEngine();
143 
144         final Set<BroadcastChannel> broadcastChannels = webWindow.getWebClient().getBroadcastChannels();
145 
146         synchronized (broadcastChannels) {
147             for (final BroadcastChannel channel : broadcastChannels) {
148                 if (channel != this && name_.equals(channel.name_)) {
149                     final Window channelWindow = channel.getWindow();
150                     final WebWindow channelWebWindow = channelWindow.getWebWindow();
151                     final Page channelPage = channelWebWindow.getEnclosedPage();
152 
153                     if (UrlUtils.isSameOrigin(currentURL, channelPage.getUrl())) {
154                         final Scriptable ports = JavaScriptEngine.newArray(w, 0);
155 
156                         final MessageEvent event = new MessageEvent();
157                         event.initMessageEvent(Event.TYPE_MESSAGE, false, false, message, origin, "", null, ports);
158                         event.setParentScope(channelWindow);
159                         event.setPrototype(channelWindow.getPrototype(event.getClass()));
160 
161                         final PostponedAction action =
162                                 new PostponedAction(channelPage, "BroadcastChannel.postMessage") {
163                                 @Override
164                                 public void execute() {
165                                     final HtmlUnitContextFactory cf = jsEngine.getContextFactory();
166                                     cf.call(cx -> channel.dispatchEvent(event));
167                                 }
168                             };
169                         jsEngine.addPostponedAction(action);
170                     }
171                 }
172             }
173         }
174     }
175 
176     /**
177      * Closes the BroadcastChannel object, indicating it won't get any new messages,
178      * and allowing it to be garbage collected.
179      */
180     @JsxFunction
181     public void close() {
182         final Set<BroadcastChannel> broadcastChannels =
183                 getWindow().getWebWindow().getWebClient().getBroadcastChannels();
184         synchronized (broadcastChannels) {
185             broadcastChannels.remove(this);
186         }
187         name_ = null;
188     }
189 }