/*
* Created on Jul 19, 2006 10:16:26 PM
* Copyright (C) 2006 Aelitis, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*/
package com.aelitis.azureus.ui.swt.browser;
import java.net.*;
import java.util.*;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.*;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.*;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.plugins.utils.StaticUtilities;
import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloader;
import org.gudy.azureus2.pluginsimpl.local.PluginInitializer;
import org.gudy.azureus2.ui.swt.Utils;
import org.gudy.azureus2.ui.swt.components.shell.ShellFactory;
import org.gudy.azureus2.ui.swt.mainwindow.TorrentOpener;
import org.gudy.azureus2.ui.swt.shells.MessageBoxShell;
import com.aelitis.azureus.core.messenger.ClientMessageContextImpl;
import com.aelitis.azureus.core.messenger.browser.listeners.BrowserMessageListener;
import com.aelitis.azureus.core.messenger.config.PlatformConfigMessenger;
import com.aelitis.azureus.core.vuzefile.VuzeFile;
import com.aelitis.azureus.core.vuzefile.VuzeFileHandler;
import com.aelitis.azureus.ui.swt.browser.msg.MessageDispatcherSWT;
import com.aelitis.azureus.util.ConstantsVuze;
import com.aelitis.azureus.util.JSONUtils;
import com.aelitis.azureus.util.UrlFilter;
/**
* Manages the context for a single SWT {@link Browser} component,
* including listeners and messages.
*
* @author dharkness
* @created Jul 19, 2006
*/
public class BrowserContext
extends ClientMessageContextImpl
implements DisposeListener
{
private static final String CONTEXT_KEY = "BrowserContext";
private static final String KEY_ENABLE_MENU = "browser.menu.enable";
private Browser browser;
private Display display;
private boolean pageLoading = false;
private long pageLoadingStart = 0;
private long pageLoadingEnd = 0;
private String lastValidURL = null;
private final boolean forceVisibleAfterLoad;
private TimerEventPeriodic checkURLEvent;
private Control widgetWaitIndicator;
private MessageDispatcherSWT messageDispatcherSWT;
protected boolean wiggleBrowser = Utils.isCarbon;
private torrentURLHandler torrentURLHandler;
private List loadingListeners = Collections.EMPTY_LIST;
private long pageLoadTime;
private long contentNetworkID = ConstantsVuze.DEFAULT_CONTENT_NETWORK_ID;
private AEMonitor mon_listJS = new AEMonitor("listJS");
private List<String> listJS = new ArrayList<String>(1);
private boolean allowPopups = true;
/**
* Creates a context and registers the given browser.
*
* @param id unique identifier of this context
* @param browser the browser to be registered
*/
public
BrowserContext(
String _id,
Browser _browser,
Control _widgetWaitingIndicator,
boolean _forceVisibleAfterLoad )
{
super( _id, null );
browser = _browser;
forceVisibleAfterLoad = _forceVisibleAfterLoad;
widgetWaitIndicator = _widgetWaitingIndicator;
// System.out.println( "Registered browser context: id=" + getID());
messageDispatcherSWT = new MessageDispatcherSWT(this);
setMessageDispatcher( messageDispatcherSWT );
final TimerEventPerformer showBrowersPerformer = new TimerEventPerformer() {
public void perform(TimerEvent event) {
if (browser != null && !browser.isDisposed()) {
Utils.execSWTThread(new AERunnable() {
public void runSupport() {
if (forceVisibleAfterLoad && browser != null
&& !browser.isDisposed() && !browser.isVisible()) {
browser.setVisible(true);
}
}
});
}
}
};
final TimerEventPerformer hideIndicatorPerformer = new TimerEventPerformer() {
public void perform(TimerEvent event) {
setPageLoading(false, browser.getUrl());
if (widgetWaitIndicator != null && !widgetWaitIndicator.isDisposed()) {
Utils.execSWTThread(new AERunnable() {
public void runSupport() {
if (widgetWaitIndicator != null
&& !widgetWaitIndicator.isDisposed()) {
widgetWaitIndicator.setVisible(false);
}
}
});
}
}
};
final TimerEventPerformer checkURLEventPerformer = new TimerEventPerformer() {
public void perform(TimerEvent event) {
if (browser != null && !browser.isDisposed()) {
Utils.execSWTThreadLater(0, new AERunnable() {
public void runSupport() {
if (browser != null && !browser.isDisposed()) {
browser.execute("try { "
+ "tuxLocString = document.location.toString();"
+ "if (tuxLocString.indexOf('res://') == 0) {"
+ " document.title = 'err: ' + tuxLocString;"
+ "} else {"
+ " tuxTitleString = document.title.toString();"
+ " if (tuxTitleString.indexOf('408 ') == 0 || tuxTitleString.indexOf('503 ') == 0 || tuxTitleString.indexOf('500 ') == 0) "
+ " { document.title = 'err: ' + tuxTitleString; } " + "}"
+ "} catch (e) { }");
}
}
});
}
}
};
if (forceVisibleAfterLoad) {
browser.setVisible(false);
}
setPageLoading(false, browser.getUrl());
if (widgetWaitIndicator != null && !widgetWaitIndicator.isDisposed()) {
widgetWaitIndicator.setVisible(false);
}
browser.addTitleListener(new TitleListener() {
public void changed(TitleEvent event) {
/*
* The browser might have been disposed already by the time this method is called
*/
if (browser == null || browser.isDisposed() || browser.getShell().isDisposed()) {
return;
}
if (!browser.isVisible()) {
SimpleTimer.addEvent("Show Browser",
System.currentTimeMillis() + 700, showBrowersPerformer);
}
if (event.title.startsWith("err: ")) {
fillWithRetry(event.title, "err in title");
}
}
});
browser.addProgressListener(new ProgressListener() {
public void changed(ProgressEvent event) {
//int pct = event.total == 0 ? 0 : 100 * event.current / event.total;
//System.out.println(pct + "%/" + event.current + "/" + event.total);
}
public void completed(ProgressEvent event) {
/*
* The browser might have been disposed already by the time this method is called
*/
if (browser == null || browser.isDisposed() || browser.getShell().isDisposed()) {
return;
}
checkURLEventPerformer.perform(null);
if (forceVisibleAfterLoad && !browser.isVisible()) {
browser.setVisible(true);
}
browser.execute("try { if (azureusClientWelcome) { azureusClientWelcome('"
+ ConstantsVuze.AZID
+ "',"
+ "{ 'azv':'"
+ org.gudy.azureus2.core3.util.Constants.AZUREUS_VERSION
+ "', 'browser-id':'" + getID() + "' }" + ");} } catch (e) { }");
if (org.gudy.azureus2.core3.util.Constants.isCVSVersion()
|| System.getProperty("debug.https", null) != null) {
if (browser.getUrl().indexOf("https") == 0) {
browser.execute("try { o = document.getElementsByTagName('body'); if (o) o[0].style.borderTop = '2px dotted #3b3b3b'; } catch (e) {}");
}
}
if (wiggleBrowser ) {
Shell shell = browser.getShell();
Point size = shell.getSize();
size.x -= 1;
size.y -= 1;
shell.setSize(size);
size.x += 1;
size.y += 1;
shell.setSize(size);
}
}
});
checkURLEvent = SimpleTimer.addPeriodicEvent("checkURL", 10000,
checkURLEventPerformer);
browser.addOpenWindowListener(new OpenWindowListener() {
public void open(WindowEvent event) {
if (browser == null || browser.isDisposed() || browser.getShell().isDisposed()) {
return;
}
event.required = true;
if (browser.getUrl().contains("js.debug=1")) {
Shell shell = ShellFactory.createMainShell(SWT.SHELL_TRIM);
shell.setLayout(new FillLayout());
Browser subBrowser = new Browser(shell,
Utils.getInitialBrowserStyle(SWT.NONE));
shell.open();
event.browser = subBrowser;
} else {
final Browser subBrowser = new Browser(browser,
Utils.getInitialBrowserStyle(SWT.NONE));
subBrowser.addLocationListener(new LocationListener() {
public void changed(LocationEvent arg0) {
// TODO Auto-generated method stub
}
public void changing(LocationEvent event) {
event.doit = false;
boolean wasAskCom = browser.getUrl().toLowerCase().startsWith(
"http://www.ask.com");
if (wasAskCom) {
Program.launch(event.location);
} else if (allowPopups()
&& !UrlFilter.getInstance().urlIsBlocked(event.location)
&& (event.location.startsWith("http://") || event.location.startsWith("https://"))) {
debug("open sub browser: " + event.location);
Program.launch(event.location);
} else {
debug("blocked open sub browser: " + event.location);
}
Utils.execSWTThreadLater(0, new AERunnable() {
public void runSupport() {
subBrowser.dispose();
}
});
}
});
event.browser = subBrowser;
}
}
});
browser.addLocationListener(new LocationListener() {
private TimerEvent timerevent;
public void changed(LocationEvent event) {
if (browser.isDisposed() || browser.getShell().isDisposed()) {
return;
}
debug("browser.changed " + event.location);
if (timerevent != null) {
timerevent.cancel();
}
checkURLEventPerformer.perform(null);
setPageLoading(false, event.top ? event.location : null);
if (widgetWaitIndicator != null && !widgetWaitIndicator.isDisposed()) {
widgetWaitIndicator.setVisible(false);
}
// event.top is only filled on changed event (not changing!)
if (!event.top) {
return;
}
String location = event.location.toLowerCase();
boolean isWebURL = location.startsWith("http://")
|| location.startsWith("https://");
if (!isWebURL) {
if (event.location.startsWith("res://")) {
fillWithRetry(event.location, "top changed");
return;
}
// we don't get a changed state on non URLs (mailto, javascript, etc)
}
if (UrlFilter.getInstance().isWhitelisted(event.location)) {
lastValidURL = event.location;
}
//System.out.println("cd" + event.location);
}
public void changing(LocationEvent event) {
// event.top is always false. changed event has it set though..
debug("browser.changing " + event.location + " from "
+ browser.getUrl() + ";" + event.top);
/*
* The browser might have been disposed already by the time this method is called
*/
if (browser.isDisposed() || browser.getShell().isDisposed()) {
return;
}
String event_location = event.location;
//Utils.openMessageBox(Utils.findAnyShell(), SWT.OK, "Location Changing", "Navigating to " + event_location );
if (event_location.startsWith("javascript")
&& event_location.indexOf("back()") > 0) {
if (browser.isBackEnabled()) {
browser.back();
} else if (lastValidURL != null) {
fillWithRetry(event_location, "back");
}
return;
}
String lowerLocation = event_location.toLowerCase();
boolean isOurURI = lowerLocation.startsWith("magnet:")
|| lowerLocation.startsWith("vuze:")
|| lowerLocation.startsWith("bc:")
|| lowerLocation.startsWith("bctp:")
|| lowerLocation.startsWith("dht:");
if (isOurURI) {
event.doit = false;
TorrentOpener.openTorrent(event_location);
return;
}
boolean isWebURL = lowerLocation.startsWith("http://")
|| lowerLocation.startsWith("https://");
if (!isWebURL) {
// we don't get a changed state on non URLs (mailto, javascript, etc)
return;
}
boolean blocked = UrlFilter.getInstance().urlIsBlocked(event_location);
if (!allowPopups()) {
if (blocked) {
return;
}
String curURL = browser.getUrl().toLowerCase();
boolean isPageLoadingOrRecent = isPageLoading()
|| (pageLoadingEnd > 0 && pageLoadingEnd + 500 > SystemTime.getCurrentTime())
|| event_location.contains(".admonkey.");
boolean wasSearch = curURL.startsWith(
"http://www.google.com/#q")
|| curURL.startsWith("http://www.google.com/search")
|| PlatformConfigMessenger.areLinksExternal(curURL);
boolean isSearch = event_location.startsWith("http://www.google.com/#q")
|| (event_location.startsWith("http://www.google.com/search"))
|| PlatformConfigMessenger.areLinksExternal(event_location);
if (wasSearch
&& !isSearch
&& !curURL.equalsIgnoreCase(event_location)
&& !event_location.equals("about:blank")
&& !isPageLoadingOrRecent) {
event.doit = false;
String[] contentTypes = getContentTypes(event_location, ((Browser)event.widget).getUrl());
boolean isTorrent = false;
for (String s : contentTypes) {
if ( s != null ){
if ( s.indexOf("torrent") != -1 ) {
isTorrent = true;
}
}
}
if (!isTorrent || !openTorrent(event)) {
Utils.launch(event.location);
}
return;
}
}
if (blocked) {
event.doit = false;
new MessageBoxShell(SWT.OK, "URL blocked", "Tried to open "
+ event_location + " but it's blocked").open(null);
browser.back();
} else {
if (UrlFilter.getInstance().isWhitelisted(event_location)) {
lastValidURL = event_location;
}
setPageLoading(true, event.location);
if(event.top) {
if (widgetWaitIndicator != null && !widgetWaitIndicator.isDisposed()) {
widgetWaitIndicator.setVisible(true);
}
// Backup in case changed(..) is never called
timerevent = SimpleTimer.addEvent("Hide Indicator",
System.currentTimeMillis() + 20000, hideIndicatorPerformer);
} else {
boolean isTorrent = false;
boolean isVuzeFile = false;
//Try to catch .torrent files
// URLs ending in "?torrent" on Amazon S3's Simple Storage Service
// return an auto-generated a torrent based on the url, but only on
// GET. HEAD will fail, so we have to trap and assume
if(event_location.endsWith(".torrent") || event_location.endsWith("?torrent")) {
isTorrent = true;
} else {
//If it's not obviously a web page
boolean can_rpc = UrlFilter.getInstance().urlCanRPC(event_location);
boolean test_for_torrent = !can_rpc && event_location.indexOf(".htm") == -1;
boolean test_for_vuze = can_rpc && ( event_location.endsWith( ".xml" ) || event_location.endsWith( ".vuze" ));
if ( test_for_torrent || test_for_vuze ){
String[] contentTypes = getContentTypes(event_location, ((Browser)event.widget).getUrl());
for (String s : contentTypes) {
if ( s != null ){
if ( test_for_torrent && s.indexOf("torrent") != -1 ) {
isTorrent = true;
}
if ( test_for_vuze && s.indexOf("vuze") != -1 ) {
isVuzeFile = true;
}
}
}
//System.out.println( "Test for t/v: " + event_location + " -> " + isTorrent + "/" + isVuzeFile );
}
}
if ( isTorrent ){
openTorrent(event);
}else if ( isVuzeFile ){
event.doit = false;
setPageLoading(false, event.location);
try {
String referer_str = null;
try{
referer_str = new URL(((Browser)event.widget).getUrl()).toExternalForm();
}catch( Throwable e ){
}
Map headers = UrlUtils.getBrowserHeaders( referer_str );
String cookies = (String) ((Browser)event.widget).getData("current-cookies");
if ( cookies != null ){
headers.put("Cookie", cookies);
}
ResourceDownloader rd = StaticUtilities.getResourceDownloaderFactory().create( new URL( event_location ));
VuzeFileHandler vfh = VuzeFileHandler.getSingleton();
VuzeFile vf = vfh.loadVuzeFile( rd.download());
if ( vf == null ){
event.doit = true;
setPageLoading(true, event.location);
}else{
vfh.handleFiles( new VuzeFile[]{ vf }, 0 );
}
}catch( Throwable e ){
e.printStackTrace();
}
}
}
}
}
});
browser.setData(CONTEXT_KEY, this);
browser.addDisposeListener(this);
// enable right-click context menu only if system property is set
final boolean enableMenu = System.getProperty(KEY_ENABLE_MENU, "0").equals(
"1");
browser.addListener(SWT.MenuDetect, new Listener() {
// @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event)
public void handleEvent(Event event) {
event.doit = enableMenu;
}
});
messageDispatcherSWT.registerBrowser(browser);
this.display = browser.getDisplay();
}
protected boolean openTorrent(LocationEvent event) {
event.doit = false;
setPageLoading(false, event.location);
try {
String referer_str = null;
try{
referer_str = new URL(((Browser)event.widget).getUrl()).toExternalForm();
}catch( Throwable e ){
}
final Map headers = UrlUtils.getBrowserHeaders( referer_str );
String cookies = (String) ((Browser)event.widget).getData("current-cookies");
if (cookies != null ){
headers.put("Cookie", cookies);
}
final String url = event.location;
if ( torrentURLHandler != null ){
try{
torrentURLHandler.handleTorrentURL(url);
}catch( Throwable e ){
Debug.printStackTrace(e);
}
}
Utils.getOffOfSWTThread(new AERunnable() {
public void runSupport() {
try {
PluginInitializer.getDefaultInterface().getDownloadManager().addDownload(
new URL(url), headers );
} catch (Exception e) {
Debug.out(e);
}
}
});
return true;
}catch( Throwable e ){
Debug.out(e);
return false;
}
}
protected String[] getContentTypes(String event_location, String _referer) {
try {
//See what the content type is
URL url = new URL(event_location);
URLConnection conn = url.openConnection();
// we're only trying to get the content type so just use head
((HttpURLConnection) conn).setRequestMethod("HEAD");
String referer_str = null;
try {
URL referer = new URL(_referer);
if (referer != null) {
referer_str = referer.toExternalForm();
}
} catch (Throwable e) {
}
UrlUtils.setBrowserHeaders(conn, referer_str);
UrlUtils.connectWithTimeouts(conn, 1500, 5000);
String contentType = conn.getContentType();
String contentDisposition = conn.getHeaderField("Content-Disposition");
// There's a bug in the ":3" server where a HEAD followed by a GET on the
// same content results in a corrupt GET reply
String server = conn.getHeaderField("Server");
if ("application/x-bittorrent".equals(contentType) && ":3".equals(server)) {
Thread.sleep(6000);
}
return new String[] {
contentType,
contentDisposition
};
} catch (Throwable e) {
}
return new String[0];
}
/**
* @param b
* @param url
*
* @since 3.1.1.1
*/
protected void setPageLoading(boolean b, String url) {
//System.out.println("SPL: " + b + ";" + url + ";" + Debug.getCompressedStackTrace());
// we may get multiple "load done"s (from each frame) which we don't
// want to skip
if (b && pageLoading) {
return;
}
mon_listJS.enter();
try {
pageLoading = b;
if (pageLoading) {
pageLoadingStart = SystemTime.getCurrentTime();
pageLoadTime = -1;
} else if (pageLoadingStart > 0 && url != null) {
pageLoadingEnd = SystemTime.getCurrentTime();
pageLoadTime = pageLoadingEnd - pageLoadingStart;
executeInBrowser("clientSetLoadTime(" + pageLoadTime + ");");
pageLoadingStart = 0;
}
if (!pageLoading && listJS.size() > 0) {
debug(listJS.size() + " javascripts queued. Executing now..");
for (String js : listJS) {
executeInBrowser(js);
}
listJS.clear();
}
} finally {
mon_listJS.exit();
}
Object[] listeners = loadingListeners.toArray();
for (int i = 0; i < listeners.length; i++) {
loadingListener l = (loadingListener) listeners[i];
l.browserLoadingChanged(b, url);
}
}
public void
setTorrentURLHandler(
torrentURLHandler handler)
{
torrentURLHandler = handler;
}
public void fillWithRetry(String s, String s2) {
Color bg = browser.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
Color fg = browser.getDisplay().getSystemColor(SWT.COLOR_WIDGET_FOREGROUND);
browser.setText("<html><body style='overflow:auto; font-family: verdana; font-size: 10pt' bgcolor=#"
+ Utils.toColorHexString(bg)
+ " text=#" + Utils.toColorHexString(fg) + ">"
+ "<br>Sorry, there was a problem loading this page.<br> "
+ "Please check if your internet connection is working and click <a href='"
+ lastValidURL
+ "' style=\"color: rgb(100, 155, 255); \">retry</a> to continue."
+ "<div style='word-wrap: break-word'><font size=1 color=#"
+ Utils.toColorHexString(bg)
+ ">"
+ s + "<br><br>" + s2
+ "</font></div>" + "</body></html>");
}
private void
deregisterBrowser()
{
if (browser == null) {
throw new IllegalStateException("Context " + getID()
+ " doesn't have a registered browser");
}
// System.out.println( "Unregistered browser context: id=" + getID());
if (!browser.isDisposed()) {
browser.setData(CONTEXT_KEY, null);
browser.removeDisposeListener(this);
messageDispatcherSWT.deregisterBrowser(browser);
}
browser = null;
if (checkURLEvent != null && !checkURLEvent.isCancelled()) {
checkURLEvent.cancel();
checkURLEvent = null;
}
}
/**
* Accesses the context associated with the given browser.
*
* @param browser holds the context in its application data map
* @return the browser's context or <code>null</code> if there is none
*/
public static BrowserContext getContext(Browser browser) {
Object data = browser.getData(CONTEXT_KEY);
if (data != null && !(data instanceof BrowserContext)) {
Debug.out("Data in Browser with key " + CONTEXT_KEY
+ " is not a BrowserContext");
return null;
}
return (BrowserContext) data;
}
public void addMessageListener(BrowserMessageListener listener) {
messageDispatcherSWT.addListener(listener);
}
public Object getBrowserData(String key) {
return browser.getData(key);
}
public void setBrowserData(String key, Object value) {
browser.setData(key, value);
}
public boolean sendBrowserMessage(String key, String op) {
return sendBrowserMessage(key, op, (Map) null);
}
public boolean sendBrowserMessage(String key, String op, Map params) {
StringBuffer msg = new StringBuffer();
msg.append("az.msg.dispatch('").append(key).append("', '").append(op).append(
"'");
if (params != null) {
msg.append(", ").append(JSONUtils.encodeToJSON(params));
}
msg.append(")");
return executeInBrowser(msg.toString());
}
public boolean sendBrowserMessage(String key, String op, Collection params) {
StringBuffer msg = new StringBuffer();
msg.append("az.msg.dispatch('").append(key).append("', '").append(op).append(
"'");
if (params != null) {
msg.append(", ").append(JSONUtils.encodeToJSON(params));
}
msg.append(")");
return executeInBrowser(msg.toString());
}
protected boolean maySend(String key, String op, Map params) {
return !pageLoading;
}
public boolean executeInBrowser(final String javascript) {
mon_listJS.enter();
try {
if (!mayExecute(javascript)) {
listJS.add(javascript);
return false;
}
} finally {
mon_listJS.exit();
}
if (display == null || display.isDisposed()) {
debug("CANNOT: browser.execute( " + getShortJavascript(javascript) + " )");
return false;
}
// swallow errors silently
final String reallyExecute = "try { " + javascript + " } catch ( e ) { }";
Utils.execSWTThread(new AERunnable() {
public void runSupport() {
if (browser == null || browser.isDisposed()) {
debug("CANNOT: browser.execute( " + getShortJavascript(javascript)
+ " )");
} else if (!browser.execute(reallyExecute)) {
debug("FAILED: browser.execute( " + getShortJavascript(javascript)
+ " )");
} else {
debug("SUCCESS: browser.execute( " + getShortJavascript(javascript)
+ " )");
}
}
});
return true;
}
protected boolean mayExecute(String javascript) {
return !pageLoading;
}
public void widgetDisposed(DisposeEvent event) {
if (event.widget == browser) {
deregisterBrowser();
}
}
private String getShortJavascript(String javascript) {
if (javascript.length() < (256 + 3 + 256)) {
return javascript;
}
StringBuffer result = new StringBuffer();
result.append(javascript.substring(0, 256));
result.append("...");
result.append(javascript.substring(javascript.length() - 256));
return result.toString();
}
public void setWiggleBrowser(boolean wiggleBrowser) {
this.wiggleBrowser = wiggleBrowser;
}
public boolean isPageLoading() {
return pageLoading;
}
public void addListener(loadingListener l) {
if (loadingListeners == Collections.EMPTY_LIST) {
loadingListeners = new ArrayList(1);
}
loadingListeners.add(l);
}
public static interface loadingListener {
public void browserLoadingChanged(boolean loading, String url);
}
public long getContentNetworkID() {
return contentNetworkID;
}
public void setContentNetworkID(long contentNetworkID) {
this.contentNetworkID = contentNetworkID;
}
public void setAllowPopups(boolean allowPopups) {
this.allowPopups = allowPopups;
}
public boolean allowPopups() {
return allowPopups;
}
}