KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > dev > shell > BrowserWidget


1 /*
2  * Copyright 2006 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.dev.shell;
17
18 import com.google.gwt.core.ext.TreeLogger;
19 import com.google.gwt.core.ext.UnableToCompleteException;
20 import com.google.gwt.dev.util.Util;
21
22 import org.eclipse.swt.SWT;
23 import org.eclipse.swt.browser.Browser;
24 import org.eclipse.swt.browser.LocationEvent;
25 import org.eclipse.swt.browser.LocationListener;
26 import org.eclipse.swt.browser.OpenWindowListener;
27 import org.eclipse.swt.browser.StatusTextEvent;
28 import org.eclipse.swt.browser.StatusTextListener;
29 import org.eclipse.swt.browser.TitleEvent;
30 import org.eclipse.swt.browser.TitleListener;
31 import org.eclipse.swt.browser.WindowEvent;
32 import org.eclipse.swt.events.DisposeEvent;
33 import org.eclipse.swt.events.DisposeListener;
34 import org.eclipse.swt.events.FocusEvent;
35 import org.eclipse.swt.events.FocusListener;
36 import org.eclipse.swt.events.KeyEvent;
37 import org.eclipse.swt.events.KeyListener;
38 import org.eclipse.swt.events.SelectionAdapter;
39 import org.eclipse.swt.events.SelectionEvent;
40 import org.eclipse.swt.events.SelectionListener;
41 import org.eclipse.swt.graphics.Color;
42 import org.eclipse.swt.graphics.Cursor;
43 import org.eclipse.swt.layout.GridData;
44 import org.eclipse.swt.layout.GridLayout;
45 import org.eclipse.swt.program.Program;
46 import org.eclipse.swt.widgets.Button;
47 import org.eclipse.swt.widgets.Composite;
48 import org.eclipse.swt.widgets.Label;
49 import org.eclipse.swt.widgets.MessageBox;
50 import org.eclipse.swt.widgets.Shell;
51 import org.eclipse.swt.widgets.Text;
52 import org.eclipse.swt.widgets.ToolItem;
53
54 import java.io.File JavaDoc;
55 import java.io.IOException JavaDoc;
56 import java.util.HashMap JavaDoc;
57 import java.util.HashSet JavaDoc;
58 import java.util.Iterator JavaDoc;
59 import java.util.Map JavaDoc;
60 import java.util.Set JavaDoc;
61
62 /**
63  * Represents an individual browser window and all of its controls.
64  */

65 public abstract class BrowserWidget extends Composite {
66
67   private class Toolbar extends HeaderBarBase implements SelectionListener {
68     private final ToolItem backButton;
69
70     private final ToolItem forwardButton;
71
72     private final ToolItem openWebModeButton;
73
74     private final ToolItem refreshButton;
75     private final ToolItem stopButton;
76
77     public Toolbar(Composite parent) {
78       super(parent);
79
80       backButton = newItem("back.gif", " &Back ", "Go back one state");
81       backButton.addSelectionListener(this);
82
83       forwardButton = newItem("forward.gif", "&Forward", "Go forward one state");
84       forwardButton.addSelectionListener(this);
85
86       refreshButton = newItem("refresh.gif", " &Refresh ", "Reload the page");
87       refreshButton.addSelectionListener(this);
88
89       stopButton = newItem("stop.gif", " &Stop ", "Stop loading the page");
90       stopButton.addSelectionListener(this);
91
92       newSeparator();
93
94       openWebModeButton = newItem("new-web-mode-window.gif", "&Compile/Browse",
95           "Compiles and opens the current URL in the system browser");
96       openWebModeButton.addSelectionListener(this);
97       openWebModeButton.setEnabled(false);
98     }
99
100     public void widgetDefaultSelected(SelectionEvent e) {
101     }
102
103     public void widgetSelected(SelectionEvent evt) {
104       if (evt.widget == backButton) {
105         browser.back();
106       } else if (evt.widget == forwardButton) {
107         browser.forward();
108       } else if (evt.widget == refreshButton) {
109         // we have to clean up old module spaces here b/c we don't get a
110
// location changed event
111

112         // lastHostPageLocation = null;
113
browser.refresh();
114       } else if (evt.widget == stopButton) {
115         browser.stop();
116       } else if (evt.widget == openWebModeButton) {
117         // first, compile
118
Set JavaDoc keySet = new HashSet JavaDoc();
119         for (Iterator JavaDoc iter = loadedModules.entrySet().iterator();
120             iter.hasNext(); ) {
121           ModuleSpace module
122               = (ModuleSpace) ((Map.Entry JavaDoc) iter.next()).getValue();
123           keySet.add(module.getModuleName());
124         }
125         String JavaDoc[] moduleNames = Util.toStringArray(keySet);
126         if (moduleNames.length == 0) {
127           // A latent problem with a module.
128
//
129
openWebModeButton.setEnabled(false);
130           return;
131         }
132         try {
133           Cursor waitCursor = getDisplay().getSystemCursor(SWT.CURSOR_WAIT);
134           getShell().setCursor(waitCursor);
135           getHost().compile(moduleNames);
136         } catch (UnableToCompleteException e) {
137           // Already logged by callee.
138
//
139
MessageBox msgBox = new MessageBox(getShell(), SWT.OK
140               | SWT.ICON_ERROR);
141           msgBox.setText("Compilation Failed");
142           msgBox.setMessage("Compilation failed. Please see the log in the development shell for details.");
143           msgBox.open();
144           return;
145         } finally {
146           // Restore the cursor.
147
//
148
Cursor normalCursor = getDisplay().getSystemCursor(SWT.CURSOR_ARROW);
149           getShell().setCursor(normalCursor);
150         }
151
152         String JavaDoc locationText = location.getText();
153
154         launchExternalBrowser(logger, locationText);
155       }
156     }
157   }
158
159   static void launchExternalBrowser(TreeLogger logger, String JavaDoc location) {
160
161     // check GWT_EXTERNAL_BROWSER first, it overrides everything else
162
LowLevel.init();
163     String JavaDoc browserCmd = LowLevel.getEnv("GWT_EXTERNAL_BROWSER");
164     if (browserCmd != null) {
165       browserCmd += " " + location;
166       try {
167         Runtime.getRuntime().exec(browserCmd);
168         return;
169       } catch (IOException JavaDoc e) {
170         logger.log(TreeLogger.ERROR,
171             "Error launching GWT_EXTERNAL_BROWSER executable '" + browserCmd
172                 + "'", e);
173         return;
174       }
175     }
176
177     // legacy: gwt.browser.default
178
browserCmd = System.getProperty("gwt.browser.default");
179     if (browserCmd != null) {
180       browserCmd += " " + location;
181       try {
182         Runtime.getRuntime().exec(browserCmd);
183         return;
184       } catch (IOException JavaDoc e) {
185         logger.log(TreeLogger.ERROR,
186             "Error launching gwt.browser.default executable '" + browserCmd
187                 + "'", e);
188         return;
189       }
190     }
191
192     // Programmatically try to find something that can handle html files
193
Program browserProgram = Program.findProgram("html");
194     if (browserProgram != null) {
195       if (browserProgram.execute(location)) {
196         return;
197       } else {
198         logger.log(TreeLogger.ERROR, "Error launching external HTML program '"
199             + browserProgram.getName() + "'", null);
200         return;
201       }
202     }
203
204     // We're out of options, so fail.
205
logger.log(TreeLogger.ERROR,
206         "Unable to find a default external web browser", null);
207
208     logger.log(TreeLogger.WARN, "Try setting the environment variable "
209         + "GWT_EXTERNAL_BROWSER to your web browser executable before "
210         + "launching the GWT shell", null);
211   }
212
213   protected Browser browser;
214   
215   private Color bgColor = new Color(null, 239, 237, 216);
216
217   private Button goButton;
218
219   private final BrowserWidgetHost host;
220
221   private final Map JavaDoc loadedModules = new HashMap JavaDoc();
222
223   private Text location;
224
225   private final TreeLogger logger;
226
227   private Label statusBar;
228
229   private Toolbar toolbar;
230
231   public BrowserWidget(Composite parent, BrowserWidgetHost host) {
232     super(parent, SWT.NONE);
233
234     this.host = host;
235     logger = this.host.getLogger();
236
237     bgColor = new Color(null, 239, 237, 216);
238
239     toolbar = new Toolbar(this);
240     Composite secondBar = buildLocationBar(this);
241
242     browser = new Browser(this, SWT.NONE);
243     
244     {
245       statusBar = new Label(this, SWT.BORDER | SWT.SHADOW_IN);
246       statusBar.setBackground(bgColor);
247       GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
248       gridData.verticalAlignment = GridData.CENTER;
249       gridData.verticalIndent = 0;
250       gridData.horizontalIndent = 0;
251       statusBar.setLayoutData(gridData);
252     }
253
254     GridLayout layout = new GridLayout();
255     layout.numColumns = 1;
256     layout.verticalSpacing = 1;
257     layout.marginWidth = 0;
258     layout.marginHeight = 0;
259     setLayout(layout);
260
261     toolbar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
262     secondBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
263
264     GridData data = new GridData(GridData.FILL_BOTH);
265     data.grabExcessVerticalSpace = true;
266     data.grabExcessHorizontalSpace = true;
267     browser.setLayoutData(data);
268
269     // Hook up all appropriate event listeners.
270
//
271
hookBrowserListeners();
272   }
273
274   /**
275    * Gets the browser object wrapped by this window.
276    */

277   public Browser getBrowser() {
278     return browser;
279   }
280
281   public BrowserWidgetHost getHost() {
282     return host;
283   }
284
285   /**
286    * Go to a given url, possibly rewriting it if it can be served from any
287    * project's public directory.
288    */

289   public void go(String JavaDoc target) {
290     String JavaDoc url = host.normalizeURL(target);
291     browser.setUrl(url);
292   }
293
294   public void onFirstShown() {
295     String JavaDoc baseUrl = host.normalizeURL("/");
296     setLocationText(baseUrl);
297     location.setFocus();
298     location.setSelection(baseUrl.length());
299     location.addFocusListener(new FocusListener() {
300       public void focusGained(FocusEvent e) {
301         int length = location.getText().length();
302         location.setSelection(length, length);
303       }
304
305       public void focusLost(FocusEvent e) {
306       }
307     });
308   }
309
310   /**
311    * Initializes and attaches module space to this browser widget. Called by
312    * subclasses in response to calls from JavaScript.
313    *
314    * @param space ModuleSpace instance to initialize
315    */

316   protected final void attachModuleSpace(ModuleSpace space)
317       throws UnableToCompleteException {
318     Object JavaDoc key = space.getKey();
319     loadedModules.put(key, space);
320
321     logger.log(TreeLogger.SPAM, "Loading module " + space.getModuleName()
322         + " (id " + key.toString() + ")", null);
323     
324     // Let the space do its thing.
325
//
326
space.onLoad(logger);
327
328     // Enable the compile button since we successfully loaded.
329
//
330
toolbar.openWebModeButton.setEnabled(true);
331   }
332
333   /**
334    * Unload one or more modules. If key is null, emulate old behavior
335    * by unloading all loaded modules.
336    *
337    * @param key unique key to identify module to unload or null for all
338    */

339   protected void doUnload(Object JavaDoc key) {
340     if (key == null) {
341       // BEGIN BACKWARD COMPATIBILITY
342
// remove all modules
343
for (Iterator JavaDoc iter = loadedModules.entrySet().iterator();
344           iter.hasNext(); ) {
345         unloadModule((ModuleSpace) ((Map.Entry JavaDoc) iter.next()).getValue());
346       }
347       loadedModules.clear();
348       // END BACKWARD COMPATIBILITY
349
} else {
350       ModuleSpace moduleSpace = (ModuleSpace) loadedModules.get(key);
351       if (moduleSpace == null) {
352         throw new HostedModeException("Can't find frame window for " + key);
353       }
354       unloadModule(moduleSpace);
355       loadedModules.remove(key);
356     }
357     if (loadedModules.isEmpty()) {
358       if (!toolbar.openWebModeButton.isDisposed()) {
359         // Disable the compile button.
360
//
361
toolbar.openWebModeButton.setEnabled(false);
362       }
363     }
364   }
365
366   /**
367    * Unload the specified module.
368    *
369    * @param moduleSpace a ModuleSpace instance to unload.
370    */

371   protected void unloadModule(ModuleSpace moduleSpace) {
372     String JavaDoc moduleName = moduleSpace.getModuleName();
373     Object JavaDoc key = moduleSpace.getKey();
374     moduleSpace.dispose();
375     logger.log(TreeLogger.SPAM, "Unloading module " + moduleName
376         + " (id " + key.toString() + ")", null);
377   }
378
379   private Composite buildLocationBar(Composite parent) {
380     Color white = new Color(null, 255, 255, 255);
381
382     Composite bar = new Composite(parent, SWT.BORDER);
383     bar.setBackground(white);
384
385     location = new Text(bar, SWT.FLAT);
386
387     goButton = new Button(bar, SWT.NONE);
388     goButton.setBackground(bgColor);
389     goButton.setText("Go");
390     goButton.setImage(LowLevel.loadImage("go.gif"));
391
392     GridLayout layout = new GridLayout();
393     layout.numColumns = 2;
394     layout.marginWidth = layout.marginHeight = 0;
395     layout.marginLeft = 2;
396     layout.verticalSpacing = layout.horizontalSpacing = 0;
397     bar.setLayout(layout);
398
399     GridData data = new GridData(GridData.FILL_HORIZONTAL);
400     data.grabExcessHorizontalSpace = true;
401     data.verticalAlignment = GridData.CENTER;
402     location.setLayoutData(data);
403
404     return bar;
405   }
406
407   /**
408    * Hooks up all necessary event listeners.
409    */

410   private void hookBrowserListeners() {
411
412     this.addDisposeListener(new DisposeListener() {
413       public void widgetDisposed(DisposeEvent e) {
414         bgColor.dispose();
415       }
416     });
417
418     goButton.addSelectionListener(new SelectionAdapter() {
419       public void widgetSelected(SelectionEvent e) {
420         go(location.getText());
421       }
422     });
423
424     // Hook up the return key in the location bar.
425
//
426
location.addKeyListener(new KeyListener() {
427       public void keyPressed(KeyEvent e) {
428         if (e.character == '\r') {
429           go(location.getText());
430         }
431       }
432
433       public void keyReleased(KeyEvent e) {
434       }
435     });
436
437     // Tie the status label to the browser's status.
438
//
439
browser.addStatusTextListener(new StatusTextListener() {
440       public void changed(StatusTextEvent evt) {
441         // Add a little space so it doesn't look so crowded.
442
statusBar.setText(" " + evt.text);
443       }
444     });
445
446     browser.addTitleListener(new TitleListener() {
447       public void changed(TitleEvent evt) {
448         browser.getShell().setText(evt.title);
449       }
450     });
451
452     // Tie the location text box to the browser's location.
453
//
454
browser.addLocationListener(new LocationListener() {
455
456       public void changed(LocationEvent evt) {
457         if (evt.top) {
458           setLocationText(evt.location);
459         }
460       }
461
462       public void changing(LocationEvent evt) {
463         String JavaDoc whitelistRuleFound = null;
464         String JavaDoc blacklistRuleFound = null;
465         if (evt.location.indexOf(":") == -1) {
466           evt.location = "file://" + evt.location;
467         }
468         String JavaDoc url = evt.location;
469         evt.doit = false;
470
471         // Ensure that the request is 'safe', meaning it targets the user's
472
// local machine or a host that has been whitelisted.
473
//
474
if (BrowserWidgetHostChecker.isAlwaysWhitelisted(url)) {
475           // if the URL is 'always whitelisted', i.e. localhost
476
// we load the page without regard to blacklisting
477
evt.doit = true;
478           return;
479         }
480         whitelistRuleFound = BrowserWidgetHostChecker.matchWhitelisted(url);
481         blacklistRuleFound = BrowserWidgetHostChecker.matchBlacklisted(url);
482
483         // If a host is blacklisted and whitelisted, disallow
484
evt.doit = whitelistRuleFound != null && blacklistRuleFound == null;
485         // We need these if we show a dialog box, so we declare them here and
486
// initialize them inside the dialog box case before we change the
487
// [in]valid hosts
488
// no opinion either way
489
if (whitelistRuleFound == null && blacklistRuleFound == null) {
490           if (DialogBase.confirmAction(
491               (Shell) getParent(),
492               "Browsing to remote sites is a security risk! A malicious site could\r\n"
493                   + "execute Java code though this browser window. Only click \"Yes\" if you\r\n"
494                   + "are sure you trust the remote site. See the log for details and\r\n"
495                   + "configuration instructions.\r\n" + "\r\n" + "\r\n"
496                   + "Allow access to '" + url
497                   + "' for the rest of this session?\r\n", "Security Warning")) {
498             evt.doit = true;
499             BrowserWidgetHostChecker.whitelistURL(url);
500           } else {
501             evt.doit = false;
502             BrowserWidgetHostChecker.blacklistURL(url);
503           }
504         }
505
506         // Check for file system.
507
//
508
if (!evt.doit) {
509           // Rip off the query string part. When launching files directly from
510
// the filesystem, the existence of a query string when doing the
511
// lookup below causes problems (e.g. we don't want to look up a file
512
// called "C:\www\myapp.html?gwt.hybrid").
513
//
514
int lastQues = url.lastIndexOf('?');
515           int lastSlash = url.lastIndexOf(File.pathSeparatorChar);
516           if (lastQues != -1 && lastQues > lastSlash) {
517             url = url.substring(0, lastQues);
518           }
519
520           // If any part of the path exists, it is at least a valid attempt.
521
// This avoids the misleading security message when a file simply
522
// cannot be found.
523
//
524
if (!url.startsWith("http:") && !url.startsWith("https:")) {
525             File JavaDoc file = new File JavaDoc(url);
526             while (file != null) {
527               if (file.exists()) {
528                 evt.doit = true;
529                 break;
530               } else {
531                 String JavaDoc msg = "Cannot find file '" + file.getAbsolutePath()
532                     + "'";
533                 TreeLogger branch = logger.branch(TreeLogger.ERROR, msg, null);
534                 if ("gwt-hosted.html".equalsIgnoreCase(file.getName())) {
535                   branch.log(
536                       TreeLogger.ERROR,
537                       "If you want to open compiled output within this hosted browser, add '?gwt.hybrid' to the end of the URL",
538                       null);
539                 }
540               }
541               file = file.getParentFile();
542             }
543           }
544         }
545         // if it wasn't whitelisted or we were blocked we want to say something
546
if (whitelistRuleFound == null || !evt.doit) {
547           // Restore the URL.
548
String JavaDoc typeStr = "untrusted";
549           if (blacklistRuleFound != null) {
550             typeStr = "blocked";
551           }
552           TreeLogger header;
553           TreeLogger.Type msgType = TreeLogger.ERROR;
554           if (!evt.doit) {
555             header = logger.branch(msgType, "Unable to visit " + typeStr
556                 + " URL: '" + url, null);
557           } else {
558             msgType = TreeLogger.WARN;
559             header = logger.branch(TreeLogger.WARN,
560                 "Confirmation was required to visit " + typeStr + " URL: '"
561                     + url, null);
562           }
563           if (blacklistRuleFound == null) {
564             BrowserWidgetHostChecker.notifyUntrustedHost(url, header, msgType);
565           } else {
566             BrowserWidgetHostChecker.notifyBlacklistedHost(blacklistRuleFound,
567                 url, header, msgType);
568           }
569           setLocationText(browser.getUrl());
570         }
571       }
572
573     });
574
575     // Handle new window requests.
576
//
577
browser.addOpenWindowListener(new OpenWindowListener() {
578       public void open(WindowEvent event) {
579         try {
580           event.browser = host.openNewBrowserWindow().getBrowser();
581           event.browser.getShell().open();
582         } catch (UnableToCompleteException e) {
583           logger.log(TreeLogger.ERROR, "Unable to open new browser window", e);
584         }
585       }
586     });
587   }
588
589   private void setLocationText(String JavaDoc text) {
590     location.setText(text);
591     int length = text.length();
592     location.setSelection(length, length);
593   }
594 }
595
Popular Tags