misc.
[phpeclipse.git] / net.sourceforge.phpeclipse.webbrowser / src / net / sourceforge / phpeclipse / webbrowser / internal / WebBrowser.java
1 /**
2  * Copyright (c) 2003 IBM Corporation and others. All rights reserved. � This
3  * program and the accompanying materials are made available under the terms of
4  * the Common Public License v1.0 which accompanies this distribution, and is
5  * available at http://www.eclipse.org/legal/cpl-v10.html �* Contributors: IBM -
6  * Initial API and implementation
7  */
8
9 // TODO 1. Handle the sizing of a popup running in shelled out secondary window.
10 // TODO 2. Support printing: waiting on eclipse bug 47937/44823.
11 package net.sourceforge.phpeclipse.webbrowser.internal;
12
13 import java.util.Iterator;
14
15 import net.sourceforge.phpeclipse.webbrowser.IURLMap;
16
17 import org.eclipse.swt.SWT;
18 import org.eclipse.swt.browser.Browser;
19 import org.eclipse.swt.browser.CloseWindowListener;
20 import org.eclipse.swt.browser.LocationEvent;
21 import org.eclipse.swt.browser.LocationListener;
22 import org.eclipse.swt.browser.OpenWindowListener;
23 import org.eclipse.swt.browser.ProgressEvent;
24 import org.eclipse.swt.browser.ProgressListener;
25 import org.eclipse.swt.browser.StatusTextEvent;
26 import org.eclipse.swt.browser.StatusTextListener;
27 import org.eclipse.swt.browser.TitleEvent;
28 import org.eclipse.swt.browser.TitleListener;
29 import org.eclipse.swt.browser.WindowEvent;
30 import org.eclipse.swt.dnd.Clipboard;
31 import org.eclipse.swt.events.SelectionAdapter;
32 import org.eclipse.swt.events.SelectionEvent;
33 import org.eclipse.swt.graphics.Point;
34 import org.eclipse.swt.graphics.Rectangle;
35 import org.eclipse.swt.layout.FillLayout;
36 import org.eclipse.swt.layout.GridData;
37 import org.eclipse.swt.layout.GridLayout;
38 import org.eclipse.swt.widgets.Combo;
39 import org.eclipse.swt.widgets.Composite;
40 import org.eclipse.swt.widgets.Control;
41 import org.eclipse.swt.widgets.Event;
42 import org.eclipse.swt.widgets.Label;
43 import org.eclipse.swt.widgets.Listener;
44 import org.eclipse.swt.widgets.Menu;
45 import org.eclipse.swt.widgets.MenuItem;
46 import org.eclipse.swt.widgets.ProgressBar;
47 import org.eclipse.swt.widgets.Shell;
48 import org.eclipse.swt.widgets.ToolBar;
49 import org.eclipse.swt.widgets.ToolItem;
50 import org.eclipse.ui.PlatformUI;
51
52 public class WebBrowser extends Composite {
53         protected Composite toolbarComp;
54
55         protected Composite statusComp;
56
57         protected Combo combo;
58
59         protected Clipboard clipboard;
60
61         protected boolean showToolbar;
62
63         protected ToolItem back;
64
65         protected ToolItem forward;
66
67         protected ToolItem stop;
68
69         protected ToolItem favorites;
70
71         protected ToolItem refresh;
72
73         protected BusyIndicator busy;
74
75         protected boolean showStatusbar;
76
77         protected ProgressBar progress;
78
79         protected Label status;
80
81         private static int MAX_HISTORY = 50;
82
83         protected static java.util.List history;
84
85         protected Browser browser;
86
87         protected Shell shell;
88
89         protected WebBrowserEditor editor;
90
91         protected String title;
92
93         public WebBrowser(Composite parent, final boolean showToolbar,
94                         final boolean showStatusbar) {
95                 super(parent, SWT.NONE);
96
97                 this.showToolbar = showToolbar;
98                 this.showStatusbar = showStatusbar;
99
100                 GridLayout layout = new GridLayout();
101                 layout.marginHeight = 3;
102                 layout.marginWidth = 3;
103                 layout.horizontalSpacing = 3;
104                 layout.verticalSpacing = 3;
105                 layout.numColumns = 1;
106                 setLayout(layout);
107                 setLayoutData(new GridData(GridData.FILL_BOTH));
108                 clipboard = new Clipboard(parent.getDisplay());
109                 PlatformUI.getWorkbench().getHelpSystem().setHelp(this,
110                                 ContextIds.WEB_BROWSER);
111
112                 if (showToolbar) {
113                         toolbarComp = new Composite(this, SWT.NONE);
114                         GridLayout outerLayout = new GridLayout();
115                         outerLayout.numColumns = 2;
116                         outerLayout.marginWidth = 0;
117                         outerLayout.marginHeight = 0;
118                         toolbarComp.setLayout(outerLayout);
119                         toolbarComp.setLayoutData(new GridData(
120                                         GridData.VERTICAL_ALIGN_BEGINNING
121                                                         | GridData.FILL_HORIZONTAL));
122
123                         // create the top line, with a combo box for history and a "go"
124                         // button
125                         Composite top = new Composite(toolbarComp, SWT.NONE);
126                         GridLayout topLayout = new GridLayout();
127                         topLayout.numColumns = 2;
128                         topLayout.marginWidth = 0;
129                         topLayout.marginHeight = 0;
130                         top.setLayout(topLayout);
131                         top.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_CENTER
132                                         | GridData.FILL_HORIZONTAL));
133
134                         combo = new Combo(top, SWT.DROP_DOWN);
135
136                         updateHistory();
137
138                         combo.addSelectionListener(new SelectionAdapter() {
139                                 public void widgetSelected(SelectionEvent we) {
140                                         try {
141                                                 if (combo.getSelectionIndex() != -1)
142                                                         setURL(combo.getItem(combo.getSelectionIndex()));
143                                         } catch (Exception e) {
144                                         }
145                                 }
146                         });
147                         combo.addListener(SWT.DefaultSelection, new Listener() {
148                                 public void handleEvent(Event e) {
149                                         setURL(combo.getText());
150                                 }
151                         });
152                         combo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
153                         PlatformUI.getWorkbench().getHelpSystem().setHelp(combo,
154                                         ContextIds.WEB_BROWSER_URL);
155
156                         ToolBar toolbar = new ToolBar(top, SWT.FLAT);
157                         fillToolBar(toolbar);
158
159                         new ToolItem(toolbar, SWT.SEPARATOR);
160
161                         busy = new BusyIndicator(toolbarComp, SWT.NONE);
162                         busy.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
163                 }
164
165                 // create a new SWT Web browser widget, checking once again to make sure
166                 // we can use it in this environment
167                 // if (WebBrowserUtil.canUseInternalWebBrowser() &
168                 // WebBrowserUtil.isInternalBrowserOperational())
169                 if (WebBrowserUtil.isInternalBrowserOperational())
170                         this.browser = new Browser(this, SWT.NONE);
171                 else {
172                         WebBrowserUtil.openError(WebBrowserUIPlugin
173                                         .getResource("%errorCouldNotLaunchInternalWebBrowser"));
174                         return;
175                 }
176
177                 if (showToolbar) {
178                         back.setEnabled(browser.isBackEnabled());
179                         forward.setEnabled(browser.isForwardEnabled());
180                 }
181
182                 PlatformUI.getWorkbench().getHelpSystem().setHelp(browser,
183                                 ContextIds.WEB_BROWSER_WEB);
184                 GridData data = new GridData();
185                 data.horizontalAlignment = GridData.FILL;
186                 data.verticalAlignment = GridData.FILL;
187                 data.horizontalSpan = 3;
188                 data.grabExcessHorizontalSpace = true;
189                 data.grabExcessVerticalSpace = true;
190                 browser.setLayoutData(data);
191
192                 if (showStatusbar)
193                         createStatusArea(this);
194
195                 addBrowserListeners();
196         }
197
198         /**
199          * 
200          */
201         protected void addBrowserListeners() {
202                 if (showStatusbar) {
203                         // respond to Browser StatusTextEvents events by updating the status
204                         // Text label
205                         browser.addStatusTextListener(new StatusTextListener() {
206                                 public void changed(StatusTextEvent event) {
207                                         status.setText(event.text);
208                                 }
209                         });
210                 }
211
212                 /**
213                  * Add listener for new window creation so that we can instead of
214                  * opening a separate new window in which the session is lost, we can
215                  * instead open a new window in a new shell within the browser area
216                  * thereby maintaining the session.
217                  */
218                 browser.addOpenWindowListener(new OpenWindowListener() {
219                         public void open(WindowEvent event) {
220                                 Shell shell2 = new Shell(getDisplay());
221                                 shell2.setLayout(new FillLayout());
222                                 shell2.setText(WebBrowserUIPlugin
223                                                 .getResource("%viewWebBrowserTitle"));
224                                 shell2.setImage(getShell().getImage());
225                                 WebBrowser browser2 = new WebBrowser(shell2, showToolbar,
226                                                 showStatusbar);
227                                 browser2.shell = shell2;
228                                 event.browser = browser2.browser;
229                                 shell2.open();
230                         }
231                 });
232
233                 browser.addCloseWindowListener(new CloseWindowListener() {
234                         public void close(WindowEvent event) {
235                                 // if shell is not null, it must be a secondary popup window,
236                                 // else its an editor window
237                                 if (shell != null)
238                                         shell.dispose();
239                                 else {
240                                         // #1365431 (toshihiro) editor.closeEditor(); causes NPE
241                                 if (editor != null) editor.closeEditor();
242                                 }
243                         }
244                 });
245
246                 browser.addProgressListener(new ProgressListener() {
247                         public void changed(ProgressEvent event) {
248                                 if (event.total == 0)
249                                         return;
250
251                                 boolean done = (event.current == event.total);
252
253                                 int percentProgress = event.current * 100 / event.total;
254                                 if (showStatusbar) {
255                                         if (done)
256                                                 progress.setSelection(0);
257                                         else
258                                                 progress.setSelection(percentProgress);
259                                 }
260
261                                 if (showToolbar) {
262                                         if (!busy.isBusy()
263                                                         && (percentProgress > 0 && percentProgress < 100)) {
264                                                 busy.setBusy(true);
265                                         }
266                                         // Once the progress hits 100 percent, done, set busy to
267                                         // false
268                                         else if (busy.isBusy() && done) {
269                                                 busy.setBusy(false);
270                                         }
271                                 }
272                         }
273
274                         public void completed(ProgressEvent event) {
275                                 if (showStatusbar)
276                                         progress.setSelection(0);
277                                 if (showToolbar) {
278                                         busy.setBusy(false);
279                                         back.setEnabled(browser.isBackEnabled());
280                                         forward.setEnabled(browser.isForwardEnabled());
281                                 }
282                         }
283                 });
284
285                 if (showToolbar) {
286                         browser.addLocationListener(new LocationListener() {
287                                 public void changed(LocationEvent event) {
288                                         if (!event.top)
289                                                 return;
290                                         if (!isHome()) {
291                                                 combo.setText(event.location);
292                                                 addToHistory(event.location);
293                                                 updateHistory();
294                                         } else
295                                                 combo.setText("");
296                                 }
297
298                                 public void changing(LocationEvent event) {
299                                 }
300                         });
301                 }
302
303                 browser.addTitleListener(new TitleListener() {
304                         public void changed(TitleEvent event) {
305                                 title = event.title;
306                         }
307                 });
308         }
309
310         /**
311          * Return the underlying browser control.
312          * 
313          * @return org.eclipse.swt.browser.Browser
314          */
315         public Browser getBrowser() {
316                 return browser;
317         }
318
319         /**
320          * 
321          */
322         protected void forward() {
323                 browser.forward();
324         }
325
326         /**
327          * 
328          */
329         protected void back() {
330                 browser.back();
331         }
332
333         /**
334          * 
335          */
336         protected void stop() {
337                 browser.stop();
338         }
339
340         /**
341          * 
342          */
343         protected void navigate(String url) {
344                 Trace.trace(Trace.FINER, "Navigate: " + url);
345                 if (url != null && url.equals(getURL())) {
346                         refresh();
347                         return;
348                 }
349                 browser.setUrl(url);
350         }
351
352         /**
353          * Refresh the currently viewed page.
354          */
355         public void refresh() {
356                 browser.refresh();
357         }
358
359         protected void setURL(String url, boolean browse) {
360                 Trace.trace(Trace.FINEST, "setURL: " + url + " " + browse);
361                 if (url == null) {
362                         home();
363                         return;
364                 }
365
366                 if (url.endsWith(WebBrowserPreference.getHomePageURL().substring(9)))
367                         return;
368
369                 // check URL maps
370                 Iterator iterator = WebBrowserUtil.getURLMaps().iterator();
371                 String newURL = null;
372                 while (iterator.hasNext() && newURL == null) {
373                         try {
374                                 IURLMap map = (IURLMap) iterator.next();
375                                 newURL = map.getMappedURL(url);
376                         } catch (Exception e) {
377                         }
378                 }
379                 if (newURL != null)
380                         url = newURL;
381
382                 if (browse)
383                         navigate(url);
384
385                 addToHistory(url);
386                 updateHistory();
387         }
388
389         protected void addToHistory(String url) {
390                 if (history == null)
391                         history = WebBrowserPreference.getInternalWebBrowserHistory();
392                 int found = -1;
393                 int size = history.size();
394                 for (int i = 0; i < size; i++) {
395                         String s = (String) history.get(i);
396                         if (s.equals(url)) {
397                                 found = i;
398                                 break;
399                         }
400                 }
401
402                 if (found == -1) {
403                         if (size >= MAX_HISTORY)
404                                 history.remove(size - 1);
405                         history.add(0, url);
406                         WebBrowserPreference.setInternalWebBrowserHistory(history);
407                 } else if (found != 0) {
408                         history.remove(found);
409                         history.add(0, url);
410                         WebBrowserPreference.setInternalWebBrowserHistory(history);
411                 }
412         }
413
414         public void setURL(String url) {
415                 setURL(url, true);
416         }
417
418         /**
419          * Creates the Web browser status area.
420          */
421         private void createStatusArea(Composite parent) {
422                 Composite composite = new Composite(parent, SWT.NONE);
423                 GridLayout layout = new GridLayout();
424                 layout.numColumns = 2;
425                 layout.marginHeight = 0;
426                 layout.marginWidth = 0;
427                 layout.horizontalSpacing = 4;
428                 layout.verticalSpacing = 0;
429                 composite.setLayout(layout);
430                 composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
431
432                 // Add a label for displaying status messages as they are received from
433                 // the control
434                 status = new Label(composite, SWT.SINGLE | SWT.READ_ONLY);
435                 GridData gridData = new GridData(GridData.FILL_HORIZONTAL
436                                 | GridData.VERTICAL_ALIGN_FILL);
437                 gridData.horizontalIndent = 2;
438                 status.setLayoutData(gridData);
439
440                 // Add a progress bar to display downloading progress information
441                 progress = new ProgressBar(composite, SWT.BORDER);
442                 gridData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING
443                                 | GridData.VERTICAL_ALIGN_FILL);
444                 gridData.widthHint = 100;
445                 gridData.heightHint = 10;
446                 progress.setLayoutData(gridData);
447         }
448
449         /**
450          * 
451          */
452         public void dispose() {
453                 super.dispose();
454
455                 showStatusbar = false;
456                 showToolbar = false;
457
458                 if (busy != null)
459                         busy.dispose();
460                 busy = null;
461
462                 browser = null;
463         }
464
465         /**
466          * Populate the toolbar.
467          * 
468          * @param toolbar
469          *            org.eclipse.swt.widgets.ToolBar
470          */
471         private void fillToolBar(final ToolBar toolbar) {
472                 ToolItem go = new ToolItem(toolbar, SWT.NONE);
473                 go.setImage(ImageResource.getImage(ImageResource.IMG_ELCL_NAV_GO));
474                 go.setHotImage(ImageResource.getImage(ImageResource.IMG_CLCL_NAV_GO));
475                 go.setDisabledImage(ImageResource
476                                 .getImage(ImageResource.IMG_DLCL_NAV_GO));
477                 go
478                                 .setToolTipText(WebBrowserUIPlugin
479                                                 .getResource("%actionWebBrowserGo"));
480                 go.addSelectionListener(new SelectionAdapter() {
481                         public void widgetSelected(SelectionEvent event) {
482                                 setURL(combo.getText());
483                         }
484                 });
485
486                 new ToolItem(toolbar, SWT.SEPARATOR);
487
488                 favorites = new ToolItem(toolbar, SWT.DROP_DOWN);
489                 favorites.setImage(ImageResource
490                                 .getImage(ImageResource.IMG_ELCL_NAV_FAVORITES));
491                 favorites.setHotImage(ImageResource
492                                 .getImage(ImageResource.IMG_CLCL_NAV_FAVORITES));
493                 favorites.setDisabledImage(ImageResource
494                                 .getImage(ImageResource.IMG_DLCL_NAV_FAVORITES));
495                 favorites.setToolTipText(WebBrowserUIPlugin
496                                 .getResource("%actionWebBrowserFavorites"));
497
498                 favorites.addSelectionListener(new SelectionAdapter() {
499                         public void widgetSelected(SelectionEvent event) {
500                                 if (event.detail == SWT.ARROW) {
501                                         Rectangle r = favorites.getBounds();
502                                         showFavorites(toolbar, toolbar.toDisplay(r.x, r.y
503                                                         + r.height));
504                                 } else
505                                         addFavorite();
506                         }
507                 });
508
509                 // create back and forward actions
510                 back = new ToolItem(toolbar, SWT.NONE);
511                 back.setImage(ImageResource
512                                 .getImage(ImageResource.IMG_ELCL_NAV_BACKWARD));
513                 back.setHotImage(ImageResource
514                                 .getImage(ImageResource.IMG_CLCL_NAV_BACKWARD));
515                 back.setDisabledImage(ImageResource
516                                 .getImage(ImageResource.IMG_DLCL_NAV_BACKWARD));
517                 back.setToolTipText(WebBrowserUIPlugin
518                                 .getResource("%actionWebBrowserBack"));
519                 back.addSelectionListener(new SelectionAdapter() {
520                         public void widgetSelected(SelectionEvent event) {
521                                 back();
522                         }
523                 });
524
525                 forward = new ToolItem(toolbar, SWT.NONE);
526                 forward.setImage(ImageResource
527                                 .getImage(ImageResource.IMG_ELCL_NAV_FORWARD));
528                 forward.setHotImage(ImageResource
529                                 .getImage(ImageResource.IMG_CLCL_NAV_FORWARD));
530                 forward.setDisabledImage(ImageResource
531                                 .getImage(ImageResource.IMG_DLCL_NAV_FORWARD));
532                 forward.setToolTipText(WebBrowserUIPlugin
533                                 .getResource("%actionWebBrowserForward"));
534                 forward.addSelectionListener(new SelectionAdapter() {
535                         public void widgetSelected(SelectionEvent event) {
536                                 forward();
537                         }
538                 });
539
540                 // create refresh, stop, and print actions
541                 stop = new ToolItem(toolbar, SWT.NONE);
542                 stop.setImage(ImageResource.getImage(ImageResource.IMG_ELCL_NAV_STOP));
543                 stop.setHotImage(ImageResource
544                                 .getImage(ImageResource.IMG_CLCL_NAV_STOP));
545                 stop.setDisabledImage(ImageResource
546                                 .getImage(ImageResource.IMG_DLCL_NAV_STOP));
547                 stop.setToolTipText(WebBrowserUIPlugin
548                                 .getResource("%actionWebBrowserStop"));
549                 stop.addSelectionListener(new SelectionAdapter() {
550                         public void widgetSelected(SelectionEvent event) {
551                                 stop();
552                         }
553                 });
554
555                 refresh = new ToolItem(toolbar, SWT.NONE);
556                 refresh.setImage(ImageResource
557                                 .getImage(ImageResource.IMG_ELCL_NAV_REFRESH));
558                 refresh.setHotImage(ImageResource
559                                 .getImage(ImageResource.IMG_CLCL_NAV_REFRESH));
560                 refresh.setDisabledImage(ImageResource
561                                 .getImage(ImageResource.IMG_DLCL_NAV_REFRESH));
562                 refresh.setToolTipText(WebBrowserUIPlugin
563                                 .getResource("%actionWebBrowserRefresh"));
564                 refresh.addSelectionListener(new SelectionAdapter() {
565                         public void widgetSelected(SelectionEvent event) {
566                                 refresh();
567                         }
568                 });
569         }
570
571         protected void addFavorite() {
572                 java.util.List list = WebBrowserPreference
573                                 .getInternalWebBrowserFavorites();
574                 Favorite f = new Favorite(title, browser.getUrl());
575                 if (!list.contains(f)) {
576                         list.add(f);
577                         WebBrowserPreference.setInternalWebBrowserFavorites(list);
578                 }
579         }
580
581         protected void showFavorites(Control parent, Point p) {
582                 Menu perspectiveBarMenu = null;
583                 if (perspectiveBarMenu == null) {
584                         Menu menu = new Menu(parent);
585
586                         // locked favorites
587                         Iterator iterator = WebBrowserUtil.getLockedFavorites().iterator();
588                         if (iterator.hasNext()) {
589                                 while (iterator.hasNext()) {
590                                         final Favorite f = (Favorite) iterator.next();
591                                         MenuItem item = new MenuItem(menu, SWT.NONE);
592                                         item.setText(f.getName());
593                                         item.setImage(ImageResource
594                                                         .getImage(ImageResource.IMG_FAVORITE));
595                                         item.addSelectionListener(new SelectionAdapter() {
596                                                 public void widgetSelected(SelectionEvent event) {
597                                                         setURL(f.getURL());
598                                                 }
599                                         });
600                                 }
601
602                                 new MenuItem(menu, SWT.SEPARATOR);
603                         }
604
605                         iterator = WebBrowserPreference.getInternalWebBrowserFavorites()
606                                         .iterator();
607                         if (!iterator.hasNext()) {
608                                 MenuItem item = new MenuItem(menu, SWT.NONE);
609                                 item.setText(WebBrowserUIPlugin
610                                                 .getResource("%actionWebBrowserNoFavorites"));
611                         }
612                         while (iterator.hasNext()) {
613                                 final Favorite f = (Favorite) iterator.next();
614                                 MenuItem item = new MenuItem(menu, SWT.NONE);
615                                 item.setText(f.getName());
616                                 item.setImage(ImageResource
617                                                 .getImage(ImageResource.IMG_FAVORITE));
618                                 item.addSelectionListener(new SelectionAdapter() {
619                                         public void widgetSelected(SelectionEvent event) {
620                                                 setURL(f.getURL());
621                                         }
622                                 });
623                         }
624
625                         new MenuItem(menu, SWT.SEPARATOR);
626
627                         MenuItem item = new MenuItem(menu, SWT.NONE);
628                         item.setText(WebBrowserUIPlugin
629                                         .getResource("%actionWebBrowserOrganizeFavorites"));
630                         item.addSelectionListener(new SelectionAdapter() {
631                                 public void widgetSelected(SelectionEvent event) {
632                                         OrganizeFavoritesDialog dialog = new OrganizeFavoritesDialog(
633                                                         shell);
634                                         dialog.open();
635                                 }
636                         });
637
638                         perspectiveBarMenu = menu;
639                 }
640
641                 if (perspectiveBarMenu != null) {
642                         perspectiveBarMenu.setLocation(p.x, p.y);
643                         perspectiveBarMenu.setVisible(true);
644                 }
645         }
646
647         public void home() {
648                 navigate(WebBrowserPreference.getHomePageURL());
649         }
650
651         /**
652          * Returns true if the homepage is currently being displayed.
653          * 
654          * @return boolean
655          */
656         protected boolean isHome() {
657                 return getURL() != null
658                                 && getURL().endsWith(
659                                                 WebBrowserPreference.getHomePageURL().substring(9));
660         }
661
662         protected String getURL() {
663                 return browser.getUrl();
664         }
665
666         /**
667          * Update the history list to the global copy.
668          */
669         protected void updateHistory() {
670                 if (combo == null)
671                         return;
672
673                 String temp = combo.getText();
674                 if (history == null)
675                         history = WebBrowserPreference.getInternalWebBrowserHistory();
676
677                 String[] historyList = new String[history.size()];
678                 history.toArray(historyList);
679                 combo.setItems(historyList);
680
681                 combo.setText(temp);
682         }
683
684         public void addProgressListener(ProgressListener listener) {
685                 browser.addProgressListener(listener);
686         }
687
688         public void addStatusTextListener(StatusTextListener listener) {
689                 browser.addStatusTextListener(listener);
690         }
691
692         public void addTitleListener(TitleListener listener) {
693                 browser.addTitleListener(listener);
694         }
695 }