/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ function nsBrowserStatusHandler() { this.init(); } nsBrowserStatusHandler.prototype = { // Stored Status, Link and Loading values status : "", defaultStatus : "", jsStatus : "", jsDefaultStatus : "", overLink : "", feeds : [], QueryInterface : function(aIID) { if (aIID.equals(Ci.nsIWebProgressListener) || aIID.equals(Ci.nsISupportsWeakReference) || aIID.equals(Ci.nsIXULBrowserWindow) || aIID.equals(Ci.nsISupports)) return this; throw Cr.NS_NOINTERFACE; }, init : function() { this.urlBar = document.getElementById("urlbar"); this.throbberElement = document.getElementById("navigator-throbber"); this.statusMeter = document.getElementById("statusbar-icon"); this.statusPanel = document.getElementById("statusbar-progresspanel"); this.stopButton = document.getElementById("stop-button"); this.stopMenu = document.getElementById("menuitem-stop"); this.stopContext = document.getElementById("context-stop"); this.statusTextField = document.getElementById("statusbar-display"); this.isImage = document.getElementById("isImage"); this.securityButton = document.getElementById("security-button"); this.evButton = document.getElementById("ev-button"); this.feedsMenu = document.getElementById("feedsMenu"); this.feedsButton = document.getElementById("feedsButton"); // Initialize the security button's state and tooltip text const nsIWebProgressListener = Ci.nsIWebProgressListener; this.onSecurityChange(null, null, nsIWebProgressListener.STATE_IS_INSECURE); }, destroy : function() { // XXXjag to avoid leaks :-/, see bug 60729 this.urlBar = null; this.throbberElement = null; this.statusMeter = null; this.statusPanel = null; this.stopButton = null; this.stopMenu = null; this.stopContext = null; this.statusTextField = null; this.isImage = null; this.securityButton = null; this.evButton = null; this.feedsButton = null; this.feedsMenu = null; }, // nsIXULBrowserWindow setJSStatus : function(status) { this.jsStatus = status; this.updateStatusField(); }, // nsIXULBrowserWindow setJSDefaultStatus : function(status) { this.jsDefaultStatus = status; this.updateStatusField(); }, setDefaultStatus : function(status) { this.defaultStatus = status; this.updateStatusField(); }, // nsIXULBrowserWindow setOverLink : function(link, context) { this.overLink = link; // clear out 'Done' (or other message) on first hover if (this.defaultStatus) this.defaultStatus = ""; this.updateStatusField(); if (link) this.statusTextField.setAttribute('crop', 'center'); else this.statusTextField.setAttribute('crop', 'end'); }, // nsIXULBrowserWindow // Called before links are navigated to to allow us to retarget them if needed. onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) { return originalTarget; }, updateStatusField : function() { var text = this.overLink || this.status || this.jsStatus || this.jsDefaultStatus || this.defaultStatus; // check the current value so we don't trigger an attribute change // and cause needless (slow!) UI updates if (this.statusTextField.label != text) this.statusTextField.label = text; }, /** * Returns true if |aMimeType| is text-based, false otherwise. * * @param aMimeType * The MIME type to check. * * If adding types to this function, please also check the similar * function in mozilla/toolkit/content/widgets/findbar.xml. */ mimeTypeIsTextBased : function(contentType) { return /^text\/|\+xml$/.test(contentType) || contentType == "application/x-javascript" || contentType == "application/javascript" || contentType == "application/xml" || contentType == "mozilla.application/cached-xul"; }, populateFeeds : function(popup) { // First clear out any old items while (popup.hasChildNodes()) popup.lastChild.remove(); for (var i = 0; i < this.feeds.length; i++) { var link = this.feeds[i]; var menuitem = document.createElement("menuitem"); menuitem.className = "menuitem-iconic bookmark-item"; menuitem.statusText = link.href; menuitem.setAttribute("label", link.title || link.href); popup.appendChild(menuitem); } }, onFeedAvailable : function(aLink) { this.feeds.push(aLink); this.feedsMenu.removeAttribute("disabled"); this.feedsButton.hidden = false; }, onLinkIconAvailable : function(aHref) { if (aHref && gProxyFavIcon && Services.prefs.getBoolPref("browser.chrome.site_icons")) { var browser = getBrowser(); if (browser.userTypedValue === null) gProxyFavIcon.setAttribute("src", aHref); } }, onProgressChange : function (aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) { if (aMaxTotalProgress > 0) { // This is highly optimized. Don't touch this code unless // you are intimately familiar with the cost of setting // attrs on XUL elements. -- hyatt var percentage = (aCurTotalProgress * 100) / aMaxTotalProgress; this.statusMeter.value = percentage; } }, onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus) { const nsIWebProgressListener = Ci.nsIWebProgressListener; const nsIChannel = Ci.nsIChannel; var ctype; if (aStateFlags & nsIWebProgressListener.STATE_START) { // This (thanks to the filter) is a network start or the first // stray request (the first request outside of the document load), // initialize the throbber and his friends. // Call start document load listeners (only if this is a network load) if (aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK && aRequest && aWebProgress.isTopLevel) this.startDocumentLoad(aRequest); if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) { // Show the progress meter this.statusPanel.collapsed = false; // Turn the throbber on. this.throbberElement.setAttribute("busy", "true"); } // XXX: These need to be based on window activity... this.stopButton.disabled = false; this.stopMenu.removeAttribute('disabled'); this.stopContext.removeAttribute('disabled'); } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) { if (aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) { if (aRequest) { if (aWebProgress.isTopLevel) this.endDocumentLoad(aRequest, aStatus); } } // This (thanks to the filter) is a network stop or the last // request stop outside of loading the document, stop throbbers // and progress bars and such if (aRequest) { var msg = ""; // Get the channel if the request is a channel if (aRequest instanceof nsIChannel) { var location = aRequest.URI.spec; if (location != "about:blank") { switch (aStatus) { case Cr.NS_BINDING_ABORTED: msg = gNavigatorBundle.getString("nv_stopped"); break; case Cr.NS_ERROR_NET_TIMEOUT: msg = gNavigatorBundle.getString("nv_timeout"); break; } } } // If msg is false then we did not have an error (channel may have // been null, in the case of a stray image load). if (!msg) { msg = gNavigatorBundle.getString("nv_done"); } this.status = ""; this.setDefaultStatus(msg); // Disable menu entries for images, enable otherwise if (content.document && this.mimeTypeIsTextBased(content.document.contentType)) this.isImage.removeAttribute('disabled'); else this.isImage.setAttribute('disabled', 'true'); } // Turn the progress meter and throbber off. this.statusPanel.collapsed = true; this.statusMeter.value = 0; // be sure to clear the progress bar this.throbberElement.removeAttribute("busy"); // XXX: These need to be based on window activity... // XXXjag: ? this.stopButton.disabled = true; this.stopMenu.setAttribute('disabled', 'true'); this.stopContext.setAttribute('disabled', 'true'); ZoomListeners.onLocationChange(getBrowser().currentURI); } }, onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags) { const nsIWebProgressListener = Ci.nsIWebProgressListener; if (gContextMenu) { // Optimise for the common case if (aWebProgress.isTopLevel) document.getElementById("contentAreaContextMenu").hidePopup(); else { for (var contextWindow = gContextMenu.target.ownerDocument.defaultView; contextWindow != contextWindow.parent; contextWindow = contextWindow.parent) { if (contextWindow == aWebProgress.DOMWindow) { document.getElementById("contentAreaContextMenu").hidePopup(); break; } } } } if (document.tooltipNode) { // Optimise for the common case if (aWebProgress.isTopLevel) { document.getElementById("aHTMLTooltip").hidePopup(); document.tooltipNode = null; } else { for (var tooltipWindow = document.tooltipNode.ownerDocument.defaultView; tooltipWindow != tooltipWindow.parent; tooltipWindow = tooltipWindow.parent) { if (tooltipWindow == aWebProgress.DOMWindow) { document.getElementById("aHTMLTooltip").hidePopup(); document.tooltipNode = null; break; } } } } // Hide the form invalid popup. if (gFormSubmitObserver.panel) { gFormSubmitObserver.panel.hidePopup(); } // XXX temporary hack for bug 104532. // Depends heavily on setOverLink implementation if (!aRequest) this.status = this.jsStatus = this.jsDefaultStatus = ""; this.setOverLink(""); // Disable menu entries for images, enable otherwise if (content.document && this.mimeTypeIsTextBased(content.document.contentType)) this.isImage.removeAttribute('disabled'); else this.isImage.setAttribute('disabled', 'true'); // We should probably not do this if the value has changed since the user // searched // Update urlbar only if a new page was loaded on the primary content area // Do not update urlbar if there was a subframe navigation var browser = getBrowser().selectedBrowser; if (aWebProgress.isTopLevel) { var userTypedValue = browser.userTypedValue; if (userTypedValue === null) { URLBarSetURI(aLocation, true); } else { this.urlBar.value = userTypedValue; SetPageProxyState("invalid", null); } BookmarkingUI.updateStarState(); this.feedsMenu.setAttribute("disabled", "true"); this.feedsButton.hidden = true; this.feeds = []; // When background tab comes into foreground or loading a new page // (aRequest set), might want to update zoom. if (FullZoom.updateBackgroundTabs || aRequest){ FullZoom.onLocationChange(getBrowser().currentURI, !aRequest, browser); ZoomListeners.onLocationChange(getBrowser().currentURI); } } UpdateBackForwardButtons(); UpdateStatusBarPopupIcon(); BrowserSearch.updateSearchButton(); }, onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage) { this.status = aMessage; this.updateStatusField(); }, onSecurityChange : function(aWebProgress, aRequest, aState) { const wpl = Ci.nsIWebProgressListener; const wpl_security_bits = wpl.STATE_IS_SECURE | wpl.STATE_IS_BROKEN | wpl.STATE_IS_INSECURE; var highlightSecure = Services.prefs.getBoolPref("browser.urlbar.highlight.secure"); /* aState is defined as a bitmask that may be extended in the future. * We filter out any unknown bits before testing for known values. */ switch (aState & wpl_security_bits) { case wpl.STATE_IS_SECURE: const nsISSLStatusProvider = Ci.nsISSLStatusProvider; var cert = getBrowser().securityUI.QueryInterface(nsISSLStatusProvider) .SSLStatus.serverCert; var issuerName = cert.issuerOrganization || cert.issuerCommonName || cert.issuerName; this.securityButton.setAttribute("tooltiptext", gNavigatorBundle.getFormattedString("securityButtonTooltipSecure", [issuerName])); this.securityButton.setAttribute("level", "high"); if (highlightSecure) this.urlBar.setAttribute("level", "high"); else this.urlBar.removeAttribute("level"); break; case wpl.STATE_IS_BROKEN: this.securityButton.setAttribute("tooltiptext", gNavigatorBundle.getString("securityButtonTooltipMixedContent")); this.securityButton.setAttribute("level", "broken"); if (highlightSecure) this.urlBar.setAttribute("level", "broken"); else this.urlBar.removeAttribute("level"); break; case wpl.STATE_IS_INSECURE: default: this.securityButton.setAttribute("tooltiptext", gNavigatorBundle.getString("securityButtonTooltipInsecure")); this.securityButton.removeAttribute("level"); this.urlBar.removeAttribute("level"); break; } if (aState & wpl.STATE_IDENTITY_EV_TOPLEVEL) { var organization = getBrowser().securityUI .QueryInterface(Ci.nsISSLStatusProvider) .SSLStatus .QueryInterface(Ci.nsISSLStatus) .serverCert.organization; this.securityButton.setAttribute("label", organization); this.evButton.setAttribute("tooltiptext", organization); this.evButton.hidden = false; } else { this.securityButton.removeAttribute("label"); this.evButton.hidden = true; } }, startDocumentLoad : function(aRequest) { var uri = aRequest.QueryInterface(Ci.nsIChannel).originalURI; // clear out search-engine data getBrowser().selectedBrowser.engines = null; // Set the URI now if it isn't already set, so that the user can tell which // site is loading. Only do this if user requested the load via chrome UI, // to minimise spoofing risk. if (!content.opener && !gURLBar.value && getWebNavigation().currentURI.spec == "about:blank") URLBarSetURI(uri); try { Services.obs.notifyObservers(content, "StartDocumentLoad", uri.spec); } catch (e) { } }, endDocumentLoad : function(aRequest, aStatus) { const nsIChannel = Ci.nsIChannel; var urlStr = aRequest.QueryInterface(nsIChannel).originalURI.spec; if (Components.isSuccessCode(aStatus)) dump("Document "+urlStr+" loaded successfully\n"); // per QA request else { // per QA request var e = new Components.Exception("", aStatus); var name = e.name; dump("Error loading URL "+urlStr+" : "+ Number(aStatus).toString(16)); if (name) dump(" ("+name+")"); dump('\n'); } var notification = Components.isSuccessCode(aStatus) ? "EndDocumentLoad" : "FailDocumentLoad"; try { Services.obs.notifyObservers(content, notification, urlStr); } catch (e) { } } }