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