mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-19 08:15:31 +00:00
Merge inbound to mozilla-central. a=merge
This commit is contained in:
commit
227f5329f7
@ -6,64 +6,66 @@
|
||||
|
||||
var EXPORTED_SYMBOLS = ["BrowserTabChild"];
|
||||
|
||||
const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "E10SUtils",
|
||||
"resource://gre/modules/E10SUtils.jsm");
|
||||
|
||||
class BrowserTabChild extends ActorChild {
|
||||
class BrowserTabChild extends JSWindowActorChild {
|
||||
constructor() {
|
||||
super();
|
||||
this.handledWindowCreated = false;
|
||||
this.handledFirstPaint = false;
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "DOMWindowCreated":
|
||||
let loadContext = this.mm.docShell.QueryInterface(Ci.nsILoadContext);
|
||||
case "DOMWindowCreated": {
|
||||
if (this.handledWindowCreated) {
|
||||
return;
|
||||
}
|
||||
this.handledWindowCreated = true;
|
||||
|
||||
let context = this.manager.browsingContext;
|
||||
let loadContext = context.docShell.QueryInterface(Ci.nsILoadContext);
|
||||
let userContextId = loadContext.originAttributes.userContextId;
|
||||
|
||||
this.mm.sendAsyncMessage("Browser:WindowCreated", { userContextId });
|
||||
this.sendAsyncMessage("Browser:WindowCreated", { userContextId });
|
||||
break;
|
||||
}
|
||||
|
||||
case "MozAfterPaint":
|
||||
this.mm.sendAsyncMessage("Browser:FirstPaint");
|
||||
if (this.handledFirstPaint) {
|
||||
return;
|
||||
}
|
||||
this.handledFirstPaint = true;
|
||||
this.sendAsyncMessage("Browser:FirstPaint", {});
|
||||
break;
|
||||
|
||||
case "MozDOMPointerLock:Entered":
|
||||
this.mm.sendAsyncMessage("PointerLock:Entered", {
|
||||
this.sendAsyncMessage("PointerLock:Entered", {
|
||||
originNoSuffix: event.target.nodePrincipal.originNoSuffix,
|
||||
});
|
||||
break;
|
||||
|
||||
case "MozDOMPointerLock:Exited":
|
||||
this.mm.sendAsyncMessage("PointerLock:Exited");
|
||||
this.sendAsyncMessage("PointerLock:Exited");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switchDocumentDirection(window = this.content) {
|
||||
// document.dir can also be "auto", in which case it won't change
|
||||
if (window.document.dir == "ltr" || window.document.dir == "") {
|
||||
window.document.dir = "rtl";
|
||||
} else if (window.document.dir == "rtl") {
|
||||
window.document.dir = "ltr";
|
||||
}
|
||||
for (let i = 0; i < window.frames.length; i++) {
|
||||
this.switchDocumentDirection(window.frames[i]);
|
||||
}
|
||||
}
|
||||
|
||||
receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "AllowScriptsToClose":
|
||||
this.content.windowUtils.allowScriptsToClose();
|
||||
break;
|
||||
let context = this.manager.browsingContext;
|
||||
let docShell = context.docShell;
|
||||
|
||||
switch (message.name) {
|
||||
case "Browser:AppTab":
|
||||
if (this.docShell) {
|
||||
this.docShell.isAppTab = message.data.isAppTab;
|
||||
if (docShell) {
|
||||
docShell.isAppTab = message.data.isAppTab;
|
||||
}
|
||||
break;
|
||||
|
||||
case "Browser:HasSiblings":
|
||||
try {
|
||||
let browserChild = this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
let browserChild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIBrowserChild);
|
||||
let hasSiblings = message.data;
|
||||
browserChild.hasSiblings = hasSiblings;
|
||||
@ -78,8 +80,7 @@ class BrowserTabChild extends ActorChild {
|
||||
* window (such as view-source) that has no session history, fall
|
||||
* back on using the web navigation's reload method.
|
||||
*/
|
||||
|
||||
let webNav = this.docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
try {
|
||||
if (webNav.sessionHistory) {
|
||||
webNav = webNav.sessionHistory;
|
||||
@ -89,23 +90,20 @@ class BrowserTabChild extends ActorChild {
|
||||
|
||||
let reloadFlags = message.data.flags;
|
||||
try {
|
||||
E10SUtils.wrapHandlingUserInput(this.content, message.data.handlingUserInput,
|
||||
E10SUtils.wrapHandlingUserInput(this.document.defaultView,
|
||||
message.data.handlingUserInput,
|
||||
() => webNav.reload(reloadFlags));
|
||||
} catch (e) {
|
||||
}
|
||||
break;
|
||||
|
||||
case "MixedContent:ReenableProtection":
|
||||
this.docShell.mixedContentChannel = null;
|
||||
break;
|
||||
|
||||
case "SwitchDocumentDirection":
|
||||
this.switchDocumentDirection();
|
||||
docShell.mixedContentChannel = null;
|
||||
break;
|
||||
|
||||
case "UpdateCharacterSet":
|
||||
this.docShell.charset = message.data.value;
|
||||
this.docShell.gatherCharsetMenuTelemetry();
|
||||
docShell.charset = message.data.value;
|
||||
docShell.gatherCharsetMenuTelemetry();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
46
browser/actors/BrowserTabParent.jsm
Normal file
46
browser/actors/BrowserTabParent.jsm
Normal file
@ -0,0 +1,46 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["BrowserTabParent"];
|
||||
|
||||
class BrowserTabParent extends JSWindowActorParent {
|
||||
receiveMessage(message) {
|
||||
let browser = this.manager.browsingContext.embedderElement;
|
||||
if (!browser) {
|
||||
return; // Can happen sometimes if browser is being destroyed
|
||||
}
|
||||
|
||||
let gBrowser = browser.ownerGlobal.gBrowser;
|
||||
if (!gBrowser) {
|
||||
// Note: gBrowser might be null because this message might be received
|
||||
// from the extension process. There's still an embedderElement involved,
|
||||
// but it's the one coming from dummy.xul.
|
||||
// This should probably be fixed by adding support to specifying "group: 'browsers"
|
||||
// in the registerWindowActor options/. See bug 1557118.
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.name) {
|
||||
case "Browser:WindowCreated": {
|
||||
gBrowser.announceWindowCreated(browser, message.data.userContextId);
|
||||
break;
|
||||
}
|
||||
|
||||
case "Browser:FirstPaint": {
|
||||
browser.ownerGlobal.gBrowserInit._firstBrowserPaintDeferred.resolve();
|
||||
break;
|
||||
}
|
||||
|
||||
case "MozDOMPointerLock:Entered": {
|
||||
browser.ownerGlobal.PointerLock.entered(message.data.originNoSuffix);
|
||||
break;
|
||||
}
|
||||
|
||||
case "MozDOMPointerLock:Exited":
|
||||
browser.ownerGlobal.PointerLock.exited();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
30
browser/actors/SwitchDocumentDirectionChild.jsm
Normal file
30
browser/actors/SwitchDocumentDirectionChild.jsm
Normal file
@ -0,0 +1,30 @@
|
||||
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["SwitchDocumentDirectionChild"];
|
||||
|
||||
class SwitchDocumentDirectionChild extends JSWindowActorChild {
|
||||
receiveMessage(message) {
|
||||
if (message.name == "SwitchDocumentDirection") {
|
||||
let docShell = this.manager.browsingContext.docShell;
|
||||
let document = docShell.QueryInterface(Ci.nsIWebNavigation).document;
|
||||
this.switchDocumentDirection(document);
|
||||
}
|
||||
}
|
||||
|
||||
switchDocumentDirection(document) {
|
||||
// document.dir can also be "auto", in which case it won't change
|
||||
if (document.dir == "ltr" || document.dir == "") {
|
||||
document.dir = "rtl";
|
||||
} else if (document.dir == "rtl") {
|
||||
document.dir = "ltr";
|
||||
}
|
||||
|
||||
for (let frame of document.defaultView.frames) {
|
||||
this.switchDocumentDirection(frame.document);
|
||||
}
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ FINAL_TARGET_FILES.actors += [
|
||||
'AboutReaderChild.jsm',
|
||||
'BlockedSiteChild.jsm',
|
||||
'BrowserTabChild.jsm',
|
||||
'BrowserTabParent.jsm',
|
||||
'ClickHandlerChild.jsm',
|
||||
'ContentSearchChild.jsm',
|
||||
'ContextMenuChild.jsm',
|
||||
@ -44,6 +45,7 @@ FINAL_TARGET_FILES.actors += [
|
||||
'SearchTelemetryChild.jsm',
|
||||
'SubframeCrashChild.jsm',
|
||||
'SubframeCrashParent.jsm',
|
||||
'SwitchDocumentDirectionChild.jsm',
|
||||
'URIFixupChild.jsm',
|
||||
'WebRTCChild.jsm',
|
||||
]
|
||||
|
@ -224,24 +224,13 @@ var PointerlockFsWarning = {
|
||||
};
|
||||
|
||||
var PointerLock = {
|
||||
entered(originNoSuffix) {
|
||||
PointerlockFsWarning.showPointerLock(originNoSuffix);
|
||||
},
|
||||
|
||||
init() {
|
||||
window.messageManager.addMessageListener("PointerLock:Entered", this);
|
||||
window.messageManager.addMessageListener("PointerLock:Exited", this);
|
||||
},
|
||||
|
||||
receiveMessage(aMessage) {
|
||||
switch (aMessage.name) {
|
||||
case "PointerLock:Entered": {
|
||||
PointerlockFsWarning.showPointerLock(aMessage.data.originNoSuffix);
|
||||
break;
|
||||
}
|
||||
case "PointerLock:Exited": {
|
||||
PointerlockFsWarning.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
exited() {
|
||||
PointerlockFsWarning.close();
|
||||
},
|
||||
};
|
||||
|
||||
var FullScreen = {
|
||||
|
@ -315,9 +315,7 @@
|
||||
hidden="true"
|
||||
label="&bidiSwitchPageDirectionItem.label;"
|
||||
accesskey="&bidiSwitchPageDirectionItem.accesskey;"
|
||||
oncommand="gBrowser.selectedBrowser
|
||||
.messageManager
|
||||
.sendAsyncMessage('SwitchDocumentDirection');"/>
|
||||
oncommand="gBrowser.selectedBrowser.sendMessageToActor('SwitchDocumentDirection', {}, 'SwitchDocumentDirection', true);"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
|
||||
|
@ -325,8 +325,7 @@ var gIdentityHandler = {
|
||||
},
|
||||
|
||||
enableMixedContentProtection() {
|
||||
gBrowser.selectedBrowser.messageManager.sendAsyncMessage(
|
||||
"MixedContent:ReenableProtection", {});
|
||||
gBrowser.selectedBrowser.sendMessageToActor("MixedContent:ReenableProtection", {}, "BrowserTab");
|
||||
BrowserReload();
|
||||
PanelMultiView.hidePopup(this._identityPopup);
|
||||
},
|
||||
|
@ -1705,7 +1705,6 @@ var gBrowserInit = {
|
||||
}
|
||||
|
||||
FullScreen.init();
|
||||
PointerLock.init();
|
||||
|
||||
if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
|
||||
MenuTouchModeObserver.init();
|
||||
@ -1774,17 +1773,12 @@ var gBrowserInit = {
|
||||
_setInitialFocus() {
|
||||
let initiallyFocusedElement = document.commandDispatcher.focusedElement;
|
||||
|
||||
let firstBrowserPaintDeferred = {};
|
||||
firstBrowserPaintDeferred.promise = new Promise(resolve => {
|
||||
firstBrowserPaintDeferred.resolve = resolve;
|
||||
this._firstBrowserPaintDeferred = {};
|
||||
this._firstBrowserPaintDeferred.promise = new Promise(resolve => {
|
||||
this._firstBrowserPaintDeferred.resolve = resolve;
|
||||
});
|
||||
|
||||
let mm = window.messageManager;
|
||||
mm.addMessageListener("Browser:FirstPaint", function onFirstPaint() {
|
||||
mm.removeMessageListener("Browser:FirstPaint", onFirstPaint);
|
||||
firstBrowserPaintDeferred.resolve();
|
||||
});
|
||||
|
||||
let initialBrowser = gBrowser.selectedBrowser;
|
||||
mm.addMessageListener("Browser:FirstNonBlankPaint",
|
||||
function onFirstNonBlankPaint() {
|
||||
@ -1807,7 +1801,7 @@ var gBrowserInit = {
|
||||
if (gBrowser.selectedBrowser.isRemoteBrowser) {
|
||||
// If the initial browser is remote, in order to optimize for first paint,
|
||||
// we'll defer switching focus to that browser until it has painted.
|
||||
firstBrowserPaintDeferred.promise.then(() => {
|
||||
this._firstBrowserPaintDeferred.promise.then(() => {
|
||||
// If focus didn't move while we were waiting for first paint, we're okay
|
||||
// to move to the browser.
|
||||
if (document.commandDispatcher.focusedElement == initiallyFocusedElement) {
|
||||
@ -3484,10 +3478,8 @@ function BrowserReloadWithFlags(reloadFlags) {
|
||||
}
|
||||
|
||||
function sendReloadMessage(tab) {
|
||||
tab.linkedBrowser
|
||||
.messageManager
|
||||
.sendAsyncMessage("Browser:Reload",
|
||||
{ flags: reloadFlags, handlingUserInput });
|
||||
tab.linkedBrowser.sendMessageToActor("Browser:Reload",
|
||||
{ flags: reloadFlags, handlingUserInput }, "BrowserTab");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1458,7 +1458,7 @@ nsContextMenu.prototype = {
|
||||
},
|
||||
|
||||
switchPageDirection: function CM_switchPageDirection() {
|
||||
this.browser.messageManager.sendAsyncMessage("SwitchDocumentDirection");
|
||||
gBrowser.selectedBrowser.sendMessageToActor("SwitchDocumentDirection", {}, "SwitchDocumentDirection", true);
|
||||
},
|
||||
|
||||
mediaCommand: function CM_mediaCommand(command, data) {
|
||||
|
@ -53,7 +53,6 @@ window._gBrowser = {
|
||||
this.selectedBrowser);
|
||||
}
|
||||
messageManager.addMessageListener("RefreshBlocker:Blocked", this);
|
||||
messageManager.addMessageListener("Browser:WindowCreated", this);
|
||||
|
||||
// To correctly handle keypresses for potential FindAsYouType, while
|
||||
// the tab's find bar is not yet initialized.
|
||||
@ -601,7 +600,7 @@ window._gBrowser = {
|
||||
},
|
||||
|
||||
_notifyPinnedStatus(aTab) {
|
||||
this.getBrowserForTab(aTab).messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: aTab.pinned });
|
||||
aTab.linkedBrowser.sendMessageToActor("Browser:AppTab", { isAppTab: aTab.pinned }, "BrowserTab");
|
||||
|
||||
let event = document.createEvent("Events");
|
||||
event.initEvent(aTab.pinned ? "TabPinned" : "TabUnpinned", true, false);
|
||||
@ -1780,7 +1779,7 @@ window._gBrowser = {
|
||||
// crashed.
|
||||
tab.removeAttribute("crashed");
|
||||
} else {
|
||||
aBrowser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned });
|
||||
aBrowser.sendMessageToActor("Browser:AppTab", { isAppTab: tab.pinned }, "BrowserTab");
|
||||
|
||||
// Register the new outerWindowID.
|
||||
this._outerWindowIDBrowserMap.set(aBrowser.outerWindowID, aBrowser);
|
||||
@ -1794,9 +1793,7 @@ window._gBrowser = {
|
||||
this.getCachedFindBar(tab).browser = aBrowser;
|
||||
}
|
||||
|
||||
tab.linkedBrowser
|
||||
.messageManager
|
||||
.sendAsyncMessage("Browser:HasSiblings", this.tabs.length > 1);
|
||||
tab.linkedBrowser.sendMessageToActor("Browser:HasSiblings", this.tabs.length > 1, "BrowserTab");
|
||||
|
||||
evt = document.createEvent("Events");
|
||||
evt.initEvent("TabRemotenessChange", true, false);
|
||||
@ -2142,12 +2139,10 @@ window._gBrowser = {
|
||||
// If we transitioned from one browser to two browsers, we need to set
|
||||
// hasSiblings=false on both the existing browser and the new browser.
|
||||
if (this.tabs.length == 2) {
|
||||
window.messageManager
|
||||
.broadcastAsyncMessage("Browser:HasSiblings", true);
|
||||
this.tabs[0].linkedBrowser.sendMessageToActor("Browser:HasSiblings", true, "BrowserTab");
|
||||
this.tabs[1].linkedBrowser.sendMessageToActor("Browser:HasSiblings", true, "BrowserTab");
|
||||
} else {
|
||||
aTab.linkedBrowser
|
||||
.messageManager
|
||||
.sendAsyncMessage("Browser:HasSiblings", this.tabs.length > 1);
|
||||
aTab.linkedBrowser.sendMessageToActor("Browser:HasSiblings", this.tabs.length > 1, "BrowserTab");
|
||||
}
|
||||
|
||||
var evt = new CustomEvent("TabBrowserInserted", { bubbles: true, detail: { insertedOnTabCreation: aInsertedOnTabCreation } });
|
||||
@ -3006,8 +3001,8 @@ window._gBrowser = {
|
||||
if (this.tabs.length == 2) {
|
||||
// We're closing one of our two open tabs, inform the other tab that its
|
||||
// sibling is going away.
|
||||
window.messageManager
|
||||
.broadcastAsyncMessage("Browser:HasSiblings", false);
|
||||
this.tabs[0].linkedBrowser.sendMessageToActor("Browser:HasSiblings", false, "BrowserTab");
|
||||
this.tabs[1].linkedBrowser.sendMessageToActor("Browser:HasSiblings", false, "BrowserTab");
|
||||
}
|
||||
|
||||
if (aTab.linkedPanel) {
|
||||
@ -3475,6 +3470,20 @@ window._gBrowser = {
|
||||
}
|
||||
},
|
||||
|
||||
announceWindowCreated(browser, userContextId) {
|
||||
let tab = this.getTabForBrowser(browser);
|
||||
if (tab && userContextId) {
|
||||
ContextualIdentityService.telemetry(userContextId);
|
||||
tab.setUserContextId(userContextId);
|
||||
}
|
||||
|
||||
// We don't want to update the container icon and identifier if
|
||||
// this is not the selected browser.
|
||||
if (browser == gBrowser.selectedBrowser) {
|
||||
updateUserContextUIIndicator();
|
||||
}
|
||||
},
|
||||
|
||||
reloadMultiSelectedTabs() {
|
||||
this.reloadTabs(this.selectedTabs);
|
||||
},
|
||||
@ -4348,23 +4357,7 @@ window._gBrowser = {
|
||||
return undefined;
|
||||
|
||||
this._outerWindowIDBrowserMap.set(browser.outerWindowID, browser);
|
||||
browser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned });
|
||||
break;
|
||||
}
|
||||
case "Browser:WindowCreated":
|
||||
{
|
||||
let tab = this.getTabForBrowser(browser);
|
||||
if (tab && data.userContextId) {
|
||||
ContextualIdentityService.telemetry(data.userContextId);
|
||||
tab.setUserContextId(data.userContextId);
|
||||
}
|
||||
|
||||
// We don't want to update the container icon and identifier if
|
||||
// this is not the selected browser.
|
||||
if (browser == gBrowser.selectedBrowser) {
|
||||
updateUserContextUIIndicator();
|
||||
}
|
||||
|
||||
browser.sendMessageToActor("Browser:AppTab", { isAppTab: tab.pinned }, "BrowserTab");
|
||||
break;
|
||||
}
|
||||
case "Findbar:Keypress":
|
||||
|
@ -16,6 +16,29 @@ ChromeUtils.defineModuleGetter(this, "ActorManagerParent",
|
||||
const PREF_PDFJS_ENABLED_CACHE_STATE = "pdfjs.enabledCache.state";
|
||||
|
||||
let ACTORS = {
|
||||
BrowserTab: {
|
||||
parent: {
|
||||
moduleURI: "resource:///actors/BrowserTabParent.jsm",
|
||||
},
|
||||
child: {
|
||||
moduleURI: "resource:///actors/BrowserTabChild.jsm",
|
||||
|
||||
events: {
|
||||
"DOMWindowCreated": {},
|
||||
"MozAfterPaint": {},
|
||||
"MozDOMPointerLock:Entered": {},
|
||||
"MozDOMPointerLock:Exited": {},
|
||||
},
|
||||
messages: [
|
||||
"Browser:Reload",
|
||||
"Browser:AppTab",
|
||||
"Browser:HasSiblings",
|
||||
"MixedContent:ReenableProtection",
|
||||
"UpdateCharacterSet",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
ContextMenu: {
|
||||
parent: {
|
||||
moduleURI: "resource:///actors/ContextMenuParent.jsm",
|
||||
@ -31,6 +54,18 @@ let ACTORS = {
|
||||
allFrames: true,
|
||||
},
|
||||
|
||||
SwitchDocumentDirection: {
|
||||
child: {
|
||||
moduleURI: "resource:///actors/SwitchDocumentDirectionChild.jsm",
|
||||
|
||||
messages: [
|
||||
"SwitchDocumentDirection",
|
||||
],
|
||||
},
|
||||
|
||||
allFrames: true,
|
||||
},
|
||||
|
||||
SubframeCrash: {
|
||||
parent: {
|
||||
moduleURI: "resource:///actors/SubframeCrashParent.jsm",
|
||||
@ -99,30 +134,6 @@ let LEGACY_ACTORS = {
|
||||
},
|
||||
},
|
||||
|
||||
BrowserTab: {
|
||||
child: {
|
||||
module: "resource:///actors/BrowserTabChild.jsm",
|
||||
group: "browsers",
|
||||
|
||||
events: {
|
||||
"DOMWindowCreated": {once: true},
|
||||
"MozAfterPaint": {once: true},
|
||||
"MozDOMPointerLock:Entered": {},
|
||||
"MozDOMPointerLock:Exited": {},
|
||||
},
|
||||
|
||||
messages: [
|
||||
"AllowScriptsToClose",
|
||||
"Browser:AppTab",
|
||||
"Browser:HasSiblings",
|
||||
"Browser:Reload",
|
||||
"MixedContent:ReenableProtection",
|
||||
"SwitchDocumentDirection",
|
||||
"UpdateCharacterSet",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
ClickHandler: {
|
||||
child: {
|
||||
module: "resource:///actors/ClickHandlerChild.jsm",
|
||||
|
@ -199,9 +199,9 @@ void GPUProcessManager::DisableGPUProcess(const char* aMessage) {
|
||||
// need to rebind to the UI process.
|
||||
HandleProcessLost();
|
||||
|
||||
// On Windows, always fallback to software.
|
||||
// On Windows and Linux, always fallback to software.
|
||||
// The assumption is that something in the graphics driver is crashing.
|
||||
#if XP_WIN
|
||||
#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
|
||||
FallbackToSoftware("GPU Process is disabled, fallback to software solution.");
|
||||
#endif
|
||||
}
|
||||
@ -494,9 +494,11 @@ void GPUProcessManager::OnRemoteProcessDeviceReset(GPUProcessHost* aHost) {
|
||||
void GPUProcessManager::FallbackToSoftware(const char* aMessage) {
|
||||
gfxConfig::SetFailed(Feature::HW_COMPOSITING, FeatureStatus::Blocked,
|
||||
aMessage);
|
||||
#ifdef XP_WIN
|
||||
gfxConfig::SetFailed(Feature::D3D11_COMPOSITING, FeatureStatus::Blocked,
|
||||
aMessage);
|
||||
gfxConfig::SetFailed(Feature::DIRECT2D, FeatureStatus::Blocked, aMessage);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GPUProcessManager::NotifyListenersOnCompositeDeviceReset() {
|
||||
|
@ -3272,12 +3272,24 @@ void gfxPlatform::GetCMSSupportInfo(mozilla::widget::InfoObject& aObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
char* encodedProfile = nullptr;
|
||||
nsresult rv =
|
||||
Base64Encode(reinterpret_cast<char*>(profile), size, &encodedProfile);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
aObj.DefineProperty("CMSOutputProfile", encodedProfile);
|
||||
free(encodedProfile);
|
||||
// Some profiles can be quite large. We don't want to include giant profiles
|
||||
// by default in about:support. For now, we only accept less than 8kiB.
|
||||
const size_t kMaxProfileSize = 8192;
|
||||
if (size < kMaxProfileSize) {
|
||||
char* encodedProfile = nullptr;
|
||||
nsresult rv =
|
||||
Base64Encode(reinterpret_cast<char*>(profile), size, &encodedProfile);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
aObj.DefineProperty("CMSOutputProfile", encodedProfile);
|
||||
free(encodedProfile);
|
||||
} else {
|
||||
nsPrintfCString msg("base64 encode failed 0x%08x",
|
||||
static_cast<uint32_t>(rv));
|
||||
aObj.DefineProperty("CMSOutputProfile", msg.get());
|
||||
}
|
||||
} else {
|
||||
nsPrintfCString msg("%zu bytes, too large", size);
|
||||
aObj.DefineProperty("CMSOutputProfile", msg.get());
|
||||
}
|
||||
|
||||
free(profile);
|
||||
|
@ -116,7 +116,6 @@ nsPNGDecoder::nsPNGDecoder(RasterImage* aImage)
|
||||
mFrameIsHidden(false),
|
||||
mDisablePremultipliedAlpha(false),
|
||||
mGotInfoCallback(false),
|
||||
mUsePipeTransform(false),
|
||||
mNumFrames(0) {}
|
||||
|
||||
nsPNGDecoder::~nsPNGDecoder() {
|
||||
@ -213,10 +212,9 @@ nsresult nsPNGDecoder::CreateFrame(const FrameInfo& aFrameInfo) {
|
||||
pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY;
|
||||
}
|
||||
|
||||
qcms_transform* pipeTransform = mUsePipeTransform ? mTransform : nullptr;
|
||||
Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(
|
||||
this, Size(), OutputSize(), aFrameInfo.mFrameRect, mFormat, animParams,
|
||||
pipeTransform, pipeFlags);
|
||||
/*aTransform*/ nullptr, pipeFlags);
|
||||
|
||||
if (!pipe) {
|
||||
mPipe = SurfacePipe();
|
||||
@ -415,7 +413,8 @@ static void PNGDoGammaCorrection(png_structp png_ptr, png_infop info_ptr) {
|
||||
|
||||
// Adapted from http://www.littlecms.com/pngchrm.c example code
|
||||
static qcms_profile* PNGGetColorProfile(png_structp png_ptr, png_infop info_ptr,
|
||||
int color_type, uint32_t* intent) {
|
||||
int color_type, qcms_data_type* inType,
|
||||
uint32_t* intent) {
|
||||
qcms_profile* profile = nullptr;
|
||||
*intent = QCMS_INTENT_PERCEPTUAL; // Our default
|
||||
|
||||
@ -493,6 +492,24 @@ static qcms_profile* PNGGetColorProfile(png_structp png_ptr, png_infop info_ptr,
|
||||
}
|
||||
}
|
||||
|
||||
if (profile) {
|
||||
uint32_t profileSpace = qcms_profile_get_color_space(profile);
|
||||
if (profileSpace == icSigGrayData) {
|
||||
if (color_type & PNG_COLOR_MASK_ALPHA) {
|
||||
*inType = QCMS_DATA_GRAYA_8;
|
||||
} else {
|
||||
*inType = QCMS_DATA_GRAY_8;
|
||||
}
|
||||
} else {
|
||||
if (color_type & PNG_COLOR_MASK_ALPHA ||
|
||||
png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
|
||||
*inType = QCMS_DATA_RGBA_8;
|
||||
} else {
|
||||
*inType = QCMS_DATA_RGB_8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
@ -570,41 +587,25 @@ void nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) {
|
||||
png_set_scale_16(png_ptr);
|
||||
}
|
||||
|
||||
// Let libpng expand interlaced images.
|
||||
const bool isInterlaced = interlace_type == PNG_INTERLACE_ADAM7;
|
||||
if (isInterlaced) {
|
||||
png_set_interlace_handling(png_ptr);
|
||||
}
|
||||
|
||||
qcms_data_type inType = QCMS_DATA_RGBA_8;
|
||||
uint32_t intent = -1;
|
||||
uint32_t pIntent;
|
||||
if (decoder->mCMSMode != eCMSMode_Off) {
|
||||
intent = gfxPlatform::GetRenderingIntent();
|
||||
decoder->mInProfile =
|
||||
PNGGetColorProfile(png_ptr, info_ptr, color_type, &pIntent);
|
||||
PNGGetColorProfile(png_ptr, info_ptr, color_type, &inType, &pIntent);
|
||||
// If we're not mandating an intent, use the one from the image.
|
||||
if (intent == uint32_t(-1)) {
|
||||
intent = pIntent;
|
||||
}
|
||||
}
|
||||
if (decoder->mInProfile && gfxPlatform::GetCMSOutputProfile()) {
|
||||
qcms_data_type inType;
|
||||
qcms_data_type outType;
|
||||
|
||||
uint32_t profileSpace = qcms_profile_get_color_space(decoder->mInProfile);
|
||||
decoder->mUsePipeTransform = profileSpace != icSigGrayData;
|
||||
if (decoder->mUsePipeTransform) {
|
||||
// If the transform happens with SurfacePipe, it will always be in BGRA.
|
||||
inType = QCMS_DATA_BGRA_8;
|
||||
outType = QCMS_DATA_BGRA_8;
|
||||
if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) {
|
||||
outType = QCMS_DATA_RGBA_8;
|
||||
} else {
|
||||
if (color_type & PNG_COLOR_MASK_ALPHA) {
|
||||
inType = QCMS_DATA_GRAYA_8;
|
||||
outType = QCMS_DATA_RGBA_8;
|
||||
} else {
|
||||
inType = QCMS_DATA_GRAY_8;
|
||||
outType = QCMS_DATA_RGB_8;
|
||||
}
|
||||
outType = QCMS_DATA_RGB_8;
|
||||
}
|
||||
|
||||
decoder->mTransform = qcms_transform_create(
|
||||
@ -619,11 +620,20 @@ void nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) {
|
||||
}
|
||||
|
||||
if (decoder->mCMSMode == eCMSMode_All) {
|
||||
decoder->mTransform = gfxPlatform::GetCMSBGRATransform();
|
||||
decoder->mUsePipeTransform = true;
|
||||
if (color_type & PNG_COLOR_MASK_ALPHA || num_trans) {
|
||||
decoder->mTransform = gfxPlatform::GetCMSRGBATransform();
|
||||
} else {
|
||||
decoder->mTransform = gfxPlatform::GetCMSRGBTransform();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let libpng expand interlaced images.
|
||||
const bool isInterlaced = interlace_type == PNG_INTERLACE_ADAM7;
|
||||
if (isInterlaced) {
|
||||
png_set_interlace_handling(png_ptr);
|
||||
}
|
||||
|
||||
// now all of those things we set above are used to update various struct
|
||||
// members and whatnot, after which we can get channels, rowbytes, etc.
|
||||
png_read_update_info(png_ptr, info_ptr);
|
||||
@ -688,8 +698,8 @@ void nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr) {
|
||||
}
|
||||
#endif
|
||||
|
||||
if (decoder->mTransform && !decoder->mUsePipeTransform) {
|
||||
uint32_t bpp[] = {0, 3, 4};
|
||||
if (decoder->mTransform && (channels <= 2 || isInterlaced)) {
|
||||
uint32_t bpp[] = {0, 3, 4, 3, 4};
|
||||
decoder->mCMSLine =
|
||||
static_cast<uint8_t*>(malloc(bpp[channels] * frameRect.Width()));
|
||||
if (!decoder->mCMSLine) {
|
||||
@ -830,11 +840,21 @@ void nsPNGDecoder::WriteRow(uint8_t* aRow) {
|
||||
uint32_t width = uint32_t(mFrameRect.Width());
|
||||
|
||||
// Apply color management to the row, if necessary, before writing it out.
|
||||
// This is only needed for grayscale images.
|
||||
if (mTransform && !mUsePipeTransform) {
|
||||
MOZ_ASSERT(mCMSLine);
|
||||
qcms_transform_data(mTransform, rowToWrite, mCMSLine, width);
|
||||
rowToWrite = mCMSLine;
|
||||
if (mTransform) {
|
||||
if (mCMSLine) {
|
||||
qcms_transform_data(mTransform, rowToWrite, mCMSLine, width);
|
||||
|
||||
// Copy alpha over.
|
||||
if (HasAlphaChannel()) {
|
||||
for (uint32_t i = 0; i < width; ++i) {
|
||||
mCMSLine[4 * i + 3] = rowToWrite[mChannels * i + mChannels - 1];
|
||||
}
|
||||
}
|
||||
|
||||
rowToWrite = mCMSLine;
|
||||
} else {
|
||||
qcms_transform_data(mTransform, rowToWrite, rowToWrite, width);
|
||||
}
|
||||
}
|
||||
|
||||
// Write this row to the SurfacePipe.
|
||||
|
@ -101,7 +101,6 @@ class nsPNGDecoder : public Decoder {
|
||||
bool mFrameIsHidden;
|
||||
bool mDisablePremultipliedAlpha;
|
||||
bool mGotInfoCallback;
|
||||
bool mUsePipeTransform;
|
||||
|
||||
struct AnimFrameInfo {
|
||||
AnimFrameInfo();
|
||||
|
@ -228,8 +228,8 @@ nsresult nsWebPDecoder::CreateFrame(const nsIntRect& aFrameRect) {
|
||||
}
|
||||
|
||||
Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(
|
||||
this, Size(), OutputSize(), aFrameRect, mFormat, animParams, mTransform,
|
||||
pipeFlags);
|
||||
this, Size(), OutputSize(), aFrameRect, mFormat, animParams,
|
||||
/*aTransform*/ nullptr, pipeFlags);
|
||||
if (!pipe) {
|
||||
MOZ_LOG(sWebPLog, LogLevel::Error,
|
||||
("[this=%p] nsWebPDecoder::CreateFrame -- no pipe\n", this));
|
||||
@ -281,7 +281,7 @@ void nsWebPDecoder::ApplyColorProfile(const char* aProfile, size_t aLength) {
|
||||
("[this=%p] nsWebPDecoder::ApplyColorProfile -- not tagged, use "
|
||||
"sRGB transform\n",
|
||||
this));
|
||||
mTransform = gfxPlatform::GetCMSBGRATransform();
|
||||
mTransform = gfxPlatform::GetCMSRGBATransform();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -311,9 +311,9 @@ void nsWebPDecoder::ApplyColorProfile(const char* aProfile, size_t aLength) {
|
||||
}
|
||||
|
||||
// Create the color management transform.
|
||||
mTransform = qcms_transform_create(mInProfile, QCMS_DATA_BGRA_8,
|
||||
mTransform = qcms_transform_create(mInProfile, QCMS_DATA_RGBA_8,
|
||||
gfxPlatform::GetCMSOutputProfile(),
|
||||
QCMS_DATA_BGRA_8, (qcms_intent)intent);
|
||||
QCMS_DATA_RGBA_8, (qcms_intent)intent);
|
||||
MOZ_LOG(sWebPLog, LogLevel::Debug,
|
||||
("[this=%p] nsWebPDecoder::ApplyColorProfile -- use tagged "
|
||||
"transform\n",
|
||||
@ -463,6 +463,9 @@ LexerResult nsWebPDecoder::ReadSingle(const uint8_t* aData, size_t aLength,
|
||||
|
||||
for (int row = mLastRow; row < lastRow; row++) {
|
||||
uint8_t* src = rowStart + row * stride;
|
||||
if (mTransform) {
|
||||
qcms_transform_data(mTransform, src, src, width);
|
||||
}
|
||||
|
||||
WriteState result;
|
||||
if (mFormat == SurfaceFormat::B8G8R8A8) {
|
||||
|
@ -427,7 +427,7 @@ namespace JS {
|
||||
D(FULL_WHOLE_CELL_BUFFER, 17) \
|
||||
D(FULL_GENERIC_BUFFER, 18) \
|
||||
D(FULL_VALUE_BUFFER, 19) \
|
||||
D(FULL_CELL_PTR_BUFFER, 20) \
|
||||
D(FULL_CELL_PTR_OBJ_BUFFER, 20) \
|
||||
D(FULL_SLOT_BUFFER, 21) \
|
||||
D(FULL_SHAPE_BUFFER, 22) \
|
||||
D(TOO_MUCH_WASM_MEMORY, 23) \
|
||||
@ -435,10 +435,10 @@ namespace JS {
|
||||
D(FINISH_GC, 25) \
|
||||
D(PREPARE_FOR_TRACING, 26) \
|
||||
D(INCREMENTAL_ALLOC_TRIGGER, 27) \
|
||||
D(FULL_CELL_PTR_STR_BUFFER, 28) \
|
||||
D(INCREMENTAL_MALLOC_TRIGGER, 29) \
|
||||
\
|
||||
/* These are reserved for future use. */ \
|
||||
D(RESERVED4, 28) \
|
||||
D(RESERVED5, 29) \
|
||||
D(RESERVED6, 30) \
|
||||
D(RESERVED7, 31) \
|
||||
D(RESERVED8, 32) \
|
||||
|
@ -371,8 +371,9 @@ bool GCRuntime::gcIfNeededAtAllocation(JSContext* cx) {
|
||||
// If we have grown past our GC heap threshold while in the middle of
|
||||
// an incremental GC, we're growing faster than we're GCing, so stop
|
||||
// the world and do a full, non-incremental GC right now, if possible.
|
||||
Zone* zone = cx->zone();
|
||||
if (isIncrementalGCInProgress() &&
|
||||
cx->zone()->totalBytes() > cx->zone()->threshold.gcTriggerBytes()) {
|
||||
zone->zoneSize.gcBytes() > zone->threshold.gcTriggerBytes()) {
|
||||
PrepareZoneForGC(cx->zone());
|
||||
gc(GC_NORMAL, JS::GCReason::INCREMENTAL_TOO_SLOW);
|
||||
}
|
||||
|
113
js/src/gc/GC.cpp
113
js/src/gc/GC.cpp
@ -1460,8 +1460,7 @@ bool GCRuntime::setParameter(JSGCParamKey key, uint32_t value,
|
||||
return false;
|
||||
}
|
||||
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
|
||||
zone->threshold.updateAfterGC(zone->totalBytes(), GC_NORMAL, tunables,
|
||||
schedulingState, lock);
|
||||
zone->updateAllGCThresholds(*this, lock);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1720,8 +1719,7 @@ void GCRuntime::resetParameter(JSGCParamKey key, AutoLockGC& lock) {
|
||||
default:
|
||||
tunables.resetParameter(key, lock);
|
||||
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
|
||||
zone->threshold.updateAfterGC(zone->totalBytes(), GC_NORMAL, tunables,
|
||||
schedulingState, lock);
|
||||
zone->updateAllGCThresholds(*this, lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2071,7 +2069,7 @@ void GCRuntime::setMaxMallocBytes(size_t value, const AutoLockGC& lock) {
|
||||
}
|
||||
}
|
||||
|
||||
float ZoneHeapThreshold::eagerAllocTrigger(bool highFrequencyGC) const {
|
||||
float ZoneThreshold::eagerAllocTrigger(bool highFrequencyGC) const {
|
||||
float eagerTriggerFactor = highFrequencyGC
|
||||
? HighFrequencyEagerAllocTriggerFactor
|
||||
: LowFrequencyEagerAllocTriggerFactor;
|
||||
@ -2167,6 +2165,22 @@ void ZoneHeapThreshold::updateForRemovedArena(
|
||||
gcTriggerBytes_ -= amount;
|
||||
}
|
||||
|
||||
/* static */
|
||||
size_t ZoneMallocThreshold::computeZoneTriggerBytes(
|
||||
float growthFactor, size_t lastBytes, const GCSchedulingTunables& tunables,
|
||||
const AutoLockGC& lock) {
|
||||
size_t base = Max(lastBytes, tunables.maxMallocBytes());
|
||||
float trigger = float(base) * growthFactor;
|
||||
return size_t(trigger);
|
||||
}
|
||||
|
||||
void ZoneMallocThreshold::updateAfterGC(size_t lastBytes,
|
||||
const GCSchedulingTunables& tunables,
|
||||
const GCSchedulingState& state,
|
||||
const AutoLockGC& lock) {
|
||||
gcTriggerBytes_ = computeZoneTriggerBytes(2.0, lastBytes, tunables, lock);
|
||||
}
|
||||
|
||||
MemoryCounter::MemoryCounter()
|
||||
: bytes_(0), maxBytes_(0), triggered_(NoTrigger) {}
|
||||
|
||||
@ -3422,10 +3436,6 @@ bool GCRuntime::triggerGC(JS::GCReason reason) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void js::gc::MaybeAllocTriggerZoneGC(JSRuntime* rt, ZoneAllocator* zoneAlloc) {
|
||||
rt->gc.maybeAllocTriggerZoneGC(Zone::from(zoneAlloc));
|
||||
}
|
||||
|
||||
void GCRuntime::maybeAllocTriggerZoneGC(Zone* zone, size_t nbytes) {
|
||||
if (!CurrentThreadCanAccessRuntime(rt)) {
|
||||
// Zones in use by a helper thread can't be collected.
|
||||
@ -3435,9 +3445,9 @@ void GCRuntime::maybeAllocTriggerZoneGC(Zone* zone, size_t nbytes) {
|
||||
|
||||
MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());
|
||||
|
||||
size_t usedBytes = zone->totalBytes(); // This already includes |nbytes|.
|
||||
size_t usedBytes =
|
||||
zone->zoneSize.gcBytes(); // This already includes |nbytes|.
|
||||
size_t thresholdBytes = zone->threshold.gcTriggerBytes();
|
||||
|
||||
if (usedBytes >= thresholdBytes) {
|
||||
// The threshold has been surpassed, immediately trigger a GC, which
|
||||
// will be done non-incrementally.
|
||||
@ -3478,6 +3488,39 @@ void GCRuntime::maybeAllocTriggerZoneGC(Zone* zone, size_t nbytes) {
|
||||
}
|
||||
}
|
||||
|
||||
void js::gc::MaybeMallocTriggerZoneGC(JSRuntime* rt, ZoneAllocator* zoneAlloc) {
|
||||
rt->gc.maybeMallocTriggerZoneGC(Zone::from(zoneAlloc));
|
||||
}
|
||||
|
||||
void GCRuntime::maybeMallocTriggerZoneGC(Zone* zone) {
|
||||
if (!CurrentThreadCanAccessRuntime(rt)) {
|
||||
// Zones in use by a helper thread can't be collected.
|
||||
MOZ_ASSERT(zone->usedByHelperThread() || zone->isAtomsZone());
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());
|
||||
|
||||
size_t usedBytes = zone->gcMallocBytes.gcBytes();
|
||||
size_t thresholdBytes = zone->gcMallocThreshold.gcTriggerBytes();
|
||||
if (usedBytes >= thresholdBytes) {
|
||||
// The threshold has been surpassed, immediately trigger a GC, which
|
||||
// will be done non-incrementally.
|
||||
triggerZoneGC(zone, JS::GCReason::TOO_MUCH_MALLOC, usedBytes,
|
||||
thresholdBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
float zoneGCThresholdFactor = tunables.allocThresholdFactor();
|
||||
size_t igcThresholdBytes = thresholdBytes * zoneGCThresholdFactor;
|
||||
if (usedBytes >= igcThresholdBytes) {
|
||||
// Start or continue an in progress incremental GC.
|
||||
triggerZoneGC(zone, JS::GCReason::INCREMENTAL_MALLOC_TRIGGER, usedBytes,
|
||||
igcThresholdBytes);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool GCRuntime::triggerZoneGC(Zone* zone, JS::GCReason reason, size_t used,
|
||||
size_t threshold) {
|
||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
|
||||
@ -3532,17 +3575,30 @@ void GCRuntime::maybeGC(Zone* zone) {
|
||||
return;
|
||||
}
|
||||
|
||||
float threshold = zone->threshold.eagerAllocTrigger(
|
||||
schedulingState.inHighFrequencyGCMode());
|
||||
float usedBytes = zone->totalBytes();
|
||||
if (usedBytes > 1024 * 1024 && usedBytes >= threshold &&
|
||||
!isIncrementalGCInProgress() && !isBackgroundSweeping()) {
|
||||
stats().recordTrigger(usedBytes, threshold);
|
||||
if (isIncrementalGCInProgress() || isBackgroundSweeping()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkEagerAllocTrigger(zone->zoneSize, zone->threshold) ||
|
||||
checkEagerAllocTrigger(zone->gcMallocBytes, zone->gcMallocThreshold)) {
|
||||
PrepareZoneForGC(zone);
|
||||
startGC(GC_NORMAL, JS::GCReason::EAGER_ALLOC_TRIGGER);
|
||||
}
|
||||
}
|
||||
|
||||
bool GCRuntime::checkEagerAllocTrigger(const HeapSize& size,
|
||||
const ZoneThreshold& threshold) {
|
||||
float thresholdBytes =
|
||||
threshold.eagerAllocTrigger(schedulingState.inHighFrequencyGCMode());
|
||||
float usedBytes = size.gcBytes();
|
||||
if (usedBytes <= 1024 * 1024 || usedBytes < thresholdBytes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
stats().recordTrigger(usedBytes, thresholdBytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GCRuntime::triggerFullGCForAtoms(JSContext* cx) {
|
||||
MOZ_ASSERT(fullGCForAtomsRequested_);
|
||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
|
||||
@ -5938,8 +5994,7 @@ IncrementalProgress GCRuntime::endSweepingSweepGroup(FreeOp* fop,
|
||||
for (SweepGroupZonesIter zone(rt); !zone.done(); zone.next()) {
|
||||
AutoLockGC lock(rt);
|
||||
zone->changeGCState(Zone::Sweep, Zone::Finished);
|
||||
zone->threshold.updateAfterGC(zone->totalBytes(), invocationKind, tunables,
|
||||
schedulingState, lock);
|
||||
zone->updateAllGCThresholds(*this, lock);
|
||||
zone->updateAllGCMallocCountersOnGCEnd(lock);
|
||||
zone->arenas.unmarkPreMarkedFreeCells();
|
||||
}
|
||||
@ -7440,7 +7495,7 @@ GCRuntime::IncrementalResult GCRuntime::budgetIncrementalGC(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (zone->totalBytes() >= zone->threshold.gcTriggerBytes()) {
|
||||
if (zone->zoneSize.gcBytes() >= zone->threshold.gcTriggerBytes()) {
|
||||
CheckZoneIsScheduled(zone, reason, "GC bytes");
|
||||
budget.makeUnlimited();
|
||||
stats().nonincremental(AbortReason::GCBytesTrigger);
|
||||
@ -7449,6 +7504,16 @@ GCRuntime::IncrementalResult GCRuntime::budgetIncrementalGC(
|
||||
}
|
||||
}
|
||||
|
||||
if (zone->gcMallocBytes.gcBytes() >=
|
||||
zone->gcMallocThreshold.gcTriggerBytes()) {
|
||||
CheckZoneIsScheduled(zone, reason, "malloc bytes");
|
||||
budget.makeUnlimited();
|
||||
stats().nonincremental(AbortReason::MallocBytesTrigger);
|
||||
if (zone->wasGCStarted() && zone->gcState() > Zone::Sweep) {
|
||||
resetReason = AbortReason::MallocBytesTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
if (zone->shouldTriggerGCForTooMuchMalloc() == NonIncrementalTrigger) {
|
||||
CheckZoneIsScheduled(zone, reason, "malloc bytes");
|
||||
budget.makeUnlimited();
|
||||
@ -7493,10 +7558,14 @@ static void ScheduleZones(GCRuntime* gc) {
|
||||
|
||||
// This is a heuristic to reduce the total number of collections.
|
||||
bool inHighFrequencyMode = gc->schedulingState.inHighFrequencyGCMode();
|
||||
if (zone->totalBytes() >=
|
||||
if (zone->zoneSize.gcBytes() >=
|
||||
zone->threshold.eagerAllocTrigger(inHighFrequencyMode)) {
|
||||
zone->scheduleGC();
|
||||
}
|
||||
if (zone->gcMallocBytes.gcBytes() >=
|
||||
zone->gcMallocThreshold.eagerAllocTrigger(inHighFrequencyMode)) {
|
||||
zone->scheduleGC();
|
||||
}
|
||||
|
||||
// This ensures we collect zones that have reached the malloc limit.
|
||||
if (zone->shouldTriggerGCForTooMuchMalloc()) {
|
||||
@ -8025,6 +8094,7 @@ void GCRuntime::minorGC(JS::GCReason reason, gcstats::PhaseKind phase) {
|
||||
|
||||
for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
|
||||
maybeAllocTriggerZoneGC(zone);
|
||||
maybeMallocTriggerZoneGC(zone);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8209,6 +8279,7 @@ void gc::MergeRealms(Realm* source, Realm* target) {
|
||||
JSRuntime* rt = source->runtimeFromMainThread();
|
||||
rt->gc.mergeRealms(source, target);
|
||||
rt->gc.maybeAllocTriggerZoneGC(target->zone());
|
||||
rt->gc.maybeMallocTriggerZoneGC(target->zone());
|
||||
}
|
||||
|
||||
void GCRuntime::mergeRealms(Realm* source, Realm* target) {
|
||||
|
@ -258,13 +258,18 @@ class GCRuntime {
|
||||
uint32_t getParameter(JSGCParamKey key, const AutoLockGC& lock);
|
||||
|
||||
MOZ_MUST_USE bool triggerGC(JS::GCReason reason);
|
||||
// Check whether to trigger a zone GC. During an incremental GC, optionally
|
||||
// count |nbytes| towards the threshold for performing the next slice.
|
||||
// Check whether to trigger a zone GC after allocating GC cells. During an
|
||||
// incremental GC, optionally count |nbytes| towards the threshold for
|
||||
// performing the next slice.
|
||||
void maybeAllocTriggerZoneGC(Zone* zone, size_t nbytes = 0);
|
||||
// Check whether to trigger a zone GC after malloc memory.
|
||||
void maybeMallocTriggerZoneGC(Zone* zone);
|
||||
// The return value indicates if we were able to do the GC.
|
||||
bool triggerZoneGC(Zone* zone, JS::GCReason reason, size_t usedBytes,
|
||||
size_t thresholdBytes);
|
||||
void maybeGC(Zone* zone);
|
||||
bool checkEagerAllocTrigger(const HeapSize& size,
|
||||
const ZoneThreshold& threshold);
|
||||
// The return value indicates whether a major GC was performed.
|
||||
bool gcIfRequested();
|
||||
void gc(JSGCInvocationKind gckind, JS::GCReason reason);
|
||||
|
@ -831,54 +831,6 @@ static_assert(
|
||||
offsetof(Chunk, trailer) + offsetof(ChunkTrailer, storeBuffer),
|
||||
"The hardcoded API storeBuffer offset must match the actual offset.");
|
||||
|
||||
/*
|
||||
* Tracks the used sizes for owned heap data and automatically maintains the
|
||||
* memory usage relationship between GCRuntime and Zones.
|
||||
*/
|
||||
class HeapSize {
|
||||
/*
|
||||
* A heap usage that contains our parent's heap usage, or null if this is
|
||||
* the top-level usage container.
|
||||
*/
|
||||
HeapSize* const parent_;
|
||||
|
||||
/*
|
||||
* The approximate number of bytes in use on the GC heap, to the nearest
|
||||
* ArenaSize. This does not include any malloc data. It also does not
|
||||
* include not-actively-used addresses that are still reserved at the OS
|
||||
* level for GC usage. It is atomic because it is updated by both the active
|
||||
* and GC helper threads.
|
||||
*/
|
||||
mozilla::Atomic<size_t, mozilla::ReleaseAcquire,
|
||||
mozilla::recordreplay::Behavior::DontPreserve>
|
||||
gcBytes_;
|
||||
|
||||
public:
|
||||
explicit HeapSize(HeapSize* parent) : parent_(parent), gcBytes_(0) {}
|
||||
|
||||
size_t gcBytes() const { return gcBytes_; }
|
||||
|
||||
void addGCArena() {
|
||||
gcBytes_ += ArenaSize;
|
||||
if (parent_) {
|
||||
parent_->addGCArena();
|
||||
}
|
||||
}
|
||||
void removeGCArena() {
|
||||
MOZ_ASSERT(gcBytes_ >= ArenaSize);
|
||||
gcBytes_ -= ArenaSize;
|
||||
if (parent_) {
|
||||
parent_->removeGCArena();
|
||||
}
|
||||
}
|
||||
|
||||
/* Pair to adoptArenas. Adopts the attendant usage statistics. */
|
||||
void adopt(HeapSize& other) {
|
||||
gcBytes_ += other.gcBytes_;
|
||||
other.gcBytes_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
inline void Arena::checkAddress() const {
|
||||
mozilla::DebugOnly<uintptr_t> addr = uintptr_t(this);
|
||||
MOZ_ASSERT(addr);
|
||||
|
@ -778,11 +778,14 @@ bool js::Nursery::shouldCollect() const {
|
||||
return belowBytesThreshold && belowFractionThreshold;
|
||||
}
|
||||
|
||||
static inline bool IsFullStoreBufferReason(JS::GCReason reason) {
|
||||
return reason == JS::GCReason::FULL_WHOLE_CELL_BUFFER ||
|
||||
// typeReason is the gcReason for specified type, for example,
|
||||
// FULL_CELL_PTR_OBJ_BUFFER is the gcReason for JSObject.
|
||||
static inline bool IsFullStoreBufferReason(JS::GCReason reason,
|
||||
JS::GCReason typeReason) {
|
||||
return reason == typeReason ||
|
||||
reason == JS::GCReason::FULL_WHOLE_CELL_BUFFER ||
|
||||
reason == JS::GCReason::FULL_GENERIC_BUFFER ||
|
||||
reason == JS::GCReason::FULL_VALUE_BUFFER ||
|
||||
reason == JS::GCReason::FULL_CELL_PTR_BUFFER ||
|
||||
reason == JS::GCReason::FULL_SLOT_BUFFER ||
|
||||
reason == JS::GCReason::FULL_SHAPE_BUFFER;
|
||||
}
|
||||
@ -840,68 +843,7 @@ void js::Nursery::collect(JS::GCReason reason) {
|
||||
// Resize the nursery.
|
||||
maybeResizeNursery(reason);
|
||||
|
||||
// If we are promoting the nursery, or exhausted the store buffer with
|
||||
// pointers to nursery things, which will force a collection well before
|
||||
// the nursery is full, look for object groups that are getting promoted
|
||||
// excessively and try to pretenure them.
|
||||
startProfile(ProfileKey::Pretenure);
|
||||
bool validPromotionRate;
|
||||
const float promotionRate = calcPromotionRate(&validPromotionRate);
|
||||
uint32_t pretenureCount = 0;
|
||||
bool shouldPretenure =
|
||||
tunables().attemptPretenuring() &&
|
||||
((validPromotionRate && promotionRate > tunables().pretenureThreshold() &&
|
||||
previousGC.nurseryUsedBytes >= 4 * 1024 * 1024) ||
|
||||
IsFullStoreBufferReason(reason));
|
||||
|
||||
if (shouldPretenure) {
|
||||
JSContext* cx = rt->mainContextFromOwnThread();
|
||||
for (auto& entry : tenureCounts.entries) {
|
||||
if (entry.count >= tunables().pretenureGroupThreshold()) {
|
||||
ObjectGroup* group = entry.group;
|
||||
AutoMaybeLeaveAtomsZone leaveAtomsZone(cx);
|
||||
AutoRealm ar(cx, group);
|
||||
AutoSweepObjectGroup sweep(group);
|
||||
if (group->canPreTenure(sweep)) {
|
||||
group->setShouldPreTenure(sweep, cx);
|
||||
pretenureCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stats().setStat(gcstats::STAT_OBJECT_GROUPS_PRETENURED, pretenureCount);
|
||||
|
||||
mozilla::Maybe<AutoGCSession> session;
|
||||
uint32_t numStringsTenured = 0;
|
||||
uint32_t numNurseryStringRealmsDisabled = 0;
|
||||
for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
|
||||
if (shouldPretenure && zone->allocNurseryStrings &&
|
||||
zone->tenuredStrings >= 30 * 1000) {
|
||||
if (!session.isSome()) {
|
||||
session.emplace(rt, JS::HeapState::MinorCollecting);
|
||||
}
|
||||
CancelOffThreadIonCompile(zone);
|
||||
bool preserving = zone->isPreservingCode();
|
||||
zone->setPreservingCode(false);
|
||||
zone->discardJitCode(rt->defaultFreeOp());
|
||||
zone->setPreservingCode(preserving);
|
||||
for (RealmsInZoneIter r(zone); !r.done(); r.next()) {
|
||||
if (jit::JitRealm* jitRealm = r->jitRealm()) {
|
||||
jitRealm->discardStubs();
|
||||
jitRealm->setStringsCanBeInNursery(false);
|
||||
numNurseryStringRealmsDisabled++;
|
||||
}
|
||||
}
|
||||
zone->allocNurseryStrings = false;
|
||||
}
|
||||
numStringsTenured += zone->tenuredStrings;
|
||||
zone->tenuredStrings = 0;
|
||||
}
|
||||
session.reset(); // End the minor GC session, if running one.
|
||||
stats().setStat(gcstats::STAT_NURSERY_STRING_REALMS_DISABLED,
|
||||
numNurseryStringRealmsDisabled);
|
||||
stats().setStat(gcstats::STAT_STRINGS_TENURED, numStringsTenured);
|
||||
endProfile(ProfileKey::Pretenure);
|
||||
const float promotionRate = doPretenuring(rt, reason, tenureCounts);
|
||||
|
||||
// We ignore gcMaxBytes when allocating for minor collection. However, if we
|
||||
// overflowed, we disable the nursery. The next time we allocate, we'll fail
|
||||
@ -920,8 +862,6 @@ void js::Nursery::collect(JS::GCReason reason) {
|
||||
rt->addTelemetry(JS_TELEMETRY_GC_MINOR_REASON_LONG, uint32_t(reason));
|
||||
}
|
||||
rt->addTelemetry(JS_TELEMETRY_GC_NURSERY_BYTES, committed());
|
||||
rt->addTelemetry(JS_TELEMETRY_GC_PRETENURE_COUNT, pretenureCount);
|
||||
rt->addTelemetry(JS_TELEMETRY_GC_NURSERY_PROMOTION_RATE, promotionRate * 100);
|
||||
|
||||
stats().endNurseryCollection(reason);
|
||||
gcTracer.traceMinorGCEnd();
|
||||
@ -1063,6 +1003,93 @@ void js::Nursery::doCollection(JS::GCReason reason,
|
||||
previousGC.tenuredCells = mover.tenuredCells;
|
||||
}
|
||||
|
||||
float js::Nursery::doPretenuring(JSRuntime* rt, JS::GCReason reason,
|
||||
TenureCountCache& tenureCounts) {
|
||||
// If we are promoting the nursery, or exhausted the store buffer with
|
||||
// pointers to nursery things, which will force a collection well before
|
||||
// the nursery is full, look for object groups that are getting promoted
|
||||
// excessively and try to pretenure them.
|
||||
startProfile(ProfileKey::Pretenure);
|
||||
bool validPromotionRate;
|
||||
const float promotionRate = calcPromotionRate(&validPromotionRate);
|
||||
uint32_t pretenureCount = 0;
|
||||
bool attempt = tunables().attemptPretenuring();
|
||||
|
||||
bool pretenureObj, pretenureStr;
|
||||
if (attempt) {
|
||||
// Should we do pretenuring regardless of gcreason?
|
||||
bool shouldPretenure = validPromotionRate &&
|
||||
promotionRate > tunables().pretenureThreshold() &&
|
||||
previousGC.nurseryUsedBytes >= 4 * 1024 * 1024;
|
||||
pretenureObj =
|
||||
shouldPretenure ||
|
||||
IsFullStoreBufferReason(reason, JS::GCReason::FULL_CELL_PTR_OBJ_BUFFER);
|
||||
pretenureStr =
|
||||
shouldPretenure ||
|
||||
IsFullStoreBufferReason(reason, JS::GCReason::FULL_CELL_PTR_STR_BUFFER);
|
||||
} else {
|
||||
pretenureObj = false;
|
||||
pretenureStr = false;
|
||||
}
|
||||
|
||||
if (pretenureObj) {
|
||||
JSContext* cx = rt->mainContextFromOwnThread();
|
||||
uint32_t threshold = tunables().pretenureGroupThreshold();
|
||||
for (auto& entry : tenureCounts.entries) {
|
||||
if (entry.count < threshold) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ObjectGroup* group = entry.group;
|
||||
AutoMaybeLeaveAtomsZone leaveAtomsZone(cx);
|
||||
AutoRealm ar(cx, group);
|
||||
AutoSweepObjectGroup sweep(group);
|
||||
if (group->canPreTenure(sweep)) {
|
||||
group->setShouldPreTenure(sweep, cx);
|
||||
pretenureCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
stats().setStat(gcstats::STAT_OBJECT_GROUPS_PRETENURED, pretenureCount);
|
||||
|
||||
mozilla::Maybe<AutoGCSession> session;
|
||||
uint32_t numStringsTenured = 0;
|
||||
uint32_t numNurseryStringRealmsDisabled = 0;
|
||||
for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
|
||||
if (pretenureStr && zone->allocNurseryStrings &&
|
||||
zone->tenuredStrings >= 30 * 1000) {
|
||||
if (!session.isSome()) {
|
||||
session.emplace(rt, JS::HeapState::MinorCollecting);
|
||||
}
|
||||
CancelOffThreadIonCompile(zone);
|
||||
bool preserving = zone->isPreservingCode();
|
||||
zone->setPreservingCode(false);
|
||||
zone->discardJitCode(rt->defaultFreeOp());
|
||||
zone->setPreservingCode(preserving);
|
||||
for (RealmsInZoneIter r(zone); !r.done(); r.next()) {
|
||||
if (jit::JitRealm* jitRealm = r->jitRealm()) {
|
||||
jitRealm->discardStubs();
|
||||
jitRealm->setStringsCanBeInNursery(false);
|
||||
numNurseryStringRealmsDisabled++;
|
||||
}
|
||||
}
|
||||
zone->allocNurseryStrings = false;
|
||||
}
|
||||
numStringsTenured += zone->tenuredStrings;
|
||||
zone->tenuredStrings = 0;
|
||||
}
|
||||
session.reset(); // End the minor GC session, if running one.
|
||||
stats().setStat(gcstats::STAT_NURSERY_STRING_REALMS_DISABLED,
|
||||
numNurseryStringRealmsDisabled);
|
||||
stats().setStat(gcstats::STAT_STRINGS_TENURED, numStringsTenured);
|
||||
endProfile(ProfileKey::Pretenure);
|
||||
|
||||
rt->addTelemetry(JS_TELEMETRY_GC_PRETENURE_COUNT, pretenureCount);
|
||||
rt->addTelemetry(JS_TELEMETRY_GC_NURSERY_PROMOTION_RATE, promotionRate * 100);
|
||||
|
||||
return promotionRate;
|
||||
}
|
||||
|
||||
bool js::Nursery::registerMallocedBuffer(void* buffer) {
|
||||
MOZ_ASSERT(buffer);
|
||||
return mallocedBuffers.putNew(buffer);
|
||||
|
@ -608,6 +608,9 @@ class Nursery {
|
||||
|
||||
void doCollection(JS::GCReason reason, gc::TenureCountCache& tenureCounts);
|
||||
|
||||
float doPretenuring(JSRuntime* rt, JS::GCReason reason,
|
||||
gc::TenureCountCache& tenureCounts);
|
||||
|
||||
/*
|
||||
* Move the object at |src| in the Nursery to an already-allocated cell
|
||||
* |dst| in Tenured.
|
||||
|
@ -618,23 +618,82 @@ class MemoryCounter {
|
||||
const AutoLockGC& lock);
|
||||
};
|
||||
|
||||
// This class encapsulates the data that determines when we need to do a zone
|
||||
// GC.
|
||||
class ZoneHeapThreshold {
|
||||
// The "growth factor" for computing our next thresholds after a GC.
|
||||
GCLockData<float> gcHeapGrowthFactor_;
|
||||
/*
|
||||
* Tracks the used sizes for owned heap data and automatically maintains the
|
||||
* memory usage relationship between GCRuntime and Zones.
|
||||
*/
|
||||
class HeapSize {
|
||||
/*
|
||||
* A heap usage that contains our parent's heap usage, or null if this is
|
||||
* the top-level usage container.
|
||||
*/
|
||||
HeapSize* const parent_;
|
||||
|
||||
// GC trigger threshold for allocations on the GC heap.
|
||||
/*
|
||||
* The approximate number of bytes in use on the GC heap, to the nearest
|
||||
* ArenaSize. This does not include any malloc data. It also does not
|
||||
* include not-actively-used addresses that are still reserved at the OS
|
||||
* level for GC usage. It is atomic because it is updated by both the active
|
||||
* and GC helper threads.
|
||||
*/
|
||||
mozilla::Atomic<size_t, mozilla::ReleaseAcquire,
|
||||
mozilla::recordreplay::Behavior::DontPreserve>
|
||||
gcBytes_;
|
||||
|
||||
public:
|
||||
explicit HeapSize(HeapSize* parent) : parent_(parent), gcBytes_(0) {}
|
||||
|
||||
size_t gcBytes() const { return gcBytes_; }
|
||||
|
||||
void addGCArena() { addBytes(ArenaSize); }
|
||||
void removeGCArena() { removeBytes(ArenaSize); }
|
||||
|
||||
void addBytes(size_t nbytes) {
|
||||
mozilla::DebugOnly<size_t> initialBytes(gcBytes_);
|
||||
MOZ_ASSERT(initialBytes + nbytes > initialBytes);
|
||||
gcBytes_ += nbytes;
|
||||
if (parent_) {
|
||||
parent_->addBytes(nbytes);
|
||||
}
|
||||
}
|
||||
void removeBytes(size_t nbytes) {
|
||||
MOZ_ASSERT(gcBytes_ >= nbytes);
|
||||
gcBytes_ -= nbytes;
|
||||
if (parent_) {
|
||||
parent_->removeBytes(nbytes);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pair to adoptArenas. Adopts the attendant usage statistics. */
|
||||
void adopt(HeapSize& other) {
|
||||
gcBytes_ += other.gcBytes_;
|
||||
other.gcBytes_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Base class for GC heap and malloc thresholds.
|
||||
class ZoneThreshold {
|
||||
protected:
|
||||
// GC trigger threshold.
|
||||
mozilla::Atomic<size_t, mozilla::Relaxed,
|
||||
mozilla::recordreplay::Behavior::DontPreserve>
|
||||
gcTriggerBytes_;
|
||||
|
||||
public:
|
||||
ZoneHeapThreshold() : gcHeapGrowthFactor_(3.0f), gcTriggerBytes_(0) {}
|
||||
|
||||
float gcHeapGrowthFactor() const { return gcHeapGrowthFactor_; }
|
||||
size_t gcTriggerBytes() const { return gcTriggerBytes_; }
|
||||
float eagerAllocTrigger(bool highFrequencyGC) const;
|
||||
};
|
||||
|
||||
// This class encapsulates the data that determines when we need to do a zone GC
|
||||
// base on GC heap size.
|
||||
class ZoneHeapThreshold : public ZoneThreshold {
|
||||
// The "growth factor" for computing our next thresholds after a GC.
|
||||
GCLockData<float> gcHeapGrowthFactor_;
|
||||
|
||||
public:
|
||||
ZoneHeapThreshold() : gcHeapGrowthFactor_(3.0f) {}
|
||||
|
||||
float gcHeapGrowthFactor() const { return gcHeapGrowthFactor_; }
|
||||
|
||||
void updateAfterGC(size_t lastBytes, JSGCInvocationKind gckind,
|
||||
const GCSchedulingTunables& tunables,
|
||||
@ -651,6 +710,19 @@ class ZoneHeapThreshold {
|
||||
const AutoLockGC& lock);
|
||||
};
|
||||
|
||||
// This class encapsulates the data that determines when we need to do a zone
|
||||
// GC based on malloc data.
|
||||
class ZoneMallocThreshold : public ZoneThreshold {
|
||||
public:
|
||||
void updateAfterGC(size_t lastBytes, const GCSchedulingTunables& tunables,
|
||||
const GCSchedulingState& state, const AutoLockGC& lock);
|
||||
|
||||
private:
|
||||
static size_t computeZoneTriggerBytes(float growthFactor, size_t lastBytes,
|
||||
const GCSchedulingTunables& tunables,
|
||||
const AutoLockGC& lock);
|
||||
};
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
// Counts memory associated with GC things in a zone.
|
||||
@ -661,8 +733,8 @@ class ZoneHeapThreshold {
|
||||
class MemoryTracker {
|
||||
public:
|
||||
MemoryTracker();
|
||||
~MemoryTracker();
|
||||
void fixupAfterMovingGC();
|
||||
void checkEmptyOnDestroy();
|
||||
|
||||
void adopt(MemoryTracker& other);
|
||||
|
||||
|
@ -55,10 +55,10 @@ void StoreBuffer::GenericBuffer::trace(JSTracer* trc) {
|
||||
}
|
||||
|
||||
StoreBuffer::StoreBuffer(JSRuntime* rt, const Nursery& nursery)
|
||||
: bufferVal(this),
|
||||
bufStrCell(this),
|
||||
bufObjCell(this),
|
||||
bufferSlot(this),
|
||||
: bufferVal(this, JS::GCReason::FULL_VALUE_BUFFER),
|
||||
bufStrCell(this, JS::GCReason::FULL_CELL_PTR_STR_BUFFER),
|
||||
bufObjCell(this, JS::GCReason::FULL_CELL_PTR_OBJ_BUFFER),
|
||||
bufferSlot(this, JS::GCReason::FULL_SLOT_BUFFER),
|
||||
bufferWholeCell(this),
|
||||
bufferGeneric(this),
|
||||
cancelIonCompilations_(false),
|
||||
|
@ -80,10 +80,13 @@ class StoreBuffer {
|
||||
|
||||
StoreBuffer* owner_;
|
||||
|
||||
JS::GCReason gcReason_;
|
||||
|
||||
/* Maximum number of entries before we request a minor GC. */
|
||||
const static size_t MaxEntries = 48 * 1024 / sizeof(T);
|
||||
|
||||
explicit MonoTypeBuffer(StoreBuffer* owner) : last_(T()), owner_(owner) {}
|
||||
explicit MonoTypeBuffer(StoreBuffer* owner, JS::GCReason reason)
|
||||
: last_(T()), owner_(owner), gcReason_(reason) {}
|
||||
|
||||
void clear() {
|
||||
last_ = T();
|
||||
@ -117,7 +120,7 @@ class StoreBuffer {
|
||||
last_ = T();
|
||||
|
||||
if (MOZ_UNLIKELY(stores_.count() > MaxEntries)) {
|
||||
owner_->setAboutToOverflow(T::FullBufferReason);
|
||||
owner_->setAboutToOverflow(gcReason_);
|
||||
}
|
||||
}
|
||||
|
||||
@ -276,8 +279,6 @@ class StoreBuffer {
|
||||
explicit operator bool() const { return edge != nullptr; }
|
||||
|
||||
typedef PointerEdgeHasher<CellPtrEdge> Hasher;
|
||||
|
||||
static const auto FullBufferReason = JS::GCReason::FULL_CELL_PTR_BUFFER;
|
||||
};
|
||||
|
||||
struct ValueEdge {
|
||||
@ -311,8 +312,6 @@ class StoreBuffer {
|
||||
explicit operator bool() const { return edge != nullptr; }
|
||||
|
||||
typedef PointerEdgeHasher<ValueEdge> Hasher;
|
||||
|
||||
static const auto FullBufferReason = JS::GCReason::FULL_VALUE_BUFFER;
|
||||
};
|
||||
|
||||
struct SlotsEdge {
|
||||
@ -394,8 +393,6 @@ class StoreBuffer {
|
||||
}
|
||||
static bool match(const SlotsEdge& k, const Lookup& l) { return k == l; }
|
||||
} Hasher;
|
||||
|
||||
static const auto FullBufferReason = JS::GCReason::FULL_SLOT_BUFFER;
|
||||
};
|
||||
|
||||
template <typename Buffer, typename Edge>
|
||||
|
@ -32,17 +32,26 @@ using namespace js::gc;
|
||||
Zone* const Zone::NotOnList = reinterpret_cast<Zone*>(1);
|
||||
|
||||
ZoneAllocator::ZoneAllocator(JSRuntime* rt)
|
||||
: JS::shadow::Zone(rt, &rt->gc.marker), zoneSize(&rt->gc.heapSize) {
|
||||
: JS::shadow::Zone(rt, &rt->gc.marker),
|
||||
zoneSize(&rt->gc.heapSize),
|
||||
gcMallocBytes(nullptr) {
|
||||
AutoLockGC lock(rt);
|
||||
threshold.updateAfterGC(8192, GC_NORMAL, rt->gc.tunables,
|
||||
rt->gc.schedulingState, lock);
|
||||
gcMallocThreshold.updateAfterGC(8192, rt->gc.tunables, rt->gc.schedulingState,
|
||||
lock);
|
||||
setGCMaxMallocBytes(rt->gc.tunables.maxMallocBytes(), lock);
|
||||
jitCodeCounter.setMax(jit::MaxCodeBytesPerProcess * 0.8, lock);
|
||||
}
|
||||
|
||||
ZoneAllocator::~ZoneAllocator() {
|
||||
MOZ_ASSERT_IF(runtimeFromAnyThread()->gc.shutdownCollectedEverything(),
|
||||
gcMallocBytes == 0);
|
||||
#ifdef DEBUG
|
||||
if (runtimeFromAnyThread()->gc.shutdownCollectedEverything()) {
|
||||
gcMallocTracker.checkEmptyOnDestroy();
|
||||
MOZ_ASSERT(zoneSize.gcBytes() == 0);
|
||||
MOZ_ASSERT(gcMallocBytes.gcBytes() == 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ZoneAllocator::fixupAfterMovingGC() {
|
||||
@ -63,6 +72,14 @@ void js::ZoneAllocator::updateAllGCMallocCountersOnGCEnd(
|
||||
jitCodeCounter.updateOnGCEnd(gc.tunables, lock);
|
||||
}
|
||||
|
||||
void js::ZoneAllocator::updateAllGCThresholds(GCRuntime& gc,
|
||||
const js::AutoLockGC& lock) {
|
||||
threshold.updateAfterGC(zoneSize.gcBytes(), GC_NORMAL, gc.tunables,
|
||||
gc.schedulingState, lock);
|
||||
gcMallocThreshold.updateAfterGC(gcMallocBytes.gcBytes(), gc.tunables,
|
||||
gc.schedulingState, lock);
|
||||
}
|
||||
|
||||
js::gc::TriggerKind js::ZoneAllocator::shouldTriggerGCForTooMuchMalloc() {
|
||||
auto& gc = runtimeFromAnyThread()->gc;
|
||||
return std::max(gcMallocCounter.shouldTriggerGC(gc.tunables),
|
||||
@ -658,12 +675,7 @@ static const char* MemoryUseName(MemoryUse use) {
|
||||
|
||||
MemoryTracker::MemoryTracker() : mutex(mutexid::MemoryTracker) {}
|
||||
|
||||
MemoryTracker::~MemoryTracker() {
|
||||
if (!TlsContext.get()->runtime()->gc.shutdownCollectedEverything()) {
|
||||
// Memory leak, suppress crashes.
|
||||
return;
|
||||
}
|
||||
|
||||
void MemoryTracker::checkEmptyOnDestroy() {
|
||||
bool ok = true;
|
||||
|
||||
if (!map.empty()) {
|
||||
|
@ -21,7 +21,7 @@ class Zone;
|
||||
namespace js {
|
||||
|
||||
namespace gc {
|
||||
void MaybeAllocTriggerZoneGC(JSRuntime* rt, ZoneAllocator* zoneAlloc);
|
||||
void MaybeMallocTriggerZoneGC(JSRuntime* rt, ZoneAllocator* zoneAlloc);
|
||||
}
|
||||
|
||||
// Base class of JS::Zone that provides malloc memory allocation and accounting.
|
||||
@ -51,8 +51,7 @@ class ZoneAllocator : public JS::shadow::Zone,
|
||||
}
|
||||
void adoptMallocBytes(ZoneAllocator* other) {
|
||||
gcMallocCounter.adopt(other->gcMallocCounter);
|
||||
gcMallocBytes += other->gcMallocBytes;
|
||||
other->gcMallocBytes = 0;
|
||||
gcMallocBytes.adopt(other->gcMallocBytes);
|
||||
#ifdef DEBUG
|
||||
gcMallocTracker.adopt(other->gcMallocTracker);
|
||||
#endif
|
||||
@ -66,6 +65,7 @@ class ZoneAllocator : public JS::shadow::Zone,
|
||||
|
||||
void updateAllGCMallocCountersOnGCStart();
|
||||
void updateAllGCMallocCountersOnGCEnd(const js::AutoLockGC& lock);
|
||||
void updateAllGCThresholds(gc::GCRuntime& gc, const js::AutoLockGC& lock);
|
||||
js::gc::TriggerKind shouldTriggerGCForTooMuchMalloc();
|
||||
|
||||
// Memory accounting APIs for malloc memory owned by GC cells.
|
||||
@ -73,10 +73,8 @@ class ZoneAllocator : public JS::shadow::Zone,
|
||||
void addCellMemory(js::gc::Cell* cell, size_t nbytes, js::MemoryUse use) {
|
||||
MOZ_ASSERT(cell);
|
||||
MOZ_ASSERT(nbytes);
|
||||
mozilla::DebugOnly<size_t> initialBytes(gcMallocBytes);
|
||||
MOZ_ASSERT(initialBytes + nbytes > initialBytes);
|
||||
gcMallocBytes.addBytes(nbytes);
|
||||
|
||||
gcMallocBytes += nbytes;
|
||||
// We don't currently check GC triggers here.
|
||||
|
||||
#ifdef DEBUG
|
||||
@ -87,9 +85,7 @@ class ZoneAllocator : public JS::shadow::Zone,
|
||||
void removeCellMemory(js::gc::Cell* cell, size_t nbytes, js::MemoryUse use) {
|
||||
MOZ_ASSERT(cell);
|
||||
MOZ_ASSERT(nbytes);
|
||||
MOZ_ASSERT(gcMallocBytes >= nbytes);
|
||||
|
||||
gcMallocBytes -= nbytes;
|
||||
gcMallocBytes.removeBytes(nbytes);
|
||||
|
||||
#ifdef DEBUG
|
||||
gcMallocTracker.untrackMemory(cell, nbytes, use);
|
||||
@ -113,36 +109,29 @@ class ZoneAllocator : public JS::shadow::Zone,
|
||||
|
||||
void incPolicyMemory(js::ZoneAllocPolicy* policy, size_t nbytes) {
|
||||
MOZ_ASSERT(nbytes);
|
||||
mozilla::DebugOnly<size_t> initialBytes(gcMallocBytes);
|
||||
MOZ_ASSERT(initialBytes + nbytes > initialBytes);
|
||||
|
||||
gcMallocBytes += nbytes;
|
||||
gcMallocBytes.addBytes(nbytes);
|
||||
|
||||
#ifdef DEBUG
|
||||
gcMallocTracker.incPolicyMemory(policy, nbytes);
|
||||
#endif
|
||||
|
||||
maybeAllocTriggerZoneGC();
|
||||
maybeMallocTriggerZoneGC();
|
||||
}
|
||||
void decPolicyMemory(js::ZoneAllocPolicy* policy, size_t nbytes) {
|
||||
MOZ_ASSERT(nbytes);
|
||||
MOZ_ASSERT(gcMallocBytes >= nbytes);
|
||||
|
||||
gcMallocBytes -= nbytes;
|
||||
gcMallocBytes.removeBytes(nbytes);
|
||||
|
||||
#ifdef DEBUG
|
||||
gcMallocTracker.decPolicyMemory(policy, nbytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t totalBytes() const { return zoneSize.gcBytes() + gcMallocBytes; }
|
||||
|
||||
// Check allocation threshold and trigger a zone GC if necessary.
|
||||
void maybeAllocTriggerZoneGC() {
|
||||
// Check malloc allocation threshold and trigger a zone GC if necessary.
|
||||
void maybeMallocTriggerZoneGC() {
|
||||
JSRuntime* rt = runtimeFromAnyThread();
|
||||
if (totalBytes() >= threshold.gcTriggerBytes() &&
|
||||
if (gcMallocBytes.gcBytes() >= threshold.gcTriggerBytes() &&
|
||||
rt->heapState() == JS::HeapState::Idle) {
|
||||
gc::MaybeAllocTriggerZoneGC(rt, this);
|
||||
gc::MaybeMallocTriggerZoneGC(rt, this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,10 +153,10 @@ class ZoneAllocator : public JS::shadow::Zone,
|
||||
js::gc::TriggerKind trigger);
|
||||
|
||||
public:
|
||||
// Track heap size under this Zone.
|
||||
// Track GC heap size under this Zone.
|
||||
js::gc::HeapSize zoneSize;
|
||||
|
||||
// Thresholds used to trigger GC.
|
||||
// Thresholds used to trigger GC based on heap size.
|
||||
js::gc::ZoneHeapThreshold threshold;
|
||||
|
||||
// Amount of data to allocate before triggering a new incremental slice for
|
||||
@ -180,12 +169,15 @@ class ZoneAllocator : public JS::shadow::Zone,
|
||||
// free. Currently this is used for all internal malloc allocations.
|
||||
js::gc::MemoryCounter gcMallocCounter;
|
||||
|
||||
public:
|
||||
// Malloc counter used for allocations where size information is
|
||||
// available. Used for some internal and all tracked external allocations.
|
||||
mozilla::Atomic<size_t, mozilla::Relaxed,
|
||||
mozilla::recordreplay::Behavior::DontPreserve>
|
||||
gcMallocBytes;
|
||||
js::gc::HeapSize gcMallocBytes;
|
||||
|
||||
// Thresholds used to trigger GC based on malloc allocations.
|
||||
js::gc::ZoneMallocThreshold gcMallocThreshold;
|
||||
|
||||
private:
|
||||
#ifdef DEBUG
|
||||
// In debug builds, malloc allocations can be tracked to make debugging easier
|
||||
// (possible?) if allocation and free sizes don't balance.
|
||||
|
@ -1172,7 +1172,7 @@ JS_PUBLIC_API void JS::AddAssociatedMemory(JSObject* obj, size_t nbytes,
|
||||
|
||||
Zone* zone = obj->zone();
|
||||
zone->addCellMemory(obj, nbytes, js::MemoryUse(use));
|
||||
zone->maybeAllocTriggerZoneGC();
|
||||
zone->maybeMallocTriggerZoneGC();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API void JS::RemoveAssociatedMemory(JSObject* obj, size_t nbytes,
|
||||
|
@ -228,7 +228,7 @@ inline bool IsSpace(char16_t ch) {
|
||||
return CharInfo(ch).isSpace();
|
||||
}
|
||||
|
||||
inline bool IsSpaceOrBOM2(char16_t ch) {
|
||||
inline bool IsSpaceOrBOM2(char32_t ch) {
|
||||
if (ch < 128) {
|
||||
return js_isspace[ch];
|
||||
}
|
||||
@ -238,6 +238,10 @@ inline bool IsSpaceOrBOM2(char16_t ch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ch >= NonBMPMin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CharInfo(ch).isSpace();
|
||||
}
|
||||
|
||||
|
@ -1650,23 +1650,20 @@ class ScriptSource::LoadSourceMatcher {
|
||||
explicit LoadSourceMatcher(JSContext* cx, ScriptSource* ss, bool* loaded)
|
||||
: cx_(cx), ss_(ss), loaded_(loaded) {}
|
||||
|
||||
template <typename Unit>
|
||||
bool operator()(const Compressed<Unit>&) const {
|
||||
template <typename Unit, SourceRetrievable CanRetrieve>
|
||||
bool operator()(const Compressed<Unit, CanRetrieve>&) const {
|
||||
*loaded_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Unit>
|
||||
bool operator()(const Uncompressed<Unit>&) const {
|
||||
template <typename Unit, SourceRetrievable CanRetrieve>
|
||||
bool operator()(const Uncompressed<Unit, CanRetrieve>&) const {
|
||||
*loaded_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Unit>
|
||||
bool operator()(const Retrievable<Unit>&) {
|
||||
MOZ_ASSERT(ss_->sourceRetrievable(),
|
||||
"should be retrievable if Retrievable");
|
||||
|
||||
if (!cx_->runtime()->sourceHook.ref()) {
|
||||
*loaded_ = false;
|
||||
return true;
|
||||
@ -1685,15 +1682,11 @@ class ScriptSource::LoadSourceMatcher {
|
||||
}
|
||||
|
||||
bool operator()(const Missing&) const {
|
||||
MOZ_ASSERT(!ss_->sourceRetrievable(),
|
||||
"should have Retrievable<Unit> source, not Missing source, if "
|
||||
"retrievable");
|
||||
*loaded_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator()(const BinAST&) const {
|
||||
MOZ_ASSERT(!ss_->sourceRetrievable(), "binast source is never retrievable");
|
||||
*loaded_ = false;
|
||||
return true;
|
||||
}
|
||||
@ -1777,7 +1770,7 @@ template <typename Unit>
|
||||
const Unit* UncompressedSourceCache::lookup(const ScriptSourceChunk& ssc,
|
||||
AutoHoldEntry& holder) {
|
||||
MOZ_ASSERT(!holder_);
|
||||
MOZ_ASSERT(ssc.ss->compressedSourceIs<Unit>());
|
||||
MOZ_ASSERT(ssc.ss->isCompressed<Unit>());
|
||||
|
||||
if (!map_) {
|
||||
return nullptr;
|
||||
@ -1841,7 +1834,7 @@ template <typename Unit>
|
||||
const Unit* ScriptSource::chunkUnits(
|
||||
JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder,
|
||||
size_t chunk) {
|
||||
const Compressed<Unit>& c = data.as<Compressed<Unit>>();
|
||||
const CompressedData<Unit>& c = *compressedData<Unit>();
|
||||
|
||||
ScriptSourceChunk ssc(this, chunk);
|
||||
if (const Unit* decompressed =
|
||||
@ -1879,18 +1872,33 @@ const Unit* ScriptSource::chunkUnits(
|
||||
}
|
||||
|
||||
template <typename Unit>
|
||||
void ScriptSource::movePendingCompressedSource() {
|
||||
void ScriptSource::convertToCompressedSource(SharedImmutableString compressed,
|
||||
size_t uncompressedLength) {
|
||||
MOZ_ASSERT(isUncompressed<Unit>());
|
||||
MOZ_ASSERT(uncompressedData<Unit>()->length() == uncompressedLength);
|
||||
|
||||
if (data.is<Uncompressed<Unit, SourceRetrievable::Yes>>()) {
|
||||
data = SourceType(Compressed<Unit, SourceRetrievable::Yes>(
|
||||
std::move(compressed), uncompressedLength));
|
||||
} else {
|
||||
data = SourceType(Compressed<Unit, SourceRetrievable::No>(
|
||||
std::move(compressed), uncompressedLength));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Unit>
|
||||
void ScriptSource::performDelayedConvertToCompressedSource() {
|
||||
// There might not be a conversion to compressed source happening at all.
|
||||
if (pendingCompressed_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Compressed<Unit>& pending = pendingCompressed_.ref<Compressed<Unit>>();
|
||||
CompressedData<Unit>& pending =
|
||||
pendingCompressed_.ref<CompressedData<Unit>>();
|
||||
|
||||
MOZ_ASSERT(!hasCompressedSource());
|
||||
MOZ_ASSERT_IF(hasUncompressedSource(),
|
||||
pending.uncompressedLength == length());
|
||||
convertToCompressedSource<Unit>(std::move(pending.raw),
|
||||
pending.uncompressedLength);
|
||||
|
||||
data = SourceType(std::move(pending));
|
||||
pendingCompressed_.destroy();
|
||||
}
|
||||
|
||||
@ -1900,7 +1908,7 @@ ScriptSource::PinnedUnits<Unit>::~PinnedUnits() {
|
||||
MOZ_ASSERT(*stack_ == this);
|
||||
*stack_ = prev_;
|
||||
if (!prev_) {
|
||||
source_->movePendingCompressedSource<Unit>();
|
||||
source_->performDelayedConvertToCompressedSource<Unit>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1912,8 +1920,8 @@ const Unit* ScriptSource::units(JSContext* cx,
|
||||
MOZ_ASSERT(begin <= length());
|
||||
MOZ_ASSERT(begin + len <= length());
|
||||
|
||||
if (data.is<Uncompressed<Unit>>()) {
|
||||
const Unit* units = data.as<Uncompressed<Unit>>().units();
|
||||
if (isUncompressed<Unit>()) {
|
||||
const Unit* units = uncompressedData<Unit>()->units();
|
||||
if (!units) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -1928,7 +1936,7 @@ const Unit* ScriptSource::units(JSContext* cx,
|
||||
MOZ_CRASH("ScriptSource::units() on ScriptSource with retrievable source");
|
||||
}
|
||||
|
||||
MOZ_ASSERT(data.is<Compressed<Unit>>());
|
||||
MOZ_ASSERT(isCompressed<Unit>());
|
||||
|
||||
// Determine first/last chunks, the offset (in bytes) into the first chunk
|
||||
// of the requested units, and the number of bytes in the last chunk.
|
||||
@ -2130,7 +2138,8 @@ JSFlatString* ScriptSource::functionBodyString(JSContext* cx) {
|
||||
|
||||
template <typename Unit>
|
||||
MOZ_MUST_USE bool ScriptSource::setUncompressedSourceHelper(
|
||||
JSContext* cx, EntryUnits<Unit>&& source, size_t length) {
|
||||
JSContext* cx, EntryUnits<Unit>&& source, size_t length,
|
||||
SourceRetrievable retrievable) {
|
||||
auto& cache = cx->zone()->runtimeFromAnyThread()->sharedImmutableStrings();
|
||||
|
||||
auto uniqueChars = SourceTypeTraits<Unit>::toCacheable(std::move(source));
|
||||
@ -2140,7 +2149,13 @@ MOZ_MUST_USE bool ScriptSource::setUncompressedSourceHelper(
|
||||
return false;
|
||||
}
|
||||
|
||||
data = SourceType(Uncompressed<Unit>(std::move(*deduped)));
|
||||
if (retrievable == SourceRetrievable::Yes) {
|
||||
data = SourceType(
|
||||
Uncompressed<Unit, SourceRetrievable::Yes>(std::move(*deduped)));
|
||||
} else {
|
||||
data = SourceType(
|
||||
Uncompressed<Unit, SourceRetrievable::No>(std::move(*deduped)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2148,11 +2163,11 @@ template <typename Unit>
|
||||
MOZ_MUST_USE bool ScriptSource::setRetrievedSource(JSContext* cx,
|
||||
EntryUnits<Unit>&& source,
|
||||
size_t length) {
|
||||
MOZ_ASSERT(sourceRetrievable_);
|
||||
MOZ_ASSERT(data.is<Retrievable<Unit>>(),
|
||||
"retrieved source can only overwrite the corresponding "
|
||||
"retrievable source");
|
||||
return setUncompressedSourceHelper(cx, std::move(source), length);
|
||||
return setUncompressedSourceHelper(cx, std::move(source), length,
|
||||
SourceRetrievable::Yes);
|
||||
}
|
||||
|
||||
#if defined(JS_BUILD_BINAST)
|
||||
@ -2226,25 +2241,31 @@ bool ScriptSource::tryCompressOffThread(JSContext* cx) {
|
||||
}
|
||||
|
||||
template <typename Unit>
|
||||
void ScriptSource::convertToCompressedSource(SharedImmutableString compressed,
|
||||
size_t uncompressedLength) {
|
||||
MOZ_ASSERT(data.is<Uncompressed<Unit>>(),
|
||||
"should only be converting uncompressed source to compressed "
|
||||
"source identically encoded");
|
||||
MOZ_ASSERT(length() == uncompressedLength);
|
||||
void ScriptSource::triggerConvertToCompressedSource(
|
||||
SharedImmutableString compressed, size_t uncompressedLength) {
|
||||
MOZ_ASSERT(isUncompressed<Unit>(),
|
||||
"should only be triggering compressed source installation to "
|
||||
"overwrite identically-encoded uncompressed source");
|
||||
MOZ_ASSERT(uncompressedData<Unit>()->length() == uncompressedLength);
|
||||
|
||||
if (pinnedUnitsStack_) {
|
||||
MOZ_ASSERT(pendingCompressed_.empty());
|
||||
pendingCompressed_.construct<Compressed<Unit>>(std::move(compressed),
|
||||
uncompressedLength);
|
||||
} else {
|
||||
data =
|
||||
SourceType(Compressed<Unit>(std::move(compressed), uncompressedLength));
|
||||
// If units aren't pinned -- and they probably won't be, we'd have to have a
|
||||
// GC in the small window of time where a |PinnedUnits| was live -- then we
|
||||
// can immediately convert.
|
||||
if (MOZ_LIKELY(!pinnedUnitsStack_)) {
|
||||
convertToCompressedSource<Unit>(std::move(compressed), uncompressedLength);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, set aside the compressed-data info. The conversion is performed
|
||||
// when the last |PinnedUnits| dies.
|
||||
MOZ_ASSERT(pendingCompressed_.empty(),
|
||||
"shouldn't be multiple conversions happening");
|
||||
pendingCompressed_.construct<CompressedData<Unit>>(std::move(compressed),
|
||||
uncompressedLength);
|
||||
}
|
||||
|
||||
template <typename Unit>
|
||||
MOZ_MUST_USE bool ScriptSource::initializeWithCompressedSource(
|
||||
MOZ_MUST_USE bool ScriptSource::initializeWithUnretrievableCompressedSource(
|
||||
JSContext* cx, UniqueChars&& compressed, size_t rawLength,
|
||||
size_t sourceLength) {
|
||||
MOZ_ASSERT(data.is<Missing>(), "shouldn't be double-initializing");
|
||||
@ -2261,7 +2282,9 @@ MOZ_MUST_USE bool ScriptSource::initializeWithCompressedSource(
|
||||
"shouldn't be initializing a ScriptSource while its characters "
|
||||
"are pinned -- that only makes sense with a ScriptSource actively "
|
||||
"being inspected");
|
||||
data = SourceType(Compressed<Unit>(std::move(*deduped), sourceLength));
|
||||
|
||||
data = SourceType(Compressed<Unit, SourceRetrievable::No>(std::move(*deduped),
|
||||
sourceLength));
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -2278,7 +2301,6 @@ bool ScriptSource::assignSource(JSContext* cx,
|
||||
}
|
||||
|
||||
if (options.sourceIsLazy) {
|
||||
sourceRetrievable_ = true;
|
||||
data = SourceType(Retrievable<Unit>());
|
||||
return true;
|
||||
}
|
||||
@ -2296,7 +2318,8 @@ bool ScriptSource::assignSource(JSContext* cx,
|
||||
return false;
|
||||
}
|
||||
|
||||
data = SourceType(Uncompressed<Unit>(std::move(*deduped)));
|
||||
data = SourceType(
|
||||
Uncompressed<Unit, SourceRetrievable::No>(std::move(*deduped)));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2334,7 +2357,7 @@ static MOZ_MUST_USE bool reallocUniquePtr(UniqueChars& unique, size_t size) {
|
||||
template <typename Unit>
|
||||
void SourceCompressionTask::workEncodingSpecific() {
|
||||
ScriptSource* source = sourceHolder_.get();
|
||||
MOZ_ASSERT(source->data.is<ScriptSource::Uncompressed<Unit>>());
|
||||
MOZ_ASSERT(source->isUncompressed<Unit>());
|
||||
|
||||
// Try to keep the maximum memory usage down by only allocating half the
|
||||
// size of the string, first.
|
||||
@ -2345,8 +2368,7 @@ void SourceCompressionTask::workEncodingSpecific() {
|
||||
return;
|
||||
}
|
||||
|
||||
const Unit* chars =
|
||||
source->data.as<ScriptSource::Uncompressed<Unit>>().units();
|
||||
const Unit* chars = source->uncompressedData<Unit>()->units();
|
||||
Compressor comp(reinterpret_cast<const unsigned char*>(chars), inputBytes);
|
||||
if (!comp.init()) {
|
||||
return;
|
||||
@ -2410,8 +2432,8 @@ struct SourceCompressionTask::PerformTaskWork {
|
||||
|
||||
explicit PerformTaskWork(SourceCompressionTask* task) : task_(task) {}
|
||||
|
||||
template <typename Unit>
|
||||
void operator()(const ScriptSource::Uncompressed<Unit>&) {
|
||||
template <typename Unit, SourceRetrievable CanRetrieve>
|
||||
void operator()(const ScriptSource::Uncompressed<Unit, CanRetrieve>&) {
|
||||
task_->workEncodingSpecific<Unit>();
|
||||
}
|
||||
|
||||
@ -2439,15 +2461,15 @@ void SourceCompressionTask::runTask() {
|
||||
source->performTaskWork(this);
|
||||
}
|
||||
|
||||
void ScriptSource::convertToCompressedSourceFromTask(
|
||||
void ScriptSource::triggerConvertToCompressedSourceFromTask(
|
||||
SharedImmutableString compressed) {
|
||||
data.match(ConvertToCompressedSourceFromTask(this, compressed));
|
||||
data.match(TriggerConvertToCompressedSourceFromTask(this, compressed));
|
||||
}
|
||||
|
||||
void SourceCompressionTask::complete() {
|
||||
if (!shouldCancel() && resultString_.isSome()) {
|
||||
ScriptSource* source = sourceHolder_.get();
|
||||
source->convertToCompressedSourceFromTask(std::move(*resultString_));
|
||||
source->triggerConvertToCompressedSourceFromTask(std::move(*resultString_));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2524,21 +2546,23 @@ bool ScriptSource::xdrFinalizeEncoder(JS::TranscodeBuffer& buffer) {
|
||||
}
|
||||
|
||||
template <typename Unit>
|
||||
MOZ_MUST_USE bool ScriptSource::initializeUncompressedSource(
|
||||
MOZ_MUST_USE bool ScriptSource::initializeUnretrievableUncompressedSource(
|
||||
JSContext* cx, EntryUnits<Unit>&& source, size_t length) {
|
||||
MOZ_ASSERT(data.is<Missing>(), "must be initializing a fresh ScriptSource");
|
||||
return setUncompressedSourceHelper(cx, std::move(source), length);
|
||||
return setUncompressedSourceHelper(cx, std::move(source), length,
|
||||
SourceRetrievable::No);
|
||||
}
|
||||
|
||||
template <typename Unit>
|
||||
struct SourceDecoder {
|
||||
struct UnretrievableSourceDecoder {
|
||||
XDRState<XDR_DECODE>* const xdr_;
|
||||
ScriptSource* const scriptSource_;
|
||||
const uint32_t uncompressedLength_;
|
||||
|
||||
public:
|
||||
SourceDecoder(XDRState<XDR_DECODE>* xdr, ScriptSource* scriptSource,
|
||||
uint32_t uncompressedLength)
|
||||
UnretrievableSourceDecoder(XDRState<XDR_DECODE>* xdr,
|
||||
ScriptSource* scriptSource,
|
||||
uint32_t uncompressedLength)
|
||||
: xdr_(xdr),
|
||||
scriptSource_(scriptSource),
|
||||
uncompressedLength_(uncompressedLength) {}
|
||||
@ -2552,7 +2576,7 @@ struct SourceDecoder {
|
||||
|
||||
MOZ_TRY(xdr_->codeChars(sourceUnits.get(), uncompressedLength_));
|
||||
|
||||
if (!scriptSource_->initializeUncompressedSource(
|
||||
if (!scriptSource_->initializeUnretrievableUncompressedSource(
|
||||
xdr_->cx(), std::move(sourceUnits), uncompressedLength_)) {
|
||||
return xdr_->fail(JS::TranscodeResult_Throw);
|
||||
}
|
||||
@ -2564,34 +2588,35 @@ struct SourceDecoder {
|
||||
namespace js {
|
||||
|
||||
template <>
|
||||
XDRResult ScriptSource::xdrUncompressedSource<XDR_DECODE>(
|
||||
XDRResult ScriptSource::xdrUnretrievableUncompressedSource<XDR_DECODE>(
|
||||
XDRState<XDR_DECODE>* xdr, uint8_t sourceCharSize,
|
||||
uint32_t uncompressedLength) {
|
||||
MOZ_ASSERT(sourceCharSize == 1 || sourceCharSize == 2);
|
||||
|
||||
if (sourceCharSize == 1) {
|
||||
SourceDecoder<Utf8Unit> decoder(xdr, this, uncompressedLength);
|
||||
UnretrievableSourceDecoder<Utf8Unit> decoder(xdr, this, uncompressedLength);
|
||||
return decoder.decode();
|
||||
}
|
||||
|
||||
SourceDecoder<char16_t> decoder(xdr, this, uncompressedLength);
|
||||
UnretrievableSourceDecoder<char16_t> decoder(xdr, this, uncompressedLength);
|
||||
return decoder.decode();
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
template <typename Unit>
|
||||
struct SourceEncoder {
|
||||
struct UnretrievableSourceEncoder {
|
||||
XDRState<XDR_ENCODE>* const xdr_;
|
||||
ScriptSource* const source_;
|
||||
const uint32_t uncompressedLength_;
|
||||
|
||||
SourceEncoder(XDRState<XDR_ENCODE>* xdr, ScriptSource* source,
|
||||
uint32_t uncompressedLength)
|
||||
UnretrievableSourceEncoder(XDRState<XDR_ENCODE>* xdr, ScriptSource* source,
|
||||
uint32_t uncompressedLength)
|
||||
: xdr_(xdr), source_(source), uncompressedLength_(uncompressedLength) {}
|
||||
|
||||
XDRResult encode() {
|
||||
Unit* sourceUnits = const_cast<Unit*>(source_->uncompressedData<Unit>());
|
||||
Unit* sourceUnits =
|
||||
const_cast<Unit*>(source_->uncompressedData<Unit>()->units());
|
||||
|
||||
return xdr_->codeChars(sourceUnits, uncompressedLength_);
|
||||
}
|
||||
@ -2600,17 +2625,17 @@ struct SourceEncoder {
|
||||
namespace js {
|
||||
|
||||
template <>
|
||||
XDRResult ScriptSource::xdrUncompressedSource<XDR_ENCODE>(
|
||||
XDRResult ScriptSource::xdrUnretrievableUncompressedSource<XDR_ENCODE>(
|
||||
XDRState<XDR_ENCODE>* xdr, uint8_t sourceCharSize,
|
||||
uint32_t uncompressedLength) {
|
||||
MOZ_ASSERT(sourceCharSize == 1 || sourceCharSize == 2);
|
||||
|
||||
if (sourceCharSize == 1) {
|
||||
SourceEncoder<Utf8Unit> encoder(xdr, this, uncompressedLength);
|
||||
UnretrievableSourceEncoder<Utf8Unit> encoder(xdr, this, uncompressedLength);
|
||||
return encoder.encode();
|
||||
}
|
||||
|
||||
SourceEncoder<char16_t> encoder(xdr, this, uncompressedLength);
|
||||
UnretrievableSourceEncoder<char16_t> encoder(xdr, this, uncompressedLength);
|
||||
return encoder.encode();
|
||||
}
|
||||
|
||||
@ -2619,73 +2644,52 @@ XDRResult ScriptSource::xdrUncompressedSource<XDR_ENCODE>(
|
||||
template <typename Unit, XDRMode mode>
|
||||
/* static */
|
||||
XDRResult ScriptSource::codeUncompressedData(XDRState<mode>* const xdr,
|
||||
ScriptSource* const ss,
|
||||
bool retrievable) {
|
||||
ScriptSource* const ss) {
|
||||
static_assert(std::is_same<Unit, Utf8Unit>::value ||
|
||||
std::is_same<Unit, char16_t>::value,
|
||||
"should handle UTF-8 and UTF-16");
|
||||
|
||||
if (mode == XDR_ENCODE) {
|
||||
MOZ_ASSERT(ss->data.is<Uncompressed<Unit>>());
|
||||
MOZ_ASSERT(ss->isUncompressed<Unit>());
|
||||
} else {
|
||||
MOZ_ASSERT(ss->data.is<Missing>());
|
||||
}
|
||||
|
||||
MOZ_ASSERT(retrievable == ss->sourceRetrievable());
|
||||
|
||||
if (retrievable) {
|
||||
// It's unnecessary to code uncompressed data if it can just be retrieved
|
||||
// using the source hook.
|
||||
if (mode == XDR_DECODE) {
|
||||
ss->data = SourceType(Retrievable<Unit>());
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
uint32_t uncompressedLength;
|
||||
if (mode == XDR_ENCODE) {
|
||||
uncompressedLength = ss->data.as<Uncompressed<Unit>>().length();
|
||||
uncompressedLength = ss->uncompressedData<Unit>()->length();
|
||||
}
|
||||
MOZ_TRY(xdr->codeUint32(&uncompressedLength));
|
||||
|
||||
return ss->xdrUncompressedSource(xdr, sizeof(Unit), uncompressedLength);
|
||||
return ss->xdrUnretrievableUncompressedSource(xdr, sizeof(Unit),
|
||||
uncompressedLength);
|
||||
}
|
||||
|
||||
template <typename Unit, XDRMode mode>
|
||||
/* static */
|
||||
XDRResult ScriptSource::codeCompressedData(XDRState<mode>* const xdr,
|
||||
ScriptSource* const ss,
|
||||
bool retrievable) {
|
||||
ScriptSource* const ss) {
|
||||
static_assert(std::is_same<Unit, Utf8Unit>::value ||
|
||||
std::is_same<Unit, char16_t>::value,
|
||||
"should handle UTF-8 and UTF-16");
|
||||
|
||||
if (mode == XDR_ENCODE) {
|
||||
MOZ_ASSERT(ss->data.is<Compressed<Unit>>());
|
||||
MOZ_ASSERT(ss->isCompressed<Unit>());
|
||||
} else {
|
||||
MOZ_ASSERT(ss->data.is<Missing>());
|
||||
}
|
||||
|
||||
MOZ_ASSERT(retrievable == ss->sourceRetrievable());
|
||||
|
||||
if (retrievable) {
|
||||
// It's unnecessary to code compressed data if it can just be retrieved
|
||||
// using the source hook.
|
||||
if (mode == XDR_DECODE) {
|
||||
ss->data = SourceType(Retrievable<Unit>());
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
uint32_t uncompressedLength;
|
||||
if (mode == XDR_ENCODE) {
|
||||
uncompressedLength = ss->data.as<Compressed<Unit>>().uncompressedLength;
|
||||
uncompressedLength = ss->data.as<Compressed<Unit, SourceRetrievable::No>>()
|
||||
.uncompressedLength;
|
||||
}
|
||||
MOZ_TRY(xdr->codeUint32(&uncompressedLength));
|
||||
|
||||
uint32_t compressedLength;
|
||||
if (mode == XDR_ENCODE) {
|
||||
compressedLength = ss->data.as<Compressed<Unit>>().raw.length();
|
||||
compressedLength =
|
||||
ss->data.as<Compressed<Unit, SourceRetrievable::No>>().raw.length();
|
||||
}
|
||||
MOZ_TRY(xdr->codeUint32(&compressedLength));
|
||||
|
||||
@ -2697,20 +2701,36 @@ XDRResult ScriptSource::codeCompressedData(XDRState<mode>* const xdr,
|
||||
}
|
||||
MOZ_TRY(xdr->codeBytes(bytes.get(), compressedLength));
|
||||
|
||||
if (!ss->initializeWithCompressedSource<Unit>(xdr->cx(), std::move(bytes),
|
||||
compressedLength,
|
||||
uncompressedLength)) {
|
||||
if (!ss->initializeWithUnretrievableCompressedSource<Unit>(
|
||||
xdr->cx(), std::move(bytes), compressedLength,
|
||||
uncompressedLength)) {
|
||||
return xdr->fail(JS::TranscodeResult_Throw);
|
||||
}
|
||||
} else {
|
||||
void* bytes =
|
||||
const_cast<char*>(ss->data.as<Compressed<Unit>>().raw.chars());
|
||||
void* bytes = const_cast<char*>(ss->compressedData<Unit>()->raw.chars());
|
||||
MOZ_TRY(xdr->codeBytes(bytes, compressedLength));
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
template <typename Unit,
|
||||
template <typename U, SourceRetrievable CanRetrieve> class Data,
|
||||
XDRMode mode>
|
||||
/* static */
|
||||
void ScriptSource::codeRetrievable(ScriptSource* const ss) {
|
||||
static_assert(std::is_same<Unit, Utf8Unit>::value ||
|
||||
std::is_same<Unit, char16_t>::value,
|
||||
"should handle UTF-8 and UTF-16");
|
||||
|
||||
if (mode == XDR_ENCODE) {
|
||||
MOZ_ASSERT((ss->data.is<Data<Unit, SourceRetrievable::Yes>>()));
|
||||
} else {
|
||||
MOZ_ASSERT(ss->data.is<Missing>());
|
||||
ss->data = SourceType(Retrievable<Unit>());
|
||||
}
|
||||
}
|
||||
|
||||
template <XDRMode mode>
|
||||
/* static */
|
||||
XDRResult ScriptSource::codeBinASTData(XDRState<mode>* const xdr,
|
||||
@ -2880,25 +2900,17 @@ template <XDRMode mode>
|
||||
/* static */
|
||||
XDRResult ScriptSource::xdrData(XDRState<mode>* const xdr,
|
||||
ScriptSource* const ss) {
|
||||
// Retrievability is kept outside |ScriptSource::data| (and not solely as
|
||||
// distinct variant types within it) because retrievable compressed or
|
||||
// uncompressed data need not be XDR'd.
|
||||
uint8_t retrievable;
|
||||
if (mode == XDR_ENCODE) {
|
||||
retrievable = ss->sourceRetrievable_;
|
||||
}
|
||||
MOZ_TRY(xdr->codeUint8(&retrievable));
|
||||
if (mode == XDR_DECODE) {
|
||||
ss->sourceRetrievable_ = retrievable != 0;
|
||||
}
|
||||
|
||||
// The order here corresponds to the type order in |ScriptSource::SourceType|
|
||||
// for simplicity, but it isn't truly necessary that it do so.
|
||||
// so number->internal Variant tag is a no-op.
|
||||
enum class DataType {
|
||||
CompressedUtf8,
|
||||
UncompressedUtf8,
|
||||
CompressedUtf16,
|
||||
UncompressedUtf16,
|
||||
CompressedUtf8Retrievable,
|
||||
UncompressedUtf8Retrievable,
|
||||
CompressedUtf8NotRetrievable,
|
||||
UncompressedUtf8NotRetrievable,
|
||||
CompressedUtf16Retrievable,
|
||||
UncompressedUtf16Retrievable,
|
||||
CompressedUtf16NotRetrievable,
|
||||
UncompressedUtf16NotRetrievable,
|
||||
RetrievableUtf8,
|
||||
RetrievableUtf16,
|
||||
Missing,
|
||||
@ -2914,17 +2926,33 @@ XDRResult ScriptSource::xdrData(XDRState<mode>* const xdr,
|
||||
// it, then switch on it (and ignore the |Variant::match| API).
|
||||
class XDRDataTag {
|
||||
public:
|
||||
DataType operator()(const Compressed<Utf8Unit>&) {
|
||||
return DataType::CompressedUtf8;
|
||||
DataType operator()(const Compressed<Utf8Unit, SourceRetrievable::Yes>&) {
|
||||
return DataType::CompressedUtf8Retrievable;
|
||||
}
|
||||
DataType operator()(const Uncompressed<Utf8Unit>&) {
|
||||
return DataType::UncompressedUtf8;
|
||||
DataType operator()(
|
||||
const Uncompressed<Utf8Unit, SourceRetrievable::Yes>&) {
|
||||
return DataType::UncompressedUtf8Retrievable;
|
||||
}
|
||||
DataType operator()(const Compressed<char16_t>&) {
|
||||
return DataType::CompressedUtf16;
|
||||
DataType operator()(const Compressed<Utf8Unit, SourceRetrievable::No>&) {
|
||||
return DataType::CompressedUtf8NotRetrievable;
|
||||
}
|
||||
DataType operator()(const Uncompressed<char16_t>&) {
|
||||
return DataType::UncompressedUtf16;
|
||||
DataType operator()(
|
||||
const Uncompressed<Utf8Unit, SourceRetrievable::No>&) {
|
||||
return DataType::UncompressedUtf8NotRetrievable;
|
||||
}
|
||||
DataType operator()(const Compressed<char16_t, SourceRetrievable::Yes>&) {
|
||||
return DataType::CompressedUtf16Retrievable;
|
||||
}
|
||||
DataType operator()(
|
||||
const Uncompressed<char16_t, SourceRetrievable::Yes>&) {
|
||||
return DataType::UncompressedUtf16Retrievable;
|
||||
}
|
||||
DataType operator()(const Compressed<char16_t, SourceRetrievable::No>&) {
|
||||
return DataType::CompressedUtf16NotRetrievable;
|
||||
}
|
||||
DataType operator()(
|
||||
const Uncompressed<char16_t, SourceRetrievable::No>&) {
|
||||
return DataType::UncompressedUtf16NotRetrievable;
|
||||
}
|
||||
DataType operator()(const Retrievable<Utf8Unit>&) {
|
||||
return DataType::RetrievableUtf8;
|
||||
@ -2952,17 +2980,33 @@ XDRResult ScriptSource::xdrData(XDRState<mode>* const xdr,
|
||||
}
|
||||
|
||||
switch (tag) {
|
||||
case DataType::CompressedUtf8:
|
||||
return ScriptSource::codeCompressedData<Utf8Unit>(xdr, ss, retrievable);
|
||||
case DataType::CompressedUtf8Retrievable:
|
||||
ScriptSource::codeRetrievable<Utf8Unit, Compressed, mode>(ss);
|
||||
return Ok();
|
||||
|
||||
case DataType::UncompressedUtf8:
|
||||
return ScriptSource::codeUncompressedData<Utf8Unit>(xdr, ss, retrievable);
|
||||
case DataType::CompressedUtf8NotRetrievable:
|
||||
return ScriptSource::codeCompressedData<Utf8Unit>(xdr, ss);
|
||||
|
||||
case DataType::CompressedUtf16:
|
||||
return ScriptSource::codeCompressedData<char16_t>(xdr, ss, retrievable);
|
||||
case DataType::UncompressedUtf8Retrievable:
|
||||
ScriptSource::codeRetrievable<Utf8Unit, Uncompressed, mode>(ss);
|
||||
return Ok();
|
||||
|
||||
case DataType::UncompressedUtf16:
|
||||
return ScriptSource::codeUncompressedData<char16_t>(xdr, ss, retrievable);
|
||||
case DataType::UncompressedUtf8NotRetrievable:
|
||||
return ScriptSource::codeUncompressedData<Utf8Unit>(xdr, ss);
|
||||
|
||||
case DataType::CompressedUtf16Retrievable:
|
||||
ScriptSource::codeRetrievable<char16_t, Compressed, mode>(ss);
|
||||
return Ok();
|
||||
|
||||
case DataType::CompressedUtf16NotRetrievable:
|
||||
return ScriptSource::codeCompressedData<char16_t>(xdr, ss);
|
||||
|
||||
case DataType::UncompressedUtf16Retrievable:
|
||||
ScriptSource::codeRetrievable<char16_t, Uncompressed, mode>(ss);
|
||||
return Ok();
|
||||
|
||||
case DataType::UncompressedUtf16NotRetrievable:
|
||||
return ScriptSource::codeUncompressedData<char16_t>(xdr, ss);
|
||||
|
||||
case DataType::Missing: {
|
||||
MOZ_ASSERT(ss->data.is<Missing>(),
|
||||
@ -2974,11 +3018,11 @@ XDRResult ScriptSource::xdrData(XDRState<mode>* const xdr,
|
||||
}
|
||||
|
||||
case DataType::RetrievableUtf8:
|
||||
codeRetrievableData<Utf8Unit, mode>(ss);
|
||||
ScriptSource::codeRetrievableData<Utf8Unit, mode>(ss);
|
||||
return Ok();
|
||||
|
||||
case DataType::RetrievableUtf16:
|
||||
codeRetrievableData<char16_t, mode>(ss);
|
||||
ScriptSource::codeRetrievableData<char16_t, mode>(ss);
|
||||
return Ok();
|
||||
|
||||
case DataType::BinAST:
|
||||
|
@ -485,6 +485,11 @@ struct SourceTypeTraits<char16_t> {
|
||||
|
||||
class ScriptSourceHolder;
|
||||
|
||||
// Retrievable source can be retrieved using the source hook (and therefore
|
||||
// need not be XDR'd, can be discarded if desired because it can always be
|
||||
// reconstituted later, etc.).
|
||||
enum class SourceRetrievable { Yes, No };
|
||||
|
||||
class ScriptSource {
|
||||
friend class SourceCompressionTask;
|
||||
|
||||
@ -533,13 +538,12 @@ class ScriptSource {
|
||||
|
||||
// Indicate which field in the |data| union is active.
|
||||
|
||||
// Uncompressed source text.
|
||||
template <typename Unit>
|
||||
class Uncompressed {
|
||||
class UncompressedData {
|
||||
typename SourceTypeTraits<Unit>::SharedImmutableString string_;
|
||||
|
||||
public:
|
||||
explicit Uncompressed(
|
||||
explicit UncompressedData(
|
||||
typename SourceTypeTraits<Unit>::SharedImmutableString str)
|
||||
: string_(std::move(str)) {}
|
||||
|
||||
@ -548,18 +552,35 @@ class ScriptSource {
|
||||
size_t length() const { return string_.length(); }
|
||||
};
|
||||
|
||||
// Compressed source text.
|
||||
// Uncompressed source text.
|
||||
template <typename Unit, SourceRetrievable CanRetrieve>
|
||||
class Uncompressed : public UncompressedData<Unit> {
|
||||
using Base = UncompressedData<Unit>;
|
||||
|
||||
public:
|
||||
using Base::Base;
|
||||
};
|
||||
|
||||
template <typename Unit>
|
||||
struct Compressed {
|
||||
struct CompressedData {
|
||||
// Single-byte compressed text, regardless whether the original text
|
||||
// was single-byte or two-byte.
|
||||
SharedImmutableString raw;
|
||||
size_t uncompressedLength;
|
||||
|
||||
Compressed(SharedImmutableString raw, size_t uncompressedLength)
|
||||
CompressedData(SharedImmutableString raw, size_t uncompressedLength)
|
||||
: raw(std::move(raw)), uncompressedLength(uncompressedLength) {}
|
||||
};
|
||||
|
||||
// Compressed source text.
|
||||
template <typename Unit, SourceRetrievable CanRetrieve>
|
||||
struct Compressed : public CompressedData<Unit> {
|
||||
using Base = CompressedData<Unit>;
|
||||
|
||||
public:
|
||||
using Base::Base;
|
||||
};
|
||||
|
||||
// Source that can be retrieved using the registered source hook. |Unit|
|
||||
// records the source type so that source-text coordinates in functions and
|
||||
// scripts that depend on this |ScriptSource| are correct.
|
||||
@ -588,17 +609,27 @@ class ScriptSource {
|
||||
};
|
||||
|
||||
using SourceType =
|
||||
mozilla::Variant<Compressed<mozilla::Utf8Unit>,
|
||||
Uncompressed<mozilla::Utf8Unit>, Compressed<char16_t>,
|
||||
Uncompressed<char16_t>, Retrievable<mozilla::Utf8Unit>,
|
||||
Retrievable<char16_t>, Missing, BinAST>;
|
||||
mozilla::Variant<Compressed<mozilla::Utf8Unit, SourceRetrievable::Yes>,
|
||||
Uncompressed<mozilla::Utf8Unit, SourceRetrievable::Yes>,
|
||||
Compressed<mozilla::Utf8Unit, SourceRetrievable::No>,
|
||||
Uncompressed<mozilla::Utf8Unit, SourceRetrievable::No>,
|
||||
Compressed<char16_t, SourceRetrievable::Yes>,
|
||||
Uncompressed<char16_t, SourceRetrievable::Yes>,
|
||||
Compressed<char16_t, SourceRetrievable::No>,
|
||||
Uncompressed<char16_t, SourceRetrievable::No>,
|
||||
Retrievable<mozilla::Utf8Unit>, Retrievable<char16_t>,
|
||||
Missing, BinAST>;
|
||||
SourceType data;
|
||||
|
||||
// If the GC attempts to call convertToCompressedSource with PinnedUnits
|
||||
// present, the first PinnedUnits (that is, bottom of the stack) will set
|
||||
// the compressed chars upon destruction.
|
||||
// If the GC calls triggerConvertToCompressedSource with PinnedUnits present,
|
||||
// the first PinnedUnits (that is, bottom of the stack) will install the
|
||||
// compressed chars upon destruction.
|
||||
//
|
||||
// Retrievability isn't part of the type here because uncompressed->compressed
|
||||
// transitions must preserve existing retrievability.
|
||||
PinnedUnitsBase* pinnedUnitsStack_;
|
||||
mozilla::MaybeOneOf<Compressed<mozilla::Utf8Unit>, Compressed<char16_t>>
|
||||
mozilla::MaybeOneOf<CompressedData<mozilla::Utf8Unit>,
|
||||
CompressedData<char16_t>>
|
||||
pendingCompressed_;
|
||||
|
||||
// The filename of this script.
|
||||
@ -671,17 +702,6 @@ class ScriptSource {
|
||||
mozilla::recordreplay::Behavior::DontPreserve>
|
||||
idCount_;
|
||||
|
||||
// If this field is true, we can call JSRuntime::sourceHook to load the source
|
||||
// on demand. Thus if this contains compressed/uncompressed data, we don't
|
||||
// have to preserve it while we're not using it, because we can use the source
|
||||
// hook to load it when we need it.
|
||||
//
|
||||
// This field is always true for retrievable source. It *may* be true for
|
||||
// compressed/uncompressed source (if retrievable source was rewritten to
|
||||
// compressed/uncompressed source loaded using the source hook). It is always
|
||||
// false for missing or BinAST source.
|
||||
bool sourceRetrievable_ : 1;
|
||||
|
||||
bool hasIntroductionOffset_ : 1;
|
||||
bool containsAsmJS_ : 1;
|
||||
|
||||
@ -693,15 +713,12 @@ class ScriptSource {
|
||||
// Return a string containing the chars starting at |begin| and ending at
|
||||
// |begin + len|.
|
||||
//
|
||||
// Warning: this is *not* GC-safe! Any chars to be handed out should use
|
||||
// Warning: this is *not* GC-safe! Any chars to be handed out must use
|
||||
// PinnedUnits. See comment below.
|
||||
template <typename Unit>
|
||||
const Unit* units(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& asp,
|
||||
size_t begin, size_t len);
|
||||
|
||||
template <typename Unit>
|
||||
void movePendingCompressedSource();
|
||||
|
||||
public:
|
||||
// When creating a JSString* from TwoByte source characters, we don't try to
|
||||
// to deflate to Latin1 for longer strings, because this can be slow.
|
||||
@ -721,7 +738,6 @@ class ScriptSource {
|
||||
introductionType_(nullptr),
|
||||
xdrEncoder_(nullptr),
|
||||
id_(++idCount_),
|
||||
sourceRetrievable_(false),
|
||||
hasIntroductionOffset_(false),
|
||||
containsAsmJS_(false) {}
|
||||
|
||||
@ -760,7 +776,6 @@ class ScriptSource {
|
||||
const JS::ReadOnlyCompileOptions& options,
|
||||
JS::SourceText<Unit>& srcBuf);
|
||||
|
||||
bool sourceRetrievable() const { return sourceRetrievable_; }
|
||||
bool hasSourceText() const {
|
||||
return hasUncompressedSource() || hasCompressedSource();
|
||||
}
|
||||
@ -778,10 +793,14 @@ class ScriptSource {
|
||||
private:
|
||||
template <typename Unit>
|
||||
struct UncompressedDataMatcher {
|
||||
const Unit* operator()(const Uncompressed<Unit>& u) { return u.units(); }
|
||||
template <SourceRetrievable CanRetrieve>
|
||||
const UncompressedData<Unit>* operator()(
|
||||
const Uncompressed<Unit, CanRetrieve>& u) {
|
||||
return &u;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const Unit* operator()(const T&) {
|
||||
const UncompressedData<Unit>* operator()(const T&) {
|
||||
MOZ_CRASH(
|
||||
"attempting to access uncompressed data in a ScriptSource not "
|
||||
"containing it");
|
||||
@ -791,19 +810,21 @@ class ScriptSource {
|
||||
|
||||
public:
|
||||
template <typename Unit>
|
||||
const Unit* uncompressedData() {
|
||||
const UncompressedData<Unit>* uncompressedData() {
|
||||
return data.match(UncompressedDataMatcher<Unit>());
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Unit>
|
||||
struct CompressedDataMatcher {
|
||||
char* operator()(const Compressed<Unit>& c) {
|
||||
return const_cast<char*>(c.raw.chars());
|
||||
template <SourceRetrievable CanRetrieve>
|
||||
const CompressedData<Unit>* operator()(
|
||||
const Compressed<Unit, CanRetrieve>& c) {
|
||||
return &c;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
char* operator()(const T&) {
|
||||
const CompressedData<Unit>* operator()(const T&) {
|
||||
MOZ_CRASH(
|
||||
"attempting to access compressed data in a ScriptSource not "
|
||||
"containing it");
|
||||
@ -813,7 +834,7 @@ class ScriptSource {
|
||||
|
||||
public:
|
||||
template <typename Unit>
|
||||
char* compressedData() {
|
||||
const CompressedData<Unit>* compressedData() {
|
||||
return data.match(CompressedDataMatcher<Unit>());
|
||||
}
|
||||
|
||||
@ -837,13 +858,13 @@ class ScriptSource {
|
||||
|
||||
private:
|
||||
struct HasUncompressedSource {
|
||||
template <typename Unit>
|
||||
bool operator()(const Uncompressed<Unit>&) {
|
||||
template <typename Unit, SourceRetrievable CanRetrieve>
|
||||
bool operator()(const Uncompressed<Unit, CanRetrieve>&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Unit>
|
||||
bool operator()(const Compressed<Unit>&) {
|
||||
template <typename Unit, SourceRetrievable CanRetrieve>
|
||||
bool operator()(const Compressed<Unit, CanRetrieve>&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -862,53 +883,84 @@ class ScriptSource {
|
||||
return data.match(HasUncompressedSource());
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Unit>
|
||||
bool uncompressedSourceIs() const {
|
||||
MOZ_ASSERT(hasUncompressedSource());
|
||||
return data.is<Uncompressed<Unit>>();
|
||||
struct IsUncompressed {
|
||||
template <SourceRetrievable CanRetrieve>
|
||||
bool operator()(const Uncompressed<Unit, CanRetrieve>&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator()(const T&) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
template <typename Unit>
|
||||
bool isUncompressed() const {
|
||||
return data.match(IsUncompressed<Unit>());
|
||||
}
|
||||
|
||||
private:
|
||||
struct HasCompressedSource {
|
||||
template <typename Unit>
|
||||
bool operator()(const Compressed<Unit>&) {
|
||||
template <typename Unit, SourceRetrievable CanRetrieve>
|
||||
bool operator()(const Compressed<Unit, CanRetrieve>&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Unit>
|
||||
bool operator()(const Uncompressed<Unit>&) {
|
||||
template <typename T>
|
||||
bool operator()(const T&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Unit>
|
||||
bool operator()(const Retrievable<Unit>&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(const BinAST&) { return false; }
|
||||
|
||||
bool operator()(const Missing&) { return false; }
|
||||
};
|
||||
|
||||
public:
|
||||
bool hasCompressedSource() const { return data.match(HasCompressedSource()); }
|
||||
|
||||
private:
|
||||
template <typename Unit>
|
||||
bool compressedSourceIs() const {
|
||||
MOZ_ASSERT(hasCompressedSource());
|
||||
return data.is<Compressed<Unit>>();
|
||||
struct IsCompressed {
|
||||
template <SourceRetrievable CanRetrieve>
|
||||
bool operator()(const Compressed<Unit, CanRetrieve>&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator()(const T&) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
template <typename Unit>
|
||||
bool isCompressed() const {
|
||||
return data.match(IsCompressed<Unit>());
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Unit>
|
||||
struct SourceTypeMatcher {
|
||||
template <template <typename C> class Data>
|
||||
bool operator()(const Data<Unit>&) {
|
||||
template <template <typename C, SourceRetrievable R> class Data,
|
||||
SourceRetrievable CanRetrieve>
|
||||
bool operator()(const Data<Unit, CanRetrieve>&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <template <typename C> class Data, typename NotUnit>
|
||||
bool operator()(const Data<NotUnit>&) {
|
||||
template <template <typename C, SourceRetrievable R> class Data,
|
||||
typename NotUnit, SourceRetrievable CanRetrieve>
|
||||
bool operator()(const Data<NotUnit, CanRetrieve>&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator()(const Retrievable<Unit>&) {
|
||||
MOZ_CRASH("source type only applies where actual text is available");
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename NotUnit>
|
||||
bool operator()(const Retrievable<NotUnit>&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -931,13 +983,13 @@ class ScriptSource {
|
||||
|
||||
private:
|
||||
struct UncompressedLengthMatcher {
|
||||
template <typename Unit>
|
||||
size_t operator()(const Uncompressed<Unit>& u) {
|
||||
template <typename Unit, SourceRetrievable CanRetrieve>
|
||||
size_t operator()(const Uncompressed<Unit, CanRetrieve>& u) {
|
||||
return u.length();
|
||||
}
|
||||
|
||||
template <typename Unit>
|
||||
size_t operator()(const Compressed<Unit>& u) {
|
||||
template <typename Unit, SourceRetrievable CanRetrieve>
|
||||
size_t operator()(const Compressed<Unit, CanRetrieve>& u) {
|
||||
return u.uncompressedLength;
|
||||
}
|
||||
|
||||
@ -982,14 +1034,14 @@ class ScriptSource {
|
||||
template <typename Unit>
|
||||
MOZ_MUST_USE bool setUncompressedSourceHelper(JSContext* cx,
|
||||
EntryUnits<Unit>&& source,
|
||||
size_t length);
|
||||
size_t length,
|
||||
SourceRetrievable retrievable);
|
||||
|
||||
public:
|
||||
// Initialize a fresh |ScriptSource| with uncompressed source.
|
||||
// Initialize a fresh |ScriptSource| with unretrievable, uncompressed source.
|
||||
template <typename Unit>
|
||||
MOZ_MUST_USE bool initializeUncompressedSource(JSContext* cx,
|
||||
EntryUnits<Unit>&& source,
|
||||
size_t length);
|
||||
MOZ_MUST_USE bool initializeUnretrievableUncompressedSource(
|
||||
JSContext* cx, EntryUnits<Unit>&& source, size_t length);
|
||||
|
||||
// Set the retrieved source for a |ScriptSource| whose source was recorded as
|
||||
// missing but retrievable.
|
||||
@ -999,20 +1051,24 @@ class ScriptSource {
|
||||
|
||||
MOZ_MUST_USE bool tryCompressOffThread(JSContext* cx);
|
||||
|
||||
// Convert this ScriptSource from storing uncompressed source of the given
|
||||
// type, to storing compressed source. (Raw compressed source is always
|
||||
// single-byte; |Unit| just records the encoding of the uncompressed source.)
|
||||
// *Trigger* the conversion of this ScriptSource from containing uncompressed
|
||||
// |Unit|-encoded source to containing compressed source. Conversion may not
|
||||
// be complete when this function returns: it'll be delayed if there's ongoing
|
||||
// use of the uncompressed source via |PinnedUnits|, in which case conversion
|
||||
// won't occur until the outermost |PinnedUnits| is destroyed.
|
||||
//
|
||||
// Compressed source is in bytes, no matter that |Unit| might be |char16_t|.
|
||||
// |sourceLength| is the length in code units (not bytes) of the uncompressed
|
||||
// source.
|
||||
template <typename Unit>
|
||||
void convertToCompressedSource(SharedImmutableString compressed,
|
||||
size_t sourceLength);
|
||||
void triggerConvertToCompressedSource(SharedImmutableString compressed,
|
||||
size_t sourceLength);
|
||||
|
||||
// Initialize a fresh ScriptSource as containing compressed source of the
|
||||
// indicated original encoding.
|
||||
// Initialize a fresh ScriptSource as containing unretrievable compressed
|
||||
// source of the indicated original encoding.
|
||||
template <typename Unit>
|
||||
MOZ_MUST_USE bool initializeWithCompressedSource(JSContext* cx,
|
||||
UniqueChars&& raw,
|
||||
size_t rawLength,
|
||||
size_t sourceLength);
|
||||
MOZ_MUST_USE bool initializeWithUnretrievableCompressedSource(
|
||||
JSContext* cx, UniqueChars&& raw, size_t rawLength, size_t sourceLength);
|
||||
|
||||
#if defined(JS_BUILD_BINAST)
|
||||
|
||||
@ -1031,22 +1087,22 @@ class ScriptSource {
|
||||
private:
|
||||
void performTaskWork(SourceCompressionTask* task);
|
||||
|
||||
struct ConvertToCompressedSourceFromTask {
|
||||
struct TriggerConvertToCompressedSourceFromTask {
|
||||
ScriptSource* const source_;
|
||||
SharedImmutableString& compressed_;
|
||||
|
||||
ConvertToCompressedSourceFromTask(ScriptSource* source,
|
||||
SharedImmutableString& compressed)
|
||||
TriggerConvertToCompressedSourceFromTask(ScriptSource* source,
|
||||
SharedImmutableString& compressed)
|
||||
: source_(source), compressed_(compressed) {}
|
||||
|
||||
template <typename Unit>
|
||||
void operator()(const Uncompressed<Unit>&) {
|
||||
source_->convertToCompressedSource<Unit>(std::move(compressed_),
|
||||
source_->length());
|
||||
template <typename Unit, SourceRetrievable CanRetrieve>
|
||||
void operator()(const Uncompressed<Unit, CanRetrieve>&) {
|
||||
source_->triggerConvertToCompressedSource<Unit>(std::move(compressed_),
|
||||
source_->length());
|
||||
}
|
||||
|
||||
template <typename Unit>
|
||||
void operator()(const Compressed<Unit>&) {
|
||||
template <typename Unit, SourceRetrievable CanRetrieve>
|
||||
void operator()(const Compressed<Unit, CanRetrieve>&) {
|
||||
MOZ_CRASH(
|
||||
"can't set compressed source when source is already compressed -- "
|
||||
"ScriptSource::tryCompressOffThread shouldn't have queued up this "
|
||||
@ -1070,7 +1126,15 @@ class ScriptSource {
|
||||
}
|
||||
};
|
||||
|
||||
void convertToCompressedSourceFromTask(SharedImmutableString compressed);
|
||||
template <typename Unit>
|
||||
void convertToCompressedSource(SharedImmutableString compressed,
|
||||
size_t uncompressedLength);
|
||||
|
||||
template <typename Unit>
|
||||
void performDelayedConvertToCompressedSource();
|
||||
|
||||
void triggerConvertToCompressedSourceFromTask(
|
||||
SharedImmutableString compressed);
|
||||
|
||||
private:
|
||||
// It'd be better to make this function take <XDRMode, Unit>, as both
|
||||
@ -1079,9 +1143,8 @@ class ScriptSource {
|
||||
// we'd need template function partial specialization to hold XDRMode
|
||||
// constant while varying Unit, so that idea's no dice.
|
||||
template <XDRMode mode>
|
||||
MOZ_MUST_USE XDRResult xdrUncompressedSource(XDRState<mode>* xdr,
|
||||
uint8_t sourceCharSize,
|
||||
uint32_t uncompressedLength);
|
||||
MOZ_MUST_USE XDRResult xdrUnretrievableUncompressedSource(
|
||||
XDRState<mode>* xdr, uint8_t sourceCharSize, uint32_t uncompressedLength);
|
||||
|
||||
public:
|
||||
MOZ_MUST_USE bool setFilename(JSContext* cx, const char* filename);
|
||||
@ -1162,15 +1225,18 @@ class ScriptSource {
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Unit,
|
||||
template <typename U, SourceRetrievable CanRetrieve> class Data,
|
||||
XDRMode mode>
|
||||
static void codeRetrievable(ScriptSource* ss);
|
||||
|
||||
template <typename Unit, XDRMode mode>
|
||||
static MOZ_MUST_USE XDRResult codeUncompressedData(XDRState<mode>* const xdr,
|
||||
ScriptSource* const ss,
|
||||
bool retrievable);
|
||||
ScriptSource* const ss);
|
||||
|
||||
template <typename Unit, XDRMode mode>
|
||||
static MOZ_MUST_USE XDRResult codeCompressedData(XDRState<mode>* const xdr,
|
||||
ScriptSource* const ss,
|
||||
bool retrievable);
|
||||
ScriptSource* const ss);
|
||||
|
||||
template <XDRMode mode>
|
||||
static MOZ_MUST_USE XDRResult codeBinASTData(XDRState<mode>* const xdr,
|
||||
|
@ -120,43 +120,20 @@ class nsPrintJob final : public nsIObserver,
|
||||
nsresult GetDocumentName(nsAString& aDocName);
|
||||
already_AddRefed<nsIPrintSettings> GetCurrentPrintSettings();
|
||||
|
||||
// The setters here also update the DocViewer
|
||||
void SetIsPrinting(bool aIsPrinting);
|
||||
bool GetIsPrinting() { return mIsDoingPrinting; }
|
||||
void SetIsPrintPreview(bool aIsPrintPreview);
|
||||
bool GetIsPrintPreview() { return mIsDoingPrintPreview; }
|
||||
bool GetIsCreatingPrintPreview() { return mIsCreatingPrintPreview; }
|
||||
|
||||
// This enum tells indicates what the default should be for the title
|
||||
// if the title from the document is null
|
||||
enum eDocTitleDefault { eDocTitleDefBlank, eDocTitleDefURLDoc };
|
||||
|
||||
void Destroy();
|
||||
void DestroyPrintingData();
|
||||
|
||||
nsresult GetSeqFrameAndCountPages(nsIFrame*& aSeqFrame, int32_t& aCount);
|
||||
|
||||
//
|
||||
// The following three methods are used for printing...
|
||||
//
|
||||
nsresult DocumentReadyForPrinting();
|
||||
nsresult GetSelectionDocument(nsIDeviceContextSpec* aDevSpec,
|
||||
mozilla::dom::Document** aNewDoc);
|
||||
|
||||
nsresult SetupToPrintContent();
|
||||
nsresult EnablePOsForPrinting();
|
||||
nsPrintObject* FindSmallestSTF();
|
||||
|
||||
bool PrintDocContent(const mozilla::UniquePtr<nsPrintObject>& aPO,
|
||||
nsresult& aStatus);
|
||||
nsresult DoPrint(const mozilla::UniquePtr<nsPrintObject>& aPO);
|
||||
|
||||
void SetPrintPO(nsPrintObject* aPO, bool aPrint);
|
||||
|
||||
void TurnScriptingOn(bool aDoTurnOn);
|
||||
bool CheckDocumentForPPCaching();
|
||||
|
||||
/**
|
||||
* Filters out certain user events while Print Preview is open to prevent
|
||||
* the user from interacting with the Print Preview document and breaking
|
||||
* printing invariants.
|
||||
*/
|
||||
void SuppressPrintPreviewUserEvents();
|
||||
|
||||
// nsIDocumentViewerPrint Printing Methods:
|
||||
|
||||
/**
|
||||
* Checks to see if the document this print engine is associated with has any
|
||||
@ -168,7 +145,48 @@ class nsPrintJob final : public nsIObserver,
|
||||
bool PrintPage(nsPrintObject* aPOect, bool& aInRange);
|
||||
bool DonePrintingPages(nsPrintObject* aPO, nsresult aResult);
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
nsresult CleanupOnFailure(nsresult aResult, bool aIsPrinting);
|
||||
// If FinishPrintPreview() fails, caller may need to reset the state of the
|
||||
// object, for example by calling CleanupOnFailure().
|
||||
nsresult FinishPrintPreview();
|
||||
void FirePrintingErrorEvent(nsresult aPrintError);
|
||||
bool CheckBeforeDestroy() const { return mPrt && mPrt->mPreparingForPrint; }
|
||||
|
||||
mozilla::PresShell* GetPrintPreviewPresShell() {
|
||||
return mPrtPreview->mPrintObject->mPresShell;
|
||||
}
|
||||
|
||||
float GetPrintPreviewScale() {
|
||||
return mPrtPreview->mPrintObject->mPresContext->GetPrintPreviewScale();
|
||||
}
|
||||
|
||||
nsresult Cancel();
|
||||
void Destroy();
|
||||
void DestroyPrintingData();
|
||||
|
||||
private:
|
||||
nsPrintJob& operator=(const nsPrintJob& aOther) = delete;
|
||||
|
||||
~nsPrintJob();
|
||||
|
||||
nsresult DocumentReadyForPrinting();
|
||||
nsresult SetupToPrintContent();
|
||||
nsresult EnablePOsForPrinting();
|
||||
nsPrintObject* FindSmallestSTF();
|
||||
|
||||
bool PrintDocContent(const mozilla::UniquePtr<nsPrintObject>& aPO,
|
||||
nsresult& aStatus);
|
||||
nsresult DoPrint(const mozilla::UniquePtr<nsPrintObject>& aPO);
|
||||
|
||||
void SetPrintPO(nsPrintObject* aPO, bool aPrint);
|
||||
|
||||
/**
|
||||
* Filters out certain user events while Print Preview is open to prevent
|
||||
* the user from interacting with the Print Preview document and breaking
|
||||
* printing invariants.
|
||||
*/
|
||||
void SuppressPrintPreviewUserEvents();
|
||||
|
||||
nsresult ReflowDocList(const mozilla::UniquePtr<nsPrintObject>& aPO,
|
||||
bool aSetPixelScale);
|
||||
|
||||
@ -177,10 +195,6 @@ class nsPrintJob final : public nsIObserver,
|
||||
|
||||
void CalcNumPrintablePages(int32_t& aNumPages);
|
||||
void ShowPrintProgress(bool aIsForPrinting, bool& aDoNotify);
|
||||
nsresult CleanupOnFailure(nsresult aResult, bool aIsPrinting);
|
||||
// If FinishPrintPreview() fails, caller may need to reset the state of the
|
||||
// object, for example by calling CleanupOnFailure().
|
||||
nsresult FinishPrintPreview();
|
||||
void SetURLAndTitleOnProgressParams(
|
||||
const mozilla::UniquePtr<nsPrintObject>& aPO,
|
||||
nsIPrintProgressParams* aParams);
|
||||
@ -188,10 +202,6 @@ class nsPrintJob final : public nsIObserver,
|
||||
|
||||
bool IsThereARangeSelection(nsPIDOMWindowOuter* aDOMWin);
|
||||
|
||||
void FirePrintingErrorEvent(nsresult aPrintError);
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
// Timer Methods
|
||||
nsresult StartPagePrintTimer(const mozilla::UniquePtr<nsPrintObject>& aPO);
|
||||
|
||||
bool IsWindowsInOurSubTree(nsPIDOMWindowOuter* aDOMWindow) const;
|
||||
@ -206,30 +216,6 @@ class nsPrintJob final : public nsIObserver,
|
||||
nsAString& aTitle, nsAString& aURLStr,
|
||||
eDocTitleDefault aDefType);
|
||||
|
||||
bool CheckBeforeDestroy() const { return mPrt && mPrt->mPreparingForPrint; }
|
||||
|
||||
nsresult Cancel();
|
||||
|
||||
mozilla::PresShell* GetPrintPreviewPresShell() {
|
||||
return mPrtPreview->mPrintObject->mPresShell;
|
||||
}
|
||||
|
||||
float GetPrintPreviewScale() {
|
||||
return mPrtPreview->mPrintObject->mPresContext->GetPrintPreviewScale();
|
||||
}
|
||||
|
||||
// These calls also update the DocViewer
|
||||
void SetIsPrinting(bool aIsPrinting);
|
||||
bool GetIsPrinting() { return mIsDoingPrinting; }
|
||||
void SetIsPrintPreview(bool aIsPrintPreview);
|
||||
bool GetIsPrintPreview() { return mIsDoingPrintPreview; }
|
||||
bool GetIsCreatingPrintPreview() { return mIsCreatingPrintPreview; }
|
||||
|
||||
private:
|
||||
nsPrintJob& operator=(const nsPrintJob& aOther) = delete;
|
||||
|
||||
~nsPrintJob();
|
||||
|
||||
nsresult CommonPrint(bool aIsPrintPreview, nsIPrintSettings* aPrintSettings,
|
||||
nsIWebProgressListener* aWebProgressListener,
|
||||
mozilla::dom::Document* aSourceDoc);
|
||||
|
@ -600,7 +600,7 @@ class MozBrowser extends MozElements.MozElementMixin(XULFrameElement) {
|
||||
|
||||
set characterSet(val) {
|
||||
if (this.isRemoteBrowser) {
|
||||
this.messageManager.sendAsyncMessage("UpdateCharacterSet", { value: val });
|
||||
this.sendMessageToActor("UpdateCharacterSet", { value: val }, "BrowserTab");
|
||||
this._characterSet = val;
|
||||
} else {
|
||||
this.docShell.charset = val;
|
||||
@ -1862,6 +1862,41 @@ class MozBrowser extends MozElements.MozElementMixin(XULFrameElement) {
|
||||
this.docShell.getContentBlockingLog() :
|
||||
Promise.reject("docshell isn't available");
|
||||
}
|
||||
|
||||
// Send an asynchronous message to the remote child via an actor.
|
||||
// Note: use this only for messages through an actor. For old-style
|
||||
// messages, use the message manager. If 'all' is true, then send
|
||||
// a message to all descendant processes.
|
||||
sendMessageToActor(messageName, args, actorName, all) {
|
||||
if (!this.frameLoader) {
|
||||
return;
|
||||
}
|
||||
|
||||
let windowGlobal = this.browsingContext.currentWindowGlobal;
|
||||
if (!windowGlobal) {
|
||||
// Workaround for bug 1523638 where about:blank is loaded in a tab.
|
||||
if (messageName == "Browser:AppTab") {
|
||||
setTimeout(() => { this.sendMessageToActor(messageName, args, actorName); }, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function sendToChildren(browsingContext, checkRoot) {
|
||||
let windowGlobal = browsingContext.currentWindowGlobal;
|
||||
if (windowGlobal && (!checkRoot || windowGlobal.isProcessRoot)) {
|
||||
windowGlobal.getActor(actorName).sendAsyncMessage(messageName, args);
|
||||
}
|
||||
|
||||
if (all) {
|
||||
let contexts = browsingContext.getChildren();
|
||||
for (let context of contexts) {
|
||||
sendToChildren(context, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendToChildren(this.browsingContext, false);
|
||||
}
|
||||
}
|
||||
|
||||
MozXULElement.implementCustomInterface(MozBrowser, [Ci.nsIBrowser]);
|
||||
|
@ -178,15 +178,15 @@ class NS_NO_VTABLE nsCycleCollectionParticipant {
|
||||
// outgoing edges at all in the cycle collection graph or they know for sure
|
||||
// they're alive _and_ none of their outgoing edges are to gray (in the GC
|
||||
// sense) gcthings. See also nsWrapperCache::HasNothingToTrace and
|
||||
// nsWrapperCache::IsBlackAndDoesNotNeedTracing. The restriction on not
|
||||
// having outgoing edges to gray gcthings is because if we _do_ have them that
|
||||
// means we have a "strong" edge to a JS thing and since we're alive we need
|
||||
// to trace through it and mark keep them alive. Outgoing edges to C++ things
|
||||
// don't matter here, because the criteria for when a CC participant is
|
||||
// considered alive are slightly different for JS and C++ things: JS things
|
||||
// are only considered alive when reachable via an edge from a live thing,
|
||||
// while C++ things are also considered alive when their refcount exceeds the
|
||||
// number of edges via which they are reachable.
|
||||
// nsWrapperCache::HasKnownLiveWrapperAndDoesNotNeedTracing. The restriction
|
||||
// on not having outgoing edges to gray gcthings is because if we _do_ have
|
||||
// them that means we have a "strong" edge to a JS thing and since we're alive
|
||||
// we need to trace through it and mark keep them alive. Outgoing edges to
|
||||
// C++ things don't matter here, because the criteria for when a CC
|
||||
// participant is considered alive are slightly different for JS and C++
|
||||
// things: JS things are only considered alive when reachable via an edge from
|
||||
// a live thing, while C++ things are also considered alive when their
|
||||
// refcount exceeds the number of edges via which they are reachable.
|
||||
bool CanSkipInCC(void* aPtr) {
|
||||
return mMightSkip ? CanSkipInCCReal(aPtr) : false;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user