xref: /trunk/main/rhino/rhino1_7_7_2.patch (revision a0d841013a7b93e3bcedcaed86403bb1797490bd)
1diff -ruN a/b/rhino1.7.7.2/build.properties a/b/rhino1.7.7.2/build.properties
2--- a/b/rhino1.7.7.2/build.properties   2026-10-31 20:42:49.489534187 -0500
3+++ a/b/rhino1.7.7.2/build.properties   2026-10-31 20:42:49.840321657 -0500
4@@ -23,4 +23,4 @@
5 source-level: 1.6
6
7 # jar generation settings
8-jar-compression: true
9+jar-compression: true
10\ No newline at end of file
11diff -ruN a/b/rhino1.7.7.2/build.xml a/b/rhino1.7.7.2/build.xml
12--- a/b/rhino1.7.7.2/build.xml  1969-12-31 19:00:00.000000000 -0500
13+++ b/b/rhino1.7.7.2/build.xml  2026-11-03 00:00:00.000000000 -0500
14@@ -0,0 +1,87 @@
15+<?xml version="1.0" encoding="UTF-8"?>
16+<!-- This Source Code Form is subject to the terms of the Mozilla Public
17+   - License, v. 2.0. If a copy of the MPL was not distributed with this
18+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
19+
20+<project name="rhino" default="jar" basedir=".">
21+
22+  <target name="init">
23+    <property file="build.properties"/>
24+    <property name="src.dir" value="src"/>
25+    <property name="build.dir" value="${build.dir}"/>
26+    <property name="classes.dir" value="${classes}"/>
27+    <property name="rhino.jar" value="${rhino.jar}"/>
28+    <property name="small-rhino.jar" value="${small-rhino.jar}"/>
29+    <property name="dist.dir" value="${dist.dir}"/>
30+    <property name="dist.name" value="${dist.name}"/>
31+
32+    <mkdir dir="${classes.dir}"/>
33+    <mkdir dir="${build.dir}"/>
34+    <mkdir dir="${dist.dir}"/>
35+  </target>
36+
37+  <target name="compile" depends="init">
38+    <javac srcdir="${src.dir}"
39+           destdir="${classes.dir}"
40+           debug="${debug}"
41+           target="${target-jvm}"
42+           source="${source-level}"
43+           includeantruntime="false"
44+           encoding="UTF-8">
45+      <compilerarg value="-Xlint:-options"/>
46+    </javac>
47+  </target>
48+
49+  <target name="jar" depends="compile">
50+    <jar destfile="${build.dir}/${rhino.jar}"
51+         compress="${jar-compression}">
52+      <manifest>
53+        <attribute name="Manifest-Version" value="1.0"/>
54+        <attribute name="Implementation-Title" value="Rhino"/>
55+        <attribute name="Implementation-Version" value="1.7.7.2"/>
56+      </manifest>
57+      <fileset dir="${classes.dir}"/>
58+      <fileset dir="${basedir}/src" includes="**/*.properties"/>
59+    </jar>
60+  </target>
61+
62+  <target name="small-jar" depends="compile">
63+    <jar destfile="${build.dir}/${small-rhino.jar}"
64+         compress="${jar-compression}">
65+      <manifest>
66+        <attribute name="Manifest-Version" value="1.0"/>
67+        <attribute name="Implementation-Title" value="Rhino"/>
68+        <attribute name="Implementation-Version" value="1.7.7.2"/>
69+      </manifest>
70+      <fileset dir="${classes.dir}">
71+        <exclude name="**/tools/**"/>
72+        <exclude name="**/debugger/**"/>
73+        <exclude name="**/shell/**"/>
74+        <exclude name="**/jsc/**"/>
75+        <exclude name="**/idswitch/**"/>
76+        <exclude name="**/classfile/**"/>
77+      </fileset>
78+      <fileset dir="${basedir}/src" includes="**/*.properties"/>
79+    </jar>
80+  </target>
81+
82+  <target name="dist" depends="jar, small-jar">
83+    <mkdir dir="${dist.dir}/${dist.name}/lib"/>
84+    <copy file="${build.dir}/${rhino.jar}"
85+          tofile="${dist.dir}/${dist.name}/lib/${rhino.jar}"/>
86+    <copy file="${build.dir}/${small-rhino.jar}"
87+          tofile="${dist.dir}/${dist.name}/lib/${small-rhino.jar}"/>
88+    <copy file="LICENSE.txt"
89+          todir="${dist.dir}/${dist.name}"/>
90+    <copy file="README.md"
91+          todir="${dist.dir}/${dist.name}"/>
92+    <copy file="RELEASE-NOTES.md"
93+          todir="${dist.dir}/${dist.name}"/>
94+  </target>
95+
96+  <target name="clean">
97+    <delete dir="${build.dir}"/>
98+    <delete dir="${dist.dir}"/>
99+  </target>
100+
101+</project>
102+
103+</project>
104\ No newline at end of file
105diff -ruN a/b/rhino1.7.7.2/src/org/mozilla/javascript/DefiningClassLoader.java a/b/rhino1.7.7.2/src/org/mozilla/javascript/DefiningClassLoader.java
106--- a/b/rhino1.7.7.2/src/org/mozilla/javascript/DefiningClassLoader.java    2026-10-31 20:42:49.607742394 -0500
107+++ a/b/rhino1.7.7.2/src/org/mozilla/javascript/DefiningClassLoader.java    2026-10-31 20:42:49.766998760 -0500
108@@ -4,6 +4,9 @@
109
110 package org.mozilla.javascript;
111
112+import java.lang.reflect.Method;
113+import java.lang.reflect.InvocationTargetException;
114+
115 /**
116  * Load generated classes.
117  *
118@@ -14,10 +17,30 @@
119 {
120     public DefiningClassLoader() {
121         this.parentLoader = getClass().getClassLoader();
122+        init();
123     }
124
125     public DefiningClassLoader(ClassLoader parentLoader) {
126         this.parentLoader = parentLoader;
127+        init();
128+    }
129+
130+    private void init() {
131+        this.contextLoader = null;
132+        if (method_getContextClassLoader != null) {
133+            try {
134+                this.contextLoader = (ClassLoader)
135+                    method_getContextClassLoader.invoke(
136+                        Thread.currentThread(),
137+                        ScriptRuntime.emptyArgs);
138+            } catch (IllegalAccessException ex) {
139+            } catch (InvocationTargetException ex) {
140+            } catch (SecurityException ex) {
141+            }
142+            if (this.contextLoader == this.parentLoader) {
143+                this.contextLoader = null;
144+            }
145+        }
146     }
147
148     public Class<?> defineClass(String name, byte[] data) {
149@@ -38,10 +61,14 @@
150     {
151         Class<?> cl = findLoadedClass(name);
152         if (cl == null) {
153-            if (parentLoader != null) {
154-                cl = parentLoader.loadClass(name);
155+            if (contextLoader == null) {
156+                cl = loadFromParent(name);
157             } else {
158-                cl = findSystemClass(name);
159+                try {
160+                    cl = loadFromParent(name);
161+                } catch (ClassNotFoundException ex) {
162+                    cl = contextLoader.loadClass(name);
163+                }
164             }
165         }
166         if (resolve) {
167@@ -50,5 +77,36 @@
168         return cl;
169     }
170
171+    private Class loadFromParent(String name)
172+        throws ClassNotFoundException
173+    {
174+        if (parentLoader != null) {
175+            return parentLoader.loadClass(name);
176+        } else {
177+            return findSystemClass(name);
178+        }
179+    }
180+
181     private final ClassLoader parentLoader;
182+
183+    private ClassLoader contextLoader;
184+
185+    // We'd like to use "Thread.getContextClassLoader", but
186+    // that's only available on Java2.
187+    private static Method method_getContextClassLoader;
188+
189+    static {
190+        try {
191+            // Don't use "Thread.class": that performs the lookup
192+            // in the class initializer, which doesn't allow us to
193+            // catch possible security exceptions.
194+            Class threadClass = Class.forName("java.lang.Thread");
195+            method_getContextClassLoader =
196+                threadClass.getDeclaredMethod("getContextClassLoader",
197+                                               new Class[0]);
198+        } catch (ClassNotFoundException e) {
199+        } catch (NoSuchMethodException e) {
200+        } catch (SecurityException e) {
201+        }
202+    }
203 }
204diff -ruN a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/Dim.java a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/Dim.java
205--- a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/Dim.java 2026-10-31 20:42:49.648044642 -0500
206+++ a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/Dim.java 2026-10-31 20:42:49.791085088 -0500
207@@ -34,6 +34,7 @@
208     private static final int IPROXY_OBJECT_TO_STRING = 5;
209     private static final int IPROXY_OBJECT_PROPERTY = 6;
210     private static final int IPROXY_OBJECT_IDS = 7;
211+    private static final int IPROXY_EVAL_SCRIPT_WITH_RETURN = 8;
212
213     /**
214      * Interface to the debugger GUI.
215@@ -398,7 +399,7 @@
216      */
217     private String getNormalizedUrl(DebuggableScript fnOrScript) {
218         String url = fnOrScript.getSourceName();
219-        if (url == null) { url = "<stdin>"; }
220+        if (url == null) { url = "document"; }
221         else {
222             // Not to produce window for eval from different lines,
223             // strip line numbers, i.e. replace all #[0-9]+\(eval\) by
224@@ -587,6 +588,17 @@
225     }
226
227     /**
228+     * Evaluates the given script with scope and return value.
229+     */
230+    public Object evalScriptWithReturn(final String url, final String text, Scriptable scope) {
231+        DimIProxy action = new DimIProxy(this, IPROXY_EVAL_SCRIPT_WITH_RETURN);
232+        action.url = url;
233+        action.text = text;
234+        action.scope = scope;
235+        return contextFactory.call(action);
236+    }
237+
238+    /**
239      * Converts the given script object to a string.
240      */
241     public String objectToString(Object object) {
242@@ -931,6 +943,11 @@
243         private Object[] objectArrayResult;
244
245         /**
246+         * The Scriptable as arguments.
247+         */
248+        private Scriptable scope;
249+
250+        /**
251          * Creates a new DimIProxy.
252          */
253         private DimIProxy(Dim dim, int type) {
254@@ -962,6 +979,20 @@
255                 }
256                 break;
257
258+              case IPROXY_EVAL_SCRIPT_WITH_RETURN:
259+                if (this.scope != null) {
260+                    return cx.evaluateString(this.scope, text, url, 1, null);
261+                } else {
262+                    Scriptable sc = null;
263+                    if (dim.scopeProvider != null) {
264+                        sc = dim.scopeProvider.getScope();
265+                    }
266+                    if (sc == null) {
267+                        sc = new ImporterTopLevel(cx);
268+                    }
269+                    return cx.evaluateString(sc, text, url, 1, null);
270+                }
271+
272               case IPROXY_STRING_IS_COMPILABLE:
273                 booleanResult = cx.stringIsCompilableUnit(text);
274                 break;
275diff -ruN a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/GuiCallback.java a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/GuiCallback.java
276--- a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/GuiCallback.java 2026-10-31 20:42:49.649184458 -0500
277+++ a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/GuiCallback.java 2026-10-31 20:42:49.791267690 -0500
278@@ -23,6 +23,11 @@
279                         String threadTitle,
280                         String alertMessage);
281
282+   /**
283+    * Called when the interrupt loop has been exited.
284+    */
285+   void exitInterrupt();
286+
287     /**
288      * Returns whether the current thread is the GUI's event thread.
289      * This information is required to avoid blocking the event thread
290@@ -35,4 +40,4 @@
291      * is necessary when the GUI event thread itself has been stopped.
292      */
293     void dispatchNextGuiEvent() throws InterruptedException;
294-}
295+}
296\ No newline at end of file
297diff -ruN a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/Main.java a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/Main.java
298--- a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/Main.java    2026-10-31 20:42:49.648814653 -0500
299+++ a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/Main.java    2026-10-31 20:42:49.791202989 -0500
300@@ -8,6 +8,7 @@
301
302 import java.io.InputStream;
303 import java.io.PrintStream;
304+import java.net.URL;
305
306 import javax.swing.JFrame;
307
308@@ -126,7 +127,7 @@
309      * Console window.
310      */
311     public InputStream getIn() {
312-        return debugGui.getConsole().getIn();
313+        return null;//return debugGui.getConsole().getIn();
314     }
315
316     /**
317@@ -134,7 +135,7 @@
318      * Console window.
319      */
320     public PrintStream getOut() {
321-        return debugGui.getConsole().getOut();
322+        return null;//return debugGui.getConsole().getOut();
323     }
324
325     /**
326@@ -142,7 +143,7 @@
327      * Console window.
328      */
329     public PrintStream getErr() {
330-        return debugGui.getConsole().getErr();
331+        return null;//return debugGui.getConsole().getErr();
332     }
333
334     /**
335@@ -349,6 +350,41 @@
336     {
337         throw new IllegalStateException();
338     }
339+
340+    // shortcut methods
341+
342+    public void addWindowListener(java.awt.event.WindowListener l) {
343+        debugGui.addWindowListener(l);
344+    }
345+
346+    public void highlighLineInScriptWindow(URL url, int lineNum) {
347+       debugGui.highlighLineInScriptWindow(url, lineNum);
348+   }
349+
350+    public Object runScriptWindow(URL scriptUrl) throws Exception
351+    {
352+       return debugGui.runScriptWindow(scriptUrl);
353+   }
354+
355+    public void openFile(URL scriptUrl, Scriptable scope, Runnable closeCallback) {
356+       debugGui.openFile(scriptUrl, scope, closeCallback);
357+   }
358+
359+    public void toFront() {
360+        debugGui.toFront();
361+    }
362+
363+    public void showScriptWindow(URL url) {
364+        debugGui.showScriptWindow(url);
365+    }
366+
367+    public boolean isModified(URL url) {
368+        return debugGui.isModified(url);
369+    }
370+
371+   public String getText(URL url) {
372+       return debugGui.getText(url);
373+   }
374
375     /**
376      * Class to consolidate all internal implementations of interfaces
377@@ -408,4 +444,4 @@
378             return scope;
379         }
380     }
381-}
382+}
383\ No newline at end of file
384diff -ruN a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/OfficeScriptInfo.java a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/OfficeScriptInfo.java
385--- a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/OfficeScriptInfo.java    1969-12-31 19:00:00.000000000 -0500
386+++ a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/OfficeScriptInfo.java    2026-10-31 20:42:49.792418706 -0500
387@@ -0,0 +1,122 @@
388+/**************************************************************
389+ *
390+ * Licensed to the Apache Software Foundation (ASF) under one
391+ * or more contributor license agreements.  See the NOTICE file
392+ * distributed with this work for additional information
393+ * regarding copyright ownership.  The ASF licenses this file
394+ * to you under the Apache License, Version 2.0 (the
395+ * "License"); you may not use this file except in compliance
396+ * with the License.  You may obtain a copy of the License at
397+ *
398+ *   http://www.apache.org/licenses/LICENSE-2.0
399+ *
400+ * Unless required by applicable law or agreed to in writing,
401+ * software distributed under the License is distributed on an
402+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
403+ * KIND, either express or implied.  See the License for the
404+ * specific language governing permissions and limitations
405+ * under the License.
406+ *
407+ *************************************************************/
408+
409+
410+package org.mozilla.javascript.tools.debugger;
411+import java.net.URL;
412+import java.util.Hashtable;
413+import org.mozilla.javascript.Scriptable;
414+
415+public class OfficeScriptInfo
416+{
417+    private Hashtable<String, SFScriptInfo> loadedSFScripts = new Hashtable<String, SFScriptInfo>();
418+
419+    public void addScript( URL url, Scriptable scope, Runnable closeCallback )
420+    {
421+        addScript( url.toString(), url, scope, closeCallback );
422+    }
423+
424+    public void addScript( String key, URL url, Scriptable scope, Runnable closeCallback )
425+    {
426+        SFScriptInfo si = (SFScriptInfo)loadedSFScripts.get( key );
427+        if ( si == null )
428+        {
429+            si = new SFScriptInfo();
430+            si.url = url;
431+            si.scope = scope;
432+            si.closeCallback = closeCallback;
433+            loadedSFScripts.put( key, si );
434+        }
435+    }
436+
437+    public void deleteScript( String key )
438+    {
439+        SFScriptInfo info = (SFScriptInfo)loadedSFScripts.remove( key );
440+        if ( info != null )
441+        {
442+            if ( info.closeCallback != null )
443+            {
444+                System.out.println("** In removeSFScriptInfo  have callback for " + key );
445+                info.closeCallback.run();
446+            }
447+        }
448+    }
449+
450+    public Scriptable getScriptScope( String key )
451+    {
452+        Scriptable result = null;
453+        SFScriptInfo info = (SFScriptInfo)loadedSFScripts.get( key );
454+        if ( info != null )
455+        {
456+            result = info.scope;
457+        }
458+        return result;
459+    }
460+
461+    public URL getScriptUrl( String key )
462+    {
463+        URL result = null;
464+        SFScriptInfo info = (SFScriptInfo)loadedSFScripts.get( key );
465+        if ( info != null )
466+        {
467+            result = info.url;
468+        }
469+        return result;
470+    }
471+    public boolean hasScript( String key )
472+    {
473+        boolean result = true;
474+        SFScriptInfo info = (SFScriptInfo)loadedSFScripts.get( key );
475+        if ( info == null )
476+        {
477+            result = false;
478+        }
479+        return result;
480+    }
481+
482+    public void setScriptRunning( String key, boolean running )
483+    {
484+        SFScriptInfo info = (SFScriptInfo)loadedSFScripts.get( key );
485+        if ( info != null )
486+        {
487+            info.isExecuting = running;
488+        }
489+    }
490+
491+    public boolean isScriptRunning( String key )
492+    {
493+        boolean result = false;
494+        SFScriptInfo info = (SFScriptInfo)loadedSFScripts.get( key );
495+        if ( info != null )
496+        {
497+            result = info.isExecuting;
498+        }
499+        return result;
500+    }
501+
502+    class SFScriptInfo
503+    {
504+        Scriptable scope;
505+        boolean isExecuting;
506+        URL url;
507+        Runnable closeCallback;
508+    }
509+}
510diff -ruN a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/SwingGui.java a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/SwingGui.java
511--- a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/SwingGui.java    2026-10-31 20:42:49.648303446 -0500
512+++ a/b/rhino1.7.7.2/src/org/mozilla/javascript/tools/debugger/SwingGui.java    2026-10-31 20:42:49.792500007 -0500
513@@ -32,6 +32,8 @@
514 import java.awt.Toolkit;
515 import java.awt.event.*;
516
517+import java.net.URL;
518+
519 import java.util.List;
520 import java.util.ArrayList;
521 import java.util.Arrays;
522@@ -47,6 +49,7 @@
523 import java.lang.reflect.Method;
524
525 import org.mozilla.javascript.Kit;
526+import org.mozilla.javascript.Scriptable;
527 import org.mozilla.javascript.SecurityUtilities;
528
529 import org.mozilla.javascript.tools.shell.ConsoleTextArea;
530@@ -59,6 +62,138 @@
531  * GUI for the Rhino debugger.
532  */
533 public class SwingGui extends JFrame implements GuiCallback {
534+    private boolean sourceEditingEnabled = true;
535+
536+    public boolean isSourceEditingEnabled() {
537+        return sourceEditingEnabled;
538+    }
539+
540+    public void setSourceEditingEnabled(boolean b) {
541+        sourceEditingEnabled = b;
542+    }
543+
544+    public void exitInterrupt() {
545+        setSourceEditingEnabled(true);
546+    }
547+
548+    // patched Office specific interface
549+    OfficeScriptInfo officeScripts = new OfficeScriptInfo();
550+
551+    void removeScript(String url) {
552+        officeScripts.deleteScript(url);
553+    }
554+
555+    private String getFileName(URL url) {
556+        if (url.getProtocol().startsWith("vnd.sun.star."))
557+            return url.toString();
558+        // Preserve full identity (protocol, host, query) for non-UNO URLs.
559+        return url.toString();
560+    }
561+
562+    public void openFile(URL scriptUrl, Scriptable scope, Runnable closeCallback) {
563+        if (scope == null) {
564+            javax.swing.JOptionPane.showMessageDialog(this,
565+                "Can't compile scripts: no scope available",
566+                "Open", javax.swing.JOptionPane.ERROR_MESSAGE);
567+        } else if (scriptUrl != null) {
568+            String fileName = getFileName(scriptUrl);
569+            // Avoid duplicating an already-open editor window.
570+            if (getFileWindow(fileName) != null) {
571+                showScriptWindow(scriptUrl);
572+                return;
573+            }
574+            String text;
575+            try (java.io.InputStreamReader reader = new java.io.InputStreamReader(scriptUrl.openStream())) {
576+                text = org.mozilla.javascript.Kit.readReader(reader);
577+            } catch (java.io.IOException e) {
578+                javax.swing.JOptionPane.showMessageDialog(this,
579+                    "Can't open stream for script: " + e.toString(),
580+                    "Open", javax.swing.JOptionPane.ERROR_MESSAGE);
581+                return;
582+            }
583+            officeScripts.addScript(fileName, scriptUrl, scope, closeCallback);
584+            // Register/compile the source with the debugger using Rhino's OPEN_FILE mechanism.
585+            RunProxy proxy = new RunProxy(this, RunProxy.OPEN_FILE);
586+            proxy.fileName = fileName;
587+            proxy.text = text;
588+            new Thread(proxy).start();
589+        }
590+    }
591+
592+    public void showScriptWindow(URL url) {
593+        String key = getFileName(url);
594+        FileWindow w = (FileWindow) getFileWindow(key);
595+        if (w != null)
596+        {
597+            desk.getDesktopManager().deiconifyFrame(w);
598+            desk.getDesktopManager().activateFrame(w);
599+            w.show();
600+            w.toFront();
601+        }
602+    }
603+
604+    public void highlighLineInScriptWindow(URL url, int lineNum) {
605+        String key = getFileName(url);
606+        FileWindow w = (FileWindow) getFileWindow(key);
607+        if (w != null)
608+        {
609+            showFileWindow(key, lineNum);
610+        }
611+    }
612+
613+    public Object runScriptWindow(URL scriptUrl) throws Exception
614+    {
615+        String key = getFileName(scriptUrl);
616+        FileWindow w = (FileWindow) getFileWindow(key);
617+        Object result = null;
618+        if (w != null)
619+        {
620+            w.toFront();
621+            Scriptable scope = officeScripts.getScriptScope(key);
622+            if (scope == null)
623+            {
624+                javax.swing.JOptionPane.showMessageDialog(this, "Can't load scripts: no scope available", "Run", javax.swing.JOptionPane.ERROR_MESSAGE);
625+            }
626+            else
627+            {
628+                String url = w.getUrl();
629+                if (url != null)
630+                {
631+                    if (officeScripts.isScriptRunning(key))
632+                    {
633+                        return result;
634+                    }
635+                    officeScripts.setScriptRunning(key, true);
636+                    try {
637+                        result = dim.evalScriptWithReturn(url, w.getText(), scope);
638+                    } catch (Exception exc) {
639+                        exc.printStackTrace();
640+                        throw exc;
641+                    } finally {
642+                        officeScripts.setScriptRunning(key, false);
643+                    }
644+                }
645+            }
646+        }
647+        return result;
648+    }
649+
650+    public boolean isModified(URL url) {
651+        String key = getFileName(url);
652+        FileWindow w = (FileWindow) getFileWindow(key);
653+        if (w != null)
654+            return w.isModified();
655+        return false;
656+    }
657+
658+    public String getText(URL url) {
659+        String key = getFileName(url);
660+        FileWindow w = (FileWindow) getFileWindow(key);
661+        if (w != null)
662+            return w.getText();
663+        return null;
664+    }
665+
666
667     /**
668      * Serializable magic number.
669@@ -2052,7 +2187,7 @@
670 /**
671  * An internal frame for script files.
672  */
673-class FileWindow extends JInternalFrame implements ActionListener {
674+class FileWindow extends JInternalFrame implements ActionListener, javax.swing.event.DocumentListener {
675
676     /**
677      * Serializable magic number.
678@@ -2089,6 +2224,83 @@
679      */
680     int currentPos;
681
682+    boolean isModified = false;
683+
684+    public void insertUpdate(javax.swing.event.DocumentEvent e) {
685+        doChanged(e);
686+    }
687+
688+    public void removeUpdate(javax.swing.event.DocumentEvent e) {
689+        doChanged(e);
690+    }
691+
692+    public void changedUpdate(javax.swing.event.DocumentEvent e) {
693+        doChanged(e);
694+    }
695+
696+    public void doChanged(javax.swing.event.DocumentEvent e) {
697+        this.isModified = true;
698+    }
699+
700+    public boolean isModified() {
701+        return this.isModified;
702+    }
703+
704+    public String getText() {
705+        return textArea.getText();
706+    }
707+
708+    void save() {
709+        String url = getUrl();
710+        if (url != null) {
711+            java.io.OutputStream os = null;
712+            try {
713+                if (url.startsWith("vnd.sun.star"))
714+                {
715+                    URL scriptUrl = debugGui.officeScripts.getScriptUrl(url);
716+                    if ( scriptUrl == null )
717+                    {
718+                        throw new java.io.IOException("Can't obtain stream for " + url);
719+                    }
720+                    os = scriptUrl.openConnection().getOutputStream();
721+                }
722+                else
723+                {
724+                   os = new java.io.FileOutputStream(url);
725+                }
726+                String txt = textArea.getText();
727+                byte[] data = txt.getBytes(java.nio.charset.StandardCharsets.UTF_8);
728+                os.write(data, 0, data.length);
729+
730+                this.isModified = false;
731+            } catch (java.io.IOException ioe) {
732+                javax.swing.JOptionPane.showMessageDialog(this,
733+                    "Error saving file: " + ioe.getMessage(),
734+                    "Error", javax.swing.JOptionPane.ERROR_MESSAGE);
735+            }
736+            finally
737+            {
738+                if ( os != null )
739+                {
740+                    try
741+                    {
742+                        os.close();
743+                        os = null;
744+                    }
745+                    catch( java.io.IOException ioe )
746+                    {
747+                        System.err.println("Error closing stream: " + ioe.getMessage() );
748+                        ioe.printStackTrace();
749+                    }
750+                }
751+            }
752+        }
753+    }
754+
755+    public boolean isEditable() {
756+        return debugGui.isSourceEditingEnabled();
757+    }
758+
759     /**
760      * Loads the file.
761      */
762@@ -3546,4 +3758,5 @@
763
764         }
765     }
766+
767 }
768