| /* |
| * Copyright 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.user.client.impl; |
| |
| import com.google.gwt.dom.client.Document; |
| import com.google.gwt.dom.client.Element; |
| import com.google.gwt.user.client.Window.Location; |
| |
| /** |
| * History implementation for IE6 and IE7, which do not support the onhashchange |
| * event, and for which {@link HistoryImplTimer} will not work either. |
| */ |
| class HistoryImplIE6 extends HistoryImpl { |
| |
| /** |
| * Sanitizes an untrusted string to be used in an HTML context. NOTE: This |
| * method of escaping strings should only be used on Internet Explorer. |
| * |
| * @param maybeHtml untrusted string that may contain html |
| * @return sanitized string |
| */ |
| private static String escapeHtml(String maybeHtml) { |
| final Element div = Document.get().createDivElement(); |
| div.setInnerText(maybeHtml); |
| return div.getInnerHTML(); |
| } |
| |
| private static native Element findHistoryFrame() /*-{ |
| return $doc.getElementById('__gwt_historyFrame'); |
| }-*/; |
| |
| /** |
| * For IE6, reading from $wnd.location.hash drops part of the fragment if the |
| * fragment contains a '?'. To avoid this bug, we use location.href instead. |
| */ |
| private static native String getLocationHash() /*-{ |
| var href = $wnd.location.href; |
| var hashLoc = href.lastIndexOf("#"); |
| return (hashLoc > 0) ? href.substring(hashLoc) : ""; |
| }-*/; |
| |
| private static native Element getTokenElement(Element historyFrame) /*-{ |
| // Initialize the history iframe. If '__gwt_historyToken' already exists, then |
| // we're probably backing into the app, so _don't_ set the iframe's location. |
| if (historyFrame.contentWindow) { |
| var doc = historyFrame.contentWindow.document; |
| return doc.getElementById('__gwt_historyToken'); |
| } |
| }-*/; |
| |
| protected Element historyFrame; |
| private boolean reloadedWindow; |
| |
| @Override |
| public boolean init() { |
| historyFrame = findHistoryFrame(); |
| if (historyFrame == null) { |
| return false; |
| } |
| |
| initHistoryToken(); |
| |
| // Initialize the history iframe. If a token element already exists, then |
| // we're probably backing into the app, so _don't_ create a new item. |
| Element tokenElement = getTokenElement(historyFrame); |
| if (tokenElement != null) { |
| setToken(getTokenElementContent(tokenElement)); |
| } else { |
| navigateFrame(getToken()); |
| } |
| |
| injectGlobalHandler(); |
| initUrlCheckTimer(); |
| return true; |
| } |
| |
| @Override |
| protected final void nativeUpdate(String historyToken) { |
| /* |
| * Must update the location hash since it isn't already correct. |
| */ |
| updateHash(historyToken); |
| navigateFrame(historyToken); |
| } |
| |
| @Override |
| protected final void nativeUpdateOnEvent(String historyToken) { |
| updateHash(historyToken); |
| } |
| |
| private native String getTokenElementContent(Element tokenElement) /*-{ |
| return tokenElement.innerText; |
| }-*/; |
| |
| /** |
| * The URL check timer sometimes reloads the window to work around an IE bug. |
| * However, the user might cancel the page navigation, resulting in a mismatch |
| * between the current history token and the URL hash value. |
| * |
| * @return true if a canceled window reload was handled |
| */ |
| private boolean handleWindowReloadCanceled() { |
| if (reloadedWindow) { |
| reloadedWindow = false; |
| updateHash(getToken()); |
| return true; |
| } |
| return false; |
| } |
| |
| private native void initHistoryToken() /*-{ |
| // Assume an empty token. |
| var token = ''; |
| // Get the initial token from the url's hash component. |
| var hash = @com.google.gwt.user.client.impl.HistoryImplIE6::getLocationHash()(); |
| if (hash.length > 0) { |
| try { |
| token = this.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1)); |
| } catch (e) { |
| // Clear the bad hash (this can't have been a valid token). |
| $wnd.location.hash = ''; |
| } |
| } |
| @com.google.gwt.user.client.impl.HistoryImpl::setToken(Ljava/lang/String;)(token); |
| }-*/; |
| |
| private native void initUrlCheckTimer() /*-{ |
| // This is the URL check timer. It detects when an unexpected change |
| // occurs in the document's URL (e.g. when the user enters one manually |
| // or selects a 'favorite', but only the #hash part changes). When this |
| // occurs, we _must_ reload the page. This is because IE has a really |
| // nasty bug that totally mangles its history stack and causes the location |
| // bar in the UI to stop working under these circumstances. |
| var historyImplRef = this; |
| var urlChecker = $entry(function() { |
| $wnd.setTimeout(urlChecker, 250); |
| |
| // Reset the hash if the user cancels a window reload triggered by the |
| // urlChecker. |
| if (historyImplRef.@com.google.gwt.user.client.impl.HistoryImplIE6::handleWindowReloadCanceled()()) { |
| return; |
| } |
| |
| var hash = @com.google.gwt.user.client.impl.HistoryImplIE6::getLocationHash()(); |
| if (hash.length > 0) { |
| var token = ''; |
| try { |
| token = historyImplRef.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1)); |
| } catch (e) { |
| // If there's a bad hash, always reload. This could only happen if |
| // if someone entered or linked to a bad url. |
| historyImplRef.@com.google.gwt.user.client.impl.HistoryImplIE6::reloadWindow()(); |
| } |
| |
| var historyToken = @com.google.gwt.user.client.impl.HistoryImpl::getToken()(); |
| if (historyToken && (token != historyToken)) { |
| historyImplRef.@com.google.gwt.user.client.impl.HistoryImplIE6::reloadWindow()(); |
| } |
| } |
| }); |
| urlChecker(); |
| }-*/; |
| |
| private native void injectGlobalHandler() /*-{ |
| var historyImplRef = this; |
| var oldOnLoad = $wnd.__gwt_onHistoryLoad; |
| |
| $wnd.__gwt_onHistoryLoad = $entry(function(token) { |
| historyImplRef.@com.google.gwt.user.client.impl.HistoryImpl::newItemOnEvent(Ljava/lang/String;)(token); |
| |
| if (oldOnLoad) { |
| oldOnLoad(token); |
| } |
| }); |
| }-*/; |
| |
| private native void navigateFrame(String token) /*-{ |
| var escaped = @com.google.gwt.user.client.impl.HistoryImplIE6::escapeHtml(Ljava/lang/String;)(token); |
| var doc = this.@com.google.gwt.user.client.impl.HistoryImplIE6::historyFrame.contentWindow.document; |
| doc.open(); |
| doc.write('<html><body onload="if(parent.__gwt_onHistoryLoad)parent.__gwt_onHistoryLoad(__gwt_historyToken.innerText)"><div id="__gwt_historyToken">' + escaped + '</div></body></html>'); |
| doc.close(); |
| }-*/; |
| |
| private void reloadWindow() { |
| reloadedWindow = true; |
| Location.reload(); |
| } |
| |
| private native void updateHash(String token) /*-{ |
| $wnd.location.hash = this.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)(token); |
| }-*/; |
| } |