diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml index f54dac919a8..746a8228da6 100644 --- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -63,10 +63,6 @@ Components.classes["@mozilla.org/docshell/urifixup;1"] .getService(Components.interfaces.nsIURIFixup); - - Components.classes["@mozilla.org/browser/favicon-service;1"] - .getService(Components.interfaces.nsIFaviconService); - Components.classes["@mozilla.org/autocomplete/search;1?name=history"] .getService(Components.interfaces.mozIPlacesAutoComplete); @@ -75,9 +71,6 @@ Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"] .getService(Components.interfaces.mozIPlacesAutoComplete); - - (Components.utils.import("resource://gre/modules/PlacesUtils.jsm", {})).PlacesUtils; - (Components.utils.import("resource://gre/modules/AppConstants.jsm", {})).AppConstants; @@ -866,7 +859,7 @@ let browser = this.getBrowserForTab(aTab); browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI; - if (aURI && this.mFaviconService) { + if (aURI) { if (!(aURI instanceof Ci.nsIURI)) { aURI = makeURI(aURI); } @@ -876,12 +869,7 @@ let loadingPrincipal = aLoadingPrincipal ? aLoadingPrincipal : Services.scriptSecurityManager.getSystemPrincipal(); - let loadType = PrivateBrowsingUtils.isWindowPrivate(window) - ? this.mFaviconService.FAVICON_LOAD_PRIVATE - : this.mFaviconService.FAVICON_LOAD_NON_PRIVATE; - - this.mFaviconService.setAndFetchFaviconForPage( - browser.currentURI, aURI, false, loadType, null, loadingPrincipal); + PlacesUIUtils.loadFavicon(browser, loadingPrincipal, aURI); } let sizedIconUrl = browser.mIconURL || ""; @@ -954,12 +942,9 @@ diff --git a/browser/components/feeds/FeedWriter.js b/browser/components/feeds/FeedWriter.js index a1279ec769f..1f808349ac9 100644 --- a/browser/components/feeds/FeedWriter.js +++ b/browser/components/feeds/FeedWriter.js @@ -214,15 +214,6 @@ FeedWriter.prototype = { element.setAttribute(attribute, uri); }, - __faviconService: null, - get _faviconService() { - if (!this.__faviconService) - this.__faviconService = Cc["@mozilla.org/browser/favicon-service;1"]. - getService(Ci.nsIFaviconService); - - return this.__faviconService; - }, - __bundle: null, get _bundle() { if (!this.__bundle) { @@ -1023,7 +1014,6 @@ FeedWriter.prototype = { prefs.removeObserver(PREF_AUDIO_SELECTED_APP, this); this._removeFeedFromCache(); - this.__faviconService = null; this.__bundle = null; this._feedURI = null; @@ -1164,46 +1154,6 @@ FeedWriter.prototype = { } }, - /** - * Sets the icon for the given web-reader item in the readers menu. - * The icon is fetched and stored through the favicon service. - * - * @param aReaderUrl - * the reader url. - * @param aMenuItem - * the reader item in the readers menulist. - * - * @note For privacy reasons we cannot set the image attribute directly - * to the icon url. See Bug 358878 for details. - */ - _setFaviconForWebReader: - function FW__setFaviconForWebReader(aReaderUrl, aMenuItem) { - let readerURI = makeURI(aReaderUrl); - if (!/^https?$/.test(readerURI.scheme)) { - // Don't try to get a favicon for non http(s) URIs. - return; - } - let faviconURI = makeURI(readerURI.prePath + "/favicon.ico"); - let self = this; - let usePrivateBrowsing = this._window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShell) - .QueryInterface(Ci.nsILoadContext) - .usePrivateBrowsing; - let nullPrincipal = Cc["@mozilla.org/nullprincipal;1"] - .createInstance(Ci.nsIPrincipal); - this._faviconService.setAndFetchFaviconForPage(readerURI, faviconURI, false, - usePrivateBrowsing ? this._faviconService.FAVICON_LOAD_PRIVATE - : this._faviconService.FAVICON_LOAD_NON_PRIVATE, - function (aURI, aDataLen, aData, aMimeType) { - if (aDataLen > 0) { - let dataURL = "data:" + aMimeType + ";base64," + - btoa(String.fromCharCode.apply(null, aData)); - aMenuItem.setAttribute('image', dataURL); - } - }, nullPrincipal); - }, - get _mm() { let mm = this._window.QueryInterface(Ci.nsIInterfaceRequestor). getInterface(Ci.nsIDocShell). diff --git a/browser/components/places/PlacesUIUtils.jsm b/browser/components/places/PlacesUIUtils.jsm index 22b03f4b685..1b01063c838 100644 --- a/browser/components/places/PlacesUIUtils.jsm +++ b/browser/components/places/PlacesUIUtils.jsm @@ -12,6 +12,7 @@ var Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Timer.jsm"); Cu.import("resource://gre/modules/PlacesUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", @@ -37,6 +38,11 @@ XPCOMUtils.defineLazyModuleGetter(this, "CloudSync", XPCOMUtils.defineLazyModuleGetter(this, "Weave", "resource://services-sync/main.js"); +const gInContentProcess = Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT; +const FAVICON_REQUEST_TIMEOUT = 60 * 1000; +// Map from windows to arrays of data about pending favicon loads. +let gFaviconLoadDataMap = new Map(); + // copied from utilityOverlay.js const TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab"; @@ -77,6 +83,149 @@ function IsLivemark(aItemId) { return self.ids.has(aItemId); } +let InternalFaviconLoader = { + /** + * This gets called for every inner window that is destroyed. + * In the parent process, we process the destruction ourselves. In the child process, + * we notify the parent which will then process it based on that message. + */ + observe(subject, topic, data) { + let innerWindowID = subject.QueryInterface(Ci.nsISupportsPRUint64).data; + this.onInnerDestroyed(innerWindowID); + }, + + /** + * Actually cancel the request, and clear the timeout for cancelling it. + */ + _cancelRequest({uri, innerWindowID, timerID, callback}, reason) { + // Break cycle + let request = callback.request; + delete callback.request; + // Ensure we don't time out. + clearTimeout(timerID); + try { + request.cancel(); + } catch (ex) { + Cu.reportError("When cancelling a request for " + uri.spec + " because " + reason + ", it was already canceled!"); + } + }, + + /** + * Called for every inner that gets destroyed, only in the parent process. + */ + onInnerDestroyed(innerID) { + for (let [window, loadDataForWindow] of gFaviconLoadDataMap) { + let newLoadDataForWindow = loadDataForWindow.filter(loadData => { + let innerWasDestroyed = loadData.innerWindowID == innerID; + if (innerWasDestroyed) { + this._cancelRequest(loadData, "the inner window was destroyed"); + } + // Keep the items whose inner is still alive. + return !innerWasDestroyed; + }); + // Map iteration with for...of is safe against modification, so + // now just replace the old value: + gFaviconLoadDataMap.set(window, newLoadDataForWindow); + } + }, + + /** + * Called when a toplevel chrome window unloads. We use this to tidy up after ourselves, + * avoid leaks, and cancel any remaining requests. The last part should in theory be + * handled by the inner-window-destroyed handlers. We clean up just to be on the safe side. + */ + onUnload(win) { + let loadDataForWindow = gFaviconLoadDataMap.get(win); + if (loadDataForWindow) { + for (let loadData of loadDataForWindow) { + this._cancelRequest(loadData, "the chrome window went away"); + } + } + gFaviconLoadDataMap.delete(win); + }, + + /** + * Create a function to use as a nsIFaviconDataCallback, so we can remove cancelling + * information when the request succeeds. Note that right now there are some edge-cases, + * such as about: URIs with chrome:// favicons where the success callback is not invoked. + * This is OK: we will 'cancel' the request after the timeout (or when the window goes + * away) but that will be a no-op in such cases. + */ + _makeCompletionCallback(win, id) { + return { + onComplete(uri) { + let loadDataForWindow = gFaviconLoadDataMap.get(win); + if (loadDataForWindow) { + let itemIndex = loadDataForWindow.findIndex(loadData => { + return loadData.innerWindowID == id && + loadData.uri.equals(uri) && + loadData.callback.request == this.request; + }); + if (itemIndex != -1) { + let loadData = loadDataForWindow[itemIndex]; + clearTimeout(loadData.timerID); + loadDataForWindow.splice(itemIndex, 1); + } + } + delete this.request; + }, + }; + }, + + ensureInitialized() { + if (this._initialized) { + return; + } + this._initialized = true; + + Services.obs.addObserver(this, "inner-window-destroyed", false); + Services.ppmm.addMessageListener("Toolkit:inner-window-destroyed", msg => { + this.onInnerDestroyed(msg.data); + }); + }, + + loadFavicon(browser, principal, uri) { + this.ensureInitialized(); + let win = browser.ownerDocument.defaultView; + if (!gFaviconLoadDataMap.has(win)) { + gFaviconLoadDataMap.set(win, []); + let unloadHandler = event => { + let doc = event.target; + let eventWin = doc.defaultview; + if (win == win.top && doc.documentURI != "about:blank") { + win.removeEventListener("unload", unloadHandler); + this.onUnload(win); + } + }; + win.addEventListener("unload", unloadHandler, true); + } + + // First we do the actual setAndFetch call: + let {innerWindowID, currentURI} = browser; + let loadType = PrivateBrowsingUtils.isWindowPrivate(win) + ? PlacesUtils.favicons.FAVICON_LOAD_PRIVATE + : PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE; + let callback = this._makeCompletionCallback(win, innerWindowID); + let request = PlacesUtils.favicons.setAndFetchFaviconForPage(currentURI, uri, false, + loadType, callback, principal); + + // Now register the result so we can cancel it if/when necessary. + if (!request) { + // The favicon service can return with success but no-op (and leave request + // as null) if the icon is the same as the page (e.g. for images) or if it is + // the favicon for an error page. In this case, we do not need to do anything else. + return; + } + callback.request = request; + let loadData = {innerWindowID, uri, callback}; + loadData.timerID = setTimeout(() => { + this._cancelRequest(loadData, "it timed out"); + }, FAVICON_REQUEST_TIMEOUT); + let loadDataForWindow = gFaviconLoadDataMap.get(win); + loadDataForWindow.push(loadData); + }, +}; + this.PlacesUIUtils = { ORGANIZER_LEFTPANE_VERSION: 7, ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder", @@ -481,6 +630,19 @@ this.PlacesUIUtils = { return RecentWindow.getMostRecentBrowserWindow(); }, + /** + * set and fetch a favicon. Can only be used from the parent process. + * @param browser {Browser} The XUL browser element for which we're fetching a favicon. + * @param principal {Principal} The loading principal to use for the fetch. + * @param uri {URI} The URI to fetch. + */ + loadFavicon(browser, principal, uri) { + if (gInContentProcess) { + throw new Error("Can't track loads from within the child process!"); + } + InternalFaviconLoader.loadFavicon(browser, principal, uri); + }, + /** * Returns the closet ancestor places view for the given DOM node * @param aNode diff --git a/toolkit/components/places/AsyncFaviconHelpers.cpp b/toolkit/components/places/AsyncFaviconHelpers.cpp index 73a338d80dd..9abafba5a2f 100644 --- a/toolkit/components/places/AsyncFaviconHelpers.cpp +++ b/toolkit/components/places/AsyncFaviconHelpers.cpp @@ -605,7 +605,9 @@ AsyncFetchAndSetIconForPage::AsyncOnChannelRedirect( , nsIAsyncVerifyRedirectCallback *cb ) { - (void)cb->OnRedirectVerifyCallback(NS_OK); + // If we've been canceled, stop the redirect with NS_BINDING_ABORTED, and + // handle the cancel on the original channel. + (void)cb->OnRedirectVerifyCallback(mCanceled ? NS_BINDING_ABORTED : NS_OK); return NS_OK; } diff --git a/toolkit/content/process-content.js b/toolkit/content/process-content.js index e20c71fbf6f..e994b769b13 100644 --- a/toolkit/content/process-content.js +++ b/toolkit/content/process-content.js @@ -12,9 +12,21 @@ var { classes: Cc, interfaces: Ci, utils: Cu } = Components; Cu.import("resource://gre/modules/RemotePageManager.jsm"); Cu.import("resource://gre/modules/Services.jsm"); +const gInContentProcess = Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT; + Services.cpmm.addMessageListener("gmp-plugin-crash", msg => { let gmpservice = Cc["@mozilla.org/gecko-media-plugin-service;1"] .getService(Ci.mozIGeckoMediaPluginService); gmpservice.RunPluginCrashCallbacks(msg.data.pluginID, msg.data.pluginName); }); + +// Forward inner-window-destroyed notifications with the inner window ID, +// so that code in the parent that should do something when content +// windows go away can do it +if (gInContentProcess) { + Services.obs.addObserver((subject, topic, data) => { + let innerWindowID = subject.QueryInterface(Ci.nsISupportsPRUint64).data; + Services.cpmm.sendAsyncMessage("Toolkit:inner-window-destroyed", innerWindowID); + }, "inner-window-destroyed", false); +}