author | Wes Kocher <wkocher@mozilla.com> |
Mon, 20 Mar 2017 18:19:52 -0700 | |
changeset 348535 | 5fe5dcf1c10a4523ba3f0a20295551462c2dae11 |
parent 348457 | f897fc6fb17f13251963aa4236e541064e9881d3 (current diff) |
parent 348534 | c9812b88b9ed45fa80e0c45a8e80cf89c66d4c8f (diff) |
child 348536 | ca4ae502156eaea6fffb296bb9c3b3930af8ab58 |
child 348553 | 667bc38a3624e8f11193a5cf382d0bb57d834e96 |
child 348573 | 942dd08a9878bf57a32b63c33ba57284211c8c37 |
push id | 31526 |
push user | kwierso@gmail.com |
push date | Tue, 21 Mar 2017 01:20:02 +0000 |
treeherder | mozilla-central@5fe5dcf1c10a [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 55.0a1 |
first release with | nightly mac
5fe5dcf1c10a
/
55.0a1
/
20170321030211
/
files
nightly win32
5fe5dcf1c10a
/
55.0a1
/
20170321030211
/
files
nightly win64
5fe5dcf1c10a
/
55.0a1
/
20170321030211
/
files
nightly linux32
nightly linux64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly mac
55.0a1
/
20170321030211
/
pushlog to previous
nightly win32
55.0a1
/
20170321030211
/
pushlog to previous
nightly win64
55.0a1
/
20170321030211
/
pushlog to previous
|
browser/base/content/browser.js | file | annotate | diff | comparison | revisions | |
dom/base/nsDocument.cpp | file | annotate | diff | comparison | revisions |
--- a/accessible/base/nsAccCache.h +++ b/accessible/base/nsAccCache.h @@ -20,26 +20,9 @@ UnbindCacheEntriesFromDocument( for (auto iter = aCache.Iter(); !iter.Done(); iter.Next()) { T* accessible = iter.Data(); MOZ_ASSERT(accessible && !accessible->IsDefunct()); accessible->Document()->UnbindFromDocument(accessible); iter.Remove(); } } -/** - * Clear the cache and shutdown the accessibles. - */ -template <class T> -static void -ClearCache(nsRefPtrHashtable<nsPtrHashKey<const void>, T>& aCache) -{ - for (auto iter = aCache.Iter(); !iter.Done(); iter.Next()) { - T* accessible = iter.Data(); - MOZ_ASSERT(accessible); - if (accessible && !accessible->IsDefunct()) { - accessible->Shutdown(); - } - iter.Remove(); - } -} - #endif
--- a/accessible/generic/DocAccessible.cpp +++ b/accessible/generic/DocAccessible.cpp @@ -478,17 +478,27 @@ DocAccessible::Shutdown() mVirtualCursor = nullptr; } mPresShell->SetDocAccessible(nullptr); mPresShell = nullptr; // Avoid reentrancy mDependentIDsHash.Clear(); mNodeToAccessibleMap.Clear(); - ClearCache(mAccessibleCache); + + for (auto iter = mAccessibleCache.Iter(); !iter.Done(); iter.Next()) { + Accessible* accessible = iter.Data(); + MOZ_ASSERT(accessible); + if (accessible && !accessible->IsDefunct()) { + // Unlink parent to avoid its cleaning overhead in shutdown. + accessible->mParent = nullptr; + accessible->Shutdown(); + } + iter.Remove(); + } HyperTextAccessibleWrap::Shutdown(); GetAccService()->NotifyOfDocumentShutdown(this, kungFuDeathGripDoc); } nsIFrame* DocAccessible::GetFrame() const
--- a/browser/base/content/browser-feeds.js +++ b/browser/base/content/browser-feeds.js @@ -1,18 +1,143 @@ /* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ +XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", + "resource://gre/modules/DeferredTask.jsm"); + +const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed"; +const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed"; +const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed"; + +const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI"; + +const PREF_SELECTED_APP = "browser.feeds.handlers.application"; +const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice"; +const PREF_SELECTED_ACTION = "browser.feeds.handler"; +const PREF_SELECTED_READER = "browser.feeds.handler.default"; + +const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application"; +const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice"; +const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler"; +const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default"; + +const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application"; +const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice"; +const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler"; +const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default"; + +const PREF_UPDATE_DELAY = 2000; + +const SETTABLE_PREFS = new Set([ + PREF_VIDEO_SELECTED_ACTION, + PREF_AUDIO_SELECTED_ACTION, + PREF_SELECTED_ACTION, + PREF_VIDEO_SELECTED_READER, + PREF_AUDIO_SELECTED_READER, + PREF_SELECTED_READER, + PREF_VIDEO_SELECTED_WEB, + PREF_AUDIO_SELECTED_WEB, + PREF_SELECTED_WEB +]); + +const EXECUTABLE_PREFS = new Set([ + PREF_SELECTED_APP, + PREF_VIDEO_SELECTED_APP, + PREF_AUDIO_SELECTED_APP +]); + +const VALID_ACTIONS = new Set(["ask", "reader", "bookmarks"]); +const VALID_READERS = new Set(["web", "client", "default", "bookmarks"]); + +XPCOMUtils.defineLazyPreferenceGetter(this, "SHOULD_LOG", + "feeds.log", false); + +function LOG(str) { + if (SHOULD_LOG) + dump("*** Feeds: " + str + "\n"); +} + +function getPrefActionForType(t) { + switch (t) { + case Ci.nsIFeed.TYPE_VIDEO: + return PREF_VIDEO_SELECTED_ACTION; + + case Ci.nsIFeed.TYPE_AUDIO: + return PREF_AUDIO_SELECTED_ACTION; + + default: + return PREF_SELECTED_ACTION; + } +} + +function getPrefReaderForType(t) { + switch (t) { + case Ci.nsIFeed.TYPE_VIDEO: + return PREF_VIDEO_SELECTED_READER; + + case Ci.nsIFeed.TYPE_AUDIO: + return PREF_AUDIO_SELECTED_READER; + + default: + return PREF_SELECTED_READER; + } +} + +function getPrefWebForType(t) { + switch (t) { + case Ci.nsIFeed.TYPE_VIDEO: + return PREF_VIDEO_SELECTED_WEB; + + case Ci.nsIFeed.TYPE_AUDIO: + return PREF_AUDIO_SELECTED_WEB; + + default: + return PREF_SELECTED_WEB; + } +} + +function getPrefAppForType(t) { + switch (t) { + case Ci.nsIFeed.TYPE_VIDEO: + return PREF_VIDEO_SELECTED_APP; + + case Ci.nsIFeed.TYPE_AUDIO: + return PREF_AUDIO_SELECTED_APP; + + default: + return PREF_SELECTED_APP; + } +} + +/** + * Maps a feed type to a maybe-feed mimetype. + */ +function getMimeTypeForFeedType(aFeedType) { + switch (aFeedType) { + case Ci.nsIFeed.TYPE_VIDEO: + return TYPE_MAYBE_VIDEO_FEED; + + case Ci.nsIFeed.TYPE_AUDIO: + return TYPE_MAYBE_AUDIO_FEED; + + default: + return TYPE_MAYBE_FEED; + } +} + /** * The Feed Handler object manages discovery of RSS/ATOM feeds in web pages * and shows UI when they are discovered. */ var FeedHandler = { + _prefChangeCallback: null, + /** Called when the user clicks on the Subscribe to This Page... menu item, * or when the user clicks the feed button when the page contains multiple * feeds. * Builds a menu of unique feeds associated with the page, and if there * is only one, shows the feed inline in the browser window. * @param container * The feed list container (menupopup or subview) to be populated. * @param isSubview @@ -190,17 +315,18 @@ var FeedHandler = { } catch (e) {} } break; } return file.leafName; }, - chooseClientApp(aTitle, aPrefName, aBrowser) { + _chooseClientApp(aTitle, aTypeName, aBrowser) { + const prefName = getPrefAppForType(aTypeName); let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); fp.init(window, aTitle, Ci.nsIFilePicker.modeOpen); fp.appendFilters(Ci.nsIFilePicker.filterApps); fp.open((aResult) => { if (aResult == Ci.nsIFilePicker.returnOK) { let selectedApp = fp.file; @@ -217,17 +343,17 @@ var FeedHandler = { appName = AppConstants.MOZ_MACBUNDLE_NAME; break; default: appName = AppConstants.MOZ_APP_NAME + "-bin"; break; } if (fp.file.leafName != appName) { - Services.prefs.setComplexValue(aPrefName, Ci.nsILocalFile, selectedApp); + Services.prefs.setComplexValue(prefName, Ci.nsILocalFile, selectedApp); aBrowser.messageManager.sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem", { name: this._getFileDisplayName(selectedApp), type: "SelectedAppMenuItem" }); } } } }); @@ -272,76 +398,246 @@ var FeedHandler = { // nsIProcess instance let p = Cc["@mozilla.org/process/util;1"] .createInstance(Ci.nsIProcess); p.init(clientApp); p.run(false, [aSpec], 1); } }, + // nsISupports + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsISupportsWeakReference]), + + init() { window.messageManager.addMessageListener("FeedWriter:ChooseClientApp", this); - window.messageManager.addMessageListener("FeedWriter:RequestClientAppName", this); - window.messageManager.addMessageListener("FeedWriter:SetFeedCharPref", this); - window.messageManager.addMessageListener("FeedWriter:SetFeedComplexString", this); + window.messageManager.addMessageListener("FeedWriter:GetSubscriptionUI", this); + window.messageManager.addMessageListener("FeedWriter:SetFeedPrefsAndSubscribe", this); window.messageManager.addMessageListener("FeedWriter:ShownFirstRun", this); Services.ppmm.addMessageListener("FeedConverter:ExecuteClientApp", this); + + const prefs = Services.prefs; + prefs.addObserver(PREF_SELECTED_ACTION, this, true); + prefs.addObserver(PREF_SELECTED_READER, this, true); + prefs.addObserver(PREF_SELECTED_WEB, this, true); + prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, true); + prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, true); + prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, true); + prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, true); + prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, true); + prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, true); }, uninit() { Services.ppmm.removeMessageListener("FeedConverter:ExecuteClientApp", this); + + this._prefChangeCallback = null; + }, + + // nsIObserver + observe(subject, topic, data) { + if (topic == "nsPref:changed") { + LOG(`Pref changed ${data}`) + if (this._prefChangeCallback) { + this._prefChangeCallback.disarm(); + } + // Multiple prefs are set at the same time, debounce to reduce noise + // This can happen in one feed and we want to message all feed pages + this._prefChangeCallback = new DeferredTask(() => { + this._prefChanged(data); + }, PREF_UPDATE_DELAY); + this._prefChangeCallback.arm(); + } + }, + + _prefChanged(prefName) { + // Don't observe for PREF_*SELECTED_APP as user likely just picked one + // That is also handled by SetApplicationLauncherMenuItem call + // Rather than the others which happen on subscription + switch (prefName) { + case PREF_SELECTED_READER: + case PREF_SELECTED_WEB: + case PREF_VIDEO_SELECTED_READER: + case PREF_VIDEO_SELECTED_WEB: + case PREF_AUDIO_SELECTED_READER: + case PREF_AUDIO_SELECTED_WEB: + case PREF_SELECTED_ACTION: + case PREF_VIDEO_SELECTED_ACTION: + case PREF_AUDIO_SELECTED_ACTION: + const response = { + default: this._getReaderForType(Ci.nsIFeed.TYPE_FEED), + [Ci.nsIFeed.TYPE_AUDIO]: this._getReaderForType(Ci.nsIFeed.TYPE_AUDIO), + [Ci.nsIFeed.TYPE_VIDEO]: this._getReaderForType(Ci.nsIFeed.TYPE_VIDEO) + }; + Services.mm.broadcastAsyncMessage("FeedWriter:PreferenceUpdated", + response); + break; + } + }, + + _initSubscriptionUIResponse(feedType) { + const wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]. + getService(Ci.nsIWebContentConverterService); + const handlersRaw = wccr.getContentHandlers(getMimeTypeForFeedType(feedType)); + const handlers = []; + for (let handler of handlersRaw) { + LOG(`Handler found: ${handler}`); + handlers.push({ + name: handler.name, + uri: handler.uri + }); + } + let showFirstRunUI = true; + // eslint-disable-next-line mozilla/use-default-preference-values + try { + showFirstRunUI = Services.prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI); + } catch (ex) { } + const response = { handlers, showFirstRunUI }; + let selectedClientApp; + const feedTypePref = getPrefAppForType(feedType); + try { + selectedClientApp = Services.prefs.getComplexValue(feedTypePref, Ci.nsILocalFile); + } catch (ex) { + // Just do nothing, then we won't bother populating + } + + let defaultClientApp = null; + try { + // This can sometimes not exist + defaultClientApp = Cc["@mozilla.org/browser/shell-service;1"] + .getService(Ci.nsIShellService) + .defaultFeedReader; + } catch (ex) { + // Just do nothing, then we don't bother populating + } + + if (selectedClientApp && selectedClientApp.exists()) { + if (defaultClientApp && selectedClientApp.path != defaultClientApp.path) { + // Only set the default menu item if it differs from the selected one + response.defaultMenuItem = this._getFileDisplayName(defaultClientApp); + } + response.selectedMenuItem = this._getFileDisplayName(selectedClientApp); + } + response.reader = this._getReaderForType(feedType); + return response; + }, + + _setPref(aPrefName, aPrefValue, aIsComplex = false) { + LOG(`FeedWriter._setPref ${aPrefName}`); + // Ensure we have a pref that is settable + if (aPrefName && SETTABLE_PREFS.has(aPrefName)) { + if (aIsComplex) { + Services.prefs.setStringPref(aPrefName, aPrefValue); + } else { + Services.prefs.setCharPref(aPrefName, aPrefValue); + } + } else { + LOG(`FeedWriter._setPref ${aPrefName} not allowed`); + } + }, + + _getReaderForType(feedType) { + let prefs = Services.prefs; + let handler = "bookmarks"; + let url; + // eslint-disable-next-line mozilla/use-default-preference-values + try { + handler = prefs.getCharPref(getPrefReaderForType(feedType)); + } catch (ex) { } + + if (handler === "web") { + try { + url = prefs.getStringPref(getPrefWebForType(feedType)); + } catch (ex) { + LOG("FeedWriter._setSelectedHandler: invalid or no handler in prefs"); + url = null; + } + } + const alwaysUse = this._getAlwaysUseState(feedType); + const action = prefs.getCharPref(getPrefActionForType(feedType)); + return { handler, url, alwaysUse, action }; + }, + + _getAlwaysUseState(feedType) { + try { + return Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask"; + } catch (ex) { } + return false; }, receiveMessage(msg) { + let handler; switch (msg.name) { - case "FeedWriter:ChooseClientApp": - this.chooseClientApp(msg.data.title, msg.data.prefName, msg.target); + case "FeedWriter:GetSubscriptionUI": + const response = this._initSubscriptionUIResponse(msg.data.feedType); + msg.target.messageManager + .sendAsyncMessage("FeedWriter:GetSubscriptionUIResponse", + response); break; - case "FeedWriter:RequestClientAppName": - let selectedClientApp; - try { - selectedClientApp = Services.prefs.getComplexValue(msg.data.feedTypePref, Ci.nsILocalFile); - } catch (ex) { - // Just do nothing, then we won't bother populating - } - - let defaultClientApp = null; - try { - // This can sometimes not exist - defaultClientApp = Cc["@mozilla.org/browser/shell-service;1"] - .getService(Ci.nsIShellService) - .defaultFeedReader; - } catch (ex) { - // Just do nothing, then we don't bother populating - } - - if (selectedClientApp && selectedClientApp.exists()) { - if (defaultClientApp && selectedClientApp.path != defaultClientApp.path) { - // Only set the default menu item if it differs from the selected one - msg.target.messageManager - .sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem", - { name: this._getFileDisplayName(defaultClientApp), - type: "DefaultAppMenuItem" }); - } - msg.target.messageManager - .sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem", - { name: this._getFileDisplayName(selectedClientApp), - type: "SelectedAppMenuItem" }); - } + case "FeedWriter:ChooseClientApp": + this._chooseClientApp(msg.data.title, msg.data.feedType, msg.target); break; case "FeedWriter:ShownFirstRun": - Services.prefs.setBoolPref("browser.feeds.showFirstRunUI", false); - break; - case "FeedWriter:SetFeedCharPref": - Services.prefs.setCharPref(msg.data.pref, msg.data.value); + Services.prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false); break; - case "FeedWriter:SetFeedComplexString": { - Services.prefs.setStringPref(msg.data.pref, msg.data.value); - break; - } + case "FeedWriter:SetFeedPrefsAndSubscribe": + const settings = msg.data; + if (!settings.action || !VALID_ACTIONS.has(settings.action)) { + LOG(`Invalid action ${settings.action}`); + return; + } + if (!settings.reader || !VALID_READERS.has(settings.reader)) { + LOG(`Invalid reader ${settings.reader}`); + return; + } + const actionPref = getPrefActionForType(settings.feedType); + this._setPref(actionPref, settings.action); + const readerPref = getPrefReaderForType(settings.feedType); + this._setPref(readerPref, settings.reader); + handler = null; + + switch (settings.reader) { + case "web": + // This is a web set URI by content using window.registerContentHandler() + // Lets make sure we know about it before setting it + const webPref = getPrefWebForType(settings.feedType); + let wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]. + getService(Ci.nsIWebContentConverterService); + // If the user provided an invalid web URL this function won't give us a reference + handler = wccr.getWebContentHandlerByURI(getMimeTypeForFeedType(settings.feedType), settings.uri); + if (handler) { + this._setPref(webPref, settings.uri, true); + if (settings.useAsDefault) { + wccr.setAutoHandler(getMimeTypeForFeedType(settings.feedType), handler); + } + msg.target.messageManager + .sendAsyncMessage("FeedWriter:SetFeedPrefsAndSubscribeResponse", + { redirect: handler.getHandlerURI(settings.feedLocation) }); + } else { + LOG(`No handler found for web ${settings.feedType} ${settings.uri}`); + } + break; + default: + const feedService = Cc["@mozilla.org/browser/feeds/result-service;1"]. + getService(Ci.nsIFeedResultService); + + feedService.addToClientReader(settings.feedLocation, + settings.feedTitle, + settings.feedSubtitle, + settings.feedType, + settings.reader); + } + break; case "FeedConverter:ExecuteClientApp": - this.executeClientApp(msg.data.spec, msg.data.title, - msg.data.subtitle, msg.data.feedHandler); + // Always check feedHandler is from a set array of executable prefs + if (EXECUTABLE_PREFS.has(msg.data.feedHandler)) { + this.executeClientApp(msg.data.spec, msg.data.title, + msg.data.subtitle, msg.data.feedHandler); + } else { + LOG(`FeedConverter:ExecuteClientApp - Will not exec ${msg.data.feedHandler}`); + } break; } }, };
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -2566,18 +2566,23 @@ function URLBarSetURI(aURI) { } catch (ex) { value = "about:blank"; } } valid = !isBlankPageURL(uri.spec); } + let isDifferentValidValue = valid && value != gURLBar.value; gURLBar.value = value; gURLBar.valueIsTyped = !valid; + if (isDifferentValidValue) { + gURLBar.selectionStart = gURLBar.selectionEnd = 0; + } + SetPageProxyState(valid ? "valid" : "invalid"); } function losslessDecodeURI(aURI) { let scheme = aURI.scheme; if (scheme == "moz-action") throw new Error("losslessDecodeURI should never get a moz-action URI");
--- a/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js +++ b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js @@ -1,10 +1,14 @@ "use strict"; +// Bug 1318389 - This test does a lot of window and tab manipulation, +// causing it to take a long time on debug. +requestLongerTimeout(2); + add_task(setupPrefsAndRecentWindowBehavior); // Each of the test cases below is run twice: once for login-success and once // for login-abort (aSuccess set to true and false respectively). let testCasesForBothSuccessAndAbort = [ /** * A portal is detected when there's no browser window, then a browser * window is opened, then the portal is freed.
--- a/browser/base/content/test/popupNotifications/browser.ini +++ b/browser/base/content/test/popupNotifications/browser.ini @@ -8,17 +8,17 @@ skip-if = (os == "linux" && (debug || as skip-if = (os == "linux" && (debug || asan)) [browser_popupNotification_2.js] skip-if = (os == "linux" && (debug || asan)) [browser_popupNotification_3.js] skip-if = (os == "linux" && (debug || asan)) [browser_popupNotification_4.js] skip-if = (os == "linux" && (debug || asan)) [browser_popupNotification_5.js] -skip-if = (os == "linux" && (debug || asan)) +skip-if = true # bug 1332646 [browser_popupNotification_checkbox.js] skip-if = (os == "linux" && (debug || asan)) [browser_popupNotification_keyboard.js] skip-if = (os == "linux" && (debug || asan)) [browser_popupNotification_no_anchors.js] skip-if = (os == "linux" && (debug || asan)) [browser_reshow_in_background.js] skip-if = (os == "linux" && (debug || asan))
--- a/browser/base/content/test/urlbar/browser_bug304198.js +++ b/browser/base/content/test/urlbar/browser_bug304198.js @@ -43,16 +43,19 @@ add_task(function* () { function urlbarBackspace() { return new Promise((resolve, reject) => { gBrowser.selectedBrowser.focus(); gURLBar.addEventListener("input", function() { resolve(); }, {once: true}); gURLBar.focus(); + if (gURLBar.selectionStart == gURLBar.selectionEnd) { + gURLBar.selectionStart = gURLBar.selectionEnd = gURLBar.textValue.length; + } EventUtils.synthesizeKey("VK_BACK_SPACE", {}); }); } function* prepareDeletedURLTab() { yield BrowserTestUtils.switchTab(gBrowser, deletedURLTab); is(gURLBar.textValue, testURL, "gURLBar.textValue should be testURL after initial switch to deletedURLTab");
--- a/browser/base/content/test/urlbar/browser_canonizeURL.js +++ b/browser/base/content/test/urlbar/browser_canonizeURL.js @@ -24,16 +24,18 @@ add_task(function*() { registerCleanupFunction(() => { Preferences.set("browser.urlbar.autoFill", autoFill); }); for (let [inputValue, expectedURL, options] of testcases) { let promiseLoad = waitForDocLoadAndStopIt(expectedURL); gURLBar.focus(); if (Object.keys(options).length > 0) { + gURLBar.selectionStart = gURLBar.selectionEnd = + gURLBar.inputField.value.length; gURLBar.inputField.value = inputValue.slice(0, -1); EventUtils.synthesizeKey(inputValue.slice(-1), {}); } else { gURLBar.textValue = inputValue; } EventUtils.synthesizeKey("VK_RETURN", options); yield promiseLoad; }
--- a/browser/components/feeds/FeedConverter.js +++ b/browser/components/feeds/FeedConverter.js @@ -218,18 +218,17 @@ FeedConverter.prototype = { LOG("unexpected handler: " + handler); // fall through -- let feed service handle error case "bookmarks": case "client": case "default": try { let title = feed.title ? feed.title.plainText() : ""; let desc = feed.subtitle ? feed.subtitle.plainText() : ""; - let feedReader = safeGetCharPref(getPrefActionForType(feed.type), "bookmarks"); - feedService.addToClientReader(result.uri.spec, title, desc, feed.type, feedReader); + feedService.addToClientReader(result.uri.spec, title, desc, feed.type, handler); return; } catch (ex) { /* fallback to preview mode */ } } } } let ios = Cc["@mozilla.org/network/io-service;1"].
--- a/browser/components/feeds/FeedWriter.js +++ b/browser/components/feeds/FeedWriter.js @@ -39,93 +39,21 @@ function makeURI(aURLSpec, aCharset) { } catch (ex) { } return null; } const XML_NS = "http://www.w3.org/XML/1998/namespace"; const HTML_NS = "http://www.w3.org/1999/xhtml"; const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; -const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed"; -const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed"; -const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed"; const URI_BUNDLE = "chrome://browser/locale/feeds/subscribe.properties"; -const PREF_SELECTED_APP = "browser.feeds.handlers.application"; -const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice"; -const PREF_SELECTED_ACTION = "browser.feeds.handler"; -const PREF_SELECTED_READER = "browser.feeds.handler.default"; - -const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application"; -const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice"; -const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler"; -const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default"; - -const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application"; -const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice"; -const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler"; -const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default"; - -const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI"; - const TITLE_ID = "feedTitleText"; const SUBTITLE_ID = "feedSubtitleText"; -function getPrefAppForType(t) { - switch (t) { - case Ci.nsIFeed.TYPE_VIDEO: - return PREF_VIDEO_SELECTED_APP; - - case Ci.nsIFeed.TYPE_AUDIO: - return PREF_AUDIO_SELECTED_APP; - - default: - return PREF_SELECTED_APP; - } -} - -function getPrefWebForType(t) { - switch (t) { - case Ci.nsIFeed.TYPE_VIDEO: - return PREF_VIDEO_SELECTED_WEB; - - case Ci.nsIFeed.TYPE_AUDIO: - return PREF_AUDIO_SELECTED_WEB; - - default: - return PREF_SELECTED_WEB; - } -} - -function getPrefActionForType(t) { - switch (t) { - case Ci.nsIFeed.TYPE_VIDEO: - return PREF_VIDEO_SELECTED_ACTION; - - case Ci.nsIFeed.TYPE_AUDIO: - return PREF_AUDIO_SELECTED_ACTION; - - default: - return PREF_SELECTED_ACTION; - } -} - -function getPrefReaderForType(t) { - switch (t) { - case Ci.nsIFeed.TYPE_VIDEO: - return PREF_VIDEO_SELECTED_READER; - - case Ci.nsIFeed.TYPE_AUDIO: - return PREF_AUDIO_SELECTED_READER; - - default: - return PREF_SELECTED_READER; - } -} - /** * Converts a number of bytes to the appropriate unit that results in a * number that needs fewer than 4 digits * * @return a pair: [new value with 3 sig. figs., its unit] */ function convertByteUnits(aBytes) { let units = ["bytes", "kilobyte", "megabyte", "gigabyte"]; @@ -148,19 +76,16 @@ function convertByteUnits(aBytes) { function FeedWriter() { this._selectedApp = undefined; this._selectedAppMenuItem = null; this._subscribeCallback = null; this._defaultHandlerMenuItem = null; } FeedWriter.prototype = { - _mimeSvc : Cc["@mozilla.org/mime;1"]. - getService(Ci.nsIMIMEService), - _getPropertyAsBag(container, property) { return container.fields.getProperty(property). QueryInterface(Ci.nsIPropertyBag2); }, _getPropertyAsString(container, property) { try { return container.fields.getPropertyAsAString(property); @@ -221,28 +146,31 @@ FeedWriter.prototype = { _getFormattedString(key, params) { return this._bundle.formatStringFromName(key, params, params.length); }, _getString(key) { return this._bundle.GetStringFromName(key); }, - _setCheckboxCheckedState(aCheckbox, aValue) { - // see checkbox.xml, xbl bindings are not applied within the sandbox! TODO - let change = (aValue != (aCheckbox.getAttribute("checked") == "true")); - if (aValue) - aCheckbox.setAttribute("checked", "true"); - else - aCheckbox.removeAttribute("checked"); + _setCheckboxCheckedState(aValue) { + let checkbox = this._document.getElementById("alwaysUse"); + if (checkbox) { + // see checkbox.xml, xbl bindings are not applied within the sandbox! TODO + let change = (aValue != (checkbox.getAttribute("checked") == "true")); + if (aValue) + checkbox.setAttribute("checked", "true"); + else + checkbox.removeAttribute("checked"); - if (change) { - let event = this._document.createEvent("Events"); - event.initEvent("CheckboxStateChange", true, true); - aCheckbox.dispatchEvent(event); + if (change) { + let event = this._document.createEvent("Events"); + event.initEvent("CheckboxStateChange", true, true); + checkbox.dispatchEvent(event); + } } }, /** * Returns a date suitable for displaying in the feed preview. * If the date cannot be parsed, the return value is "false". * @param dateString * A date as extracted from a feed entry. (entry.updated) @@ -286,32 +214,16 @@ FeedWriter.prototype = { this.__feedType = feed.type; return feed.type; } catch (ex) { } return Ci.nsIFeed.TYPE_FEED; }, /** - * Maps a feed type to a maybe-feed mimetype. - */ - _getMimeTypeForFeedType() { - switch (this._getFeedType()) { - case Ci.nsIFeed.TYPE_VIDEO: - return TYPE_MAYBE_VIDEO_FEED; - - case Ci.nsIFeed.TYPE_AUDIO: - return TYPE_MAYBE_AUDIO_FEED; - - default: - return TYPE_MAYBE_FEED; - } - }, - - /** * Writes the feed title into the preview document. * @param container * The feed container */ _setTitleText(container) { if (container.title) { let title = container.title.plainText(); this._setContentText(TITLE_ID, container.title); @@ -486,27 +398,21 @@ FeedWriter.prototype = { enclosureDiv.setAttribute("class", "enclosure"); let mozicon = "moz-icon://.txt?size=16"; let type_text = null; let size_text = null; if (enc.hasKey("type")) { type_text = enc.get("type"); - try { - let handlerInfoWrapper = this._mimeSvc.getFromTypeAndExtension(enc.get("type"), null); - - if (handlerInfoWrapper) - type_text = handlerInfoWrapper.description; + if (enc.hasKey("typeDesc")) + type_text = enc.get("typeDesc"); - if (type_text && type_text.length > 0) - mozicon = "moz-icon://goat?size=16&contentType=" + enc.get("type"); - - } catch (ex) { } - + if (type_text && type_text.length > 0) + mozicon = "moz-icon://goat?size=16&contentType=" + enc.get("type"); } if (enc.hasKey("length") && /^[0-9]+$/.test(enc.get("length"))) { let enc_size = convertByteUnits(parseInt(enc.get("length"))); size_text = this._getFormattedString("enclosureSizeText", [enc_size[0], this._getString(enc_size[1])]); @@ -591,29 +497,17 @@ FeedWriter.prototype = { * Displays a prompt from which the user may choose a (client) feed reader. * @param aCallback the callback method, passes in true if a feed reader was * selected, false otherwise. */ _chooseClientApp(aCallback) { this._subscribeCallback = aCallback; this._mm.sendAsyncMessage("FeedWriter:ChooseClientApp", { title: this._getString("chooseApplicationDialogTitle"), - prefName: getPrefAppForType(this._getFeedType()) }); - }, - - _setAlwaysUseCheckedState(feedType) { - let checkbox = this._document.getElementById("alwaysUse"); - if (checkbox) { - let alwaysUse = false; - try { - if (Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask") - alwaysUse = true; - } catch (ex) { } - this._setCheckboxCheckedState(checkbox, alwaysUse); - } + feedType: this._getFeedType() }); }, _setSubscribeUsingLabel() { let stringLabel = "subscribeFeedUsing"; switch (this._getFeedType()) { case Ci.nsIFeed.TYPE_VIDEO: stringLabel = "subscribeVideoPodcastUsing"; break; @@ -666,54 +560,44 @@ FeedWriter.prototype = { switch (event.type) { case "click": if (event.target.id == "subscribeButton") { this.subscribe(); } break; case "change": + LOG("Change fired"); if (event.target.selectedOptions[0].id == "chooseApplicationMenuItem") { - this._chooseClientApp((aResult) => { - if (!aResult) { - // Select the (per-prefs) selected handler if no application - // was selected - this._setSelectedHandler(this._getFeedType()); - } + this._chooseClientApp(() => { + // Select the (per-prefs) selected handler if no application + // was selected + LOG("Selected handler after callback"); + this._setAlwaysUseLabel(); }); } else { this._setAlwaysUseLabel(); } break; } }, _getWebHandlerElementsForURL(aURL) { - let menu = this._document.getElementById("handlersMenuList"); - return menu.querySelectorAll('[webhandlerurl="' + aURL + '"]'); + return this._handlersList.querySelectorAll('[webhandlerurl="' + aURL + '"]'); }, - _setSelectedHandler(feedType) { - let prefs = Services.prefs; - let handler = prefs.getCharPref(getPrefReaderForType(feedType), "bookmarks"); - + _setSelectedHandlerResponse(handler, url) { + LOG(`Selecting handler response ${handler} ${url}`); switch (handler) { case "web": { if (this._handlersList) { - let url; - try { - url = prefs.getStringPref(getPrefWebForType(feedType)); - } catch (ex) { - LOG("FeedWriter._setSelectedHandler: invalid or no handler in prefs"); - return; - } let handlers = this._getWebHandlerElementsForURL(url); if (handlers.length == 0) { - LOG("FeedWriter._setSelectedHandler: selected web handler isn't in the menulist") + LOG(`Selected web handler isn't in the menulist ${url}`); return; } handlers[0].selected = true; } break; } case "client": @@ -724,20 +608,20 @@ FeedWriter.prototype = { default: { let liveBookmarksMenuItem = this._document.getElementById("liveBookmarksMenuItem"); if (liveBookmarksMenuItem) liveBookmarksMenuItem.selected = true; } } }, - _initSubscriptionUI() { - let handlersList = this._document.getElementById("handlersMenuList"); - if (!handlersList) + _initSubscriptionUI(setupMessage) { + if (!this._handlersList) return; + LOG("UI init"); let feedType = this._getFeedType(); // change the background let header = this._document.getElementById("feedHeader"); switch (feedType) { case Ci.nsIFeed.TYPE_VIDEO: header.className = "videoPodcastBackground"; @@ -758,83 +642,84 @@ FeedWriter.prototype = { menuItem.removeAttribute("selected"); menuItem.setAttribute("id", "selectedAppMenuItem"); menuItem.setAttribute("handlerType", "client"); // Hide the menuitem until we select an app menuItem.style.display = "none"; this._selectedAppMenuItem = menuItem; - handlersList.appendChild(this._selectedAppMenuItem); + this._handlersList.appendChild(this._selectedAppMenuItem); // Create the menuitem for the default reader, but don't show/populate it until // we get confirmation of what it is from the parent menuItem = liveBookmarksMenuItem.cloneNode(false); menuItem.removeAttribute("selected"); menuItem.setAttribute("id", "defaultHandlerMenuItem"); menuItem.setAttribute("handlerType", "client"); menuItem.style.display = "none"; this._defaultHandlerMenuItem = menuItem; - handlersList.appendChild(this._defaultHandlerMenuItem); - - this._mm.sendAsyncMessage("FeedWriter:RequestClientAppName", - { feedTypePref: getPrefAppForType(feedType) }); + this._handlersList.appendChild(this._defaultHandlerMenuItem); // "Choose Application..." menuitem menuItem = liveBookmarksMenuItem.cloneNode(false); menuItem.removeAttribute("selected"); menuItem.setAttribute("id", "chooseApplicationMenuItem"); menuItem.textContent = this._getString("chooseApplicationMenuItem"); - handlersList.appendChild(menuItem); + this._handlersList.appendChild(menuItem); // separator let chooseAppSep = liveBookmarksMenuItem.nextElementSibling.cloneNode(false); chooseAppSep.textContent = liveBookmarksMenuItem.nextElementSibling.textContent; - handlersList.appendChild(chooseAppSep); + this._handlersList.appendChild(chooseAppSep); - // List of web handlers - let wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]. - getService(Ci.nsIWebContentConverterService); - let handlers = wccr.getContentHandlers(this._getMimeTypeForFeedType(feedType)); - for (let handler of handlers) { + for (let handler of setupMessage.handlers) { if (!handler.uri) { LOG("Handler with name " + handler.name + " has no URI!? Skipping..."); continue; } menuItem = liveBookmarksMenuItem.cloneNode(false); menuItem.removeAttribute("selected"); menuItem.className = "menuitem-iconic"; menuItem.textContent = handler.name; menuItem.setAttribute("handlerType", "web"); menuItem.setAttribute("webhandlerurl", handler.uri); - handlersList.appendChild(menuItem); + this._handlersList.appendChild(menuItem); } - this._setSelectedHandler(feedType); + this._setSelectedHandlerResponse(setupMessage.reader.handler, setupMessage.reader.url); + + if (setupMessage.defaultMenuItem) { + LOG(`Setting default menu item ${setupMessage.defaultMenuItem}`); + this._setApplicationLauncherMenuItem(this._defaultHandlerMenuItem, setupMessage.defaultMenuItem); + } + if (setupMessage.selectedMenuItem) { + LOG(`Setting selected menu item ${setupMessage.selectedMenuItem}`); + this._setApplicationLauncherMenuItem(this._selectedAppMenuItem, setupMessage.selectedMenuItem); + } // "Subscribe using..." this._setSubscribeUsingLabel(); // "Always use..." checkbox initial state - this._setAlwaysUseCheckedState(feedType); + this._setCheckboxCheckedState(setupMessage.reader.alwaysUse); this._setAlwaysUseLabel(); // We update the "Always use.." checkbox label whenever the selected item // in the list is changed - handlersList.addEventListener("change", this); + this._handlersList.addEventListener("change", this); // Set up the "Subscribe Now" button this._document.getElementById("subscribeButton") .addEventListener("click", this); // first-run ui - let showFirstRunUI = Services.prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI, true); - if (showFirstRunUI) { + if (setupMessage.showFirstRunUI) { let textfeedinfo1, textfeedinfo2; switch (feedType) { case Ci.nsIFeed.TYPE_VIDEO: textfeedinfo1 = "feedSubscriptionVideoPodcast1"; textfeedinfo2 = "feedSubscriptionVideoPodcast2"; break; case Ci.nsIFeed.TYPE_AUDIO: textfeedinfo1 = "feedSubscriptionAudioPodcast1"; @@ -910,63 +795,84 @@ FeedWriter.prototype = { this._handlersList = this._document.getElementById("handlersMenuList"); let secman = Cc["@mozilla.org/scriptsecuritymanager;1"]. getService(Ci.nsIScriptSecurityManager); this._feedPrincipal = secman.createCodebasePrincipal(this._feedURI, {}); LOG("Subscribe Preview: feed uri = " + this._window.location.href); - // Set up the subscription UI - this._initSubscriptionUI(); - let prefs = Services.prefs; - prefs.addObserver(PREF_SELECTED_ACTION, this, false); - prefs.addObserver(PREF_SELECTED_READER, this, false); - prefs.addObserver(PREF_SELECTED_WEB, this, false); - prefs.addObserver(PREF_SELECTED_APP, this, false); - prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, false); - prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, false); - prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, false); - prefs.addObserver(PREF_VIDEO_SELECTED_APP, this, false); - prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, false); - prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, false); - prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, false); - prefs.addObserver(PREF_AUDIO_SELECTED_APP, this, false); + this._mm.addMessageListener("FeedWriter:PreferenceUpdated", this); + this._mm.addMessageListener("FeedWriter:SetApplicationLauncherMenuItem", this); + this._mm.addMessageListener("FeedWriter:GetSubscriptionUIResponse", this); + this._mm.addMessageListener("FeedWriter:SetFeedPrefsAndSubscribeResponse", this); - this._mm.addMessageListener("FeedWriter:SetApplicationLauncherMenuItem", this); + const feedType = this._getFeedType(); + this._mm.sendAsyncMessage("FeedWriter:GetSubscriptionUI", + { feedType }); }, receiveMessage(msg) { + if (!this._window) { + // this._window is null unless this.init was called with a trusted + // window object. + return; + } + LOG(`received message from parent ${msg.name}`); switch (msg.name) { + case "FeedWriter:PreferenceUpdated": + // This is called when browser-feeds.js spots a pref change + // This will happen when + // - about:preferences#applications changes + // - another feed reader page changes the preference + // - when this page itself changes the select and there isn't a redirect + // bookmarks and launching an external app means the page stays open after subscribe + const feedType = this._getFeedType(); + LOG(`Got prefChange! ${JSON.stringify(msg.data)} current type: ${feedType}`); + let feedTypePref = msg.data.default; + if (feedType in msg.data) { + feedTypePref = msg.data[feedType]; + } + LOG(`Got pref ${JSON.stringify(feedTypePref)}`); + this._setCheckboxCheckedState(feedTypePref.alwaysUse); + this._setSelectedHandlerResponse(feedTypePref.handler, feedTypePref.url); + this._setAlwaysUseLabel(); + break; + case "FeedWriter:SetFeedPrefsAndSubscribeResponse": + LOG(`FeedWriter:SetFeedPrefsAndSubscribeResponse - Redirecting ${msg.data.redirect}`); + this._window.location.href = msg.data.redirect; + break; + case "FeedWriter:GetSubscriptionUIResponse": + // Set up the subscription UI + this._initSubscriptionUI(msg.data); + break; case "FeedWriter:SetApplicationLauncherMenuItem": - let menuItem = null; - - if (msg.data.type == "DefaultAppMenuItem") { - menuItem = this._defaultHandlerMenuItem; - } else { - // Most likely SelectedAppMenuItem - menuItem = this._selectedAppMenuItem; - } - - menuItem.textContent = msg.data.name; - menuItem.style.display = ""; - menuItem.selected = true; - + LOG(`FeedWriter:SetApplicationLauncherMenuItem - picked ${msg.data.name}`); + this._setApplicationLauncherMenuItem(this._selectedAppMenuItem, msg.data.name); // Potentially a bit racy, but I don't think we can get into a state where this callback is set and // we're not coming back from ChooseClientApp in browser-feeds.js if (this._subscribeCallback) { this._subscribeCallback(); this._subscribeCallback = null; } - break; } }, + _setApplicationLauncherMenuItem(menuItem, aName) { + /* unselect all handlers */ + [...this._handlersList.children].forEach((option) => { + option.removeAttribute("selected"); + }); + menuItem.textContent = aName; + menuItem.style.display = ""; + menuItem.selected = true; + }, + writeContent() { if (!this._window) return; try { // Set up the feed content let container = this._getContainer(); if (!container) @@ -978,34 +884,21 @@ FeedWriter.prototype = { } finally { this._removeFeedFromCache(); } }, close() { this._document.getElementById("subscribeButton") .removeEventListener("click", this); - this._document.getElementById("handlersMenuList") + this._handlersList .removeEventListener("change", this); this._document = null; this._window = null; - let prefs = Services.prefs; - prefs.removeObserver(PREF_SELECTED_ACTION, this); - prefs.removeObserver(PREF_SELECTED_READER, this); - prefs.removeObserver(PREF_SELECTED_WEB, this); - prefs.removeObserver(PREF_SELECTED_APP, this); - prefs.removeObserver(PREF_VIDEO_SELECTED_ACTION, this); - prefs.removeObserver(PREF_VIDEO_SELECTED_READER, this); - prefs.removeObserver(PREF_VIDEO_SELECTED_WEB, this); - prefs.removeObserver(PREF_VIDEO_SELECTED_APP, this); - - prefs.removeObserver(PREF_AUDIO_SELECTED_ACTION, this); - prefs.removeObserver(PREF_AUDIO_SELECTED_READER, this); - prefs.removeObserver(PREF_AUDIO_SELECTED_WEB, this); - prefs.removeObserver(PREF_AUDIO_SELECTED_APP, this); + this._handlersList = null; this._removeFeedFromCache(); this.__bundle = null; this._feedURI = null; this._selectedApp = undefined; this._selectedAppMenuItem = null; this._defaultHandlerMenuItem = null; @@ -1015,90 +908,64 @@ FeedWriter.prototype = { if (this._feedURI) { let feedService = Cc["@mozilla.org/browser/feeds/result-service;1"]. getService(Ci.nsIFeedResultService); feedService.removeFeedResult(this._feedURI); this._feedURI = null; } }, - setFeedCharPref(aPrefName, aPrefValue) { - this._mm.sendAsyncMessage("FeedWriter:SetFeedCharPref", - { pref: aPrefName, - value: aPrefValue }); - }, - - setFeedComplexString(aPrefName, aPrefValue) { - // This sends the string data across to the parent, which will use it in an nsISupportsString - // for a complex value pref. - this._mm.sendAsyncMessage("FeedWriter:SetFeedComplexString", - { pref: aPrefName, - value: aPrefValue }); - }, - subscribe() { let feedType = this._getFeedType(); // Subscribe to the feed using the selected handler and save prefs let defaultHandler = "reader"; let useAsDefault = this._document.getElementById("alwaysUse").getAttribute("checked"); - let menuList = this._document.getElementById("handlersMenuList"); - let selectedItem = menuList.selectedOptions[0]; + let selectedItem = this._handlersList.selectedOptions[0]; let subscribeCallback = () => { + let feedReader = null; + let settings = { + feedType, + useAsDefault, + // Pull the title and subtitle out of the document + feedTitle: this._document.getElementById(TITLE_ID).textContent, + feedSubtitle: this._document.getElementById(SUBTITLE_ID).textContent, + feedLocation: this._window.location.href + }; if (selectedItem.hasAttribute("webhandlerurl")) { - let webURI = selectedItem.getAttribute("webhandlerurl"); - this.setFeedCharPref(getPrefReaderForType(feedType), "web"); - this.setFeedComplexString(getPrefWebForType(feedType), webURI); - - let wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]. - getService(Ci.nsIWebContentConverterService); - let handler = wccr.getWebContentHandlerByURI(this._getMimeTypeForFeedType(feedType), webURI); - if (handler) { - if (useAsDefault) { - wccr.setAutoHandler(this._getMimeTypeForFeedType(feedType), handler); - } - - this._window.location.href = handler.getHandlerURI(this._window.location.href); - } + feedReader = "web"; + settings.uri = selectedItem.getAttribute("webhandlerurl"); } else { - let feedReader = null; switch (selectedItem.id) { case "selectedAppMenuItem": feedReader = "client"; break; case "defaultHandlerMenuItem": feedReader = "default"; break; case "liveBookmarksMenuItem": defaultHandler = "bookmarks"; feedReader = "bookmarks"; break; } - - this.setFeedCharPref(getPrefReaderForType(feedType), feedReader); - - let feedService = Cc["@mozilla.org/browser/feeds/result-service;1"]. - getService(Ci.nsIFeedResultService); - - // Pull the title and subtitle out of the document - let feedTitle = this._document.getElementById(TITLE_ID).textContent; - let feedSubtitle = this._document.getElementById(SUBTITLE_ID).textContent; - feedService.addToClientReader(this._window.location.href, feedTitle, feedSubtitle, feedType, feedReader); } + settings.reader = feedReader; // If "Always use..." is checked, we should set PREF_*SELECTED_ACTION // to either "reader" (If a web reader or if an application is selected), // or to "bookmarks" (if the live bookmarks option is selected). // Otherwise, we should set it to "ask" - if (useAsDefault) { - this.setFeedCharPref(getPrefActionForType(feedType), defaultHandler); - } else { - this.setFeedCharPref(getPrefActionForType(feedType), "ask"); + if (!useAsDefault) { + defaultHandler = "ask"; } + settings.action = defaultHandler; + LOG(`FeedWriter:SetFeedPrefsAndSubscribe - ${JSON.stringify(settings)}`); + this._mm.sendAsyncMessage("FeedWriter:SetFeedPrefsAndSubscribe", + settings); } // Show the file picker before subscribing if the // choose application menuitem was chosen using the keyboard if (selectedItem.id == "chooseApplicationMenuItem") { this._chooseClientApp(function(aResult) { if (aResult) { selectedItem = @@ -1106,47 +973,16 @@ FeedWriter.prototype = { subscribeCallback(); } }.bind(this)); } else { subscribeCallback(); } }, - // nsIObserver - observe(subject, topic, data) { - if (!this._window) { - // this._window is null unless this.init was called with a trusted - // window object. - return; - } - - let feedType = this._getFeedType(); - - if (topic == "nsPref:changed") { - switch (data) { - case PREF_SELECTED_READER: - case PREF_SELECTED_WEB: - case PREF_SELECTED_APP: - case PREF_VIDEO_SELECTED_READER: - case PREF_VIDEO_SELECTED_WEB: - case PREF_VIDEO_SELECTED_APP: - case PREF_AUDIO_SELECTED_READER: - case PREF_AUDIO_SELECTED_WEB: - case PREF_AUDIO_SELECTED_APP: - this._setSelectedHandler(feedType); - break; - case PREF_SELECTED_ACTION: - case PREF_VIDEO_SELECTED_ACTION: - case PREF_AUDIO_SELECTED_ACTION: - this._setAlwaysUseCheckedState(feedType); - } - } - }, - get _mm() { let mm = this._window.QueryInterface(Ci.nsIInterfaceRequestor). getInterface(Ci.nsIDocShell). QueryInterface(Ci.nsIInterfaceRequestor). getInterface(Ci.nsIContentFrameMessageManager); delete this._mm; return this._mm = mm; },
--- a/browser/extensions/e10srollout/bootstrap.js +++ b/browser/extensions/e10srollout/bootstrap.js @@ -14,17 +14,17 @@ Cu.import("resource://gre/modules/Update const TEST_THRESHOLD = { "beta" : 0.5, // 50% "release" : 1.0, // 100% "esr" : 1.0, // 100% }; const ADDON_ROLLOUT_POLICY = { "beta" : "51alladdons", // Any WebExtension or addon except with mpc = false - "release" : "51set1", + "release" : "50allmpc", "esr" : "esrA", // WebExtensions and Addons with mpc=true }; const PREF_COHORT_SAMPLE = "e10s.rollout.cohortSample"; const PREF_COHORT_NAME = "e10s.rollout.cohort"; const PREF_E10S_OPTED_IN = "browser.tabs.remote.autostart"; const PREF_E10S_FORCE_ENABLED = "browser.tabs.remote.force-enable"; const PREF_E10S_FORCE_DISABLED = "browser.tabs.remote.force-disable"; @@ -68,17 +68,20 @@ function defineCohort() { if (updateChannel in ADDON_ROLLOUT_POLICY) { addonPolicy = ADDON_ROLLOUT_POLICY[updateChannel]; Preferences.set(PREF_E10S_ADDON_POLICY, addonPolicy); // This is also the proper place to set the blocklist pref // in case it is necessary. Preferences.set(PREF_E10S_ADDON_BLOCKLIST, // bug 1185672 - Tab Mix Plus - "{dc572301-7619-498c-a57d-39143191b318};"); + "{dc572301-7619-498c-a57d-39143191b318};" + // bug 1344345 - Mega + + "firefox@mega.co.nz" + ); } else { Preferences.reset(PREF_E10S_ADDON_POLICY); } let userOptedOut = optedOut(); let userOptedIn = optedIn(); let disqualified = (Services.appinfo.multiprocessBlockPolicy != 0); let testGroup = (getUserSample() < TEST_THRESHOLD[updateChannel]);
--- a/browser/extensions/e10srollout/install.rdf.in +++ b/browser/extensions/e10srollout/install.rdf.in @@ -5,17 +5,17 @@ #filter substitution <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#"> <Description about="urn:mozilla:install-manifest"> <em:id>e10srollout@mozilla.org</em:id> - <em:version>1.11</em:version> + <em:version>1.12</em:version> <em:type>2</em:type> <em:bootstrap>true</em:bootstrap> <em:multiprocessCompatible>true</em:multiprocessCompatible> <!-- Target Application this theme can install into, with minimum and maximum supported versions. --> <em:targetApplication> <Description>
--- a/caps/nsJSPrincipals.cpp +++ b/caps/nsJSPrincipals.cpp @@ -130,26 +130,32 @@ ReadSuffixAndSpec(JSStructuredCloneReade nsACString& aSpec) { uint32_t suffixLength, specLength; if (!JS_ReadUint32Pair(aReader, &suffixLength, &specLength)) { return false; } nsAutoCString suffix; - suffix.SetLength(suffixLength); + if (!suffix.SetLength(suffixLength, fallible)) { + return false; + } + if (!JS_ReadBytes(aReader, suffix.BeginWriting(), suffixLength)) { return false; } if (!aAttrs.PopulateFromSuffix(suffix)) { return false; } - aSpec.SetLength(specLength); + if (!aSpec.SetLength(specLength, fallible)) { + return false; + } + if (!JS_ReadBytes(aReader, aSpec.BeginWriting(), specLength)) { return false; } return true; } static bool @@ -190,17 +196,17 @@ ReadPrincipalInfo(JSStructuredCloneReade aInfo = expanded; } else if (aTag == SCTAG_DOM_CONTENT_PRINCIPAL) { OriginAttributes attrs; nsAutoCString spec; if (!ReadSuffixAndSpec(aReader, attrs, spec)) { return false; } - aInfo = ContentPrincipalInfo(attrs, spec); + aInfo = ContentPrincipalInfo(attrs, void_t(), spec); } else { MOZ_CRASH("unexpected principal structured clone tag"); } return true; } /* static */ bool
--- a/devtools/client/webconsole/test/browser.ini +++ b/devtools/client/webconsole/test/browser.ini @@ -176,17 +176,16 @@ skip-if = (os == 'linux' && bits == 32 & [browser_console_error_source_click.js] [browser_console_filters.js] [browser_console_iframe_messages.js] [browser_console_keyboard_accessibility.js] [browser_console_log_inspectable_object.js] [browser_console_native_getters.js] [browser_console_navigation_marker.js] [browser_console_netlogging.js] -skip-if = true # Bug 1298364 [browser_console_nsiconsolemessage.js] [browser_console_optimized_out_vars.js] [browser_console_private_browsing.js] skip-if = e10s # Bug 1042253 - webconsole e10s tests [browser_console_server_logging.js] [browser_console_variables_view.js] [browser_console_variables_view_filter.js] [browser_console_variables_view_dom_nodes.js] @@ -333,16 +332,17 @@ skip-if = e10s # Bug 1042253 - webconsol [browser_webconsole_js_input_expansion.js] [browser_webconsole_jsterm.js] skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout) [browser_webconsole_live_filtering_of_message_types.js] [browser_webconsole_live_filtering_on_search_strings.js] [browser_webconsole_message_node_id.js] [browser_webconsole_multiline_input.js] [browser_webconsole_netlogging.js] +skip-if = true # Bug 1298364 [browser_webconsole_netlogging_basic.js] [browser_webconsole_netlogging_panel.js] [browser_webconsole_netlogging_reset_filter.js] [browser_webconsole_notifications.js] [browser_webconsole_open-links-without-callback.js] [browser_webconsole_promise.js] [browser_webconsole_output_copy_newlines.js] subsuite = clipboard
--- a/devtools/server/actors/layout.js +++ b/devtools/server/actors/layout.js @@ -120,21 +120,26 @@ var LayoutActor = ActorClassWithSpec(lay * iterating below the given rootNode and optionally including nested frames. * * @param {NodeActor} rootNode * @param {Boolean} traverseFrames * Whether or not we should iterate through nested frames. * @return {Array} An array of GridActor objects. */ getAllGrids: function (rootNode, traverseFrames) { + let grids = []; + + if (!rootNode) { + return grids; + } + if (!traverseFrames) { return this.getGrids(rootNode.rawNode); } - let grids = []; for (let {document} of this.tabActor.windows) { grids = [...grids, ...this.getGrids(document.documentElement)]; } return grids; }, onNavigate: function () {
--- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -10560,24 +10560,22 @@ nsIDocument::ObsoleteSheet(const nsAStri class UnblockParsingPromiseHandler final : public PromiseNativeHandler { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS(UnblockParsingPromiseHandler) explicit UnblockParsingPromiseHandler(nsIDocument* aDocument, Promise* aPromise) - : mDocument(aDocument) - , mPromise(aPromise) + : mPromise(aPromise) { - nsCOMPtr<nsIParser> parser = mDocument->CreatorParserOrNull(); + nsCOMPtr<nsIParser> parser = aDocument->CreatorParserOrNull(); if (parser) { parser->BlockParser(); - } else { - mDocument = nullptr; + mParser = do_GetWeakReference(parser); } } void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override { MaybeUnblockParser(); @@ -10595,31 +10593,29 @@ public: protected: virtual ~UnblockParsingPromiseHandler() { MaybeUnblockParser(); } private: void MaybeUnblockParser() { - if (mDocument) { - nsCOMPtr<nsIParser> parser = mDocument->CreatorParserOrNull(); - if (parser) { - parser->UnblockParser(); - parser->ContinueInterruptedParsingAsync(); - } - mDocument = nullptr; - } - } - - RefPtr<nsIDocument> mDocument; + nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser); + if (parser) { + parser->UnblockParser(); + parser->ContinueInterruptedParsingAsync(); + mParser = nullptr; + } + } + + nsWeakPtr mParser; RefPtr<Promise> mPromise; }; -NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mDocument, mPromise) +NS_IMPL_CYCLE_COLLECTION(UnblockParsingPromiseHandler, mPromise) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnblockParsingPromiseHandler) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(UnblockParsingPromiseHandler) NS_IMPL_CYCLE_COLLECTING_RELEASE(UnblockParsingPromiseHandler)
--- a/dom/cache/DBSchema.cpp +++ b/dom/cache/DBSchema.cpp @@ -1970,25 +1970,25 @@ ReadResponse(mozIStorageConnection* aCon } nsAutoCString serializedInfo; rv = state->GetUTF8String(5, serializedInfo); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } aSavedResponseOut->mValue.principalInfo() = void_t(); if (!serializedInfo.IsEmpty()) { - nsAutoCString originNoSuffix; + nsAutoCString specNoSuffix; OriginAttributes attrs; - if (!attrs.PopulateFromOrigin(serializedInfo, originNoSuffix)) { + if (!attrs.PopulateFromOrigin(serializedInfo, specNoSuffix)) { NS_WARNING("Something went wrong parsing a serialized principal!"); return NS_ERROR_FAILURE; } aSavedResponseOut->mValue.principalInfo() = - mozilla::ipc::ContentPrincipalInfo(attrs, originNoSuffix); + mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), specNoSuffix); } rv = state->GetBlobAsUTF8String(6, aSavedResponseOut->mValue.channelInfo().securityInfo()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT " "name, "
--- a/dom/events/test/pointerevents/mochitest.ini +++ b/dom/events/test/pointerevents/mochitest.ini @@ -1,16 +1,26 @@ [DEFAULT] skip-if = os == 'android' # Bug 1312791 support-files = mochitest_support_external.js mochitest_support_internal.js pointerevent_styles.css pointerevent_support.js +[test_bug1285128.html] +[test_bug1293174_implicit_pointer_capture_for_touch_1.html] + support-files = bug1293174_implicit_pointer_capture_for_touch_1.html +[test_bug1293174_implicit_pointer_capture_for_touch_2.html] + support-files = bug1293174_implicit_pointer_capture_for_touch_2.html +[test_bug1303704.html] +[test_bug1315862.html] +[test_bug1323158.html] +[test_empty_file.html] + disabled = disabled # Bug 1150091 - Issue with support-files [test_pointerevent_attributes_hoverable_pointers-manual.html] support-files = pointerevent_attributes_hoverable_pointers-manual.html ./resources/pointerevent_attributes_hoverable_pointers-iframe.html [test_pointerevent_attributes_nohover_pointers-manual.html] support-files = pointerevent_attributes_nohover_pointers-manual.html ./resources/pointerevent_attributes_hoverable_pointers-iframe.html @@ -113,19 +123,10 @@ support-files = pointerevent_touch-action-pan-y-css_touch-manual.html pointerevent_touch-action-span-test_touch-manual.html pointerevent_touch-action-svg-test_touch-manual.html pointerevent_touch-action-table-test_touch-manual.html pointerevent_touch-action-pan-down-css_touch-manual.html pointerevent_touch-action-pan-left-css_touch-manual.html pointerevent_touch-action-pan-right-css_touch-manual.html pointerevent_touch-action-pan-up-css_touch-manual.html -[test_bug1285128.html] -[test_bug1293174_implicit_pointer_capture_for_touch_1.html] - support-files = bug1293174_implicit_pointer_capture_for_touch_1.html -[test_bug1293174_implicit_pointer_capture_for_touch_2.html] - support-files = bug1293174_implicit_pointer_capture_for_touch_2.html -[test_bug1303704.html] -[test_bug1323158.html] +[test_trigger_fullscreen_by_pointer_events.html] [test_trigger_popup_by_pointer_events.html] -[test_empty_file.html] - disabled = disabled # Bug 1150091 - Issue with support-files -[test_bug1315862.html]
new file mode 100644 --- /dev/null +++ b/dom/events/test/pointerevents/test_trigger_fullscreen_by_pointer_events.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Test for triggering Fullscreen by pointer events</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<div id="target" style="width: 50px; height: 50px; background: green"></div> +<script> + +SimpleTest.waitForExplicitFinish(); + +var target = document.getElementById("target"); +target.addEventListener("pointerdown", () => { + target.requestFullscreen(); + target.addEventListener("pointerdown", () => { + document.exitFullscreen(); + }, {once: true}); +}, {once: true}); + +document.addEventListener("fullscreenchange", () => { + if (document.fullscreenElement) { + ok(document.fullscreenElement, target, "fullscreenElement should be the div element"); + // synthesize mouse events to generate pointer events and leave full screen. + synthesizeMouseAtCenter(target, { type: "mousedown" }); + synthesizeMouseAtCenter(target, { type: "mouseup" }); + } else { + SimpleTest.finish(); + } +}); + +function startTest() { + // synthesize mouse events to generate pointer events and enter full screen. + synthesizeMouseAtCenter(target, { type: "mousedown" }); + synthesizeMouseAtCenter(target, { type: "mouseup" }); +} + +SimpleTest.waitForFocus(() => { + SpecialPowers.pushPrefEnv({ + "set": [ + ["full-screen-api.unprefix.enabled", true], + ["full-screen-api.allow-trusted-requests-only", false], + ["dom.w3c_pointer_events.enabled", true] + ] + }, startTest); +}); + +</script> +</body> +</html>
--- a/dom/indexedDB/ActorsParent.cpp +++ b/dom/indexedDB/ActorsParent.cpp @@ -18791,20 +18791,22 @@ Maintenance::DirectoryWork() } // Found a database. if (databasePaths.IsEmpty()) { MOZ_ASSERT(group.IsEmpty()); MOZ_ASSERT(origin.IsEmpty()); int64_t dummyTimeStamp; + bool dummyPersisted; nsCString dummySuffix; if (NS_WARN_IF(NS_FAILED( quotaManager->GetDirectoryMetadata2(originDir, &dummyTimeStamp, + &dummyPersisted, dummySuffix, group, origin)))) { // Not much we can do here... continue; } }
--- a/dom/media/MediaStreamGraph.cpp +++ b/dom/media/MediaStreamGraph.cpp @@ -4004,17 +4004,18 @@ MediaStreamGraph::ApplyAudioContextOpera } void Run() override { mStream->GraphImpl()->ApplyAudioContextOperationImpl(mStream, mStreams, mAudioContextOperation, mPromise); } void RunDuringShutdown() override { - MOZ_ASSERT(false, "We should be reviving the graph?"); + MOZ_ASSERT(mAudioContextOperation == AudioContextOperation::Close, + "We should be reviving the graph?"); } private: // We don't need strong references here for the same reason ControlMessage // doesn't. nsTArray<MediaStream*> mStreams; AudioContextOperation mAudioContextOperation; void* mPromise;
--- a/dom/media/systemservices/MediaParent.cpp +++ b/dom/media/systemservices/MediaParent.cpp @@ -102,38 +102,38 @@ class OriginKeyStore : public nsISupport } } mPersistCount = 0; } private: void PrincipalInfoToString(const ipc::PrincipalInfo& aPrincipalInfo, - nsAutoCString aString) + nsACString& aString) { switch (aPrincipalInfo.type()) { case ipc::PrincipalInfo::TSystemPrincipalInfo: aString.Assign("[System Principal]"); return; case ipc::PrincipalInfo::TNullPrincipalInfo: { const ipc::NullPrincipalInfo& info = aPrincipalInfo.get_NullPrincipalInfo(); aString.Assign(info.spec()); return; } case ipc::PrincipalInfo::TContentPrincipalInfo: { const ipc::ContentPrincipalInfo& info = aPrincipalInfo.get_ContentPrincipalInfo(); - aString.Assign(info.spec()); + aString.Assign(info.originNoSuffix()); nsAutoCString suffix; info.attrs().CreateSuffix(suffix); - suffix.Append(suffix); + aString.Append(suffix); return; } case ipc::PrincipalInfo::TExpandedPrincipalInfo: { const ipc::ExpandedPrincipalInfo& info = aPrincipalInfo.get_ExpandedPrincipalInfo(); aString.Assign("[Expanded Principal [");
--- a/dom/media/test/mochitest.ini +++ b/dom/media/test/mochitest.ini @@ -1091,17 +1091,17 @@ tags = webvtt skip-if = toolkit == 'android' # android(bug 1232305) tags = webvtt [test_trackevent.html] skip-if = toolkit == 'android' # android(bug 1232305) tags = webvtt [test_unseekable.html] skip-if = toolkit == 'android' # android(bug 1232305) [test_video_to_canvas.html] -skip-if = android_version == '15' || android_version == '17' || (android_version == '19' && debug) || android_version == '22' # bug 1320418, android(bug 1232305) +skip-if = toolkit == 'android' # android(bug 1232305), bugs 1320418,1347953,1347954,1348140,1348386 [test_video_in_audio_element.html] skip-if = android_version == '15' || android_version == '17' # bug 1320417, 1326326, android(bug 1232323, bug 1232305) [test_videoDocumentTitle.html] skip-if = toolkit == 'android' # android(bug 1232305) [test_VideoPlaybackQuality.html] skip-if = toolkit == 'android' # android(bug 1232305) [test_VideoPlaybackQuality_disabled.html] skip-if = android_version == '15' || android_version == '17' # android(bug 1232305)
--- a/dom/media/tests/mochitest/test_enumerateDevices.html +++ b/dom/media/tests/mochitest/test_enumerateDevices.html @@ -112,18 +112,17 @@ runTest(async () => { is(differentOriginDevices.length, devices.length); [...sameOriginDevices, ...differentOriginDevices].forEach(d => validateDevice(d)); for (let device of sameOriginDevices) { ok(devices.find(d => d.deviceId == device.deviceId), "Same origin deviceId for " + device.label + " must match"); } for (let device of differentOriginDevices) { - // TODO: s/todo/ok/ once bug 1340163 is fixed. - todo(!devices.find(d => d.deviceId == device.deviceId), + ok(!devices.find(d => d.deviceId == device.deviceId), "Different origin deviceId for " + device.label + " must be different"); } // Check the special case of no devices found. await pushPrefs(["media.navigator.streams.fake", false], ["media.audio_loopback_dev", "none"], ["media.video_loopback_dev", "none"]); devices = await navigator.mediaDevices.enumerateDevices();
--- a/dom/quota/ActorsChild.cpp +++ b/dom/quota/ActorsChild.cpp @@ -267,23 +267,28 @@ QuotaRequestChild::Recv__delete__(const HandleResponse(aResponse.get_nsresult()); break; case RequestResponse::TInitResponse: case RequestResponse::TClearOriginResponse: case RequestResponse::TClearDataResponse: case RequestResponse::TClearAllResponse: case RequestResponse::TResetAllResponse: + case RequestResponse::TPersistResponse: HandleResponse(); break; case RequestResponse::TInitOriginResponse: HandleResponse(aResponse.get_InitOriginResponse().created()); break; + case RequestResponse::TPersistedResponse: + HandleResponse(aResponse.get_PersistedResponse().persisted()); + break; + default: MOZ_CRASH("Unknown response type!"); } return IPC_OK(); } } // namespace quota
--- a/dom/quota/ActorsParent.cpp +++ b/dom/quota/ActorsParent.cpp @@ -1250,16 +1250,73 @@ private: nsresult DoInitOnMainThread() override; void GetResponse(RequestResponse& aResponse) override; }; +class PersistRequestBase + : public QuotaRequestBase +{ + const PrincipalInfo mPrincipalInfo; + +protected: + nsCString mSuffix; + nsCString mGroup; + +public: + bool + Init(Quota* aQuota) override; + +protected: + explicit PersistRequestBase(const PrincipalInfo& aPrincipalInfo); + +private: + nsresult + DoInitOnMainThread() override; +}; + +class PersistedOp final + : public PersistRequestBase +{ + bool mPersisted; + +public: + explicit PersistedOp(const RequestParams& aParams); + +private: + ~PersistedOp() + { } + + nsresult + DoDirectoryWork(QuotaManager* aQuotaManager) override; + + void + GetResponse(RequestResponse& aResponse) override; +}; + +class PersistOp final + : public PersistRequestBase +{ +public: + explicit PersistOp(const RequestParams& aParams); + +private: + ~PersistOp() + { } + + nsresult + DoDirectoryWork(QuotaManager* aQuotaManager) override; + + void + GetResponse(RequestResponse& aResponse) override; +}; + /******************************************************************************* * Helper Functions ******************************************************************************/ template <typename T, bool = mozilla::IsUnsigned<T>::value> struct IntChecker { static void @@ -1847,16 +1904,62 @@ EnsureDirectory(nsIFile* aDirectory, boo NS_ENSURE_SUCCESS(rv, rv); *aCreated = true; } return NS_OK; } +nsresult +EnsureOriginDirectory(nsIFile* aDirectory, bool* aCreated) +{ + AssertIsOnIOThread(); + + nsresult rv; + +#ifndef RELEASE_OR_BETA + bool exists; + rv = aDirectory->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!exists) { + nsString leafName; + nsresult rv = aDirectory->GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!leafName.EqualsLiteral(kChromeOrigin)) { + nsCString spec; + OriginAttributes attrs; + OriginParser::ResultType result = + OriginParser::ParseOrigin(NS_ConvertUTF16toUTF8(leafName), + spec, + &attrs); + if (NS_WARN_IF(result != OriginParser::ValidOrigin)) { + QM_WARNING("Preventing creation of a new origin directory which is not " + "supported by our origin parser or is obsolete!"); + + return NS_ERROR_FAILURE; + } + } + } +#endif + + rv = EnsureDirectory(aDirectory, aCreated); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + enum FileFlag { kTruncateFileFlag, kUpdateFileFlag, kAppendFileFlag }; nsresult GetOutputStream(nsIFile* aFile, @@ -2072,16 +2175,17 @@ CreateDirectoryMetadata(nsIFile* aDirect } return NS_OK; } nsresult CreateDirectoryMetadata2(nsIFile* aDirectory, int64_t aTimestamp, + bool aPersisted, const nsACString& aSuffix, const nsACString& aGroup, const nsACString& aOrigin) { AssertIsOnIOThread(); MOZ_ASSERT(aDirectory); nsCOMPtr<nsIFile> file; @@ -2103,18 +2207,17 @@ CreateDirectoryMetadata2(nsIFile* aDirec MOZ_ASSERT(stream); rv = stream->Write64(aTimestamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - // Reserved for navigator.persist() - rv = stream->WriteBoolean(false); + rv = stream->WriteBoolean(aPersisted); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Reserved data 1 rv = stream->Write32(0); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; @@ -2164,16 +2267,53 @@ CreateDirectoryMetadata2(nsIFile* aDirec if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult +CreateDirectoryMetadataFiles(nsIFile* aDirectory, + bool aPersisted, + const nsACString& aSuffix, + const nsACString& aGroup, + const nsACString& aOrigin, + int64_t* aTimestamp) +{ + AssertIsOnIOThread(); + + int64_t timestamp = PR_Now(); + + nsresult rv = CreateDirectoryMetadata(aDirectory, + timestamp, + aSuffix, + aGroup, + aOrigin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = CreateDirectoryMetadata2(aDirectory, + timestamp, + aPersisted, + aSuffix, + aGroup, + aOrigin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (aTimestamp) { + *aTimestamp = timestamp; + } + return NS_OK; +} + +nsresult GetBinaryInputStream(nsIFile* aDirectory, const nsAString& aFilename, nsIBinaryInputStream** aStream) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aDirectory); MOZ_ASSERT(aStream); @@ -3507,16 +3647,18 @@ QuotaManager::UpdateOriginAccessTime(Per op->RunImmediately(); } } void QuotaManager::RemoveQuota() { + AssertIsOnIOThread(); + MutexAutoLock lock(mQuotaMutex); for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) { nsAutoPtr<GroupInfoPair>& pair = iter.Data(); MOZ_ASSERT(!iter.Key().IsEmpty(), "Empty key!"); MOZ_ASSERT(pair, "Null pointer!"); @@ -3632,16 +3774,50 @@ QuotaManager::GetQuotaObject(Persistence NS_ENSURE_SUCCESS(rv, nullptr); rv = file->InitWithPath(aPath); NS_ENSURE_SUCCESS(rv, nullptr); return GetQuotaObject(aPersistenceType, aGroup, aOrigin, file); } +Nullable<bool> +QuotaManager::OriginPersisted(const nsACString& aGroup, + const nsACString& aOrigin) +{ + AssertIsOnIOThread(); + + MutexAutoLock lock(mQuotaMutex); + + RefPtr<OriginInfo> originInfo = LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, + aGroup, + aOrigin); + if (originInfo) { + return Nullable<bool>(originInfo->LockedPersisted()); + } + + return Nullable<bool>(); +} + +void +QuotaManager::PersistOrigin(const nsACString& aGroup, + const nsACString& aOrigin) +{ + AssertIsOnIOThread(); + + MutexAutoLock lock(mQuotaMutex); + + RefPtr<OriginInfo> originInfo = LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, + aGroup, + aOrigin); + if (originInfo && !originInfo->LockedPersisted()) { + originInfo->LockedPersist(); + } +} + void QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId) { AssertIsOnOwningThread(); for (RefPtr<Client>& client : mClients) { client->AbortOperationsForProcess(aContentParentId); } @@ -3686,23 +3862,25 @@ QuotaManager::RestoreDirectoryMetadata2( } return NS_OK; } nsresult QuotaManager::GetDirectoryMetadata2(nsIFile* aDirectory, int64_t* aTimestamp, + bool* aPersisted, nsACString& aSuffix, nsACString& aGroup, nsACString& aOrigin) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aDirectory); MOZ_ASSERT(aTimestamp); + MOZ_ASSERT(aPersisted); MOZ_ASSERT(mStorageInitialized); nsCOMPtr<nsIBinaryInputStream> binaryStream; nsresult rv = GetBinaryInputStream(aDirectory, NS_LITERAL_STRING(METADATA_V2_FILE_NAME), getter_AddRefs(binaryStream)); NS_ENSURE_SUCCESS(rv, rv); @@ -3745,93 +3923,113 @@ QuotaManager::GetDirectoryMetadata2(nsIF // Currently unused (used to be isApp). bool dummy; rv = binaryStream->ReadBoolean(&dummy); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } *aTimestamp = timestamp; + *aPersisted = persisted; aSuffix = suffix; aGroup = group; aOrigin = origin; return NS_OK; } nsresult QuotaManager::GetDirectoryMetadata2WithRestore(nsIFile* aDirectory, bool aPersistent, int64_t* aTimestamp, + bool* aPersisted, nsACString& aSuffix, nsACString& aGroup, nsACString& aOrigin) { nsresult rv = GetDirectoryMetadata2(aDirectory, aTimestamp, + aPersisted, aSuffix, aGroup, aOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { rv = RestoreDirectoryMetadata2(aDirectory, aPersistent); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = GetDirectoryMetadata2(aDirectory, aTimestamp, + aPersisted, aSuffix, aGroup, aOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return NS_OK; } nsresult -QuotaManager::GetDirectoryMetadata2(nsIFile* aDirectory, int64_t* aTimestamp) +QuotaManager::GetDirectoryMetadata2(nsIFile* aDirectory, + int64_t* aTimestamp, + bool* aPersisted) { AssertIsOnIOThread(); MOZ_ASSERT(aDirectory); - MOZ_ASSERT(aTimestamp); + MOZ_ASSERT(aTimestamp || aPersisted); MOZ_ASSERT(mStorageInitialized); nsCOMPtr<nsIBinaryInputStream> binaryStream; nsresult rv = GetBinaryInputStream(aDirectory, NS_LITERAL_STRING(METADATA_V2_FILE_NAME), getter_AddRefs(binaryStream)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } uint64_t timestamp; rv = binaryStream->Read64(×tamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - *aTimestamp = timestamp; + bool persisted; + if (aPersisted) { + rv = binaryStream->ReadBoolean(&persisted); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + if (aTimestamp) { + *aTimestamp = timestamp; + } + if (aPersisted) { + *aPersisted = persisted; + } return NS_OK; } nsresult QuotaManager::GetDirectoryMetadata2WithRestore(nsIFile* aDirectory, bool aPersistent, - int64_t* aTimestamp) -{ - nsresult rv = GetDirectoryMetadata2(aDirectory, aTimestamp); + int64_t* aTimestamp, + bool* aPersisted) +{ + nsresult rv = GetDirectoryMetadata2(aDirectory, aTimestamp, aPersisted); if (NS_WARN_IF(NS_FAILED(rv))) { rv = RestoreDirectoryMetadata2(aDirectory, aPersistent); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = GetDirectoryMetadata2(aDirectory, aTimestamp); + rv = GetDirectoryMetadata2(aDirectory, aTimestamp, aPersisted); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return NS_OK; } @@ -3894,31 +4092,33 @@ QuotaManager::InitializeRepository(Persi continue; } UNKNOWN_FILE_WARNING(leafName); return NS_ERROR_UNEXPECTED; } int64_t timestamp; + bool persisted; nsCString suffix; nsCString group; nsCString origin; rv = GetDirectoryMetadata2WithRestore(childDirectory, /* aPersistent */ false, ×tamp, + &persisted, suffix, group, origin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = InitializeOrigin(aPersistenceType, group, origin, timestamp, - /* aPersisted */ false, childDirectory); + rv = InitializeOrigin(aPersistenceType, group, origin, timestamp, persisted, + childDirectory); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -4815,107 +5015,62 @@ QuotaManager::EnsureOriginIsInitializedI NS_ENSURE_SUCCESS(rv, rv); } mTemporaryStorageInitialized = true; CheckTemporaryStorageLimits(); } + bool created; + rv = EnsureOriginDirectory(directory, &created); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + int64_t timestamp; - -#ifndef RELEASE_OR_BETA - bool exists; - rv = directory->Exists(&exists); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - if (!exists) { - nsString leafName; - nsresult rv = directory->GetLeafName(leafName); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - if (!leafName.EqualsLiteral(kChromeOrigin)) { - nsCString spec; - OriginAttributes attrs; - OriginParser::ResultType result = - OriginParser::ParseOrigin(NS_ConvertUTF16toUTF8(leafName), - spec, - &attrs); - if (NS_WARN_IF(result != OriginParser::ValidOrigin)) { - QM_WARNING("Preventing creation of a new origin directory which is not " - "supported by our origin parser or is obsolete!"); - - return NS_ERROR_FAILURE; - } - } - } -#endif - - bool created; - rv = EnsureDirectory(directory, &created); - NS_ENSURE_SUCCESS(rv, rv); - if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { if (created) { - timestamp = PR_Now(); - - rv = CreateDirectoryMetadata(directory, - timestamp, - aSuffix, - aGroup, - aOrigin); + rv = CreateDirectoryMetadataFiles(directory, + /* aPersisted */ true, + aSuffix, + aGroup, + aOrigin, + ×tamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - - rv = CreateDirectoryMetadata2(directory, - timestamp, - aSuffix, - aGroup, - aOrigin); - NS_ENSURE_SUCCESS(rv, rv); } else { rv = GetDirectoryMetadata2WithRestore(directory, /* aPersistent */ true, - ×tamp); + ×tamp, + /* aPersisted */ nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(timestamp <= PR_Now()); } rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, timestamp, /* aPersisted */ true, directory); NS_ENSURE_SUCCESS(rv, rv); mInitializedOrigins.AppendElement(aOrigin); } else if (created) { - timestamp = PR_Now(); - - rv = CreateDirectoryMetadata(directory, - timestamp, - aSuffix, - aGroup, - aOrigin); + rv = CreateDirectoryMetadataFiles(directory, + /* aPersisted */ false, + aSuffix, + aGroup, + aOrigin, + ×tamp); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = CreateDirectoryMetadata2(directory, - timestamp, - aSuffix, - aGroup, - aOrigin); - NS_ENSURE_SUCCESS(rv, rv); - rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, timestamp, /* aPersisted */ false, directory); NS_ENSURE_SUCCESS(rv, rv); } directory.forget(aDirectory); *aCreated = created; return NS_OK; @@ -5223,16 +5378,35 @@ QuotaManager::LockedRemoveQuotaForOrigin if (!pair->LockedHasGroupInfos()) { mGroupInfoPairs.Remove(aGroup); } } } } +already_AddRefed<OriginInfo> +QuotaManager::LockedGetOriginInfo(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin) +{ + mQuotaMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); + + GroupInfoPair* pair; + if (mGroupInfoPairs.Get(aGroup, &pair)) { + RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType); + if (groupInfo) { + return groupInfo->LockedGetOriginInfo(aOrigin); + } + } + + return nullptr; +} + void QuotaManager::CheckTemporaryStorageLimits() { AssertIsOnIOThread(); nsTArray<OriginInfo*> doomedOriginInfos; { MutexAutoLock lock(mQuotaMutex); @@ -6130,16 +6304,24 @@ Quota::AllocPQuotaRequestParent(const Re case RequestParams::TClearAllParams: actor = new ResetOrClearOp(/* aClear */ true); break; case RequestParams::TResetAllParams: actor = new ResetOrClearOp(/* aClear */ false); break; + case RequestParams::TPersistedParams: + actor = new PersistedOp(aParams); + break; + + case RequestParams::TPersistParams: + actor = new PersistOp(aParams); + break; + default: MOZ_CRASH("Should never get here!"); } MOZ_ASSERT(actor); // Transfer ownership to IPDL. return actor.forget().take(); @@ -6812,19 +6994,21 @@ ClearRequestBase::DeleteFiles(QuotaManag } bool persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT; int64_t timestamp; nsCString suffix; nsCString group; nsCString origin; + bool persisted; rv = aQuotaManager->GetDirectoryMetadata2WithRestore(file, persistent, ×tamp, + &persisted, suffix, group, origin); if (NS_WARN_IF(NS_FAILED(rv))) { return; } for (uint32_t index = 0; index < 10; index++) { @@ -6976,16 +7160,245 @@ ClearDataOp::DoInitOnMainThread() void ClearDataOp::GetResponse(RequestResponse& aResponse) { AssertIsOnOwningThread(); aResponse = ClearDataResponse(); } +PersistRequestBase::PersistRequestBase(const PrincipalInfo& aPrincipalInfo) + : QuotaRequestBase(/* aExclusive */ false) + , mPrincipalInfo(aPrincipalInfo) +{ + AssertIsOnOwningThread(); +} + +bool +PersistRequestBase::Init(Quota* aQuota) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aQuota); + + if (NS_WARN_IF(!QuotaRequestBase::Init(aQuota))) { + return false; + } + + mPersistenceType.SetValue(PERSISTENCE_TYPE_DEFAULT); + + mNeedsMainThreadInit = true; + + return true; +} + +nsresult +PersistRequestBase::DoInitOnMainThread() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(GetState() == State_Initializing); + MOZ_ASSERT(mNeedsMainThreadInit); + + nsresult rv; + nsCOMPtr<nsIPrincipal> principal = + PrincipalInfoToPrincipal(mPrincipalInfo, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Figure out which origin we're dealing with. + nsCString origin; + rv = QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup, + &origin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mOriginScope.SetFromOrigin(origin); + + return NS_OK; +} + +PersistedOp::PersistedOp(const RequestParams& aParams) + : PersistRequestBase(aParams.get_PersistedParams().principalInfo()) + , mPersisted(false) +{ + MOZ_ASSERT(aParams.type() == RequestParams::TPersistedParams); +} + +nsresult +PersistedOp::DoDirectoryWork(QuotaManager* aQuotaManager) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(!mPersistenceType.IsNull()); + MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_DEFAULT); + MOZ_ASSERT(mOriginScope.IsOrigin()); + + PROFILER_LABEL("Quota", "PersistedOp::DoDirectoryWork", + js::ProfileEntry::Category::OTHER); + + Nullable<bool> persisted = + aQuotaManager->OriginPersisted(mGroup, mOriginScope.GetOrigin()); + + if (!persisted.IsNull()) { + mPersisted = persisted.Value(); + return NS_OK; + } + + // If we get here, it means the origin hasn't been initialized yet. + // Try to get the persisted flag from directory metadata on disk. + + nsCOMPtr<nsIFile> directory; + nsresult rv = aQuotaManager->GetDirectoryForOrigin(mPersistenceType.Value(), + mOriginScope.GetOrigin(), + getter_AddRefs(directory)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = directory->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (exists) { + // Get the persisted flag. + bool persisted; + rv = + aQuotaManager->GetDirectoryMetadata2WithRestore(directory, + /* aPersistent */ false, + /* aTimestamp */ nullptr, + &persisted); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mPersisted = persisted; + } else { + // The directory has not been created yet. + mPersisted = false; + } + + return NS_OK; +} + +void +PersistedOp::GetResponse(RequestResponse& aResponse) +{ + AssertIsOnOwningThread(); + + PersistedResponse persistedResponse; + persistedResponse.persisted() = mPersisted; + + aResponse = persistedResponse; +} + +PersistOp::PersistOp(const RequestParams& aParams) + : PersistRequestBase(aParams.get_PersistParams().principalInfo()) +{ + MOZ_ASSERT(aParams.type() == RequestParams::TPersistParams); +} + +nsresult +PersistOp::DoDirectoryWork(QuotaManager* aQuotaManager) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(!mPersistenceType.IsNull()); + MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_DEFAULT); + MOZ_ASSERT(mOriginScope.IsOrigin()); + + PROFILER_LABEL("Quota", "PersistOp::DoDirectoryWork", + js::ProfileEntry::Category::OTHER); + + // Update directory metadata on disk first. + nsCOMPtr<nsIFile> directory; + nsresult rv = aQuotaManager->GetDirectoryForOrigin(mPersistenceType.Value(), + mOriginScope.GetOrigin(), + getter_AddRefs(directory)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool created; + rv = EnsureOriginDirectory(directory, &created); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (created) { + rv = CreateDirectoryMetadataFiles(directory, + /* aPersisted */ true, + mSuffix, + mGroup, + mOriginScope.GetOrigin(), + /* aTimestamp */ nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + // Get the persisted flag (restore the metadata file if necessary). + bool persisted; + rv = + aQuotaManager->GetDirectoryMetadata2WithRestore(directory, + /* aPersistent */ false, + /* aTimestamp */ nullptr, + &persisted); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!persisted) { + nsCOMPtr<nsIFile> file; + nsresult rv = directory->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = file->Append(NS_LITERAL_STRING(METADATA_V2_FILE_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIBinaryOutputStream> stream; + rv = GetBinaryOutputStream(file, kUpdateFileFlag, getter_AddRefs(stream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(stream); + + // Update origin access time while we are here. + rv = stream->Write64(PR_Now()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Set the persisted flag to true. + rv = stream->WriteBoolean(true); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } + + // Directory metadata has been successfully created/updated, try to update + // OriginInfo too (it's ok if OriginInfo doesn't exist yet). + aQuotaManager->PersistOrigin(mGroup, mOriginScope.GetOrigin()); + + return NS_OK; +} + +void +PersistOp::GetResponse(RequestResponse& aResponse) +{ + AssertIsOnOwningThread(); + + aResponse = PersistResponse(); +} + nsresult StorageDirectoryHelper::GetDirectoryMetadata(nsIFile* aDirectory, int64_t& aTimestamp, nsACString& aGroup, nsACString& aOrigin, Nullable<bool>& aIsApp) { AssertIsOnIOThread(); @@ -8120,16 +8533,17 @@ UpgradeStorageFrom0_0To1_0Helper::Proces aOriginProps.mOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } rv = CreateDirectoryMetadata2(aOriginProps.mDirectory, aOriginProps.mTimestamp, + /* aPersisted */ false, aOriginProps.mSuffix, aOriginProps.mGroup, aOriginProps.mOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsString oldName; @@ -8402,16 +8816,17 @@ UpgradeStorageFrom1_0To2_0Helper::MaybeS aOriginProps.mGroup, aOriginProps.mOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = CreateDirectoryMetadata2(aOriginProps.mDirectory, aOriginProps.mTimestamp, + /* aPersisted */ false, aOriginProps.mSuffix, aOriginProps.mGroup, aOriginProps.mOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr<nsIFile> newFile; @@ -8473,16 +8888,17 @@ UpgradeStorageFrom1_0To2_0Helper::Proces if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } if (aOriginProps.mNeedsRestore2) { rv = CreateDirectoryMetadata2(aOriginProps.mDirectory, aOriginProps.mTimestamp, + /* aPersisted */ false, aOriginProps.mSuffix, aOriginProps.mGroup, aOriginProps.mOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } @@ -8515,18 +8931,20 @@ RestoreDirectoryMetadata2Helper::Restore } nsresult RestoreDirectoryMetadata2Helper::ProcessOriginDirectory( const OriginProps& aOriginProps) { AssertIsOnIOThread(); + // We don't have any approach to restore aPersisted, so reset it to false. nsresult rv = CreateDirectoryMetadata2(aOriginProps.mDirectory, aOriginProps.mTimestamp, + /* aPersisted */ false, aOriginProps.mSuffix, aOriginProps.mGroup, aOriginProps.mOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK;
--- a/dom/quota/PQuota.ipdl +++ b/dom/quota/PQuota.ipdl @@ -54,24 +54,36 @@ struct ClearDataParams struct ClearAllParams { }; struct ResetAllParams { }; +struct PersistedParams +{ + PrincipalInfo principalInfo; +}; + +struct PersistParams +{ + PrincipalInfo principalInfo; +}; + union RequestParams { InitParams; InitOriginParams; ClearOriginParams; ClearDataParams; ClearAllParams; ResetAllParams; + PersistedParams; + PersistParams; }; protocol PQuota { manager PBackground; manages PQuotaRequest; manages PQuotaUsageRequest;
--- a/dom/quota/PQuotaRequest.ipdl +++ b/dom/quota/PQuotaRequest.ipdl @@ -28,25 +28,36 @@ struct ClearDataResponse struct ClearAllResponse { }; struct ResetAllResponse { }; +struct PersistedResponse +{ + bool persisted; +}; + +struct PersistResponse +{ +}; + union RequestResponse { nsresult; InitResponse; InitOriginResponse; ClearOriginResponse; ClearDataResponse; ClearAllResponse; ResetAllResponse; + PersistedResponse; + PersistResponse; }; protocol PQuotaRequest { manager PQuota; child: async __delete__(RequestResponse response);
--- a/dom/quota/QuotaManager.h +++ b/dom/quota/QuotaManager.h @@ -180,51 +180,64 @@ public: nsIFile* aFile); already_AddRefed<QuotaObject> GetQuotaObject(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, const nsAString& aPath); + Nullable<bool> + OriginPersisted(const nsACString& aGroup, + const nsACString& aOrigin); + + void + PersistOrigin(const nsACString& aGroup, + const nsACString& aOrigin); + // Called when a process is being shot down. Aborts any running operations // for the given process. void AbortOperationsForProcess(ContentParentId aContentParentId); nsresult GetDirectoryForOrigin(PersistenceType aPersistenceType, const nsACString& aASCIIOrigin, nsIFile** aDirectory) const; nsresult RestoreDirectoryMetadata2(nsIFile* aDirectory, bool aPersistent); nsresult GetDirectoryMetadata2(nsIFile* aDirectory, int64_t* aTimestamp, + bool* aPersisted, nsACString& aSuffix, nsACString& aGroup, nsACString& aOrigin); nsresult GetDirectoryMetadata2WithRestore(nsIFile* aDirectory, bool aPersistent, int64_t* aTimestamp, + bool* aPersisted, nsACString& aSuffix, nsACString& aGroup, nsACString& aOrigin); nsresult - GetDirectoryMetadata2(nsIFile* aDirectory, int64_t* aTimestamp); + GetDirectoryMetadata2(nsIFile* aDirectory, + int64_t* aTimestamp, + bool* aPersisted); nsresult GetDirectoryMetadata2WithRestore(nsIFile* aDirectory, bool aPersistent, - int64_t* aTimestamp); + int64_t* aTimestamp, + bool* aPersisted); // This is the main entry point into the QuotaManager API. // Any storage API implementation (quota client) that participates in // centralized quota and storage handling should call this method to get // a directory lock which will protect client's files from being deleted // while they are still in use. // After a lock is acquired, client is notified via the open listener's // method DirectoryLockAcquired. If the lock couldn't be acquired, client @@ -433,16 +446,21 @@ private: uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<DirectoryLockImpl>>& aLocks); void LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin); + already_AddRefed<OriginInfo> + LockedGetOriginInfo(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin); + nsresult MaybeUpgradeIndexedDBDirectory(); nsresult MaybeUpgradePersistentStorageDirectory(); nsresult MaybeRemoveOldDirectories();
--- a/dom/quota/QuotaManagerService.cpp +++ b/dom/quota/QuotaManagerService.cpp @@ -55,16 +55,35 @@ TestingPrefChangedCallback(const char* a { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!strcmp(aPrefName, kTestingPref)); MOZ_ASSERT(!aClosure); gTestingMode = Preferences::GetBool(aPrefName); } +nsresult +CheckedPrincipalToPrincipalInfo(nsIPrincipal* aPrincipal, + PrincipalInfo& aPrincipalInfo) +{ + MOZ_ASSERT(aPrincipal); + + nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &aPrincipalInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (aPrincipalInfo.type() != PrincipalInfo::TContentPrincipalInfo && + aPrincipalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) { + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + class AbortOperationsRunnable final : public Runnable { ContentParentId mContentParentId; public: explicit AbortOperationsRunnable(ContentParentId aContentParentId) : mContentParentId(aContentParentId) @@ -535,28 +554,22 @@ QuotaManagerService::InitStoragesForPrin if (NS_WARN_IF(!gTestingMode)) { return NS_ERROR_UNEXPECTED; } RefPtr<Request> request = new Request(); InitOriginParams params; - PrincipalInfo& principalInfo = params.principalInfo(); - - nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo); + nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal, + params.principalInfo()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - if (principalInfo.type() != PrincipalInfo::TContentPrincipalInfo && - principalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) { - return NS_ERROR_UNEXPECTED; - } - Nullable<PersistenceType> persistenceType; rv = NullablePersistenceTypeFromText(aPersistenceType, &persistenceType); if (NS_WARN_IF(NS_FAILED(rv)) || persistenceType.IsNull()) { return NS_ERROR_INVALID_ARG; } params.persistenceType() = persistenceType.Value(); @@ -580,27 +593,22 @@ QuotaManagerService::GetUsageForPrincipa MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPrincipal); MOZ_ASSERT(aCallback); RefPtr<UsageRequest> request = new UsageRequest(aPrincipal, aCallback); UsageParams params; - PrincipalInfo& principalInfo = params.principalInfo(); - nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo); + nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal, + params.principalInfo()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - if (principalInfo.type() != PrincipalInfo::TContentPrincipalInfo && - principalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) { - return NS_ERROR_UNEXPECTED; - } - params.getGroupUsage() = aGetGroupUsage; nsAutoPtr<PendingRequestInfo> info(new UsageRequestInfo(request, params)); rv = InitiateRequest(info); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -650,28 +658,22 @@ QuotaManagerService::ClearStoragesForPri // aClearAll flag is set. return NS_ERROR_INVALID_ARG; } RefPtr<Request> request = new Request(aPrincipal); ClearOriginParams params; - PrincipalInfo& principalInfo = params.principalInfo(); - - nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo); + nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal, + params.principalInfo()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - if (principalInfo.type() != PrincipalInfo::TContentPrincipalInfo && - principalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) { - return NS_ERROR_UNEXPECTED; - } - Nullable<PersistenceType> persistenceType; rv = NullablePersistenceTypeFromText(aPersistenceType, &persistenceType); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_ERROR_INVALID_ARG; } if (persistenceType.IsNull()) { params.persistenceTypeIsExplicit() = false; @@ -713,16 +715,74 @@ QuotaManagerService::Reset(nsIQuotaReque return rv; } request.forget(_retval); return NS_OK; } NS_IMETHODIMP +QuotaManagerService::Persisted(nsIPrincipal* aPrincipal, + nsIQuotaRequest** _retval) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(_retval); + + RefPtr<Request> request = new Request(aPrincipal); + + PersistedParams params; + + nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal, + params.principalInfo()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params)); + + rv = InitiateRequest(info); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + request.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +QuotaManagerService::Persist(nsIPrincipal* aPrincipal, + nsIQuotaRequest** _retval) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(_retval); + + RefPtr<Request> request = new Request(aPrincipal); + + PersistParams params; + + nsresult rv = CheckedPrincipalToPrincipalInfo(aPrincipal, + params.principalInfo()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params)); + + rv = InitiateRequest(info); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + request.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP QuotaManagerService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(NS_IsMainThread()); if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) {
--- a/dom/quota/nsIQuotaManagerService.idl +++ b/dom/quota/nsIQuotaManagerService.idl @@ -95,9 +95,27 @@ interface nsIQuotaManagerService : nsISu * overriding the temp storage limit has changed. * Be carefull, this invalidates all live storages! * * If the dom.quotaManager.testing preference is not true the call will be * a no-op. */ [must_use] nsIQuotaRequest reset(); + + /** + * Check if given origin is persisted. + * + * @param aPrincipal + * A principal for the origin which we want to check. + */ + [must_use] nsIQuotaRequest + persisted(in nsIPrincipal aPrincipal); + + /** + * Persist given origin. + * + * @param aPrincipal + * A principal for the origin which we want to persist. + */ + [must_use] nsIQuotaRequest + persist(in nsIPrincipal aPrincipal); };
--- a/dom/quota/test/unit/head.js +++ b/dom/quota/test/unit/head.js @@ -1,16 +1,17 @@ /** * Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; const NS_OK = Cr.NS_OK; +const NS_ERROR_FAILURE = Cr.NS_ERROR_FAILURE; const NS_ERROR_UNEXPECTED = Cr.NS_ERROR_UNEXPECTED; function is(a, b, msg) { do_check_eq(a, b, Components.stack.caller); } function ok(cond, msg) @@ -141,16 +142,30 @@ function clearChromeOrigin(callback) function reset(callback) { let request = SpecialPowers._getQuotaManager().reset(); request.callback = callback; return request; } +function persist(principal, callback) { + let request = SpecialPowers._getQuotaManager().persist(principal); + request.callback = callback; + + return request; +} + +function persisted(principal, callback) { + let request = SpecialPowers._getQuotaManager().persisted(principal); + request.callback = callback; + + return request; +} + function installPackage(packageName) { let directoryService = Cc["@mozilla.org/file/directory_service;1"] .getService(Ci.nsIProperties); let currentDir = directoryService.get("CurWorkD", Ci.nsIFile); let packageFile = currentDir.clone(); @@ -232,16 +247,25 @@ function compareBuffers(buffer1, buffer2 for (let i = 0; i < buffer1.byteLength; i++) { if (view1[i] != view2[i]) { return false; } } return true; } +function getPersistedFromMetadata(readBuffer) +{ + const persistedPosition = 8; // Persisted state is stored in the 9th byte + let view = + readBuffer instanceof Uint8Array ? readBuffer : new Uint8Array(readBuffer); + + return !!view[persistedPosition]; +} + function grabUsageAndContinueHandler(request) { testGenerator.next(request.usage); } function getUsage(usageHandler) { let principal = Cc["@mozilla.org/systemprincipal;1"]
new file mode 100644 --- /dev/null +++ b/dom/quota/test/unit/test_persist.js @@ -0,0 +1,150 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var testGenerator = testSteps(); + +function* testSteps() +{ + const origins = [ + { + url: "http://default.test.persist", + path: "storage/default/http+++default.test.persist", + persistence: "default" + }, + + { + url: "ftp://ftp.invalid.origin", + path: "storage/default/ftp+++ftp.invalid.origin", + persistence: "default" + }, + ]; + + const metadataFileName = ".metadata-v2"; + + let principal = getPrincipal(origins[0].url); + + info("Persisting an uninitialized origin"); + + // Origin directory doesn't exist yet, so only check the result for + // persisted(). + let request = persisted(principal, continueToNextStepSync); + yield undefined; + + ok(request.resultCode === NS_OK, "Persisted() succeeded"); + ok(!request.result, "The origin is not persisted"); + + info("Verifying persist() does update the metadata"); + + request = persist(principal, continueToNextStepSync); + yield undefined; + + ok(request.resultCode === NS_OK, "Persist() succeeded"); + + let originDir = getRelativeFile(origins[0].path); + let exists = originDir.exists(); + ok(exists, "Origin directory does exist"); + + info("Reading out contents of metadata file"); + + let metadataFile = originDir.clone(); + metadataFile.append(metadataFileName); + + File.createFromNsIFile(metadataFile).then(grabArgAndContinueHandler); + let file = yield undefined; + + let fileReader = new FileReader(); + fileReader.onload = continueToNextStepSync; + fileReader.readAsArrayBuffer(file); + yield undefined; + + let originPersisted = getPersistedFromMetadata(fileReader.result); + ok(originPersisted, "The origin is persisted"); + + info("Verifying persisted()"); + + request = persisted(principal, continueToNextStepSync); + yield undefined; + + ok(request.resultCode === NS_OK, "Persisted() succeeded"); + ok(request.result === originPersisted, "Persisted() concurs with metadata"); + + info("Clearing the origin"); + + // Clear the origin since we'll test the same directory again under different + // circumstances. + clearOrigin(principal, origins[0].persistence, continueToNextStepSync); + yield undefined; + + info("Persisting an already initialized origin"); + + initOrigin(principal, origins[0].persistence, continueToNextStepSync); + yield undefined; + + info("Reading out contents of metadata file"); + + fileReader = new FileReader(); + fileReader.onload = continueToNextStepSync; + fileReader.readAsArrayBuffer(file); + yield undefined; + + originPersisted = getPersistedFromMetadata(fileReader.result); + ok(!originPersisted, "The origin isn't persisted after clearing"); + + info("Verifying persisted()"); + + request = persisted(principal, continueToNextStepSync); + yield undefined; + + ok(request.resultCode === NS_OK, "Persisted() succeeded"); + ok(request.result === originPersisted, "Persisted() concurs with metadata"); + + info("Verifying persist() does update the metadata"); + + request = persist(principal, continueToNextStepSync); + yield undefined; + + ok(request.resultCode === NS_OK, "Persist() succeeded"); + + info("Reading out contents of metadata file"); + + fileReader = new FileReader(); + fileReader.onload = continueToNextStepSync; + fileReader.readAsArrayBuffer(file); + yield undefined; + + originPersisted = getPersistedFromMetadata(fileReader.result); + ok(originPersisted, "The origin is persisted"); + + info("Verifying persisted()"); + + request = persisted(principal, continueToNextStepSync); + yield undefined; + + ok(request.resultCode === NS_OK, "Persisted() succeeded"); + ok(request.result === originPersisted, "Persisted() concurs with metadata"); + + info("Persisting an invalid origin"); + + let invalidPrincipal = getPrincipal(origins[1].url); + + request = persist(invalidPrincipal, continueToNextStepSync); + yield undefined; + + ok(request.resultCode === NS_ERROR_FAILURE, + "Persist() failed because of the invalid origin"); + + originDir = getRelativeFile(origins[1].path); + exists = originDir.exists(); + ok(!exists, "Directory for invalid origin doesn't exist"); + + request = persisted(invalidPrincipal, continueToNextStepSync); + yield undefined; + + ok(request.resultCode === NS_OK, "Persisted() succeeded"); + ok(!request.result, + "The origin isn't persisted since the operation failed"); + + finishTest(); +}
--- a/dom/quota/test/unit/xpcshell.ini +++ b/dom/quota/test/unit/xpcshell.ini @@ -17,12 +17,13 @@ support-files = tempMetadataCleanup_profile.zip [test_basics.js] [test_defaultStorageUpgrade.js] [test_idbSubdirUpgrade.js] [test_morgueCleanup.js] [test_obsoleteOriginAttributesUpgrade.js] [test_originAttributesUpgrade.js] +[test_persist.js] [test_removeAppsUpgrade.js] [test_storagePersistentUpgrade.js] [test_tempMetadataCleanup.js] [test_unknownFiles.js]
--- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -653,17 +653,17 @@ RespondWithHandler::ResolvedCallback(JSC nsCOMPtr<nsIInputStream> body; ir->GetUnfilteredBody(getter_AddRefs(body)); // Errors and redirects may not have a body. if (body) { response->SetBodyUsed(); nsCOMPtr<nsIOutputStream> responseBody; rv = mInterceptedChannel->GetResponseBody(getter_AddRefs(responseBody)); - if (NS_WARN_IF(NS_FAILED(rv))) { + if (NS_WARN_IF(NS_FAILED(rv)) || !responseBody) { return; } const uint32_t kCopySegmentSize = 4096; // Depending on how the Response passed to .respondWith() was created, we may // get a non-buffered input stream. In addition, in some configurations the // destination channel's output stream can be unbuffered. We wrap the output
--- a/dom/workers/ServiceWorkerPrivate.cpp +++ b/dom/workers/ServiceWorkerPrivate.cpp @@ -1575,17 +1575,18 @@ private: } event->PostInit(mInterceptedChannel, mRegistration, mScriptSpec); event->SetTrusted(true); nsresult rv2 = DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(), event, nullptr); - if (NS_WARN_IF(NS_FAILED(rv2)) || !event->WaitToRespond()) { + if ((NS_WARN_IF(NS_FAILED(rv2)) && rv2 != NS_ERROR_XPC_JS_THREW_EXCEPTION) || + !event->WaitToRespond()) { nsCOMPtr<nsIRunnable> runnable; MOZ_ASSERT(!aWorkerPrivate->UsesSystemPrincipal(), "We don't support system-principal serviceworkers"); if (event->DefaultPrevented(CallerType::NonSystem)) { runnable = new CancelChannelRunnable(mInterceptedChannel, mRegistration, NS_ERROR_INTERCEPTION_FAILED); } else {
--- a/dom/workers/ServiceWorkerRegistrar.cpp +++ b/dom/workers/ServiceWorkerRegistrar.cpp @@ -349,17 +349,17 @@ ServiceWorkerRegistrar::ReadData() OriginAttributes attrs; if (!attrs.PopulateFromSuffix(suffix)) { return NS_ERROR_INVALID_ARG; } GET_LINE(entry->scope()); entry->principal() = - mozilla::ipc::ContentPrincipalInfo(attrs, entry->scope()); + mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope()); GET_LINE(entry->currentWorkerURL()); nsAutoCString fetchFlag; GET_LINE(fetchFlag); if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) && !fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) { return NS_ERROR_INVALID_ARG; @@ -390,17 +390,17 @@ ServiceWorkerRegistrar::ReadData() OriginAttributes attrs; if (!attrs.PopulateFromSuffix(suffix)) { return NS_ERROR_INVALID_ARG; } GET_LINE(entry->scope()); entry->principal() = - mozilla::ipc::ContentPrincipalInfo(attrs, entry->scope()); + mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope()); GET_LINE(entry->currentWorkerURL()); nsAutoCString fetchFlag; GET_LINE(fetchFlag); if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) && !fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) { return NS_ERROR_INVALID_ARG; @@ -423,17 +423,17 @@ ServiceWorkerRegistrar::ReadData() OriginAttributes attrs; if (!attrs.PopulateFromSuffix(suffix)) { return NS_ERROR_INVALID_ARG; } GET_LINE(entry->scope()); entry->principal() = - mozilla::ipc::ContentPrincipalInfo(attrs, entry->scope()); + mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope()); GET_LINE(entry->currentWorkerURL()); // default handlesFetch flag to Enabled entry->currentWorkerHandlesFetch() = true; nsAutoCString cacheName; GET_LINE(cacheName); @@ -453,17 +453,17 @@ ServiceWorkerRegistrar::ReadData() } // principal spec is no longer used; we use scope directly instead GET_LINE(unused); GET_LINE(entry->scope()); entry->principal() = - mozilla::ipc::ContentPrincipalInfo(attrs, entry->scope()); + mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope()); GET_LINE(entry->currentWorkerURL()); // default handlesFetch flag to Enabled entry->currentWorkerHandlesFetch() = true; nsAutoCString cacheName; GET_LINE(cacheName); @@ -483,17 +483,17 @@ ServiceWorkerRegistrar::ReadData() } // principal spec is no longer used; we use scope directly instead GET_LINE(unused); GET_LINE(entry->scope()); entry->principal() = - mozilla::ipc::ContentPrincipalInfo(attrs, entry->scope()); + mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope()); // scriptSpec is no more used in latest version. GET_LINE(unused); GET_LINE(entry->currentWorkerURL()); // default handlesFetch flag to Enabled entry->currentWorkerHandlesFetch() = true;
--- a/dom/workers/test/gtest/TestReadWrite.cpp +++ b/dom/workers/test/gtest/TestReadWrite.cpp @@ -238,17 +238,18 @@ TEST(ServiceWorkerRegistrar, TestWriteDa reg.currentWorkerHandlesFetch() = true; reg.cacheName() = NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i)); reg.loadFlags() = nsIRequest::VALIDATE_ALWAYS; nsAutoCString spec; spec.AppendPrintf("spec write %d", i); reg.principal() = - mozilla::ipc::ContentPrincipalInfo(mozilla::OriginAttributes(i, i % 2), spec); + mozilla::ipc::ContentPrincipalInfo(mozilla::OriginAttributes(i, i % 2), + mozilla::void_t(), spec); swr->TestRegisterServiceWorker(reg); } nsresult rv = swr->TestWriteData(); ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail"; } @@ -592,17 +593,18 @@ TEST(ServiceWorkerRegistrar, TestDedupeW reg.currentWorkerHandlesFetch() = true; reg.cacheName() = NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i)); reg.loadFlags() = nsIRequest::VALIDATE_ALWAYS; nsAutoCString spec; spec.AppendPrintf("spec write dedupe/%d", i); reg.principal() = - mozilla::ipc::ContentPrincipalInfo(mozilla::OriginAttributes(0, false), spec); + mozilla::ipc::ContentPrincipalInfo(mozilla::OriginAttributes(0, false), + mozilla::void_t(), spec); swr->TestRegisterServiceWorker(reg); } nsresult rv = swr->TestWriteData(); ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail"; }
--- a/gfx/angle/src/compiler/translator/glslang_lex.cpp +++ b/gfx/angle/src/compiler/translator/glslang_lex.cpp @@ -2333,17 +2333,17 @@ static int yy_get_next_buffer (yyscan_t if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) /* don't do the read, it's not guaranteed to return an EOF, * just force an EOF */ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; else { - yy_size_t num_to_read = + int num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; while ( num_to_read <= 0 ) { /* Not enough room in the buffer - grow it. */ /* just a shorter name for the current buffer */ YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE;
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -1268,16 +1268,19 @@ nsEventStatus AsyncPanZoomController::On // what our final state is to avoid notification churn. StateChangeNotificationBlocker blocker(this); SetState(NOTHING); APZC_LOG("%p starting a fling animation if %f >= %f\n", this, flingVelocity.Length().value, gfxPrefs::APZFlingMinVelocityThreshold()); if (flingVelocity.Length() < gfxPrefs::APZFlingMinVelocityThreshold()) { + // Relieve overscroll now if needed, since we will not transition to a fling + // animation and then an overscroll animation, and relieve it then. + GetCurrentTouchBlock()->GetOverscrollHandoffChain()->SnapBackOverscrolledApzc(this); return nsEventStatus_eConsumeNoDefault; } // Make a local copy of the tree manager pointer and check that it's not // null before calling DispatchFling(). This is necessary because Destroy(), // which nulls out mTreeManager, could be called concurrently. if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) { FlingHandoffState handoffState{flingVelocity,
--- a/gfx/layers/apz/test/gtest/TestBasic.cpp +++ b/gfx/layers/apz/test/gtest/TestBasic.cpp @@ -309,16 +309,33 @@ TEST_F(APZCBasicTester, OverScroll_Bug11 // Sample the second overscroll animation to its end. // If the ending of the first overscroll animation fails to clear state // properly, this will assert. ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost()); SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); } +// Tests that the page doesn't get stuck in an +// overscroll animation after a low-velocity pan. +TEST_F(APZCBasicTester, OverScrollAfterLowVelocityPan_Bug1343775) { + SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); + + // Pan into overscroll with a velocity less than the + // apz.fling_min_velocity_threshold preference. + Pan(apzc, 10, 30); + + EXPECT_TRUE(apzc->IsOverscrolled()); + + apzc->AdvanceAnimationsUntilEnd(); + + // Check that we recovered from overscroll. + EXPECT_FALSE(apzc->IsOverscrolled()); +} + TEST_F(APZCBasicTester, OverScrollAbort) { SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true); // Pan sufficiently to hit overscroll behavior int touchStart = 500; int touchEnd = 10; Pan(apzc, touchStart, touchEnd); EXPECT_TRUE(apzc->IsOverscrolled());
--- a/image/imgFrame.h +++ b/image/imgFrame.h @@ -473,16 +473,19 @@ class DrawableFrameRef final typedef gfx::DataSourceSurface DataSourceSurface; public: DrawableFrameRef() { } explicit DrawableFrameRef(imgFrame* aFrame) : mFrame(aFrame) { + MOZ_ASSERT(aFrame); + MonitorAutoLock lock(aFrame->mMonitor); + // Paletted images won't have a surface so there is no strong reference // to hold on to. Since Draw() and GetSourceSurface() calls will not work // in that case, we should be using RawAccessFrameRef exclusively instead. // See FrameAnimator::GetRawFrame for an example of this behaviour. if (aFrame->mRawSurface) { mRef = new DataSourceSurface::ScopedMap(aFrame->mRawSurface, DataSourceSurface::READ_WRITE); if (!mRef->IsMapped()) {
--- a/intl/locale/LocaleService.cpp +++ b/intl/locale/LocaleService.cpp @@ -458,17 +458,17 @@ LocaleService::NegotiateLanguages(const { if (aStrategy < 0 || aStrategy > 2) { return NS_ERROR_INVALID_ARG; } // Check that the given string contains only ASCII characters valid in tags // (i.e. alphanumerics, plus '-' and '_'), and is non-empty. auto validTagChars = [](const char* s) { - if (!*s) { + if (!s || !*s) { return false; } while (*s) { if (isalnum((unsigned char)*s) || *s == '-' || *s == '_' || *s == '*') { s++; } else { return false; }
--- a/intl/locale/tests/unit/test_localeService_negotiateLanguages.js +++ b/intl/locale/tests/unit/test_localeService_negotiateLanguages.js @@ -81,16 +81,21 @@ const data = { [["fr_Fr"], ["fr-fR"], ["fr-fR"]], [["fr_lAtN_fr"], ["fr-Latn-FR"], ["fr-Latn-FR"]], [["fr_FR"], ["fr_FR"], ["fr_FR"]], [["fr-FR"], ["fr_FR"], ["fr_FR"]], [["fr_Cyrl_FR_mac"], ["fr_Cyrl_fr-mac"], ["fr_Cyrl_fr-mac"]], ], "should not crash on invalid input": [ [null, ["fr-FR"], []], + [[null], [], []], + [[undefined], [], []], + [[undefined], [null], []], + [[undefined], [undefined], []], + [[null], [null], null, null, []], [undefined, ["fr-FR"], []], [2, ["fr-FR"], []], ["fr-FR", ["fr-FR"], []], [["fr-FR"], null, []], [["fr-FR"], undefined, []], [["fr-FR"], 2, []], [["fr-FR"], "fr-FR", []], [["2"], ["ąóżł"], []],
--- a/ipc/glue/BackgroundUtils.cpp +++ b/ipc/glue/BackgroundUtils.cpp @@ -89,16 +89,28 @@ PrincipalInfoToPrincipal(const Principal attrs = info.attrs(); } principal = BasePrincipal::CreateCodebasePrincipal(uri, attrs); rv = principal ? NS_OK : NS_ERROR_FAILURE; if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } + // When the principal is serialized, the origin is extract from it. This + // can fail, and in case, here we will havea Tvoid_t. If we have a string, + // it must match with what the_new_principal.getOrigin returns. + if (info.originNoSuffix().type() == ContentPrincipalInfoOriginNoSuffix::TnsCString) { + nsAutoCString originNoSuffix; + rv = principal->GetOriginNoSuffix(originNoSuffix); + if (NS_WARN_IF(NS_FAILED(rv)) || + !info.originNoSuffix().get_nsCString().Equals(originNoSuffix)) { + MOZ_CRASH("If the origin was in the contentPrincipalInfo, it must be available when deserialized"); + } + } + return principal.forget(); } case PrincipalInfo::TExpandedPrincipalInfo: { const ExpandedPrincipalInfo& info = aPrincipalInfo.get_ExpandedPrincipalInfo(); nsTArray<nsCOMPtr<nsIPrincipal>> whitelist; nsCOMPtr<nsIPrincipal> wlPrincipal; @@ -217,18 +229,28 @@ PrincipalToPrincipalInfo(nsIPrincipal* a } nsAutoCString spec; rv = uri->GetSpec(spec); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + ContentPrincipalInfoOriginNoSuffix infoOriginNoSuffix; + + nsCString originNoSuffix; + rv = aPrincipal->GetOriginNoSuffix(originNoSuffix); + if (NS_WARN_IF(NS_FAILED(rv))) { + infoOriginNoSuffix = void_t(); + } else { + infoOriginNoSuffix = originNoSuffix; + } + *aPrincipalInfo = ContentPrincipalInfo(aPrincipal->OriginAttributesRef(), - spec); + infoOriginNoSuffix, spec); return NS_OK; } bool IsPincipalInfoPrivate(const PrincipalInfo& aPrincipalInfo) { if (aPrincipalInfo.type() != ipc::PrincipalInfo::TContentPrincipalInfo) { return false;
--- a/ipc/glue/PBackgroundSharedTypes.ipdlh +++ b/ipc/glue/PBackgroundSharedTypes.ipdlh @@ -3,19 +3,31 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h"; using struct mozilla::void_t from "ipc/IPCMessageUtils.h"; namespace mozilla { namespace ipc { +union ContentPrincipalInfoOriginNoSuffix +{ + nsCString; + void_t; +}; + struct ContentPrincipalInfo { OriginAttributes attrs; + + // nsIPrincipal.originNoSuffix can fail. In case this happens, this value + // will be set to void_t. So far, this is used only for dom/media. + // It will be removed in bug 1347817. + ContentPrincipalInfoOriginNoSuffix originNoSuffix; + nsCString spec; }; struct SystemPrincipalInfo { }; struct NullPrincipalInfo {
--- a/js/src/devtools/rootAnalysis/annotations.js +++ b/js/src/devtools/rootAnalysis/annotations.js @@ -5,20 +5,16 @@ // RAII types within which we should assume GC is suppressed, eg // AutoSuppressGC. var GCSuppressionTypes = []; // Ignore calls made through these function pointers var ignoreIndirectCalls = { "mallocSizeOf" : true, "aMallocSizeOf" : true, - "_malloc_message" : true, - "je_malloc_message" : true, - "chunk_dalloc" : true, - "chunk_alloc" : true, "__conv" : true, "__convf" : true, "prerrortable.c:callback_newtable" : true, "mozalloc_oom.cpp:void (* gAbortHandler)(size_t)" : true, }; function indirectCallCannotGC(fullCaller, fullVariable) { @@ -170,16 +166,22 @@ var ignoreFunctions = { // Has an indirect call under it by the name "__f", which seemed too // generic to ignore by itself. "void* std::_Locale_impl::~_Locale_impl(int32)" : true, // Bug 1056410 - devirtualization prevents the standard nsISupports::Release heuristic from working "uint32 nsXPConnect::Release()" : true, + // Allocation API + "malloc": true, + "calloc": true, + "realloc": true, + "free": true, + // FIXME! "NS_LogInit": true, "NS_LogTerm": true, "NS_LogAddRef": true, "NS_LogRelease": true, "NS_LogCtor": true, "NS_LogDtor": true, "NS_LogCOMPtrAddRef": true,
--- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -1005,18 +1005,20 @@ Parser<ParseHandler>::hasValidSimpleStri } template <typename ParseHandler> void Parser<ParseHandler>::reportMissingClosing(unsigned errorNumber, unsigned noteNumber, uint32_t openedPos) { auto notes = MakeUnique<JSErrorNotes>(); - if (!notes) + if (!notes) { + ReportOutOfMemory(pc->sc()->context); return; + } uint32_t line, column; tokenStream.srcCoords.lineNumAndColumnIndex(openedPos, &line, &column); const size_t MaxWidth = sizeof("4294967295"); char columnNumber[MaxWidth]; SprintfLiteral(columnNumber, "%" PRIu32, column); char lineNumber[MaxWidth]; @@ -1043,18 +1045,20 @@ Parser<ParseHandler>::reportRedeclaratio return; if (prevPos == DeclaredNameInfo::npos) { errorAt(pos.begin, JSMSG_REDECLARED_VAR, DeclarationKindString(prevKind), bytes.ptr()); return; } auto notes = MakeUnique<JSErrorNotes>(); - if (!notes) + if (!notes) { + ReportOutOfMemory(pc->sc()->context); return; + } uint32_t line, column; tokenStream.srcCoords.lineNumAndColumnIndex(prevPos, &line, &column); const size_t MaxWidth = sizeof("4294967295"); char columnNumber[MaxWidth]; SprintfLiteral(columnNumber, "%" PRIu32, column); char lineNumber[MaxWidth];
--- a/js/src/jit-test/tests/asm.js/testZOOB.js +++ b/js/src/jit-test/tests/asm.js/testZOOB.js @@ -1,15 +1,20 @@ // |jit-test| load(libdir + "asm.js"); load(libdir + "asserts.js"); if (!isAsmJSCompilationAvailable()) quit(); +// This test runs a lot of code and is very slow with --ion-eager. Use a minimum +// Ion warmup trigger of 2 to avoid timeouts. +if (getJitCompilerOptions()["ion.warmup.trigger"] < 5) + setJitCompilerOption("ion.warmup.trigger", 5); + var ab = new ArrayBuffer(BUF_MIN); // Compute a set of interesting indices. indices = [0] for (var i of [4,1024,BUF_MIN,Math.pow(2,30),Math.pow(2,31),Math.pow(2,32),Math.pow(2,33)]) { for (var j of [-2,-1,0,1,2]) { for (var k of [1,-1]) indices.push((i+j)*k);
--- a/js/src/jit/BaselineCacheIRCompiler.cpp +++ b/js/src/jit/BaselineCacheIRCompiler.cpp @@ -377,16 +377,136 @@ BaselineCacheIRCompiler::emitLoadDynamic masm.load32(stubAddress(reader.stubOffset()), scratch); masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch2); masm.loadValue(BaseIndex(scratch2, scratch, TimesOne), output.valueReg()); return true; } bool +BaselineCacheIRCompiler::emitMegamorphicLoadSlotResult() +{ + AutoOutputRegister output(*this); + + Register obj = allocator.useRegister(masm, reader.objOperandId()); + Address nameAddr = stubAddress(reader.stubOffset()); + bool handleMissing = reader.readBool(); + + AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output); + AutoScratchRegister scratch2(allocator, masm); + AutoScratchRegister scratch3(allocator, masm); + + FailurePath* failure; + if (!addFailurePath(&failure)) + return false; + + masm.Push(UndefinedValue()); + masm.moveStackPtrTo(scratch3.get()); + + LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs()); + volatileRegs.takeUnchecked(scratch1); + volatileRegs.takeUnchecked(scratch2); + volatileRegs.takeUnchecked(scratch3); + masm.PushRegsInMask(volatileRegs); + + masm.setupUnalignedABICall(scratch1); + masm.loadJSContext(scratch1); + masm.passABIArg(scratch1); + masm.passABIArg(obj); + masm.loadPtr(nameAddr, scratch2); + masm.passABIArg(scratch2); + masm.passABIArg(scratch3); + if (handleMissing) + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (GetNativeDataProperty<true>))); + else + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (GetNativeDataProperty<false>))); + masm.mov(ReturnReg, scratch2); + masm.PopRegsInMask(volatileRegs); + + masm.loadTypedOrValue(Address(masm.getStackPointer(), 0), output); + masm.adjustStack(sizeof(Value)); + + masm.branchIfFalseBool(scratch2, failure->label()); + return true; +} + +bool +BaselineCacheIRCompiler::emitMegamorphicStoreSlot() +{ + Register obj = allocator.useRegister(masm, reader.objOperandId()); + Address nameAddr = stubAddress(reader.stubOffset()); + ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId()); + + AutoScratchRegister scratch1(allocator, masm); + AutoScratchRegister scratch2(allocator, masm); + + FailurePath* failure; + if (!addFailurePath(&failure)) + return false; + + masm.Push(val); + masm.moveStackPtrTo(val.scratchReg()); + + LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs()); + volatileRegs.takeUnchecked(scratch1); + volatileRegs.takeUnchecked(scratch2); + volatileRegs.takeUnchecked(val); + masm.PushRegsInMask(volatileRegs); + + masm.setupUnalignedABICall(scratch1); + masm.loadJSContext(scratch1); + masm.passABIArg(scratch1); + masm.passABIArg(obj); + masm.loadPtr(nameAddr, scratch2); + masm.passABIArg(scratch2); + masm.passABIArg(val.scratchReg()); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, SetNativeDataProperty)); + masm.mov(ReturnReg, scratch1); + masm.PopRegsInMask(volatileRegs); + + masm.loadValue(Address(masm.getStackPointer(), 0), val); + masm.adjustStack(sizeof(Value)); + + masm.branchIfFalseBool(scratch1, failure->label()); + return true; +} + +bool +BaselineCacheIRCompiler::emitGuardHasGetterSetter() +{ + Register obj = allocator.useRegister(masm, reader.objOperandId()); + Address shapeAddr = stubAddress(reader.stubOffset()); + + AutoScratchRegister scratch1(allocator, masm); + AutoScratchRegister scratch2(allocator, masm); + + FailurePath* failure; + if (!addFailurePath(&failure)) + return false; + + LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs()); + volatileRegs.takeUnchecked(scratch1); + volatileRegs.takeUnchecked(scratch2); + masm.PushRegsInMask(volatileRegs); + + masm.setupUnalignedABICall(scratch1); + masm.loadJSContext(scratch1); + masm.passABIArg(scratch1); + masm.passABIArg(obj); + masm.loadPtr(shapeAddr, scratch2); + masm.passABIArg(scratch2); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ObjectHasGetterSetter)); + masm.mov(ReturnReg, scratch1); + masm.PopRegsInMask(volatileRegs); + + masm.branchIfFalseBool(scratch1, failure->label()); + return true; +} + +bool BaselineCacheIRCompiler::emitCallScriptedGetterResult() { MOZ_ASSERT(engine_ == ICStubEngine::Baseline); Register obj = allocator.useRegister(masm, reader.objOperandId()); Address getterAddr(stubAddress(reader.stubOffset())); AutoScratchRegisterExcluding code(allocator, masm, ArgumentsRectifierReg); @@ -1732,22 +1852,24 @@ BaselineCacheIRCompiler::init(CacheKind return true; } static const size_t MaxOptimizedCacheIRStubs = 16; ICStub* jit::AttachBaselineCacheIRStub(JSContext* cx, const CacheIRWriter& writer, CacheKind kind, ICStubEngine engine, JSScript* outerScript, - ICFallbackStub* stub) + ICFallbackStub* stub, bool* attached) { // We shouldn't GC or report OOM (or any other exception) here. AutoAssertNoPendingException aanpe(cx); JS::AutoCheckCannotGC nogc; + MOZ_ASSERT(!*attached); + if (writer.failed()) return nullptr; // Just a sanity check: the caller should ensure we don't attach an // unlimited number of stubs. MOZ_ASSERT(stub->numOptimizedStubs() < MaxOptimizedCacheIRStubs); enum class CacheIRStubKind { Regular, Monitored, Updated }; @@ -1805,52 +1927,55 @@ jit::AttachBaselineCacheIRStub(JSContext MOZ_ASSERT(code); MOZ_ASSERT(stubInfo); MOZ_ASSERT(stubInfo->stubDataSize() == writer.stubDataSize()); // Ensure we don't attach duplicate stubs. This can happen if a stub failed // for some reason and the IR generator doesn't check for exactly the same // conditions. for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) { + bool updated = false; switch (stubKind) { case CacheIRStubKind::Regular: { if (!iter->isCacheIR_Regular()) continue; auto otherStub = iter->toCacheIR_Regular(); if (otherStub->stubInfo() != stubInfo) continue; - if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart())) + if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart(), &updated)) continue; break; } case CacheIRStubKind::Monitored: { if (!iter->isCacheIR_Monitored()) continue; auto otherStub = iter->toCacheIR_Monitored(); if (otherStub->stubInfo() != stubInfo) continue; - if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart())) + if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart(), &updated)) continue; break; } case CacheIRStubKind::Updated: { if (!iter->isCacheIR_Updated()) continue; auto otherStub = iter->toCacheIR_Updated(); if (otherStub->stubInfo() != stubInfo) continue; - if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart())) + if (!writer.stubDataEqualsMaybeUpdate(otherStub->stubDataStart(), &updated)) continue; break; } } // We found a stub that's exactly the same as the stub we're about to // attach. Just return nullptr, the caller should do nothing in this // case. + if (updated) + *attached = true; return nullptr; } // Time to allocate and attach a new stub. size_t bytesNeeded = stubInfo->stubDataOffset() + stubInfo->stubDataSize(); ICStubSpace* stubSpace = ICStubCompiler::StubSpaceForStub(stubInfo->makesGCCalls(), @@ -1859,34 +1984,37 @@ jit::AttachBaselineCacheIRStub(JSContext if (!newStubMem) return nullptr; switch (stubKind) { case CacheIRStubKind::Regular: { auto newStub = new(newStubMem) ICCacheIR_Regular(code, stubInfo); writer.copyStubData(newStub->stubDataStart()); stub->addNewStub(newStub); + *attached = true; return newStub; } case CacheIRStubKind::Monitored: { ICStub* monitorStub = stub->toMonitoredFallbackStub()->fallbackMonitorStub()->firstMonitorStub(); auto newStub = new(newStubMem) ICCacheIR_Monitored(code, monitorStub, stubInfo); writer.copyStubData(newStub->stubDataStart()); stub->addNewStub(newStub); + *attached = true; return newStub; } case CacheIRStubKind::Updated: { auto newStub = new(newStubMem) ICCacheIR_Updated(code, stubInfo); if (!newStub->initUpdatingChain(cx, stubSpace)) { cx->recoverFromOutOfMemory(); return nullptr; } writer.copyStubData(newStub->stubDataStart()); stub->addNewStub(newStub); + *attached = true; return newStub; } } MOZ_CRASH("Invalid kind"); } uint8_t*
--- a/js/src/jit/BaselineCacheIRCompiler.h +++ b/js/src/jit/BaselineCacheIRCompiler.h @@ -14,14 +14,14 @@ namespace js { namespace jit { class ICFallbackStub; class ICStub; ICStub* AttachBaselineCacheIRStub(JSContext* cx, const CacheIRWriter& writer, CacheKind kind, ICStubEngine engine, JSScript* outerScript, - ICFallbackStub* stub); + ICFallbackStub* stub, bool* attached); } // namespace jit } // namespace js #endif /* jit_BaselineCacheIRCompiler_h */
--- a/js/src/jit/BaselineDebugModeOSR.cpp +++ b/js/src/jit/BaselineDebugModeOSR.cpp @@ -694,18 +694,17 @@ RecompileBaselineScriptForDebugMode(JSCo _(CacheIR_Monitored) \ _(CacheIR_Updated) \ _(Call_Scripted) \ _(Call_AnyScripted) \ _(Call_Native) \ _(Call_ClassHook) \ _(Call_ScriptedApplyArray) \ _(Call_ScriptedApplyArguments) \ - _(Call_ScriptedFunCall) \ - _(GetProp_Generic) + _(Call_ScriptedFunCall) static bool CloneOldBaselineStub(JSContext* cx, DebugModeOSREntryVector& entries, size_t entryIndex) { DebugModeOSREntry& entry = entries[entryIndex]; if (!entry.oldStub) return true;
--- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -798,40 +798,39 @@ DoGetElemFallback(JSContext* cx, Baselin // Handle optimized arguments[i] access. if (!GetElemOptimizedArguments(cx, frame, &lhsCopy, rhs, res, &isOptimizedArgs)) return false; if (isOptimizedArgs) TypeScript::Monitor(cx, frame->script(), pc, res); } bool attached = false; - if (stub->numOptimizedStubs() >= ICGetElem_Fallback::MAX_OPTIMIZED_STUBS) { - // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. - // But for now we just bail. - stub->noteUnoptimizableAccess(); - attached = true; - } - bool isTemporarilyUnoptimizable = false; - if (!attached && !JitOptions.disableCacheIR) { + + if (stub->state().maybeTransition()) + stub->discardStubs(cx); + + if (stub->state().canAttachStub()) { ICStubEngine engine = ICStubEngine::Baseline; - GetPropIRGenerator gen(cx, script, pc, CacheKind::GetElem, + GetPropIRGenerator gen(cx, script, pc, CacheKind::GetElem, stub->state().mode(), &isTemporarilyUnoptimizable, lhs, rhs, CanAttachGetter::Yes); if (gen.tryAttachStub()) { ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), - engine, info.outerScript(cx), stub); + engine, info.outerScript(cx), stub, + &attached); if (newStub) { JitSpew(JitSpew_BaselineIC, " Attached CacheIR stub"); - attached = true; if (gen.shouldNotePreliminaryObjectStub()) newStub->toCacheIR_Monitored()->notePreliminaryObject(); else if (gen.shouldUnlinkPreliminaryObjectStubs()) StripPreliminaryObjectStubs(cx, stub); } } + if (!attached && !isTemporarilyUnoptimizable) + stub->state().trackNotAttached(); } if (!isOptimizedArgs) { if (!GetElementOperation(cx, op, lhsCopy, rhs, res)) return false; TypeScript::Monitor(cx, frame->script(), pc, res); } @@ -952,29 +951,30 @@ DoSetElemFallback(JSContext* cx, Baselin if (obj->is<UnboxedPlainObject>()) { MOZ_ASSERT(!oldShape); if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) oldShape = expando->lastProperty(); } bool isTemporarilyUnoptimizable = false; - bool attached = false; - if (stub->numOptimizedStubs() < ICSetElem_Fallback::MAX_OPTIMIZED_STUBS && - !JitOptions.disableCacheIR) - { - SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, &isTemporarilyUnoptimizable, - objv, index, rhs); + + if (stub->state().maybeTransition()) + stub->discardStubs(cx); + + if (stub->state().canAttachStub()) { + SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, stub->state().mode(), + &isTemporarilyUnoptimizable, objv, index, rhs); if (gen.tryAttachStub()) { ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), - ICStubEngine::Baseline, frame->script(), stub); + ICStubEngine::Baseline, frame->script(), + stub, &attached); if (newStub) { JitSpew(JitSpew_BaselineIC, " Attached CacheIR stub"); - attached = true; SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo()); if (gen.shouldNotePreliminaryObjectStub()) newStub->toCacheIR_Updated()->notePreliminaryObject(); else if (gen.shouldUnlinkPreliminaryObjectStubs()) StripPreliminaryObjectStubs(cx, stub); @@ -1014,37 +1014,33 @@ DoSetElemFallback(JSContext* cx, Baselin // Check if debug mode toggling made the stub invalid. if (stub.invalid()) return true; if (attached) return true; - if (stub->numOptimizedStubs() >= ICSetElem_Fallback::MAX_OPTIMIZED_STUBS) { - // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. - // But for now we just bail. - return true; - } - - if (!JitOptions.disableCacheIR) { - SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, &isTemporarilyUnoptimizable, - objv, index, rhs); + if (stub->state().canAttachStub()) { + SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, stub->state().mode(), + &isTemporarilyUnoptimizable, objv, index, rhs); if (gen.tryAttachAddSlotStub(oldGroup, oldShape)) { ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), - ICStubEngine::Baseline, frame->script(), stub); + ICStubEngine::Baseline, frame->script(), + stub, &attached); if (newStub) { JitSpew(JitSpew_BaselineIC, " Attached CacheIR stub"); - attached = true; SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo()); return true; } } else { gen.trackNotAttached(); } + if (!attached && !isTemporarilyUnoptimizable) + stub->state().trackNotAttached(); } return true; } typedef bool (*DoSetElemFallbackFn)(JSContext*, BaselineFrame*, ICSetElem_Fallback*, Value*, HandleValue, HandleValue, HandleValue); static const VMFunction DoSetElemFallbackInfo = @@ -1203,36 +1199,36 @@ DoInFallback(JSContext* cx, BaselineFram FallbackICSpew(cx, stub, "In"); if (!objValue.isObject()) { ReportValueError(cx, JSMSG_IN_NOT_OBJECT, -1, objValue, nullptr); return false; } - bool attached = false; - - if (stub->numOptimizedStubs() >= ICIn_Fallback::MAX_OPTIMIZED_STUBS) - attached = true; - - RootedScript script(cx, frame->script()); RootedObject obj(cx, &objValue.toObject()); - jsbytecode* pc = stub->icEntry()->pc(script); - - if (!attached && !JitOptions.disableCacheIR) { + + if (stub->state().maybeTransition()) + stub->discardStubs(cx); + + if (stub->state().canAttachStub()) { + RootedScript script(cx, frame->script()); + jsbytecode* pc = stub->icEntry()->pc(script); + ICStubEngine engine = ICStubEngine::Baseline; - InIRGenerator gen(cx, script, pc, key, obj); + InIRGenerator gen(cx, script, pc, stub->state().mode(), key, obj); + bool attached = false; if (gen.tryAttachStub()) { ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), - engine, script, stub); - if (newStub) { + engine, script, stub, &attached); + if (newStub) JitSpew(JitSpew_BaselineIC, " Attached CacheIR stub"); - attached = true; - } } + if (!attached) + stub->state().trackNotAttached(); } bool cond = false; if (!OperatorIn(cx, key, obj, &cond)) return false; res.setBoolean(cond); return true; @@ -1277,33 +1273,31 @@ DoGetNameFallback(JSContext* cx, Baselin mozilla::DebugOnly<JSOp> op = JSOp(*pc); FallbackICSpew(cx, stub, "GetName(%s)", CodeName[JSOp(*pc)]); MOZ_ASSERT(op == JSOP_GETNAME || op == JSOP_GETGNAME); RootedPropertyName name(cx, script->getName(pc)); bool attached = false; - // Attach new stub. - if (stub->numOptimizedStubs() >= ICGetName_Fallback::MAX_OPTIMIZED_STUBS) { - // TODO: Discard all stubs in this IC and replace with generic stub. - attached = true; - } - - if (!attached && !JitOptions.disableCacheIR) { + if (stub->state().maybeTransition()) + stub->discardStubs(cx); + + if (stub->state().canAttachStub()) { ICStubEngine engine = ICStubEngine::Baseline; - GetNameIRGenerator gen(cx, script, pc, envChain, name); + GetNameIRGenerator gen(cx, script, pc, stub->state().mode(), envChain, name); if (gen.tryAttachStub()) { ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), - engine, info.outerScript(cx), stub); - if (newStub) { + engine, info.outerScript(cx), stub, + &attached); + if (newStub) JitSpew(JitSpew_BaselineIC, " Attached CacheIR stub"); - attached = true; - } } + if (!attached) + stub->state().trackNotAttached(); } static_assert(JSOP_GETGNAME_LENGTH == JSOP_GETNAME_LENGTH, "Otherwise our check for JSOP_TYPEOF isn't ok"); if (JSOp(pc[JSOP_GETGNAME_LENGTH]) == JSOP_TYPEOF) { if (!GetEnvironmentName<GetNameMode::TypeOf>(cx, envChain, name, res)) return false; } else { @@ -1513,28 +1507,29 @@ DoSetPropFallback(JSContext* cx, Baselin // There are some reasons we can fail to attach a stub that are temporary. // We want to avoid calling noteUnoptimizableAccess() if the reason we // failed to attach a stub is one of those temporary reasons, since we might // end up attaching a stub for the exact same access later. bool isTemporarilyUnoptimizable = false; bool attached = false; - if (stub->numOptimizedStubs() < ICSetProp_Fallback::MAX_OPTIMIZED_STUBS && - !JitOptions.disableCacheIR) - { + if (stub->state().maybeTransition()) + stub->discardStubs(cx); + + if (stub->state().canAttachStub()) { RootedValue idVal(cx, StringValue(name)); - SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, &isTemporarilyUnoptimizable, - lhs, idVal, rhs); + SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, stub->state().mode(), + &isTemporarilyUnoptimizable, lhs, idVal, rhs); if (gen.tryAttachStub()) { ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), - ICStubEngine::Baseline, frame->script(), stub); + ICStubEngine::Baseline, frame->script(), + stub, &attached); if (newStub) { JitSpew(JitSpew_BaselineIC, " Attached CacheIR stub"); - attached = true; SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo()); if (gen.shouldNotePreliminaryObjectStub()) newStub->toCacheIR_Updated()->notePreliminaryObject(); else if (gen.shouldUnlinkPreliminaryObjectStubs()) StripPreliminaryObjectStubs(cx, stub); } @@ -1578,34 +1573,33 @@ DoSetPropFallback(JSContext* cx, Baselin // Overwrite the LHS on the stack (pushed for the decompiler) with the RHS. MOZ_ASSERT(stack[1] == lhs); stack[1] = rhs; // Check if debug mode toggling made the stub invalid. if (stub.invalid()) return true; - if (!attached && - stub->numOptimizedStubs() < ICSetProp_Fallback::MAX_OPTIMIZED_STUBS && - !JitOptions.disableCacheIR) - { + if (!attached && stub->state().canAttachStub()) { RootedValue idVal(cx, StringValue(name)); - SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, &isTemporarilyUnoptimizable, - lhs, idVal, rhs); + SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, stub->state().mode(), + &isTemporarilyUnoptimizable, lhs, idVal, rhs); if (gen.tryAttachAddSlotStub(oldGroup, oldShape)) { ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), - ICStubEngine::Baseline, frame->script(), stub); + ICStubEngine::Baseline, frame->script(), + stub, &attached); if (newStub) { JitSpew(JitSpew_BaselineIC, " Attached CacheIR stub"); - attached = true; SetUpdateStubData(newStub->toCacheIR_Updated(), gen.typeCheckInfo()); } } else { gen.trackNotAttached(); } + if (!attached && !isTemporarilyUnoptimizable) + stub->state().trackNotAttached(); } if (!attached && !isTemporarilyUnoptimizable) stub->noteUnoptimizableAccess(); return true; }
--- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -371,18 +371,16 @@ class ICGetElem_Fallback : public ICMoni : ICMonitoredFallbackStub(ICStub::GetElem_Fallback, stubCode) { } static const uint16_t EXTRA_NON_NATIVE = 0x1; static const uint16_t EXTRA_NEGATIVE_INDEX = 0x2; static const uint16_t EXTRA_UNOPTIMIZABLE_ACCESS = 0x4; public: - static const uint32_t MAX_OPTIMIZED_STUBS = 16; - void noteNonNativeAccess() { extra_ |= EXTRA_NON_NATIVE; } bool hasNonNativeAccess() const { return extra_ & EXTRA_NON_NATIVE; } void noteNegativeIndex() { @@ -430,18 +428,16 @@ class ICSetElem_Fallback : public ICFall explicit ICSetElem_Fallback(JitCode* stubCode) : ICFallbackStub(ICStub::SetElem_Fallback, stubCode) { } static const size_t HasDenseAddFlag = 0x1; static const size_t HasTypedArrayOOBFlag = 0x2; public: - static const uint32_t MAX_OPTIMIZED_STUBS = 8; - void noteHasDenseAdd() { extra_ |= HasDenseAddFlag; } bool hasDenseAdd() const { return extra_ & HasDenseAddFlag; } void noteHasTypedArrayOOB() { extra_ |= HasTypedArrayOOBFlag; } bool hasTypedArrayOOB() const { return extra_ & HasTypedArrayOOBFlag; } // Compiler for this stub kind. class Compiler : public ICStubCompiler { @@ -465,18 +461,16 @@ class ICIn_Fallback : public ICFallbackS { friend class ICStubSpace; explicit ICIn_Fallback(JitCode* stubCode) : ICFallbackStub(ICStub::In_Fallback, stubCode) { } public: - static const uint32_t MAX_OPTIMIZED_STUBS = 8; - class Compiler : public ICStubCompiler { protected: MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm); public: explicit Compiler(JSContext* cx) : ICStubCompiler(cx, ICStub::In_Fallback, Engine::Baseline) { } @@ -495,17 +489,16 @@ class ICGetName_Fallback : public ICMoni { friend class ICStubSpace; explicit ICGetName_Fallback(JitCode* stubCode) : ICMonitoredFallbackStub(ICStub::GetName_Fallback, stubCode) { } public: - static const uint32_t MAX_OPTIMIZED_STUBS = 8; static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0; void noteUnoptimizableAccess() { extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT); } bool hadUnoptimizableAccess() const { return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT); } @@ -629,18 +622,16 @@ class ICSetProp_Fallback : public ICFall { friend class ICStubSpace; explicit ICSetProp_Fallback(JitCode* stubCode) : ICFallbackStub(ICStub::SetProp_Fallback, stubCode) { } public: - static const uint32_t MAX_OPTIMIZED_STUBS = 8; - static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0; void noteUnoptimizableAccess() { extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT); } bool hadUnoptimizableAccess() const { return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT); }
--- a/js/src/jit/BaselineInspector.cpp +++ b/js/src/jit/BaselineInspector.cpp @@ -1013,16 +1013,97 @@ BaselineInspector::commonGetPropFunction if (!*commonGetter) return false; MOZ_ASSERT(*isOwnProperty == !*holder); MOZ_ASSERT(*isOwnProperty == (receivers.empty() && convertUnboxedGroups.empty())); return true; } +static JSFunction* +GetMegamorphicGetterSetterFunction(ICStub* stub, const CacheIRStubInfo* stubInfo, bool isGetter) +{ + // We match: + // + // GuardIsObject objId + // GuardHasGetterSetter objId propShape + // + // propShape has the getter/setter we're interested in. + + CacheIRReader reader(stubInfo); + + ObjOperandId objId = ObjOperandId(0); + if (!reader.matchOp(CacheOp::GuardIsObject, objId)) + return nullptr; + + if (!reader.matchOp(CacheOp::GuardHasGetterSetter, objId)) + return nullptr; + Shape* propShape = stubInfo->getStubField<Shape*>(stub, reader.stubOffset()); + + JSObject* obj = isGetter ? propShape->getterObject() : propShape->setterObject(); + return &obj->as<JSFunction>(); +} + +bool +BaselineInspector::megamorphicGetterSetterFunction(jsbytecode* pc, bool isGetter, + JSFunction** getterOrSetter) +{ + if (!hasBaselineScript()) + return false; + + *getterOrSetter = nullptr; + const ICEntry& entry = icEntryFromPC(pc); + + for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) { + if (stub->isCacheIR_Monitored()) { + MOZ_ASSERT(isGetter); + JSFunction* getter = + GetMegamorphicGetterSetterFunction(stub, + stub->toCacheIR_Monitored()->stubInfo(), + isGetter); + if (!getter || (*getterOrSetter && *getterOrSetter != getter)) + return false; + *getterOrSetter = getter; + continue; + } + if (stub->isCacheIR_Updated()) { + MOZ_ASSERT(!isGetter); + JSFunction* setter = + GetMegamorphicGetterSetterFunction(stub, + stub->toCacheIR_Updated()->stubInfo(), + isGetter); + if (!setter || (*getterOrSetter && *getterOrSetter != setter)) + return false; + *getterOrSetter = setter; + continue; + } + if (stub->isGetProp_Fallback()) { + if (stub->toGetProp_Fallback()->hadUnoptimizableAccess()) + return false; + if (stub->toGetProp_Fallback()->state().mode() != ICState::Mode::Megamorphic) + return false; + continue; + } + if (stub->isSetProp_Fallback()) { + if (stub->toSetProp_Fallback()->hadUnoptimizableAccess()) + return false; + if (stub->toSetProp_Fallback()->state().mode() != ICState::Mode::Megamorphic) + return false; + continue; + } + + return false; + } + + if (!*getterOrSetter) + return false; + + return true; +} + static bool AddCacheIRSetPropFunction(ICCacheIR_Updated* stub, JSObject** holder, Shape** holderShape, JSFunction** commonSetter, bool* isOwnProperty, BaselineInspector::ReceiverVector& receivers, BaselineInspector::ObjectGroupVector& convertUnboxedGroups) { // We match either an own setter: // @@ -1182,19 +1263,16 @@ BaselineInspector::expectedPropertyAcces return MIRType::Value; continue; case ICStub::GetElem_Fallback: if (stub->toGetElem_Fallback()->hadUnoptimizableAccess()) return MIRType::Value; continue; - case ICStub::GetProp_Generic: - return MIRType::Value; - case ICStub::CacheIR_Monitored: stubType = GetCacheIRExpectedInputType(stub->toCacheIR_Monitored()); if (stubType == MIRType::Value) return MIRType::Value; break; default: MOZ_CRASH("Unexpected stub");
--- a/js/src/jit/BaselineInspector.h +++ b/js/src/jit/BaselineInspector.h @@ -132,16 +132,19 @@ class BaselineInspector // global object) instead. In this case we should only look for Baseline // stubs that performed the same optimization. MOZ_MUST_USE bool commonGetPropFunction(jsbytecode* pc, bool innerized, JSObject** holder, Shape** holderShape, JSFunction** commonGetter, Shape** globalShape, bool* isOwnProperty, ReceiverVector& receivers, ObjectGroupVector& convertUnboxedGroups); + MOZ_MUST_USE bool megamorphicGetterSetterFunction(jsbytecode* pc, bool isGetter, + JSFunction** getterOrSetter); + MOZ_MUST_USE bool commonSetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape, JSFunction** commonSetter, bool* isOwnProperty, ReceiverVector& receivers, ObjectGroupVector& convertUnboxedGroups); MOZ_MUST_USE bool instanceOfData(jsbytecode* pc, Shape** shape, uint32_t* slot, JSObject** prototypeObject); };
--- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -26,29 +26,31 @@ using mozilla::Maybe; const char* js::jit::CacheKindNames[] = { #define DEFINE_KIND(kind) #kind, CACHE_IR_KINDS(DEFINE_KIND) #undef DEFINE_KIND }; -IRGenerator::IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind) +IRGenerator::IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind, + ICState::Mode mode) : writer(cx), cx_(cx), script_(script), pc_(pc), - cacheKind_(cacheKind) + cacheKind_(cacheKind), + mode_(mode) {} GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, - CacheKind cacheKind, bool* isTemporarilyUnoptimizable, - HandleValue val, HandleValue idVal, - CanAttachGetter canAttachGetter) - : IRGenerator(cx, script, pc, cacheKind), + CacheKind cacheKind, ICState::Mode mode, + bool* isTemporarilyUnoptimizable, HandleValue val, + HandleValue idVal, CanAttachGetter canAttachGetter) + : IRGenerator(cx, script, pc, cacheKind, mode), val_(val), idVal_(idVal), isTemporarilyUnoptimizable_(isTemporarilyUnoptimizable), canAttachGetter_(canAttachGetter), preliminaryObjectAction_(PreliminaryObjectAction::None) {} static void @@ -500,47 +502,79 @@ EmitCallGetterResultNoGuards(CacheIRWrit JSFunction* target = &shape->getterValue().toObject().as<JSFunction>(); MOZ_ASSERT(target->hasJITCode()); writer.callScriptedGetterResult(objId, target); writer.typeMonitorResult(); } static void EmitCallGetterResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder, - Shape* shape, ObjOperandId objId) + Shape* shape, ObjOperandId objId, ICState::Mode mode) { - Maybe<ObjOperandId> expandoId; - TestMatchingReceiver(writer, obj, shape, objId, &expandoId); - - if (obj != holder) { - GeneratePrototypeGuards(writer, obj, holder, objId); - - // Guard on the holder's shape. - ObjOperandId holderId = writer.loadObject(holder); - writer.guardShape(holderId, holder->as<NativeObject>().lastProperty()); + // Use the megamorphic guard if we're in megamorphic mode, except if |obj| + // is a Window as GuardHasGetterSetter doesn't support this yet (Window may + // require outerizing). + if (mode == ICState::Mode::Specialized || IsWindow(obj)) { + Maybe<ObjOperandId> expandoId; + TestMatchingReceiver(writer, obj, shape, objId, &expandoId); + + if (obj != holder) { + GeneratePrototypeGuards(writer, obj, holder, objId); + + // Guard on the holder's shape. + ObjOperandId holderId = writer.loadObject(holder); + writer.guardShape(holderId, holder->as<NativeObject>().lastProperty()); + } + } else { + writer.guardHasGetterSetter(objId, shape); } EmitCallGetterResultNoGuards(writer, obj, holder, shape, objId); } +void +GetPropIRGenerator::attachMegamorphicNativeSlot(ObjOperandId objId, jsid id, bool handleMissing) +{ + MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic); + + // The stub handles the missing-properties case only if we're seeing one + // now, to make sure Ion ICs correctly monitor the undefined type. + + if (cacheKind_ == CacheKind::GetProp) { + writer.megamorphicLoadSlotResult(objId, JSID_TO_ATOM(id)->asPropertyName(), + handleMissing); + } else { + MOZ_ASSERT(cacheKind_ == CacheKind::GetElem); + writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId(), handleMissing); + } + writer.typeMonitorResult(); + + trackAttached("MegamorphicNativeSlot"); +} + bool GetPropIRGenerator::tryAttachNative(HandleObject obj, ObjOperandId objId, HandleId id) { RootedShape shape(cx_); RootedNativeObject holder(cx_); NativeGetPropCacheability type = CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_, canAttachGetter_, isTemporarilyUnoptimizable_); MOZ_ASSERT_IF(idempotent(), type == CanAttachNone || (type == CanAttachReadSlot && holder)); switch (type) { case CanAttachNone: return false; case CanAttachReadSlot: + if (mode_ == ICState::Mode::Megamorphic) { + attachMegamorphicNativeSlot(objId, id, holder == nullptr); + return true; + } + maybeEmitIdGuard(id); if (holder) { EnsureTrackPropertyTypes(cx_, holder, id); if (obj == holder) { // See the comment in StripPreliminaryObjectStubs. if (IsPreliminaryObject(obj)) preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary; else @@ -549,17 +583,17 @@ GetPropIRGenerator::tryAttachNative(Hand } EmitReadSlotResult(writer, obj, holder, shape, objId); EmitReadSlotReturn(writer, obj, holder, shape); trackAttached("NativeSlot"); return true; case CanAttachCallGetter: maybeEmitIdGuard(id); - EmitCallGetterResult(writer, obj, holder, shape, objId); + EmitCallGetterResult(writer, obj, holder, shape, objId, mode_); trackAttached("NativeGetter"); return true; } MOZ_CRASH("Bad NativeGetPropCacheability"); } @@ -567,16 +601,21 @@ bool GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj, ObjOperandId objId, HandleId id) { // Attach a stub when the receiver is a WindowProxy and we can do the lookup // on the Window (the global object). if (!IsWindowProxy(obj)) return false; + // If we're megamorphic prefer a generic proxy stub that handles a lot more + // cases. + if (mode_ == ICState::Mode::Megamorphic) + return false; + // This must be a WindowProxy for the current Window/global. Else it would // be a cross-compartment wrapper and IsWindowProxy returns false for // those. MOZ_ASSERT(obj->getClass() == cx_->runtime()->maybeWindowProxyClass()); MOZ_ASSERT(ToWindowIfWindowProxy(obj) == cx_->global()); // Now try to do the lookup on the Window (the current global). HandleObject windowObj = cx_->global(); @@ -612,17 +651,17 @@ GetPropIRGenerator::tryAttachWindowProxy if (!callee->jitInfo() || callee->jitInfo()->needsOuterizedThisObject()) return false; // Guard the incoming object is a WindowProxy and inline a getter call based // on the Window object. maybeEmitIdGuard(id); writer.guardClass(objId, GuardClassKind::WindowProxy); ObjOperandId windowObjId = writer.loadObject(windowObj); - EmitCallGetterResult(writer, windowObj, holder, shape, windowObjId); + EmitCallGetterResult(writer, windowObj, holder, shape, windowObjId, mode_); trackAttached("WindowProxyGetter"); return true; } } MOZ_CRASH("Unreachable"); } @@ -631,16 +670,21 @@ bool GetPropIRGenerator::tryAttachCrossCompartmentWrapper(HandleObject obj, ObjOperandId objId, HandleId id) { // We can only optimize this very wrapper-handler, because others might // have a security policy. if (!IsWrapper(obj) || Wrapper::wrapperHandler(obj) != &CrossCompartmentWrapper::singleton) return false; + // If we're megamorphic prefer a generic proxy stub that handles a lot more + // cases. + if (mode_ == ICState::Mode::Megamorphic) + return false; + RootedObject unwrapped(cx_, Wrapper::wrappedObject(obj)); MOZ_ASSERT(unwrapped == UnwrapOneChecked(obj)); // If we allowed different zones we would have to wrap strings. if (unwrapped->compartment()->zone() != cx_->compartment()->zone()) return false; AutoCompartment ac(cx_, unwrapped); @@ -695,33 +739,36 @@ GetPropIRGenerator::tryAttachCrossCompar EmitReadSlotResult(writer, unwrapped, holder, shape, unwrappedId); EmitReadSlotReturn(writer, unwrapped, holder, shape, /* wrapResult = */ true); trackAttached("CCWSlot"); return true; } bool -GetPropIRGenerator::tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id) +GetPropIRGenerator::tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id, + bool handleDOMProxies) { MOZ_ASSERT(obj->is<ProxyObject>()); writer.guardIsProxy(objId); - // Ensure that the incoming object is not a DOM proxy, so that we can get to - // the specialized stubs - writer.guardNotDOMProxy(objId); - - if (cacheKind_ == CacheKind::GetProp) { + if (!handleDOMProxies) { + // Ensure that the incoming object is not a DOM proxy, so that we can get to + // the specialized stubs + writer.guardNotDOMProxy(objId); + } + + if (cacheKind_ == CacheKind::GetProp || mode_ == ICState::Mode::Specialized) { + maybeEmitIdGuard(id); writer.callProxyGetResult(objId, id); } else { - // We could call maybeEmitIdGuard here and then emit CallProxyGetResult, - // but for GetElem we prefer to attach a stub that can handle any Value - // so we don't attach a new stub for every id. + // Attach a stub that handles every id. MOZ_ASSERT(cacheKind_ == CacheKind::GetElem); + MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic); writer.callProxyGetByValueResult(objId, getElemKeyValueId()); } writer.typeMonitorResult(); trackAttached("GenericProxy"); return true; } @@ -893,33 +940,46 @@ GetPropIRGenerator::tryAttachDOMProxyUns trackAttached("DOMProxyUnshadowed"); return true; } bool GetPropIRGenerator::tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id) { - switch (GetProxyStubType(cx_, obj, id)) { + ProxyStubType type = GetProxyStubType(cx_, obj, id); + if (type == ProxyStubType::None) + return false; + + if (mode_ == ICState::Mode::Megamorphic) + return tryAttachGenericProxy(obj, objId, id, /* handleDOMProxies = */ true); + + switch (type) { case ProxyStubType::None: - return false; + break; case ProxyStubType::DOMExpando: if (tryAttachDOMProxyExpando(obj, objId, id)) return true; if (*isTemporarilyUnoptimizable_) { // Scripted getter without JIT code. Just wait. return false; } MOZ_FALLTHROUGH; // Fall through to the generic shadowed case. case ProxyStubType::DOMShadowed: return tryAttachDOMProxyShadowed(obj, objId, id); case ProxyStubType::DOMUnshadowed: - return tryAttachDOMProxyUnshadowed(obj, objId, id); + if (tryAttachDOMProxyUnshadowed(obj, objId, id)) + return true; + if (*isTemporarilyUnoptimizable_) { + // Scripted getter without JIT code. Just wait. + return false; + } + return tryAttachGenericProxy(obj, objId, id, /* handleDOMProxies = */ true); case ProxyStubType::Generic: - return tryAttachGenericProxy(obj, objId, id); + return tryAttachGenericProxy(obj, objId, id, /* handleDOMProxies = */ false); } MOZ_CRASH("Unexpected ProxyStubType"); } bool GetPropIRGenerator::tryAttachUnboxed(HandleObject obj, ObjOperandId objId, HandleId id) { @@ -1525,18 +1585,19 @@ SetPropIRGenerator::maybeEmitIdGuard(jsi return; } MOZ_ASSERT(cacheKind_ == CacheKind::SetElem); emitIdGuard(setElemKeyValueId(), id); } GetNameIRGenerator::GetNameIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, - HandleObject env, HandlePropertyName name) - : IRGenerator(cx, script, pc, CacheKind::GetName), + ICState::Mode mode, HandleObject env, + HandlePropertyName name) + : IRGenerator(cx, script, pc, CacheKind::GetName, mode), env_(env), name_(name) {} bool GetNameIRGenerator::tryAttachStub() { MOZ_ASSERT(cacheKind_ == CacheKind::GetName); @@ -1746,18 +1807,18 @@ GetNameIRGenerator::tryAttachEnvironment writer.loadEnvironmentDynamicSlotResult(lastObjId, dynamicSlotOffset); } writer.typeMonitorResult(); return true; } InIRGenerator::InIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, - HandleValue key, HandleObject obj) - : IRGenerator(cx, script, pc, CacheKind::In), + ICState::Mode mode, HandleValue key, HandleObject obj) + : IRGenerator(cx, script, pc, CacheKind::In, mode), key_(key), obj_(obj) { } bool InIRGenerator::tryAttachDenseIn(uint32_t index, Int32OperandId indexId, HandleObject obj, ObjOperandId objId) { if (!obj->isNative()) @@ -1950,20 +2011,21 @@ IRGenerator::maybeGuardInt32Index(const *int32IndexId = writer.guardAndGetIndexFromString(strId); return true; } return false; } SetPropIRGenerator::SetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, - CacheKind cacheKind, bool* isTemporarilyUnoptimizable, + CacheKind cacheKind, ICState::Mode mode, + bool* isTemporarilyUnoptimizable, HandleValue lhsVal, HandleValue idVal, HandleValue rhsVal, bool needsTypeBarrier, bool maybeHasExtraIndexedProps) - : IRGenerator(cx, script, pc, cacheKind), + : IRGenerator(cx, script, pc, cacheKind, mode), lhsVal_(lhsVal), idVal_(idVal), rhsVal_(rhsVal), isTemporarilyUnoptimizable_(isTemporarilyUnoptimizable), typeCheckInfo_(cx, needsTypeBarrier), preliminaryObjectAction_(PreliminaryObjectAction::None), attachedTypedArrayOOBStub_(false), maybeHasExtraIndexedProps_(maybeHasExtraIndexedProps) @@ -2088,16 +2150,26 @@ SetPropIRGenerator::tryAttachNativeSetSl // overwritten. Don't attach a stub in this case, so that we don't // execute another write to the property without TI seeing that write. EnsureTrackPropertyTypes(cx_, obj, id); if (!PropertyHasBeenMarkedNonConstant(obj, id)) { *isTemporarilyUnoptimizable_ = true; return false; } + if (mode_ == ICState::Mode::Megamorphic && + cacheKind_ == CacheKind::SetProp && + !IsPreliminaryObject(obj)) + { + writer.megamorphicStoreSlot(objId, JSID_TO_ATOM(id)->asPropertyName(), rhsId); + writer.returnFromIC(); + trackAttached("MegamorphicNativeSlot"); + return true; + } + maybeEmitIdGuard(id); // If we need a property type barrier (always in Baseline, sometimes in // Ion), guard on both the shape and the group. If Ion knows the property // types match, we don't need the group guard. NativeObject* nobj = &obj->as<NativeObject>(); if (typeCheckInfo_.needsTypeBarrier()) writer.guardGroup(objId, nobj->group()); @@ -2328,25 +2400,32 @@ SetPropIRGenerator::tryAttachSetter(Hand { RootedObject holder(cx_); RootedShape propShape(cx_); if (!CanAttachSetter(cx_, pc_, obj, id, &holder, &propShape, isTemporarilyUnoptimizable_)) return false; maybeEmitIdGuard(id); - Maybe<ObjOperandId> expandoId; - TestMatchingReceiver(writer, obj, propShape, objId, &expandoId); - - if (obj != holder) { - GeneratePrototypeGuards(writer, obj, holder, objId); - - // Guard on the holder's shape. - ObjOperandId holderId = writer.loadObject(holder); - writer.guardShape(holderId, holder->as<NativeObject>().lastProperty()); + // Use the megamorphic guard if we're in megamorphic mode, except if |obj| + // is a Window as GuardHasGetterSetter doesn't support this yet (Window may + // require outerizing). + if (mode_ == ICState::Mode::Specialized || IsWindow(obj)) { + Maybe<ObjOperandId> expandoId; + TestMatchingReceiver(writer, obj, propShape, objId, &expandoId); + + if (obj != holder) { + GeneratePrototypeGuards(writer, obj, holder, objId); + + // Guard on the holder's shape. + ObjOperandId holderId = writer.loadObject(holder); + writer.guardShape(holderId, holder->as<NativeObject>().lastProperty()); + } + } else { + writer.guardHasGetterSetter(objId, propShape); } EmitCallSetterNoGuards(writer, obj, holder, propShape, objId, rhsId); trackAttached("Setter"); return true; } @@ -2649,23 +2728,23 @@ SetPropIRGenerator::tryAttachGenericProx if (!handleDOMProxies) { // Ensure that the incoming object is not a DOM proxy, so that we can // get to the specialized stubs. If handleDOMProxies is true, we were // unable to attach a specialized DOM stub, so we just handle all // proxies here. writer.guardNotDOMProxy(objId); } - if (cacheKind_ == CacheKind::SetProp) { + if (cacheKind_ == CacheKind::SetProp || mode_ == ICState::Mode::Specialized) { + maybeEmitIdGuard(id); writer.callProxySet(objId, id, rhsId, IsStrictSetPC(pc_)); } else { - // We could call maybeEmitIdGuard here and then emit CallProxySet, but - // for SetElem we prefer to attach a stub that can handle any Value - // so we don't attach a new stub for every id. + // Attach a stub that handles every id. MOZ_ASSERT(cacheKind_ == CacheKind::SetElem); + MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic); writer.callProxySetByValue(objId, setElemKeyValueId(), rhsId, IsStrictSetPC(pc_)); } writer.returnFromIC(); trackAttached("GenericProxy"); return true; } @@ -2727,25 +2806,36 @@ SetPropIRGenerator::tryAttachDOMProxyUns bool SetPropIRGenerator::tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) { // Don't attach a proxy stub for ops like JSOP_INITELEM. MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); - switch (GetProxyStubType(cx_, obj, id)) { + ProxyStubType type = GetProxyStubType(cx_, obj, id); + if (type == ProxyStubType::None) + return false; + + if (mode_ == ICState::Mode::Megamorphic) + return tryAttachGenericProxy(obj, objId, id, rhsId, /* handleDOMProxies = */ true); + + switch (type) { case ProxyStubType::None: - return false; + break; case ProxyStubType::DOMExpando: case ProxyStubType::DOMShadowed: return tryAttachDOMProxyShadowed(obj, objId, id, rhsId); case ProxyStubType::DOMUnshadowed: if (tryAttachDOMProxyUnshadowed(obj, objId, id, rhsId)) return true; + if (*isTemporarilyUnoptimizable_) { + // Scripted setter without JIT code. Just wait. + return false; + } return tryAttachGenericProxy(obj, objId, id, rhsId, /* handleDOMProxies = */ true); case ProxyStubType::Generic: return tryAttachGenericProxy(obj, objId, id, rhsId, /* handleDOMProxies = */ false); } MOZ_CRASH("Unexpected ProxyStubType"); }
--- a/js/src/jit/CacheIR.h +++ b/js/src/jit/CacheIR.h @@ -8,16 +8,17 @@ #define jit_CacheIR_h #include "mozilla/Maybe.h" #include "NamespaceImports.h" #include "gc/Rooting.h" #include "jit/CompactBuffer.h" +#include "jit/ICState.h" #include "jit/SharedIC.h" namespace js { namespace jit { // CacheIR is an (extremely simple) linear IR language for inline caches. // From this IR, we can generate machine code for Baseline or Ion IC stubs. // @@ -169,21 +170,26 @@ extern const char* CacheKindNames[]; _(GuardSpecificSymbol) \ _(GuardNoDetachedTypedObjects) \ _(GuardMagicValue) \ _(GuardFrameHasNoArgumentsObject) \ _(GuardNoDenseElements) \ _(GuardNoUnboxedExpando) \ _(GuardAndLoadUnboxedExpando) \ _(GuardAndGetIndexFromString) \ + _(GuardHasGetterSetter) \ _(LoadObject) \ _(LoadProto) \ _(LoadEnclosingEnvironment) \ _(LoadWrapperTarget) \ \ + _(MegamorphicLoadSlotResult) \ + _(MegamorphicLoadSlotByValueResult) \ + _(MegamorphicStoreSlot) \ + \ /* See CacheIR.cpp 'DOM proxies' comment. */ \ _(LoadDOMExpandoValue) \ _(LoadDOMExpandoValueGuardGeneration) \ _(LoadDOMExpandoValueIgnoreGeneration)\ _(GuardDOMExpandoMissingOrGuardShape) \ \ _(StoreFixedSlot) \ _(StoreDynamicSlot) \ @@ -412,17 +418,17 @@ class MOZ_RAII CacheIRWriter : public JS // For now, assert we only GC before we append stub fields. MOZ_RELEASE_ASSERT(stubFields_.empty()); } size_t stubDataSize() const { return stubDataSize_; } void copyStubData(uint8_t* dest) const; - bool stubDataEqualsMaybeUpdate(uint8_t* stubData) const; + bool stubDataEqualsMaybeUpdate(uint8_t* stubData, bool* updated) const; bool operandIsDead(uint32_t operandId, uint32_t currentInstruction) const { if (operandId >= operandLastUsed_.length()) return false; return currentInstruction > operandLastUsed_[operandId]; } const uint8_t* codeStart() const { MOZ_ASSERT(!failed()); @@ -530,16 +536,21 @@ class MOZ_RAII CacheIRWriter : public JS Int32OperandId guardAndGetIndexFromString(StringOperandId str) { Int32OperandId res(nextOperandId_++); writeOpWithOperandId(CacheOp::GuardAndGetIndexFromString, str); writeOperandId(res); return res; } + void guardHasGetterSetter(ObjOperandId obj, Shape* shape) { + writeOpWithOperandId(CacheOp::GuardHasGetterSetter, obj); + addStubField(uintptr_t(shape), StubField::Type::Shape); + } + void loadFrameCalleeResult() { writeOp(CacheOp::LoadFrameCalleeResult); } void loadFrameNumActualArgsResult() { writeOp(CacheOp::LoadFrameNumActualArgsResult); } void loadFrameArgumentResult(Int32OperandId index) { writeOpWithOperandId(CacheOp::LoadFrameArgumentResult, index); @@ -743,16 +754,32 @@ class MOZ_RAII CacheIRWriter : public JS } void callProxySetByValue(ObjOperandId obj, ValOperandId id, ValOperandId rhs, bool strict) { writeOpWithOperandId(CacheOp::CallProxySetByValue, obj); writeOperandId(id); writeOperandId(rhs); buffer_.writeByte(uint32_t(strict)); } + void megamorphicLoadSlotResult(ObjOperandId obj, PropertyName* name, bool handleMissing) { + writeOpWithOperandId(CacheOp::MegamorphicLoadSlotResult, obj); + addStubField(uintptr_t(name), StubField::Type::String); + buffer_.writeByte(uint32_t(handleMissing)); + } + void megamorphicLoadSlotByValueResult(ObjOperandId obj, ValOperandId id, bool handleMissing) { + writeOpWithOperandId(CacheOp::MegamorphicLoadSlotByValueResult, obj); + writeOperandId(id); + buffer_.writeByte(uint32_t(handleMissing)); + } + void megamorphicStoreSlot(ObjOperandId obj, PropertyName* name, ValOperandId rhs) { + writeOpWithOperandId(CacheOp::MegamorphicStoreSlot, obj); + addStubField(uintptr_t(name), StubField::Type::String); + writeOperandId(rhs); + } + void loadBooleanResult(bool val) { writeOp(CacheOp::LoadBooleanResult); buffer_.writeByte(uint32_t(val)); } void loadUndefinedResult() { writeOp(CacheOp::LoadUndefinedResult); } void loadFixedSlotResult(ObjOperandId obj, size_t offset) { @@ -940,29 +967,31 @@ class MOZ_RAII CacheIRReader class MOZ_RAII IRGenerator { protected: CacheIRWriter writer; JSContext* cx_; HandleScript script_; jsbytecode* pc_; CacheKind cacheKind_; + ICState::Mode mode_; IRGenerator(const IRGenerator&) = delete; IRGenerator& operator=(const IRGenerator&) = delete; bool maybeGuardInt32Index(const Value& index, ValOperandId indexId, uint32_t* int32Index, Int32OperandId* int32IndexId); void emitIdGuard(ValOperandId valId, jsid id); friend class CacheIRSpewer; public: - explicit IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind); + explicit IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind, + ICState::Mode mode); const CacheIRWriter& writerRef() const { return writer; } CacheKind cacheKind() const { return cacheKind_; } }; enum class CanAttachGetter { Yes, No }; // GetPropIRGenerator generates CacheIR for a GetProp IC. @@ -981,17 +1010,18 @@ class MOZ_RAII GetPropIRGenerator : publ bool tryAttachUnboxedExpando(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachTypedObject(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachObjectLength(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachModuleNamespace(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachWindowProxy(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachCrossCompartmentWrapper(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachFunction(HandleObject obj, ObjOperandId objId, HandleId id); - bool tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id); + bool tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id, + bool handleDOMProxies); bool tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachDOMProxyShadowed(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachDOMProxyUnshadowed(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachPrimitive(ValOperandId valId, HandleId id); bool tryAttachStringChar(ValOperandId valId, ValOperandId indexId); bool tryAttachStringLength(ValOperandId valId, HandleId id); @@ -1007,16 +1037,18 @@ class MOZ_RAII GetPropIRGenerator : publ uint32_t index, Int32OperandId indexId); bool tryAttachUnboxedArrayElement(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId); bool tryAttachTypedElement(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId); bool tryAttachProxyElement(HandleObject obj, ObjOperandId objId); + void attachMegamorphicNativeSlot(ObjOperandId objId, jsid id, bool handleMissing); + ValOperandId getElemKeyValueId() const { MOZ_ASSERT(cacheKind_ == CacheKind::GetElem); return ValOperandId(1); } // No pc if idempotent, as there can be multiple bytecode locations // due to GVN. bool idempotent() const { return pc_ == nullptr; } @@ -1025,18 +1057,18 @@ class MOZ_RAII GetPropIRGenerator : publ // matches |id|. void maybeEmitIdGuard(jsid id); void trackAttached(const char* name); void trackNotAttached(); public: GetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind, - bool* isTemporarilyUnoptimizable, HandleValue val,HandleValue idVal, - CanAttachGetter canAttachGetter); + ICState::Mode mode, bool* isTemporarilyUnoptimizable, HandleValue val, + HandleValue idVal, CanAttachGetter canAttachGetter); bool tryAttachStub(); bool tryAttachIdempotentStub(); bool shouldUnlinkPreliminaryObjectStubs() const { return preliminaryObjectAction_ == PreliminaryObjectAction::Unlink; } bool shouldNotePreliminaryObjectStub() const { @@ -1050,17 +1082,17 @@ class MOZ_RAII GetNameIRGenerator : publ HandleObject env_; HandlePropertyName name_; bool tryAttachGlobalNameValue(ObjOperandId objId, HandleId id); bool tryAttachGlobalNameGetter(ObjOperandId objId, HandleId id); bool tryAttachEnvironmentName(ObjOperandId objId, HandleId id); public: - GetNameIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, + GetNameIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode, HandleObject env, HandlePropertyName name); bool tryAttachStub(); }; // Information used by SetProp/SetElem stubs to check/update property types. class MOZ_RAII PropertyTypeCheckInfo { @@ -1154,18 +1186,18 @@ class MOZ_RAII SetPropIRGenerator : publ ValOperandId rhsId); bool tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId); bool tryAttachProxyElement(HandleObject obj, ObjOperandId objId, ValOperandId rhsId); void trackAttached(const char* name); public: SetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind, - bool* isTemporarilyUnoptimizable, HandleValue lhsVal, HandleValue idVal, - HandleValue rhsVal, bool needsTypeBarrier = true, + ICState::Mode mode, bool* isTemporarilyUnoptimizable, HandleValue lhsVal, + HandleValue idVal, HandleValue rhsVal, bool needsTypeBarrier = true, bool maybeHasExtraIndexedProps = true); bool tryAttachStub(); bool tryAttachAddSlotStub(HandleObjectGroup oldGroup, HandleShape oldShape); void trackNotAttached(); bool shouldUnlinkPreliminaryObjectStubs() const { return preliminaryObjectAction_ == PreliminaryObjectAction::Unlink; @@ -1197,17 +1229,18 @@ class MOZ_RAII InIRGenerator : public IR HandleObject obj, ObjOperandId objId); bool tryAttachNativeInDoesNotExist(HandleId key, ValOperandId keyId, HandleObject obj, ObjOperandId objId); void trackAttached(const char* name); void trackNotAttached(); public: - InIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc, HandleValue key, HandleObject obj); + InIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc, ICState::Mode mode, HandleValue key, + HandleObject obj); bool tryAttachStub(); }; } // namespace jit } // namespace js #endif /* jit_CacheIR_h */
--- a/js/src/jit/CacheIRCompiler.cpp +++ b/js/src/jit/CacheIRCompiler.cpp @@ -956,20 +956,21 @@ jit::TraceCacheIRStub(JSTracer* trc, T* template void jit::TraceCacheIRStub(JSTracer* trc, ICStub* stub, const CacheIRStubInfo* stubInfo); template void jit::TraceCacheIRStub(JSTracer* trc, IonICStub* stub, const CacheIRStubInfo* stubInfo); bool -CacheIRWriter::stubDataEqualsMaybeUpdate(uint8_t* stubData) const +CacheIRWriter::stubDataEqualsMaybeUpdate(uint8_t* stubData, bool* updated) const { MOZ_ASSERT(!failed()); + *updated = false; const uintptr_t* stubDataWords = reinterpret_cast<const uintptr_t*>(stubData); // If DOMExpandoGeneration fields are different but all other stub fields // are exactly the same, we overwrite the old stub data instead of attaching // a new stub, as the old stub is never going to succeed. This works because // even Ion stubs read the DOMExpandoGeneration field from the stub instead // of baking it in. bool expandoGenerationIsDifferent = false; @@ -985,18 +986,20 @@ CacheIRWriter::stubDataEqualsMaybeUpdate if (field.asInt64() != *reinterpret_cast<const uint64_t*>(stubDataWords)) { if (field.type() != StubField::Type::DOMExpandoGeneration) return false; expandoGenerationIsDifferent = true; } stubDataWords += sizeof(uint64_t) / sizeof(uintptr_t); } - if (expandoGenerationIsDifferent) + if (expandoGenerationIsDifferent) { copyStubData(stubData); + *updated = true; + } return true; } HashNumber CacheIRStubKey::hash(const CacheIRStubKey::Lookup& l) { HashNumber hash = mozilla::HashBytes(l.code, l.length); @@ -2197,8 +2200,60 @@ CacheIRCompiler::emitWrapResult() masm.branchTestPtr(Assembler::Zero, obj, obj, failure->label()); // We clobbered the output register, so we have to retag. masm.tagValue(JSVAL_TYPE_OBJECT, obj, output.valueReg()); masm.bind(&done); return true; } + +bool +CacheIRCompiler::emitMegamorphicLoadSlotByValueResult() +{ + AutoOutputRegister output(*this); + + Register obj = allocator.useRegister(masm, reader.objOperandId()); + ValueOperand idVal = allocator.useValueRegister(masm, reader.valOperandId()); + bool handleMissing = reader.readBool(); + + AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); + + FailurePath* failure; + if (!addFailurePath(&failure)) + return false; + + // idVal will be in vp[0], result will be stored in vp[1]. + masm.reserveStack(sizeof(Value)); + masm.Push(idVal); + masm.moveStackPtrTo(idVal.scratchReg()); + + LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs()); + volatileRegs.takeUnchecked(scratch); + volatileRegs.takeUnchecked(idVal); + masm.PushRegsInMask(volatileRegs); + + masm.setupUnalignedABICall(scratch); + masm.loadJSContext(scratch); + masm.passABIArg(scratch); + masm.passABIArg(obj); + masm.passABIArg(idVal.scratchReg()); + if (handleMissing) + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (GetNativeDataPropertyByValue<true>))); + else + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (GetNativeDataPropertyByValue<false>))); + masm.mov(ReturnReg, scratch); + masm.PopRegsInMask(volatileRegs); + + masm.Pop(idVal); + + Label ok; + uint32_t framePushed = masm.framePushed(); + masm.branchIfTrueBool(scratch, &ok); + masm.adjustStack(sizeof(Value)); + masm.jump(failure->label()); + + masm.bind(&ok); + masm.setFramePushed(framePushed); + masm.loadTypedOrValue(Address(masm.getStackPointer(), 0), output); + masm.adjustStack(sizeof(Value)); + return true; +}
--- a/js/src/jit/CacheIRCompiler.h +++ b/js/src/jit/CacheIRCompiler.h @@ -46,16 +46,17 @@ namespace jit { _(LoadStringCharResult) \ _(LoadArgumentsObjectArgResult) \ _(LoadDenseElementResult) \ _(LoadDenseElementHoleResult) \ _(LoadDenseElementExistsResult) \ _(LoadDenseElementHoleExistsResult) \ _(LoadUnboxedArrayElementResult) \ _(LoadTypedElementResult) \ + _(MegamorphicLoadSlotByValueResult) \ _(WrapResult) // Represents a Value on the Baseline frame's expression stack. Slot 0 is the // value on top of the stack (the most recently pushed value), slot 1 is the // value pushed before that, etc. class BaselineFrameSlot { uint32_t slot_;
new file mode 100644 --- /dev/null +++ b/js/src/jit/ICState.h @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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/. */ + +#ifndef jit_ICState_h +#define jit_ICState_h + +#include "jit/JitOptions.h" + +namespace js { +namespace jit { + +// ICState stores information about a Baseline or Ion IC. +class ICState +{ + public: + // When we attach the maximum number of stubs, we discard all stubs and + // transition the IC to Megamorphic to attach stubs that are more generic + // (handle more cases). If we again attach the maximum number of stubs, we + // transition to Generic and (depending on the IC) will either attach a + // single stub that handles everything or stop attaching new stubs. + // + // We also transition to Generic when we repeatedly fail to attach a stub, + // to avoid wasting time trying. + enum class Mode : uint8_t { Specialized = 0, Megamorphic, Generic }; + + private: + Mode mode_; + + // Number of optimized stubs currently attached to this IC. + uint8_t numOptimizedStubs_; + + // Number of times we failed to attach a stub. + uint8_t numFailures_; + + // This is only used for shared Baseline ICs and stored here to save space. + bool invalid_ : 1; + + static const size_t MaxOptimizedStubs = 6; + + void transition(Mode mode) { + MOZ_ASSERT(mode > mode_); + mode_ = mode; + numFailures_ = 0; + } + + MOZ_ALWAYS_INLINE size_t maxFailures() const { + // Allow more failures if we attached stubs. + static_assert(MaxOptimizedStubs == 6, + "numFailures_/maxFailures should fit in uint8_t"); + size_t res = 5 + size_t(40) * numOptimizedStubs_; + MOZ_ASSERT(res <= UINT8_MAX, "numFailures_ should not overflow"); + return res; + } + + public: + ICState() + : invalid_(false) + { + reset(); + } + + Mode mode() const { return mode_; } + size_t numOptimizedStubs() const { return numOptimizedStubs_; } + + MOZ_ALWAYS_INLINE bool canAttachStub() const { + MOZ_ASSERT(numOptimizedStubs_ <= MaxOptimizedStubs); + if (mode_ == Mode::Generic || JitOptions.disableCacheIR) + return false; + return true; + } + + bool invalid() const { return invalid_; } + void setInvalid() { invalid_ = true; } + + // If this returns true, we transitioned to a new mode and the caller + // should discard all stubs. + MOZ_MUST_USE MOZ_ALWAYS_INLINE bool maybeTransition() { + MOZ_ASSERT(numOptimizedStubs_ <= MaxOptimizedStubs); + if (mode_ == Mode::Generic) + return false; + if (numOptimizedStubs_ < MaxOptimizedStubs && numFailures_ < maxFailures()) + return false; + if (numFailures_ == maxFailures() || mode_ == Mode::Megamorphic) { + transition(Mode::Generic); + return true; + } + MOZ_ASSERT(mode_ == Mode::Specialized); + transition(Mode::Megamorphic); + return true; + } + void reset() { + mode_ = Mode::Specialized; + numOptimizedStubs_ = 0; + numFailures_ = 0; + } + void trackAttached() { + // We'd like to assert numOptimizedStubs_ < MaxOptimizedStubs, but + // since this code is also used for non-CacheIR Baseline stubs, assert + // < 16 for now. Note that we do have the stronger assert in other + // methods, because they are only used by CacheIR ICs. + MOZ_ASSERT(numOptimizedStubs_ < 16); + numOptimizedStubs_++; + numFailures_ = 0; + } + void trackNotAttached() { + // Note: we can't assert numFailures_ < maxFailures() because + // maxFailures() depends on numOptimizedStubs_ and it's possible a + // GC discarded stubs before we got here. + numFailures_++; + MOZ_ASSERT(numFailures_ > 0, "numFailures_ should not overflow"); + } + void trackUnlinkedStub() { + MOZ_ASSERT(numOptimizedStubs_ > 0); + numOptimizedStubs_--; + } + void trackUnlinkedAllStubs() { + numOptimizedStubs_ = 0; + } +}; + +} // namespace jit +} // namespace js + +#endif /* jit_ICState_h */
--- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -10792,16 +10792,25 @@ IonBuilder::getPropTryCommonGetter(bool* // If it's an own property or type information is bad, we can still // optimize the getter if we shape guard. obj = addShapeGuardsForGetterSetter(obj, foundProto, lastProperty, receivers, convertUnboxedGroups, isOwnProperty); if (!obj) return abort(AbortReason::Alloc); } + } else if (inspector->megamorphicGetterSetterFunction(pc, /* isGetter = */ true, + &commonGetter)) + { + // Try to use TI to guard on this getter. + if (!testCommonGetterSetter(objTypes, name, /* isGetter = */ true, + commonGetter, &guard)) + { + return Ok(); + } } else { // The Baseline IC didn't have any information we can use. return Ok(); } } bool isDOM = objTypes && objTypes->isDOMClass(constraints()); if (isDOM) @@ -11344,16 +11353,25 @@ IonBuilder::setPropTryCommonSetter(bool* // If it's an own property or type information is bad, we can still // optimize the setter if we shape guard. obj = addShapeGuardsForGetterSetter(obj, foundProto, lastProperty, receivers, convertUnboxedGroups, isOwnProperty); if (!obj) return abort(AbortReason::Alloc); } + } else if (inspector->megamorphicGetterSetterFunction(pc, /* isGetter = */ false, + &commonSetter)) + { + // Try to use TI to guard on this setter. + if (!testCommonGetterSetter(objTypes, name, /* isGetter = */ false, + commonSetter, &guard)) + { + return Ok(); + } } else { // The Baseline IC didn't have any information we can use. return Ok(); } } // Emit common setter.
--- a/js/src/jit/IonCacheIRCompiler.cpp +++ b/js/src/jit/IonCacheIRCompiler.cpp @@ -650,16 +650,136 @@ IonCacheIRCompiler::emitLoadDynamicSlotR AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch); masm.loadTypedOrValue(Address(scratch, offset), output); return true; } bool +IonCacheIRCompiler::emitMegamorphicLoadSlotResult() +{ + AutoOutputRegister output(*this); + + Register obj = allocator.useRegister(masm, reader.objOperandId()); + PropertyName* name = stringStubField(reader.stubOffset())->asAtom().asPropertyName(); + bool handleMissing = reader.readBool(); + + AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output); + AutoScratchRegister scratch2(allocator, masm); + AutoScratchRegister scratch3(allocator, masm); + + FailurePath* failure; + if (!addFailurePath(&failure)) + return false; + + masm.Push(UndefinedValue()); + masm.moveStackPtrTo(scratch3.get()); + + LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs()); + volatileRegs.takeUnchecked(scratch1); + volatileRegs.takeUnchecked(scratch2); + volatileRegs.takeUnchecked(scratch3); + masm.PushRegsInMask(volatileRegs); + + masm.setupUnalignedABICall(scratch1); + masm.loadJSContext(scratch1); + masm.passABIArg(scratch1); + masm.passABIArg(obj); + masm.movePtr(ImmGCPtr(name), scratch2); + masm.passABIArg(scratch2); + masm.passABIArg(scratch3); + if (handleMissing) + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (GetNativeDataProperty<true>))); + else + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (GetNativeDataProperty<false>))); + masm.mov(ReturnReg, scratch2); + masm.PopRegsInMask(volatileRegs); + + masm.loadTypedOrValue(Address(masm.getStackPointer(), 0), output); + masm.adjustStack(sizeof(Value)); + + masm.branchIfFalseBool(scratch2, failure->label()); + return true; +} + +bool +IonCacheIRCompiler::emitMegamorphicStoreSlot() +{ + Register obj = allocator.useRegister(masm, reader.objOperandId()); + PropertyName* name = stringStubField(reader.stubOffset())->asAtom().asPropertyName(); + ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId()); + + AutoScratchRegister scratch1(allocator, masm); + AutoScratchRegister scratch2(allocator, masm); + + FailurePath* failure; + if (!addFailurePath(&failure)) + return false; + + masm.Push(val); + masm.moveStackPtrTo(val.scratchReg()); + + LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs()); + volatileRegs.takeUnchecked(scratch1); + volatileRegs.takeUnchecked(scratch2); + volatileRegs.takeUnchecked(val); + masm.PushRegsInMask(volatileRegs); + + masm.setupUnalignedABICall(scratch1); + masm.loadJSContext(scratch1); + masm.passABIArg(scratch1); + masm.passABIArg(obj); + masm.movePtr(ImmGCPtr(name), scratch2); + masm.passABIArg(scratch2); + masm.passABIArg(val.scratchReg()); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, SetNativeDataProperty)); + masm.mov(ReturnReg, scratch1); + masm.PopRegsInMask(volatileRegs); + + masm.loadValue(Address(masm.getStackPointer(), 0), val); + masm.adjustStack(sizeof(Value)); + + masm.branchIfFalseBool(scratch1, failure->label()); + return true; +} + +bool +IonCacheIRCompiler::emitGuardHasGetterSetter() +{ + Register obj = allocator.useRegister(masm, reader.objOperandId()); + Shape* shape = shapeStubField(reader.stubOffset()); + + AutoScratchRegister scratch1(allocator, masm); + AutoScratchRegister scratch2(allocator, masm); + + FailurePath* failure; + if (!addFailurePath(&failure)) + return false; + + LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs()); + volatileRegs.takeUnchecked(scratch1); + volatileRegs.takeUnchecked(scratch2); + masm.PushRegsInMask(volatileRegs); + + masm.setupUnalignedABICall(scratch1); + masm.loadJSContext(scratch1); + masm.passABIArg(scratch1); + masm.passABIArg(obj); + masm.movePtr(ImmGCPtr(shape), scratch2); + masm.passABIArg(scratch2); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ObjectHasGetterSetter)); + masm.mov(ReturnReg, scratch1); + masm.PopRegsInMask(volatileRegs); + + masm.branchIfFalseBool(scratch1, failure->label()); + return true; +} + +bool IonCacheIRCompiler::emitCallScriptedGetterResult() { AutoSaveLiveRegisters save(*this); AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, reader.objOperandId()); JSFunction* target = &objectStubField(reader.stubOffset())->as<JSFunction>(); AutoScratchRegister scratch(allocator, masm); @@ -1761,31 +1881,34 @@ IonCacheIRCompiler::emitLoadDOMExpandoVa scratch2, failure->label()); // Load expandoAndGeneration->expando into the output Value register. masm.loadValue(Address(output.scratchReg(), ExpandoAndGeneration::offsetOfExpando()), output); return true; } -bool +void IonIC::attachCacheIRStub(JSContext* cx, const CacheIRWriter& writer, CacheKind kind, - IonScript* ionScript, const PropertyTypeCheckInfo* typeCheckInfo) + IonScript* ionScript, bool* attached, + const PropertyTypeCheckInfo* typeCheckInfo) { // We shouldn't GC or report OOM (or any other exception) here. AutoAssertNoPendingException aanpe(cx); JS::AutoCheckCannotGC nogc; + MOZ_ASSERT(!*attached); + // SetProp/SetElem stubs must have non-null typeCheckInfo. MOZ_ASSERT(!!typeCheckInfo == (kind == CacheKind::SetProp || kind == CacheKind::SetElem)); // Do nothing if the IR generator failed or triggered a GC that invalidated // the script. if (writer.failed() || ionScript->invalidated()) - return false; + return; JitZone* jitZone = cx->zone()->jitZone(); uint32_t stubDataOffset = sizeof(IonICStub); // Try to reuse a previously-allocated CacheIRStubInfo. CacheIRStubKey::Lookup lookup(kind, ICStubEngine::IonIC, writer.codeStart(), writer.codeLength()); CacheIRStubInfo* stubInfo = jitZone->getIonCacheIRStubInfo(lookup); @@ -1795,55 +1918,62 @@ IonIC::attachCacheIRStub(JSContext* cx, // the stub info HashSet, so we don't have to worry about freeing // it below. // For Ion ICs, we don't track/use the makesGCCalls flag, so just pass true. bool makesGCCalls = true; stubInfo = CacheIRStubInfo::New(kind, ICStubEngine::IonIC, makesGCCalls, stubDataOffset, writer); if (!stubInfo) - return false; + return; CacheIRStubKey key(stubInfo); if (!jitZone->putIonCacheIRStubInfo(lookup, key)) - return false; + return; } MOZ_ASSERT(stubInfo); // Ensure we don't attach duplicate stubs. This can happen if a stub failed // for some reason and the IR generator doesn't check for exactly the same // conditions. for (IonICStub* stub = firstStub_; stub; stub = stub->next()) { if (stub->stubInfo() != stubInfo) continue; - if (!writer.stubDataEqualsMaybeUpdate(stub->stubDataStart())) + bool updated = false; + if (!writer.stubDataEqualsMaybeUpdate(stub->stubDataStart(), &updated)) continue; - return true; + if (updated || (typeCheckInfo && typeCheckInfo->needsTypeBarrier())) { + // We updated a stub or have a stub that requires property type + // checks. In this case the stub will likely handle more cases in + // the future and we shouldn't deoptimize. + *attached = true; + } + return; } size_t bytesNeeded = stubInfo->stubDataOffset() + stubInfo->stubDataSize(); // Allocate the IonICStub in the optimized stub space. Ion stubs and // CacheIRStubInfo instances for Ion stubs can be purged on GC. That's okay // because the stub code is rooted separately when we make a VM call, and // stub code should never access the IonICStub after making a VM call. The // IonICStub::poison method poisons the stub to catch bugs in this area. ICStubSpace* stubSpace = cx->zone()->jitZone()->optimizedStubSpace(); void* newStubMem = stubSpace->alloc(bytesNeeded); if (!newStubMem) - return false; + return; IonICStub* newStub = new(newStubMem) IonICStub(fallbackLabel_.raw(), stubInfo); writer.copyStubData(newStub->stubDataStart()); JitContext jctx(cx, nullptr); IonCacheIRCompiler compiler(cx, writer, this, ionScript, newStub, typeCheckInfo); if (!compiler.init()) - return false; + return; JitCode* code = compiler.compile(); if (!code) - return false; + return; attachStub(newStub, code); - return true; + *attached = true; }
--- a/js/src/jit/IonIC.cpp +++ b/js/src/jit/IonIC.cpp @@ -48,17 +48,17 @@ IonIC::scratchRegisterForEntryJump() case CacheKind::In: MOZ_CRASH("Baseline-specific for now"); } MOZ_CRASH("Invalid kind"); } void -IonIC::reset(Zone* zone) +IonIC::discardStubs(Zone* zone) { if (firstStub_ && zone->needsIncrementalBarrier()) { // We are removing edges from IonIC to gcthings. Perform one final trace // of the stub for incremental GC, as it must know about those edges. trace(zone->barrierTracer()); } #ifdef JS_CRASH_DIAGNOSTICS @@ -67,17 +67,24 @@ IonIC::reset(Zone* zone) IonICStub* next = stub->next(); stub->poison(); stub = next; } #endif firstStub_ = nullptr; codeRaw_ = fallbackLabel_.raw(); - numStubs_ = 0; + state_.trackUnlinkedAllStubs(); +} + +void +IonIC::reset(Zone* zone) +{ + discardStubs(zone); + state_.reset(); } void IonIC::trace(JSTracer* trc) { if (script_) TraceManuallyBarrieredEdge(trc, &script_, "IonIC::script_"); @@ -102,69 +109,48 @@ IonIC::togglePreBarriers(bool enabled, R JitCode* code = JitCode::FromExecutable(nextCodeRaw); code->togglePreBarriers(enabled, reprotect); nextCodeRaw = stub->nextCodeRaw(); } MOZ_ASSERT(nextCodeRaw == fallbackLabel_.raw()); } -void -IonGetPropertyIC::maybeDisable(Zone* zone, bool attached) -{ - if (attached) { - failedUpdates_ = 0; - return; - } - - if (!canAttachStub() && kind() == CacheKind::GetProp) { - // Don't disable the cache (and discard stubs) if we have a GETPROP and - // attached the maximum number of stubs. This can happen when JS code - // uses an AST-like data structure and accesses a field of a "base - // class", like node.nodeType. This should be temporary until we handle - // this case better, see bug 1107515. - return; - } - - if (++failedUpdates_ > MAX_FAILED_UPDATES) { - JitSpew(JitSpew_IonIC, "Disable inline cache"); - disable(zone); - } -} - /* static */ bool IonGetPropertyIC::update(JSContext* cx, HandleScript outerScript, IonGetPropertyIC* ic, HandleValue val, HandleValue idVal, MutableHandleValue res) { // Override the return value if we are invalidated (bug 728188). IonScript* ionScript = outerScript->ionScript(); AutoDetectInvalidation adi(cx, res, ionScript); // If the IC is idempotent, we will redo the op in the interpreter. if (ic->idempotent()) adi.disable(); + if (ic->state().maybeTransition()) + ic->discardStubs(cx->zone()); + bool attached = false; - if (!JitOptions.disableCacheIR && !ic->disabled()) { - if (ic->canAttachStub()) { - // IonBuilder calls PropertyReadNeedsTypeBarrier to determine if it - // needs a type barrier. Unfortunately, PropertyReadNeedsTypeBarrier - // does not account for getters, so we should only attach a getter - // stub if we inserted a type barrier. - CanAttachGetter canAttachGetter = - ic->monitoredResult() ? CanAttachGetter::Yes : CanAttachGetter::No; - jsbytecode* pc = ic->idempotent() ? nullptr : ic->pc(); - bool isTemporarilyUnoptimizable; - GetPropIRGenerator gen(cx, outerScript, pc, ic->kind(), &isTemporarilyUnoptimizable, - val, idVal, canAttachGetter); - if (ic->idempotent() ? gen.tryAttachIdempotentStub() : gen.tryAttachStub()) { - attached = ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript); - } - } - ic->maybeDisable(cx->zone(), attached); + if (ic->state().canAttachStub()) { + // IonBuilder calls PropertyReadNeedsTypeBarrier to determine if it + // needs a type barrier. Unfortunately, PropertyReadNeedsTypeBarrier + // does not account for getters, so we should only attach a getter + // stub if we inserted a type barrier. + CanAttachGetter canAttachGetter = + ic->monitoredResult() ? CanAttachGetter::Yes : CanAttachGetter::No; + jsbytecode* pc = ic->idempotent() ? nullptr : ic->pc(); + bool isTemporarilyUnoptimizable = false; + GetPropIRGenerator gen(cx, outerScript, pc, ic->kind(), ic->state().mode(), + &isTemporarilyUnoptimizable, val, idVal, canAttachGetter); + if (ic->idempotent() ? gen.tryAttachIdempotentStub() : gen.tryAttachStub()) + ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached); + + if (!attached && !isTemporarilyUnoptimizable) + ic->state().trackNotAttached(); } if (!attached && ic->idempotent()) { // Invalidate the cache if the property was not found, or was found on // a non-native object. This ensures: // 1) The property read has no observable side-effects. // 2) There's no need to dynamically monitor the return type. This would // be complicated since (due to GVN) there can be multiple pc's @@ -205,36 +191,42 @@ IonGetPropertyIC::update(JSContext* cx, IonSetPropertyIC::update(JSContext* cx, HandleScript outerScript, IonSetPropertyIC* ic, HandleObject obj, HandleValue idVal, HandleValue rhs) { RootedShape oldShape(cx); RootedObjectGroup oldGroup(cx); IonScript* ionScript = outerScript->ionScript(); bool attached = false; - if (!JitOptions.disableCacheIR && ic->canAttachStub()) { + bool isTemporarilyUnoptimizable = false; + + if (ic->state().maybeTransition()) + ic->discardStubs(cx->zone()); + + if (ic->state().canAttachStub()) { oldShape = obj->maybeShape(); oldGroup = JSObject::getGroup(cx, obj); if (!oldGroup) return false; if (obj->is<UnboxedPlainObject>()) { MOZ_ASSERT(!oldShape); if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) oldShape = expando->lastProperty(); } RootedValue objv(cx, ObjectValue(*obj)); RootedScript script(cx, ic->script()); jsbytecode* pc = ic->pc(); bool isTemporarilyUnoptimizable; - SetPropIRGenerator gen(cx, script, pc, ic->kind(), &isTemporarilyUnoptimizable, + SetPropIRGenerator gen(cx, script, pc, ic->kind(), ic->state().mode(), + &isTemporarilyUnoptimizable, objv, idVal, rhs, ic->needsTypeBarrier(), ic->guardHoles()); if (gen.tryAttachStub()) { - attached = ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), - ionScript, gen.typeCheckInfo()); + ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached, + gen.typeCheckInfo()); } } jsbytecode* pc = ic->pc(); if (ic->kind() == CacheKind::SetElem) { if (IsPropertyInitOp(JSOp(*pc))) { if (!InitElemOperation(cx, pc, obj, idVal, rhs)) return false; @@ -252,51 +244,53 @@ IonSetPropertyIC::update(JSContext* cx, InitGlobalLexicalOperation(cx, &cx->global()->lexicalEnvironment(), script, pc, rhs); } else { RootedPropertyName name(cx, idVal.toString()->asAtom().asPropertyName()); if (!SetProperty(cx, obj, name, rhs, ic->strict(), pc)) return false; } } - if (!attached && !JitOptions.disableCacheIR && ic->canAttachStub()) { + if (!attached && ic->state().canAttachStub()) { RootedValue objv(cx, ObjectValue(*obj)); RootedScript script(cx, ic->script()); jsbytecode* pc = ic->pc(); - bool isTemporarilyUnoptimizable; - SetPropIRGenerator gen(cx, script, pc, ic->kind(), &isTemporarilyUnoptimizable, + SetPropIRGenerator gen(cx, script, pc, ic->kind(), ic->state().mode(), + &isTemporarilyUnoptimizable, objv, idVal, rhs, ic->needsTypeBarrier(), ic->guardHoles()); if (gen.tryAttachAddSlotStub(oldGroup, oldShape)) { - attached = ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), - ionScript, gen.typeCheckInfo()); + ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached, + gen.typeCheckInfo()); } else { gen.trackNotAttached(); } + + if (!attached && !isTemporarilyUnoptimizable) + ic->state().trackNotAttached(); } return true; } uint8_t* IonICStub::stubDataStart() { return reinterpret_cast<uint8_t*>(this) + stubInfo_->stubDataOffset(); } void IonIC::attachStub(IonICStub* newStub, JitCode* code) { - MOZ_ASSERT(canAttachStub()); MOZ_ASSERT(newStub); MOZ_ASSERT(code); if (firstStub_) { IonICStub* last = firstStub_; while (IonICStub* next = last->next()) last = next; last->setNext(newStub, code); } else { firstStub_ = newStub; codeRaw_ = code->raw(); } - numStubs_++; + state_.trackAttached(); }
--- a/js/src/jit/IonIC.h +++ b/js/src/jit/IonIC.h @@ -74,32 +74,30 @@ class IonIC // The OOL path that calls the IC's update function. CodeLocationLabel fallbackLabel_; // Location of this IC, nullptr for idempotent caches. JSScript* script_; jsbytecode* pc_; CacheKind kind_; - uint8_t numStubs_; bool idempotent_ : 1; - bool disabled_ : 1; + ICState state_; protected: explicit IonIC(CacheKind kind) : codeRaw_(nullptr), firstStub_(nullptr), rejoinLabel_(), fallbackLabel_(), script_(nullptr), pc_(nullptr), kind_(kind), - numStubs_(0), idempotent_(false), - disabled_(false) + state_() {} void attachStub(IonICStub* newStub, JitCode* code); public: void setScriptedLocation(JSScript* script, jsbytecode* pc) { MOZ_ASSERT(!script_ && !pc_); MOZ_ASSERT(script && pc); @@ -107,29 +105,25 @@ class IonIC pc_ = pc; } JSScript* script() const { MOZ_ASSERT(script_); return script_; } jsbytecode* pc() const { MOZ_ASSERT(pc_); return pc_; } CodeLocationLabel rejoinLabel() const { return rejoinLabel_; } - static const size_t MAX_STUBS = 16; - - bool canAttachStub() const { return numStubs_ < MAX_STUBS; } + // Discard all stubs. + void discardStubs(Zone* zone); - void disable(Zone* zone) { - reset(zone); - disabled_ = true; - } + // Discard all stubs and reset the ICState. + void reset(Zone* zone); - bool disabled() const { return disabled_; } - - // Discard all stubs. - void reset(Zone* zone); + ICState& state() { + return state_; + } void togglePreBarriers(bool enabled, ReprotectCode reprotect); CacheKind kind() const { return kind_; } uint8_t** codeRawPtr() { return &codeRaw_; } bool idempotent() const { return idempotent_; } void setIdempotent() { idempotent_ = true; } @@ -149,61 +143,55 @@ class IonIC void updateBaseAddress(JitCode* code, MacroAssembler& masm); // Returns the Register to use as scratch when entering IC stubs. This // should either be an output register or a temp. Register scratchRegisterForEntryJump(); void trace(JSTracer* trc); - bool attachCacheIRStub(JSContext* cx, const CacheIRWriter& writer, CacheKind kind, - IonScript* ionScript, + void attachCacheIRStub(JSContext* cx, const CacheIRWriter& writer, CacheKind kind, + IonScript* ionScript, bool* attached, const PropertyTypeCheckInfo* typeCheckInfo = nullptr); }; class IonGetPropertyIC : public IonIC { LiveRegisterSet liveRegs_; TypedOrValueRegister value_; ConstantOrRegister id_; TypedOrValueRegister output_; Register maybeTemp_; // Might be InvalidReg. - static const size_t MAX_FAILED_UPDATES = 16; - uint16_t failedUpdates_; - bool monitoredResult_ : 1; bool allowDoubleResult_ : 1; public: IonGetPropertyIC(CacheKind kind, LiveRegisterSet liveRegs, TypedOrValueRegister value, const ConstantOrRegister& id, TypedOrValueRegister output, Register maybeTemp, bool monitoredResult, bool allowDoubleResult) : IonIC(kind), liveRegs_(liveRegs), value_(value), id_(id), output_(output), maybeTemp_(maybeTemp), - failedUpdates_(0), monitoredResult_(monitoredResult), allowDoubleResult_(allowDoubleResult) { } bool monitoredResult() const { return monitoredResult_; } TypedOrValueRegister value() const { return value_; } ConstantOrRegister id() const { return id_; } TypedOrValueRegister output() const { return output_; } Register maybeTemp() const { return maybeTemp_; } LiveRegisterSet liveRegs() const { return liveRegs_; } bool allowDoubleResult() const { return allowDoubleResult_; } - void maybeDisable(Zone* zone, bool attached); - static MOZ_MUST_USE bool update(JSContext* cx, HandleScript outerScript, IonGetPropertyIC* ic, HandleValue val, HandleValue idVal, MutableHandleValue res); }; class IonSetPropertyIC : public IonIC { LiveRegisterSet liveRegs_;
--- a/js/src/jit/SharedIC.cpp +++ b/js/src/jit/SharedIC.cpp @@ -170,17 +170,16 @@ ICStub::NonCacheIRStubMakesGCCalls(Kind case Call_AnyScripted: case Call_Native: case Call_ClassHook: case Call_ScriptedApplyArray: case Call_ScriptedApplyArguments: case Call_ScriptedFunCall: case Call_StringSplit: case WarmUpCounter_Fallback: - case GetProp_Generic: case RetSub_Fallback: // These two fallback stubs don't actually make non-tail calls, // but the fallback code for the bailout path needs to pop the stub frame // pushed during the bailout. case GetProp_Fallback: case SetProp_Fallback: return true; default: @@ -350,18 +349,17 @@ ICFallbackStub::unlinkStub(Zone* zone, I MOZ_ASSERT(prev->next() == stub); prev->setNext(stub->next()); } else { MOZ_ASSERT(icEntry()->firstStub() == stub); icEntry()->setFirstStub(stub->next()); } } - MOZ_ASSERT(numOptimizedStubs_ > 0); - numOptimizedStubs_--; + state_.trackUnlinkedStub(); if (zone->needsIncrementalBarrier()) { // We are removing edges from ICStub to gcthings. Perform one final trace // of the stub for incremental GC, as it must know about those edges. stub->trace(zone->barrierTracer()); } if (stub->makesGCCalls() && stub->isMonitored()) { @@ -388,16 +386,23 @@ ICFallbackStub::unlinkStubsWithKind(JSCo { for (ICStubIterator iter = beginChain(); !iter.atEnd(); iter++) { if (iter->kind() == kind) iter.unlink(cx); } } void +ICFallbackStub::discardStubs(JSContext* cx) +{ + for (ICStubIterator iter = beginChain(); !iter.atEnd(); iter++) + iter.unlink(cx); +} + +void ICTypeMonitor_Fallback::resetMonitorStubChain(Zone* zone) { if (zone->needsIncrementalBarrier()) { // We are removing edges from monitored stubs to gcthings (JitCode). // Perform one final trace of all monitor stubs for incremental GC, // as it must know about those edges. for (ICStub* s = firstMonitorStub_; !s->isTypeMonitor_Fallback(); s = s->next()) s->trace(zone->barrierTracer()); @@ -1988,62 +1993,46 @@ DoGetPropFallback(JSContext* cx, Baselin JSOp op = JSOp(*pc); FallbackICSpew(cx, stub, "GetProp(%s)", CodeName[op]); MOZ_ASSERT(op == JSOP_GETPROP || op == JSOP_CALLPROP || op == JSOP_LENGTH || op == JSOP_GETBOUNDNAME); - // Grab our old shape before it goes away. - RootedShape oldShape(cx); - if (val.isObject()) - oldShape = val.toObject().maybeShape(); - - bool attached = false; + RootedPropertyName name(cx, script->getName(pc)); + // There are some reasons we can fail to attach a stub that are temporary. // We want to avoid calling noteUnoptimizableAccess() if the reason we // failed to attach a stub is one of those temporary reasons, since we might // end up attaching a stub for the exact same access later. bool isTemporarilyUnoptimizable = false; - RootedPropertyName name(cx, script->getName(pc)); - - // After the Genericstub was added, we should never reach the Fallbackstub again. - MOZ_ASSERT(!stub->hasStub(ICStub::GetProp_Generic)); - - if (stub->numOptimizedStubs() >= ICGetProp_Fallback::MAX_OPTIMIZED_STUBS && !stub.invalid()) { - // Discard all stubs in this IC and replace with generic getprop stub. - for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) - iter.unlink(cx); - ICGetProp_Generic::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub()); - ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); - if (!newStub) - return false; - stub->addNewStub(newStub); - attached = true; - } - - if (!attached && !JitOptions.disableCacheIR) { + if (stub->state().maybeTransition()) + stub->discardStubs(cx); + + bool attached = false; + if (stub->state().canAttachStub()) { RootedValue idVal(cx, StringValue(name)); - GetPropIRGenerator gen(cx, script, pc, CacheKind::GetProp, &isTemporarilyUnoptimizable, - val, idVal, CanAttachGetter::Yes); + GetPropIRGenerator gen(cx, script, pc, CacheKind::GetProp, stub->state().mode(), + &isTemporarilyUnoptimizable, val, idVal, CanAttachGetter::Yes); if (gen.tryAttachStub()) { ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ICStubEngine::Baseline, script, - stub); + stub, &attached); if (newStub) { JitSpew(JitSpew_BaselineIC, " Attached CacheIR stub"); - attached = true; if (gen.shouldNotePreliminaryObjectStub()) newStub->toCacheIR_Monitored()->notePreliminaryObject(); else if (gen.shouldUnlinkPreliminaryObjectStubs()) StripPreliminaryObjectStubs(cx, stub); } } + if (!attached && !isTemporarilyUnoptimizable) + stub->state().trackNotAttached(); } if (!ComputeGetPropResult(cx, frame, op, name, val, res)) return false; TypeScript::Monitor(cx, script, pc, res); // Check if debug mode toggling made the stub invalid. @@ -2117,70 +2106,16 @@ void ICGetProp_Fallback::Compiler::postGenerateStubCode(MacroAssembler& masm, Handle<JitCode*> code) { if (engine_ == Engine::Baseline) { void* address = code->raw() + returnOffset_; cx->compartment()->jitCompartment()->initBaselineGetPropReturnAddr(address); } } -/* static */ ICGetProp_Generic* -ICGetProp_Generic::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, - ICGetProp_Generic& other) -{ - return New<ICGetProp_Generic>(cx, space, other.jitCode(), firstMonitorStub); -} - -static bool -DoGetPropGeneric(JSContext* cx, BaselineFrame* frame, ICGetProp_Generic* stub, - MutableHandleValue val, MutableHandleValue res) -{ - ICFallbackStub* fallback = stub->getChainFallback(); - JSScript* script = frame->script(); - jsbytecode* pc = fallback->icEntry()->pc(script); - JSOp op = JSOp(*pc); - RootedPropertyName name(cx, script->getName(pc)); - return ComputeGetPropResult(cx, frame, op, name, val, res); -} - -typedef bool (*DoGetPropGenericFn)(JSContext*, BaselineFrame*, ICGetProp_Generic*, - MutableHandleValue, MutableHandleValue); -static const VMFunction DoGetPropGenericInfo = - FunctionInfo<DoGetPropGenericFn>(DoGetPropGeneric, "DoGetPropGeneric"); - -bool -ICGetProp_Generic::Compiler::generateStubCode(MacroAssembler& masm) -{ - AllocatableGeneralRegisterSet regs(availableGeneralRegs(1)); - - Register scratch = regs.takeAnyExcluding(ICTailCallReg); - - // Sync for the decompiler. - if (engine_ == Engine::Baseline) - EmitStowICValues(masm, 1); - - enterStubFrame(masm, scratch); - - // Push arguments. - masm.Push(R0); - masm.Push(ICStubReg); - PushStubPayload(masm, R0.scratchReg()); - - if (!callVM(DoGetPropGenericInfo, masm)) - return false; - - leaveStubFrame(masm); - - if (engine_ == Engine::Baseline) - EmitUnstowICValues(masm, 1, /* discard = */ true); - - EmitEnterTypeMonitorIC(masm); - return true; -} - void CheckForTypedObjectWithDetachedStorage(JSContext* cx, MacroAssembler& masm, Label* failure) { // All stubs manipulating typed objects must check the compartment-wide // flag indicating whether their underlying storage might be detached, to // bail out if needed. int32_t* address = &cx->compartment()->detachedTypedObjects; masm.branch32(Assembler::NotEqual, AbsoluteAddress(address), Imm32(0), failure);
--- a/js/src/jit/SharedIC.h +++ b/js/src/jit/SharedIC.h @@ -8,16 +8,17 @@ #define jit_SharedIC_h #include "jscntxt.h" #include "jscompartment.h" #include "jsgc.h" #include "jit/BaselineICList.h" #include "jit/BaselineJIT.h" +#include "jit/ICState.h" #include "jit/MacroAssembler.h" #include "jit/SharedICList.h" #include "jit/SharedICRegisters.h" #include "vm/ReceiverGuard.h" #include "vm/TypedArrayObject.h" namespace js { namespace jit { @@ -730,59 +731,60 @@ class ICFallbackStub : public ICStub protected: // Fallback stubs need these fields to easily add new stubs to // the linked list of stubs for an IC. // The IC entry for this linked list of stubs. ICEntry* icEntry_; // The number of stubs kept in the IC entry. - uint32_t numOptimizedStubs_ : 31; - uint32_t invalid_ : 1; + ICState state_; // A pointer to the location stub pointer that needs to be // changed to add a new "last" stub immediately before the fallback // stub. This'll start out pointing to the icEntry's "firstStub_" // field, and as new stubs are added, it'll point to the current // last stub's "next_" field. ICStub** lastStubPtrAddr_; ICFallbackStub(Kind kind, JitCode* stubCode) : ICStub(kind, ICStub::Fallback, stubCode), icEntry_(nullptr), - numOptimizedStubs_(0), - invalid_(false), + state_(), lastStubPtrAddr_(nullptr) {} ICFallbackStub(Kind kind, Trait trait, JitCode* stubCode) : ICStub(kind, trait, stubCode), icEntry_(nullptr), - numOptimizedStubs_(0), - invalid_(false), + state_(), lastStubPtrAddr_(nullptr) { MOZ_ASSERT(trait == ICStub::Fallback || trait == ICStub::MonitoredFallback); } public: inline ICEntry* icEntry() const { return icEntry_; } inline size_t numOptimizedStubs() const { - return (size_t) numOptimizedStubs_; + return state_.numOptimizedStubs(); } void setInvalid() { - invalid_ = 1; + state_.setInvalid(); } bool invalid() const { - return invalid_; + return state_.invalid(); + } + + ICState& state() { + return state_; } // The icEntry and lastStubPtrAddr_ fields can't be initialized when the stub is // created since the stub is created at compile time, and we won't know the IC entry // address until after compile when the JitScript is created. This method // allows these fields to be fixed up at that point. void fixupICEntry(ICEntry* icEntry) { MOZ_ASSERT(icEntry_ == nullptr); @@ -794,17 +796,17 @@ class ICFallbackStub : public ICStub // Add a new stub to the IC chain terminated by this fallback stub. void addNewStub(ICStub* stub) { MOZ_ASSERT(!invalid()); MOZ_ASSERT(*lastStubPtrAddr_ == this); MOZ_ASSERT(stub->next() == nullptr); stub->setNext(this); *lastStubPtrAddr_ = stub; lastStubPtrAddr_ = stub->addressOfNext(); - numOptimizedStubs_++; + state_.trackAttached(); } ICStubConstIterator beginChainConst() const { return ICStubConstIterator(icEntry_->firstStub()); } ICStubIterator beginChain() { return ICStubIterator(this); @@ -822,16 +824,18 @@ class ICFallbackStub : public ICStub unsigned count = 0; for (ICStubConstIterator iter = beginChainConst(); !iter.atEnd(); iter++) { if (iter->kind() == kind) count++; } return count; } + void discardStubs(JSContext* cx); + void unlinkStub(Zone* zone, ICStub* prev, ICStub* stub); void unlinkStubsWithKind(JSContext* cx, ICStub::Kind kind); }; // Base class for Trait::Regular CacheIR stubs class ICCacheIR_Regular : public ICStub { const CacheIRStubInfo* stubInfo_; @@ -2319,17 +2323,16 @@ class ICGetProp_Fallback : public ICMoni { friend class ICStubSpace; explicit ICGetProp_Fallback(JitCode* stubCode) : ICMonitoredFallbackStub(ICStub::GetProp_Fallback, stubCode) { } public: - static const uint32_t MAX_OPTIMIZED_STUBS = 16; static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0; static const size_t ACCESSED_GETTER_BIT = 1; void noteUnoptimizableAccess() { extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT); } bool hadUnoptimizableAccess() const { return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT); @@ -2362,45 +2365,16 @@ class ICGetProp_Fallback : public ICMoni ICGetProp_Fallback* stub = newStub<ICGetProp_Fallback>(space, getStubCode()); if (!stub || !stub->initMonitoringChain(cx, space, engine_)) return nullptr; return stub; } }; }; -// Stub for sites, which are too polymorphic (i.e. MAX_OPTIMIZED_STUBS was reached) -class ICGetProp_Generic : public ICMonitoredStub -{ - friend class ICStubSpace; - - protected: - explicit ICGetProp_Generic(JitCode* stubCode, ICStub* firstMonitorStub) - : ICMonitoredStub(ICStub::GetProp_Generic, stubCode, firstMonitorStub) {} - - public: - static ICGetProp_Generic* Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, - ICGetProp_Generic& other); - - class Compiler : public ICStubCompiler { - protected: - MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm); - ICStub* firstMonitorStub_; - public: - explicit Compiler(JSContext* cx, ICStub* firstMonitorStub) - : ICStubCompiler(cx, ICStub::GetProp_Generic, ICStubEngine::Baseline), - firstMonitorStub_(firstMonitorStub) - {} - - ICStub* getStub(ICStubSpace* space) { - return newStub<ICGetProp_Generic>(space, getStubCode(), firstMonitorStub_); - } - }; -}; - static inline uint32_t SimpleTypeDescrKey(SimpleTypeDescr* descr) { if (descr->is<ScalarTypeDescr>()) return uint32_t(descr->as<ScalarTypeDescr>().type()) << 1; return (uint32_t(descr->as<ReferenceTypeDescr>().type()) << 1) | 1; }
--- a/js/src/jit/SharedICList.h +++ b/js/src/jit/SharedICList.h @@ -31,17 +31,16 @@ namespace jit { _(Compare_String) \ _(Compare_Symbol) \ _(Compare_Boolean) \ _(Compare_Object) \ _(Compare_ObjectWithUndefined) \ _(Compare_Int32WithBoolean) \ \ _(GetProp_Fallback) \ - _(GetProp_Generic) \ \ _(CacheIR_Regular) \ _(CacheIR_Monitored) \ _(CacheIR_Updated) \ \ } // namespace jit } // namespace js
--- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -1522,10 +1522,189 @@ bool CheckIsCallable(JSContext* cx, HandleValue v, CheckIsCallableKind kind) { if (!IsCallable(v)) return ThrowCheckIsCallable(cx, kind); return true; } +template <bool HandleMissing> +static MOZ_ALWAYS_INLINE bool +GetNativeDataProperty(JSContext* cx, NativeObject* obj, jsid id, Value* vp) +{ + // Fast path used by megamorphic IC stubs. Unlike our other property + // lookup paths, this is optimized to be as fast as possible for simple + // data property lookups. + + JS::AutoCheckCannotGC nogc; + + MOZ_ASSERT(JSID_IS_ATOM(id) || JSID_IS_SYMBOL(id)); + + while (true) { + if (Shape* shape = obj->lastProperty()->search(cx, id)) { + if (!shape->hasSlot() || !shape->hasDefaultGetter()) + return false; + + *vp = obj->getSlot(shape->slot()); + return true; + } + + // Property not found. Watch out for Class hooks. + if (MOZ_UNLIKELY(!obj->is<PlainObject>())) { + if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj) || + obj->getClass()->getGetProperty()) + { + return false; + } + } + + JSObject* proto = obj->staticPrototype(); + if (!proto) { + if (HandleMissing) { + vp->setUndefined(); + return true; + } + return false; + } + + if (!proto->isNative()) + return false; + obj = &proto->as<NativeObject>(); + } +} + +template <bool HandleMissing> +bool +GetNativeDataProperty(JSContext* cx, JSObject* obj, PropertyName* name, Value* vp) +{ + if (MOZ_UNLIKELY(!obj->isNative())) + return false; + return GetNativeDataProperty<HandleMissing>(cx, &obj->as<NativeObject>(), NameToId(name), vp); +} + +template bool +GetNativeDataProperty<true>(JSContext* cx, JSObject* obj, PropertyName* name, Value* vp); + +template bool +GetNativeDataProperty<false>(JSContext* cx, JSObject* obj, PropertyName* name, Value* vp); + +template <bool HandleMissing> +bool +GetNativeDataPropertyByValue(JSContext* cx, JSObject* obj, Value* vp) +{ + JS::AutoCheckCannotGC nogc; + + if (MOZ_UNLIKELY(!obj->isNative())) + return false; + + // vp[0] contains the id, result will be stored in vp[1]. + Value idVal = vp[0]; + + jsid id; + if (MOZ_LIKELY(idVal.isString())) { + JSString* s = idVal.toString(); + JSAtom* atom; + if (s->isAtom()) { + atom = &s->asAtom(); + } else { + atom = AtomizeString(cx, s); + if (!atom) + return false; + } + id = AtomToId(atom); + } else if (idVal.isSymbol()) { + id = SYMBOL_TO_JSID(idVal.toSymbol()); + } else { + if (!ValueToIdPure(idVal, &id)) + return false; + } + + // Watch out for ids that may be stored in dense elements. + static_assert(NativeObject::MAX_DENSE_ELEMENTS_COUNT < JSID_INT_MAX, + "All dense elements must have integer jsids"); + if (MOZ_UNLIKELY(JSID_IS_INT(id))) + return false; + + Value* res = vp + 1; + return GetNativeDataProperty<HandleMissing>(cx, &obj->as<NativeObject>(), id, res); +} + +template bool +GetNativeDataPropertyByValue<true>(JSContext* cx, JSObject* obj, Value* vp); + +template bool +GetNativeDataPropertyByValue<false>(JSContext* cx, JSObject* obj, Value* vp); + +bool +SetNativeDataProperty(JSContext* cx, JSObject* obj, PropertyName* name, Value* val) +{ + JS::AutoCheckCannotGC nogc; + + if (MOZ_UNLIKELY(!obj->isNative())) + return false; + + NativeObject* nobj = &obj->as<NativeObject>(); + Shape* shape = nobj->lastProperty()->search(cx, NameToId(name)); + if (!shape || + !shape->hasSlot() || + !shape->hasDefaultSetter() || + !shape->writable() || + nobj->watched()) + { + return false; + } + + if (!HasTypePropertyId(nobj, NameToId(name), *val)) + return false; + + nobj->setSlot(shape->slot(), *val); + return true; +} + +bool +ObjectHasGetterSetter(JSContext* cx, JSObject* objArg, Shape* propShape) +{ + JS::AutoCheckCannotGC nogc; + + MOZ_ASSERT(propShape->hasGetterObject() || propShape->hasSetterObject()); + + // Window objects may require outerizing (passing the WindowProxy to the + // getter/setter), so we don't support them here. + if (MOZ_UNLIKELY(!objArg->isNative() || IsWindow(objArg))) + return false; + + NativeObject* nobj = &objArg->as<NativeObject>(); + jsid id = propShape->propid(); + + while (true) { + if (Shape* shape = nobj->lastProperty()->search(cx, id)) { + if (shape == propShape) + return true; + if (shape->getterOrUndefined() == propShape->getterOrUndefined() && + shape->setterOrUndefined() == propShape->setterOrUndefined()) + { + return true; + } + return false; + } + + // Property not found. Watch out for Class hooks. + if (!nobj->is<PlainObject>()) { + if (ClassMayResolveId(cx->names(), nobj->getClass(), id, nobj) || + nobj->getClass()->getGetProperty()) + { + return false; + } + } + + JSObject* proto = nobj->staticPrototype(); + if (!proto) + return false; + + if (!proto->isNative()) + return false; + nobj = &proto->as<NativeObject>(); + } +} + } // namespace jit } // namespace js
--- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -838,12 +838,26 @@ CallNativeSetter(JSContext* cx, HandleFu HandleValue rhs); MOZ_MUST_USE bool EqualStringsHelper(JSString* str1, JSString* str2); MOZ_MUST_USE bool CheckIsCallable(JSContext* cx, HandleValue v, CheckIsCallableKind kind); +template <bool HandleMissing> +bool +GetNativeDataProperty(JSContext* cx, JSObject* obj, PropertyName* name, Value* vp); + +template <bool HandleMissing> +bool +GetNativeDataPropertyByValue(JSContext* cx, JSObject* obj, Value* vp); + +bool +SetNativeDataProperty(JSContext* cx, JSObject* obj, PropertyName* name, Value* val); + +bool +ObjectHasGetterSetter(JSContext* cx, JSObject* obj, Shape* propShape); + } // namespace jit } // namespace js #endif /* jit_VMFunctions_h */
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -6336,18 +6336,20 @@ JSErrorNotes::~JSErrorNotes() static UniquePtr<JSErrorNotes::Note> CreateErrorNoteVA(JSContext* cx, const char* filename, unsigned lineno, unsigned column, JSErrorCallback errorCallback, void* userRef, const unsigned errorNumber, ErrorArgumentsType argumentsType, va_list ap) { auto note = MakeUnique<JSErrorNotes::Note>(); - if (!note) + if (!note) { + ReportOutOfMemory(cx); return nullptr; + } note->errorNumber = errorNumber; note->filename = filename; note->lineno = lineno; note->column = column; if (!ExpandErrorArgumentsVA(cx, errorCallback, userRef, errorNumber, nullptr, argumentsType, note.get(), ap)) { @@ -6419,18 +6421,20 @@ JSErrorNotes::length() { return notes_.length(); } UniquePtr<JSErrorNotes> JSErrorNotes::copy(JSContext* cx) { auto copiedNotes = MakeUnique<JSErrorNotes>(); - if (!copiedNotes) + if (!copiedNotes) { + ReportOutOfMemory(cx); return nullptr; + } for (auto&& note : *this) { js::UniquePtr<JSErrorNotes::Note> copied(CopyErrorNote(cx, note.get())); if (!copied) return nullptr; if (!copiedNotes->notes_.append(Move(copied))) return nullptr;
--- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -3874,22 +3874,23 @@ JS::ubi::Concrete<JSObject>::size(mozill const char16_t JS::ubi::Concrete<JSObject>::concreteTypeName[] = u"JSObject"; void JSObject::traceChildren(JSTracer* trc) { TraceEdge(trc, &group_, "group"); + if (is<ShapedObject>()) + as<ShapedObject>().traceShape(trc); + const Class* clasp = group_->clasp(); if (clasp->isNative()) { NativeObject* nobj = &as<NativeObject>(); - TraceEdge(trc, &nobj->shape_, "shape"); - { GetObjectSlotNameFunctor func(nobj); JS::AutoTracingDetails ctx(trc, func); JS::AutoTracingIndex index(trc); // Tracing can mutate the target but cannot change the slot count, // but the compiler has no way of knowing this. const uint32_t nslots = nobj->slotSpan(); for (uint32_t i = 0; i < nslots; ++i) {
--- a/js/src/vm/Scope.cpp +++ b/js/src/vm/Scope.cpp @@ -625,16 +625,17 @@ FunctionScope::create(JSContext* cx, Han // The data that's passed in may be from the frontend and LifoAlloc'd. // Copy it now that we're creating a permanent VM scope. RootedShape envShape(cx); Rooted<UniquePtr<Data>> copy(cx, copyData(cx, data, hasParameterExprs, &envShape)); if (!copy) return nullptr; copy->hasParameterExprs = hasParameterExprs; + copy->canonicalFunction.init(fun); // An environment may be needed regardless of existence of any closed over // bindings: // - Extensible scopes (i.e., due to direct eval) // - Needing a home object // - Being a derived class constructor // - Being a generator if (!envShape && needsEnvironment) { @@ -642,18 +643,16 @@ FunctionScope::create(JSContext* cx, Han if (!envShape) return nullptr; } Scope* scope = Scope::create(cx, ScopeKind::Function, enclosing, envShape); if (!scope) return nullptr; - copy->canonicalFunction.init(fun); - funScope = &scope->as<FunctionScope>(); funScope->initData(Move(copy.get())); } return funScope; } JSScript* @@ -696,22 +695,22 @@ FunctionScope::clone(JSContext* cx, Hand return nullptr; } Rooted<Data*> dataOriginal(cx, &scope->as<FunctionScope>().data()); Rooted<UniquePtr<Data>> dataClone(cx, CopyScopeData<FunctionScope>(cx, dataOriginal)); if (!dataClone) return nullptr; - Scope* scopeClone= Scope::create(cx, scope->kind(), enclosing, envShape); + dataClone->canonicalFunction.init(fun); + + Scope* scopeClone = Scope::create(cx, scope->kind(), enclosing, envShape); if (!scopeClone) return nullptr; - dataClone->canonicalFunction.init(fun); - funScopeClone = &scopeClone->as<FunctionScope>(); funScopeClone->initData(Move(dataClone.get())); } return funScopeClone; } template <XDRMode mode>
--- a/js/src/vm/ShapedObject.h +++ b/js/src/vm/ShapedObject.h @@ -34,16 +34,20 @@ class ShapedObject : public JSObject } void setShape(Shape* shape) { this->shape_ = shape; } Shape* shape() const { return this->shape_; } + void traceShape(JSTracer* trc) { + TraceEdge(trc, &shape_, "shape"); + } + static size_t offsetOfShape() { return offsetof(ShapedObject, shape_); } private: static void staticAsserts() { static_assert(offsetof(ShapedObject, shape_) == offsetof(shadow::Object, shape), "shadow shape must match actual shape"); } };
--- a/js/src/vm/TypeInference-inl.h +++ b/js/src/vm/TypeInference-inl.h @@ -409,17 +409,17 @@ HasTypePropertyId(JSObject* obj, jsid id { if (obj->hasLazyGroup()) return true; if (obj->group()->unknownProperties()) return true; if (HeapTypeSet* types = obj->group()->maybeGetProperty(IdToTypeId(id))) - return types->hasType(type); + return types->hasType(type) && !types->nonConstantProperty(); return false; } inline bool HasTypePropertyId(JSObject* obj, jsid id, const Value& value) { return HasTypePropertyId(obj, id, TypeSet::GetValueType(value));
--- a/js/src/wasm/WasmIonCompile.cpp +++ b/js/src/wasm/WasmIonCompile.cpp @@ -763,76 +763,86 @@ class FunctionCompiler MOZ_ASSERT(access->offset() == 0); MWasmLoadTls* boundsCheckLimit = maybeLoadBoundsCheckLimit(); load = MAsmJSLoadHeap::New(alloc(), memoryBase, base, boundsCheckLimit, access->type()); } else { checkOffsetAndBounds(access, &base); load = MWasmLoad::New(alloc(), memoryBase, base, *access, ToMIRType(result)); } - curBlock_->add(load); + if (load) + curBlock_->add(load); + return load; } - void store(MDefinition* base, MemoryAccessDesc* access, MDefinition* v) + MOZ_MUST_USE bool store(MDefinition* base, MemoryAccessDesc* access, MDefinition* v) { if (inDeadCode()) - return; + return true; MWasmLoadTls* memoryBase = maybeLoadMemoryBase(); MInstruction* store = nullptr; if (access->isPlainAsmJS()) { MOZ_ASSERT(access->offset() == 0); MWasmLoadTls* boundsCheckLimit = maybeLoadBoundsCheckLimit(); - store = MAsmJSStoreHeap::New(alloc(), memoryBase, base, boundsCheckLimit, access->type(), v); + store = MAsmJSStoreHeap::New(alloc(), memoryBase, base, boundsCheckLimit, + access->type(), v); } else { checkOffsetAndBounds(access, &base); store = MWasmStore::New(alloc(), memoryBase, base, *access, v); } - curBlock_->add(store); + if (store) + curBlock_->add(store); + + return !!store; } MDefinition* atomicCompareExchangeHeap(MDefinition* base, MemoryAccessDesc* access, MDefinition* oldv, MDefinition* newv) { if (inDeadCode()) return nullptr; checkOffsetAndBounds(access, &base); MWasmLoadTls* memoryBase = maybeLoadMemoryBase(); - auto* cas = MAsmJSCompareExchangeHeap::New(alloc(), memoryBase, base, *access, oldv, newv, tlsPointer_); - curBlock_->add(cas); + auto* cas = MAsmJSCompareExchangeHeap::New(alloc(), memoryBase, base, *access, oldv, newv, + tlsPointer_); + if (cas) + curBlock_->add(cas); return cas; } MDefinition* atomicExchangeHeap(MDefinition* base, MemoryAccessDesc* access, MDefinition* value) { if (inDeadCode()) return nullptr; checkOffsetAndBounds(access, &base); MWasmLoadTls* memoryBase = maybeLoadMemoryBase(); auto* cas = MAsmJSAtomicExchangeHeap::New(alloc(), memoryBase, base, *access, value, tlsPointer_); - curBlock_->add(cas); + if (cas) + curBlock_->add(cas); return cas; } MDefinition* atomicBinopHeap(js::jit::AtomicOp op, - MDefinition* base, MemoryAccessDesc* access, - MDefinition* v) + MDefinition* base, MemoryAccessDesc* access, MDefinition* v) { if (inDeadCode()) return nullptr; checkOffsetAndBounds(access, &base); MWasmLoadTls* memoryBase = maybeLoadMemoryBase(); - auto* binop = MAsmJSAtomicBinopHeap::New(alloc(), op, memoryBase, base, *access, v, tlsPointer_); - curBlock_->add(binop); + auto* binop = MAsmJSAtomicBinopHeap::New(alloc(), op, memoryBase, base, *access, v, + tlsPointer_); + if (binop) + curBlock_->add(binop); return binop; } MDefinition* loadGlobalVar(unsigned globalDataOffset, bool isConst, MIRType type) { if (inDeadCode()) return nullptr; @@ -2450,46 +2460,48 @@ EmitSelect(FunctionCompiler& f) static bool EmitLoad(FunctionCompiler& f, ValType type, Scalar::Type viewType) { LinearMemoryAddress<MDefinition*> addr; if (!f.iter().readLoad(type, Scalar::byteSize(viewType), &addr)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.trapIfNotAsmJS()); - f.iter().setResult(f.load(addr.base, &access, type)); + auto* ins = f.load(addr.base, &access, type); + if (!f.inDeadCode() && !ins) + return false; + + f.iter().setResult(ins); return true; } static bool EmitStore(FunctionCompiler& f, ValType resultType, Scalar::Type viewType) { LinearMemoryAddress<MDefinition*> addr; MDefinition* value; if (!f.iter().readStore(resultType, Scalar::byteSize(viewType), &addr, &value)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.trapIfNotAsmJS()); - f.store(addr.base, &access, value); - return true; + return f.store(addr.base, &access, value); } static bool EmitTeeStore(FunctionCompiler& f, ValType resultType, Scalar::Type viewType) { LinearMemoryAddress<MDefinition*> addr; MDefinition* value; if (!f.iter().readTeeStore(resultType, Scalar::byteSize(viewType), &addr, &value)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.trapIfNotAsmJS()); - f.store(addr.base, &access, value); - return true; + return f.store(addr.base, &access, value); } static bool EmitTeeStoreWithCoercion(FunctionCompiler& f, ValType resultType, Scalar::Type viewType) { LinearMemoryAddress<MDefinition*> addr; MDefinition* value; if (!f.iter().readTeeStore(resultType, Scalar::byteSize(viewType), &addr, &value)) @@ -2499,18 +2511,17 @@ EmitTeeStoreWithCoercion(FunctionCompile value = f.unary<MToDouble>(value); else if (resultType == ValType::F64 && viewType == Scalar::Float32) value = f.unary<MToFloat32>(value); else MOZ_CRASH("unexpected coerced store"); MemoryAccessDesc access(viewType, addr.align, addr.offset, f.trapIfNotAsmJS()); - f.store(addr.base, &access, value); - return true; + return f.store(addr.base, &access, value); } static bool TryInlineUnaryBuiltin(FunctionCompiler& f, SymbolicAddress callee, MDefinition* input) { if (!input) return false; @@ -2594,81 +2605,99 @@ EmitAtomicsLoad(FunctionCompiler& f) LinearMemoryAddress<MDefinition*> addr; Scalar::Type viewType; if (!f.iter().readAtomicLoad(&addr, &viewType)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()), 0, MembarBeforeLoad, MembarAfterLoad); - f.iter().setResult(f.load(addr.base, &access, ValType::I32)); + auto* ins = f.load(addr.base, &access, ValType::I32); + if (!f.inDeadCode() && !ins) + return false; + + f.iter().setResult(ins); return true; } static bool EmitAtomicsStore(FunctionCompiler& f) { LinearMemoryAddress<MDefinition*> addr; Scalar::Type viewType; MDefinition* value; if (!f.iter().readAtomicStore(&addr, &viewType, &value)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()), 0, MembarBeforeStore, MembarAfterStore); - f.store(addr.base, &access, value); + if (!f.store(addr.base, &access, value)) + return false; + f.iter().setResult(value); return true; } static bool EmitAtomicsBinOp(FunctionCompiler& f) { LinearMemoryAddress<MDefinition*> addr; Scalar::Type viewType; jit::AtomicOp op; MDefinition* value; if (!f.iter().readAtomicBinOp(&addr, &viewType, &op, &value)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset())); - f.iter().setResult(f.atomicBinopHeap(op, addr.base, &access, value)); + auto* ins = f.atomicBinopHeap(op, addr.base, &access, value); + if (!f.inDeadCode() && !ins) + return false; + + f.iter().setResult(ins); return true; } static bool EmitAtomicsCompareExchange(FunctionCompiler& f) { LinearMemoryAddress<MDefinition*> addr; Scalar::Type viewType; MDefinition* oldValue; MDefinition* newValue; if (!f.iter().readAtomicCompareExchange(&addr, &viewType, &oldValue, &newValue)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset())); - f.iter().setResult(f.atomicCompareExchangeHeap(addr.base, &access, oldValue, newValue)); + auto* ins = f.atomicCompareExchangeHeap(addr.base, &access, oldValue, newValue); + if (!f.inDeadCode() && !ins) + return false; + + f.iter().setResult(ins); return true; } static bool EmitAtomicsExchange(FunctionCompiler& f) { LinearMemoryAddress<MDefinition*> addr; Scalar::Type viewType; MDefinition* value; if (!f.iter().readAtomicExchange(&addr, &viewType, &value)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset())); - f.iter().setResult(f.atomicExchangeHeap(addr.base, &access, value)); + auto* ins = f.atomicExchangeHeap(addr.base, &access, value); + if (!f.inDeadCode() && !ins) + return false; + + f.iter().setResult(ins); return true; } static bool EmitSimdUnary(FunctionCompiler& f, ValType type, SimdOperation simdOp) { MSimdUnaryArith::Operation op; switch (simdOp) { @@ -2881,17 +2910,21 @@ EmitSimdLoad(FunctionCompiler& f, ValTyp numElems = defaultNumElems; LinearMemoryAddress<MDefinition*> addr; if (!f.iter().readLoad(resultType, Scalar::byteSize(viewType), &addr)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()), numElems); - f.iter().setResult(f.load(addr.base, &access, resultType)); + auto* ins = f.load(addr.base, &access, resultType); + if (!f.inDeadCode() && !ins) + return false; + + f.iter().setResult(ins); return true; } static bool EmitSimdStore(FunctionCompiler& f, ValType resultType, unsigned numElems) { unsigned defaultNumElems; Scalar::Type viewType = SimdExprTypeToViewType(resultType, &defaultNumElems); @@ -2901,18 +2934,17 @@ EmitSimdStore(FunctionCompiler& f, ValTy LinearMemoryAddress<MDefinition*> addr; MDefinition* value; if (!f.iter().readTeeStore(resultType, Scalar::byteSize(viewType), &addr, &value)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()), numElems); - f.store(addr.base, &access, value); - return true; + return f.store(addr.base, &access, value); } static bool EmitSimdSelect(FunctionCompiler& f, ValType simdType) { MDefinition* trueValue; MDefinition* falseValue; MDefinition* condition;
--- a/layout/base/GeckoRestyleManager.cpp +++ b/layout/base/GeckoRestyleManager.cpp @@ -195,34 +195,33 @@ GeckoRestyleManager::ReframingStyleConte static inline dom::Element* ElementForStyleContext(nsIContent* aParentContent, nsIFrame* aFrame, CSSPseudoElementType aPseudoType); // Forwarded nsIDocumentObserver method, to handle restyling (and // passing the notification to the frame). -nsresult +void GeckoRestyleManager::ContentStateChanged(nsIContent* aContent, EventStates aStateMask) { // XXXbz it would be good if this function only took Elements, but // we'd have to make ESM guarantee that usefully. if (!aContent->IsElement()) { - return NS_OK; + return; } Element* aElement = aContent->AsElement(); nsChangeHint changeHint; nsRestyleHint restyleHint; ContentStateChangedInternal(aElement, aStateMask, &changeHint, &restyleHint); PostRestyleEvent(aElement, restyleHint, changeHint); - return NS_OK; } // Forwarded nsIMutationObserver method, to handle restyling. void GeckoRestyleManager::AttributeWillChange(Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType, @@ -1510,17 +1509,17 @@ ElementRestyler::ConditionallyRestyleUnd if (aUndisplayedParent && aUndisplayedParent->IsElement() && aUndisplayedParent->HasFlag(mRestyleTracker.RootBit())) { MOZ_ASSERT(!aUndisplayedParent->IsStyledByServo()); aRestyleRoot = aUndisplayedParent->AsElement(); } for (UndisplayedNode* undisplayed = aUndisplayed; undisplayed; - undisplayed = undisplayed->mNext) { + undisplayed = undisplayed->getNext()) { if (!undisplayed->mContent->IsElement()) { continue; } Element* element = undisplayed->mContent->AsElement(); if (!ConditionallyRestyle(element, aRestyleRoot)) { @@ -3169,17 +3168,17 @@ ElementRestyler::RestyleUndisplayedNodes const StyleDisplay aDisplay) { nsIContent* undisplayedParent = aUndisplayedParent; UndisplayedNode* undisplayed = aUndisplayed; TreeMatchContext::AutoAncestorPusher pusher(mTreeMatchContext); if (undisplayed) { pusher.PushAncestorAndStyleScope(undisplayedParent); } - for (; undisplayed; undisplayed = undisplayed->mNext) { + for (; undisplayed; undisplayed = undisplayed->getNext()) { NS_ASSERTION(undisplayedParent || undisplayed->mContent == mPresContext->Document()->GetRootElement(), "undisplayed node child of null must be root"); NS_ASSERTION(!undisplayed->mStyle->GetPseudo(), "Shouldn't have random pseudo style contexts in the " "undisplayed map");
--- a/layout/base/GeckoRestyleManager.h +++ b/layout/base/GeckoRestyleManager.h @@ -49,18 +49,18 @@ protected: { MOZ_ASSERT(!mReframingStyleContexts, "temporary member should be nulled out before destruction"); } public: // Forwarded nsIDocumentObserver method, to handle restyling (and // passing the notification to the frame). - nsresult ContentStateChanged(nsIContent* aContent, - EventStates aStateMask); + void ContentStateChanged(nsIContent* aContent, + EventStates aStateMask); // Forwarded nsIMutationObserver method, to handle restyling. void AttributeWillChange(Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType, const nsAttrValue* aNewValue); // Forwarded nsIMutationObserver method, to handle restyling (and
--- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -4521,43 +4521,41 @@ void PresShell::NotifyCounterStylesAreDirty() { nsAutoCauseReflowNotifier reflowNotifier(this); mFrameConstructor->BeginUpdate(); mFrameConstructor->NotifyCounterStylesAreDirty(); mFrameConstructor->EndUpdate(); } -nsresult -PresShell::ReconstructFrames(void) +void +PresShell::ReconstructFrames() { NS_PRECONDITION(!mFrameConstructor->GetRootFrame() || mDidInitialize, "Must not have root frame before initial reflow"); if (!mDidInitialize || mIsDestroying) { // Nothing to do here - return NS_OK; + return; } nsCOMPtr<nsIPresShell> kungFuDeathGrip(this); // Have to make sure that the content notifications are flushed before we // start messing with the frame model; otherwise we can get content doubling. mDocument->FlushPendingNotifications(FlushType::ContentAndNotify); if (mIsDestroying) { - return NS_OK; + return; } nsAutoCauseReflowNotifier crNotifier(this); mFrameConstructor->BeginUpdate(); - nsresult rv = mFrameConstructor->ReconstructDocElementHierarchy(); + mFrameConstructor->ReconstructDocElementHierarchy(); VERIFY_STYLE_TREE; mFrameConstructor->EndUpdate(); - - return rv; } void nsIPresShell::RestyleForCSSRuleChanges() { AutoTArray<RefPtr<mozilla::dom::Element>,1> scopeRoots; mChangedScopeStyleRoots.SwapElements(scopeRoots);
--- a/layout/base/PresShell.h +++ b/layout/base/PresShell.h @@ -183,17 +183,17 @@ public: nsIContent* aContent, nsEventStatus* aStatus) override; virtual nsIFrame* GetEventTargetFrame() override; virtual already_AddRefed<nsIContent> GetEventTargetContent( mozilla::WidgetEvent* aEvent) override; virtual void NotifyCounterStylesAreDirty() override; - virtual nsresult ReconstructFrames(void) override; + virtual void ReconstructFrames(void) override; virtual void Freeze() override; virtual void Thaw() override; virtual void FireOrClearDelayedEvents(bool aFireEvents) override; virtual nsresult RenderDocument(const nsRect& aRect, uint32_t aFlags, nscolor aBackgroundColor, gfxContext* aThebesContext) override;
--- a/layout/base/RestyleManager.h +++ b/layout/base/RestyleManager.h @@ -169,18 +169,18 @@ public: inline void PostRestyleEvent(dom::Element* aElement, nsRestyleHint aRestyleHint, nsChangeHint aMinChangeHint); inline void RebuildAllStyleData(nsChangeHint aExtraHint, nsRestyleHint aRestyleHint); inline void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint, nsRestyleHint aRestyleHint); inline void ProcessPendingRestyles(); - inline nsresult ContentStateChanged(nsIContent* aContent, - EventStates aStateMask); + inline void ContentStateChanged(nsIContent* aContent, + EventStates aStateMask); inline void AttributeWillChange(dom::Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType, const nsAttrValue* aNewValue); inline void AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute,
--- a/layout/base/RestyleManagerInlines.h +++ b/layout/base/RestyleManagerInlines.h @@ -39,17 +39,17 @@ RestyleManager::PostRebuildAllStyleDataE } void RestyleManager::ProcessPendingRestyles() { MOZ_STYLO_FORWARD(ProcessPendingRestyles, ()); } -nsresult +void RestyleManager::ContentStateChanged(nsIContent* aContent, EventStates aStateMask) { MOZ_STYLO_FORWARD(ContentStateChanged, (aContent, aStateMask)); } void RestyleManager::AttributeWillChange(dom::Element* aElement,
--- a/layout/base/ServoRestyleManager.cpp +++ b/layout/base/ServoRestyleManager.cpp @@ -457,22 +457,22 @@ ServoRestyleManager::RestyleForAppend(ns void ServoRestyleManager::ContentRemoved(nsINode* aContainer, nsIContent* aOldChild, nsIContent* aFollowingSibling) { NS_WARNING("stylo: ServoRestyleManager::ContentRemoved not implemented"); } -nsresult +void ServoRestyleManager::ContentStateChanged(nsIContent* aContent, EventStates aChangedBits) { if (!aContent->IsElement()) { - return NS_OK; + return; } Element* aElement = aContent->AsElement(); nsChangeHint changeHint; nsRestyleHint restyleHint; // NOTE: restyleHint here is effectively always 0, since that's what // ServoStyleSet::HasStateDependentStyle returns. Servo computes on @@ -497,18 +497,16 @@ ServoRestyleManager::ContentStateChanged &restyleHint); EventStates previousState = aElement->StyleState() ^ aChangedBits; ServoElementSnapshot* snapshot = Servo_Element_GetSnapshot(aElement); if (snapshot) { snapshot->AddState(previousState); PostRestyleEvent(aElement, restyleHint, changeHint); } - - return NS_OK; } void ServoRestyleManager::AttributeWillChange(Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType, const nsAttrValue* aNewValue) {
--- a/layout/base/ServoRestyleManager.h +++ b/layout/base/ServoRestyleManager.h @@ -58,18 +58,17 @@ public: void ContentRemoved(nsINode* aContainer, nsIContent* aOldChild, nsIContent* aFollowingSibling); void RestyleForInsertOrChange(nsINode* aContainer, nsIContent* aChild); void RestyleForAppend(nsIContent* aContainer, nsIContent* aFirstNewContent); - nsresult ContentStateChanged(nsIContent* aContent, - EventStates aStateMask); + void ContentStateChanged(nsIContent* aContent, EventStates aStateMask); void AttributeWillChange(dom::Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType, const nsAttrValue* aNewValue); void AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType,
--- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -3210,17 +3210,17 @@ nsCSSFrameConstructor::ConstructSelectFr return listFrame; } /** * Used to be InitializeScrollFrame but now it's only used for the select tag * But the select tag should really be fixed to use GFX scrollbars that can * be create with BuildScrollFrame. */ -nsresult +void nsCSSFrameConstructor::InitializeSelectFrame(nsFrameConstructorState& aState, nsContainerFrame* scrollFrame, nsContainerFrame* scrolledFrame, nsIContent* aContent, nsContainerFrame* aParentFrame, nsStyleContext* aStyleContext, bool aBuildCombobox, PendingBinding* aPendingBinding, @@ -3256,17 +3256,16 @@ nsCSSFrameConstructor::InitializeSelectF // Process children nsFrameItems childItems; ProcessChildren(aState, aContent, aStyleContext, scrolledFrame, false, childItems, false, aPendingBinding); // Set the scrolled frame's initial child lists scrolledFrame->SetInitialChildList(kPrincipalList, childItems); - return NS_OK; } nsIFrame* nsCSSFrameConstructor::ConstructFieldSetFrame(nsFrameConstructorState& aState, FrameConstructionItem& aItem, nsContainerFrame* aParentFrame, const nsStyleDisplay* aStyleDisplay, nsFrameItems& aFrameItems) @@ -6259,26 +6258,26 @@ nsCSSFrameConstructor::ConstructFramesFr inline bool IsRootBoxFrame(nsIFrame *aFrame) { return (aFrame->GetType() == nsGkAtoms::rootFrame); } -nsresult +void nsCSSFrameConstructor::ReconstructDocElementHierarchy() { Element* rootElement = mDocument->GetRootElement(); if (!rootElement) { /* nothing to do */ - return NS_OK; - } - return RecreateFramesForContent(rootElement, false, REMOVE_FOR_RECONSTRUCTION, - nullptr); + return; + } + RecreateFramesForContent(rootElement, false, REMOVE_FOR_RECONSTRUCTION, + nullptr); } nsContainerFrame* nsCSSFrameConstructor::GetAbsoluteContainingBlock(nsIFrame* aFrame, ContainingBlockType aType) { // Starting with aFrame, look for a frame that is absolutely positioned or // relatively positioned (and transformed, if aType is FIXED) @@ -6518,17 +6517,17 @@ GetInsertNextSibling(nsIFrame* aParentFr return aParentFrame->PrincipalChildList().FirstChild(); } /** * This function is called by ContentAppended() and ContentInserted() when * appending flowed frames to a parent's principal child list. It handles the * case where the parent is the trailing inline of an {ib} split. */ -nsresult +void nsCSSFrameConstructor::AppendFramesToParent(nsFrameConstructorState& aState, nsContainerFrame* aParentFrame, nsFrameItems& aFrameList, nsIFrame* aPrevSibling, bool aIsRecursiveCall) { NS_PRECONDITION(!IsFramePartOfIBSplit(aParentFrame) || !GetIBSplitSibling(aParentFrame) || @@ -6601,25 +6600,23 @@ nsCSSFrameConstructor::AppendFramesToPar // last one and now isn't anymore, since its GetSkipSides() has // changed. mPresShell->FrameNeedsReflow(aParentFrame, nsIPresShell::eTreeChange, NS_FRAME_HAS_DIRTY_CHILDREN); // Recurse so we create new ib siblings as needed for aParentFrame's parent return AppendFramesToParent(aState, aParentFrame->GetParent(), ibSiblings, - aParentFrame, true); - } - - return NS_OK; + aParentFrame, true); + } + return; } // Insert the frames after our aPrevSibling InsertFrames(aParentFrame, kPrincipalList, aPrevSibling, aFrameList); - return NS_OK; } #define UNSET_DISPLAY static_cast<StyleDisplay>(255) // This gets called to see if the frames corresponding to aSibling and aContent // should be siblings in the frame tree. Although (1) rows and cols, (2) row // groups and col groups, (3) row groups and captions, (4) legends and content // inside fieldsets, (5) popups and other kids of the menu are siblings from a @@ -7378,17 +7375,17 @@ nsCSSFrameConstructor::MaybeRecreateForF REMOVE_FOR_RECONSTRUCTION, nullptr); return true; } } } return false; } -nsresult +void nsCSSFrameConstructor::ContentAppended(nsIContent* aContainer, nsIContent* aFirstNewContent, bool aAllowLazyConstruction, TreeMatchContext* aProvidedTreeMatchContext) { MOZ_ASSERT_IF(aProvidedTreeMatchContext, !aAllowLazyConstruction); AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC); @@ -7424,34 +7421,33 @@ nsCSSFrameConstructor::ContentAppended(n int32_t namespaceID; nsIAtom* tag = mDocument->BindingManager()->ResolveTag(aContainer, &namespaceID); // Just ignore tree tags, anyway we don't create any frames for them. if (tag == nsGkAtoms::treechildren || tag == nsGkAtoms::treeitem || tag == nsGkAtoms::treerow) - return NS_OK; - + return; } #endif // MOZ_XUL if (aContainer && aContainer->HasFlag(NODE_IS_IN_SHADOW_TREE) && !aContainer->IsInNativeAnonymousSubtree() && !aFirstNewContent->IsInNativeAnonymousSubtree()) { // Recreate frames if content is appended into a ShadowRoot // because children of ShadowRoot are rendered in place of children // of the host. //XXXsmaug This is super unefficient! nsIContent* bindingParent = aContainer->GetBindingParent(); LAYOUT_PHASE_TEMP_EXIT(); - nsresult rv = RecreateFramesForContent(bindingParent, false, - REMOVE_FOR_RECONSTRUCTION, nullptr); + RecreateFramesForContent(bindingParent, false, + REMOVE_FOR_RECONSTRUCTION, nullptr); LAYOUT_PHASE_TEMP_REENTER(); - return rv; + return; } // The frame constructor uses this codepath both for bonafide newly-added // content and for RestyleManager-driven frame construction (RECONSTRUCT_FRAME // and lazy frame construction). If we're using the Servo style system, we // want to ensure that styles get resolved in the first case, whereas for the // second case they should have already been resolved if needed. bool isNewlyAddedContentForServo = aContainer->IsStyledByServo() && @@ -7461,62 +7457,62 @@ nsCSSFrameConstructor::ContentAppended(n if (!GetContentInsertionFrameFor(aContainer) && !aContainer->IsActiveChildrenElement()) { // We're punting on frame construction because there's no container frame. // The Servo-backed style system handles this case like the lazy frame // construction case. if (isNewlyAddedContentForServo) { aContainer->AsElement()->NoteDirtyDescendantsForServo(); } - return NS_OK; + return; } if (aAllowLazyConstruction && MaybeConstructLazily(CONTENTAPPEND, aContainer, aFirstNewContent)) { if (isNewlyAddedContentForServo) { aContainer->AsElement()->NoteDirtyDescendantsForServo(); } - return NS_OK; + return; } // We couldn't construct lazily. Make Servo eagerly traverse the subtree. if (isNewlyAddedContentForServo) { mPresShell->StyleSet()->AsServo()->StyleNewChildren(aContainer->AsElement()); } LAYOUT_PHASE_TEMP_EXIT(); InsertionPoint insertion = GetRangeInsertionPoint(aContainer, aFirstNewContent, nullptr, aAllowLazyConstruction); nsContainerFrame*& parentFrame = insertion.mParentFrame; LAYOUT_PHASE_TEMP_REENTER(); if (!parentFrame) { - return NS_OK; + return; } LAYOUT_PHASE_TEMP_EXIT(); if (MaybeRecreateForFrameset(parentFrame, aFirstNewContent, nullptr)) { LAYOUT_PHASE_TEMP_REENTER(); - return NS_OK; + return; } LAYOUT_PHASE_TEMP_REENTER(); if (parentFrame->IsLeaf()) { // Nothing to do here; we shouldn't be constructing kids of leaves // Clear lazy bits so we don't try to construct again. ClearLazyBits(aFirstNewContent, nullptr); - return NS_OK; + return; } if (parentFrame->IsFrameOfType(nsIFrame::eMathML)) { LAYOUT_PHASE_TEMP_EXIT(); - nsresult rv = RecreateFramesForContent(parentFrame->GetContent(), false, - REMOVE_FOR_RECONSTRUCTION, nullptr); + RecreateFramesForContent(parentFrame->GetContent(), false, + REMOVE_FOR_RECONSTRUCTION, nullptr); LAYOUT_PHASE_TEMP_REENTER(); - return rv; + return; } // If the frame we are manipulating is a ib-split frame (that is, one // that's been created as a result of a block-in-inline situation) then we // need to append to the last ib-split sibling, not to the frame itself. bool parentIBSplit = IsFramePartOfIBSplit(parentFrame); if (parentIBSplit) { #ifdef DEBUG @@ -7613,17 +7609,17 @@ nsCSSFrameConstructor::ContentAppended(n // Perform special check for diddling around with the frames in // a ib-split inline frame. // If we're appending before :after content, then we're not really // appending, so let WipeContainingBlock know that. LAYOUT_PHASE_TEMP_EXIT(); if (WipeContainingBlock(state, containingBlock, parentFrame, items, true, prevSibling)) { LAYOUT_PHASE_TEMP_REENTER(); - return NS_OK; + return; } LAYOUT_PHASE_TEMP_REENTER(); // If the parent is a block frame, and we're not in a special case // where frames can be moved around, determine if the list is for the // start or end of the block. if (nsLayoutUtils::GetAsBlock(parentFrame) && !haveFirstLetterStyle && !haveFirstLineStyle && !parentIBSplit) { @@ -7699,18 +7695,16 @@ nsCSSFrameConstructor::ContentAppended(n #ifdef ACCESSIBILITY nsAccessibilityService* accService = nsIPresShell::AccService(); if (accService) { accService->ContentRangeInserted(mPresShell, aContainer, aFirstNewContent, nullptr); } #endif - - return NS_OK; } #ifdef MOZ_XUL enum content_operation { CONTENT_INSERTED, CONTENT_REMOVED @@ -7743,27 +7737,27 @@ bool NotifyListBoxBody(nsPresContext* return true; } } return false; } #endif // MOZ_XUL -nsresult +void nsCSSFrameConstructor::ContentInserted(nsIContent* aContainer, nsIContent* aChild, nsILayoutHistoryState* aFrameState, bool aAllowLazyConstruction) { - return ContentRangeInserted(aContainer, - aChild, - aChild->GetNextSibling(), - aFrameState, - aAllowLazyConstruction); + ContentRangeInserted(aContainer, + aChild, + aChild->GetNextSibling(), + aFrameState, + aAllowLazyConstruction); } // ContentRangeInserted handles creating frames for a range of nodes that // aren't at the end of their childlist. ContentRangeInserted isn't a real // content notification, but rather it handles regular ContentInserted calls // for a single node as well as the lazy construction of frames for a range of // nodes when called from CreateNeededFrames. For a range of nodes to be // suitable to have its frames constructed all at once they must meet the same @@ -7774,17 +7768,17 @@ nsCSSFrameConstructor::ContentInserted(n // pass the first node in the range to GetInsertionPrevSibling, and if // IsValidSibling (the only place GetInsertionPrevSibling might look at the // passed in node itself) needs to resolve style on the node we record this and // return that this range needs to be split up and inserted separately. Table // captions need extra attention as we need to determine where to insert them // in the caption list, while skipping any nodes in the range being inserted // (because when we treat the caption frames the other nodes have had their // frames constructed but not yet inserted into the frame tree). -nsresult +void nsCSSFrameConstructor::ContentRangeInserted(nsIContent* aContainer, nsIContent* aStartChild, nsIContent* aEndChild, nsILayoutHistoryState* aFrameState, bool aAllowLazyConstruction, TreeMatchContext* aProvidedTreeMatchContext) { AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC); @@ -7832,41 +7826,41 @@ nsCSSFrameConstructor::ContentRangeInser #ifdef MOZ_XUL if (aContainer && IsXULListBox(aContainer)) { if (isSingleInsert) { if (NotifyListBoxBody(mPresShell->GetPresContext(), aContainer, // The insert case in NotifyListBoxBody // doesn't use "old next sibling". aStartChild, nullptr, nullptr, CONTENT_INSERTED)) { - return NS_OK; + return; } } else { // We don't handle a range insert to a listbox parent, issue single // ContertInserted calls for each node inserted. LAYOUT_PHASE_TEMP_EXIT(); IssueSingleInsertNofications(aContainer, aStartChild, aEndChild, aAllowLazyConstruction); LAYOUT_PHASE_TEMP_REENTER(); - return NS_OK; + return; } } #endif // MOZ_XUL // If we have a null parent, then this must be the document element being // inserted, or some other child of the document in the DOM (might be a PI, // say). if (! aContainer) { NS_ASSERTION(isSingleInsert, "root node insertion should be a single insertion"); Element *docElement = mDocument->GetRootElement(); if (aStartChild != docElement) { // Not the root element; just bail out - return NS_OK; + return; } NS_PRECONDITION(nullptr == mRootElementFrame, "root element frame already created"); // Create frames for the document element and its child elements nsIFrame* docElementFrame = ConstructDocElementFrame(docElement, aFrameState); @@ -7893,33 +7887,33 @@ nsCSSFrameConstructor::ContentRangeInser #ifdef ACCESSIBILITY nsAccessibilityService* accService = nsIPresShell::AccService(); if (accService) { accService->ContentRangeInserted(mPresShell, aContainer, aStartChild, aEndChild); } #endif - return NS_OK; + return; } if (aContainer->HasFlag(NODE_IS_IN_SHADOW_TREE) && !aContainer->IsInNativeAnonymousSubtree() && (!aStartChild || !aStartChild->IsInNativeAnonymousSubtree()) && (!aEndChild || !aEndChild->IsInNativeAnonymousSubtree())) { // Recreate frames if content is inserted into a ShadowRoot // because children of ShadowRoot are rendered in place of // the children of the host. //XXXsmaug This is super unefficient! nsIContent* bindingParent = aContainer->GetBindingParent(); LAYOUT_PHASE_TEMP_EXIT(); - nsresult rv = RecreateFramesForContent(bindingParent, false, - REMOVE_FOR_RECONSTRUCTION, nullptr); + RecreateFramesForContent(bindingParent, false, + REMOVE_FOR_RECONSTRUCTION, nullptr); LAYOUT_PHASE_TEMP_REENTER(); - return rv; + return; } // The frame constructor uses this codepath both for bonafide newly-added // content and for RestyleManager-driven frame construction (RECONSTRUCT_FRAME // and lazy frame construction). If we're using the Servo style system, we // want to ensure that styles get resolved in the first case, whereas for the // second case they should have already been resolved if needed. bool isNewlyAddedContentForServo = aContainer->IsStyledByServo() && @@ -7935,29 +7929,29 @@ nsCSSFrameConstructor::ContentRangeInser // for example, can be reframed by having its value attribute set or removed. if (!parentFrame && !aContainer->IsActiveChildrenElement()) { // We're punting on frame construction because there's no container frame. // The Servo-backed style system handles this case like the lazy frame // construction case. if (isNewlyAddedContentForServo) { aContainer->AsElement()->NoteDirtyDescendantsForServo(); } - return NS_OK; + return; } // Otherwise, we've got parent content. Find its frame. NS_ASSERTION(!parentFrame || parentFrame->GetContent() == aContainer || GetDisplayContentsStyleFor(aContainer), "New XBL code is possibly wrong!"); if (aAllowLazyConstruction && MaybeConstructLazily(CONTENTINSERT, aContainer, aStartChild)) { if (isNewlyAddedContentForServo) { aContainer->AsElement()->NoteDirtyDescendantsForServo(); } - return NS_OK; + return; } } // We couldn't construct lazily. Make Servo eagerly traverse the subtree. if (isNewlyAddedContentForServo) { mPresShell->StyleSet()->AsServo()->StyleNewChildren(aContainer->AsElement()); } @@ -7972,91 +7966,90 @@ nsCSSFrameConstructor::ContentRangeInser // GetRangeInsertionPoint will take care of that for us. LAYOUT_PHASE_TEMP_EXIT(); insertion = GetRangeInsertionPoint(aContainer, aStartChild, aEndChild, aAllowLazyConstruction); LAYOUT_PHASE_TEMP_REENTER(); } if (!insertion.mParentFrame) { - return NS_OK; + return; } bool isAppend, isRangeInsertSafe; nsIFrame* prevSibling = GetInsertionPrevSibling(&insertion, aStartChild, &isAppend, &isRangeInsertSafe); // check if range insert is safe if (!isSingleInsert && !isRangeInsertSafe) { // must fall back to a single ContertInserted for each child in the range LAYOUT_PHASE_TEMP_EXIT(); IssueSingleInsertNofications(aContainer, aStartChild, aEndChild, aAllowLazyConstruction); LAYOUT_PHASE_TEMP_REENTER(); - return NS_OK; + return; } nsIContent* container = insertion.mParentFrame->GetContent(); nsIAtom* frameType = insertion.mParentFrame->GetType(); LAYOUT_PHASE_TEMP_EXIT(); if (MaybeRecreateForFrameset(insertion.mParentFrame, aStartChild, aEndChild)) { LAYOUT_PHASE_TEMP_REENTER(); - return NS_OK; + return; } LAYOUT_PHASE_TEMP_REENTER(); // We should only get here with fieldsets when doing a single insert, because // fieldsets have multiple insertion points. NS_ASSERTION(isSingleInsert || frameType != nsGkAtoms::fieldSetFrame, "Unexpected parent"); if (IsFrameForFieldSet(insertion.mParentFrame, frameType) && aStartChild->NodeInfo()->NameAtom() == nsGkAtoms::legend) { // Just reframe the parent, since figuring out whether this // should be the new legend and then handling it is too complex. // We could do a little better here --- check if the fieldset already // has a legend which occurs earlier in its child list than this node, // and if so, proceed. But we'd have to extend nsFieldSetFrame // to locate this legend in the inserted frames and extract it. LAYOUT_PHASE_TEMP_EXIT(); - nsresult rv = RecreateFramesForContent(insertion.mParentFrame->GetContent(), false, - REMOVE_FOR_RECONSTRUCTION, nullptr); + RecreateFramesForContent(insertion.mParentFrame->GetContent(), false, + REMOVE_FOR_RECONSTRUCTION, nullptr); LAYOUT_PHASE_TEMP_REENTER(); - return rv; + return; } // We should only get here with details when doing a single insertion because // we treat details frame as if it has multiple insertion points. MOZ_ASSERT(isSingleInsert || frameType != nsGkAtoms::detailsFrame); if (frameType == nsGkAtoms::detailsFrame) { // When inserting an element into <details>, just reframe the details frame // and let it figure out where the element should be laid out. It might seem // expensive to recreate the entire details frame, but it's the simplest way // to handle the insertion. LAYOUT_PHASE_TEMP_EXIT(); - nsresult rv = - RecreateFramesForContent(insertion.mParentFrame->GetContent(), false, - REMOVE_FOR_RECONSTRUCTION, nullptr); + RecreateFramesForContent(insertion.mParentFrame->GetContent(), false, + REMOVE_FOR_RECONSTRUCTION, nullptr); LAYOUT_PHASE_TEMP_REENTER(); - return rv; + return; } // Don't construct kids of leaves if (insertion.mParentFrame->IsLeaf()) { // Clear lazy bits so we don't try to construct again. ClearLazyBits(aStartChild, aEndChild); - return NS_OK; + return; } if (insertion.mParentFrame->IsFrameOfType(nsIFrame::eMathML)) { LAYOUT_PHASE_TEMP_EXIT(); - nsresult rv = RecreateFramesForContent(insertion.mParentFrame->GetContent(), false, - REMOVE_FOR_RECONSTRUCTION, nullptr); + RecreateFramesForContent(insertion.mParentFrame->GetContent(), false, + REMOVE_FOR_RECONSTRUCTION, nullptr); LAYOUT_PHASE_TEMP_REENTER(); - return rv; + return; } Maybe<TreeMatchContext> matchContext; if (!aProvidedTreeMatchContext) { matchContext.emplace(mDocument, TreeMatchContext::ForFrameConstruction); matchContext->InitAncestors(aContainer ? aContainer->AsElement() : nullptr); } nsFrameConstructorState state(mPresShell, @@ -8127,17 +8120,17 @@ nsCSSFrameConstructor::ContentRangeInser // Need to recover the letter frames first. RecoverLetterFrames(state.mFloatedItems.containingBlock); // must fall back to a single ContertInserted for each child in the range LAYOUT_PHASE_TEMP_EXIT(); IssueSingleInsertNofications(aContainer, aStartChild, aEndChild, aAllowLazyConstruction); LAYOUT_PHASE_TEMP_REENTER(); - return NS_OK; + return; } container = insertion.mParentFrame->GetContent(); frameType = insertion.mParentFrame->GetType(); } } if (!prevSibling) { @@ -8195,17 +8188,17 @@ nsCSSFrameConstructor::ContentRangeInser // Perform special check for diddling around with the frames in // a special inline frame. // If we're appending before :after content, then we're not really // appending, so let WipeContainingBlock know that. LAYOUT_PHASE_TEMP_EXIT(); if (WipeContainingBlock(state, containingBlock, insertion.mParentFrame, items, isAppend, prevSibling)) { LAYOUT_PHASE_TEMP_REENTER(); - return NS_OK; + return; } LAYOUT_PHASE_TEMP_REENTER(); // If the container is a table and a caption will be appended, it needs to be // put in the table wrapper frame's additional child list. // We make no attempt here to set flags to indicate whether the list // will be at the start or end of a block. It doesn't seem worthwhile. nsFrameItems frameItems, captionItems; @@ -8366,21 +8359,19 @@ nsCSSFrameConstructor::ContentRangeInser #ifdef ACCESSIBILITY nsAccessibilityService* accService = nsIPresShell::AccService(); if (accService) { accService->ContentRangeInserted(mPresShell, aContainer, aStartChild, aEndChild); } #endif - - return NS_OK; -} - -nsresult +} + +void nsCSSFrameConstructor::ContentRemoved(nsIContent* aContainer, nsIContent* aChild, nsIContent* aOldNextSibling, RemoveFlags aFlags, bool* aDidReconstruct, nsIContent** aDestroyedFramesFor) { AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC); @@ -8410,17 +8401,16 @@ nsCSSFrameConstructor::ContentRemoved(ns static_cast<void*>(aChild), static_cast<void*>(aOldNextSibling)); if (gReallyNoisyContentUpdates) { aContainer->List(stdout, 0); } } #endif - nsresult rv = NS_OK; nsIFrame* childFrame = aChild->GetPrimaryFrame(); if (!childFrame || childFrame->GetContent() != aChild) { // XXXbz the GetContent() != aChild check is needed due to bug 135040. // Remove it once that's fixed. ClearUndisplayedContentIn(aChild, aContainer); } MOZ_ASSERT(!childFrame || !GetDisplayContentsStyleFor(aChild), "display:contents nodes shouldn't have a frame"); @@ -8437,43 +8427,42 @@ nsCSSFrameConstructor::ContentRemoved(ns nsTArray<nsIContent*>* generated = ancestorFrame->GetGenConPseudos(); if (generated) { *aDidReconstruct = true; LAYOUT_PHASE_TEMP_EXIT(); // XXXmats Can we recreate frames only for the ::after/::before content? // XXX Perhaps even only those that belong to the aChild sub-tree? RecreateFramesForContent(ancestor, false, aFlags, aDestroyedFramesFor); LAYOUT_PHASE_TEMP_REENTER(); - return NS_OK; + return; } } FlattenedChildIterator iter(aChild); for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) { if (c->GetPrimaryFrame() || GetDisplayContentsStyleFor(c)) { LAYOUT_PHASE_TEMP_EXIT(); - rv = ContentRemoved(aChild, c, nullptr, aFlags, aDidReconstruct, aDestroyedFramesFor); + ContentRemoved(aChild, c, nullptr, aFlags, aDidReconstruct, aDestroyedFramesFor); LAYOUT_PHASE_TEMP_REENTER(); - NS_ENSURE_SUCCESS(rv, rv); if (aFlags != REMOVE_DESTROY_FRAMES && *aDidReconstruct) { - return rv; + return; } } } ClearDisplayContentsIn(aChild, aContainer); } nsPresContext* presContext = mPresShell->GetPresContext(); #ifdef MOZ_XUL if (NotifyListBoxBody(presContext, aContainer, aChild, aOldNextSibling, childFrame, CONTENT_REMOVED)) { if (aFlags == REMOVE_DESTROY_FRAMES) { CaptureStateForFramesOf(aChild, mTempFrameTreeState); } - return NS_OK; + return; } #endif // MOZ_XUL // If we're removing the root, then make sure to remove things starting at // the viewport's child instead of the primary frame (which might even be // null if the root had an XBL binding or display:none, even though the // frames above it got created). We do the adjustment after the childFrame @@ -8501,87 +8490,86 @@ nsCSSFrameConstructor::ContentRemoved(ns !aChild->IsInNativeAnonymousSubtree()) { // Recreate frames if content is removed from a ShadowRoot // because it may contain an insertion point which can change // how the host is rendered. //XXXsmaug This is super unefficient! nsIContent* bindingParent = aContainer->GetBindingParent(); *aDidReconstruct = true; LAYOUT_PHASE_TEMP_EXIT(); - nsresult rv = RecreateFramesForContent(bindingParent, false, - aFlags, aDestroyedFramesFor); + RecreateFramesForContent(bindingParent, false, aFlags, aDestroyedFramesFor); LAYOUT_PHASE_TEMP_REENTER(); - return rv; + return; } if (aFlags == REMOVE_DESTROY_FRAMES) { CaptureStateForFramesOf(aChild, mTempFrameTreeState); } if (childFrame) { InvalidateCanvasIfNeeded(mPresShell, aChild); // See whether we need to remove more than just childFrame LAYOUT_PHASE_TEMP_EXIT(); nsIContent* container; - if (MaybeRecreateContainerForFrameRemoval(childFrame, aFlags, &rv, &container)) { + if (MaybeRecreateContainerForFrameRemoval(childFrame, aFlags, &container)) { LAYOUT_PHASE_TEMP_REENTER(); MOZ_ASSERT(container); *aDidReconstruct = true; if (aDestroyedFramesFor) { *aDestroyedFramesFor = container; } - return rv; + return; } LAYOUT_PHASE_TEMP_REENTER(); // Get the childFrame's parent frame nsIFrame* parentFrame = childFrame->GetParent(); nsIAtom* parentType = parentFrame->GetType(); if (parentType == nsGkAtoms::frameSetFrame && IsSpecialFramesetChild(aChild)) { // Just reframe the parent, since framesets are weird like that. *aDidReconstruct = true; LAYOUT_PHASE_TEMP_EXIT(); - nsresult rv = RecreateFramesForContent(parentFrame->GetContent(), false, - aFlags, aDestroyedFramesFor); + RecreateFramesForContent(parentFrame->GetContent(), false, + aFlags, aDestroyedFramesFor); LAYOUT_PHASE_TEMP_REENTER(); - return rv; + return; } // If we're a child of MathML, then we should reframe the MathML content. // If we're non-MathML, then we would be wrapped in a block so we need to // check our grandparent in that case. nsIFrame* possibleMathMLAncestor = parentType == nsGkAtoms::blockFrame ? parentFrame->GetParent() : parentFrame; if (possibleMathMLAncestor->IsFrameOfType(nsIFrame::eMathML)) { *aDidReconstruct = true; LAYOUT_PHASE_TEMP_EXIT(); - nsresult rv = RecreateFramesForContent(possibleMathMLAncestor->GetContent(), - false, aFlags, aDestroyedFramesFor); + RecreateFramesForContent(possibleMathMLAncestor->GetContent(), + false, aFlags, aDestroyedFramesFor); LAYOUT_PHASE_TEMP_REENTER(); - return rv; + return; } // Undo XUL wrapping if it's no longer needed. // (If we're in the XUL block-wrapping situation, parentFrame is the // wrapper frame.) nsIFrame* grandparentFrame = parentFrame->GetParent(); if (grandparentFrame && grandparentFrame->IsXULBoxFrame() && (grandparentFrame->GetStateBits() & NS_STATE_BOX_WRAPS_KIDS_IN_BLOCK) && // check if this frame is the only one needing wrapping aChild == AnyKidsNeedBlockParent(parentFrame->PrincipalChildList().FirstChild()) && !AnyKidsNeedBlockParent(childFrame->GetNextSibling())) { *aDidReconstruct = true; LAYOUT_PHASE_TEMP_EXIT(); - nsresult rv = RecreateFramesForContent(grandparentFrame->GetContent(), true, - aFlags, aDestroyedFramesFor); + RecreateFramesForContent(grandparentFrame->GetContent(), true, + aFlags, aDestroyedFramesFor); LAYOUT_PHASE_TEMP_REENTER(); - return rv; + return; } #ifdef ACCESSIBILITY nsAccessibilityService* accService = nsIPresShell::AccService(); if (accService) { accService->ContentRemoved(mPresShell, aChild); } #endif @@ -8615,17 +8603,17 @@ nsCSSFrameConstructor::ContentRemoved(ns RemoveLetterFrames(mPresShell, containingBlock); // Recover childFrame and parentFrame childFrame = aChild->GetPrimaryFrame(); if (!childFrame || childFrame->GetContent() != aChild) { // XXXbz the GetContent() != aChild check is needed due to bug 135040. // Remove it once that's fixed. ClearUndisplayedContentIn(aChild, aContainer); - return NS_OK; + return; } parentFrame = childFrame->GetParent(); parentType = parentFrame->GetType(); #ifdef NOISY_FIRST_LETTER printf(" ==> revised parentFrame="); nsFrame::ListTag(stdout, parentFrame); printf(" childFrame="); @@ -8703,18 +8691,16 @@ nsCSSFrameConstructor::ContentRemoved(ns #ifdef DEBUG if (gReallyNoisyContentUpdates && parentFrame) { printf("nsCSSFrameConstructor::ContentRemoved: resulting frame model:\n"); parentFrame->List(stdout, 0); } #endif } - - return rv; } /** * This method invalidates the canvas when frames are removed or added for a * node that might have its background propagated to the canvas, i.e., a * document root node or an HTML BODY which is a child of the root node. * * @param aFrame a frame for a content node about to be removed or a frame that @@ -8766,37 +8752,36 @@ nsCSSFrameConstructor::EnsureFrameForTex nsAutoScriptBlocker blocker; BeginUpdate(); ReconstructDocElementHierarchy(); EndUpdate(); } return aContent->GetPrimaryFrame(); } -nsresult +void nsCSSFrameConstructor::CharacterDataChanged(nsIContent* aContent, CharacterDataChangeInfo* aInfo) { AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC); - nsresult rv = NS_OK; if ((aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE) && !aContent->TextIsOnlyWhitespace()) || (aContent->HasFlag(NS_REFRAME_IF_WHITESPACE) && aContent->TextIsOnlyWhitespace())) { #ifdef DEBUG nsIFrame* frame = aContent->GetPrimaryFrame(); NS_ASSERTION(!frame || !frame->IsGeneratedContentFrame(), "Bit should never be set on generated content"); #endif LAYOUT_PHASE_TEMP_EXIT(); - nsresult rv = RecreateFramesForContent(aContent, false, - REMOVE_FOR_RECONSTRUCTION, nullptr); + RecreateFramesForContent(aContent, false, + REMOVE_FOR_RECONSTRUCTION, nullptr); LAYOUT_PHASE_TEMP_REENTER(); - return rv; + return; } // Find the child frame nsIFrame* frame = aContent->GetPrimaryFrame(); // Notify the first frame that maps the content. It will generate a reflow // command @@ -8834,18 +8819,16 @@ nsCSSFrameConstructor::CharacterDataChan } frame->CharacterDataChanged(aInfo); if (haveFirstLetterStyle) { RecoverLetterFrames(block); } } - - return rv; } void nsCSSFrameConstructor::BeginUpdate() { NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), "Someone forgot a script blocker"); nsRootPresContext* rootPresContext = @@ -9502,22 +9485,20 @@ FindPreviousNonWhitespaceSibling(nsIFram f = f->GetPrevSibling(); } while (f && IsWhitespaceFrame(f)); return f; } bool nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval(nsIFrame* aFrame, RemoveFlags aFlags, - nsresult* aResult, nsIContent** aDestroyedFramesFor) { NS_PRECONDITION(aFrame, "Must have a frame"); NS_PRECONDITION(aFrame->GetParent(), "Frame shouldn't be root"); - NS_PRECONDITION(aResult, "Null out param?"); NS_PRECONDITION(aFrame == aFrame->FirstContinuation(), "aFrame not the result of GetPrimaryFrame()?"); *aDestroyedFramesFor = nullptr; if (IsFramePartOfIBSplit(aFrame)) { // The removal functions can't handle removal of an {ib} split directly; we // need to rebuild the containing block. @@ -9525,42 +9506,42 @@ nsCSSFrameConstructor::MaybeRecreateCont if (gNoisyContentUpdates) { printf("nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval: " "frame="); nsFrame::ListTag(stdout, aFrame); printf(" is ib-split\n"); } #endif - *aResult = ReframeContainingBlock(aFrame, aFlags, aDestroyedFramesFor); + ReframeContainingBlock(aFrame, aFlags, aDestroyedFramesFor); return true; } nsContainerFrame* insertionFrame = aFrame->GetContentInsertionFrame(); if (insertionFrame && insertionFrame->GetType() == nsGkAtoms::legendFrame && aFrame->GetParent()->GetType() == nsGkAtoms::fieldSetFrame) { // When we remove the legend for a fieldset, we should reframe // the fieldset to ensure another legend is used, if there is one - *aResult = RecreateFramesForContent(aFrame->GetParent()->GetContent(), false, - aFlags, aDestroyedFramesFor); + RecreateFramesForContent(aFrame->GetParent()->GetContent(), false, + aFlags, aDestroyedFramesFor); return true; } if (insertionFrame && aFrame->GetParent()->GetType() == nsGkAtoms::detailsFrame) { HTMLSummaryElement* summary = HTMLSummaryElement::FromContent(insertionFrame->GetContent()); if (summary && summary->IsMainSummary()) { // When removing a summary, we should reframe the parent details frame to // ensure that another summary is used or the default summary is // generated. - *aResult = RecreateFramesForContent(aFrame->GetParent()->GetContent(), - false, REMOVE_FOR_RECONSTRUCTION, - aDestroyedFramesFor); + RecreateFramesForContent(aFrame->GetParent()->GetContent(), + false, REMOVE_FOR_RECONSTRUCTION, + aDestroyedFramesFor); return true; } } // Now check for possibly needing to reconstruct due to a pseudo parent nsIFrame* inFlowFrame = (aFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) ? GetPlaceholderFrameFor(aFrame) : aFrame; @@ -9582,18 +9563,18 @@ nsCSSFrameConstructor::MaybeRecreateCont // not going to catch cases when we're the first child. (inFlowFrame->GetType() == nsGkAtoms::tableColGroupFrame && parent->GetChildList(nsIFrame::kColGroupList).FirstChild() == inFlowFrame) || // Similar if we're a table-caption. (inFlowFrame->IsTableCaption() && parent->GetChildList(nsIFrame::kCaptionList).FirstChild() == inFlowFrame)) { // We're the first or last frame in the pseudo. Need to reframe. // Good enough to recreate frames for |parent|'s content - *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags, - aDestroyedFramesFor); + RecreateFramesForContent(parent->GetContent(), true, aFlags, + aDestroyedFramesFor); return true; } } // Might need to reconstruct things if this frame's nextSibling is a table // or ruby pseudo, since removal of this frame might mean that this pseudo // needs to get merged with the frame's prevSibling if that's also a table // or ruby pseudo. @@ -9612,35 +9593,35 @@ nsCSSFrameConstructor::MaybeRecreateCont "frame="); nsFrame::ListTag(stdout, aFrame); printf(" has a table pseudo next sibling of different type and a " "table pseudo prevsibling\n"); } #endif // Good enough to recreate frames for aFrame's parent's content; even if // aFrame's parent is a pseudo, that'll be the right content node. - *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags, - aDestroyedFramesFor); + RecreateFramesForContent(parent->GetContent(), true, aFlags, + aDestroyedFramesFor); return true; } } // Check ruby containers nsIAtom* parentType = parent->GetType(); if (parentType == nsGkAtoms::rubyFrame || RubyUtils::IsRubyContainerBox(parentType)) { // In ruby containers, pseudo frames may be created from // whitespaces or even nothing. There are two cases we actually // need to handle here, but hard to check exactly: // 1. Status of spaces beside the frame may vary, and related // frames may be constructed or destroyed accordingly. // 2. The type of the first child of a ruby frame determines // whether a pseudo ruby base container should exist. - *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags, - aDestroyedFramesFor); + RecreateFramesForContent(parent->GetContent(), true, aFlags, + aDestroyedFramesFor); return true; } // Might need to reconstruct things if the removed frame's nextSibling is an // anonymous flex item. The removed frame might've been what divided two // runs of inline content into two anonymous flex items, which would now // need to be merged. // NOTE: It's fine that we've advanced nextSibling past whitespace (up above); @@ -9653,18 +9634,18 @@ nsCSSFrameConstructor::MaybeRecreateCont if (gNoisyContentUpdates) { printf("nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval: " "frame="); nsFrame::ListTag(stdout, aFrame); printf(" has an anonymous flex item as its next sibling\n"); } #endif // DEBUG // Recreate frames for the flex container (the removed frame's parent) - *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags, - aDestroyedFramesFor); + RecreateFramesForContent(parent->GetContent(), true, aFlags, + aDestroyedFramesFor); return true; } // Might need to reconstruct things if the removed frame's nextSibling is // null and its parent is an anonymous flex item. (This might be the last // remaining child of that anonymous flex item, which can then go away.) if (!nextSibling && IsAnonymousFlexOrGridItem(parent)) { AssertAnonymousFlexOrGridItemParent(parent, parent->GetParent()); @@ -9672,39 +9653,39 @@ nsCSSFrameConstructor::MaybeRecreateCont if (gNoisyContentUpdates) { printf("nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval: " "frame="); nsFrame::ListTag(stdout, aFrame); printf(" has an anonymous flex item as its parent\n"); } #endif // DEBUG // Recreate frames for the flex container (the removed frame's grandparent) - *aResult = RecreateFramesForContent(parent->GetParent()->GetContent(), true, - aFlags, aDestroyedFramesFor); + RecreateFramesForContent(parent->GetParent()->GetContent(), true, + aFlags, aDestroyedFramesFor); return true; } #ifdef MOZ_XUL if (aFrame->GetType() == nsGkAtoms::popupSetFrame) { nsIRootBox* rootBox = nsIRootBox::GetRootBox(mPresShell); if (rootBox && rootBox->GetPopupSetFrame() == aFrame) { - *aResult = ReconstructDocElementHierarchy(); + ReconstructDocElementHierarchy(); return true; } } #endif // Reconstruct if inflowFrame is parent's only child, and parent is, or has, // a non-fluid continuation, i.e. it was split by bidi resolution if (!inFlowFrame->GetPrevSibling() && !inFlowFrame->GetNextSibling() && ((parent->GetPrevContinuation() && !parent->GetPrevInFlow()) || (parent->GetNextContinuation() && !parent->GetNextInFlow()))) { - *aResult = RecreateFramesForContent(parent->GetContent(), true, aFlags, - aDestroyedFramesFor); + RecreateFramesForContent(parent->GetContent(), true, aFlags, + aDestroyedFramesFor); return true; } // We might still need to reconstruct things if the parent of inFlowFrame is // ib-split, since in that case the removal of aFrame might affect the // splitting of its parent. if (!IsFramePartOfIBSplit(parent)) { return false; @@ -9730,34 +9711,36 @@ nsCSSFrameConstructor::MaybeRecreateCont if (gNoisyContentUpdates) { printf("nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval: " "frame="); nsFrame::ListTag(stdout, parent); printf(" is ib-split\n"); } #endif - *aResult = ReframeContainingBlock(parent, aFlags, aDestroyedFramesFor); + ReframeContainingBlock(parent, aFlags, aDestroyedFramesFor); return true; } -nsresult +void nsCSSFrameConstructor::RecreateFramesForContent(nsIContent* aContent, bool aAsyncInsert, RemoveFlags aFlags, nsIContent** aDestroyedFramesFor) { NS_PRECONDITION(!aAsyncInsert || aContent->IsElement(), "Can only insert elements async"); // If there is no document, we don't want to recreate frames for it. (You // shouldn't generally be giving this method content without a document // anyway). // Rebuilding the frame tree can have bad effects, especially if it's the // frame tree for chrome (see bug 157322). - NS_ENSURE_TRUE(aContent->GetComposedDoc(), NS_ERROR_FAILURE); + if (NS_WARN_IF(!aContent->GetComposedDoc())) { + return; + } // Is the frame ib-split? If so, we need to reframe the containing // block *here*, rather than trying to remove and re-insert the // content (which would otherwise result in *two* nested reframe // containing block from ContentRemoved() and ContentInserted(), // below!). We'd really like to optimize away one of those // containing block reframes, hence the code here. @@ -9811,25 +9794,24 @@ nsCSSFrameConstructor::RecreateFramesFor // with native anonymous content from the editor. if (parent && parent->IsLeaf() && parentContent && parentContent != aContent) { return RecreateFramesForContent(parentContent, aAsyncInsert, aFlags, aDestroyedFramesFor); } } - nsresult rv = NS_OK; nsIContent* container; - if (frame && MaybeRecreateContainerForFrameRemoval(frame, aFlags, &rv, + if (frame && MaybeRecreateContainerForFrameRemoval(frame, aFlags, &container)) { MOZ_ASSERT(container); if (aDestroyedFramesFor) { *aDestroyedFramesFor = container; } - return rv; + return; } nsINode* containerNode = aContent->GetParentNode(); // XXXbz how can containerNode be null here? if (containerNode) { // Before removing the frames associated with the content object, // ask them to save their state onto a temporary state object. CaptureStateForFramesOf(aContent, mTempFrameTreeState); @@ -9839,36 +9821,31 @@ nsCSSFrameConstructor::RecreateFramesFor nsCOMPtr<nsIContent> container = aContent->GetParent(); // Remove the frames associated with the content object. bool didReconstruct; nsIContent* nextSibling = aContent->IsRootOfAnonymousSubtree() ? nullptr : aContent->GetNextSibling(); const bool reconstruct = aFlags != REMOVE_DESTROY_FRAMES; RemoveFlags flags = reconstruct ? REMOVE_FOR_RECONSTRUCTION : aFlags; - rv = ContentRemoved(container, aContent, nextSibling, flags, - &didReconstruct, aDestroyedFramesFor); - if (NS_FAILED(rv)) { - return rv; - } + ContentRemoved(container, aContent, nextSibling, flags, + &didReconstruct, aDestroyedFramesFor); if (reconstruct && !didReconstruct) { // Now, recreate the frames associated with this content object. If // ContentRemoved triggered reconstruction, then we don't need to do this // because the frames will already have been built. if (aAsyncInsert) { // XXXmats doesn't frame state need to be restored in this case too? RestyleManager()->PostRestyleEvent( aContent->AsElement(), nsRestyleHint(0), nsChangeHint_ReconstructFrame); } else { - rv = ContentInserted(container, aContent, mTempFrameTreeState, false); - } - } - } - - return rv; + ContentInserted(container, aContent, mTempFrameTreeState, false); + } + } + } } void nsCSSFrameConstructor::DestroyFramesFor(nsIContent* aContent, nsIContent** aDestroyedFramesFor) { MOZ_ASSERT(aContent && aContent->GetParentNode()); @@ -11266,26 +11243,25 @@ nsCSSFrameConstructor::AppendFirstLineFr nsFirstLineFrame* lineFrame = static_cast<nsFirstLineFrame*>(lastBlockKid); WrapFramesInFirstLineFrame(aState, aBlockContent, aBlockFrame, lineFrame, aFrameItems); } // Special routine to handle inserting a new frame into a block // frame's child list. Takes care of placing the new frame into the // right place when first-line style is present. -nsresult +void nsCSSFrameConstructor::InsertFirstLineFrames( nsFrameConstructorState& aState, nsIContent* aContent, nsIFrame* aBlockFrame, nsContainerFrame** aParentFrame, nsIFrame* aPrevSibling, nsFrameItems& aFrameItems) { - nsresult rv = NS_OK; // XXXbz If you make this method actually do something, check to // make sure that the caller is passing what you expect. In // particular, which content is aContent? And audit the rest of // this code too; it makes bogus assumptions and may not build. #if 0 nsIFrame* parentFrame = *aParentFrame; nsIFrame* newFrame = aFrameItems.childList; bool isInline = IsInlineOutside(newFrame); @@ -11405,17 +11381,16 @@ nsCSSFrameConstructor::InsertFirstLineFr aFrameItems.childList = nullptr; aFrameItems.lastChild = nullptr; } } } } #endif - return rv; } //---------------------------------------------------------------------- // First-letter support // Determine how many characters in the text fragment apply to the // first letter @@ -11732,58 +11707,58 @@ FindFirstLetterFrame(nsIFrame* aFrame, n for (nsFrameList::Enumerator e(list); !e.AtEnd(); e.Next()) { if (nsGkAtoms::letterFrame == e.get()->GetType()) { return e.get(); } } return nullptr; } -nsresult +void nsCSSFrameConstructor::RemoveFloatingFirstLetterFrames( nsIPresShell* aPresShell, nsIFrame* aBlockFrame) { // Look for the first letter frame on the kFloatList, then kPushedFloatsList. nsIFrame* floatFrame = ::FindFirstLetterFrame(aBlockFrame, nsIFrame::kFloatList); if (!floatFrame) { floatFrame = ::FindFirstLetterFrame(aBlockFrame, nsIFrame::kPushedFloatsList); if (!floatFrame) { - return NS_OK; + return; } } // Take the text frame away from the letter frame (so it isn't // destroyed when we destroy the letter frame). nsIFrame* textFrame = floatFrame->PrincipalChildList().FirstChild(); if (!textFrame) { - return NS_OK; + return; } // Discover the placeholder frame for the letter frame nsPlaceholderFrame* placeholderFrame = GetPlaceholderFrameFor(floatFrame); if (!placeholderFrame) { // Somethings really wrong - return NS_OK; + return; } nsContainerFrame* parentFrame = placeholderFrame->GetParent(); if (!parentFrame) { // Somethings really wrong - return NS_OK; + return; } // Create a new text frame with the right style context that maps // all of the content that was previously part of the letter frame // (and probably continued elsewhere). nsStyleContext* parentSC = parentFrame->StyleContext(); nsIContent* textContent = textFrame->GetContent(); if (!textContent) { - return NS_OK; + return; } RefPtr<nsStyleContext> newSC = aPresShell->StyleSet()-> ResolveStyleForText(textContent, parentSC); nsIFrame* newTextFrame = NS_NewTextFrame(aPresShell, newSC); newTextFrame->Init(textContent, parentFrame, nullptr); // Destroy the old text frame's continuations (the old text frame // will be destroyed when its letter frame is destroyed). @@ -11818,21 +11793,19 @@ nsCSSFrameConstructor::RemoveFloatingFir // Insert text frame in its place nsFrameList textList(newTextFrame, newTextFrame); InsertFrames(parentFrame, kPrincipalList, prevSibling, textList); if (offsetsNeedFixing) { prevSibling->RemoveStateBits(TEXT_OFFSETS_NEED_FIXING); } - - return NS_OK; -} - -nsresult +} + +void nsCSSFrameConstructor::RemoveFirstLetterFrames(nsIPresShell* aPresShell, nsContainerFrame* aFrame, nsContainerFrame* aBlockFrame, bool* aStopLooking) { nsIFrame* prevSibling = nullptr; nsIFrame* kid = aFrame->PrincipalChildList().FirstChild(); @@ -11895,43 +11868,37 @@ nsCSSFrameConstructor::RemoveFirstLetter if (*aStopLooking) { break; } } } prevSibling = kid; kid = kid->GetNextSibling(); } - - return NS_OK; -} - -nsresult +} + +void nsCSSFrameConstructor::RemoveLetterFrames(nsIPresShell* aPresShell, nsContainerFrame* aBlockFrame) { aBlockFrame = static_cast<nsContainerFrame*>(aBlockFrame->FirstContinuation()); nsContainerFrame* continuation = aBlockFrame; bool stopLooking = false; - nsresult rv; do { - rv = RemoveFloatingFirstLetterFrames(aPresShell, continuation); - if (NS_SUCCEEDED(rv)) { - rv = RemoveFirstLetterFrames(aPresShell, - continuation, aBlockFrame, &stopLooking); - } + RemoveFloatingFirstLetterFrames(aPresShell, continuation); + RemoveFirstLetterFrames(aPresShell, continuation, aBlockFrame, + &stopLooking); if (stopLooking) { break; } continuation = static_cast<nsContainerFrame*>(continuation->GetNextContinuation()); } while (continuation); - return rv; } // Fixup the letter frame situation for the given block void nsCSSFrameConstructor::RecoverLetterFrames(nsContainerFrame* aBlockFrame) { aBlockFrame = static_cast<nsContainerFrame*>(aBlockFrame->FirstContinuation()); @@ -11964,26 +11931,24 @@ nsCSSFrameConstructor::RecoverLetterFram parentFrame->InsertFrames(kPrincipalList, prevFrame, letterFrames); } } //---------------------------------------------------------------------- // listbox Widget Routines -nsresult +void nsCSSFrameConstructor::CreateListBoxContent(nsContainerFrame* aParentFrame, nsIFrame* aPrevFrame, nsIContent* aChild, nsIFrame** aNewFrame, bool aIsAppend) { #ifdef MOZ_XUL - nsresult rv = NS_OK; - // Construct a new frame if (nullptr != aParentFrame) { nsFrameItems frameItems; TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction); nsFrameConstructorState state(mPresShell, matchContext, GetAbsoluteContainingBlock(aParentFrame, FIXED_POS), GetAbsoluteContainingBlock(aParentFrame, ABS_POS), @@ -11997,17 +11962,17 @@ nsCSSFrameConstructor::CreateListBoxCont styleContext = ResolveStyleContext(aParentFrame, aChild, &state); // Pre-check for display "none" - only if we find that, do we create // any frame at all const nsStyleDisplay* display = styleContext->StyleDisplay(); if (StyleDisplay::None == display->mDisplay) { *aNewFrame = nullptr; - return NS_OK; + return; } BeginUpdate(); FrameConstructionItemList items; AddFrameConstructionItemsInternal(state, aChild, aParentFrame, aChild->NodeInfo()->NameAtom(), aChild->GetNameSpaceID(), @@ -12016,37 +11981,33 @@ nsCSSFrameConstructor::CreateListBoxCont ConstructFramesFromItemList(state, items, aParentFrame, frameItems); nsIFrame* newFrame = frameItems.FirstChild(); *aNewFrame = newFrame; if (newFrame) { // Notify the parent frame if (aIsAppend) - rv = ((nsListBoxBodyFrame*)aParentFrame)->ListBoxAppendFrames(frameItems); + ((nsListBoxBodyFrame*)aParentFrame)->ListBoxAppendFrames(frameItems); else - rv = ((nsListBoxBodyFrame*)aParentFrame)->ListBoxInsertFrames(aPrevFrame, frameItems); + ((nsListBoxBodyFrame*)aParentFrame)->ListBoxInsertFrames(aPrevFrame, frameItems); } EndUpdate(); #ifdef ACCESSIBILITY if (newFrame) { nsAccessibilityService* accService = nsIPresShell::AccService(); if (accService) { accService->ContentRangeInserted(mPresShell, aChild->GetParent(), aChild, aChild->GetNextSibling()); } } #endif } - - return rv; -#else - return NS_ERROR_FAILURE; #endif } //---------------------------------------- void nsCSSFrameConstructor::ConstructBlock(nsFrameConstructorState& aState, nsIContent* aContent, @@ -12810,17 +12771,17 @@ nsCSSFrameConstructor::WipeContainingBlo static_cast<void*>(blockContent)); } #endif RecreateFramesForContent(blockContent, true, REMOVE_FOR_RECONSTRUCTION, nullptr); return true; } -nsresult +void nsCSSFrameConstructor::ReframeContainingBlock(nsIFrame* aFrame, RemoveFlags aFlags, nsIContent** aDestroyedFramesFor) { #ifdef DEBUG // ReframeContainingBlock is a NASTY routine, it causes terrible performance problems // so I want to see when it is happening! Unfortunately, it is happening way to often because @@ -12833,17 +12794,17 @@ nsCSSFrameConstructor::ReframeContaining #endif // XXXbz how exactly would we get here while isReflowing anyway? Should this // whole test be ifdef DEBUG? if (mPresShell->IsReflowLocked()) { // don't ReframeContainingBlock, this will result in a crash // if we remove a tree that's in reflow - see bug 121368 for testcase NS_ERROR("Atemptted to nsCSSFrameConstructor::ReframeContainingBlock during a Reflow!!!"); - return NS_OK; + return; } // Get the first "normal" ancestor of the target frame. nsIFrame* containingBlock = GetIBContainingBlockFor(aFrame); if (containingBlock) { // From here we look for the containing block in case the target // frame is already a block (which can happen when an inline frame // wraps some of its content in an anonymous block; see @@ -12865,17 +12826,17 @@ nsCSSFrameConstructor::ReframeContaining } } // If we get here, we're screwed! return RecreateFramesForContent(mPresShell->GetDocument()->GetRootElement(), true, aFlags, nullptr); } -nsresult +void nsCSSFrameConstructor::GenerateChildFrames(nsContainerFrame* aFrame) { { nsAutoScriptBlocker scriptBlocker; BeginUpdate(); nsFrameItems childItems; TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction); @@ -12900,18 +12861,16 @@ nsCSSFrameConstructor::GenerateChildFram if (child) { accService->ContentRangeInserted(mPresShell, container, child, nullptr); } } #endif // call XBL constructors after the frames are created mPresShell->GetDocument()->BindingManager()->ProcessAttachedQueue(); - - return NS_OK; } ////////////////////////////////////////////////////////// // nsCSSFrameConstructor::FrameConstructionItem methods // ////////////////////////////////////////////////////////// bool nsCSSFrameConstructor:: FrameConstructionItem::IsWhitespace(nsFrameConstructorState& aState) const
--- a/layout/base/nsCSSFrameConstructor.h +++ b/layout/base/nsCSSFrameConstructor.h @@ -43,29 +43,29 @@ namespace mozilla { namespace dom { class FlattenedChildIterator; } // namespace dom } // namespace mozilla -class nsCSSFrameConstructor : public nsFrameManager +class nsCSSFrameConstructor final : public nsFrameManager { public: typedef mozilla::CSSPseudoElementType CSSPseudoElementType; typedef mozilla::dom::Element Element; friend class mozilla::RestyleManager; friend class mozilla::GeckoRestyleManager; friend class mozilla::ServoRestyleManager; nsCSSFrameConstructor(nsIDocument* aDocument, nsIPresShell* aPresShell); ~nsCSSFrameConstructor(void) { - NS_ASSERTION(mUpdateCount == 0, "Dying in the middle of our own update?"); + MOZ_ASSERT(mUpdateCount == 0, "Dying in the middle of our own update?"); } // get the alternate text for a content node static void GetAlternateTextFor(nsIContent* aContent, nsIAtom* aTag, // content object's tag nsXPIDLString& aAltText); private: @@ -73,17 +73,17 @@ private: nsCSSFrameConstructor& operator=(const nsCSSFrameConstructor& aCopy) = delete; public: mozilla::RestyleManager* RestyleManager() const { return mPresShell->GetPresContext()->RestyleManager(); } nsIFrame* ConstructRootFrame(); - nsresult ReconstructDocElementHierarchy(); + void ReconstructDocElementHierarchy(); // Create frames for content nodes that are marked as needing frames. This // should be called before ProcessPendingRestyles. // Note: It's the caller's responsibility to make sure to wrap a // CreateNeededFrames call in a view update batch and a script blocker. void CreateNeededFrames(); private: @@ -203,45 +203,45 @@ public: // If aAllowLazyConstruction is true then frame construction of the new // children can be done lazily. // // When constructing frames lazily, we can keep the tree match context in a // much easier way than nsFrameConstructorState, and thus, we're allowed to // provide a TreeMatchContext to avoid calling InitAncestors repeatedly deep // in the DOM. - nsresult ContentAppended(nsIContent* aContainer, - nsIContent* aFirstNewContent, - bool aAllowLazyConstruction, - TreeMatchContext* aProvidedTreeMatchContext = nullptr); + void ContentAppended(nsIContent* aContainer, + nsIContent* aFirstNewContent, + bool aAllowLazyConstruction, + TreeMatchContext* aProvidedTreeMatchContext = nullptr); // If aAllowLazyConstruction is true then frame construction of the new child // can be done lazily. - nsresult ContentInserted(nsIContent* aContainer, - nsIContent* aChild, - nsILayoutHistoryState* aFrameState, - bool aAllowLazyConstruction); + void ContentInserted(nsIContent* aContainer, + nsIContent* aChild, + nsILayoutHistoryState* aFrameState, + bool aAllowLazyConstruction); // Like ContentInserted but handles inserting the children of aContainer in // the range [aStartChild, aEndChild). aStartChild must be non-null. // aEndChild may be null to indicate the range includes all kids after // aStartChild. // // If aAllowLazyConstruction is true then frame construction of // the new children can be done lazily. It is only allowed to be true when // inserting a single node. // // See ContentAppended to see why we allow passing an already initialized // TreeMatchContext. - nsresult ContentRangeInserted(nsIContent* aContainer, - nsIContent* aStartChild, - nsIContent* aEndChild, - nsILayoutHistoryState* aFrameState, - bool aAllowLazyConstruction, - TreeMatchContext* aProvidedTreeMatchContext = nullptr); + void ContentRangeInserted(nsIContent* aContainer, + nsIContent* aStartChild, + nsIContent* aEndChild, + nsILayoutHistoryState* aFrameState, + bool aAllowLazyConstruction, + TreeMatchContext* aProvidedTreeMatchContext = nullptr); enum RemoveFlags { REMOVE_CONTENT, REMOVE_FOR_RECONSTRUCTION, REMOVE_DESTROY_FRAMES }; /** * Recreate or destroy frames for aChild in aContainer. * aFlags == REMOVE_CONTENT means aChild has been removed from the document. * aFlags == REMOVE_FOR_RECONSTRUCTION means the caller will reconstruct the * frames later. @@ -251,35 +251,35 @@ public: * it. Ancestors may have been reframed though. * aFlags == REMOVE_DESTROY_FRAMES is the same as REMOVE_FOR_RECONSTRUCTION * except it will never try to reconstruct frames. Instead, the caller is * responsible for doing that, on the content returned in aDestroyedFramesFor. * The layout frame state is guarranted to be captured for the removed frames * only when aFlags == REMOVE_DESTROY_FRAMES, otherwise it will only be * captured if we reconstructed frames for an ancestor. */ - nsresult ContentRemoved(nsIContent* aContainer, - nsIContent* aChild, - nsIContent* aOldNextSibling, - RemoveFlags aFlags, - bool* aDidReconstruct, - nsIContent** aDestroyedFramesFor = nullptr); + void ContentRemoved(nsIContent* aContainer, + nsIContent* aChild, + nsIContent* aOldNextSibling, + RemoveFlags aFlags, + bool* aDidReconstruct, + nsIContent** aDestroyedFramesFor = nullptr); - nsresult CharacterDataChanged(nsIContent* aContent, - CharacterDataChangeInfo* aInfo); + void CharacterDataChanged(nsIContent* aContent, + CharacterDataChangeInfo* aInfo); // If aContent is a text node that has been optimized away due to being // whitespace next to a block boundary (or for some other reason), stop // doing that and create a frame for it if it should have one. This recreates // frames so be careful (although this should not change actual layout). // Returns the frame for aContent if there is one. nsIFrame* EnsureFrameForTextNode(nsGenericDOMDataNode* aContent); - // generate the child frames and process bindings - nsresult GenerateChildFrames(nsContainerFrame* aFrame); + // Generate the child frames and process bindings + void GenerateChildFrames(nsContainerFrame* aFrame); // Should be called when a frame is going to be destroyed and // WillDestroyFrameTree hasn't been called yet. void NotifyDestroyingFrame(nsIFrame* aFrame); void BeginUpdate(); void EndUpdate(); void RecalcQuotesAndCounters(); @@ -311,21 +311,21 @@ public: // Copy over fixed frames from aParentFrame's prev-in-flow nsresult ReplicateFixedFrames(nsPageContentFrame* aParentFrame); /** * Get the XBL insertion point for aChild in aContainer. */ InsertionPoint GetInsertionPoint(nsIContent* aContainer, nsIContent* aChild); - nsresult CreateListBoxContent(nsContainerFrame* aParentFrame, - nsIFrame* aPrevFrame, - nsIContent* aChild, - nsIFrame** aResult, - bool aIsAppend); + void CreateListBoxContent(nsContainerFrame* aParentFrame, + nsIFrame* aPrevFrame, + nsIContent* aChild, + nsIFrame** aResult, + bool aIsAppend); // GetInitialContainingBlock() is deprecated in favor of GetRootElementFrame(); // nsIFrame* GetInitialContainingBlock() { return mRootElementFrame; } // This returns the outermost frame for the root element nsContainerFrame* GetRootElementFrame() { return mRootElementFrame; } // This returns the frame for the root element that does not // have a psuedo-element style nsIFrame* GetRootElementStyleFrame() { return mRootElementStyleFrame; } @@ -427,24 +427,24 @@ private: * @param aAttrNamespace the namespace of the attribute in question * @param aAttrName the localname of the attribute * @param aStyleContext the style context to use * @param aGeneratedContent the array of generated content to append the * created content to. * @param [out] aNewContent the content node we create * @param [out] aNewFrame the new frame we create */ - nsresult CreateAttributeContent(nsIContent* aParentContent, - nsIFrame* aParentFrame, - int32_t aAttrNamespace, - nsIAtom* aAttrName, - nsStyleContext* aStyleContext, - nsCOMArray<nsIContent>& aGeneratedContent, - nsIContent** aNewContent, - nsIFrame** aNewFrame); + void CreateAttributeContent(nsIContent* aParentContent, + nsIFrame* aParentFrame, + int32_t aAttrNamespace, + nsIAtom* aAttrName, + nsStyleContext* aStyleContext, + nsCOMArray<nsIContent>& aGeneratedContent, + nsIContent** aNewContent, + nsIFrame** aNewFrame); /** * Create a text node containing the given string. If aText is non-null * then we also set aText to the returned node. */ already_AddRefed<nsIContent> CreateGenConTextNode(nsFrameConstructorState& aState, const nsString& aString, RefPtr<nsTextNode>* aText, @@ -472,21 +472,21 @@ private: CSSPseudoElementType aPseudoElement, FrameConstructionItemList& aItems); // This method can change aFrameList: it can chop off the beginning and put // it in aParentFrame while putting the remainder into a ib-split sibling of // aParentFrame. aPrevSibling must be the frame after which aFrameList is to // be placed on aParentFrame's principal child list. It may be null if // aFrameList is being added at the beginning of the child list. - nsresult AppendFramesToParent(nsFrameConstructorState& aState, - nsContainerFrame* aParentFrame, - nsFrameItems& aFrameList, - nsIFrame* aPrevSibling, - bool aIsRecursiveCall = false); + void AppendFramesToParent(nsFrameConstructorState& aState, + nsContainerFrame* aParentFrame, + nsFrameItems& aFrameList, + nsIFrame* aPrevSibling, + bool aIsRecursiveCall = false); // BEGIN TABLE SECTION /** * Construct a table wrapper frame. This is the FrameConstructionData * callback used for the job. */ nsIFrame* ConstructTable(nsFrameConstructorState& aState, FrameConstructionItem& aItem, @@ -1698,17 +1698,17 @@ private: // Creates a view for the scrolledframe and makes it the child of the scrollframe. void FinishBuildingScrollFrame(nsContainerFrame* aScrollFrame, nsIFrame* aScrolledFrame); // InitializeSelectFrame puts scrollFrame in aFrameItems if aBuildCombobox is false // aBuildCombobox indicates if we are building a combobox that has a dropdown // popup widget or not. - nsresult + void InitializeSelectFrame(nsFrameConstructorState& aState, nsContainerFrame* aScrollFrame, nsContainerFrame* aScrolledFrame, nsIContent* aContent, nsContainerFrame* aParentFrame, nsStyleContext* aStyleContext, bool aBuildCombobox, PendingBinding* aPendingBinding, @@ -1727,17 +1727,17 @@ private: * Recreate frames for aContent. * @param aContent the content to recreate frames for * @param aAsyncInsert if true then a restyle event will be posted to handle * the required ContentInserted call instead of doing it immediately. * @param aFlags normally you want to pass REMOVE_FOR_RECONSTRUCTION here * @param aDestroyedFramesFor if non-null, it will contain the content that * was actually reframed - it may be different than aContent. */ - nsresult + void RecreateFramesForContent(nsIContent* aContent, bool aAsyncInsert, RemoveFlags aFlags, nsIContent** aDestroyedFramesFor); // If removal of aFrame from the frame tree requires reconstruction of some // containing block (either of aFrame or of its parent) due to {ib} splits or // table pseudo-frames, recreate the relevant frame subtree. The return value @@ -1745,17 +1745,16 @@ private: // the return value of ReframeContainingBlock or RecreateFramesForContent. If // this method returns false, the value of *aResult is not affected. aFrame // and aResult must not be null. aFrame must be the result of a // GetPrimaryFrame() call on a content node (which means its parent is also // not null). If this method returns true, aDestroyedFramesFor contains the // content that was reframed. bool MaybeRecreateContainerForFrameRemoval(nsIFrame* aFrame, RemoveFlags aFlags, - nsresult* aResult, nsIContent** aDestroyedFramesFor); nsIFrame* CreateContinuingOuterTableFrame(nsIPresShell* aPresShell, nsPresContext* aPresContext, nsIFrame* aFrame, nsContainerFrame* aParentFrame, nsIContent* aContent, nsStyleContext* aStyleContext); @@ -1870,19 +1869,19 @@ private: // otherwise bool WipeContainingBlock(nsFrameConstructorState& aState, nsIFrame* aContainingBlock, nsIFrame* aFrame, FrameConstructionItemList& aItems, bool aIsAppend, nsIFrame* aPrevSibling); - nsresult ReframeContainingBlock(nsIFrame* aFrame, - RemoveFlags aFlags, - nsIContent** aReframeContent); + void ReframeContainingBlock(nsIFrame* aFrame, + RemoveFlags aFlags, + nsIContent** aReframeContent); //---------------------------------------- // Methods support :first-letter style void CreateFloatingLetterFrame(nsFrameConstructorState& aState, nsIContent* aTextContent, nsIFrame* aTextFrame, @@ -1926,28 +1925,28 @@ private: nsIFrame** aTextFrame, nsIFrame** aPrevFrame, nsFrameItems& aLetterFrames, bool* aStopLooking); void RecoverLetterFrames(nsContainerFrame* aBlockFrame); // - nsresult RemoveLetterFrames(nsIPresShell* aPresShell, - nsContainerFrame* aBlockFrame); + void RemoveLetterFrames(nsIPresShell* aPresShell, + nsContainerFrame* aBlockFrame); // Recursive helper for RemoveLetterFrames - nsresult RemoveFirstLetterFrames(nsIPresShell* aPresShell, - nsContainerFrame* aFrame, - nsContainerFrame* aBlockFrame, - bool* aStopLooking); + void RemoveFirstLetterFrames(nsIPresShell* aPresShell, + nsContainerFrame* aFrame, + nsContainerFrame* aBlockFrame, + bool* aStopLooking); // Special remove method for those pesky floating first-letter frames - nsresult RemoveFloatingFirstLetterFrames(nsIPresShell* aPresShell, - nsIFrame* aBlockFrame); + void RemoveFloatingFirstLetterFrames(nsIPresShell* aPresShell, + nsIFrame* aBlockFrame); // Capture state for the frame tree rooted at the frame associated with the // content object, aContent void CaptureStateForFramesOf(nsIContent* aContent, nsILayoutHistoryState* aHistoryState); //---------------------------------------- @@ -1968,22 +1967,22 @@ private: // Handle the case when a block with first-line style is appended to (by // possibly calling WrapFramesInFirstLineFrame as needed). void AppendFirstLineFrames(nsFrameConstructorState& aState, nsIContent* aContent, nsContainerFrame* aBlockFrame, nsFrameItems& aFrameItems); - nsresult InsertFirstLineFrames(nsFrameConstructorState& aState, - nsIContent* aContent, - nsIFrame* aBlockFrame, - nsContainerFrame** aParentFrame, - nsIFrame* aPrevSibling, - nsFrameItems& aFrameItems); + void InsertFirstLineFrames(nsFrameConstructorState& aState, + nsIContent* aContent, + nsIFrame* aBlockFrame, + nsContainerFrame** aParentFrame, + nsIFrame* aPrevSibling, + nsFrameItems& aFrameItems); /** * Find the right frame to use for aContent when looking for sibling * frames for aTargetContent. If aPrevSibling is true, this * will look for last continuations, etc, as necessary. This calls * IsValidSibling as needed; if that returns false it returns null. * * @param aContent the content to search for frames
--- a/layout/base/nsFrameManager.cpp +++ b/layout/base/nsFrameManager.cpp @@ -33,23 +33,18 @@ #include "nsAbsoluteContainingBlock.h" #include "ChildIterator.h" #include "nsFrameManager.h" #include "GeckoProfiler.h" #include "nsIStatefulFrame.h" #include "nsContainerFrame.h" - #ifdef DEBUG - //#define DEBUG_UNDISPLAYED_MAP - //#define DEBUG_DISPLAY_CONTENTS_MAP - #else - #undef DEBUG_UNDISPLAYED_MAP - #undef DEBUG_DISPLAY_CONTENTS_MAP - #endif +// #define DEBUG_UNDISPLAYED_MAP +// #define DEBUG_DISPLAY_CONTENTS_MAP using namespace mozilla; using namespace mozilla::dom; //---------------------------------------------------------------------- struct PlaceholderMapEntry : public PLDHashEntryHdr { // key (the out of flow frame) can be obtained through placeholder frame @@ -82,50 +77,59 @@ nsFrameManagerBase::nsFrameManagerBase() , mUndisplayedMap(nullptr) , mDisplayContentsMap(nullptr) , mIsDestroyingFrames(false) { } //---------------------------------------------------------------------- -// XXXldb This seems too complicated for what I think it's doing, and it -// should also be using PLDHashTable rather than plhash to use less memory. +/** + * The undisplayed map is a class that maps a parent content node to the + * undisplayed content children, and their style contexts. + * + * The linked list of nodes holds strong references to the style contexts and + * the content. + */ +class nsFrameManagerBase::UndisplayedMap : + private nsClassHashtable<nsPtrHashKey<nsIContent>, + LinkedList<UndisplayedNode>> +{ + typedef nsClassHashtable<nsPtrHashKey<nsIContent>, LinkedList<UndisplayedNode>> base_type; -class nsFrameManagerBase::UndisplayedMap { public: - explicit UndisplayedMap(uint32_t aNumBuckets = 16); - ~UndisplayedMap(void); + UndisplayedMap(); + ~UndisplayedMap(); UndisplayedNode* GetFirstNode(nsIContent* aParentContent); - nsresult AddNodeFor(nsIContent* aParentContent, - nsIContent* aChild, nsStyleContext* aStyle); + void AddNodeFor(nsIContent* aParentContent, + nsIContent* aChild, + nsStyleContext* aStyle); - void RemoveNodeFor(nsIContent* aParentContent, - UndisplayedNode* aNode); + void RemoveNodeFor(nsIContent* aParentContent, UndisplayedNode* aNode); void RemoveNodesFor(nsIContent* aParentContent); - UndisplayedNode* UnlinkNodesFor(nsIContent* aParentContent); + + nsAutoPtr<LinkedList<UndisplayedNode>> + UnlinkNodesFor(nsIContent* aParentContent); // Removes all entries from the hash table - void Clear(void); + void Clear(); protected: + LinkedList<UndisplayedNode>* GetListFor(nsIContent** aParentContent); + LinkedList<UndisplayedNode>* GetOrCreateListFor(nsIContent** aParentContent); + void AppendNodeFor(UndisplayedNode* aNode, nsIContent* aParentContent); /** - * Gets the entry for the provided parent content. If the content - * is a <xbl:children> element, |**aParentContent| is set to - * the parent of the children element. + * Get the applicable parent for the map lookup. This is almost always the + * provided argument, except if it's a <xbl:children> element, in which case + * it's the parent of the children element. */ - PLHashEntry** GetEntryFor(nsIContent** aParentContent); - void AppendNodeFor(UndisplayedNode* aNode, - nsIContent* aParentContent); - - PLHashTable* mTable; - PLHashEntry** mLastLookup; + nsIContent* GetApplicableParent(nsIContent* aParent); }; //---------------------------------------------------------------------- nsFrameManager::~nsFrameManager() { NS_ASSERTION(!mPresShell, "nsFrameManager::Destroy never called"); } @@ -140,17 +144,17 @@ nsFrameManager::Destroy() // Unregister all placeholders before tearing down the frame tree nsFrameManager::ClearPlaceholderFrameMap(); if (mRootFrame) { mRootFrame->Destroy(); mRootFrame = nullptr; } - + delete mUndisplayedMap; mUndisplayedMap = nullptr; delete mDisplayContentsMap; mDisplayContentsMap = nullptr; mPresShell = nullptr; } @@ -229,17 +233,17 @@ nsFrameManager::GetStyleContextInMap(Und nsFrameManager::GetUndisplayedNodeInMapFor(UndisplayedMap* aMap, const nsIContent* aContent) { if (!aContent) { return nullptr; } nsIContent* parent = ParentForUndisplayedMap(aContent); for (UndisplayedNode* node = aMap->GetFirstNode(parent); - node; node = node->mNext) { + node; node = node->getNext()) { if (node->mContent == aContent) return node; } return nullptr; } @@ -256,26 +260,26 @@ nsFrameManager::GetAllUndisplayedContent return GetAllUndisplayedNodesInMapFor(mUndisplayedMap, aParentContent); } /* static */ void nsFrameManager::SetStyleContextInMap(UndisplayedMap* aMap, nsIContent* aContent, nsStyleContext* aStyleContext) { - NS_PRECONDITION(!aStyleContext->GetPseudo(), - "Should only have actual elements here"); + MOZ_ASSERT(!aStyleContext->GetPseudo(), + "Should only have actual elements here"); #if defined(DEBUG_UNDISPLAYED_MAP) || defined(DEBUG_DISPLAY_BOX_CONTENTS_MAP) static int i = 0; printf("SetStyleContextInMap(%d): p=%p \n", i++, (void *)aContent); #endif - NS_ASSERTION(!GetStyleContextInMap(aMap, aContent), - "Already have an entry for aContent"); + MOZ_ASSERT(!GetStyleContextInMap(aMap, aContent), + "Already have an entry for aContent"); nsIContent* parent = ParentForUndisplayedMap(aContent); #ifdef DEBUG nsIPresShell* shell = aStyleContext->PresContext()->PresShell(); NS_ASSERTION(parent || (shell && shell->GetDocument() && shell->GetDocument()->GetRootElement() == aContent), "undisplayed content must have a parent, unless it's the root " "element"); @@ -301,17 +305,17 @@ nsFrameManager::ChangeStyleContextInMap( MOZ_ASSERT(aMap, "expecting a map"); #if defined(DEBUG_UNDISPLAYED_MAP) || defined(DEBUG_DISPLAY_BOX_CONTENTS_MAP) static int i = 0; printf("ChangeStyleContextInMap(%d): p=%p \n", i++, (void *)aContent); #endif for (UndisplayedNode* node = aMap->GetFirstNode(aContent->GetParent()); - node; node = node->mNext) { + node; node = node->getNext()) { if (node->mContent == aContent) { node->mStyle = aStyleContext; return; } } MOZ_CRASH("couldn't find the entry to change"); } @@ -319,36 +323,36 @@ nsFrameManager::ChangeStyleContextInMap( void nsFrameManager::ClearUndisplayedContentIn(nsIContent* aContent, nsIContent* aParentContent) { #ifdef DEBUG_UNDISPLAYED_MAP static int i = 0; printf("ClearUndisplayedContent(%d): content=%p parent=%p --> ", i++, (void *)aContent, (void*)aParentContent); #endif - - if (mUndisplayedMap) { - UndisplayedNode* node = mUndisplayedMap->GetFirstNode(aParentContent); - while (node) { - if (node->mContent == aContent) { - mUndisplayedMap->RemoveNodeFor(aParentContent, node); + + if (!mUndisplayedMap) { + return; + } + + for (UndisplayedNode* node = mUndisplayedMap->GetFirstNode(aParentContent); + node; node = node->getNext()) { + if (node->mContent == aContent) { + mUndisplayedMap->RemoveNodeFor(aParentContent, node); #ifdef DEBUG_UNDISPLAYED_MAP - printf( "REMOVED!\n"); + printf( "REMOVED!\n"); #endif -#ifdef DEBUG - // make sure that there are no more entries for the same content - nsStyleContext *context = GetUndisplayedContent(aContent); - NS_ASSERTION(context == nullptr, "Found more undisplayed content data after removal"); -#endif - return; - } - node = node->mNext; + // make sure that there are no more entries for the same content + MOZ_ASSERT(!GetUndisplayedContent(aContent), + "Found more undisplayed content data after removal"); + return; } } + #ifdef DEBUG_UNDISPLAYED_MAP printf( "not found.\n"); #endif } void nsFrameManager::ClearAllUndisplayedContentIn(nsIContent* aParentContent) { @@ -394,60 +398,59 @@ nsFrameManager::GetAllDisplayContentsIn( void nsFrameManager::ClearDisplayContentsIn(nsIContent* aContent, nsIContent* aParentContent) { #ifdef DEBUG_DISPLAY_CONTENTS_MAP static int i = 0; printf("ClearDisplayContents(%d): content=%p parent=%p --> ", i++, (void *)aContent, (void*)aParentContent); #endif - - if (mDisplayContentsMap) { - UndisplayedNode* node = mDisplayContentsMap->GetFirstNode(aParentContent); - while (node) { - if (node->mContent == aContent) { - mDisplayContentsMap->RemoveNodeFor(aParentContent, node); + + if (!mDisplayContentsMap) { + return; + } + + for (UndisplayedNode* node = mDisplayContentsMap->GetFirstNode(aParentContent); + node; node = node->getNext()) { + if (node->mContent == aContent) { + mDisplayContentsMap->RemoveNodeFor(aParentContent, node); #ifdef DEBUG_DISPLAY_CONTENTS_MAP - printf( "REMOVED!\n"); + printf( "REMOVED!\n"); #endif -#ifdef DEBUG - // make sure that there are no more entries for the same content - nsStyleContext* context = GetDisplayContentsStyleFor(aContent); - NS_ASSERTION(context == nullptr, "Found more entries for aContent after removal"); -#endif - ClearAllDisplayContentsIn(aContent); - ClearAllUndisplayedContentIn(aContent); - return; - } - node = node->mNext; + // make sure that there are no more entries for the same content + MOZ_ASSERT(!GetDisplayContentsStyleFor(aContent), + "Found more entries for aContent after removal"); + ClearAllDisplayContentsIn(aContent); + ClearAllUndisplayedContentIn(aContent); + return; } } #ifdef DEBUG_DISPLAY_CONTENTS_MAP printf( "not found.\n"); #endif } void nsFrameManager::ClearAllDisplayContentsIn(nsIContent* aParentContent) { #ifdef DEBUG_DISPLAY_CONTENTS_MAP static int i = 0; printf("ClearAllDisplayContentsIn(%d): parent=%p \n", i++, (void*)aParentContent); #endif if (mDisplayContentsMap) { - UndisplayedNode* cur = mDisplayContentsMap->UnlinkNodesFor(aParentContent); - while (cur) { - UndisplayedNode* next = cur->mNext; - cur->mNext = nullptr; - ClearAllDisplayContentsIn(cur->mContent); - ClearAllUndisplayedContentIn(cur->mContent); - delete cur; - cur = next; + nsAutoPtr<LinkedList<UndisplayedNode>> list = + mDisplayContentsMap->UnlinkNodesFor(aParentContent); + if (list) { + while (UndisplayedNode* node = list->popFirst()) { + ClearAllDisplayContentsIn(node->mContent); + ClearAllUndisplayedContentIn(node->mContent); + delete node; + } } } // Need to look at aParentContent's content list due to XBL insertions. // Nodes in aParentContent's content list do not have aParentContent as a // parent, but are treated as children of aParentContent. We iterate over // the flattened content list and just ignore any nodes we don't care about. FlattenedChildIterator iter(aParentContent); @@ -668,185 +671,137 @@ nsFrameManager::RestoreFrameState(nsIFra for (; !childFrames.AtEnd(); childFrames.Next()) { RestoreFrameState(childFrames.get(), aState); } } } //---------------------------------------------------------------------- -static PLHashNumber -HashKey(void* key) -{ - return NS_PTR_TO_INT32(key); -} - -static int -CompareKeys(void* key1, void* key2) -{ - return key1 == key2; -} - -//---------------------------------------------------------------------- - -nsFrameManagerBase::UndisplayedMap::UndisplayedMap(uint32_t aNumBuckets) +nsFrameManagerBase::UndisplayedMap::UndisplayedMap() { MOZ_COUNT_CTOR(nsFrameManagerBase::UndisplayedMap); - mTable = PL_NewHashTable(aNumBuckets, (PLHashFunction)HashKey, - (PLHashComparator)CompareKeys, - (PLHashComparator)nullptr, - nullptr, nullptr); - mLastLookup = nullptr; } nsFrameManagerBase::UndisplayedMap::~UndisplayedMap(void) { MOZ_COUNT_DTOR(nsFrameManagerBase::UndisplayedMap); Clear(); - PL_HashTableDestroy(mTable); } -PLHashEntry** -nsFrameManagerBase::UndisplayedMap::GetEntryFor(nsIContent** aParentContent) +void +nsFrameManagerBase::UndisplayedMap::Clear() { - nsIContent* parentContent = *aParentContent; + for (auto iter = Iter(); !iter.Done(); iter.Next()) { + auto* list = iter.UserData(); + while (auto* node = list->popFirst()) { + delete node; + } + iter.Remove(); + } +} - if (mLastLookup && (parentContent == (*mLastLookup)->key)) { - return mLastLookup; - } +nsIContent* +nsFrameManagerBase::UndisplayedMap::GetApplicableParent(nsIContent* aParent) +{ // In the case of XBL default content, <xbl:children> elements do not get a // frame causing a mismatch between the content tree and the frame tree. // |GetEntryFor| is sometimes called with the content tree parent (which may // be a <xbl:children> element) but the parent in the frame tree would be the // insertion parent (parent of the <xbl:children> element). Here the children // elements are normalized to the insertion parent to correct for the mismatch. - if (parentContent && nsContentUtils::IsContentInsertionPoint(parentContent)) { - parentContent = parentContent->GetParent(); - // Change the caller's pointer for the parent content to be the insertion parent. - *aParentContent = parentContent; + if (aParent && nsContentUtils::IsContentInsertionPoint(aParent)) { + return aParent->GetParent(); + } + + return aParent; +} + +LinkedList<UndisplayedNode>* +nsFrameManagerBase::UndisplayedMap::GetListFor(nsIContent** aParent) +{ + *aParent = GetApplicableParent(*aParent); + + LinkedList<UndisplayedNode>* list; + if (Get(*aParent, &list)) { + return list; } - PLHashNumber hashCode = NS_PTR_TO_INT32(parentContent); - PLHashEntry** entry = PL_HashTableRawLookup(mTable, hashCode, parentContent); - if (*entry && !ServoStyleSet::IsInServoTraversal()) { - mLastLookup = entry; - } - return entry; + return nullptr; } -UndisplayedNode* +LinkedList<UndisplayedNode>* +nsFrameManagerBase::UndisplayedMap::GetOrCreateListFor(nsIContent** aParent) +{ + *aParent = GetApplicableParent(*aParent); + return LookupOrAdd(*aParent); +} + + +UndisplayedNode* nsFrameManagerBase::UndisplayedMap::GetFirstNode(nsIContent* aParentContent) { - PLHashEntry** entry = GetEntryFor(&aParentContent); - if (*entry) { - return (UndisplayedNode*)((*entry)->value); - } - return nullptr; + auto* list = GetListFor(&aParentContent); + return list ? list->getFirst() : nullptr; } + void nsFrameManagerBase::UndisplayedMap::AppendNodeFor(UndisplayedNode* aNode, nsIContent* aParentContent) { - PLHashEntry** entry = GetEntryFor(&aParentContent); - if (*entry) { - UndisplayedNode* node = (UndisplayedNode*)((*entry)->value); - while (node->mNext) { - if (node->mContent == aNode->mContent) { - // We actually need to check this in optimized builds because - // there are some callers that do this. See bug 118014, bug - // 136704, etc. - NS_NOTREACHED("node in map twice"); - delete aNode; - return; - } - node = node->mNext; - } - node->mNext = aNode; + LinkedList<UndisplayedNode>* list = GetOrCreateListFor(&aParentContent); + +#ifdef DEBUG + for (UndisplayedNode* node = list->getFirst(); node; node = node->getNext()) { + // NOTE: In the original code there was a work around for this case, I want + // to check it still happens before hacking around it the same way. + MOZ_ASSERT(node->mContent != aNode->mContent, + "Duplicated content in undisplayed list!"); } - else { - PLHashNumber hashCode = NS_PTR_TO_INT32(aParentContent); - PL_HashTableRawAdd(mTable, entry, hashCode, aParentContent, aNode); - mLastLookup = nullptr; // hashtable may have shifted bucket out from under us - } +#endif + + list->insertBack(aNode); } -nsresult +void nsFrameManagerBase::UndisplayedMap::AddNodeFor(nsIContent* aParentContent, - nsIContent* aChild, + nsIContent* aChild, nsStyleContext* aStyle) { UndisplayedNode* node = new UndisplayedNode(aChild, aStyle); - AppendNodeFor(node, aParentContent); - return NS_OK; } void nsFrameManagerBase::UndisplayedMap::RemoveNodeFor(nsIContent* aParentContent, UndisplayedNode* aNode) { - PLHashEntry** entry = GetEntryFor(&aParentContent); - NS_ASSERTION(*entry, "content not in map"); - if (*entry) { - if ((UndisplayedNode*)((*entry)->value) == aNode) { // first node - if (aNode->mNext) { - (*entry)->value = aNode->mNext; - aNode->mNext = nullptr; - } - else { - PL_HashTableRawRemove(mTable, entry, *entry); - mLastLookup = nullptr; // hashtable may have shifted bucket out from under us - } - } - else { - UndisplayedNode* node = (UndisplayedNode*)((*entry)->value); - while (node->mNext) { - if (node->mNext == aNode) { - node->mNext = aNode->mNext; - aNode->mNext = nullptr; - break; - } - node = node->mNext; - } - } - } +#ifdef DEBUG + auto list = GetListFor(&aParentContent); + MOZ_ASSERT(list, "content not in map"); + aNode->removeFrom(*list); +#else + aNode->remove(); +#endif delete aNode; } -UndisplayedNode* +nsAutoPtr<LinkedList<UndisplayedNode>> nsFrameManagerBase::UndisplayedMap::UnlinkNodesFor(nsIContent* aParentContent) { - PLHashEntry** entry = GetEntryFor(&aParentContent); - NS_ASSERTION(entry, "content not in map"); - if (*entry) { - UndisplayedNode* node = (UndisplayedNode*)((*entry)->value); - NS_ASSERTION(node, "null node for non-null entry in UndisplayedMap"); - PL_HashTableRawRemove(mTable, entry, *entry); - mLastLookup = nullptr; // hashtable may have shifted bucket out from under us - return node; - } - return nullptr; + nsAutoPtr<LinkedList<UndisplayedNode>> list; + RemoveAndForget(GetApplicableParent(aParentContent), list); + return list; } void nsFrameManagerBase::UndisplayedMap::RemoveNodesFor(nsIContent* aParentContent) { - delete UnlinkNodesFor(aParentContent); + nsAutoPtr<LinkedList<UndisplayedNode>> list = UnlinkNodesFor(aParentContent); + if (list) { + while (auto* node = list->popFirst()) { + delete node; + } + } } - -static int -RemoveUndisplayedEntry(PLHashEntry* he, int i, void* arg) -{ - UndisplayedNode* node = (UndisplayedNode*)(he->value); - delete node; - // Remove and free this entry and continue enumerating - return HT_ENUMERATE_REMOVE | HT_ENUMERATE_NEXT; -} - -void -nsFrameManagerBase::UndisplayedMap::Clear(void) -{ - mLastLookup = nullptr; - PL_HashTableEnumerateEntries(mTable, RemoveUndisplayedEntry, 0); -}
--- a/layout/base/nsFrameManager.h +++ b/layout/base/nsFrameManager.h @@ -28,59 +28,45 @@ class nsContainerFrame; class nsPlaceholderFrame; namespace mozilla { /** * Node in a linked list, containing the style for an element that * does not have a frame but whose parent does have a frame. */ -struct UndisplayedNode { +struct UndisplayedNode : public LinkedListElement<UndisplayedNode> +{ UndisplayedNode(nsIContent* aContent, nsStyleContext* aStyle) - : mContent(aContent), - mStyle(aStyle), - mNext(nullptr) + : mContent(aContent) + , mStyle(aStyle) { MOZ_COUNT_CTOR(mozilla::UndisplayedNode); } - ~UndisplayedNode() - { - MOZ_COUNT_DTOR(mozilla::UndisplayedNode); + ~UndisplayedNode() { MOZ_COUNT_DTOR(mozilla::UndisplayedNode); } - // Delete mNext iteratively to avoid blowing