/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim: set ts=4 sw=4 tw=80 et: */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Mozilla browser. * * The Initial Developer of the Original Code is * Netscape Communications, Inc. * Portions created by the Initial Developer are Copyright (C) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Travis Bogard * Pierre Phaneuf * Peter Annema * Dan Rosen * Mats Palmgren * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "mozilla/Util.h" #ifdef MOZ_LOGGING // so we can get logging even in release builds (but only for some things) #define FORCE_PR_LOG 1 #endif #include "nsIBrowserDOMWindow.h" #include "nsIComponentManager.h" #include "nsIContent.h" #include "mozilla/dom/Element.h" #include "nsIDocument.h" #include "nsIDOMDocument.h" #include "nsIDOMElement.h" #include "nsIDOMStorage.h" #include "nsPIDOMStorage.h" #include "nsIContentViewer.h" #include "nsIDocumentLoaderFactory.h" #include "nsCURILoader.h" #include "nsURILoader.h" #include "nsDocShellCID.h" #include "nsLayoutCID.h" #include "nsDOMCID.h" #include "nsIDOMScriptObjectFactory.h" #include "nsNetUtil.h" #include "nsRect.h" #include "prprf.h" #include "prenv.h" #include "nsIMarkupDocumentViewer.h" #include "nsXPIDLString.h" #include "nsReadableUtils.h" #include "nsIDOMChromeWindow.h" #include "nsIDOMWindow.h" #include "nsIWebBrowserChrome.h" #include "nsPoint.h" #include "nsGfxCIID.h" #include "nsIObserverService.h" #include "nsIPrompt.h" #include "nsIAuthPrompt.h" #include "nsIAuthPrompt2.h" #include "nsTextFormatter.h" #include "nsIChannelEventSink.h" #include "nsIAsyncVerifyRedirectCallback.h" #include "nsIUploadChannel.h" #include "nsISecurityEventSink.h" #include "mozilla/FunctionTimer.h" #include "nsIScriptSecurityManager.h" #include "nsIJSContextStack.h" #include "nsIScriptObjectPrincipal.h" #include "nsIScrollableFrame.h" #include "nsContentPolicyUtils.h" // NS_CheckContentLoadPolicy(...) #include "nsICategoryManager.h" #include "nsXPCOMCID.h" #include "nsISeekableStream.h" #include "nsAutoPtr.h" #include "nsIWritablePropertyBag2.h" #include "nsIAppShell.h" #include "nsWidgetsCID.h" #include "nsDOMJSUtils.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIView.h" #include "nsIViewManager.h" #include "nsIScriptChannel.h" #include "nsIOfflineCacheUpdate.h" #include "nsITimedChannel.h" #include "nsCPrefetchService.h" #include "nsJSON.h" #include "IHistory.h" #include "mozilla/Services.h" #include "mozilla/Preferences.h" #include "mozilla/Telemetry.h" #include "mozilla/AutoRestore.h" // we want to explore making the document own the load group // so we can associate the document URI with the load group. // until this point, we have an evil hack: #include "nsIHttpChannelInternal.h" // Local Includes #include "nsDocShell.h" #include "nsDocShellLoadInfo.h" #include "nsCDefaultURIFixup.h" #include "nsDocShellEnumerator.h" #include "nsSHistory.h" #include "nsDocShellEditorData.h" // Helper Classes #include "nsDOMError.h" #include "nsEscape.h" // Interfaces Needed #include "nsIUploadChannel.h" #include "nsIProgressEventSink.h" #include "nsIWebProgress.h" #include "nsILayoutHistoryState.h" #include "nsITimer.h" #include "nsISHistoryInternal.h" #include "nsIPrincipal.h" #include "nsIFileURL.h" #include "nsIHistoryEntry.h" #include "nsISHistoryListener.h" #include "nsIWindowWatcher.h" #include "nsIPromptFactory.h" #include "nsIObserver.h" #include "nsINestedURI.h" #include "nsITransportSecurityInfo.h" #include "nsINSSErrorsService.h" #include "nsIApplicationCache.h" #include "nsIApplicationCacheChannel.h" #include "nsIApplicationCacheContainer.h" #include "nsIPermissionManager.h" #include "nsStreamUtils.h" #include "nsIController.h" #include "nsPICommandUpdater.h" #include "nsIDOMHTMLAnchorElement.h" #include "nsIWebBrowserChrome3.h" #include "nsITabChild.h" #include "nsIStrictTransportSecurityService.h" #include "nsStructuredCloneContainer.h" #include "nsIStructuredCloneContainer.h" #include "nsIFaviconService.h" #include "mozIAsyncFavicons.h" // Editor-related #include "nsIEditingSession.h" #include "nsPIDOMWindow.h" #include "nsPIWindowRoot.h" #include "nsIDOMDocument.h" #include "nsICachingChannel.h" #include "nsICacheVisitor.h" #include "nsICacheEntryDescriptor.h" #include "nsIMultiPartChannel.h" #include "nsIWyciwygChannel.h" // For reporting errors with the console service. // These can go away if error reporting is propagated up past nsDocShell. #include "nsIConsoleService.h" #include "nsIScriptError.h" // used to dispatch urls to default protocol handlers #include "nsCExternalHandlerService.h" #include "nsIExternalProtocolService.h" #include "nsFocusManager.h" #include "nsITextToSubURI.h" #include "nsIJARChannel.h" #include "prlog.h" #include "prmem.h" #include "nsISelectionDisplay.h" #include "nsIGlobalHistory2.h" #ifdef DEBUG_DOCSHELL_FOCUS #include "nsEventStateManager.h" #endif #include "nsIFrame.h" // for embedding #include "nsIWebBrowserChromeFocus.h" #if NS_PRINT_PREVIEW #include "nsIDocumentViewerPrint.h" #include "nsIWebBrowserPrint.h" #endif #include "nsPluginError.h" #include "nsContentUtils.h" #include "nsContentErrors.h" #include "nsIChannelPolicy.h" #include "nsIContentSecurityPolicy.h" #include "nsXULAppAPI.h" #include "nsDOMNavigationTiming.h" #include "nsITimedChannel.h" #include "mozilla/StartupTimeline.h" static NS_DEFINE_CID(kDOMScriptObjectFactoryCID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID); static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); #if defined(DEBUG_bryner) || defined(DEBUG_chb) //#define DEBUG_DOCSHELL_FOCUS #define DEBUG_PAGE_CACHE #endif using namespace mozilla; // Number of documents currently loading static PRInt32 gNumberOfDocumentsLoading = 0; // Global count of existing docshells. static PRInt32 gDocShellCount = 0; // Global reference to the URI fixup service. nsIURIFixup *nsDocShell::sURIFixup = 0; // True means we validate window targets to prevent frameset // spoofing. Initialize this to a non-bolean value so we know to check // the pref on the creation of the first docshell. static PRUint32 gValidateOrigin = 0xffffffff; // Hint for native dispatch of events on how long to delay after // all documents have loaded in milliseconds before favoring normal // native event dispatch priorites over performance #define NS_EVENT_STARVATION_DELAY_HINT 2000 // This is needed for displaying an error message // when navigation is attempted on a document when printing // The value arbitrary as long as it doesn't conflict with // any of the other values in the errors in DisplayLoadError #define NS_ERROR_DOCUMENT_IS_PRINTMODE NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_GENERAL,2001) #ifdef PR_LOGGING #ifdef DEBUG static PRLogModuleInfo* gDocShellLog; #endif static PRLogModuleInfo* gDocShellLeakLog; #endif const char kBrandBundleURL[] = "chrome://branding/locale/brand.properties"; const char kAppstringsBundleURL[] = "chrome://global/locale/appstrings.properties"; static void FavorPerformanceHint(bool perfOverStarvation, PRUint32 starvationDelay) { nsCOMPtr appShell = do_GetService(kAppShellCID); if (appShell) appShell->FavorPerformanceHint(perfOverStarvation, starvationDelay); } //***************************************************************************** // support //***************************************************************************** #define PREF_PINGS_ENABLED "browser.send_pings" #define PREF_PINGS_MAX_PER_LINK "browser.send_pings.max_per_link" #define PREF_PINGS_REQUIRE_SAME_HOST "browser.send_pings.require_same_host" // Check prefs to see if pings are enabled and if so what restrictions might // be applied. // // @param maxPerLink // This parameter returns the number of pings that are allowed per link click // // @param requireSameHost // This parameter returns true if pings are restricted to the same host as // the document in which the click occurs. If the same host restriction is // imposed, then we still allow for pings to cross over to different // protocols and ports for flexibility and because it is not possible to send // a ping via FTP. // // @returns // true if pings are enabled and false otherwise. // static bool PingsEnabled(PRInt32 *maxPerLink, bool *requireSameHost) { bool allow = Preferences::GetBool(PREF_PINGS_ENABLED, false); *maxPerLink = 1; *requireSameHost = true; if (allow) { Preferences::GetInt(PREF_PINGS_MAX_PER_LINK, maxPerLink); Preferences::GetBool(PREF_PINGS_REQUIRE_SAME_HOST, requireSameHost); } return allow; } static bool CheckPingURI(nsIURI* uri, nsIContent* content) { if (!uri) return false; // Check with nsIScriptSecurityManager nsCOMPtr ssmgr = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); NS_ENSURE_TRUE(ssmgr, false); nsresult rv = ssmgr->CheckLoadURIWithPrincipal(content->NodePrincipal(), uri, nsIScriptSecurityManager::STANDARD); if (NS_FAILED(rv)) { return false; } // Ignore non-HTTP(S) bool match; if ((NS_FAILED(uri->SchemeIs("http", &match)) || !match) && (NS_FAILED(uri->SchemeIs("https", &match)) || !match)) { return false; } // Check with contentpolicy PRInt16 shouldLoad = nsIContentPolicy::ACCEPT; rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_PING, uri, content->NodePrincipal(), content, EmptyCString(), // mime hint nsnull, //extra &shouldLoad); return NS_SUCCEEDED(rv) && NS_CP_ACCEPTED(shouldLoad); } typedef void (* ForEachPingCallback)(void *closure, nsIContent *content, nsIURI *uri, nsIIOService *ios); static void ForEachPing(nsIContent *content, ForEachPingCallback callback, void *closure) { // NOTE: Using nsIDOMHTMLAnchorElement::GetPing isn't really worth it here // since we'd still need to parse the resulting string. Instead, we // just parse the raw attribute. It might be nice if the content node // implemented an interface that exposed an enumeration of nsIURIs. // Make sure we are dealing with either an or element in the HTML // or XHTML namespace. if (!content->IsHTML()) return; nsIAtom *nameAtom = content->Tag(); if (!nameAtom->Equals(NS_LITERAL_STRING("a")) && !nameAtom->Equals(NS_LITERAL_STRING("area"))) return; nsCOMPtr pingAtom = do_GetAtom("ping"); if (!pingAtom) return; nsAutoString value; content->GetAttr(kNameSpaceID_None, pingAtom, value); if (value.IsEmpty()) return; nsCOMPtr ios = do_GetIOService(); if (!ios) return; nsIDocument *doc = content->OwnerDoc(); // value contains relative URIs split on spaces (U+0020) const PRUnichar *start = value.BeginReading(); const PRUnichar *end = value.EndReading(); const PRUnichar *iter = start; for (;;) { if (iter < end && *iter != ' ') { ++iter; } else { // iter is pointing at either end or a space while (*start == ' ' && start < iter) ++start; if (iter != start) { nsCOMPtr uri, baseURI = content->GetBaseURI(); ios->NewURI(NS_ConvertUTF16toUTF8(Substring(start, iter)), doc->GetDocumentCharacterSet().get(), baseURI, getter_AddRefs(uri)); if (CheckPingURI(uri, content)) { callback(closure, content, uri, ios); } } start = iter = iter + 1; if (iter >= end) break; } } } //---------------------------------------------------------------------- // We wait this many milliseconds before killing the ping channel... #define PING_TIMEOUT 10000 static void OnPingTimeout(nsITimer *timer, void *closure) { nsILoadGroup *loadGroup = static_cast(closure); loadGroup->Cancel(NS_ERROR_ABORT); loadGroup->Release(); } // Check to see if two URIs have the same host or not static bool IsSameHost(nsIURI *uri1, nsIURI *uri2) { nsCAutoString host1, host2; uri1->GetAsciiHost(host1); uri2->GetAsciiHost(host2); return host1.Equals(host2); } class nsPingListener : public nsIStreamListener , public nsIInterfaceRequestor , public nsIChannelEventSink { public: NS_DECL_ISUPPORTS NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSICHANNELEVENTSINK nsPingListener(bool requireSameHost, nsIContent* content) : mRequireSameHost(requireSameHost), mContent(content) {} private: bool mRequireSameHost; nsCOMPtr mContent; }; NS_IMPL_ISUPPORTS4(nsPingListener, nsIStreamListener, nsIRequestObserver, nsIInterfaceRequestor, nsIChannelEventSink) NS_IMETHODIMP nsPingListener::OnStartRequest(nsIRequest *request, nsISupports *context) { return NS_OK; } NS_IMETHODIMP nsPingListener::OnDataAvailable(nsIRequest *request, nsISupports *context, nsIInputStream *stream, PRUint32 offset, PRUint32 count) { PRUint32 result; return stream->ReadSegments(NS_DiscardSegment, nsnull, count, &result); } NS_IMETHODIMP nsPingListener::OnStopRequest(nsIRequest *request, nsISupports *context, nsresult status) { return NS_OK; } NS_IMETHODIMP nsPingListener::GetInterface(const nsIID &iid, void **result) { if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) { NS_ADDREF_THIS(); *result = (nsIChannelEventSink *) this; return NS_OK; } return NS_ERROR_NO_INTERFACE; } NS_IMETHODIMP nsPingListener::AsyncOnChannelRedirect(nsIChannel *oldChan, nsIChannel *newChan, PRUint32 flags, nsIAsyncVerifyRedirectCallback *callback) { nsCOMPtr newURI; newChan->GetURI(getter_AddRefs(newURI)); if (!CheckPingURI(newURI, mContent)) return NS_ERROR_ABORT; if (!mRequireSameHost) { callback->OnRedirectVerifyCallback(NS_OK); return NS_OK; } // XXXbz should this be using something more like the nsContentUtils // same-origin checker? nsCOMPtr oldURI; oldChan->GetURI(getter_AddRefs(oldURI)); NS_ENSURE_STATE(oldURI && newURI); if (!IsSameHost(oldURI, newURI)) return NS_ERROR_ABORT; callback->OnRedirectVerifyCallback(NS_OK); return NS_OK; } struct SendPingInfo { PRInt32 numPings; PRInt32 maxPings; bool requireSameHost; nsIURI *referrer; }; static void SendPing(void *closure, nsIContent *content, nsIURI *uri, nsIIOService *ios) { SendPingInfo *info = static_cast(closure); if (info->numPings >= info->maxPings) return; if (info->requireSameHost) { // Make sure the referrer and the given uri share the same origin. We // only require the same hostname. The scheme and port may differ. if (!IsSameHost(uri, info->referrer)) return; } nsIDocument *doc = content->OwnerDoc(); nsCOMPtr chan; ios->NewChannelFromURI(uri, getter_AddRefs(chan)); if (!chan) return; // Don't bother caching the result of this URI load. chan->SetLoadFlags(nsIRequest::INHIBIT_CACHING); nsCOMPtr httpChan = do_QueryInterface(chan); if (!httpChan) return; // This is needed in order for 3rd-party cookie blocking to work. nsCOMPtr httpInternal = do_QueryInterface(httpChan); if (httpInternal) httpInternal->SetDocumentURI(doc->GetDocumentURI()); if (info->referrer) httpChan->SetReferrer(info->referrer); httpChan->SetRequestMethod(NS_LITERAL_CSTRING("POST")); // Remove extraneous request headers (to reduce request size) httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept"), EmptyCString(), false); httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-language"), EmptyCString(), false); httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-encoding"), EmptyCString(), false); nsCOMPtr uploadChan = do_QueryInterface(httpChan); if (!uploadChan) return; // To avoid sending an unnecessary Content-Type header, we encode the // closing portion of the headers in the POST body. NS_NAMED_LITERAL_CSTRING(uploadData, "Content-Length: 0\r\n\r\n"); nsCOMPtr uploadStream; NS_NewPostDataStream(getter_AddRefs(uploadStream), false, uploadData, 0); if (!uploadStream) return; uploadChan->SetUploadStream(uploadStream, EmptyCString(), -1); // The channel needs to have a loadgroup associated with it, so that we can // cancel the channel and any redirected channels it may create. nsCOMPtr loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID); if (!loadGroup) return; chan->SetLoadGroup(loadGroup); // Construct a listener that merely discards any response. If successful at // opening the channel, then it is not necessary to hold a reference to the // channel. The networking subsystem will take care of that for us. nsCOMPtr listener = new nsPingListener(info->requireSameHost, content); if (!listener) return; // Observe redirects as well: nsCOMPtr callbacks = do_QueryInterface(listener); NS_ASSERTION(callbacks, "oops"); loadGroup->SetNotificationCallbacks(callbacks); chan->AsyncOpen(listener, nsnull); // Even if AsyncOpen failed, we still count this as a successful ping. It's // possible that AsyncOpen may have failed after triggering some background // process that may have written something to the network. info->numPings++; // Prevent ping requests from stalling and never being garbage collected... nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID); if (timer) { nsresult rv = timer->InitWithFuncCallback(OnPingTimeout, loadGroup, PING_TIMEOUT, nsITimer::TYPE_ONE_SHOT); if (NS_SUCCEEDED(rv)) { // When the timer expires, the callback function will release this // reference to the loadgroup. static_cast(loadGroup.get())->AddRef(); loadGroup = 0; } } // If we failed to setup the timer, then we should just cancel the channel // because we won't be able to ensure that it goes away in a timely manner. if (loadGroup) chan->Cancel(NS_ERROR_ABORT); } // Spec: http://whatwg.org/specs/web-apps/current-work/#ping static void DispatchPings(nsIContent *content, nsIURI *referrer) { SendPingInfo info; if (!PingsEnabled(&info.maxPings, &info.requireSameHost)) return; if (info.maxPings == 0) return; info.numPings = 0; info.referrer = referrer; ForEachPing(content, SendPing, &info); } static nsDOMPerformanceNavigationType ConvertLoadTypeToNavigationType(PRUint32 aLoadType) { // Not initialized, assume it's normal load. if (aLoadType == 0) { aLoadType = LOAD_NORMAL; } nsDOMPerformanceNavigationType result = nsIDOMPerformanceNavigation::TYPE_RESERVED; switch (aLoadType) { case LOAD_NORMAL: case LOAD_NORMAL_EXTERNAL: case LOAD_NORMAL_BYPASS_CACHE: case LOAD_NORMAL_BYPASS_PROXY: case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE: case LOAD_NORMAL_REPLACE: case LOAD_LINK: case LOAD_STOP_CONTENT: result = nsIDOMPerformanceNavigation::TYPE_NAVIGATE; break; case LOAD_HISTORY: result = nsIDOMPerformanceNavigation::TYPE_BACK_FORWARD; break; case LOAD_RELOAD_NORMAL: case LOAD_RELOAD_CHARSET_CHANGE: case LOAD_RELOAD_BYPASS_CACHE: case LOAD_RELOAD_BYPASS_PROXY: case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: result = nsIDOMPerformanceNavigation::TYPE_RELOAD; break; case LOAD_STOP_CONTENT_AND_REPLACE: case LOAD_REFRESH: case LOAD_BYPASS_HISTORY: case LOAD_ERROR_PAGE: case LOAD_PUSHSTATE: result = nsIDOMPerformanceNavigation::TYPE_RESERVED; break; default: // NS_NOTREACHED("Unexpected load type value"); result = nsIDOMPerformanceNavigation::TYPE_RESERVED; break; } return result; } static nsISHEntry* GetRootSHEntry(nsISHEntry *entry); //***************************************************************************** //*** nsDocShell: Object Management //***************************************************************************** static PRUint64 gDocshellIDCounter = 0; // Note: operator new zeros our memory nsDocShell::nsDocShell(): nsDocLoader(), mDefaultScrollbarPref(Scrollbar_Auto, Scrollbar_Auto), mTreeOwner(nsnull), mChromeEventHandler(nsnull), mCharsetReloadState(eCharsetReloadInit), mChildOffset(0), mBusyFlags(BUSY_FLAGS_NONE), mAppType(nsIDocShell::APP_TYPE_UNKNOWN), mLoadType(0), mMarginWidth(-1), mMarginHeight(-1), mItemType(typeContent), mPreviousTransIndex(-1), mLoadedTransIndex(-1), mCreated(false), mAllowSubframes(true), mAllowPlugins(true), mAllowJavascript(true), mAllowMetaRedirects(true), mAllowImages(true), mAllowDNSPrefetch(true), mAllowWindowControl(true), mCreatingDocument(false), mUseErrorPages(false), mObserveErrorPages(true), mAllowAuth(true), mAllowKeywordFixup(false), mIsOffScreenBrowser(false), mIsActive(true), mIsAppTab(false), mUseGlobalHistory(false), mInPrivateBrowsing(false), mFiredUnloadEvent(false), mEODForCurrentDocument(false), mURIResultedInDocument(false), mIsBeingDestroyed(false), mIsExecutingOnLoadHandler(false), mIsPrintingOrPP(false), mSavingOldViewer(false), #ifdef DEBUG mInEnsureScriptEnv(false), #endif mParentCharsetSource(0) { mHistoryID = ++gDocshellIDCounter; if (gDocShellCount++ == 0) { NS_ASSERTION(sURIFixup == nsnull, "Huh, sURIFixup not null in first nsDocShell ctor!"); CallGetService(NS_URIFIXUP_CONTRACTID, &sURIFixup); } #ifdef PR_LOGGING #ifdef DEBUG if (! gDocShellLog) gDocShellLog = PR_NewLogModule("nsDocShell"); #endif if (nsnull == gDocShellLeakLog) gDocShellLeakLog = PR_NewLogModule("nsDocShellLeak"); if (gDocShellLeakLog) PR_LOG(gDocShellLeakLog, PR_LOG_DEBUG, ("DOCSHELL %p created\n", this)); #endif #ifdef DEBUG // We're counting the number of |nsDocShells| to help find leaks ++gNumberOfDocShells; if (!PR_GetEnv("MOZ_QUIET")) { printf("++DOCSHELL %p == %ld\n", (void*) this, gNumberOfDocShells); } #endif } nsDocShell::~nsDocShell() { Destroy(); nsCOMPtr shPrivate(do_QueryInterface(mSessionHistory)); if (shPrivate) { shPrivate->SetRootDocShell(nsnull); } if (--gDocShellCount == 0) { NS_IF_RELEASE(sURIFixup); } #ifdef PR_LOGGING if (gDocShellLeakLog) PR_LOG(gDocShellLeakLog, PR_LOG_DEBUG, ("DOCSHELL %p destroyed\n", this)); #endif #ifdef DEBUG // We're counting the number of |nsDocShells| to help find leaks --gNumberOfDocShells; if (!PR_GetEnv("MOZ_QUIET")) { printf("--DOCSHELL %p == %ld\n", (void*) this, gNumberOfDocShells); } #endif } nsresult nsDocShell::Init() { nsresult rv = nsDocLoader::Init(); NS_ENSURE_SUCCESS(rv, rv); NS_ASSERTION(mLoadGroup, "Something went wrong!"); mContentListener = new nsDSURIContentListener(this); NS_ENSURE_TRUE(mContentListener, NS_ERROR_OUT_OF_MEMORY); rv = mContentListener->Init(); NS_ENSURE_SUCCESS(rv, rv); if (!mStorages.Init()) return NS_ERROR_OUT_OF_MEMORY; // We want to hold a strong ref to the loadgroup, so it better hold a weak // ref to us... use an InterfaceRequestorProxy to do this. nsCOMPtr proxy = new InterfaceRequestorProxy(static_cast (this)); NS_ENSURE_TRUE(proxy, NS_ERROR_OUT_OF_MEMORY); mLoadGroup->SetNotificationCallbacks(proxy); rv = nsDocLoader::AddDocLoaderAsChildOfRoot(this); NS_ENSURE_SUCCESS(rv, rv); // Add as |this| a progress listener to itself. A little weird, but // simpler than reproducing all the listener-notification logic in // overrides of the various methods via which nsDocLoader can be // notified. Note that this holds an nsWeakPtr to ourselves, so it's ok. return AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT | nsIWebProgress::NOTIFY_STATE_NETWORK); } void nsDocShell::DestroyChildren() { nsCOMPtr shell; PRInt32 n = mChildList.Count(); for (PRInt32 i = 0; i < n; i++) { shell = do_QueryInterface(ChildAt(i)); NS_ASSERTION(shell, "docshell has null child"); if (shell) { shell->SetTreeOwner(nsnull); } } nsDocLoader::DestroyChildren(); } //***************************************************************************** // nsDocShell::nsISupports //***************************************************************************** NS_IMPL_ADDREF_INHERITED(nsDocShell, nsDocLoader) NS_IMPL_RELEASE_INHERITED(nsDocShell, nsDocLoader) NS_INTERFACE_MAP_BEGIN(nsDocShell) NS_INTERFACE_MAP_ENTRY(nsIDocShell) NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem) NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeNode) NS_INTERFACE_MAP_ENTRY(nsIDocShellHistory) NS_INTERFACE_MAP_ENTRY(nsIWebNavigation) NS_INTERFACE_MAP_ENTRY(nsIBaseWindow) NS_INTERFACE_MAP_ENTRY(nsIScrollable) NS_INTERFACE_MAP_ENTRY(nsITextScroll) NS_INTERFACE_MAP_ENTRY(nsIDocCharset) NS_INTERFACE_MAP_ENTRY(nsIScriptGlobalObjectOwner) NS_INTERFACE_MAP_ENTRY(nsIRefreshURI) NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY(nsIContentViewerContainer) NS_INTERFACE_MAP_ENTRY(nsIEditorDocShell) NS_INTERFACE_MAP_ENTRY(nsIWebPageDescriptor) NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_ENTRY(nsILoadContext) NS_INTERFACE_MAP_ENTRY(nsIWebShellServices) NS_INTERFACE_MAP_ENTRY(nsILinkHandler) NS_INTERFACE_MAP_ENTRY(nsIClipboardCommands) NS_INTERFACE_MAP_END_INHERITING(nsDocLoader) ///***************************************************************************** // nsDocShell::nsIInterfaceRequestor //***************************************************************************** NS_IMETHODIMP nsDocShell::GetInterface(const nsIID & aIID, void **aSink) { NS_PRECONDITION(aSink, "null out param"); *aSink = nsnull; if (aIID.Equals(NS_GET_IID(nsICommandManager))) { NS_ENSURE_SUCCESS(EnsureCommandHandler(), NS_ERROR_FAILURE); *aSink = mCommandManager; } else if (aIID.Equals(NS_GET_IID(nsIURIContentListener))) { *aSink = mContentListener; } else if (aIID.Equals(NS_GET_IID(nsIScriptGlobalObject)) && NS_SUCCEEDED(EnsureScriptEnvironment())) { *aSink = mScriptGlobal; } else if ((aIID.Equals(NS_GET_IID(nsPIDOMWindow)) || aIID.Equals(NS_GET_IID(nsIDOMWindow)) || aIID.Equals(NS_GET_IID(nsIDOMWindowInternal))) && NS_SUCCEEDED(EnsureScriptEnvironment())) { return mScriptGlobal->QueryInterface(aIID, aSink); } else if (aIID.Equals(NS_GET_IID(nsIDOMDocument)) && NS_SUCCEEDED(EnsureContentViewer())) { mContentViewer->GetDOMDocument((nsIDOMDocument **) aSink); return *aSink ? NS_OK : NS_NOINTERFACE; } else if (aIID.Equals(NS_GET_IID(nsIDocument)) && NS_SUCCEEDED(EnsureContentViewer())) { nsCOMPtr domDoc; mContentViewer->GetDOMDocument(getter_AddRefs(domDoc)); if (!domDoc) return NS_NOINTERFACE; return domDoc->QueryInterface(aIID, aSink); } else if (aIID.Equals(NS_GET_IID(nsIApplicationCacheContainer))) { *aSink = nsnull; // Return application cache associated with this docshell, if any nsCOMPtr contentViewer; GetContentViewer(getter_AddRefs(contentViewer)); if (!contentViewer) return NS_ERROR_NO_INTERFACE; nsCOMPtr domDoc; contentViewer->GetDOMDocument(getter_AddRefs(domDoc)); NS_ASSERTION(domDoc, "Should have a document."); if (!domDoc) return NS_ERROR_NO_INTERFACE; #if defined(PR_LOGGING) && defined(DEBUG) PR_LOG(gDocShellLog, PR_LOG_DEBUG, ("nsDocShell[%p]: returning app cache container %p", this, domDoc.get())); #endif return domDoc->QueryInterface(aIID, aSink); } else if (aIID.Equals(NS_GET_IID(nsIPrompt)) && NS_SUCCEEDED(EnsureScriptEnvironment())) { nsresult rv; nsCOMPtr wwatch = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr window(do_QueryInterface(mScriptGlobal)); // Get the an auth prompter for our window so that the parenting // of the dialogs works as it should when using tabs. nsIPrompt *prompt; rv = wwatch->GetNewPrompter(window, &prompt); NS_ENSURE_SUCCESS(rv, rv); *aSink = prompt; return NS_OK; } else if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) || aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { return NS_SUCCEEDED( GetAuthPrompt(PROMPT_NORMAL, aIID, aSink)) ? NS_OK : NS_NOINTERFACE; } else if (aIID.Equals(NS_GET_IID(nsISHistory))) { nsCOMPtr shistory; nsresult rv = GetSessionHistory(getter_AddRefs(shistory)); if (NS_SUCCEEDED(rv) && shistory) { *aSink = shistory; NS_ADDREF((nsISupports *) * aSink); return NS_OK; } return NS_NOINTERFACE; } else if (aIID.Equals(NS_GET_IID(nsIWebBrowserFind))) { nsresult rv = EnsureFind(); if (NS_FAILED(rv)) return rv; *aSink = mFind; NS_ADDREF((nsISupports*)*aSink); return NS_OK; } else if (aIID.Equals(NS_GET_IID(nsIEditingSession)) && NS_SUCCEEDED(EnsureEditorData())) { nsCOMPtr editingSession; mEditorData->GetEditingSession(getter_AddRefs(editingSession)); if (editingSession) { *aSink = editingSession; NS_ADDREF((nsISupports *)*aSink); return NS_OK; } return NS_NOINTERFACE; } else if (aIID.Equals(NS_GET_IID(nsIClipboardDragDropHookList)) && NS_SUCCEEDED(EnsureTransferableHookData())) { *aSink = mTransferableHookData; NS_ADDREF((nsISupports *)*aSink); return NS_OK; } else if (aIID.Equals(NS_GET_IID(nsISelectionDisplay))) { nsCOMPtr shell; nsresult rv = GetPresShell(getter_AddRefs(shell)); if (NS_SUCCEEDED(rv) && shell) return shell->QueryInterface(aIID,aSink); } else if (aIID.Equals(NS_GET_IID(nsIDocShellTreeOwner))) { nsCOMPtr treeOwner; nsresult rv = GetTreeOwner(getter_AddRefs(treeOwner)); if (NS_SUCCEEDED(rv) && treeOwner) return treeOwner->QueryInterface(aIID, aSink); } else if (aIID.Equals(NS_GET_IID(nsITabChild))) { nsCOMPtr treeOwner; nsresult rv = GetTreeOwner(getter_AddRefs(treeOwner)); if (NS_SUCCEEDED(rv) && treeOwner) { nsCOMPtr ir = do_QueryInterface(treeOwner); if (ir) return ir->GetInterface(aIID, aSink); } } else { return nsDocLoader::GetInterface(aIID, aSink); } NS_IF_ADDREF(((nsISupports *) * aSink)); return *aSink ? NS_OK : NS_NOINTERFACE; } PRUint32 nsDocShell:: ConvertDocShellLoadInfoToLoadType(nsDocShellInfoLoadType aDocShellLoadType) { PRUint32 loadType = LOAD_NORMAL; switch (aDocShellLoadType) { case nsIDocShellLoadInfo::loadNormal: loadType = LOAD_NORMAL; break; case nsIDocShellLoadInfo::loadNormalReplace: loadType = LOAD_NORMAL_REPLACE; break; case nsIDocShellLoadInfo::loadNormalExternal: loadType = LOAD_NORMAL_EXTERNAL; break; case nsIDocShellLoadInfo::loadHistory: loadType = LOAD_HISTORY; break; case nsIDocShellLoadInfo::loadNormalBypassCache: loadType = LOAD_NORMAL_BYPASS_CACHE; break; case nsIDocShellLoadInfo::loadNormalBypassProxy: loadType = LOAD_NORMAL_BYPASS_PROXY; break; case nsIDocShellLoadInfo::loadNormalBypassProxyAndCache: loadType = LOAD_NORMAL_BYPASS_PROXY_AND_CACHE; break; case nsIDocShellLoadInfo::loadReloadNormal: loadType = LOAD_RELOAD_NORMAL; break; case nsIDocShellLoadInfo::loadReloadCharsetChange: loadType = LOAD_RELOAD_CHARSET_CHANGE; break; case nsIDocShellLoadInfo::loadReloadBypassCache: loadType = LOAD_RELOAD_BYPASS_CACHE; break; case nsIDocShellLoadInfo::loadReloadBypassProxy: loadType = LOAD_RELOAD_BYPASS_PROXY; break; case nsIDocShellLoadInfo::loadReloadBypassProxyAndCache: loadType = LOAD_RELOAD_BYPASS_PROXY_AND_CACHE; break; case nsIDocShellLoadInfo::loadLink: loadType = LOAD_LINK; break; case nsIDocShellLoadInfo::loadRefresh: loadType = LOAD_REFRESH; break; case nsIDocShellLoadInfo::loadBypassHistory: loadType = LOAD_BYPASS_HISTORY; break; case nsIDocShellLoadInfo::loadStopContent: loadType = LOAD_STOP_CONTENT; break; case nsIDocShellLoadInfo::loadStopContentAndReplace: loadType = LOAD_STOP_CONTENT_AND_REPLACE; break; case nsIDocShellLoadInfo::loadPushState: loadType = LOAD_PUSHSTATE; break; default: NS_NOTREACHED("Unexpected nsDocShellInfoLoadType value"); } return loadType; } nsDocShellInfoLoadType nsDocShell::ConvertLoadTypeToDocShellLoadInfo(PRUint32 aLoadType) { nsDocShellInfoLoadType docShellLoadType = nsIDocShellLoadInfo::loadNormal; switch (aLoadType) { case LOAD_NORMAL: docShellLoadType = nsIDocShellLoadInfo::loadNormal; break; case LOAD_NORMAL_REPLACE: docShellLoadType = nsIDocShellLoadInfo::loadNormalReplace; break; case LOAD_NORMAL_EXTERNAL: docShellLoadType = nsIDocShellLoadInfo::loadNormalExternal; break; case LOAD_NORMAL_BYPASS_CACHE: docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassCache; break; case LOAD_NORMAL_BYPASS_PROXY: docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassProxy; break; case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE: docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassProxyAndCache; break; case LOAD_HISTORY: docShellLoadType = nsIDocShellLoadInfo::loadHistory; break; case LOAD_RELOAD_NORMAL: docShellLoadType = nsIDocShellLoadInfo::loadReloadNormal; break; case LOAD_RELOAD_CHARSET_CHANGE: docShellLoadType = nsIDocShellLoadInfo::loadReloadCharsetChange; break; case LOAD_RELOAD_BYPASS_CACHE: docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassCache; break; case LOAD_RELOAD_BYPASS_PROXY: docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassProxy; break; case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassProxyAndCache; break; case LOAD_LINK: docShellLoadType = nsIDocShellLoadInfo::loadLink; break; case LOAD_REFRESH: docShellLoadType = nsIDocShellLoadInfo::loadRefresh; break; case LOAD_BYPASS_HISTORY: case LOAD_ERROR_PAGE: docShellLoadType = nsIDocShellLoadInfo::loadBypassHistory; break; case LOAD_STOP_CONTENT: docShellLoadType = nsIDocShellLoadInfo::loadStopContent; break; case LOAD_STOP_CONTENT_AND_REPLACE: docShellLoadType = nsIDocShellLoadInfo::loadStopContentAndReplace; break; case LOAD_PUSHSTATE: docShellLoadType = nsIDocShellLoadInfo::loadPushState; break; default: NS_NOTREACHED("Unexpected load type value"); } return docShellLoadType; } //***************************************************************************** // nsDocShell::nsIDocShell //***************************************************************************** NS_IMETHODIMP nsDocShell::LoadURI(nsIURI * aURI, nsIDocShellLoadInfo * aLoadInfo, PRUint32 aLoadFlags, bool aFirstParty) { NS_PRECONDITION(aLoadInfo || (aLoadFlags & EXTRA_LOAD_FLAGS) == 0, "Unexpected flags"); NS_PRECONDITION((aLoadFlags & 0xf) == 0, "Should not have these flags set"); // Note: we allow loads to get through here even if mFiredUnloadEvent is // true; that case will get handled in LoadInternal or LoadHistoryEntry. if (IsPrintingOrPP()) { return NS_OK; // JS may not handle returning of an error code } nsresult rv; nsCOMPtr referrer; nsCOMPtr postStream; nsCOMPtr headersStream; nsCOMPtr owner; bool inheritOwner = false; bool ownerIsExplicit = false; bool sendReferrer = true; nsCOMPtr shEntry; nsXPIDLString target; PRUint32 loadType = MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags); NS_ENSURE_ARG(aURI); if (!StartupTimeline::HasRecord(StartupTimeline::FIRST_LOAD_URI) && mItemType == typeContent && !NS_IsAboutBlank(aURI)) { StartupTimeline::RecordOnce(StartupTimeline::FIRST_LOAD_URI); } // Extract the info from the DocShellLoadInfo struct... if (aLoadInfo) { aLoadInfo->GetReferrer(getter_AddRefs(referrer)); nsDocShellInfoLoadType lt = nsIDocShellLoadInfo::loadNormal; aLoadInfo->GetLoadType(<); // Get the appropriate loadType from nsIDocShellLoadInfo type loadType = ConvertDocShellLoadInfoToLoadType(lt); aLoadInfo->GetOwner(getter_AddRefs(owner)); aLoadInfo->GetInheritOwner(&inheritOwner); aLoadInfo->GetOwnerIsExplicit(&ownerIsExplicit); aLoadInfo->GetSHEntry(getter_AddRefs(shEntry)); aLoadInfo->GetTarget(getter_Copies(target)); aLoadInfo->GetPostDataStream(getter_AddRefs(postStream)); aLoadInfo->GetHeadersStream(getter_AddRefs(headersStream)); aLoadInfo->GetSendReferrer(&sendReferrer); } #if defined(PR_LOGGING) && defined(DEBUG) if (PR_LOG_TEST(gDocShellLog, PR_LOG_DEBUG)) { nsCAutoString uristr; aURI->GetAsciiSpec(uristr); PR_LOG(gDocShellLog, PR_LOG_DEBUG, ("nsDocShell[%p]: loading %s with flags 0x%08x", this, uristr.get(), aLoadFlags)); } #endif if (!shEntry && !LOAD_TYPE_HAS_FLAGS(loadType, LOAD_FLAGS_REPLACE_HISTORY)) { // First verify if this is a subframe. nsCOMPtr parentAsItem; GetSameTypeParent(getter_AddRefs(parentAsItem)); nsCOMPtr parentDS(do_QueryInterface(parentAsItem)); PRUint32 parentLoadType; if (parentDS && parentDS != static_cast(this)) { /* OK. It is a subframe. Checkout the * parent's loadtype. If the parent was loaded thro' a history * mechanism, then get the SH entry for the child from the parent. * This is done to restore frameset navigation while going back/forward. * If the parent was loaded through any other loadType, set the * child's loadType too accordingly, so that session history does not * get confused. */ // Get the parent's load type parentDS->GetLoadType(&parentLoadType); nsCOMPtr parent(do_QueryInterface(parentAsItem)); if (parent) { // Get the ShEntry for the child from the parent nsCOMPtr currentSH; bool oshe = false; parent->GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe); bool dynamicallyAddedChild = mDynamicallyCreated; if (!dynamicallyAddedChild && !oshe && currentSH) { currentSH->HasDynamicallyAddedChild(&dynamicallyAddedChild); } if (!dynamicallyAddedChild) { // Only use the old SHEntry, if we're sure enough that // it wasn't originally for some other frame. parent->GetChildSHEntry(mChildOffset, getter_AddRefs(shEntry)); } // Make some decisions on the child frame's loadType based on the // parent's loadType. if (mCurrentURI == nsnull) { // This is a newly created frame. Check for exception cases first. // By default the subframe will inherit the parent's loadType. if (shEntry && (parentLoadType == LOAD_NORMAL || parentLoadType == LOAD_LINK || parentLoadType == LOAD_NORMAL_EXTERNAL)) { // The parent was loaded normally. In this case, this *brand new* child really shouldn't // have a SHEntry. If it does, it could be because the parent is replacing an // existing frame with a new frame, in the onLoadHandler. We don't want this // url to get into session history. Clear off shEntry, and set load type to // LOAD_BYPASS_HISTORY. bool inOnLoadHandler=false; parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler); if (inOnLoadHandler) { loadType = LOAD_NORMAL_REPLACE; shEntry = nsnull; } } else if (parentLoadType == LOAD_REFRESH) { // Clear shEntry. For refresh loads, we have to load // what comes thro' the pipe, not what's in history. shEntry = nsnull; } else if ((parentLoadType == LOAD_BYPASS_HISTORY) || (parentLoadType == LOAD_ERROR_PAGE) || (shEntry && ((parentLoadType & LOAD_CMD_HISTORY) || (parentLoadType == LOAD_RELOAD_NORMAL) || (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE)))) { // If the parent url, bypassed history or was loaded from // history, pass on the parent's loadType to the new child // frame too, so that the child frame will also // avoid getting into history. loadType = parentLoadType; } } else { // This is a pre-existing subframe. If the load was not originally initiated // by session history, (if (!shEntry) condition succeeded) and mCurrentURI is not null, // it is possible that a parent's onLoadHandler or even self's onLoadHandler is loading // a new page in this child. Check parent's and self's busy flag and if it is set, // we don't want this onLoadHandler load to get in to session history. PRUint32 parentBusy = BUSY_FLAGS_NONE; PRUint32 selfBusy = BUSY_FLAGS_NONE; parentDS->GetBusyFlags(&parentBusy); GetBusyFlags(&selfBusy); if (parentBusy & BUSY_FLAGS_BUSY || selfBusy & BUSY_FLAGS_BUSY) { loadType = LOAD_NORMAL_REPLACE; shEntry = nsnull; } } } // parent } //parentDS else { // This is the root docshell. If we got here while // executing an onLoad Handler,this load will not go // into session history. bool inOnLoadHandler=false; GetIsExecutingOnLoadHandler(&inOnLoadHandler); if (inOnLoadHandler) { loadType = LOAD_NORMAL_REPLACE; } } } // !shEntry if (shEntry) { #ifdef DEBUG PR_LOG(gDocShellLog, PR_LOG_DEBUG, ("nsDocShell[%p]: loading from session history", this)); #endif return LoadHistoryEntry(shEntry, loadType); } // Perform the load... // We need an owner (a referring principal). // // If ownerIsExplicit is not set there are 4 possibilities: // (1) If the system principal was passed in and we're a typeContent // docshell, inherit the principal from the current document // instead. // (2) In all other cases when the principal passed in is not null, // use that principal. // (3) If the caller has allowed inheriting from the current document, // or if we're being called from system code (eg chrome JS or pure // C++) then inheritOwner should be true and InternalLoad will get // an owner from the current document. If none of these things are // true, then // (4) we pass a null owner into the channel, and an owner will be // created later from the channel's internal data. // // If ownerIsExplicit *is* set, there are 4 possibilities // (1) If the system principal was passed in and we're a typeContent // docshell, return an error. // (2) In all other cases when the principal passed in is not null, // use that principal. // (3) If the caller has allowed inheriting from the current document, // then inheritOwner should be true and InternalLoad will get an owner // from the current document. If none of these things are true, then // (4) we pass a null owner into the channel, and an owner will be // created later from the channel's internal data. // // NOTE: This all only works because the only thing the owner is used // for in InternalLoad is data:, javascript:, and about:blank // URIs. For other URIs this would all be dead wrong! nsCOMPtr secMan = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); if (owner && mItemType != typeChrome) { nsCOMPtr ownerPrincipal = do_QueryInterface(owner); bool isSystem; rv = secMan->IsSystemPrincipal(ownerPrincipal, &isSystem); NS_ENSURE_SUCCESS(rv, rv); if (isSystem) { if (ownerIsExplicit) { return NS_ERROR_DOM_SECURITY_ERR; } owner = nsnull; inheritOwner = true; } } if (!owner && !inheritOwner && !ownerIsExplicit) { // See if there's system or chrome JS code running rv = secMan->SubjectPrincipalIsSystem(&inheritOwner); if (NS_FAILED(rv)) { // Set it back to false inheritOwner = false; } } if (aLoadFlags & LOAD_FLAGS_DISALLOW_INHERIT_OWNER) { inheritOwner = false; owner = do_CreateInstance("@mozilla.org/nullprincipal;1"); } PRUint32 flags = 0; if (inheritOwner) flags |= INTERNAL_LOAD_FLAGS_INHERIT_OWNER; if (!sendReferrer) flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER; if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) flags |= INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; if (aLoadFlags & LOAD_FLAGS_FIRST_LOAD) flags |= INTERNAL_LOAD_FLAGS_FIRST_LOAD; if (aLoadFlags & LOAD_FLAGS_BYPASS_CLASSIFIER) flags |= INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER; if (aLoadFlags & LOAD_FLAGS_FORCE_ALLOW_COOKIES) flags |= INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES; return InternalLoad(aURI, referrer, owner, flags, target.get(), nsnull, // No type hint postStream, headersStream, loadType, nsnull, // No SHEntry aFirstParty, nsnull, // No nsIDocShell nsnull); // No nsIRequest } NS_IMETHODIMP nsDocShell::LoadStream(nsIInputStream *aStream, nsIURI * aURI, const nsACString &aContentType, const nsACString &aContentCharset, nsIDocShellLoadInfo * aLoadInfo) { NS_ENSURE_ARG(aStream); mAllowKeywordFixup = false; // if the caller doesn't pass in a URI we need to create a dummy URI. necko // currently requires a URI in various places during the load. Some consumers // do as well. nsCOMPtr uri = aURI; if (!uri) { // HACK ALERT nsresult rv = NS_OK; uri = do_CreateInstance(NS_SIMPLEURI_CONTRACTID, &rv); if (NS_FAILED(rv)) return rv; // Make sure that the URI spec "looks" like a protocol and path... // For now, just use a bogus protocol called "internal" rv = uri->SetSpec(NS_LITERAL_CSTRING("internal:load-stream")); if (NS_FAILED(rv)) return rv; } PRUint32 loadType = LOAD_NORMAL; if (aLoadInfo) { nsDocShellInfoLoadType lt = nsIDocShellLoadInfo::loadNormal; (void) aLoadInfo->GetLoadType(<); // Get the appropriate LoadType from nsIDocShellLoadInfo type loadType = ConvertDocShellLoadInfoToLoadType(lt); } NS_ENSURE_SUCCESS(Stop(nsIWebNavigation::STOP_NETWORK), NS_ERROR_FAILURE); mLoadType = loadType; // build up a channel for this stream. nsCOMPtr channel; NS_ENSURE_SUCCESS(NS_NewInputStreamChannel (getter_AddRefs(channel), uri, aStream, aContentType, aContentCharset), NS_ERROR_FAILURE); nsCOMPtr uriLoader(do_GetService(NS_URI_LOADER_CONTRACTID)); NS_ENSURE_TRUE(uriLoader, NS_ERROR_FAILURE); NS_ENSURE_SUCCESS(DoChannelLoad(channel, uriLoader, false), NS_ERROR_FAILURE); return NS_OK; } NS_IMETHODIMP nsDocShell::CreateLoadInfo(nsIDocShellLoadInfo ** aLoadInfo) { nsDocShellLoadInfo *loadInfo = new nsDocShellLoadInfo(); NS_ENSURE_TRUE(loadInfo, NS_ERROR_OUT_OF_MEMORY); nsCOMPtr localRef(loadInfo); *aLoadInfo = localRef; NS_ADDREF(*aLoadInfo); return NS_OK; } /* * Reset state to a new content model within the current document and the document * viewer. Called by the document before initiating an out of band document.write(). */ NS_IMETHODIMP nsDocShell::PrepareForNewContentModel() { mEODForCurrentDocument = false; return NS_OK; } NS_IMETHODIMP nsDocShell::FirePageHideNotification(bool aIsUnload) { if (mContentViewer && !mFiredUnloadEvent) { // Keep an explicit reference since calling PageHide could release // mContentViewer nsCOMPtr kungFuDeathGrip(mContentViewer); mFiredUnloadEvent = true; if (mTiming) { mTiming->NotifyUnloadEventStart(); } mContentViewer->PageHide(aIsUnload); if (mTiming) { mTiming->NotifyUnloadEventEnd(); } nsAutoTArray, 8> kids; PRInt32 i, n = mChildList.Count(); kids.SetCapacity(n); for (i = 0; i < n; i++) { kids.AppendElement(do_QueryInterface(ChildAt(i))); } n = kids.Length(); for (i = 0; i < n; ++i) { if (kids[i]) { kids[i]->FirePageHideNotification(aIsUnload); } } // Now make sure our editor, if any, is detached before we go // any farther. DetachEditorFromWindow(); } return NS_OK; } nsresult nsDocShell::MaybeInitTiming() { if (mTiming) { return NS_OK; } if (Preferences::GetBool("dom.enable_performance", false)) { mTiming = new nsDOMNavigationTiming(); mTiming->NotifyNavigationStart(); } return NS_OK; } // // Bug 13871: Prevent frameset spoofing // // This routine answers: 'Is origin's document from same domain as // target's document?' // // file: uris are considered the same domain for the purpose of // frame navigation regardless of script accessibility (bug 420425) // /* static */ bool nsDocShell::ValidateOrigin(nsIDocShellTreeItem* aOriginTreeItem, nsIDocShellTreeItem* aTargetTreeItem) { nsCOMPtr securityManager = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); NS_ENSURE_TRUE(securityManager, false); nsCOMPtr subjectPrincipal; nsresult rv = securityManager->GetSubjectPrincipal(getter_AddRefs(subjectPrincipal)); NS_ENSURE_SUCCESS(rv, false); if (subjectPrincipal) { // We're called from JS, check if UniversalXPConnect is // enabled. bool ubwEnabled = false; rv = securityManager->IsCapabilityEnabled("UniversalXPConnect", &ubwEnabled); NS_ENSURE_SUCCESS(rv, false); if (ubwEnabled) { return true; } } // Get origin document principal nsCOMPtr originDocument(do_GetInterface(aOriginTreeItem)); NS_ENSURE_TRUE(originDocument, false); // Get target principal nsCOMPtr targetDocument(do_GetInterface(aTargetTreeItem)); NS_ENSURE_TRUE(targetDocument, false); bool equal; rv = originDocument->NodePrincipal()-> Equals(targetDocument->NodePrincipal(), &equal); if (NS_SUCCEEDED(rv) && equal) { return true; } // Not strictly equal, special case if both are file: uris bool originIsFile = false; bool targetIsFile = false; nsCOMPtr originURI; nsCOMPtr targetURI; nsCOMPtr innerOriginURI; nsCOMPtr innerTargetURI; rv = originDocument->NodePrincipal()->GetURI(getter_AddRefs(originURI)); if (NS_SUCCEEDED(rv) && originURI) innerOriginURI = NS_GetInnermostURI(originURI); rv = targetDocument->NodePrincipal()->GetURI(getter_AddRefs(targetURI)); if (NS_SUCCEEDED(rv) && targetURI) innerTargetURI = NS_GetInnermostURI(targetURI); return innerOriginURI && innerTargetURI && NS_SUCCEEDED(innerOriginURI->SchemeIs("file", &originIsFile)) && NS_SUCCEEDED(innerTargetURI->SchemeIs("file", &targetIsFile)) && originIsFile && targetIsFile; } NS_IMETHODIMP nsDocShell::GetEldestPresContext(nsPresContext** aPresContext) { NS_ENSURE_ARG_POINTER(aPresContext); *aPresContext = nsnull; nsCOMPtr viewer = mContentViewer; while (viewer) { nsCOMPtr prevViewer; viewer->GetPreviousViewer(getter_AddRefs(prevViewer)); if (!prevViewer) { return viewer->GetPresContext(aPresContext); } viewer = prevViewer; } return NS_OK; } NS_IMETHODIMP nsDocShell::GetPresContext(nsPresContext ** aPresContext) { NS_ENSURE_ARG_POINTER(aPresContext); *aPresContext = nsnull; if (!mContentViewer) return NS_OK; return mContentViewer->GetPresContext(aPresContext); } NS_IMETHODIMP nsDocShell::GetPresShell(nsIPresShell ** aPresShell) { nsresult rv = NS_OK; NS_ENSURE_ARG_POINTER(aPresShell); *aPresShell = nsnull; nsRefPtr presContext; (void) GetPresContext(getter_AddRefs(presContext)); if (presContext) { NS_IF_ADDREF(*aPresShell = presContext->GetPresShell()); } return rv; } NS_IMETHODIMP nsDocShell::GetEldestPresShell(nsIPresShell** aPresShell) { nsresult rv = NS_OK; NS_ENSURE_ARG_POINTER(aPresShell); *aPresShell = nsnull; nsRefPtr presContext; (void) GetEldestPresContext(getter_AddRefs(presContext)); if (presContext) { NS_IF_ADDREF(*aPresShell = presContext->GetPresShell()); } return rv; } NS_IMETHODIMP nsDocShell::GetContentViewer(nsIContentViewer ** aContentViewer) { NS_ENSURE_ARG_POINTER(aContentViewer); *aContentViewer = mContentViewer; NS_IF_ADDREF(*aContentViewer); return NS_OK; } NS_IMETHODIMP nsDocShell::SetChromeEventHandler(nsIDOMEventTarget* aChromeEventHandler) { // Weak reference. Don't addref. mChromeEventHandler = aChromeEventHandler; nsCOMPtr win(do_QueryInterface(mScriptGlobal)); if (win) { win->SetChromeEventHandler(aChromeEventHandler); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetChromeEventHandler(nsIDOMEventTarget** aChromeEventHandler) { NS_ENSURE_ARG_POINTER(aChromeEventHandler); nsCOMPtr target = do_QueryInterface(mChromeEventHandler); target.swap(*aChromeEventHandler); return NS_OK; } /* void setCurrentURI (in nsIURI uri); */ NS_IMETHODIMP nsDocShell::SetCurrentURI(nsIURI *aURI) { // Note that securityUI will set STATE_IS_INSECURE, even if // the scheme of |aURI| is "https". SetCurrentURI(aURI, nsnull, true, 0); return NS_OK; } bool nsDocShell::SetCurrentURI(nsIURI *aURI, nsIRequest *aRequest, bool aFireOnLocationChange, PRUint32 aLocationFlags) { #ifdef PR_LOGGING if (gDocShellLeakLog && PR_LOG_TEST(gDocShellLeakLog, PR_LOG_DEBUG)) { nsCAutoString spec; if (aURI) aURI->GetSpec(spec); PR_LogPrint("DOCSHELL %p SetCurrentURI %s\n", this, spec.get()); } #endif // We don't want to send a location change when we're displaying an error // page, and we don't want to change our idea of "current URI" either if (mLoadType == LOAD_ERROR_PAGE) { return false; } mCurrentURI = NS_TryToMakeImmutable(aURI); bool isRoot = false; // Is this the root docshell bool isSubFrame = false; // Is this a subframe navigation? nsCOMPtr root; GetSameTypeRootTreeItem(getter_AddRefs(root)); if (root.get() == static_cast(this)) { // This is the root docshell isRoot = true; } if (mLSHE) { mLSHE->GetIsSubFrame(&isSubFrame); } if (!isSubFrame && !isRoot) { /* * We don't want to send OnLocationChange notifications when * a subframe is being loaded for the first time, while * visiting a frameset page */ return false; } if (aFireOnLocationChange) { FireOnLocationChange(this, aRequest, aURI, aLocationFlags); } return !aFireOnLocationChange; } NS_IMETHODIMP nsDocShell::GetCharset(char** aCharset) { NS_ENSURE_ARG_POINTER(aCharset); *aCharset = nsnull; nsCOMPtr presShell; GetPresShell(getter_AddRefs(presShell)); NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); nsIDocument *doc = presShell->GetDocument(); NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); *aCharset = ToNewCString(doc->GetDocumentCharacterSet()); if (!*aCharset) { return NS_ERROR_OUT_OF_MEMORY; } return NS_OK; } NS_IMETHODIMP nsDocShell::SetCharset(const char* aCharset) { // set the default charset nsCOMPtr viewer; GetContentViewer(getter_AddRefs(viewer)); if (viewer) { nsCOMPtr muDV(do_QueryInterface(viewer)); if (muDV) { nsCString charset(aCharset); NS_ENSURE_SUCCESS(muDV->SetDefaultCharacterSet(charset), NS_ERROR_FAILURE); } } // set the charset override nsCOMPtr csAtom = do_GetAtom(aCharset); SetForcedCharset(csAtom); return NS_OK; } NS_IMETHODIMP nsDocShell::SetForcedCharset(nsIAtom * aCharset) { mForcedCharset = aCharset; return NS_OK; } NS_IMETHODIMP nsDocShell::GetForcedCharset(nsIAtom ** aResult) { *aResult = mForcedCharset; if (mForcedCharset) NS_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP nsDocShell::SetParentCharset(nsIAtom * aCharset) { mParentCharset = aCharset; return NS_OK; } NS_IMETHODIMP nsDocShell::GetParentCharset(nsIAtom ** aResult) { *aResult = mParentCharset; if (mParentCharset) NS_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP nsDocShell::SetParentCharsetSource(PRInt32 aCharsetSource) { mParentCharsetSource = aCharsetSource; return NS_OK; } NS_IMETHODIMP nsDocShell::GetParentCharsetSource(PRInt32 * aParentCharsetSource) { *aParentCharsetSource = mParentCharsetSource; return NS_OK; } NS_IMETHODIMP nsDocShell::GetChannelIsUnsafe(bool *aUnsafe) { *aUnsafe = false; nsIChannel* channel = GetCurrentDocChannel(); if (!channel) { return NS_OK; } nsCOMPtr jarChannel = do_QueryInterface(channel); if (!jarChannel) { return NS_OK; } return jarChannel->GetIsUnsafe(aUnsafe); } NS_IMETHODIMP nsDocShell::GetAllowPlugins(bool * aAllowPlugins) { NS_ENSURE_ARG_POINTER(aAllowPlugins); *aAllowPlugins = mAllowPlugins; if (!mAllowPlugins) { return NS_OK; } bool unsafe; *aAllowPlugins = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowPlugins(bool aAllowPlugins) { mAllowPlugins = aAllowPlugins; //XXX should enable or disable a plugin host return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowJavascript(bool * aAllowJavascript) { NS_ENSURE_ARG_POINTER(aAllowJavascript); *aAllowJavascript = mAllowJavascript; if (!mAllowJavascript) { return NS_OK; } bool unsafe; *aAllowJavascript = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowJavascript(bool aAllowJavascript) { mAllowJavascript = aAllowJavascript; return NS_OK; } NS_IMETHODIMP nsDocShell::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) { NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing); *aUsePrivateBrowsing = mInPrivateBrowsing; return NS_OK; } NS_IMETHODIMP nsDocShell::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) { mInPrivateBrowsing = aUsePrivateBrowsing; PRInt32 count = mChildList.Count(); for (PRInt32 i = 0; i < count; ++i) { nsCOMPtr shell = do_QueryInterface(ChildAt(i)); if (shell) { shell->SetUsePrivateBrowsing(aUsePrivateBrowsing); } } return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowMetaRedirects(bool * aReturn) { NS_ENSURE_ARG_POINTER(aReturn); *aReturn = mAllowMetaRedirects; if (!mAllowMetaRedirects) { return NS_OK; } bool unsafe; *aReturn = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowMetaRedirects(bool aValue) { mAllowMetaRedirects = aValue; return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowSubframes(bool * aAllowSubframes) { NS_ENSURE_ARG_POINTER(aAllowSubframes); *aAllowSubframes = mAllowSubframes; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowSubframes(bool aAllowSubframes) { mAllowSubframes = aAllowSubframes; return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowImages(bool * aAllowImages) { NS_ENSURE_ARG_POINTER(aAllowImages); *aAllowImages = mAllowImages; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowImages(bool aAllowImages) { mAllowImages = aAllowImages; return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowDNSPrefetch(bool * aAllowDNSPrefetch) { *aAllowDNSPrefetch = mAllowDNSPrefetch; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowDNSPrefetch(bool aAllowDNSPrefetch) { mAllowDNSPrefetch = aAllowDNSPrefetch; return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowWindowControl(bool * aAllowWindowControl) { *aAllowWindowControl = mAllowWindowControl; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowWindowControl(bool aAllowWindowControl) { mAllowWindowControl = aAllowWindowControl; return NS_OK; } NS_IMETHODIMP nsDocShell::GetDocShellEnumerator(PRInt32 aItemType, PRInt32 aDirection, nsISimpleEnumerator **outEnum) { NS_ENSURE_ARG_POINTER(outEnum); *outEnum = nsnull; nsRefPtr docShellEnum; if (aDirection == ENUMERATE_FORWARDS) docShellEnum = new nsDocShellForwardsEnumerator; else docShellEnum = new nsDocShellBackwardsEnumerator; if (!docShellEnum) return NS_ERROR_OUT_OF_MEMORY; nsresult rv = docShellEnum->SetEnumDocShellType(aItemType); if (NS_FAILED(rv)) return rv; rv = docShellEnum->SetEnumerationRootItem((nsIDocShellTreeItem *)this); if (NS_FAILED(rv)) return rv; rv = docShellEnum->First(); if (NS_FAILED(rv)) return rv; rv = docShellEnum->QueryInterface(NS_GET_IID(nsISimpleEnumerator), (void **)outEnum); return rv; } NS_IMETHODIMP nsDocShell::GetAppType(PRUint32 * aAppType) { *aAppType = mAppType; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAppType(PRUint32 aAppType) { mAppType = aAppType; return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowAuth(bool * aAllowAuth) { *aAllowAuth = mAllowAuth; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowAuth(bool aAllowAuth) { mAllowAuth = aAllowAuth; return NS_OK; } NS_IMETHODIMP nsDocShell::GetZoom(float *zoom) { NS_ENSURE_ARG_POINTER(zoom); *zoom = 1.0f; return NS_OK; } NS_IMETHODIMP nsDocShell::SetZoom(float zoom) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsDocShell::GetMarginWidth(PRInt32 * aWidth) { NS_ENSURE_ARG_POINTER(aWidth); *aWidth = mMarginWidth; return NS_OK; } NS_IMETHODIMP nsDocShell::SetMarginWidth(PRInt32 aWidth) { mMarginWidth = aWidth; return NS_OK; } NS_IMETHODIMP nsDocShell::GetMarginHeight(PRInt32 * aHeight) { NS_ENSURE_ARG_POINTER(aHeight); *aHeight = mMarginHeight; return NS_OK; } NS_IMETHODIMP nsDocShell::SetMarginHeight(PRInt32 aHeight) { mMarginHeight = aHeight; return NS_OK; } NS_IMETHODIMP nsDocShell::GetBusyFlags(PRUint32 * aBusyFlags) { NS_ENSURE_ARG_POINTER(aBusyFlags); *aBusyFlags = mBusyFlags; return NS_OK; } NS_IMETHODIMP nsDocShell::TabToTreeOwner(bool aForward, bool* aTookFocus) { NS_ENSURE_ARG_POINTER(aTookFocus); nsCOMPtr chromeFocus = do_GetInterface(mTreeOwner); if (chromeFocus) { if (aForward) *aTookFocus = NS_SUCCEEDED(chromeFocus->FocusNextElement()); else *aTookFocus = NS_SUCCEEDED(chromeFocus->FocusPrevElement()); } else *aTookFocus = false; return NS_OK; } NS_IMETHODIMP nsDocShell::GetSecurityUI(nsISecureBrowserUI **aSecurityUI) { NS_IF_ADDREF(*aSecurityUI = mSecurityUI); return NS_OK; } NS_IMETHODIMP nsDocShell::SetSecurityUI(nsISecureBrowserUI *aSecurityUI) { mSecurityUI = aSecurityUI; return NS_OK; } NS_IMETHODIMP nsDocShell::GetUseErrorPages(bool *aUseErrorPages) { *aUseErrorPages = mUseErrorPages; return NS_OK; } NS_IMETHODIMP nsDocShell::SetUseErrorPages(bool aUseErrorPages) { // If mUseErrorPages is set explicitly, stop observing the pref. if (mObserveErrorPages) { Preferences::RemoveObserver(this, "browser.xul.error_pages.enabled"); mObserveErrorPages = false; } mUseErrorPages = aUseErrorPages; return NS_OK; } NS_IMETHODIMP nsDocShell::GetPreviousTransIndex(PRInt32 *aPreviousTransIndex) { *aPreviousTransIndex = mPreviousTransIndex; return NS_OK; } NS_IMETHODIMP nsDocShell::GetLoadedTransIndex(PRInt32 *aLoadedTransIndex) { *aLoadedTransIndex = mLoadedTransIndex; return NS_OK; } NS_IMETHODIMP nsDocShell::HistoryPurged(PRInt32 aNumEntries) { // These indices are used for fastback cache eviction, to determine // which session history entries are candidates for content viewer // eviction. We need to adjust by the number of entries that we // just purged from history, so that we look at the right session history // entries during eviction. mPreviousTransIndex = NS_MAX(-1, mPreviousTransIndex - aNumEntries); mLoadedTransIndex = NS_MAX(0, mLoadedTransIndex - aNumEntries); PRInt32 count = mChildList.Count(); for (PRInt32 i = 0; i < count; ++i) { nsCOMPtr shell = do_QueryInterface(ChildAt(i)); if (shell) { shell->HistoryPurged(aNumEntries); } } return NS_OK; } nsresult nsDocShell::HistoryTransactionRemoved(PRInt32 aIndex) { // These indices are used for fastback cache eviction, to determine // which session history entries are candidates for content viewer // eviction. We need to adjust by the number of entries that we // just purged from history, so that we look at the right session history // entries during eviction. if (aIndex == mPreviousTransIndex) { mPreviousTransIndex = -1; } else if (aIndex < mPreviousTransIndex) { --mPreviousTransIndex; } if (mLoadedTransIndex == aIndex) { mLoadedTransIndex = 0; } else if (aIndex < mLoadedTransIndex) { --mLoadedTransIndex; } PRInt32 count = mChildList.Count(); for (PRInt32 i = 0; i < count; ++i) { nsCOMPtr shell = do_QueryInterface(ChildAt(i)); if (shell) { static_cast(shell.get())-> HistoryTransactionRemoved(aIndex); } } return NS_OK; } NS_IMETHODIMP nsDocShell::GetSessionStorageForPrincipal(nsIPrincipal* aPrincipal, const nsAString& aDocumentURI, bool aCreate, nsIDOMStorage** aStorage) { NS_ENSURE_ARG_POINTER(aStorage); *aStorage = nsnull; if (!aPrincipal) return NS_OK; nsresult rv; nsCOMPtr topItem; rv = GetSameTypeRootTreeItem(getter_AddRefs(topItem)); if (NS_FAILED(rv)) return rv; if (!topItem) return NS_ERROR_FAILURE; nsDocShell* topDocShell = static_cast(topItem.get()); if (topDocShell != this) return topDocShell->GetSessionStorageForPrincipal(aPrincipal, aDocumentURI, aCreate, aStorage); nsXPIDLCString origin; rv = aPrincipal->GetOrigin(getter_Copies(origin)); if (NS_FAILED(rv)) return rv; if (origin.IsEmpty()) return NS_OK; if (!mStorages.Get(origin, aStorage) && aCreate) { nsCOMPtr newstorage = do_CreateInstance("@mozilla.org/dom/storage;2"); if (!newstorage) return NS_ERROR_OUT_OF_MEMORY; nsCOMPtr pistorage = do_QueryInterface(newstorage); if (!pistorage) return NS_ERROR_FAILURE; rv = pistorage->InitAsSessionStorage(aPrincipal, aDocumentURI); if (NS_FAILED(rv)) return rv; if (!mStorages.Put(origin, newstorage)) return NS_ERROR_OUT_OF_MEMORY; newstorage.swap(*aStorage); #if defined(PR_LOGGING) && defined(DEBUG) PR_LOG(gDocShellLog, PR_LOG_DEBUG, ("nsDocShell[%p]: created a new sessionStorage %p", this, *aStorage)); #endif } else if (*aStorage) { nsCOMPtr piStorage = do_QueryInterface(*aStorage); if (piStorage) { nsCOMPtr storagePrincipal = piStorage->Principal(); // The origin string used to map items in the hash table is // an implicit security check. That check is double-confirmed // by checking the principal a storage was demanded for // really is the principal for which that storage was originally // created. Originally, the check was hidden in the CanAccess // method but it's implementation has changed. bool equals; nsresult rv = aPrincipal->EqualsIgnoringDomain(storagePrincipal, &equals); NS_ASSERTION(NS_SUCCEEDED(rv) && equals, "GetSessionStorageForPrincipal got a storage " "that could not be accessed!"); if (NS_FAILED(rv) || !equals) { NS_RELEASE(*aStorage); return NS_ERROR_DOM_SECURITY_ERR; } } #if defined(PR_LOGGING) && defined(DEBUG) PR_LOG(gDocShellLog, PR_LOG_DEBUG, ("nsDocShell[%p]: returns existing sessionStorage %p", this, *aStorage)); #endif } if (aCreate) { // We are asked to create a new storage object. This indicates // that a new windows wants it. At this moment we "fork" the existing // storage object (what it means is described in the paragraph bellow). // We must create a single object per a single window to distinguish // a window originating oparations on the storage object to succesfully // prevent dispatch of a storage event to this same window that ivoked // a change in its storage. We also do this to correctly fill // documentURI property in the storage event. // // The difference between clone and fork is that clone creates // a completelly new and independent storage, but fork only creates // a new object wrapping the storage implementation and data and // the forked storage then behaves completelly the same way as // the storage it has been forked of, all such forked storage objects // shares their state and data and change on one such object affects // all others the same way. nsCOMPtr piStorage = do_QueryInterface(*aStorage); nsCOMPtr fork = piStorage->Fork(aDocumentURI); #if defined(PR_LOGGING) && defined(DEBUG) PR_LOG(gDocShellLog, PR_LOG_DEBUG, ("nsDocShell[%p]: forked sessionStorage %p to %p", this, *aStorage, fork.get())); #endif fork.swap(*aStorage); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetSessionStorageForURI(nsIURI* aURI, const nsAString& aDocumentURI, nsIDOMStorage** aStorage) { return GetSessionStorageForURI(aURI, aDocumentURI, true, aStorage); } nsresult nsDocShell::GetSessionStorageForURI(nsIURI* aURI, const nsSubstring& aDocumentURI, bool aCreate, nsIDOMStorage** aStorage) { NS_ENSURE_ARG(aURI); NS_ENSURE_ARG_POINTER(aStorage); *aStorage = nsnull; nsresult rv; nsCOMPtr securityManager = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); // This is terrible hack and should go away along with this whole method. nsCOMPtr principal; rv = securityManager->GetCodebasePrincipal(aURI, getter_AddRefs(principal)); if (NS_FAILED(rv)) return rv; return GetSessionStorageForPrincipal(principal, aDocumentURI, aCreate, aStorage); } nsresult nsDocShell::AddSessionStorage(nsIPrincipal* aPrincipal, nsIDOMStorage* aStorage) { NS_ENSURE_ARG_POINTER(aStorage); if (!aPrincipal) return NS_OK; nsCOMPtr topItem; nsresult rv = GetSameTypeRootTreeItem(getter_AddRefs(topItem)); if (NS_FAILED(rv)) return rv; if (topItem) { nsCOMPtr topDocShell = do_QueryInterface(topItem); if (topDocShell == this) { nsXPIDLCString origin; rv = aPrincipal->GetOrigin(getter_Copies(origin)); if (NS_FAILED(rv)) return rv; if (origin.IsEmpty()) return NS_ERROR_FAILURE; // Do not replace an existing session storage. if (mStorages.GetWeak(origin)) return NS_ERROR_NOT_AVAILABLE; #if defined(PR_LOGGING) && defined(DEBUG) PR_LOG(gDocShellLog, PR_LOG_DEBUG, ("nsDocShell[%p]: was added a sessionStorage %p", this, aStorage)); #endif if (!mStorages.Put(origin, aStorage)) return NS_ERROR_OUT_OF_MEMORY; } else { return topDocShell->AddSessionStorage(aPrincipal, aStorage); } } return NS_OK; } NS_IMETHODIMP nsDocShell::GetCurrentDocumentChannel(nsIChannel** aResult) { NS_IF_ADDREF(*aResult = GetCurrentDocChannel()); return NS_OK; } nsIChannel* nsDocShell::GetCurrentDocChannel() { if (mContentViewer) { nsIDocument* doc = mContentViewer->GetDocument(); if (doc) { return doc->GetChannel(); } } return nsnull; } //***************************************************************************** // nsDocShell::nsIDocShellTreeItem //***************************************************************************** NS_IMETHODIMP nsDocShell::GetName(PRUnichar ** aName) { NS_ENSURE_ARG_POINTER(aName); *aName = ToNewUnicode(mName); return NS_OK; } NS_IMETHODIMP nsDocShell::SetName(const PRUnichar * aName) { mName = aName; // this does a copy of aName return NS_OK; } NS_IMETHODIMP nsDocShell::NameEquals(const PRUnichar *aName, bool *_retval) { NS_ENSURE_ARG_POINTER(aName); NS_ENSURE_ARG_POINTER(_retval); *_retval = mName.Equals(aName); return NS_OK; } NS_IMETHODIMP nsDocShell::GetItemType(PRInt32 * aItemType) { NS_ENSURE_ARG_POINTER(aItemType); *aItemType = mItemType; return NS_OK; } NS_IMETHODIMP nsDocShell::SetItemType(PRInt32 aItemType) { NS_ENSURE_ARG((aItemType == typeChrome) || (typeContent == aItemType)); // Only allow setting the type on root docshells. Those would be the ones // that have the docloader service as mParent or have no mParent at all. nsCOMPtr docLoaderService = do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); NS_ENSURE_TRUE(docLoaderService, NS_ERROR_UNEXPECTED); NS_ENSURE_STATE(!mParent || mParent == docLoaderService); mItemType = aItemType; // disable auth prompting for anything but content mAllowAuth = mItemType == typeContent; nsRefPtr presContext = nsnull; GetPresContext(getter_AddRefs(presContext)); if (presContext) { presContext->InvalidateIsChromeCache(); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetParent(nsIDocShellTreeItem ** aParent) { if (!mParent) { *aParent = nsnull; } else { CallQueryInterface(mParent, aParent); } // Note that in the case when the parent is not an nsIDocShellTreeItem we // don't want to throw; we just want to return null. return NS_OK; } nsresult nsDocShell::SetDocLoaderParent(nsDocLoader * aParent) { nsDocLoader::SetDocLoaderParent(aParent); // Curse ambiguous nsISupports inheritance! nsISupports* parent = GetAsSupports(aParent); // If parent is another docshell, we inherit all their flags for // allowing plugins, scripting etc. bool value; nsCOMPtr parentAsDocShell(do_QueryInterface(parent)); if (parentAsDocShell) { if (NS_SUCCEEDED(parentAsDocShell->GetAllowPlugins(&value))) { SetAllowPlugins(value); } if (NS_SUCCEEDED(parentAsDocShell->GetAllowJavascript(&value))) { SetAllowJavascript(value); } if (NS_SUCCEEDED(parentAsDocShell->GetAllowMetaRedirects(&value))) { SetAllowMetaRedirects(value); } if (NS_SUCCEEDED(parentAsDocShell->GetAllowSubframes(&value))) { SetAllowSubframes(value); } if (NS_SUCCEEDED(parentAsDocShell->GetAllowImages(&value))) { SetAllowImages(value); } if (NS_SUCCEEDED(parentAsDocShell->GetAllowWindowControl(&value))) { SetAllowWindowControl(value); } if (NS_SUCCEEDED(parentAsDocShell->GetIsActive(&value))) { SetIsActive(value); } if (NS_FAILED(parentAsDocShell->GetAllowDNSPrefetch(&value))) { value = false; } SetAllowDNSPrefetch(value); } nsCOMPtr parentAsLoadContext(do_QueryInterface(parent)); if (parentAsLoadContext && NS_SUCCEEDED(parentAsLoadContext->GetUsePrivateBrowsing(&value))) { SetUsePrivateBrowsing(value); } nsCOMPtr parentURIListener(do_GetInterface(parent)); if (parentURIListener) mContentListener->SetParentContentListener(parentURIListener); return NS_OK; } NS_IMETHODIMP nsDocShell::GetSameTypeParent(nsIDocShellTreeItem ** aParent) { NS_ENSURE_ARG_POINTER(aParent); *aParent = nsnull; nsCOMPtr parent = do_QueryInterface(GetAsSupports(mParent)); if (!parent) return NS_OK; PRInt32 parentType; NS_ENSURE_SUCCESS(parent->GetItemType(&parentType), NS_ERROR_FAILURE); if (parentType == mItemType) { parent.swap(*aParent); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetRootTreeItem(nsIDocShellTreeItem ** aRootTreeItem) { NS_ENSURE_ARG_POINTER(aRootTreeItem); *aRootTreeItem = static_cast(this); nsCOMPtr parent; NS_ENSURE_SUCCESS(GetParent(getter_AddRefs(parent)), NS_ERROR_FAILURE); while (parent) { *aRootTreeItem = parent; NS_ENSURE_SUCCESS((*aRootTreeItem)->GetParent(getter_AddRefs(parent)), NS_ERROR_FAILURE); } NS_ADDREF(*aRootTreeItem); return NS_OK; } NS_IMETHODIMP nsDocShell::GetSameTypeRootTreeItem(nsIDocShellTreeItem ** aRootTreeItem) { NS_ENSURE_ARG_POINTER(aRootTreeItem); *aRootTreeItem = static_cast(this); nsCOMPtr parent; NS_ENSURE_SUCCESS(GetSameTypeParent(getter_AddRefs(parent)), NS_ERROR_FAILURE); while (parent) { *aRootTreeItem = parent; NS_ENSURE_SUCCESS((*aRootTreeItem)-> GetSameTypeParent(getter_AddRefs(parent)), NS_ERROR_FAILURE); } NS_ADDREF(*aRootTreeItem); return NS_OK; } /* static */ bool nsDocShell::CanAccessItem(nsIDocShellTreeItem* aTargetItem, nsIDocShellTreeItem* aAccessingItem, bool aConsiderOpener) { NS_PRECONDITION(aTargetItem, "Must have target item!"); if (!gValidateOrigin || !aAccessingItem) { // Good to go return true; } // XXXbz should we care if aAccessingItem or the document therein is // chrome? Should those get extra privileges? // For historical context, see: // // Bug 13871: Prevent frameset spoofing // Bug 103638: Targets with same name in different windows open in wrong // window with javascript // Bug 408052: Adopt "ancestor" frame navigation policy // Now do a security check // // Allow navigation if // 1) aAccessingItem can script aTargetItem or one of its ancestors in // the frame hierarchy or // 2) aTargetItem is a top-level frame and aAccessingItem is its descendant // 3) aTargetItem is a top-level frame and aAccessingItem can target // its opener per rule (1) or (2). if (aTargetItem == aAccessingItem) { // A frame is allowed to navigate itself. return true; } nsCOMPtr accessingRoot; aAccessingItem->GetSameTypeRootTreeItem(getter_AddRefs(accessingRoot)); if (aTargetItem == accessingRoot) { // A frame can navigate its root. return true; } // Check if aAccessingItem can navigate one of aTargetItem's ancestors. nsCOMPtr target = aTargetItem; do { if (ValidateOrigin(aAccessingItem, target)) { return true; } nsCOMPtr parent; target->GetSameTypeParent(getter_AddRefs(parent)); parent.swap(target); } while (target); nsCOMPtr targetRoot; aTargetItem->GetSameTypeRootTreeItem(getter_AddRefs(targetRoot)); if (aTargetItem != targetRoot) { // target is a subframe, not in accessor's frame hierarchy, and all its // ancestors have origins different from that of the accessor. Don't // allow access. return false; } if (!aConsiderOpener) { // All done here return false; } nsCOMPtr targetWindow = do_GetInterface(aTargetItem); if (!targetWindow) { NS_ERROR("This should not happen, really"); return false; } nsCOMPtr targetOpener; targetWindow->GetOpener(getter_AddRefs(targetOpener)); nsCOMPtr openerWebNav(do_GetInterface(targetOpener)); nsCOMPtr openerItem(do_QueryInterface(openerWebNav)); if (!openerItem) { return false; } return CanAccessItem(openerItem, aAccessingItem, false); } static bool ItemIsActive(nsIDocShellTreeItem *aItem) { nsCOMPtr window(do_GetInterface(aItem)); if (window) { bool isClosed; if (NS_SUCCEEDED(window->GetClosed(&isClosed)) && !isClosed) { return true; } } return false; } NS_IMETHODIMP nsDocShell::FindItemWithName(const PRUnichar * aName, nsISupports * aRequestor, nsIDocShellTreeItem * aOriginalRequestor, nsIDocShellTreeItem ** _retval) { NS_ENSURE_ARG(aName); NS_ENSURE_ARG_POINTER(_retval); // If we don't find one, we return NS_OK and a null result *_retval = nsnull; if (!*aName) return NS_OK; if (!aRequestor) { nsCOMPtr foundItem; // This is the entry point into the target-finding algorithm. Check // for special names. This should only be done once, hence the check // for a null aRequestor. nsDependentString name(aName); if (name.LowerCaseEqualsLiteral("_self")) { foundItem = this; } else if (name.LowerCaseEqualsLiteral("_blank")) { // Just return null. Caller must handle creating a new window with // a blank name himself. return NS_OK; } else if (name.LowerCaseEqualsLiteral("_parent")) { GetSameTypeParent(getter_AddRefs(foundItem)); if(!foundItem) foundItem = this; } else if (name.LowerCaseEqualsLiteral("_top")) { GetSameTypeRootTreeItem(getter_AddRefs(foundItem)); NS_ASSERTION(foundItem, "Must have this; worst case it's us!"); } // _main is an IE target which should be case-insensitive but isn't // see bug 217886 for details else if (name.LowerCaseEqualsLiteral("_content") || name.EqualsLiteral("_main")) { // Must pass our same type root as requestor to the // treeowner to make sure things work right. nsCOMPtr root; GetSameTypeRootTreeItem(getter_AddRefs(root)); if (mTreeOwner) { NS_ASSERTION(root, "Must have this; worst case it's us!"); mTreeOwner->FindItemWithName(aName, root, aOriginalRequestor, getter_AddRefs(foundItem)); } #ifdef DEBUG else { NS_ERROR("Someone isn't setting up the tree owner. " "You might like to try that. " "Things will.....you know, work."); // Note: _content should always exist. If we don't have one // hanging off the treeowner, just create a named window.... // so don't return here, in case we did that and can now find // it. // XXXbz should we be using |root| instead of creating // a new window? } #endif } if (foundItem && !CanAccessItem(foundItem, aOriginalRequestor)) { foundItem = nsnull; } if (foundItem) { // We return foundItem here even if it's not an active // item since all the names we've dealt with so far are // special cases that we won't bother looking for further. foundItem.swap(*_retval); return NS_OK; } } // Keep looking // First we check our name. if (mName.Equals(aName) && ItemIsActive(this) && CanAccessItem(this, aOriginalRequestor)) { NS_ADDREF(*_retval = this); return NS_OK; } // This QI may fail, but the places where we want to compare, comparing // against nsnull serves the same purpose. nsCOMPtr reqAsTreeItem(do_QueryInterface(aRequestor)); // Second we check our children making sure not to ask a child if // it is the aRequestor. #ifdef DEBUG nsresult rv = #endif FindChildWithName(aName, true, true, reqAsTreeItem, aOriginalRequestor, _retval); NS_ASSERTION(NS_SUCCEEDED(rv), "FindChildWithName should not be failing here."); if (*_retval) return NS_OK; // Third if we have a parent and it isn't the requestor then we // should ask it to do the search. If it is the requestor we // should just stop here and let the parent do the rest. If we // don't have a parent, then we should ask the // docShellTreeOwner to do the search. nsCOMPtr parentAsTreeItem = do_QueryInterface(GetAsSupports(mParent)); if (parentAsTreeItem) { if (parentAsTreeItem == reqAsTreeItem) return NS_OK; PRInt32 parentType; parentAsTreeItem->GetItemType(&parentType); if (parentType == mItemType) { return parentAsTreeItem-> FindItemWithName(aName, static_cast (this), aOriginalRequestor, _retval); } } // If the parent is null or not of the same type fall through and ask tree // owner. // This may fail, but comparing against null serves the same purpose nsCOMPtr reqAsTreeOwner(do_QueryInterface(aRequestor)); if (mTreeOwner && mTreeOwner != reqAsTreeOwner) { return mTreeOwner-> FindItemWithName(aName, this, aOriginalRequestor, _retval); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetTreeOwner(nsIDocShellTreeOwner ** aTreeOwner) { NS_ENSURE_ARG_POINTER(aTreeOwner); *aTreeOwner = mTreeOwner; NS_IF_ADDREF(*aTreeOwner); return NS_OK; } #ifdef DEBUG_DOCSHELL_FOCUS static void PrintDocTree(nsIDocShellTreeItem * aParentNode, int aLevel) { for (PRInt32 i=0;iGetChildCount(&childWebshellCount); nsCOMPtr parentAsDocShell(do_QueryInterface(aParentNode)); PRInt32 type; aParentNode->GetItemType(&type); nsCOMPtr presShell; parentAsDocShell->GetPresShell(getter_AddRefs(presShell)); nsRefPtr presContext; parentAsDocShell->GetPresContext(getter_AddRefs(presContext)); nsIDocument *doc = presShell->GetDocument(); nsCOMPtr domwin(doc->GetWindow()); nsCOMPtr widget; nsIViewManager* vm = presShell->GetViewManager(); if (vm) { vm->GetWidget(getter_AddRefs(widget)); } dom::Element* rootElement = doc->GetRootElement(); printf("DS %p Ty %s Doc %p DW %p EM %p CN %p\n", (void*)parentAsDocShell.get(), type==nsIDocShellTreeItem::typeChrome?"Chr":"Con", (void*)doc, (void*)domwin.get(), (void*)presContext->EventStateManager(), (void*)rootElement); if (childWebshellCount > 0) { for (PRInt32 i=0;i child; aParentNode->GetChildAt(i, getter_AddRefs(child)); PrintDocTree(child, aLevel+1); } } } static void PrintDocTree(nsIDocShellTreeItem * aParentNode) { NS_ASSERTION(aParentNode, "Pointer is null!"); nsCOMPtr parentItem; aParentNode->GetParent(getter_AddRefs(parentItem)); while (parentItem) { nsCOMPtrtmp; parentItem->GetParent(getter_AddRefs(tmp)); if (!tmp) { break; } parentItem = tmp; } if (!parentItem) { parentItem = aParentNode; } PrintDocTree(parentItem, 0); } #endif NS_IMETHODIMP nsDocShell::SetTreeOwner(nsIDocShellTreeOwner * aTreeOwner) { #ifdef DEBUG_DOCSHELL_FOCUS nsCOMPtr item(do_QueryInterface(aTreeOwner)); if (item) { PrintDocTree(item); } #endif // Don't automatically set the progress based on the tree owner for frames if (!IsFrame()) { nsCOMPtr webProgress = do_QueryInterface(GetAsSupports(this)); if (webProgress) { nsCOMPtr oldListener(do_QueryInterface(mTreeOwner)); nsCOMPtr newListener(do_QueryInterface(aTreeOwner)); if (oldListener) { webProgress->RemoveProgressListener(oldListener); } if (newListener) { webProgress->AddProgressListener(newListener, nsIWebProgress::NOTIFY_ALL); } } } mTreeOwner = aTreeOwner; // Weak reference per API PRInt32 i, n = mChildList.Count(); for (i = 0; i < n; i++) { nsCOMPtr child = do_QueryInterface(ChildAt(i)); NS_ENSURE_TRUE(child, NS_ERROR_FAILURE); PRInt32 childType = ~mItemType; // Set it to not us in case the get fails child->GetItemType(&childType); // We don't care if this fails, if it does we won't set the owner if (childType == mItemType) child->SetTreeOwner(aTreeOwner); } return NS_OK; } NS_IMETHODIMP nsDocShell::SetChildOffset(PRUint32 aChildOffset) { mChildOffset = aChildOffset; return NS_OK; } NS_IMETHODIMP nsDocShell::GetHistoryID(PRUint64* aID) { *aID = mHistoryID; return NS_OK; } NS_IMETHODIMP nsDocShell::GetIsInUnload(bool* aIsInUnload) { *aIsInUnload = mFiredUnloadEvent; return NS_OK; } //***************************************************************************** // nsDocShell::nsIDocShellTreeNode //***************************************************************************** NS_IMETHODIMP nsDocShell::GetChildCount(PRInt32 * aChildCount) { NS_ENSURE_ARG_POINTER(aChildCount); *aChildCount = mChildList.Count(); return NS_OK; } NS_IMETHODIMP nsDocShell::AddChild(nsIDocShellTreeItem * aChild) { NS_ENSURE_ARG_POINTER(aChild); nsRefPtr childAsDocLoader = GetAsDocLoader(aChild); NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED); // Make sure we're not creating a loop in the docshell tree nsDocLoader* ancestor = this; do { if (childAsDocLoader == ancestor) { return NS_ERROR_ILLEGAL_VALUE; } ancestor = ancestor->GetParent(); } while (ancestor); // Make sure to remove the child from its current parent. nsDocLoader* childsParent = childAsDocLoader->GetParent(); if (childsParent) { childsParent->RemoveChildLoader(childAsDocLoader); } // Make sure to clear the treeowner in case this child is a different type // from us. aChild->SetTreeOwner(nsnull); nsresult res = AddChildLoader(childAsDocLoader); NS_ENSURE_SUCCESS(res, res); NS_ASSERTION(mChildList.Count() > 0, "child list must not be empty after a successful add"); nsCOMPtr docshellhistory = do_QueryInterface(aChild); bool dynamic = false; docshellhistory->GetCreatedDynamically(&dynamic); if (!dynamic) { nsCOMPtr currentSH; bool oshe = false; GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe); if (currentSH) { currentSH->HasDynamicallyAddedChild(&dynamic); } } nsCOMPtr childDocShell = do_QueryInterface(aChild); childDocShell->SetChildOffset(dynamic ? -1 : mChildList.Count() - 1); /* Set the child's global history if the parent has one */ if (mUseGlobalHistory) { nsCOMPtr dsHistoryChild(do_QueryInterface(aChild)); if (dsHistoryChild) dsHistoryChild->SetUseGlobalHistory(true); } PRInt32 childType = ~mItemType; // Set it to not us in case the get fails aChild->GetItemType(&childType); if (childType != mItemType) return NS_OK; // Everything below here is only done when the child is the same type. aChild->SetTreeOwner(mTreeOwner); nsCOMPtr childAsDocShell(do_QueryInterface(aChild)); if (!childAsDocShell) return NS_OK; // charset, style-disabling, and zoom will be inherited in SetupNewViewer() // Now take this document's charset and set the child's parentCharset field // to it. We'll later use that field, in the loading process, for the // charset choosing algorithm. // If we fail, at any point, we just return NS_OK. // This code has some performance impact. But this will be reduced when // the current charset will finally be stored as an Atom, avoiding the // alias resolution extra look-up. // we are NOT going to propagate the charset is this Chrome's docshell if (mItemType == nsIDocShellTreeItem::typeChrome) return NS_OK; // get the parent's current charset if (!mContentViewer) return NS_OK; nsIDocument* doc = mContentViewer->GetDocument(); if (!doc) return NS_OK; const nsACString &parentCS = doc->GetDocumentCharacterSet(); bool isWyciwyg = false; if (mCurrentURI) { // Check if the url is wyciwyg mCurrentURI->SchemeIs("wyciwyg", &isWyciwyg); } if (!isWyciwyg) { // If this docshell is loaded from a wyciwyg: URI, don't // advertise our charset since it does not in any way reflect // the actual source charset, which is what we're trying to // expose here. // set the child's parentCharset nsCOMPtr parentCSAtom(do_GetAtom(parentCS)); res = childAsDocShell->SetParentCharset(parentCSAtom); if (NS_FAILED(res)) return NS_OK; PRInt32 charsetSource = doc->GetDocumentCharacterSetSource(); // set the child's parentCharset res = childAsDocShell->SetParentCharsetSource(charsetSource); if (NS_FAILED(res)) return NS_OK; } // printf("### 1 >>> Adding child. Parent CS = %s. ItemType = %d.\n", NS_LossyConvertUTF16toASCII(parentCS).get(), mItemType); return NS_OK; } NS_IMETHODIMP nsDocShell::RemoveChild(nsIDocShellTreeItem * aChild) { NS_ENSURE_ARG_POINTER(aChild); nsRefPtr childAsDocLoader = GetAsDocLoader(aChild); NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED); nsresult rv = RemoveChildLoader(childAsDocLoader); NS_ENSURE_SUCCESS(rv, rv); aChild->SetTreeOwner(nsnull); return nsDocLoader::AddDocLoaderAsChildOfRoot(childAsDocLoader); } NS_IMETHODIMP nsDocShell::GetChildAt(PRInt32 aIndex, nsIDocShellTreeItem ** aChild) { NS_ENSURE_ARG_POINTER(aChild); #ifdef DEBUG if (aIndex < 0) { NS_WARNING("Negative index passed to GetChildAt"); } else if (aIndex >= mChildList.Count()) { NS_WARNING("Too large an index passed to GetChildAt"); } #endif nsIDocumentLoader* child = SafeChildAt(aIndex); NS_ENSURE_TRUE(child, NS_ERROR_UNEXPECTED); return CallQueryInterface(child, aChild); } NS_IMETHODIMP nsDocShell::FindChildWithName(const PRUnichar * aName, bool aRecurse, bool aSameType, nsIDocShellTreeItem * aRequestor, nsIDocShellTreeItem * aOriginalRequestor, nsIDocShellTreeItem ** _retval) { NS_ENSURE_ARG(aName); NS_ENSURE_ARG_POINTER(_retval); *_retval = nsnull; // if we don't find one, we return NS_OK and a null result if (!*aName) return NS_OK; nsXPIDLString childName; PRInt32 i, n = mChildList.Count(); for (i = 0; i < n; i++) { nsCOMPtr child = do_QueryInterface(ChildAt(i)); NS_ENSURE_TRUE(child, NS_ERROR_FAILURE); PRInt32 childType; child->GetItemType(&childType); if (aSameType && (childType != mItemType)) continue; bool childNameEquals = false; child->NameEquals(aName, &childNameEquals); if (childNameEquals && ItemIsActive(child) && CanAccessItem(child, aOriginalRequestor)) { child.swap(*_retval); break; } if (childType != mItemType) //Only ask it to check children if it is same type continue; if (aRecurse && (aRequestor != child)) // Only ask the child if it isn't the requestor { // See if child contains the shell with the given name #ifdef DEBUG nsresult rv = #endif child->FindChildWithName(aName, true, aSameType, static_cast (this), aOriginalRequestor, _retval); NS_ASSERTION(NS_SUCCEEDED(rv), "FindChildWithName should not fail here"); if (*_retval) // found it return NS_OK; } } return NS_OK; } //***************************************************************************** // nsDocShell::nsIDocShellHistory //***************************************************************************** NS_IMETHODIMP nsDocShell::GetChildSHEntry(PRInt32 aChildOffset, nsISHEntry ** aResult) { nsresult rv = NS_OK; NS_ENSURE_ARG_POINTER(aResult); *aResult = nsnull; // A nsISHEntry for a child is *only* available when the parent is in // the progress of loading a document too... if (mLSHE) { /* Before looking for the subframe's url, check * the expiration status of the parent. If the parent * has expired from cache, then subframes will not be * loaded from history in certain situations. */ bool parentExpired=false; mLSHE->GetExpirationStatus(&parentExpired); /* Get the parent's Load Type so that it can be set on the child too. * By default give a loadHistory value */ PRUint32 loadType = nsIDocShellLoadInfo::loadHistory; mLSHE->GetLoadType(&loadType); // If the user did a shift-reload on this frameset page, // we don't want to load the subframes from history. if (loadType == nsIDocShellLoadInfo::loadReloadBypassCache || loadType == nsIDocShellLoadInfo::loadReloadBypassProxy || loadType == nsIDocShellLoadInfo::loadReloadBypassProxyAndCache || loadType == nsIDocShellLoadInfo::loadRefresh) return rv; /* If the user pressed reload and the parent frame has expired * from cache, we do not want to load the child frame from history. */ if (parentExpired && (loadType == nsIDocShellLoadInfo::loadReloadNormal)) { // The parent has expired. Return null. *aResult = nsnull; return rv; } nsCOMPtr container(do_QueryInterface(mLSHE)); if (container) { // Get the child subframe from session history. rv = container->GetChildAt(aChildOffset, aResult); if (*aResult) (*aResult)->SetLoadType(loadType); } } return rv; } NS_IMETHODIMP nsDocShell::AddChildSHEntry(nsISHEntry * aCloneRef, nsISHEntry * aNewEntry, PRInt32 aChildOffset, PRUint32 loadType, bool aCloneChildren) { nsresult rv; if (mLSHE && loadType != LOAD_PUSHSTATE) { /* You get here if you are currently building a * hierarchy ie.,you just visited a frameset page */ nsCOMPtr container(do_QueryInterface(mLSHE, &rv)); if (container) { rv = container->AddChild(aNewEntry, aChildOffset); } } else if (!aCloneRef) { /* This is an initial load in some subframe. Just append it if we can */ nsCOMPtr container(do_QueryInterface(mOSHE, &rv)); if (container) { rv = container->AddChild(aNewEntry, aChildOffset); } } else if (mSessionHistory) { /* You are currently in the rootDocShell. * You will get here when a subframe has a new url * to load and you have walked up the tree all the * way to the top to clone the current SHEntry hierarchy * and replace the subframe where a new url was loaded with * a new entry. */ PRInt32 index = -1; nsCOMPtr currentHE; mSessionHistory->GetIndex(&index); if (index < 0) return NS_ERROR_FAILURE; rv = mSessionHistory->GetEntryAtIndex(index, false, getter_AddRefs(currentHE)); NS_ENSURE_TRUE(currentHE, NS_ERROR_FAILURE); nsCOMPtr currentEntry(do_QueryInterface(currentHE)); if (currentEntry) { PRUint32 cloneID = 0; nsCOMPtr nextEntry; aCloneRef->GetID(&cloneID); rv = CloneAndReplace(currentEntry, this, cloneID, aNewEntry, aCloneChildren, getter_AddRefs(nextEntry)); if (NS_SUCCEEDED(rv)) { nsCOMPtr shPrivate(do_QueryInterface(mSessionHistory)); NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE); rv = shPrivate->AddEntry(nextEntry, true); } } } else { /* Just pass this along */ nsCOMPtr parent = do_QueryInterface(GetAsSupports(mParent), &rv); if (parent) { rv = parent->AddChildSHEntry(aCloneRef, aNewEntry, aChildOffset, loadType, aCloneChildren); } } return rv; } nsresult nsDocShell::DoAddChildSHEntry(nsISHEntry* aNewEntry, PRInt32 aChildOffset, bool aCloneChildren) { /* You will get here when you are in a subframe and * a new url has been loaded on you. * The mOSHE in this subframe will be the previous url's * mOSHE. This mOSHE will be used as the identification * for this subframe in the CloneAndReplace function. */ // In this case, we will end up calling AddEntry, which increases the // current index by 1 nsCOMPtr rootSH; GetRootSessionHistory(getter_AddRefs(rootSH)); if (rootSH) { rootSH->GetIndex(&mPreviousTransIndex); } nsresult rv; nsCOMPtr parent = do_QueryInterface(GetAsSupports(mParent), &rv); if (parent) { rv = parent->AddChildSHEntry(mOSHE, aNewEntry, aChildOffset, mLoadType, aCloneChildren); } if (rootSH) { rootSH->GetIndex(&mLoadedTransIndex); #ifdef DEBUG_PAGE_CACHE printf("Previous index: %d, Loaded index: %d\n\n", mPreviousTransIndex, mLoadedTransIndex); #endif } return rv; } NS_IMETHODIMP nsDocShell::SetUseGlobalHistory(bool aUseGlobalHistory) { nsresult rv; mUseGlobalHistory = aUseGlobalHistory; if (!aUseGlobalHistory) { mGlobalHistory = nsnull; return NS_OK; } // No need to initialize mGlobalHistory if IHistory is available. nsCOMPtr history = services::GetHistoryService(); if (history) { return NS_OK; } if (mGlobalHistory) { return NS_OK; } mGlobalHistory = do_GetService(NS_GLOBALHISTORY2_CONTRACTID, &rv); return rv; } NS_IMETHODIMP nsDocShell::GetUseGlobalHistory(bool *aUseGlobalHistory) { *aUseGlobalHistory = mUseGlobalHistory; return NS_OK; } NS_IMETHODIMP nsDocShell::RemoveFromSessionHistory() { nsCOMPtr internalHistory; nsCOMPtr sessionHistory; nsCOMPtr root; GetSameTypeRootTreeItem(getter_AddRefs(root)); if (root) { nsCOMPtr rootAsWebnav = do_QueryInterface(root); if (rootAsWebnav) { rootAsWebnav->GetSessionHistory(getter_AddRefs(sessionHistory)); internalHistory = do_QueryInterface(sessionHistory); } } if (!internalHistory) { return NS_OK; } PRInt32 index = 0; sessionHistory->GetIndex(&index); nsAutoTArray ids; ids.AppendElement(mHistoryID); internalHistory->RemoveEntries(ids, index); return NS_OK; } NS_IMETHODIMP nsDocShell::SetCreatedDynamically(bool aDynamic) { mDynamicallyCreated = aDynamic; return NS_OK; } NS_IMETHODIMP nsDocShell::GetCreatedDynamically(bool* aDynamic) { *aDynamic = mDynamicallyCreated; return NS_OK; } NS_IMETHODIMP nsDocShell::GetCurrentSHEntry(nsISHEntry** aEntry, bool* aOSHE) { *aOSHE = false; *aEntry = nsnull; if (mLSHE) { NS_ADDREF(*aEntry = mLSHE); } else if (mOSHE) { NS_ADDREF(*aEntry = mOSHE); *aOSHE = true; } return NS_OK; } void nsDocShell::ClearFrameHistory(nsISHEntry* aEntry) { nsCOMPtr shcontainer = do_QueryInterface(aEntry); nsCOMPtr rootSH; GetRootSessionHistory(getter_AddRefs(rootSH)); nsCOMPtr history = do_QueryInterface(rootSH); if (!history || !shcontainer) { return; } PRInt32 count = 0; shcontainer->GetChildCount(&count); nsAutoTArray ids; for (PRInt32 i = 0; i < count; ++i) { nsCOMPtr child; shcontainer->GetChildAt(i, getter_AddRefs(child)); if (child) { PRUint64 id = 0; child->GetDocshellID(&id); ids.AppendElement(id); } } PRInt32 index = 0; rootSH->GetIndex(&index); history->RemoveEntries(ids, index); } //------------------------------------- //-- Helper Method for Print discovery //------------------------------------- bool nsDocShell::IsPrintingOrPP(bool aDisplayErrorDialog) { if (mIsPrintingOrPP && aDisplayErrorDialog) { DisplayLoadError(NS_ERROR_DOCUMENT_IS_PRINTMODE, nsnull, nsnull); } return mIsPrintingOrPP; } bool nsDocShell::IsNavigationAllowed(bool aDisplayPrintErrorDialog) { return !IsPrintingOrPP(aDisplayPrintErrorDialog) && !mFiredUnloadEvent; } //***************************************************************************** // nsDocShell::nsIWebNavigation //***************************************************************************** NS_IMETHODIMP nsDocShell::GetCanGoBack(bool * aCanGoBack) { if (!IsNavigationAllowed(false)) { *aCanGoBack = false; return NS_OK; // JS may not handle returning of an error code } nsresult rv; nsCOMPtr rootSH; rv = GetRootSessionHistory(getter_AddRefs(rootSH)); nsCOMPtr webnav(do_QueryInterface(rootSH)); NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); rv = webnav->GetCanGoBack(aCanGoBack); return rv; } NS_IMETHODIMP nsDocShell::GetCanGoForward(bool * aCanGoForward) { if (!IsNavigationAllowed(false)) { *aCanGoForward = false; return NS_OK; // JS may not handle returning of an error code } nsresult rv; nsCOMPtr rootSH; rv = GetRootSessionHistory(getter_AddRefs(rootSH)); nsCOMPtr webnav(do_QueryInterface(rootSH)); NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); rv = webnav->GetCanGoForward(aCanGoForward); return rv; } NS_IMETHODIMP nsDocShell::GoBack() { if (!IsNavigationAllowed()) { return NS_OK; // JS may not handle returning of an error code } nsresult rv; nsCOMPtr rootSH; rv = GetRootSessionHistory(getter_AddRefs(rootSH)); nsCOMPtr webnav(do_QueryInterface(rootSH)); NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); rv = webnav->GoBack(); return rv; } NS_IMETHODIMP nsDocShell::GoForward() { if (!IsNavigationAllowed()) { return NS_OK; // JS may not handle returning of an error code } nsresult rv; nsCOMPtr rootSH; rv = GetRootSessionHistory(getter_AddRefs(rootSH)); nsCOMPtr webnav(do_QueryInterface(rootSH)); NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); rv = webnav->GoForward(); return rv; } NS_IMETHODIMP nsDocShell::GotoIndex(PRInt32 aIndex) { if (!IsNavigationAllowed()) { return NS_OK; // JS may not handle returning of an error code } nsresult rv; nsCOMPtr rootSH; rv = GetRootSessionHistory(getter_AddRefs(rootSH)); nsCOMPtr webnav(do_QueryInterface(rootSH)); NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); rv = webnav->GotoIndex(aIndex); return rv; } NS_IMETHODIMP nsDocShell::LoadURI(const PRUnichar * aURI, PRUint32 aLoadFlags, nsIURI * aReferringURI, nsIInputStream * aPostStream, nsIInputStream * aHeaderStream) { NS_ASSERTION((aLoadFlags & 0xf) == 0, "Unexpected flags"); if (!IsNavigationAllowed()) { return NS_OK; // JS may not handle returning of an error code } nsCOMPtr uri; nsresult rv = NS_OK; // Create a URI from our string; if that succeeds, we want to // change aLoadFlags to not include the ALLOW_THIRD_PARTY_FIXUP // flag. NS_ConvertUTF16toUTF8 uriString(aURI); // Cleanup the empty spaces that might be on each end. uriString.Trim(" "); // Eliminate embedded newlines, which single-line text fields now allow: uriString.StripChars("\r\n"); NS_ENSURE_TRUE(!uriString.IsEmpty(), NS_ERROR_FAILURE); rv = NS_NewURI(getter_AddRefs(uri), uriString); if (uri) { aLoadFlags &= ~LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; } if (sURIFixup) { // Call the fixup object. This will clobber the rv from NS_NewURI // above, but that's fine with us. Note that we need to do this even // if NS_NewURI returned a URI, because fixup handles nested URIs, etc // (things like view-source:mozilla.org for example). PRUint32 fixupFlags = 0; if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) { fixupFlags |= nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; } if (aLoadFlags & LOAD_FLAGS_URI_IS_UTF8) { fixupFlags |= nsIURIFixup::FIXUP_FLAG_USE_UTF8; } rv = sURIFixup->CreateFixupURI(uriString, fixupFlags, getter_AddRefs(uri)); } // else no fixup service so just use the URI we created and see // what happens if (NS_ERROR_MALFORMED_URI == rv) { DisplayLoadError(rv, uri, aURI); } if (NS_FAILED(rv) || !uri) return NS_ERROR_FAILURE; PopupControlState popupState; if (aLoadFlags & LOAD_FLAGS_ALLOW_POPUPS) { popupState = openAllowed; aLoadFlags &= ~LOAD_FLAGS_ALLOW_POPUPS; } else { popupState = openOverridden; } nsCOMPtr win(do_QueryInterface(mScriptGlobal)); nsAutoPopupStatePusher statePusher(win, popupState); // Don't pass certain flags that aren't needed and end up confusing // ConvertLoadTypeToDocShellLoadInfo. We do need to ensure that they are // passed to LoadURI though, since it uses them. PRUint32 extraFlags = (aLoadFlags & EXTRA_LOAD_FLAGS); aLoadFlags &= ~EXTRA_LOAD_FLAGS; nsCOMPtr loadInfo; rv = CreateLoadInfo(getter_AddRefs(loadInfo)); if (NS_FAILED(rv)) return rv; PRUint32 loadType = MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags); loadInfo->SetLoadType(ConvertLoadTypeToDocShellLoadInfo(loadType)); loadInfo->SetPostDataStream(aPostStream); loadInfo->SetReferrer(aReferringURI); loadInfo->SetHeadersStream(aHeaderStream); rv = LoadURI(uri, loadInfo, extraFlags, true); return rv; } NS_IMETHODIMP nsDocShell::DisplayLoadError(nsresult aError, nsIURI *aURI, const PRUnichar *aURL, nsIChannel* aFailedChannel) { // Get prompt and string bundle servcies nsCOMPtr prompter; nsCOMPtr stringBundle; GetPromptAndStringBundle(getter_AddRefs(prompter), getter_AddRefs(stringBundle)); NS_ENSURE_TRUE(stringBundle, NS_ERROR_FAILURE); NS_ENSURE_TRUE(prompter, NS_ERROR_FAILURE); nsAutoString error; const PRUint32 kMaxFormatStrArgs = 3; nsAutoString formatStrs[kMaxFormatStrArgs]; PRUint32 formatStrCount = 0; bool addHostPort = false; nsresult rv = NS_OK; nsAutoString messageStr; nsCAutoString cssClass; nsCAutoString errorPage; errorPage.AssignLiteral("neterror"); // Turn the error code into a human readable error message. if (NS_ERROR_UNKNOWN_PROTOCOL == aError) { NS_ENSURE_ARG_POINTER(aURI); // extract the scheme nsCAutoString scheme; aURI->GetScheme(scheme); CopyASCIItoUTF16(scheme, formatStrs[0]); formatStrCount = 1; error.AssignLiteral("protocolNotFound"); } else if (NS_ERROR_FILE_NOT_FOUND == aError) { NS_ENSURE_ARG_POINTER(aURI); error.AssignLiteral("fileNotFound"); } else if (NS_ERROR_UNKNOWN_HOST == aError) { NS_ENSURE_ARG_POINTER(aURI); // Get the host nsCAutoString host; nsCOMPtr innermostURI = NS_GetInnermostURI(aURI); innermostURI->GetHost(host); CopyUTF8toUTF16(host, formatStrs[0]); formatStrCount = 1; error.AssignLiteral("dnsNotFound"); } else if(NS_ERROR_CONNECTION_REFUSED == aError) { NS_ENSURE_ARG_POINTER(aURI); addHostPort = true; error.AssignLiteral("connectionFailure"); } else if(NS_ERROR_NET_INTERRUPT == aError) { NS_ENSURE_ARG_POINTER(aURI); addHostPort = true; error.AssignLiteral("netInterrupt"); } else if (NS_ERROR_NET_TIMEOUT == aError) { NS_ENSURE_ARG_POINTER(aURI); // Get the host nsCAutoString host; aURI->GetHost(host); CopyUTF8toUTF16(host, formatStrs[0]); formatStrCount = 1; error.AssignLiteral("netTimeout"); } else if (NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION == aError) { // CSP error cssClass.AssignLiteral("neterror"); error.AssignLiteral("cspFrameAncestorBlocked"); } else if (NS_ERROR_GET_MODULE(aError) == NS_ERROR_MODULE_SECURITY) { nsCOMPtr nsserr = do_GetService(NS_NSS_ERRORS_SERVICE_CONTRACTID); PRUint32 errorClass; if (!nsserr || NS_FAILED(nsserr->GetErrorClass(aError, &errorClass))) { errorClass = nsINSSErrorsService::ERROR_CLASS_SSL_PROTOCOL; } nsCOMPtr securityInfo; nsCOMPtr tsi; if (aFailedChannel) aFailedChannel->GetSecurityInfo(getter_AddRefs(securityInfo)); tsi = do_QueryInterface(securityInfo); if (tsi) { // Usually we should have aFailedChannel and get a detailed message tsi->GetErrorMessage(getter_Copies(messageStr)); } else { // No channel, let's obtain the generic error message if (nsserr) { nsserr->GetErrorMessage(aError, messageStr); } } if (!messageStr.IsEmpty()) { if (errorClass == nsINSSErrorsService::ERROR_CLASS_BAD_CERT) { error.AssignLiteral("nssBadCert"); // if this is a Strict-Transport-Security host and the cert // is bad, don't allow overrides (STS Spec section 7.3). nsCOMPtr stss = do_GetService(NS_STSSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); bool isStsHost = false; rv = stss->IsStsURI(aURI, &isStsHost); NS_ENSURE_SUCCESS(rv, rv); if (isStsHost) cssClass.AssignLiteral("badStsCert"); if (Preferences::GetBool( "browser.xul.error_pages.expert_bad_cert", false)) { cssClass.AssignLiteral("expertBadCert"); } // See if an alternate cert error page is registered nsAdoptingCString alternateErrorPage = Preferences::GetCString( "security.alternate_certificate_error_page"); if (alternateErrorPage) errorPage.Assign(alternateErrorPage); } else { error.AssignLiteral("nssFailure2"); } } } else if (NS_ERROR_PHISHING_URI == aError || NS_ERROR_MALWARE_URI == aError) { nsCAutoString host; aURI->GetHost(host); CopyUTF8toUTF16(host, formatStrs[0]); formatStrCount = 1; // Malware and phishing detectors may want to use an alternate error // page, but if the pref's not set, we'll fall back on the standard page nsAdoptingCString alternateErrorPage = Preferences::GetCString("urlclassifier.alternate_error_page"); if (alternateErrorPage) errorPage.Assign(alternateErrorPage); if (NS_ERROR_PHISHING_URI == aError) error.AssignLiteral("phishingBlocked"); else error.AssignLiteral("malwareBlocked"); cssClass.AssignLiteral("blacklist"); } else { // Errors requiring simple formatting switch (aError) { case NS_ERROR_MALFORMED_URI: // URI is malformed error.AssignLiteral("malformedURI"); break; case NS_ERROR_REDIRECT_LOOP: // Doc failed to load because the server generated too many redirects error.AssignLiteral("redirectLoop"); break; case NS_ERROR_UNKNOWN_SOCKET_TYPE: // Doc failed to load because PSM is not installed error.AssignLiteral("unknownSocketType"); break; case NS_ERROR_NET_RESET: // Doc failed to load because the server kept reseting the connection // before we could read any data from it error.AssignLiteral("netReset"); break; case NS_ERROR_DOCUMENT_NOT_CACHED: // Doc failed to load because the cache does not contain a copy of // the document. error.AssignLiteral("notCached"); break; case NS_ERROR_OFFLINE: // Doc failed to load because we are offline. error.AssignLiteral("netOffline"); break; case NS_ERROR_DOCUMENT_IS_PRINTMODE: // Doc navigation attempted while Printing or Print Preview error.AssignLiteral("isprinting"); break; case NS_ERROR_PORT_ACCESS_NOT_ALLOWED: // Port blocked for security reasons addHostPort = true; error.AssignLiteral("deniedPortAccess"); break; case NS_ERROR_UNKNOWN_PROXY_HOST: // Proxy hostname could not be resolved. error.AssignLiteral("proxyResolveFailure"); break; case NS_ERROR_PROXY_CONNECTION_REFUSED: // Proxy connection was refused. error.AssignLiteral("proxyConnectFailure"); break; case NS_ERROR_INVALID_CONTENT_ENCODING: // Bad Content Encoding. error.AssignLiteral("contentEncodingError"); break; case NS_ERROR_REMOTE_XUL: { error.AssignLiteral("remoteXUL"); break; } case NS_ERROR_UNSAFE_CONTENT_TYPE: // Channel refused to load from an unrecognized content type. error.AssignLiteral("unsafeContentType"); break; case NS_ERROR_CORRUPTED_CONTENT: // Broken Content Detected. e.g. Content-MD5 check failure. error.AssignLiteral("corruptedContentError"); break; } } // Test if the error should be displayed if (error.IsEmpty()) { return NS_OK; } // Test if the error needs to be formatted if (!messageStr.IsEmpty()) { // already obtained message } else { if (addHostPort) { // Build up the host:port string. nsCAutoString hostport; if (aURI) { aURI->GetHostPort(hostport); } else { hostport.AssignLiteral("?"); } CopyUTF8toUTF16(hostport, formatStrs[formatStrCount++]); } nsCAutoString spec; rv = NS_ERROR_NOT_AVAILABLE; if (aURI) { // displaying "file://" is aesthetically unpleasing and could even be // confusing to the user bool isFileURI = false; rv = aURI->SchemeIs("file", &isFileURI); if (NS_SUCCEEDED(rv) && isFileURI) aURI->GetPath(spec); else aURI->GetSpec(spec); nsCAutoString charset; // unescape and convert from origin charset aURI->GetOriginCharset(charset); nsCOMPtr textToSubURI( do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { rv = textToSubURI->UnEscapeURIForUI(charset, spec, formatStrs[formatStrCount]); } } else { spec.AssignLiteral("?"); } if (NS_FAILED(rv)) CopyUTF8toUTF16(spec, formatStrs[formatStrCount]); rv = NS_OK; ++formatStrCount; const PRUnichar *strs[kMaxFormatStrArgs]; for (PRUint32 i = 0; i < formatStrCount; i++) { strs[i] = formatStrs[i].get(); } nsXPIDLString str; rv = stringBundle->FormatStringFromName( error.get(), strs, formatStrCount, getter_Copies(str)); NS_ENSURE_SUCCESS(rv, rv); messageStr.Assign(str.get()); } // Display the error as a page or an alert prompt NS_ENSURE_FALSE(messageStr.IsEmpty(), NS_ERROR_FAILURE); if (mUseErrorPages) { // Display an error page LoadErrorPage(aURI, aURL, errorPage.get(), error.get(), messageStr.get(), cssClass.get(), aFailedChannel); } else { // The prompter reqires that our private window has a document (or it // asserts). Satisfy that assertion now since GetDocument will force // creation of one if it hasn't already been created. nsCOMPtr pwin(do_QueryInterface(mScriptGlobal)); if (pwin) { nsCOMPtr doc; pwin->GetDocument(getter_AddRefs(doc)); } // Display a message box prompter->Alert(nsnull, messageStr.get()); } return NS_OK; } NS_IMETHODIMP nsDocShell::LoadErrorPage(nsIURI *aURI, const PRUnichar *aURL, const char *aErrorPage, const PRUnichar *aErrorType, const PRUnichar *aDescription, const char *aCSSClass, nsIChannel* aFailedChannel) { #if defined(PR_LOGGING) && defined(DEBUG) if (PR_LOG_TEST(gDocShellLog, PR_LOG_DEBUG)) { nsCAutoString spec; aURI->GetSpec(spec); nsCAutoString chanName; if (aFailedChannel) aFailedChannel->GetName(chanName); else chanName.AssignLiteral(""); PR_LOG(gDocShellLog, PR_LOG_DEBUG, ("nsDocShell[%p]::LoadErrorPage(\"%s\", \"%s\", {...}, [%s])\n", this, spec.get(), NS_ConvertUTF16toUTF8(aURL).get(), chanName.get())); } #endif mFailedChannel = aFailedChannel; mFailedURI = aURI; mFailedLoadType = mLoadType; if (mLSHE) { // Abandon mLSHE's BFCache entry and create a new one. This way, if // we go back or forward to another SHEntry with the same doc // identifier, the error page won't persist. mLSHE->AbandonBFCacheEntry(); } nsCAutoString url; nsCAutoString charset; if (aURI) { nsresult rv = aURI->GetSpec(url); rv |= aURI->GetOriginCharset(charset); NS_ENSURE_SUCCESS(rv, rv); } else if (aURL) { // We need a URI object to store a session history entry, so make up a URI nsresult rv = NS_NewURI(getter_AddRefs(mFailedURI), "about:blank"); NS_ENSURE_SUCCESS(rv, rv); CopyUTF16toUTF8(aURL, url); } else { return NS_ERROR_INVALID_POINTER; } // Create a URL to pass all the error information through to the page. #undef SAFE_ESCAPE #define SAFE_ESCAPE(cstring, escArg1, escArg2) \ { \ char* s = nsEscape(escArg1, escArg2); \ if (!s) \ return NS_ERROR_OUT_OF_MEMORY; \ cstring.Adopt(s); \ } nsCString escapedUrl, escapedCharset, escapedError, escapedDescription, escapedCSSClass; SAFE_ESCAPE(escapedUrl, url.get(), url_Path); SAFE_ESCAPE(escapedCharset, charset.get(), url_Path); SAFE_ESCAPE(escapedError, NS_ConvertUTF16toUTF8(aErrorType).get(), url_Path); SAFE_ESCAPE(escapedDescription, NS_ConvertUTF16toUTF8(aDescription).get(), url_Path); if (aCSSClass) { SAFE_ESCAPE(escapedCSSClass, aCSSClass, url_Path); } nsCString errorPageUrl("about:"); errorPageUrl.AppendASCII(aErrorPage); errorPageUrl.AppendLiteral("?e="); errorPageUrl.AppendASCII(escapedError.get()); errorPageUrl.AppendLiteral("&u="); errorPageUrl.AppendASCII(escapedUrl.get()); if (!escapedCSSClass.IsEmpty()) { errorPageUrl.AppendASCII("&s="); errorPageUrl.AppendASCII(escapedCSSClass.get()); } errorPageUrl.AppendLiteral("&c="); errorPageUrl.AppendASCII(escapedCharset.get()); errorPageUrl.AppendLiteral("&d="); errorPageUrl.AppendASCII(escapedDescription.get()); nsCOMPtr errorPageURI; nsresult rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl); NS_ENSURE_SUCCESS(rv, rv); return InternalLoad(errorPageURI, nsnull, nsnull, INTERNAL_LOAD_FLAGS_INHERIT_OWNER, nsnull, nsnull, nsnull, nsnull, LOAD_ERROR_PAGE, nsnull, true, nsnull, nsnull); } NS_IMETHODIMP nsDocShell::Reload(PRUint32 aReloadFlags) { if (!IsNavigationAllowed()) { return NS_OK; // JS may not handle returning of an error code } nsresult rv; NS_ASSERTION(((aReloadFlags & 0xf) == 0), "Reload command not updated to use load flags!"); NS_ASSERTION((aReloadFlags & EXTRA_LOAD_FLAGS) == 0, "Don't pass these flags to Reload"); PRUint32 loadType = MAKE_LOAD_TYPE(LOAD_RELOAD_NORMAL, aReloadFlags); NS_ENSURE_TRUE(IsValidLoadType(loadType), NS_ERROR_INVALID_ARG); // Send notifications to the HistoryListener if any, about the impending reload nsCOMPtr rootSH; rv = GetRootSessionHistory(getter_AddRefs(rootSH)); nsCOMPtr shistInt(do_QueryInterface(rootSH)); bool canReload = true; if (rootSH) { nsCOMPtr listener; shistInt->GetListener(getter_AddRefs(listener)); if (listener) { listener->OnHistoryReload(mCurrentURI, aReloadFlags, &canReload); } } if (!canReload) return NS_OK; /* If you change this part of code, make sure bug 45297 does not re-occur */ if (mOSHE) { rv = LoadHistoryEntry(mOSHE, loadType); } else if (mLSHE) { // In case a reload happened before the current load is done rv = LoadHistoryEntry(mLSHE, loadType); } else { nsCOMPtr doc(do_GetInterface(GetAsSupports(this))); nsIPrincipal* principal = nsnull; nsAutoString contentTypeHint; if (doc) { principal = doc->NodePrincipal(); doc->GetContentType(contentTypeHint); } rv = InternalLoad(mCurrentURI, mReferrerURI, principal, INTERNAL_LOAD_FLAGS_NONE, // Do not inherit owner from document nsnull, // No window target NS_LossyConvertUTF16toASCII(contentTypeHint).get(), nsnull, // No post data nsnull, // No headers data loadType, // Load type nsnull, // No SHEntry true, nsnull, // No nsIDocShell nsnull); // No nsIRequest } return rv; } NS_IMETHODIMP nsDocShell::Stop(PRUint32 aStopFlags) { // Revoke any pending event related to content viewer restoration mRestorePresentationEvent.Revoke(); if (mLoadType == LOAD_ERROR_PAGE) { if (mLSHE) { // Since error page loads never unset mLSHE, do so now SetHistoryEntry(&mOSHE, mLSHE); SetHistoryEntry(&mLSHE, nsnull); } mFailedChannel = nsnull; mFailedURI = nsnull; } if (nsIWebNavigation::STOP_CONTENT & aStopFlags) { // Stop the document loading if (mContentViewer) mContentViewer->Stop(); } if (nsIWebNavigation::STOP_NETWORK & aStopFlags) { // Suspend any timers that were set for this loader. We'll clear // them out for good in CreateContentViewer. if (mRefreshURIList) { SuspendRefreshURIs(); mSavedRefreshURIList.swap(mRefreshURIList); mRefreshURIList = nsnull; } // XXXbz We could also pass |this| to nsIURILoader::Stop. That will // just call Stop() on us as an nsIDocumentLoader... We need fewer // redundant apis! Stop(); } PRInt32 n; PRInt32 count = mChildList.Count(); for (n = 0; n < count; n++) { nsCOMPtr shellAsNav(do_QueryInterface(ChildAt(n))); if (shellAsNav) shellAsNav->Stop(aStopFlags); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetDocument(nsIDOMDocument ** aDocument) { NS_ENSURE_ARG_POINTER(aDocument); NS_ENSURE_SUCCESS(EnsureContentViewer(), NS_ERROR_FAILURE); return mContentViewer->GetDOMDocument(aDocument); } NS_IMETHODIMP nsDocShell::GetCurrentURI(nsIURI ** aURI) { NS_ENSURE_ARG_POINTER(aURI); if (mCurrentURI) { return NS_EnsureSafeToReturn(mCurrentURI, aURI); } *aURI = nsnull; return NS_OK; } NS_IMETHODIMP nsDocShell::GetReferringURI(nsIURI ** aURI) { NS_ENSURE_ARG_POINTER(aURI); *aURI = mReferrerURI; NS_IF_ADDREF(*aURI); return NS_OK; } NS_IMETHODIMP nsDocShell::SetSessionHistory(nsISHistory * aSessionHistory) { NS_ENSURE_TRUE(aSessionHistory, NS_ERROR_FAILURE); // make sure that we are the root docshell and // set a handle to root docshell in SH. nsCOMPtr root; /* Get the root docshell. If *this* is the root docshell * then save a handle to *this* in SH. SH needs it to do * traversions thro' its entries */ GetSameTypeRootTreeItem(getter_AddRefs(root)); NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); if (root.get() == static_cast(this)) { mSessionHistory = aSessionHistory; nsCOMPtr shPrivate(do_QueryInterface(mSessionHistory)); NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE); shPrivate->SetRootDocShell(this); return NS_OK; } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsDocShell::GetSessionHistory(nsISHistory ** aSessionHistory) { NS_ENSURE_ARG_POINTER(aSessionHistory); *aSessionHistory = mSessionHistory; NS_IF_ADDREF(*aSessionHistory); return NS_OK; } //***************************************************************************** // nsDocShell::nsIWebPageDescriptor //***************************************************************************** NS_IMETHODIMP nsDocShell::LoadPage(nsISupports *aPageDescriptor, PRUint32 aDisplayType) { nsCOMPtr shEntryIn(do_QueryInterface(aPageDescriptor)); // Currently, the opaque 'page descriptor' is an nsISHEntry... if (!shEntryIn) { return NS_ERROR_INVALID_POINTER; } // Now clone shEntryIn, since we might end up modifying it later on, and we // want a page descriptor to be reusable. nsCOMPtr shEntry; nsresult rv = shEntryIn->Clone(getter_AddRefs(shEntry)); NS_ENSURE_SUCCESS(rv, rv); // Give our cloned shEntry a new bfcache entry so this load is independent // of all other loads. (This is important, in particular, for bugs 582795 // and 585298.) rv = shEntry->AbandonBFCacheEntry(); NS_ENSURE_SUCCESS(rv, rv); // // load the page as view-source // if (nsIWebPageDescriptor::DISPLAY_AS_SOURCE == aDisplayType) { nsCOMPtr oldUri, newUri; nsCString spec, newSpec; // Create a new view-source URI and replace the original. rv = shEntry->GetURI(getter_AddRefs(oldUri)); if (NS_FAILED(rv)) return rv; oldUri->GetSpec(spec); newSpec.AppendLiteral("view-source:"); newSpec.Append(spec); rv = NS_NewURI(getter_AddRefs(newUri), newSpec); if (NS_FAILED(rv)) { return rv; } shEntry->SetURI(newUri); } rv = LoadHistoryEntry(shEntry, LOAD_HISTORY); return rv; } NS_IMETHODIMP nsDocShell::GetCurrentDescriptor(nsISupports **aPageDescriptor) { NS_PRECONDITION(aPageDescriptor, "Null out param?"); *aPageDescriptor = nsnull; nsISHEntry* src = mOSHE ? mOSHE : mLSHE; if (src) { nsCOMPtr dest; nsresult rv = src->Clone(getter_AddRefs(dest)); if (NS_FAILED(rv)) { return rv; } // null out inappropriate cloned attributes... dest->SetParent(nsnull); dest->SetIsSubFrame(false); return CallQueryInterface(dest, aPageDescriptor); } return NS_ERROR_NOT_AVAILABLE; } //***************************************************************************** // nsDocShell::nsIBaseWindow //***************************************************************************** NS_IMETHODIMP nsDocShell::InitWindow(nativeWindow parentNativeWindow, nsIWidget * parentWidget, PRInt32 x, PRInt32 y, PRInt32 cx, PRInt32 cy) { SetParentWidget(parentWidget); SetPositionAndSize(x, y, cx, cy, false); return NS_OK; } NS_IMETHODIMP nsDocShell::Create() { if (mCreated) { // We've already been created return NS_OK; } NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, "Unexpected item type in docshell"); NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE); mCreated = true; mAllowSubframes = Preferences::GetBool("browser.frames.enabled", mAllowSubframes); if (gValidateOrigin == 0xffffffff) { // Check pref to see if we should prevent frameset spoofing gValidateOrigin = Preferences::GetBool("browser.frame.validate_origin", true); } // Should we use XUL error pages instead of alerts if possible? mUseErrorPages = Preferences::GetBool("browser.xul.error_pages.enabled", mUseErrorPages); if (mObserveErrorPages) { Preferences::AddStrongObserver(this, "browser.xul.error_pages.enabled"); } nsCOMPtr serv = services::GetObserverService(); if (serv) { const char* msg = mItemType == typeContent ? NS_WEBNAVIGATION_CREATE : NS_CHROME_WEBNAVIGATION_CREATE; serv->NotifyObservers(GetAsSupports(this), msg, nsnull); } return NS_OK; } NS_IMETHODIMP nsDocShell::Destroy() { NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, "Unexpected item type in docshell"); if (!mIsBeingDestroyed) { nsCOMPtr serv = services::GetObserverService(); if (serv) { const char* msg = mItemType == typeContent ? NS_WEBNAVIGATION_DESTROY : NS_CHROME_WEBNAVIGATION_DESTROY; serv->NotifyObservers(GetAsSupports(this), msg, nsnull); } } mIsBeingDestroyed = true; // Remove our pref observers if (mObserveErrorPages) { Preferences::RemoveObserver(this, "browser.xul.error_pages.enabled"); mObserveErrorPages = false; } // Make sure to blow away our mLoadingURI just in case. No loads // from inside this pagehide. mLoadingURI = nsnull; // Fire unload event before we blow anything away. (void) FirePageHideNotification(true); // Clear pointers to any detached nsEditorData that's lying // around in shistory entries. Breaks cycle. See bug 430921. if (mOSHE) mOSHE->SetEditorData(nsnull); if (mLSHE) mLSHE->SetEditorData(nsnull); // Note: mContentListener can be null if Init() failed and we're being // called from the destructor. if (mContentListener) { mContentListener->DropDocShellreference(); mContentListener->SetParentContentListener(nsnull); // Note that we do NOT set mContentListener to null here; that // way if someone tries to do a load in us after this point // the nsDSURIContentListener will block it. All of which // means that we should do this before calling Stop(), of // course. } // Stop any URLs that are currently being loaded... Stop(nsIWebNavigation::STOP_ALL); mEditorData = nsnull; mTransferableHookData = nsnull; // Save the state of the current document, before destroying the window. // This is needed to capture the state of a frameset when the new document // causes the frameset to be destroyed... PersistLayoutHistoryState(); // Remove this docshell from its parent's child list nsCOMPtr docShellParentAsItem = do_QueryInterface(GetAsSupports(mParent)); if (docShellParentAsItem) docShellParentAsItem->RemoveChild(this); if (mContentViewer) { mContentViewer->Close(nsnull); mContentViewer->Destroy(); mContentViewer = nsnull; } nsDocLoader::Destroy(); mParentWidget = nsnull; mCurrentURI = nsnull; if (mScriptGlobal) { nsCOMPtr win(do_QueryInterface(mScriptGlobal)); win->SetDocShell(nsnull); mScriptGlobal = nsnull; } if (mSessionHistory) { // We want to destroy these content viewers now rather than // letting their destruction wait for the session history // entries to get garbage collected. (Bug 488394) nsCOMPtr shPrivate = do_QueryInterface(mSessionHistory); if (shPrivate) { shPrivate->EvictAllContentViewers(); } mSessionHistory = nsnull; } SetTreeOwner(nsnull); // required to break ref cycle mSecurityUI = nsnull; // Cancel any timers that were set for this docshell; this is needed // to break the cycle between us and the timers. CancelRefreshURITimers(); return NS_OK; } NS_IMETHODIMP nsDocShell::SetPosition(PRInt32 x, PRInt32 y) { mBounds.x = x; mBounds.y = y; if (mContentViewer) NS_ENSURE_SUCCESS(mContentViewer->Move(x, y), NS_ERROR_FAILURE); return NS_OK; } NS_IMETHODIMP nsDocShell::GetPosition(PRInt32 * aX, PRInt32 * aY) { PRInt32 dummyHolder; return GetPositionAndSize(aX, aY, &dummyHolder, &dummyHolder); } NS_IMETHODIMP nsDocShell::SetSize(PRInt32 aCX, PRInt32 aCY, bool aRepaint) { PRInt32 x = 0, y = 0; GetPosition(&x, &y); return SetPositionAndSize(x, y, aCX, aCY, aRepaint); } NS_IMETHODIMP nsDocShell::GetSize(PRInt32 * aCX, PRInt32 * aCY) { PRInt32 dummyHolder; return GetPositionAndSize(&dummyHolder, &dummyHolder, aCX, aCY); } NS_IMETHODIMP nsDocShell::SetPositionAndSize(PRInt32 x, PRInt32 y, PRInt32 cx, PRInt32 cy, bool fRepaint) { mBounds.x = x; mBounds.y = y; mBounds.width = cx; mBounds.height = cy; // Hold strong ref, since SetBounds can make us null out mContentViewer nsCOMPtr viewer = mContentViewer; if (viewer) { //XXX Border figured in here or is that handled elsewhere? NS_ENSURE_SUCCESS(viewer->SetBounds(mBounds), NS_ERROR_FAILURE); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetPositionAndSize(PRInt32 * x, PRInt32 * y, PRInt32 * cx, PRInt32 * cy) { // We should really consider just getting this information from // our window instead of duplicating the storage and code... if (cx || cy) { // Caller wants to know our size; make sure to give them up to // date information. nsCOMPtr doc(do_GetInterface(GetAsSupports(mParent))); if (doc) { doc->FlushPendingNotifications(Flush_Layout); } } DoGetPositionAndSize(x, y, cx, cy); return NS_OK; } void nsDocShell::DoGetPositionAndSize(PRInt32 * x, PRInt32 * y, PRInt32 * cx, PRInt32 * cy) { if (x) *x = mBounds.x; if (y) *y = mBounds.y; if (cx) *cx = mBounds.width; if (cy) *cy = mBounds.height; } NS_IMETHODIMP nsDocShell::Repaint(bool aForce) { nsCOMPtr presShell; GetPresShell(getter_AddRefs(presShell)); NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); nsIViewManager* viewManager = presShell->GetViewManager(); NS_ENSURE_TRUE(viewManager, NS_ERROR_FAILURE); NS_ENSURE_SUCCESS(viewManager->InvalidateAllViews(), NS_ERROR_FAILURE); return NS_OK; } NS_IMETHODIMP nsDocShell::GetParentWidget(nsIWidget ** parentWidget) { NS_ENSURE_ARG_POINTER(parentWidget); *parentWidget = mParentWidget; NS_IF_ADDREF(*parentWidget); return NS_OK; } NS_IMETHODIMP nsDocShell::SetParentWidget(nsIWidget * aParentWidget) { mParentWidget = aParentWidget; return NS_OK; } NS_IMETHODIMP nsDocShell::GetParentNativeWindow(nativeWindow * parentNativeWindow) { NS_ENSURE_ARG_POINTER(parentNativeWindow); if (mParentWidget) *parentNativeWindow = mParentWidget->GetNativeData(NS_NATIVE_WIDGET); else *parentNativeWindow = nsnull; return NS_OK; } NS_IMETHODIMP nsDocShell::SetParentNativeWindow(nativeWindow parentNativeWindow) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsDocShell::GetVisibility(bool * aVisibility) { NS_ENSURE_ARG_POINTER(aVisibility); *aVisibility = false; if (!mContentViewer) return NS_OK; nsCOMPtr presShell; GetPresShell(getter_AddRefs(presShell)); if (!presShell) return NS_OK; // get the view manager nsIViewManager* vm = presShell->GetViewManager(); NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE); // get the root view nsIView *view = vm->GetRootView(); // views are not ref counted NS_ENSURE_TRUE(view, NS_ERROR_FAILURE); // if our root view is hidden, we are not visible if (view->GetVisibility() == nsViewVisibility_kHide) return NS_OK; // otherwise, we must walk up the document and view trees checking // for a hidden view, unless we're an off screen browser, which // would make this test meaningless. nsCOMPtr treeItem = this; nsCOMPtr parentItem; treeItem->GetParent(getter_AddRefs(parentItem)); while (parentItem) { nsCOMPtr docShell(do_QueryInterface(treeItem)); docShell->GetPresShell(getter_AddRefs(presShell)); nsCOMPtr parentDS = do_QueryInterface(parentItem); nsCOMPtr pPresShell; parentDS->GetPresShell(getter_AddRefs(pPresShell)); // Null-check for crash in bug 267804 if (!pPresShell) { NS_NOTREACHED("parent docshell has null pres shell"); return NS_OK; } nsIContent *shellContent = pPresShell->GetDocument()->FindContentForSubDocument(presShell->GetDocument()); NS_ASSERTION(shellContent, "subshell not in the map"); nsIFrame* frame = shellContent ? shellContent->GetPrimaryFrame() : nsnull; bool isDocShellOffScreen = false; docShell->GetIsOffScreenBrowser(&isDocShellOffScreen); if (frame && !frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) && !isDocShellOffScreen) { return NS_OK; } treeItem = parentItem; treeItem->GetParent(getter_AddRefs(parentItem)); } nsCOMPtr treeOwnerAsWin(do_QueryInterface(mTreeOwner)); if (!treeOwnerAsWin) { *aVisibility = true; return NS_OK; } // Check with the tree owner as well to give embedders a chance to // expose visibility as well. return treeOwnerAsWin->GetVisibility(aVisibility); } NS_IMETHODIMP nsDocShell::SetIsOffScreenBrowser(bool aIsOffScreen) { mIsOffScreenBrowser = aIsOffScreen; return NS_OK; } NS_IMETHODIMP nsDocShell::GetIsOffScreenBrowser(bool *aIsOffScreen) { *aIsOffScreen = mIsOffScreenBrowser; return NS_OK; } NS_IMETHODIMP nsDocShell::SetIsActive(bool aIsActive) { // We disallow setting active on chrome docshells. if (mItemType == nsIDocShellTreeItem::typeChrome) return NS_ERROR_INVALID_ARG; // Keep track ourselves. mIsActive = aIsActive; // Tell the PresShell about it. nsCOMPtr pshell; GetPresShell(getter_AddRefs(pshell)); if (pshell) pshell->SetIsActive(aIsActive); // Tell the window about it nsCOMPtr win = do_QueryInterface(mScriptGlobal); if (win) { win->SetIsBackground(!aIsActive); nsCOMPtr doc = do_QueryInterface(win->GetExtantDocument()); if (doc) { doc->PostVisibilityUpdateEvent(); } } // Recursively tell all of our children PRInt32 n = mChildList.Count(); for (PRInt32 i = 0; i < n; ++i) { nsCOMPtr docshell = do_QueryInterface(ChildAt(i)); if (docshell) docshell->SetIsActive(aIsActive); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetIsActive(bool *aIsActive) { *aIsActive = mIsActive; return NS_OK; } NS_IMETHODIMP nsDocShell::SetIsAppTab(bool aIsAppTab) { mIsAppTab = aIsAppTab; return NS_OK; } NS_IMETHODIMP nsDocShell::GetIsAppTab(bool *aIsAppTab) { *aIsAppTab = mIsAppTab; return NS_OK; } NS_IMETHODIMP nsDocShell::SetVisibility(bool aVisibility) { if (!mContentViewer) return NS_OK; if (aVisibility) { mContentViewer->Show(); } else { mContentViewer->Hide(); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetEnabled(bool *aEnabled) { NS_ENSURE_ARG_POINTER(aEnabled); *aEnabled = true; return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsDocShell::SetEnabled(bool aEnabled) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsDocShell::SetFocus() { return NS_OK; } NS_IMETHODIMP nsDocShell::GetMainWidget(nsIWidget ** aMainWidget) { // We don't create our own widget, so simply return the parent one. return GetParentWidget(aMainWidget); } NS_IMETHODIMP nsDocShell::GetTitle(PRUnichar ** aTitle) { NS_ENSURE_ARG_POINTER(aTitle); *aTitle = ToNewUnicode(mTitle); return NS_OK; } NS_IMETHODIMP nsDocShell::SetTitle(const PRUnichar * aTitle) { // Store local title mTitle = aTitle; nsCOMPtr parent; GetSameTypeParent(getter_AddRefs(parent)); // When title is set on the top object it should then be passed to the // tree owner. if (!parent) { nsCOMPtr treeOwnerAsWin(do_QueryInterface(mTreeOwner)); if (treeOwnerAsWin) treeOwnerAsWin->SetTitle(aTitle); } if (mCurrentURI && mLoadType != LOAD_ERROR_PAGE && mUseGlobalHistory) { nsCOMPtr history = services::GetHistoryService(); if (history) { history->SetURITitle(mCurrentURI, mTitle); } else if (mGlobalHistory) { mGlobalHistory->SetPageTitle(mCurrentURI, nsString(mTitle)); } } // Update SessionHistory with the document's title. if (mOSHE && mLoadType != LOAD_BYPASS_HISTORY && mLoadType != LOAD_ERROR_PAGE) { mOSHE->SetTitle(mTitle); } return NS_OK; } //***************************************************************************** // nsDocShell::nsIScrollable //***************************************************************************** NS_IMETHODIMP nsDocShell::GetCurScrollPos(PRInt32 scrollOrientation, PRInt32 * curPos) { NS_ENSURE_ARG_POINTER(curPos); nsIScrollableFrame* sf = GetRootScrollFrame(); NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); nsPoint pt = sf->GetScrollPosition(); switch (scrollOrientation) { case ScrollOrientation_X: *curPos = pt.x; return NS_OK; case ScrollOrientation_Y: *curPos = pt.y; return NS_OK; default: NS_ENSURE_TRUE(false, NS_ERROR_INVALID_ARG); } } NS_IMETHODIMP nsDocShell::SetCurScrollPos(PRInt32 scrollOrientation, PRInt32 curPos) { nsIScrollableFrame* sf = GetRootScrollFrame(); NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); nsPoint pt = sf->GetScrollPosition(); switch (scrollOrientation) { case ScrollOrientation_X: pt.x = curPos; break; case ScrollOrientation_Y: pt.y = curPos; break; default: NS_ENSURE_TRUE(false, NS_ERROR_INVALID_ARG); } sf->ScrollTo(pt, nsIScrollableFrame::INSTANT); return NS_OK; } NS_IMETHODIMP nsDocShell::SetCurScrollPosEx(PRInt32 curHorizontalPos, PRInt32 curVerticalPos) { nsIScrollableFrame* sf = GetRootScrollFrame(); NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); sf->ScrollTo(nsPoint(curHorizontalPos, curVerticalPos), nsIScrollableFrame::INSTANT); return NS_OK; } // XXX This is wrong NS_IMETHODIMP nsDocShell::GetScrollRange(PRInt32 scrollOrientation, PRInt32 * minPos, PRInt32 * maxPos) { NS_ENSURE_ARG_POINTER(minPos && maxPos); nsIScrollableFrame* sf = GetRootScrollFrame(); NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); nsSize portSize = sf->GetScrollPortRect().Size(); nsRect range = sf->GetScrollRange(); switch (scrollOrientation) { case ScrollOrientation_X: *minPos = range.x; *maxPos = range.XMost() + portSize.width; return NS_OK; case ScrollOrientation_Y: *minPos = range.y; *maxPos = range.YMost() + portSize.height; return NS_OK; default: NS_ENSURE_TRUE(false, NS_ERROR_INVALID_ARG); } } NS_IMETHODIMP nsDocShell::SetScrollRange(PRInt32 scrollOrientation, PRInt32 minPos, PRInt32 maxPos) { //XXX First Check /* Retrieves or Sets the valid ranges for the thumb. When maxPos is set to something less than the current thumb position, curPos is set = to maxPos. @return NS_OK - Setting or Getting completed successfully. NS_ERROR_INVALID_ARG - returned when curPos is not within the minPos and maxPos. */ return NS_ERROR_FAILURE; } NS_IMETHODIMP nsDocShell::SetScrollRangeEx(PRInt32 minHorizontalPos, PRInt32 maxHorizontalPos, PRInt32 minVerticalPos, PRInt32 maxVerticalPos) { //XXX First Check /* Retrieves or Sets the valid ranges for the thumb. When maxPos is set to something less than the current thumb position, curPos is set = to maxPos. @return NS_OK - Setting or Getting completed successfully. NS_ERROR_INVALID_ARG - returned when curPos is not within the minPos and maxPos. */ return NS_ERROR_FAILURE; } // This returns setting for all documents in this docshell NS_IMETHODIMP nsDocShell::GetDefaultScrollbarPreferences(PRInt32 scrollOrientation, PRInt32 * scrollbarPref) { NS_ENSURE_ARG_POINTER(scrollbarPref); switch (scrollOrientation) { case ScrollOrientation_X: *scrollbarPref = mDefaultScrollbarPref.x; return NS_OK; case ScrollOrientation_Y: *scrollbarPref = mDefaultScrollbarPref.y; return NS_OK; default: NS_ENSURE_TRUE(false, NS_ERROR_INVALID_ARG); } return NS_ERROR_FAILURE; } // Set scrolling preference for all documents in this shell // // There are three possible values stored in the shell: // 1) nsIScrollable::Scrollbar_Never = no scrollbar // 2) nsIScrollable::Scrollbar_Auto = scrollbar appears if the document // being displayed would normally have scrollbar // 3) nsIScrollable::Scrollbar_Always = scrollbar always appears // // One important client is nsHTMLFrameInnerFrame::CreateWebShell() NS_IMETHODIMP nsDocShell::SetDefaultScrollbarPreferences(PRInt32 scrollOrientation, PRInt32 scrollbarPref) { switch (scrollOrientation) { case ScrollOrientation_X: mDefaultScrollbarPref.x = scrollbarPref; return NS_OK; case ScrollOrientation_Y: mDefaultScrollbarPref.y = scrollbarPref; return NS_OK; default: NS_ENSURE_TRUE(false, NS_ERROR_INVALID_ARG); } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsDocShell::GetScrollbarVisibility(bool * verticalVisible, bool * horizontalVisible) { nsIScrollableFrame* sf = GetRootScrollFrame(); NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); PRUint32 scrollbarVisibility = sf->GetScrollbarVisibility(); if (verticalVisible) *verticalVisible = (scrollbarVisibility & nsIScrollableFrame::VERTICAL) != 0; if (horizontalVisible) *horizontalVisible = (scrollbarVisibility & nsIScrollableFrame::HORIZONTAL) != 0; return NS_OK; } //***************************************************************************** // nsDocShell::nsITextScroll //***************************************************************************** NS_IMETHODIMP nsDocShell::ScrollByLines(PRInt32 numLines) { nsIScrollableFrame* sf = GetRootScrollFrame(); NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); sf->ScrollBy(nsIntPoint(0, numLines), nsIScrollableFrame::LINES, nsIScrollableFrame::SMOOTH); return NS_OK; } NS_IMETHODIMP nsDocShell::ScrollByPages(PRInt32 numPages) { nsIScrollableFrame* sf = GetRootScrollFrame(); NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); sf->ScrollBy(nsIntPoint(0, numPages), nsIScrollableFrame::PAGES, nsIScrollableFrame::SMOOTH); return NS_OK; } //***************************************************************************** // nsDocShell::nsIScriptGlobalObjectOwner //***************************************************************************** nsIScriptGlobalObject* nsDocShell::GetScriptGlobalObject() { NS_ENSURE_SUCCESS(EnsureScriptEnvironment(), nsnull); return mScriptGlobal; } //***************************************************************************** // nsDocShell::nsIRefreshURI //***************************************************************************** NS_IMETHODIMP nsDocShell::RefreshURI(nsIURI * aURI, PRInt32 aDelay, bool aRepeat, bool aMetaRefresh) { NS_ENSURE_ARG(aURI); /* Check if Meta refresh/redirects are permitted. Some * embedded applications may not want to do this. * Must do this before sending out NOTIFY_REFRESH events * because listeners may have side effects (e.g. displaying a * button to manually trigger the refresh later). */ bool allowRedirects = true; GetAllowMetaRedirects(&allowRedirects); if (!allowRedirects) return NS_OK; // If any web progress listeners are listening for NOTIFY_REFRESH events, // give them a chance to block this refresh. bool sameURI; nsresult rv = aURI->Equals(mCurrentURI, &sameURI); if (NS_FAILED(rv)) sameURI = false; if (!RefreshAttempted(this, aURI, aDelay, sameURI)) return NS_OK; nsRefreshTimer *refreshTimer = new nsRefreshTimer(); NS_ENSURE_TRUE(refreshTimer, NS_ERROR_OUT_OF_MEMORY); PRUint32 busyFlags = 0; GetBusyFlags(&busyFlags); nsCOMPtr dataRef = refreshTimer; // Get the ref count to 1 refreshTimer->mDocShell = this; refreshTimer->mURI = aURI; refreshTimer->mDelay = aDelay; refreshTimer->mRepeat = aRepeat; refreshTimer->mMetaRefresh = aMetaRefresh; if (!mRefreshURIList) { NS_ENSURE_SUCCESS(NS_NewISupportsArray(getter_AddRefs(mRefreshURIList)), NS_ERROR_FAILURE); } if (busyFlags & BUSY_FLAGS_BUSY) { // We are busy loading another page. Don't create the // timer right now. Instead queue up the request and trigger the // timer in EndPageLoad(). mRefreshURIList->AppendElement(refreshTimer); } else { // There is no page loading going on right now. Create the // timer and fire it right away. nsCOMPtr timer = do_CreateInstance("@mozilla.org/timer;1"); NS_ENSURE_TRUE(timer, NS_ERROR_FAILURE); mRefreshURIList->AppendElement(timer); // owning timer ref timer->InitWithCallback(refreshTimer, aDelay, nsITimer::TYPE_ONE_SHOT); } return NS_OK; } nsresult nsDocShell::ForceRefreshURIFromTimer(nsIURI * aURI, PRInt32 aDelay, bool aMetaRefresh, nsITimer* aTimer) { NS_PRECONDITION(aTimer, "Must have a timer here"); // Remove aTimer from mRefreshURIList if needed if (mRefreshURIList) { PRUint32 n = 0; mRefreshURIList->Count(&n); for (PRUint32 i = 0; i < n; ++i) { nsCOMPtr timer = do_QueryElementAt(mRefreshURIList, i); if (timer == aTimer) { mRefreshURIList->RemoveElementAt(i); break; } } } return ForceRefreshURI(aURI, aDelay, aMetaRefresh); } NS_IMETHODIMP nsDocShell::ForceRefreshURI(nsIURI * aURI, PRInt32 aDelay, bool aMetaRefresh) { NS_ENSURE_ARG(aURI); nsCOMPtr loadInfo; CreateLoadInfo(getter_AddRefs(loadInfo)); NS_ENSURE_TRUE(loadInfo, NS_ERROR_OUT_OF_MEMORY); /* We do need to pass in a referrer, but we don't want it to * be sent to the server. */ loadInfo->SetSendReferrer(false); /* for most refreshes the current URI is an appropriate * internal referrer */ loadInfo->SetReferrer(mCurrentURI); /* Don't ever "guess" on which owner to use to avoid picking * the current owner. */ loadInfo->SetOwnerIsExplicit(true); /* Check if this META refresh causes a redirection * to another site. */ bool equalUri = false; nsresult rv = aURI->Equals(mCurrentURI, &equalUri); if (NS_SUCCEEDED(rv) && (!equalUri) && aMetaRefresh && aDelay <= REFRESH_REDIRECT_TIMER) { /* It is a META refresh based redirection within the threshold time * we have in mind (15000 ms as defined by REFRESH_REDIRECT_TIMER). * Pass a REPLACE flag to LoadURI(). */ loadInfo->SetLoadType(nsIDocShellLoadInfo::loadNormalReplace); /* for redirects we mimic HTTP, which passes the * original referrer */ nsCOMPtr internalReferrer; GetReferringURI(getter_AddRefs(internalReferrer)); if (internalReferrer) { loadInfo->SetReferrer(internalReferrer); } } else { loadInfo->SetLoadType(nsIDocShellLoadInfo::loadRefresh); } /* * LoadURI(...) will cancel all refresh timers... This causes the * Timer and its refreshData instance to be released... */ LoadURI(aURI, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, true); return NS_OK; } nsresult nsDocShell::SetupRefreshURIFromHeader(nsIURI * aBaseURI, const nsACString & aHeader) { // Refresh headers are parsed with the following format in mind // // By the time we are here, the following is true: // header = "REFRESH" // content = "5; URL=http://uri" // note the URL attribute is // optional, if it is absent, the currently loaded url is used. // Also note that the seconds and URL separator can be either // a ';' or a ','. The ',' separator should be illegal but CNN // is using it. // // We need to handle the following strings, where // - X is a set of digits // - URI is either a relative or absolute URI // // Note that URI should start with "url=" but we allow omission // // "" || ";" || "," // empty string. use the currently loaded URI // and refresh immediately. // "X" || "X;" || "X," // Refresh the currently loaded URI in X seconds. // "X; URI" || "X, URI" // Refresh using URI as the destination in X seconds. // "URI" || "; URI" || ", URI" // Refresh immediately using URI as the destination. // // Currently, anything immediately following the URI, if // separated by any char in the set "'\"\t\r\n " will be // ignored. So "10; url=go.html ; foo=bar" will work, // and so will "10; url='go.html'; foo=bar". However, // "10; url=go.html; foo=bar" will result in the uri // "go.html;" since ';' and ',' are valid uri characters. // // Note that we need to remove any tokens wrapping the URI. // These tokens currently include spaces, double and single // quotes. // when done, seconds is 0 or the given number of seconds // uriAttrib is empty or the URI specified nsCAutoString uriAttrib; PRInt32 seconds = 0; bool specifiesSeconds = false; nsACString::const_iterator iter, tokenStart, doneIterating; aHeader.BeginReading(iter); aHeader.EndReading(doneIterating); // skip leading whitespace while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) ++iter; tokenStart = iter; // skip leading + and - if (iter != doneIterating && (*iter == '-' || *iter == '+')) ++iter; // parse number while (iter != doneIterating && (*iter >= '0' && *iter <= '9')) { seconds = seconds * 10 + (*iter - '0'); specifiesSeconds = true; ++iter; } if (iter != doneIterating) { // if we started with a '-', number is negative if (*tokenStart == '-') seconds = -seconds; // skip to next ';' or ',' nsACString::const_iterator iterAfterDigit = iter; while (iter != doneIterating && !(*iter == ';' || *iter == ',')) { if (specifiesSeconds) { // Non-whitespace characters here mean that the string is // malformed but tolerate sites that specify a decimal point, // even though meta refresh only works on whole seconds. if (iter == iterAfterDigit && !nsCRT::IsAsciiSpace(*iter) && *iter != '.') { // The characters between the seconds and the next // section are just garbage! // e.g. content="2a0z+,URL=http://www.mozilla.org/" // Just ignore this redirect. return NS_ERROR_FAILURE; } else if (nsCRT::IsAsciiSpace(*iter)) { // We've had at least one whitespace so tolerate the mistake // and drop through. // e.g. content="10 foo" ++iter; break; } } ++iter; } // skip any remaining whitespace while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) ++iter; // skip ';' or ',' if (iter != doneIterating && (*iter == ';' || *iter == ',')) { ++iter; } // skip whitespace while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) ++iter; } // possible start of URI tokenStart = iter; // skip "url = " to real start of URI if (iter != doneIterating && (*iter == 'u' || *iter == 'U')) { ++iter; if (iter != doneIterating && (*iter == 'r' || *iter == 'R')) { ++iter; if (iter != doneIterating && (*iter == 'l' || *iter == 'L')) { ++iter; // skip whitespace while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) ++iter; if (iter != doneIterating && *iter == '=') { ++iter; // skip whitespace while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) ++iter; // found real start of URI tokenStart = iter; } } } } // skip a leading '"' or '\''. bool isQuotedURI = false; if (tokenStart != doneIterating && (*tokenStart == '"' || *tokenStart == '\'')) { isQuotedURI = true; ++tokenStart; } // set iter to start of URI iter = tokenStart; // tokenStart here points to the beginning of URI // grab the rest of the URI while (iter != doneIterating) { if (isQuotedURI && (*iter == '"' || *iter == '\'')) break; ++iter; } // move iter one back if the last character is a '"' or '\'' if (iter != tokenStart && isQuotedURI) { --iter; if (!(*iter == '"' || *iter == '\'')) ++iter; } // URI is whatever's contained from tokenStart to iter. // note: if tokenStart == doneIterating, so is iter. nsresult rv = NS_OK; nsCOMPtr uri; bool specifiesURI = false; if (tokenStart == iter) { uri = aBaseURI; } else { uriAttrib = Substring(tokenStart, iter); // NS_NewURI takes care of any whitespace surrounding the URL rv = NS_NewURI(getter_AddRefs(uri), uriAttrib, nsnull, aBaseURI); specifiesURI = true; } // No URI or seconds were specified if (!specifiesSeconds && !specifiesURI) { // Do nothing because the alternative is to spin around in a refresh // loop forever! return NS_ERROR_FAILURE; } if (NS_SUCCEEDED(rv)) { nsCOMPtr securityManager(do_GetService (NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { rv = securityManager-> CheckLoadURI(aBaseURI, uri, nsIScriptSecurityManager:: LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT); if (NS_SUCCEEDED(rv)) { bool isjs = true; rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_OPENING_EXECUTES_SCRIPT, &isjs); NS_ENSURE_SUCCESS(rv, rv); if (isjs) { return NS_ERROR_FAILURE; } } if (NS_SUCCEEDED(rv)) { // Since we can't travel back in time yet, just pretend // negative numbers do nothing at all. if (seconds < 0) return NS_ERROR_FAILURE; rv = RefreshURI(uri, seconds * 1000, false, true); } } } return rv; } NS_IMETHODIMP nsDocShell::SetupRefreshURI(nsIChannel * aChannel) { nsresult rv; nsCOMPtr httpChannel(do_QueryInterface(aChannel, &rv)); if (NS_SUCCEEDED(rv)) { nsCAutoString refreshHeader; rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"), refreshHeader); if (!refreshHeader.IsEmpty()) { SetupReferrerFromChannel(aChannel); rv = SetupRefreshURIFromHeader(mCurrentURI, refreshHeader); if (NS_SUCCEEDED(rv)) { return NS_REFRESHURI_HEADER_FOUND; } } } return rv; } static void DoCancelRefreshURITimers(nsISupportsArray* aTimerList) { if (!aTimerList) return; PRUint32 n=0; aTimerList->Count(&n); while (n) { nsCOMPtr timer(do_QueryElementAt(aTimerList, --n)); aTimerList->RemoveElementAt(n); // bye bye owning timer ref if (timer) timer->Cancel(); } } NS_IMETHODIMP nsDocShell::CancelRefreshURITimers() { DoCancelRefreshURITimers(mRefreshURIList); DoCancelRefreshURITimers(mSavedRefreshURIList); mRefreshURIList = nsnull; mSavedRefreshURIList = nsnull; return NS_OK; } NS_IMETHODIMP nsDocShell::GetRefreshPending(bool* _retval) { if (!mRefreshURIList) { *_retval = false; return NS_OK; } PRUint32 count; nsresult rv = mRefreshURIList->Count(&count); if (NS_SUCCEEDED(rv)) *_retval = (count != 0); return rv; } NS_IMETHODIMP nsDocShell::SuspendRefreshURIs() { if (mRefreshURIList) { PRUint32 n = 0; mRefreshURIList->Count(&n); for (PRUint32 i = 0; i < n; ++i) { nsCOMPtr timer = do_QueryElementAt(mRefreshURIList, i); if (!timer) continue; // this must be a nsRefreshURI already // Replace this timer object with a nsRefreshTimer object. nsCOMPtr callback; timer->GetCallback(getter_AddRefs(callback)); timer->Cancel(); nsCOMPtr rt = do_QueryInterface(callback); NS_ASSERTION(rt, "RefreshURIList timer callbacks should only be RefreshTimer objects"); mRefreshURIList->ReplaceElementAt(rt, i); } } // Suspend refresh URIs for our child shells as well. PRInt32 n = mChildList.Count(); for (PRInt32 i = 0; i < n; ++i) { nsCOMPtr shell = do_QueryInterface(ChildAt(i)); if (shell) shell->SuspendRefreshURIs(); } return NS_OK; } NS_IMETHODIMP nsDocShell::ResumeRefreshURIs() { RefreshURIFromQueue(); // Resume refresh URIs for our child shells as well. PRInt32 n = mChildList.Count(); for (PRInt32 i = 0; i < n; ++i) { nsCOMPtr shell = do_QueryInterface(ChildAt(i)); if (shell) shell->ResumeRefreshURIs(); } return NS_OK; } nsresult nsDocShell::RefreshURIFromQueue() { if (!mRefreshURIList) return NS_OK; PRUint32 n = 0; mRefreshURIList->Count(&n); while (n) { nsCOMPtr element; mRefreshURIList->GetElementAt(--n, getter_AddRefs(element)); nsCOMPtr refreshInfo(do_QueryInterface(element)); if (refreshInfo) { // This is the nsRefreshTimer object, waiting to be // setup in a timer object and fired. // Create the timer and trigger it. PRUint32 delay = static_cast(static_cast(refreshInfo))->GetDelay(); nsCOMPtr timer = do_CreateInstance("@mozilla.org/timer;1"); if (timer) { // Replace the nsRefreshTimer element in the queue with // its corresponding timer object, so that in case another // load comes through before the timer can go off, the timer will // get cancelled in CancelRefreshURITimer() mRefreshURIList->ReplaceElementAt(timer, n); timer->InitWithCallback(refreshInfo, delay, nsITimer::TYPE_ONE_SHOT); } } } // while return NS_OK; } //***************************************************************************** // nsDocShell::nsIContentViewerContainer //***************************************************************************** NS_IMETHODIMP nsDocShell::Embed(nsIContentViewer * aContentViewer, const char *aCommand, nsISupports * aExtraInfo) { // Save the LayoutHistoryState of the previous document, before // setting up new document PersistLayoutHistoryState(); nsresult rv = SetupNewViewer(aContentViewer); // If we are loading a wyciwyg url from history, change the base URI for // the document to the original http url that created the document.write(). // This makes sure that all relative urls in a document.written page loaded // via history work properly. if (mCurrentURI && (mLoadType & LOAD_CMD_HISTORY || mLoadType == LOAD_RELOAD_NORMAL || mLoadType == LOAD_RELOAD_CHARSET_CHANGE)){ bool isWyciwyg = false; // Check if the url is wyciwyg rv = mCurrentURI->SchemeIs("wyciwyg", &isWyciwyg); if (isWyciwyg && NS_SUCCEEDED(rv)) SetBaseUrlForWyciwyg(aContentViewer); } // XXX What if SetupNewViewer fails? if (mLSHE) { // Restore the editing state, if it's stored in session history. if (mLSHE->HasDetachedEditor()) { ReattachEditorToWindow(mLSHE); } // Set history.state SetDocCurrentStateObj(mLSHE); SetHistoryEntry(&mOSHE, mLSHE); } bool updateHistory = true; // Determine if this type of load should update history switch (mLoadType) { case LOAD_NORMAL_REPLACE: case LOAD_STOP_CONTENT_AND_REPLACE: case LOAD_RELOAD_BYPASS_CACHE: case LOAD_RELOAD_BYPASS_PROXY: case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: updateHistory = false; break; default: break; } if (!updateHistory) SetLayoutHistoryState(nsnull); return NS_OK; } /* void setIsPrinting (in boolean aIsPrinting); */ NS_IMETHODIMP nsDocShell::SetIsPrinting(bool aIsPrinting) { mIsPrintingOrPP = aIsPrinting; return NS_OK; } //***************************************************************************** // nsDocShell::nsIWebProgressListener //***************************************************************************** NS_IMETHODIMP nsDocShell::OnProgressChange(nsIWebProgress * aProgress, nsIRequest * aRequest, PRInt32 aCurSelfProgress, PRInt32 aMaxSelfProgress, PRInt32 aCurTotalProgress, PRInt32 aMaxTotalProgress) { return NS_OK; } NS_IMETHODIMP nsDocShell::OnStateChange(nsIWebProgress * aProgress, nsIRequest * aRequest, PRUint32 aStateFlags, nsresult aStatus) { nsresult rv; if ((~aStateFlags & (STATE_START | STATE_IS_NETWORK)) == 0) { // Save timing statistics. nsCOMPtr channel(do_QueryInterface(aRequest)); nsCOMPtr uri; channel->GetURI(getter_AddRefs(uri)); nsCAutoString aURI; uri->GetAsciiSpec(aURI); if (this == aProgress){ rv = MaybeInitTiming(); if (mTiming) { mTiming->NotifyFetchStart(uri, ConvertLoadTypeToNavigationType(mLoadType)); } } nsCOMPtr wcwgChannel(do_QueryInterface(aRequest)); nsCOMPtr webProgress = do_QueryInterface(GetAsSupports(this)); // Was the wyciwyg document loaded on this docshell? if (wcwgChannel && !mLSHE && (mItemType == typeContent) && aProgress == webProgress.get()) { bool equalUri = true; // Store the wyciwyg url in session history, only if it is // being loaded fresh for the first time. We don't want // multiple entries for successive loads if (mCurrentURI && NS_SUCCEEDED(uri->Equals(mCurrentURI, &equalUri)) && !equalUri) { nsCOMPtr parentAsItem; GetSameTypeParent(getter_AddRefs(parentAsItem)); nsCOMPtr parentDS(do_QueryInterface(parentAsItem)); bool inOnLoadHandler = false; if (parentDS) { parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler); } if (inOnLoadHandler) { // We're handling parent's load event listener, which causes // document.write in a subdocument. // Need to clear the session history for all child // docshells so that we can handle them like they would // all be added dynamically. nsCOMPtr parent = do_QueryInterface(parentAsItem); if (parent) { bool oshe = false; nsCOMPtr entry; parent->GetCurrentSHEntry(getter_AddRefs(entry), &oshe); static_cast(parent.get())-> ClearFrameHistory(entry); } } // This is a document.write(). Get the made-up url // from the channel and store it in session history. // Pass false for aCloneChildren, since we're creating // a new DOM here. rv = AddToSessionHistory(uri, wcwgChannel, nsnull, false, getter_AddRefs(mLSHE)); SetCurrentURI(uri, aRequest, true, 0); // Save history state of the previous page rv = PersistLayoutHistoryState(); // We'll never get an Embed() for this load, so just go ahead // and SetHistoryEntry now. SetHistoryEntry(&mOSHE, mLSHE); } } // Page has begun to load mBusyFlags = BUSY_FLAGS_BUSY | BUSY_FLAGS_BEFORE_PAGE_LOAD; if ((aStateFlags & STATE_RESTORING) == 0) { // Show the progress cursor if the pref is set if (Preferences::GetBool("ui.use_activity_cursor", false)) { nsCOMPtr mainWidget; GetMainWidget(getter_AddRefs(mainWidget)); if (mainWidget) { mainWidget->SetCursor(eCursor_spinning); } } } } else if ((~aStateFlags & (STATE_TRANSFERRING | STATE_IS_DOCUMENT)) == 0) { // Page is loading mBusyFlags = BUSY_FLAGS_BUSY | BUSY_FLAGS_PAGE_LOADING; } else if ((aStateFlags & STATE_STOP) && (aStateFlags & STATE_IS_NETWORK)) { // Page has finished loading mBusyFlags = BUSY_FLAGS_NONE; // Hide the progress cursor if the pref is set if (Preferences::GetBool("ui.use_activity_cursor", false)) { nsCOMPtr mainWidget; GetMainWidget(getter_AddRefs(mainWidget)); if (mainWidget) { mainWidget->SetCursor(eCursor_standard); } } } if ((~aStateFlags & (STATE_IS_DOCUMENT | STATE_STOP)) == 0) { nsCOMPtr webProgress = do_QueryInterface(GetAsSupports(this)); // Is the document stop notification for this document? if (aProgress == webProgress.get()) { nsCOMPtr channel(do_QueryInterface(aRequest)); EndPageLoad(aProgress, channel, aStatus); } } // note that redirect state changes will go through here as well, but it // is better to handle those in OnRedirectStateChange where more // information is available. return NS_OK; } NS_IMETHODIMP nsDocShell::OnLocationChange(nsIWebProgress * aProgress, nsIRequest * aRequest, nsIURI * aURI, PRUint32 aFlags) { NS_NOTREACHED("notification excluded in AddProgressListener(...)"); return NS_OK; } void nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel, nsIChannel* aNewChannel, PRUint32 aRedirectFlags, PRUint32 aStateFlags) { NS_ASSERTION(aStateFlags & STATE_REDIRECTING, "Calling OnRedirectStateChange when there is no redirect"); if (!(aStateFlags & STATE_IS_DOCUMENT)) return; // not a toplevel document nsCOMPtr oldURI, newURI; aOldChannel->GetURI(getter_AddRefs(oldURI)); aNewChannel->GetURI(getter_AddRefs(newURI)); if (!oldURI || !newURI) { return; } // On session restore we get a redirect from page to itself. Don't count it. bool equals = false; if (mTiming && !(mLoadType == LOAD_HISTORY && NS_SUCCEEDED(newURI->Equals(oldURI, &equals)) && equals)) { mTiming->NotifyRedirect(oldURI, newURI); } // Below a URI visit is saved (see AddURIVisit method doc). // The visit chain looks something like: // ... // Site N - 1 // => Site N // (redirect to =>) Site N + 1 (we are here!) // Get N - 1 and transition type nsCOMPtr previousURI; PRUint32 previousFlags = 0; ExtractLastVisit(aOldChannel, getter_AddRefs(previousURI), &previousFlags); if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL || ChannelIsPost(aOldChannel)) { // 1. Internal redirects are ignored because they are specific to the // channel implementation. // 2. POSTs are not saved by global history. // // Regardless, we need to propagate the previous visit to the new // channel. SaveLastVisit(aNewChannel, previousURI, previousFlags); } else { nsCOMPtr referrer; // Treat referrer as null if there is an error getting it. (void)NS_GetReferrerFromChannel(aOldChannel, getter_AddRefs(referrer)); // Add visit N -1 => N AddURIVisit(oldURI, referrer, previousURI, previousFlags); // Since N + 1 could be the final destination, we will not save N => N + 1 // here. OnNewURI will do that, so we will cache it. SaveLastVisit(aNewChannel, oldURI, aRedirectFlags); } // check if the new load should go through the application cache. nsCOMPtr appCacheChannel = do_QueryInterface(aNewChannel); if (appCacheChannel) { // Permission will be checked in the parent process. if (GeckoProcessType_Default != XRE_GetProcessType()) appCacheChannel->SetChooseApplicationCache(true); else appCacheChannel->SetChooseApplicationCache(ShouldCheckAppCache(newURI)); } if (!(aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) && mLoadType & (LOAD_CMD_RELOAD | LOAD_CMD_HISTORY)) { mLoadType = LOAD_NORMAL_REPLACE; SetHistoryEntry(&mLSHE, nsnull); } } NS_IMETHODIMP nsDocShell::OnStatusChange(nsIWebProgress * aWebProgress, nsIRequest * aRequest, nsresult aStatus, const PRUnichar * aMessage) { NS_NOTREACHED("notification excluded in AddProgressListener(...)"); return NS_OK; } NS_IMETHODIMP nsDocShell::OnSecurityChange(nsIWebProgress * aWebProgress, nsIRequest * aRequest, PRUint32 state) { NS_NOTREACHED("notification excluded in AddProgressListener(...)"); return NS_OK; } nsresult nsDocShell::EndPageLoad(nsIWebProgress * aProgress, nsIChannel * aChannel, nsresult aStatus) { if(!aChannel) return NS_ERROR_NULL_POINTER; nsCOMPtr url; nsresult rv = aChannel->GetURI(getter_AddRefs(url)); if (NS_FAILED(rv)) return rv; nsCOMPtr timingChannel = do_QueryInterface(aChannel); if (timingChannel) { TimeStamp channelCreationTime; rv = timingChannel->GetChannelCreation(&channelCreationTime); if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) Telemetry::AccumulateTimeDelta( Telemetry::TOTAL_CONTENT_PAGE_LOAD_TIME, channelCreationTime); } // Timing is picked up by the window, we don't need it anymore mTiming = nsnull; // clean up reload state for meta charset if (eCharsetReloadRequested == mCharsetReloadState) mCharsetReloadState = eCharsetReloadStopOrigional; else mCharsetReloadState = eCharsetReloadInit; // Save a pointer to the currently-loading history entry. // nsDocShell::EndPageLoad will clear mLSHE, but we may need this history // entry further down in this method. nsCOMPtr loadingSHE = mLSHE; // // one of many safeguards that prevent death and destruction if // someone is so very very rude as to bring this window down // during this load handler. // nsCOMPtr kungFuDeathGrip(this); // Notify the ContentViewer that the Document has finished loading. This // will cause any OnLoad(...) and PopState(...) handlers to fire. if (!mEODForCurrentDocument && mContentViewer) { mIsExecutingOnLoadHandler = true; mContentViewer->LoadComplete(aStatus); mIsExecutingOnLoadHandler = false; mEODForCurrentDocument = true; // If all documents have completed their loading // favor native event dispatch priorities // over performance if (--gNumberOfDocumentsLoading == 0) { // Hint to use normal native event dispatch priorities FavorPerformanceHint(false, NS_EVENT_STARVATION_DELAY_HINT); } } /* Check if the httpChannel has any cache-control related response headers, * like no-store, no-cache. If so, update SHEntry so that * when a user goes back/forward to this page, we appropriately do * form value restoration or load from server. */ nsCOMPtr httpChannel(do_QueryInterface(aChannel)); if (!httpChannel) // HttpChannel could be hiding underneath a Multipart channel. GetHttpChannel(aChannel, getter_AddRefs(httpChannel)); if (httpChannel) { // figure out if SH should be saving layout state. bool discardLayoutState = ShouldDiscardLayoutState(httpChannel); if (mLSHE && discardLayoutState && (mLoadType & LOAD_CMD_NORMAL) && (mLoadType != LOAD_BYPASS_HISTORY) && (mLoadType != LOAD_ERROR_PAGE)) mLSHE->SetSaveLayoutStateFlag(false); } // Clear mLSHE after calling the onLoadHandlers. This way, if the // onLoadHandler tries to load something different in // itself or one of its children, we can deal with it appropriately. if (mLSHE) { mLSHE->SetLoadType(nsIDocShellLoadInfo::loadHistory); // Clear the mLSHE reference to indicate document loading is done one // way or another. SetHistoryEntry(&mLSHE, nsnull); } // if there's a refresh header in the channel, this method // will set it up for us. RefreshURIFromQueue(); // Test whether this is the top frame or a subframe bool isTopFrame = true; nsCOMPtr targetParentTreeItem; rv = GetSameTypeParent(getter_AddRefs(targetParentTreeItem)); if (NS_SUCCEEDED(rv) && targetParentTreeItem) { isTopFrame = false; } // // If the page load failed, then deal with the error condition... // Errors are handled as follows: // 1. Check to see if it's a file not found error or bad content // encoding error. // 2. Send the URI to a keyword server (if enabled) // 3. If the error was DNS failure, then add www and .com to the URI // (if appropriate). // 4. Throw an error dialog box... // if (url && NS_FAILED(aStatus)) { if (aStatus == NS_ERROR_FILE_NOT_FOUND || aStatus == NS_ERROR_CORRUPTED_CONTENT || aStatus == NS_ERROR_INVALID_CONTENT_ENCODING) { DisplayLoadError(aStatus, url, nsnull, aChannel); return NS_OK; } if (sURIFixup) { // // Try and make an alternative URI from the old one // nsCOMPtr newURI; nsCAutoString oldSpec; url->GetSpec(oldSpec); // // First try keyword fixup // if (aStatus == NS_ERROR_UNKNOWN_HOST && mAllowKeywordFixup) { bool keywordsEnabled = Preferences::GetBool("keyword.enabled", false); nsCAutoString host; url->GetHost(host); nsCAutoString scheme; url->GetScheme(scheme); PRInt32 dotLoc = host.FindChar('.'); // we should only perform a keyword search under the following // conditions: // (0) Pref keyword.enabled is true // (1) the url scheme is http (or https) // (2) the url does not have a protocol scheme // If we don't enforce such a policy, then we end up doing // keyword searchs on urls we don't intend like imap, file, // mailbox, etc. This could lead to a security problem where we // send data to the keyword server that we shouldn't be. // Someone needs to clean up keywords in general so we can // determine on a per url basis if we want keywords // enabled...this is just a bandaid... if (keywordsEnabled && !scheme.IsEmpty() && (scheme.Find("http") != 0)) { keywordsEnabled = false; } if (keywordsEnabled && (kNotFound == dotLoc)) { // only send non-qualified hosts to the keyword server // // If this string was passed through nsStandardURL by // chance, then it may have been converted from UTF-8 to // ACE, which would result in a completely bogus keyword // query. Here we try to recover the original Unicode // value, but this is not 100% correct since the value may // have been normalized per the IDN normalization rules. // // Since we don't have access to the exact original string // that was entered by the user, this will just have to do. bool isACE; nsCAutoString utf8Host; nsCOMPtr idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID); if (idnSrv && NS_SUCCEEDED(idnSrv->IsACE(host, &isACE)) && isACE && NS_SUCCEEDED(idnSrv->ConvertACEtoUTF8(host, utf8Host))) sURIFixup->KeywordToURI(utf8Host, getter_AddRefs(newURI)); else sURIFixup->KeywordToURI(host, getter_AddRefs(newURI)); } // end keywordsEnabled } // // Now try change the address, e.g. turn http://foo into // http://www.foo.com // if (aStatus == NS_ERROR_UNKNOWN_HOST || aStatus == NS_ERROR_NET_RESET) { bool doCreateAlternate = true; // Skip fixup for anything except a normal document load // operation on the topframe. if (mLoadType != LOAD_NORMAL || !isTopFrame) { doCreateAlternate = false; } else { // Test if keyword lookup produced a new URI or not if (newURI) { bool sameURI = false; url->Equals(newURI, &sameURI); if (!sameURI) { // Keyword lookup made a new URI so no need to try // an alternate one. doCreateAlternate = false; } } } if (doCreateAlternate) { newURI = nsnull; sURIFixup->CreateFixupURI(oldSpec, nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI, getter_AddRefs(newURI)); } } // Did we make a new URI that is different to the old one? If so // load it. // if (newURI) { // Make sure the new URI is different from the old one, // otherwise there's little point trying to load it again. bool sameURI = false; url->Equals(newURI, &sameURI); if (!sameURI) { nsCAutoString newSpec; newURI->GetSpec(newSpec); NS_ConvertUTF8toUTF16 newSpecW(newSpec); return LoadURI(newSpecW.get(), // URI string LOAD_FLAGS_NONE, // Load flags nsnull, // Referring URI nsnull, // Post data stream nsnull); // Headers stream } } } // Well, fixup didn't work :-( // It is time to throw an error dialog box, and be done with it... // Errors to be shown only on top-level frames if ((aStatus == NS_ERROR_UNKNOWN_HOST || aStatus == NS_ERROR_CONNECTION_REFUSED || aStatus == NS_ERROR_UNKNOWN_PROXY_HOST || aStatus == NS_ERROR_PROXY_CONNECTION_REFUSED) && (isTopFrame || mUseErrorPages)) { DisplayLoadError(aStatus, url, nsnull, aChannel); } // Errors to be shown for any frame else if (aStatus == NS_ERROR_NET_TIMEOUT || aStatus == NS_ERROR_REDIRECT_LOOP || aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE || aStatus == NS_ERROR_NET_INTERRUPT || aStatus == NS_ERROR_NET_RESET || aStatus == NS_ERROR_OFFLINE || aStatus == NS_ERROR_MALWARE_URI || aStatus == NS_ERROR_PHISHING_URI || aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE || aStatus == NS_ERROR_REMOTE_XUL || aStatus == NS_ERROR_OFFLINE || NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) { DisplayLoadError(aStatus, url, nsnull, aChannel); } else if (aStatus == NS_ERROR_DOCUMENT_NOT_CACHED) { // Non-caching channels will simply return NS_ERROR_OFFLINE. // Caching channels would have to look at their flags to work // out which error to return. Or we can fix up the error here. if (!(mLoadType & LOAD_CMD_HISTORY)) aStatus = NS_ERROR_OFFLINE; DisplayLoadError(aStatus, url, nsnull, aChannel); } } // if we have a host return NS_OK; } //***************************************************************************** // nsDocShell: Content Viewer Management //***************************************************************************** NS_IMETHODIMP nsDocShell::EnsureContentViewer() { if (mContentViewer) return NS_OK; if (mIsBeingDestroyed) return NS_ERROR_FAILURE; NS_TIME_FUNCTION; nsIPrincipal* principal = nsnull; nsCOMPtr baseURI; nsCOMPtr piDOMWindow(do_QueryInterface(mScriptGlobal)); if (piDOMWindow) { principal = piDOMWindow->GetOpenerScriptPrincipal(); } if (!principal) { principal = GetInheritedPrincipal(false); nsCOMPtr parentItem; GetSameTypeParent(getter_AddRefs(parentItem)); if (parentItem) { nsCOMPtr domWin = do_GetInterface(GetAsSupports(this)); if (domWin) { nsCOMPtr parentContent = do_QueryInterface(domWin->GetFrameElementInternal()); if (parentContent) { baseURI = parentContent->GetBaseURI(); } } } } nsresult rv = CreateAboutBlankContentViewer(principal, baseURI); if (NS_SUCCEEDED(rv)) { nsCOMPtr doc(do_GetInterface(GetAsSupports(this))); NS_ASSERTION(doc, "Should have doc if CreateAboutBlankContentViewer " "succeeded!"); doc->SetIsInitialDocument(true); } return rv; } nsresult nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal, nsIURI* aBaseURI, bool aTryToSaveOldPresentation) { nsCOMPtr blankDoc; nsCOMPtr viewer; nsresult rv = NS_ERROR_FAILURE; /* mCreatingDocument should never be true at this point. However, it's a theoretical possibility. We want to know about it and make it stop, and this sounds like a job for an assertion. */ NS_ASSERTION(!mCreatingDocument, "infinite(?) loop creating document averted"); if (mCreatingDocument) return NS_ERROR_FAILURE; mCreatingDocument = true; // mContentViewer->PermitUnload may release |this| docshell. nsCOMPtr kungFuDeathGrip(this); if (mContentViewer) { // We've got a content viewer already. Make sure the user // permits us to discard the current document and replace it // with about:blank. And also ensure we fire the unload events // in the current document. // Make sure timing is created. Unload gets fired first for // document loaded from the session history. rv = MaybeInitTiming(); if (mTiming) { mTiming->NotifyBeforeUnload(); } bool okToUnload; rv = mContentViewer->PermitUnload(false, &okToUnload); if (NS_SUCCEEDED(rv) && !okToUnload) { // The user chose not to unload the page, interrupt the load. return NS_ERROR_FAILURE; } mSavingOldViewer = aTryToSaveOldPresentation && CanSavePresentation(LOAD_NORMAL, nsnull, nsnull); if (mTiming) { mTiming->NotifyUnloadAccepted(mCurrentURI); } // Make sure to blow away our mLoadingURI just in case. No loads // from inside this pagehide. mLoadingURI = nsnull; // Stop any in-progress loading, so that we don't accidentally trigger any // PageShow notifications from Embed() interrupting our loading below. Stop(); // Notify the current document that it is about to be unloaded!! // // It is important to fire the unload() notification *before* any state // is changed within the DocShell - otherwise, javascript will get the // wrong information :-( // (void) FirePageHideNotification(!mSavingOldViewer); } // Now make sure we don't think we're in the middle of firing unload after // this point. This will make us fire unload when the about:blank document // unloads... but that's ok, more or less. Would be nice if it fired load // too, of course. mFiredUnloadEvent = false; nsCOMPtr docFactory = nsContentUtils::FindInternalContentViewer("text/html"); if (docFactory) { // generate (about:blank) document to load docFactory->CreateBlankDocument(mLoadGroup, aPrincipal, getter_AddRefs(blankDoc)); if (blankDoc) { // Hack: set the base URI manually, since this document never // got Reset() with a channel. blankDoc->SetBaseURI(aBaseURI); blankDoc->SetContainer(static_cast(this)); // create a content viewer for us and the new document docFactory->CreateInstanceForDocument(NS_ISUPPORTS_CAST(nsIDocShell *, this), blankDoc, "view", getter_AddRefs(viewer)); // hook 'em up if (viewer) { viewer->SetContainer(static_cast(this)); Embed(viewer, "", 0); SetCurrentURI(blankDoc->GetDocumentURI(), nsnull, true, 0); rv = mIsBeingDestroyed ? NS_ERROR_NOT_AVAILABLE : NS_OK; } } } mCreatingDocument = false; // The transient about:blank viewer doesn't have a session history entry. SetHistoryEntry(&mOSHE, nsnull); return rv; } NS_IMETHODIMP nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal *aPrincipal) { return CreateAboutBlankContentViewer(aPrincipal, nsnull); } bool nsDocShell::CanSavePresentation(PRUint32 aLoadType, nsIRequest *aNewRequest, nsIDocument *aNewDocument) { if (!mOSHE) return false; // no entry to save into nsCOMPtr viewer; mOSHE->GetContentViewer(getter_AddRefs(viewer)); if (viewer) { NS_WARNING("mOSHE already has a content viewer!"); return false; } // Only save presentation for "normal" loads and link loads. Anything else // probably wants to refetch the page, so caching the old presentation // would be incorrect. if (aLoadType != LOAD_NORMAL && aLoadType != LOAD_HISTORY && aLoadType != LOAD_LINK && aLoadType != LOAD_STOP_CONTENT && aLoadType != LOAD_STOP_CONTENT_AND_REPLACE && aLoadType != LOAD_ERROR_PAGE) return false; // If the session history entry has the saveLayoutState flag set to false, // then we should not cache the presentation. bool canSaveState; mOSHE->GetSaveLayoutStateFlag(&canSaveState); if (!canSaveState) return false; // If the document is not done loading, don't cache it. nsCOMPtr pWin = do_QueryInterface(mScriptGlobal); if (!pWin || pWin->IsLoading()) return false; if (pWin->WouldReuseInnerWindow(aNewDocument)) return false; // Avoid doing the work of saving the presentation state in the case where // the content viewer cache is disabled. if (nsSHistory::GetMaxTotalViewers() == 0) return false; // Don't cache the content viewer if we're in a subframe and the subframe // pref is disabled. bool cacheFrames = Preferences::GetBool("browser.sessionhistory.cache_subframes", false); if (!cacheFrames) { nsCOMPtr root; GetSameTypeParent(getter_AddRefs(root)); if (root && root != this) { return false; // this is a subframe load } } // If the document does not want its presentation cached, then don't. nsCOMPtr doc = do_QueryInterface(pWin->GetExtantDocument()); if (!doc || !doc->CanSavePresentation(aNewRequest)) return false; return true; } void nsDocShell::ReattachEditorToWindow(nsISHEntry *aSHEntry) { NS_ASSERTION(!mEditorData, "Why reattach an editor when we already have one?"); NS_ASSERTION(aSHEntry && aSHEntry->HasDetachedEditor(), "Reattaching when there's not a detached editor."); if (mEditorData || !aSHEntry) return; mEditorData = aSHEntry->ForgetEditorData(); if (mEditorData) { #ifdef DEBUG nsresult rv = #endif mEditorData->ReattachToWindow(this); NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to reattach editing session"); } } void nsDocShell::DetachEditorFromWindow() { if (!mEditorData || mEditorData->WaitingForLoad()) { // If there's nothing to detach, or if the editor data is actually set // up for the _new_ page that's coming in, don't detach. return; } NS_ASSERTION(!mOSHE || !mOSHE->HasDetachedEditor(), "Detaching editor when it's already detached."); nsresult res = mEditorData->DetachFromWindow(); NS_ASSERTION(NS_SUCCEEDED(res), "Failed to detach editor"); if (NS_SUCCEEDED(res)) { // Make mOSHE hold the owning ref to the editor data. if (mOSHE) mOSHE->SetEditorData(mEditorData.forget()); else mEditorData = nsnull; } #ifdef DEBUG { bool isEditable; GetEditable(&isEditable); NS_ASSERTION(!isEditable, "Window is still editable after detaching editor."); } #endif // DEBUG } nsresult nsDocShell::CaptureState() { if (!mOSHE || mOSHE == mLSHE) { // No entry to save into, or we're replacing the existing entry. return NS_ERROR_FAILURE; } nsCOMPtr privWin = do_QueryInterface(mScriptGlobal); if (!privWin) return NS_ERROR_FAILURE; nsCOMPtr windowState; nsresult rv = privWin->SaveWindowState(getter_AddRefs(windowState)); NS_ENSURE_SUCCESS(rv, rv); #ifdef DEBUG_PAGE_CACHE nsCOMPtr uri; mOSHE->GetURI(getter_AddRefs(uri)); nsCAutoString spec; if (uri) uri->GetSpec(spec); printf("Saving presentation into session history\n"); printf(" SH URI: %s\n", spec.get()); #endif rv = mOSHE->SetWindowState(windowState); NS_ENSURE_SUCCESS(rv, rv); // Suspend refresh URIs and save off the timer queue rv = mOSHE->SetRefreshURIList(mSavedRefreshURIList); NS_ENSURE_SUCCESS(rv, rv); // Capture the current content viewer bounds. if (mContentViewer) { nsIntRect bounds; mContentViewer->GetBounds(bounds); rv = mOSHE->SetViewerBounds(bounds); NS_ENSURE_SUCCESS(rv, rv); } // Capture the docshell hierarchy. mOSHE->ClearChildShells(); PRInt32 childCount = mChildList.Count(); for (PRInt32 i = 0; i < childCount; ++i) { nsCOMPtr childShell = do_QueryInterface(ChildAt(i)); NS_ASSERTION(childShell, "null child shell"); mOSHE->AddChildShell(childShell); } return NS_OK; } NS_IMETHODIMP nsDocShell::RestorePresentationEvent::Run() { if (mDocShell && NS_FAILED(mDocShell->RestoreFromHistory())) NS_WARNING("RestoreFromHistory failed"); return NS_OK; } NS_IMETHODIMP nsDocShell::BeginRestore(nsIContentViewer *aContentViewer, bool aTop) { nsresult rv; if (!aContentViewer) { rv = EnsureContentViewer(); NS_ENSURE_SUCCESS(rv, rv); aContentViewer = mContentViewer; } // Dispatch events for restoring the presentation. We try to simulate // the progress notifications loading the document would cause, so we add // the document's channel to the loadgroup to initiate stateChange // notifications. nsCOMPtr domDoc; aContentViewer->GetDOMDocument(getter_AddRefs(domDoc)); nsCOMPtr doc = do_QueryInterface(domDoc); if (doc) { nsIChannel *channel = doc->GetChannel(); if (channel) { mEODForCurrentDocument = false; mIsRestoringDocument = true; mLoadGroup->AddRequest(channel, nsnull); mIsRestoringDocument = false; } } if (!aTop) { // This point corresponds to us having gotten OnStartRequest or // STATE_START, so do the same thing that CreateContentViewer does at // this point to ensure that unload/pagehide events for this document // will fire when it's unloaded again. mFiredUnloadEvent = false; // For non-top frames, there is no notion of making sure that the // previous document is in the domwindow when STATE_START notifications // happen. We can just call BeginRestore for all of the child shells // now. rv = BeginRestoreChildren(); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } nsresult nsDocShell::BeginRestoreChildren() { PRInt32 n = mChildList.Count(); for (PRInt32 i = 0; i < n; ++i) { nsCOMPtr child = do_QueryInterface(ChildAt(i)); if (child) { nsresult rv = child->BeginRestore(nsnull, false); NS_ENSURE_SUCCESS(rv, rv); } } return NS_OK; } NS_IMETHODIMP nsDocShell::FinishRestore() { // First we call finishRestore() on our children. In the simulated load, // all of the child frames finish loading before the main document. PRInt32 n = mChildList.Count(); for (PRInt32 i = 0; i < n; ++i) { nsCOMPtr child = do_QueryInterface(ChildAt(i)); if (child) { child->FinishRestore(); } } if (mOSHE && mOSHE->HasDetachedEditor()) { ReattachEditorToWindow(mOSHE); } nsCOMPtr doc = do_GetInterface(GetAsSupports(this)); if (doc) { // Finally, we remove the request from the loadgroup. This will // cause onStateChange(STATE_STOP) to fire, which will fire the // pageshow event to the chrome. nsIChannel *channel = doc->GetChannel(); if (channel) { mIsRestoringDocument = true; mLoadGroup->RemoveRequest(channel, nsnull, NS_OK); mIsRestoringDocument = false; } } return NS_OK; } NS_IMETHODIMP nsDocShell::GetRestoringDocument(bool *aRestoring) { *aRestoring = mIsRestoringDocument; return NS_OK; } nsresult nsDocShell::RestorePresentation(nsISHEntry *aSHEntry, bool *aRestoring) { NS_ASSERTION(mLoadType & LOAD_CMD_HISTORY, "RestorePresentation should only be called for history loads"); nsCOMPtr viewer; aSHEntry->GetContentViewer(getter_AddRefs(viewer)); #ifdef DEBUG_PAGE_CACHE nsCOMPtr uri; aSHEntry->GetURI(getter_AddRefs(uri)); nsCAutoString spec; if (uri) uri->GetSpec(spec); #endif *aRestoring = false; if (!viewer) { #ifdef DEBUG_PAGE_CACHE printf("no saved presentation for uri: %s\n", spec.get()); #endif return NS_OK; } // We need to make sure the content viewer's container is this docshell. // In subframe navigation, it's possible for the docshell that the // content viewer was originally loaded into to be replaced with a // different one. We don't currently support restoring the presentation // in that case. nsCOMPtr container; viewer->GetContainer(getter_AddRefs(container)); if (!::SameCOMIdentity(container, GetAsSupports(this))) { #ifdef DEBUG_PAGE_CACHE printf("No valid container, clearing presentation\n"); #endif aSHEntry->SetContentViewer(nsnull); return NS_ERROR_FAILURE; } NS_ASSERTION(mContentViewer != viewer, "Restoring existing presentation"); #ifdef DEBUG_PAGE_CACHE printf("restoring presentation from session history: %s\n", spec.get()); #endif SetHistoryEntry(&mLSHE, aSHEntry); // Add the request to our load group. We do this before swapping out // the content viewers so that consumers of STATE_START can access // the old document. We only deal with the toplevel load at this time -- // to be consistent with normal document loading, subframes cannot start // loading until after data arrives, which is after STATE_START completes. BeginRestore(viewer, true); // Post an event that will remove the request after we've returned // to the event loop. This mimics the way it is called by nsIChannel // implementations. // Revoke any pending restore (just in case) NS_ASSERTION(!mRestorePresentationEvent.IsPending(), "should only have one RestorePresentationEvent"); mRestorePresentationEvent.Revoke(); nsRefPtr evt = new RestorePresentationEvent(this); nsresult rv = NS_DispatchToCurrentThread(evt); if (NS_SUCCEEDED(rv)) { mRestorePresentationEvent = evt.get(); // The rest of the restore processing will happen on our event // callback. *aRestoring = true; } return rv; } nsresult nsDocShell::RestoreFromHistory() { mRestorePresentationEvent.Forget(); // This section of code follows the same ordering as CreateContentViewer. if (!mLSHE) return NS_ERROR_FAILURE; nsCOMPtr viewer; mLSHE->GetContentViewer(getter_AddRefs(viewer)); if (!viewer) return NS_ERROR_FAILURE; if (mSavingOldViewer) { // We determined that it was safe to cache the document presentation // at the time we initiated the new load. We need to check whether // it's still safe to do so, since there may have been DOM mutations // or new requests initiated. nsCOMPtr domDoc; viewer->GetDOMDocument(getter_AddRefs(domDoc)); nsCOMPtr doc = do_QueryInterface(domDoc); nsIRequest *request = nsnull; if (doc) request = doc->GetChannel(); mSavingOldViewer = CanSavePresentation(mLoadType, request, doc); } nsCOMPtr oldMUDV( do_QueryInterface(mContentViewer)); nsCOMPtr newMUDV( do_QueryInterface(viewer)); PRInt32 minFontSize = 0; float textZoom = 1.0f; float pageZoom = 1.0f; bool styleDisabled = false; if (oldMUDV && newMUDV) { oldMUDV->GetMinFontSize(&minFontSize); oldMUDV->GetTextZoom(&textZoom); oldMUDV->GetFullZoom(&pageZoom); oldMUDV->GetAuthorStyleDisabled(&styleDisabled); } // Protect against mLSHE going away via a load triggered from // pagehide or unload. nsCOMPtr origLSHE = mLSHE; // Make sure to blow away our mLoadingURI just in case. No loads // from inside this pagehide. mLoadingURI = nsnull; // Notify the old content viewer that it's being hidden. FirePageHideNotification(!mSavingOldViewer); // If mLSHE was changed as a result of the pagehide event, then // something else was loaded. Don't finish restoring. if (mLSHE != origLSHE) return NS_OK; // Set mFiredUnloadEvent = false so that the unload handler for the // *new* document will fire. mFiredUnloadEvent = false; mURIResultedInDocument = true; nsCOMPtr rootSH; GetRootSessionHistory(getter_AddRefs(rootSH)); if (rootSH) { nsCOMPtr hist = do_QueryInterface(rootSH); rootSH->GetIndex(&mPreviousTransIndex); hist->UpdateIndex(); rootSH->GetIndex(&mLoadedTransIndex); #ifdef DEBUG_PAGE_CACHE printf("Previous index: %d, Loaded index: %d\n\n", mPreviousTransIndex, mLoadedTransIndex); #endif } // Rather than call Embed(), we will retrieve the viewer from the session // history entry and swap it in. // XXX can we refactor this so that we can just call Embed()? PersistLayoutHistoryState(); nsresult rv; if (mContentViewer) { if (mSavingOldViewer && NS_FAILED(CaptureState())) { if (mOSHE) { mOSHE->SyncPresentationState(); } mSavingOldViewer = false; } } mSavedRefreshURIList = nsnull; // In cases where we use a transient about:blank viewer between loads, // we never show the transient viewer, so _its_ previous viewer is never // unhooked from the view hierarchy. Destroy any such previous viewer now, // before we grab the root view sibling, so that we don't grab a view // that's about to go away. if (mContentViewer) { nsCOMPtr previousViewer; mContentViewer->GetPreviousViewer(getter_AddRefs(previousViewer)); if (previousViewer) { mContentViewer->SetPreviousViewer(nsnull); previousViewer->Destroy(); } } // Save off the root view's parent and sibling so that we can insert the // new content viewer's root view at the same position. Also save the // bounds of the root view's widget. nsIView *rootViewSibling = nsnull, *rootViewParent = nsnull; nsIntRect newBounds(0, 0, 0, 0); nsCOMPtr oldPresShell; nsDocShell::GetPresShell(getter_AddRefs(oldPresShell)); if (oldPresShell) { nsIViewManager *vm = oldPresShell->GetViewManager(); if (vm) { nsIView *oldRootView = vm->GetRootView(); if (oldRootView) { rootViewSibling = oldRootView->GetNextSibling(); rootViewParent = oldRootView->GetParent(); mContentViewer->GetBounds(newBounds); } } } // Transfer ownership to mContentViewer. By ensuring that either the // docshell or the session history, but not both, have references to the // content viewer, we prevent the viewer from being torn down after // Destroy() is called. if (mContentViewer) { mContentViewer->Close(mSavingOldViewer ? mOSHE.get() : nsnull); viewer->SetPreviousViewer(mContentViewer); } if (mOSHE && (!mContentViewer || !mSavingOldViewer)) { // We don't plan to save a viewer in mOSHE; tell it to drop // any other state it's holding. mOSHE->SyncPresentationState(); } // Order the mContentViewer setup just like Embed does. mContentViewer = nsnull; // Now that we're about to switch documents, forget all of our children. // Note that we cached them as needed up in CaptureState above. DestroyChildren(); mContentViewer.swap(viewer); // Grab all of the related presentation from the SHEntry now. // Clearing the viewer from the SHEntry will clear all of this state. nsCOMPtr windowState; mLSHE->GetWindowState(getter_AddRefs(windowState)); mLSHE->SetWindowState(nsnull); bool sticky; mLSHE->GetSticky(&sticky); nsCOMPtr domDoc; mContentViewer->GetDOMDocument(getter_AddRefs(domDoc)); nsCOMArray childShells; PRInt32 i = 0; nsCOMPtr child; while (NS_SUCCEEDED(mLSHE->ChildShellAt(i++, getter_AddRefs(child))) && child) { childShells.AppendObject(child); } // get the previous content viewer size nsIntRect oldBounds(0, 0, 0, 0); mLSHE->GetViewerBounds(oldBounds); // Restore the refresh URI list. The refresh timers will be restarted // when EndPageLoad() is called. nsCOMPtr refreshURIList; mLSHE->GetRefreshURIList(getter_AddRefs(refreshURIList)); // Reattach to the window object. rv = mContentViewer->Open(windowState, mLSHE); // Hack to keep nsDocShellEditorData alive across the // SetContentViewer(nsnull) call below. nsAutoPtr data(mLSHE->ForgetEditorData()); // Now remove it from the cached presentation. mLSHE->SetContentViewer(nsnull); mEODForCurrentDocument = false; mLSHE->SetEditorData(data.forget()); #ifdef DEBUG { nsCOMPtr refreshURIs; mLSHE->GetRefreshURIList(getter_AddRefs(refreshURIs)); nsCOMPtr childShell; mLSHE->ChildShellAt(0, getter_AddRefs(childShell)); NS_ASSERTION(!refreshURIs && !childShell, "SHEntry should have cleared presentation state"); } #endif // Restore the sticky state of the viewer. The viewer has set this state // on the history entry in Destroy() just before marking itself non-sticky, // to avoid teardown of the presentation. mContentViewer->SetSticky(sticky); NS_ENSURE_SUCCESS(rv, rv); // mLSHE is now our currently-loaded document. SetHistoryEntry(&mOSHE, mLSHE); // XXX special wyciwyg handling in Embed()? // We aren't going to restore any items from the LayoutHistoryState, // but we don't want them to stay around in case the page is reloaded. SetLayoutHistoryState(nsnull); // This is the end of our Embed() replacement mSavingOldViewer = false; mEODForCurrentDocument = false; // Tell the event loop to favor plevents over user events, see comments // in CreateContentViewer. if (++gNumberOfDocumentsLoading == 1) FavorPerformanceHint(true, NS_EVENT_STARVATION_DELAY_HINT); if (oldMUDV && newMUDV) { newMUDV->SetMinFontSize(minFontSize); newMUDV->SetTextZoom(textZoom); newMUDV->SetFullZoom(pageZoom); newMUDV->SetAuthorStyleDisabled(styleDisabled); } nsCOMPtr document = do_QueryInterface(domDoc); PRUint32 parentSuspendCount = 0; if (document) { nsCOMPtr parent; GetParent(getter_AddRefs(parent)); nsCOMPtr d = do_GetInterface(parent); if (d) { if (d->EventHandlingSuppressed()) { document->SuppressEventHandling(d->EventHandlingSuppressed()); } nsCOMPtr parentWindow = d->GetWindow(); if (parentWindow) { parentSuspendCount = parentWindow->TimeoutSuspendCount(); } } // Use the uri from the mLSHE we had when we entered this function // (which need not match the document's URI if anchors are involved), // since that's the history entry we're loading. Note that if we use // origLSHE we don't have to worry about whether the entry in question // is still mLSHE or whether it's now mOSHE. nsCOMPtr uri; origLSHE->GetURI(getter_AddRefs(uri)); SetCurrentURI(uri, document->GetChannel(), true, 0); } // This is the end of our CreateContentViewer() replacement. // Now we simulate a load. First, we restore the state of the javascript // window object. nsCOMPtr privWin = do_GetInterface(static_cast(this)); NS_ASSERTION(privWin, "could not get nsPIDOMWindow interface"); rv = privWin->RestoreWindowState(windowState); NS_ENSURE_SUCCESS(rv, rv); // Now, dispatch a title change event which would happen as the // is parsed. document->NotifyPossibleTitleChange(false); // Now we simulate appending child docshells for subframes. for (i = 0; i < childShells.Count(); ++i) { nsIDocShellTreeItem *childItem = childShells.ObjectAt(i); nsCOMPtr childShell = do_QueryInterface(childItem); // Make sure to not clobber the state of the child. Since AddChild // always clobbers it, save it off first. bool allowPlugins; childShell->GetAllowPlugins(&allowPlugins); bool allowJavascript; childShell->GetAllowJavascript(&allowJavascript); bool allowRedirects; childShell->GetAllowMetaRedirects(&allowRedirects); bool allowSubframes; childShell->GetAllowSubframes(&allowSubframes); bool allowImages; childShell->GetAllowImages(&allowImages); bool allowDNSPrefetch; childShell->GetAllowDNSPrefetch(&allowDNSPrefetch); // this.AddChild(child) calls child.SetDocLoaderParent(this), meaning // that the child inherits our state. Among other things, this means // that the child inherits our mIsActive and mInPrivateBrowsing, which // is what we want. AddChild(childItem); childShell->SetAllowPlugins(allowPlugins); childShell->SetAllowJavascript(allowJavascript); childShell->SetAllowMetaRedirects(allowRedirects); childShell->SetAllowSubframes(allowSubframes); childShell->SetAllowImages(allowImages); childShell->SetAllowDNSPrefetch(allowDNSPrefetch); rv = childShell->BeginRestore(nsnull, false); NS_ENSURE_SUCCESS(rv, rv); } nsCOMPtr shell; nsDocShell::GetPresShell(getter_AddRefs(shell)); nsIViewManager *newVM = shell ? shell->GetViewManager() : nsnull; nsIView *newRootView = newVM ? newVM->GetRootView() : nsnull; // Insert the new root view at the correct location in the view tree. if (rootViewParent) { nsIViewManager *parentVM = rootViewParent->GetViewManager(); if (parentVM && newRootView) { // InsertChild(parent, child, sib, true) inserts the child after // sib in content order, which is before sib in view order. BUT // when sib is null it inserts at the end of the the document // order, i.e., first in view order. But when oldRootSibling is // null, the old root as at the end of the view list --- last in // content order --- and we want to call InsertChild(parent, child, // nsnull, false) in that case. parentVM->InsertChild(rootViewParent, newRootView, rootViewSibling, rootViewSibling ? true : false); NS_ASSERTION(newRootView->GetNextSibling() == rootViewSibling, "error in InsertChild"); } } // If parent is suspended, increase suspension count. // This can't be done as early as event suppression since this // depends on docshell tree. if (parentSuspendCount) { privWin->SuspendTimeouts(parentSuspendCount, false); } // Now that all of the child docshells have been put into place, we can // restart the timers for the window and all of the child frames. privWin->ResumeTimeouts(); // Restore the refresh URI list. The refresh timers will be restarted // when EndPageLoad() is called. mRefreshURIList = refreshURIList; // Meta-refresh timers have been restarted for this shell, but not // for our children. Walk the child shells and restart their timers. PRInt32 n = mChildList.Count(); for (i = 0; i < n; ++i) { nsCOMPtr child = do_QueryInterface(ChildAt(i)); if (child) child->ResumeRefreshURIs(); } // Make sure this presentation is the same size as the previous // presentation. If this is not the same size we showed it at last time, // then we need to resize the widget. // XXXbryner This interacts poorly with Firefox's infobar. If the old // presentation had the infobar visible, then we will resize the new // presentation to that smaller size. However, firing the locationchanged // event will hide the infobar, which will immediately resize the window // back to the larger size. A future optimization might be to restore // the presentation at the "wrong" size, then fire the locationchanged // event and check whether the docshell's new size is the same as the // cached viewer size (skipping the resize if they are equal). if (newRootView) { if (!newBounds.IsEmpty() && !newBounds.IsEqualEdges(oldBounds)) { #ifdef DEBUG_PAGE_CACHE printf("resize widget(%d, %d, %d, %d)\n", newBounds.x, newBounds.y, newBounds.width, newBounds.height); #endif mContentViewer->SetBounds(newBounds); } else { nsIScrollableFrame *rootScrollFrame = shell->GetRootScrollFrameAsScrollableExternal(); if (rootScrollFrame) { rootScrollFrame->PostScrolledAreaEventForCurrentArea(); } } } // The FinishRestore call below can kill these, null them out so we don't // have invalid pointer lying around. newRootView = rootViewSibling = rootViewParent = nsnull; newVM = nsnull; // Simulate the completion of the load. nsDocShell::FinishRestore(); // Restart plugins, and paint the content. if (shell) { shell->Thaw(); newVM = shell->GetViewManager(); if (newVM) { // When we insert the root view above the resulting invalidate is // dropped because painting is suppressed in the presshell until we // call Thaw. So we issue the invalidate here. newRootView = newVM->GetRootView(); if (newRootView) { newVM->InvalidateView(newRootView); } } } return privWin->FireDelayedDOMEvents(); } NS_IMETHODIMP nsDocShell::CreateContentViewer(const char *aContentType, nsIRequest * request, nsIStreamListener ** aContentHandler) { *aContentHandler = nsnull; // Can we check the content type of the current content viewer // and reuse it without destroying it and re-creating it? NS_ASSERTION(mLoadGroup, "Someone ignored return from Init()?"); // Instantiate the content viewer object nsCOMPtr viewer; nsresult rv = NewContentViewerObj(aContentType, request, mLoadGroup, aContentHandler, getter_AddRefs(viewer)); if (NS_FAILED(rv)) return rv; // Notify the current document that it is about to be unloaded!! // // It is important to fire the unload() notification *before* any state // is changed within the DocShell - otherwise, javascript will get the // wrong information :-( // if (mSavingOldViewer) { // We determined that it was safe to cache the document presentation // at the time we initiated the new load. We need to check whether // it's still safe to do so, since there may have been DOM mutations // or new requests initiated. nsCOMPtr domDoc; viewer->GetDOMDocument(getter_AddRefs(domDoc)); nsCOMPtr doc = do_QueryInterface(domDoc); mSavingOldViewer = CanSavePresentation(mLoadType, request, doc); } NS_ASSERTION(!mLoadingURI, "Re-entering unload?"); nsCOMPtr aOpenedChannel = do_QueryInterface(request); if (aOpenedChannel) { aOpenedChannel->GetURI(getter_AddRefs(mLoadingURI)); } FirePageHideNotification(!mSavingOldViewer); mLoadingURI = nsnull; // Set mFiredUnloadEvent = false so that the unload handler for the // *new* document will fire. mFiredUnloadEvent = false; // we've created a new document so go ahead and call // OnLoadingSite(), but don't fire OnLocationChange() // notifications before we've called Embed(). See bug 284993. mURIResultedInDocument = true; if (mLoadType == LOAD_ERROR_PAGE) { // We need to set the SH entry and our current URI here and not // at the moment we load the page. We want the same behavior // of Stop() as for a normal page load. See bug 514232 for details. // Revert mLoadType to load type to state the page load failed, // following function calls need it. mLoadType = mFailedLoadType; nsCOMPtr failedChannel = mFailedChannel; nsCOMPtr failedURI = mFailedURI; mFailedChannel = nsnull; mFailedURI = nsnull; // Create an shistory entry for the old load, if we have a channel if (failedChannel) { mURIResultedInDocument = true; OnLoadingSite(failedChannel, true, false); } else if (failedURI) { mURIResultedInDocument = true; OnNewURI(failedURI, nsnull, nsnull, mLoadType, true, false, false); } // Be sure to have a correct mLSHE, it may have been cleared by // EndPageLoad. See bug 302115. if (mSessionHistory && !mLSHE) { PRInt32 idx; mSessionHistory->GetRequestedIndex(&idx); if (idx == -1) mSessionHistory->GetIndex(&idx); nsCOMPtr entry; mSessionHistory->GetEntryAtIndex(idx, false, getter_AddRefs(entry)); mLSHE = do_QueryInterface(entry); } // Set our current URI SetCurrentURI(failedURI); mLoadType = LOAD_ERROR_PAGE; } bool onLocationChangeNeeded = OnLoadingSite(aOpenedChannel, false); // let's try resetting the load group if we need to... nsCOMPtr currentLoadGroup; NS_ENSURE_SUCCESS(aOpenedChannel-> GetLoadGroup(getter_AddRefs(currentLoadGroup)), NS_ERROR_FAILURE); if (currentLoadGroup != mLoadGroup) { nsLoadFlags loadFlags = 0; //Cancel any URIs that are currently loading... /// XXX: Need to do this eventually Stop(); // // Retarget the document to this loadgroup... // /* First attach the channel to the right loadgroup * and then remove from the old loadgroup. This * puts the notifications in the right order and * we don't null-out mLSHE in OnStateChange() for * all redirected urls */ aOpenedChannel->SetLoadGroup(mLoadGroup); // Mark the channel as being a document URI... aOpenedChannel->GetLoadFlags(&loadFlags); loadFlags |= nsIChannel::LOAD_DOCUMENT_URI; aOpenedChannel->SetLoadFlags(loadFlags); mLoadGroup->AddRequest(request, nsnull); if (currentLoadGroup) currentLoadGroup->RemoveRequest(request, nsnull, NS_BINDING_RETARGETED); // Update the notification callbacks, so that progress and // status information are sent to the right docshell... aOpenedChannel->SetNotificationCallbacks(this); } NS_ENSURE_SUCCESS(Embed(viewer, "", (nsISupports *) nsnull), NS_ERROR_FAILURE); mSavedRefreshURIList = nsnull; mSavingOldViewer = false; mEODForCurrentDocument = false; // if this document is part of a multipart document, // the ID can be used to distinguish it from the other parts. nsCOMPtr multiPartChannel(do_QueryInterface(request)); if (multiPartChannel) { nsCOMPtr shell; rv = GetPresShell(getter_AddRefs(shell)); if (NS_SUCCEEDED(rv) && shell) { nsIDocument *doc = shell->GetDocument(); if (doc) { PRUint32 partID; multiPartChannel->GetPartID(&partID); doc->SetPartID(partID); } } } // Give hint to native plevent dispatch mechanism. If a document // is loading the native plevent dispatch mechanism should favor // performance over normal native event dispatch priorities. if (++gNumberOfDocumentsLoading == 1) { // Hint to favor performance for the plevent notification mechanism. // We want the pages to load as fast as possible even if its means // native messages might be starved. FavorPerformanceHint(true, NS_EVENT_STARVATION_DELAY_HINT); } if (onLocationChangeNeeded) { FireOnLocationChange(this, request, mCurrentURI, 0); } return NS_OK; } nsresult nsDocShell::NewContentViewerObj(const char *aContentType, nsIRequest * request, nsILoadGroup * aLoadGroup, nsIStreamListener ** aContentHandler, nsIContentViewer ** aViewer) { nsCOMPtr aOpenedChannel = do_QueryInterface(request); nsCOMPtr docLoaderFactory = nsContentUtils::FindInternalContentViewer(aContentType); if (!docLoaderFactory) { return NS_ERROR_FAILURE; } // Now create an instance of the content viewer // nsLayoutDLF makes the determination if it should be a "view-source" instead of "view" nsresult rv = docLoaderFactory->CreateInstance("view", aOpenedChannel, aLoadGroup, aContentType, static_cast(this), nsnull, aContentHandler, aViewer); NS_ENSURE_SUCCESS(rv, rv); (*aViewer)->SetContainer(static_cast(this)); return NS_OK; } NS_IMETHODIMP nsDocShell::SetupNewViewer(nsIContentViewer * aNewViewer) { // // Copy content viewer state from previous or parent content viewer. // // The following logic is mirrored in nsHTMLDocument::StartDocumentLoad! // // Do NOT to maintain a reference to the old content viewer outside // of this "copying" block, or it will not be destroyed until the end of // this routine and all