mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-28 03:18:41 +00:00
Merge mozilla-central to b2g-inbound
This commit is contained in:
commit
994cd0cce2
@ -234,9 +234,6 @@ this.AccessFu = { // jshint ignore:line
|
||||
case 'AccessFu:Input':
|
||||
this.Input.setEditState(aMessage.json);
|
||||
break;
|
||||
case 'AccessFu:ActivateContextMenu':
|
||||
this.Input.activateContextMenu(aMessage.json);
|
||||
break;
|
||||
case 'AccessFu:DoScroll':
|
||||
this.Input.doScroll(aMessage.json);
|
||||
break;
|
||||
@ -283,7 +280,6 @@ this.AccessFu = { // jshint ignore:line
|
||||
aMessageManager.addMessageListener('AccessFu:Present', this);
|
||||
aMessageManager.addMessageListener('AccessFu:Input', this);
|
||||
aMessageManager.addMessageListener('AccessFu:Ready', this);
|
||||
aMessageManager.addMessageListener('AccessFu:ActivateContextMenu', this);
|
||||
aMessageManager.addMessageListener('AccessFu:DoScroll', this);
|
||||
},
|
||||
|
||||
@ -291,7 +287,6 @@ this.AccessFu = { // jshint ignore:line
|
||||
aMessageManager.removeMessageListener('AccessFu:Present', this);
|
||||
aMessageManager.removeMessageListener('AccessFu:Input', this);
|
||||
aMessageManager.removeMessageListener('AccessFu:Ready', this);
|
||||
aMessageManager.removeMessageListener('AccessFu:ActivateContextMenu', this);
|
||||
aMessageManager.removeMessageListener('AccessFu:DoScroll', this);
|
||||
},
|
||||
|
||||
@ -673,9 +668,6 @@ var Input = {
|
||||
case 'doubletap1':
|
||||
this.activateCurrent();
|
||||
break;
|
||||
case 'taphold1':
|
||||
this.sendContextMenuMessage();
|
||||
break;
|
||||
case 'doubletaphold1':
|
||||
Utils.dispatchChromeEvent('accessibility-control', 'quicknav-menu');
|
||||
break;
|
||||
@ -883,15 +875,6 @@ var Input = {
|
||||
mm.sendAsyncMessage('AccessFu:ContextMenu', {});
|
||||
},
|
||||
|
||||
activateContextMenu: function activateContextMenu(aDetails) {
|
||||
if (Utils.MozBuildApp === 'mobile/android') {
|
||||
let p = AccessFu.adjustContentBounds(aDetails.bounds,
|
||||
Utils.CurrentBrowser, true).center();
|
||||
Services.obs.notifyObservers(null, 'Gesture:LongPress',
|
||||
JSON.stringify({x: p.x, y: p.y}));
|
||||
}
|
||||
},
|
||||
|
||||
setEditState: function setEditState(aEditState) {
|
||||
Logger.debug(() => { return ['setEditState', JSON.stringify(aEditState)] });
|
||||
this.editState = aEditState;
|
||||
|
@ -69,8 +69,13 @@ function forwardToChild(aMessage, aListener, aVCPosition) {
|
||||
function activateContextMenu(aMessage) {
|
||||
let position = Utils.getVirtualCursor(content.document).position;
|
||||
if (!forwardToChild(aMessage, activateContextMenu, position)) {
|
||||
sendAsyncMessage('AccessFu:ActivateContextMenu',
|
||||
{ bounds: Utils.getBounds(position, true) });
|
||||
let center = Utils.getBounds(position, true).center();
|
||||
|
||||
let evt = content.document.createEvent('HTMLEvents');
|
||||
evt.initEvent('contextmenu', true, true);
|
||||
evt.clientX = center.x;
|
||||
evt.clientY = center.y;
|
||||
position.DOMNode.dispatchEvent(evt);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -509,8 +509,8 @@ SocialShare = {
|
||||
iframe.setAttribute("disableglobalhistory", "true");
|
||||
iframe.setAttribute("flex", "1");
|
||||
panel.appendChild(iframe);
|
||||
iframe.addEventListener("DOMContentLoaded", function _firstload() {
|
||||
iframe.removeEventListener("DOMContentLoaded", _firstload, true);
|
||||
iframe.addEventListener("load", function _firstload() {
|
||||
iframe.removeEventListener("load", _firstload, true);
|
||||
iframe.messageManager.loadFrameScript("chrome://browser/content/content.js", true);
|
||||
}, true);
|
||||
this.populateProviderMenu();
|
||||
|
@ -6682,11 +6682,11 @@ var gIdentityHandler = {
|
||||
this._encryptionLabel[this.IDENTITY_MODE_UNKNOWN] =
|
||||
gNavigatorBundle.getString("identity.unencrypted");
|
||||
this._encryptionLabel[this.IDENTITY_MODE_MIXED_DISPLAY_LOADED] =
|
||||
gNavigatorBundle.getString("identity.mixed_display_loaded");
|
||||
gNavigatorBundle.getString("identity.broken_loaded");
|
||||
this._encryptionLabel[this.IDENTITY_MODE_MIXED_ACTIVE_LOADED] =
|
||||
gNavigatorBundle.getString("identity.mixed_active_loaded2");
|
||||
this._encryptionLabel[this.IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED] =
|
||||
gNavigatorBundle.getString("identity.mixed_display_loaded");
|
||||
gNavigatorBundle.getString("identity.broken_loaded");
|
||||
return this._encryptionLabel;
|
||||
},
|
||||
get _identityPopup () {
|
||||
|
@ -254,7 +254,7 @@ function securityOnLoad() {
|
||||
|
||||
if (info.isBroken) {
|
||||
hdr = pkiBundle.getString("pageInfo_MixedContent");
|
||||
msg1 = pkiBundle.getString("pageInfo_Privacy_Mixed1");
|
||||
msg1 = pkiBundle.getString("pageInfo_Privacy_Broken1");
|
||||
msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
|
||||
}
|
||||
else if (info.encryptionStrength > 0) {
|
||||
|
@ -94,6 +94,15 @@ this.BingTranslator.prototype = {
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets the expiration time of the current token, in order to
|
||||
* force the token manager to ask for a new token during the next request.
|
||||
*/
|
||||
_resetToken : function() {
|
||||
// Force the token manager to get update token
|
||||
BingTokenManager._currentExpiryTime = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Function called when a request sent to the server completed successfully.
|
||||
* This function handles calling the function to parse the result and the
|
||||
@ -115,17 +124,19 @@ this.BingTranslator.prototype = {
|
||||
/**
|
||||
* Function called when a request sent to the server has failed.
|
||||
* This function handles deciding if the error is transient or means the
|
||||
* service is unavailable (zero balance on the key) and calling the
|
||||
* function to resolve the promise returned by the public `translate()`
|
||||
* method when there's no pending request left.
|
||||
* service is unavailable (zero balance on the key or request credentials are
|
||||
* not in an active state) and calling the function to resolve the promise
|
||||
* returned by the public `translate()` method when there's no pending.
|
||||
* request left.
|
||||
*
|
||||
* @param aError [optional] The RESTRequest that failed.
|
||||
*/
|
||||
_chunkFailed: function(aError) {
|
||||
if (aError instanceof RESTRequest &&
|
||||
aError.response.status == 400) {
|
||||
[400, 401].indexOf(aError.response.status) != -1) {
|
||||
let body = aError.response.body;
|
||||
if (body.contains("TranslateApiException") && body.contains("balance"))
|
||||
if (body.contains("TranslateApiException") &&
|
||||
(body.contains("balance") || body.contains("active state")))
|
||||
this._serviceUnavailable = true;
|
||||
}
|
||||
|
||||
|
@ -128,6 +128,13 @@ function checkAuth(req) {
|
||||
let auth = req.getHeader("Authorization");
|
||||
if (!auth.startsWith("Bearer "))
|
||||
throw new HTTPError(401, "Invalid Authorization header content: '" + auth + "'");
|
||||
|
||||
// Rejecting inactive subscriptions.
|
||||
if (auth.contains("inactive")) {
|
||||
const INACTIVE_STATE_RESPONSE = "<html><body><h1>TranslateApiException</h1><p>Method: TranslateArray()</p><p>Message: The Azure Market Place Translator Subscription associated with the request credentials is not in an active state.</p><code></code><p>message id=5641.V2_Rest.TranslateArray.48CC6470</p></body></html>";
|
||||
throw new HTTPError(401, INACTIVE_STATE_RESPONSE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function reallyHandleRequest(req, res) {
|
||||
@ -189,8 +196,17 @@ const methodHandlers = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Defines the tokens for certain client ids.
|
||||
const TOKEN_MAP = {
|
||||
'testInactive' : 'inactive',
|
||||
'testClient' : 'test'
|
||||
};
|
||||
let token = 'test'; // Default token.
|
||||
if((params.client_id in TOKEN_MAP)){
|
||||
token = TOKEN_MAP[params.client_id];
|
||||
}
|
||||
let content = JSON.stringify({
|
||||
access_token: "test",
|
||||
access_token: token,
|
||||
expires_in: 600
|
||||
});
|
||||
|
||||
|
@ -11,46 +11,111 @@ const kClientSecretPref = "browser.translation.bing.apiKeyOverride";
|
||||
|
||||
const {BingTranslator} = Cu.import("resource:///modules/translation/BingTranslator.jsm", {});
|
||||
const {TranslationDocument} = Cu.import("resource:///modules/translation/TranslationDocument.jsm", {});
|
||||
const {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
add_task(function* setup() {
|
||||
Services.prefs.setCharPref(kClientIdPref, "testClient");
|
||||
Services.prefs.setCharPref(kClientSecretPref, "testSecret");
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
Services.prefs.clearUserPref(kClientIdPref);
|
||||
Services.prefs.clearUserPref(kClientSecretPref);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks if the translation is happening.
|
||||
*/
|
||||
add_task(function* test_bing_translation() {
|
||||
|
||||
// Ensure the correct client id is used for authentication.
|
||||
Services.prefs.setCharPref(kClientIdPref, "testClient");
|
||||
|
||||
// Loading the fixture page.
|
||||
let url = constructFixtureURL("bug1022725-fr.html");
|
||||
let tab = yield promiseTestPageLoad(url);
|
||||
|
||||
// Translating the contents of the loaded tab.
|
||||
gBrowser.selectedTab = tab;
|
||||
let browser = tab.linkedBrowser;
|
||||
let client = new BingTranslator(
|
||||
new TranslationDocument(browser.contentDocument), "fr", "en");
|
||||
let result = yield client.translate();
|
||||
|
||||
// XXXmikedeboer; here you would continue the test/ content inspection.
|
||||
Assert.ok(result, "There should be a result.");
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
/**
|
||||
* Ensures that the BingTranslator handles out-of-valid-key response
|
||||
* correctly. Sometimes Bing Translate replies with
|
||||
* "request credentials is not in an active state" error. BingTranslator
|
||||
* should catch this error and classify it as Service Unavailable.
|
||||
*
|
||||
*/
|
||||
add_task(function* test_handling_out_of_valid_key_error() {
|
||||
|
||||
// Simulating request from inactive subscription.
|
||||
Services.prefs.setCharPref(kClientIdPref, "testInactive");
|
||||
|
||||
// Loading the fixture page.
|
||||
let url = constructFixtureURL("bug1022725-fr.html");
|
||||
let tab = yield promiseTestPageLoad(url);
|
||||
|
||||
// Translating the contents of the loaded tab.
|
||||
gBrowser.selectedTab = tab;
|
||||
let browser = tab.linkedBrowser;
|
||||
let client = new BingTranslator(
|
||||
new TranslationDocument(browser.contentDocument), "fr", "en");
|
||||
client._resetToken();
|
||||
try {
|
||||
yield client.translate();
|
||||
} catch (ex) {
|
||||
// It is alright that the translation fails.
|
||||
}
|
||||
client._resetToken();
|
||||
|
||||
// Checking if the client detected service and unavailable.
|
||||
Assert.ok(client._serviceUnavailable, "Service should be detected unavailable.");
|
||||
|
||||
// Cleaning up.
|
||||
Services.prefs.setCharPref(kClientIdPref, "testClient");
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
/**
|
||||
* A helper function for constructing a URL to a page stored in the
|
||||
* local fixture folder.
|
||||
*
|
||||
* @param filename Name of a fixture file.
|
||||
*/
|
||||
function constructFixtureURL(filename){
|
||||
// Deduce the Mochitest server address in use from a pref that was pre-processed.
|
||||
let server = Services.prefs.getCharPref("browser.translation.bing.authURL")
|
||||
.replace("http://", "");
|
||||
server = server.substr(0, server.indexOf("/"));
|
||||
let tab = gBrowser.addTab("http://" + server +
|
||||
"/browser/browser/components/translation/test/fixtures/bug1022725-fr.html");
|
||||
gBrowser.selectedTab = tab;
|
||||
let url = "http://" + server +
|
||||
"/browser/browser/components/translation/test/fixtures/" + filename;
|
||||
return url;
|
||||
}
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
gBrowser.removeTab(tab);
|
||||
Services.prefs.clearUserPref(kClientIdPref);
|
||||
Services.prefs.clearUserPref(kClientSecretPref);
|
||||
});
|
||||
|
||||
let browser = tab.linkedBrowser;
|
||||
browser.addEventListener("load", function onload() {
|
||||
/**
|
||||
* A helper function to open a new tab and wait for its content to load.
|
||||
*
|
||||
* @param String url A URL to be loaded in the new tab.
|
||||
*/
|
||||
function promiseTestPageLoad(url) {
|
||||
let deferred = Promise.defer();
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab(url);
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
browser.addEventListener("load", function listener() {
|
||||
if (browser.currentURI.spec == "about:blank")
|
||||
return;
|
||||
|
||||
browser.removeEventListener("load", onload, true);
|
||||
let client = new BingTranslator(
|
||||
new TranslationDocument(browser.contentDocument), "fr", "en");
|
||||
|
||||
client.translate().then(
|
||||
result => {
|
||||
// XXXmikedeboer; here you would continue the test/ content inspection.
|
||||
ok(result, "There should be a result.");
|
||||
finish();
|
||||
},
|
||||
error => {
|
||||
ok(false, "Unexpected Client Error: " + error);
|
||||
finish();
|
||||
}
|
||||
);
|
||||
info("Page loaded: " + browser.currentURI.spec);
|
||||
browser.removeEventListener("load", listener, true);
|
||||
deferred.resolve(tab);
|
||||
}, true);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
@ -430,33 +430,33 @@ skip-if = e10s
|
||||
[browser_dbg_terminate-on-tab-close.js]
|
||||
skip-if = e10s
|
||||
[browser_dbg_tracing-01.js]
|
||||
skip-if = e10s
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_tracing-02.js]
|
||||
skip-if = e10s
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_tracing-03.js]
|
||||
skip-if = e10s
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_tracing-04.js]
|
||||
skip-if = e10s
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_tracing-05.js]
|
||||
skip-if = e10s
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_tracing-06.js]
|
||||
skip-if = e10s
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_tracing-07.js]
|
||||
skip-if = e10s
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_tracing-08.js]
|
||||
skip-if = e10s
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_variables-view-01.js]
|
||||
skip-if = e10s
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_variables-view-02.js]
|
||||
skip-if = e10s
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_variables-view-03.js]
|
||||
skip-if = e10s
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_variables-view-04.js]
|
||||
skip-if = e10s
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_variables-view-05.js]
|
||||
skip-if = e10s
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_variables-view-06.js]
|
||||
skip-if = e10s
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_variables-view-accessibility.js]
|
||||
skip-if = e10s
|
||||
[browser_dbg_variables-view-data.js]
|
||||
|
@ -7,13 +7,12 @@
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
let gTab, gPanel, gDebugger;
|
||||
|
||||
function test() {
|
||||
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
|
||||
@ -37,9 +36,7 @@ function test() {
|
||||
}
|
||||
|
||||
function clickButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebuggee.document.querySelector("button"),
|
||||
gDebuggee);
|
||||
sendMouseClickToTab(gTab, content.document.querySelector("button"));
|
||||
}
|
||||
|
||||
function testTraceLogs() {
|
||||
@ -103,7 +100,6 @@ function testTraceLogs() {
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
});
|
||||
|
@ -7,13 +7,12 @@
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
let gTab, gPanel, gDebugger;
|
||||
|
||||
function test() {
|
||||
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
|
||||
@ -40,9 +39,7 @@ function test() {
|
||||
}
|
||||
|
||||
function clickButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebuggee.document.querySelector("button"),
|
||||
gDebuggee);
|
||||
sendMouseClickToTab(gTab, content.document.querySelector("button"));
|
||||
}
|
||||
|
||||
function highlightCall() {
|
||||
@ -72,7 +69,6 @@ function testNoneHighlighted() {
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
});
|
||||
|
@ -7,13 +7,12 @@
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
let gTab, gPanel, gDebugger;
|
||||
|
||||
function test() {
|
||||
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
|
||||
@ -48,9 +47,7 @@ function test() {
|
||||
}
|
||||
|
||||
function clickButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebuggee.document.querySelector("button"),
|
||||
gDebuggee);
|
||||
sendMouseClickToTab(gTab, content.document.querySelector("button"));
|
||||
}
|
||||
|
||||
function clickTraceLog() {
|
||||
@ -64,7 +61,6 @@ function testCorrectLine() {
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
});
|
||||
|
@ -7,13 +7,12 @@
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
let gTab, gPanel, gDebugger;
|
||||
|
||||
function test() {
|
||||
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
|
||||
@ -41,9 +40,7 @@ function test() {
|
||||
}
|
||||
|
||||
function clickButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebuggee.document.querySelector("button"),
|
||||
gDebuggee);
|
||||
sendMouseClickToTab(gTab, content.document.querySelector("button"));
|
||||
}
|
||||
|
||||
function clickTraceCall() {
|
||||
@ -78,7 +75,6 @@ function testReturn() {
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
});
|
||||
|
@ -7,14 +7,13 @@
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
let gTab, gPanel, gDebugger;
|
||||
let gTracer, gL10N;
|
||||
|
||||
function test() {
|
||||
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
gTracer = gDebugger.DebuggerView.Tracer;
|
||||
@ -70,14 +69,11 @@ function testNoEmptyText() {
|
||||
}
|
||||
|
||||
function clickButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebuggee.document.querySelector("button"),
|
||||
gDebuggee);
|
||||
sendMouseClickToTab(gTab, content.document.querySelector("button"));
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
gTracer = null;
|
||||
|
@ -8,14 +8,13 @@
|
||||
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
|
||||
const TRACER_PREF = "devtools.debugger.tracer";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
let gTab, gPanel, gDebugger;
|
||||
let gOriginalPref = Services.prefs.getBoolPref(TRACER_PREF);
|
||||
Services.prefs.setBoolPref(TRACER_PREF, false);
|
||||
|
||||
function test() {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
|
||||
@ -32,7 +31,6 @@ function test() {
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
Services.prefs.setBoolPref(TRACER_PREF, gOriginalPref);
|
||||
|
@ -8,13 +8,13 @@
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
|
||||
|
||||
let gTab, gDebuggee, gPanel;
|
||||
let gTab, gPanel;
|
||||
|
||||
function test() {
|
||||
Task.async(function*() {
|
||||
yield pushPref();
|
||||
|
||||
[gTab, gDebuggee, gPanel] = yield initDebugger(TAB_URL);
|
||||
[gTab,, gPanel] = yield initDebugger(TAB_URL);
|
||||
|
||||
yield startTracing(gPanel);
|
||||
yield clickButton();
|
||||
@ -60,9 +60,7 @@ function test() {
|
||||
}
|
||||
|
||||
function clickButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebuggee.document.querySelector("button"),
|
||||
gDebuggee);
|
||||
sendMouseClickToTab(gTab, content.document.querySelector("button"));
|
||||
}
|
||||
|
||||
function pushPref() {
|
||||
@ -80,7 +78,6 @@ function popPref() {
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
});
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
|
||||
|
||||
function test() {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
|
||||
let variables = aPanel.panelWin.DebuggerView.Variables;
|
||||
let testScope = variables.addScope("test");
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
|
||||
|
||||
function test() {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
|
||||
let variables = aPanel.panelWin.DebuggerView.Variables;
|
||||
let testScope = variables.addScope("test");
|
||||
let testVar = testScope.addItem("something");
|
||||
|
@ -9,7 +9,7 @@
|
||||
const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
|
||||
|
||||
function test() {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
|
||||
let variables = aPanel.panelWin.DebuggerView.Variables;
|
||||
let testScope = variables.addScope("test");
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
|
||||
|
||||
function test() {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
|
||||
let variables = aPanel.panelWin.DebuggerView.Variables;
|
||||
let testScope = variables.addScope("test");
|
||||
let testVar = testScope.addItem("something");
|
||||
|
@ -8,7 +8,7 @@
|
||||
const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
|
||||
|
||||
function test() {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
|
||||
let variables = aPanel.panelWin.DebuggerView.Variables;
|
||||
|
||||
let globalScope = variables.addScope("Test-Global");
|
||||
|
@ -8,11 +8,11 @@
|
||||
const TAB_URL = EXAMPLE_URL + "doc_promise.html";
|
||||
|
||||
const test = Task.async(function* () {
|
||||
const [tab, debuggee, panel] = yield initDebugger(TAB_URL);
|
||||
const [tab,, panel] = yield initDebugger(TAB_URL);
|
||||
yield ensureSourceIs(panel, "doc_promise.html", true);
|
||||
|
||||
const scopes = waitForCaretAndScopes(panel, 21);
|
||||
executeSoon(debuggee.doPause);
|
||||
callInTab(tab, "doPause");
|
||||
yield scopes;
|
||||
|
||||
const variables = panel.panelWin.DebuggerView.Variables;
|
||||
|
@ -295,7 +295,7 @@ identity.identified.verified_by_you=You have added a security exception for this
|
||||
identity.identified.state_and_country=%S, %S
|
||||
|
||||
identity.encrypted2=The connection to this website is secure.
|
||||
identity.mixed_display_loaded=The connection to this website is not fully secure because it contains unencrypted elements (such as images).
|
||||
identity.broken_loaded=The connection to this website is not fully secure because it contains unencrypted elements (such as images) or the encryption is not strong enough.
|
||||
identity.mixed_active_loaded2=This website contains interactive content that isn't encrypted (such as scripts). Other people can view your information or modify the website's behavior.
|
||||
identity.unencrypted=Your connection to this website is not encrypted.
|
||||
|
||||
|
@ -36,6 +36,7 @@
|
||||
--tab-selection-box-shadow: 0 2px 0 #d7f1ff inset,
|
||||
0 8px 3px -5px #2b82bf inset,
|
||||
0 -1px 0 rgba(0,0,0,.2) inset;
|
||||
--pinned-tab-glow: radial-gradient(22px at center calc(100% - 2px), rgba(76,158,217,0.9) 13%, rgba(0,0,0,0.4) 16%, transparent 70%);
|
||||
|
||||
/* Toolbar buttons */
|
||||
--toolbarbutton-hover-background: rgba(25,33, 38,.6) linear-gradient(rgba(25,33,38,.6), rgba(25,33,38,.6)) padding-box;
|
||||
@ -97,6 +98,8 @@
|
||||
--tab-selection-box-shadow: 0 2px 0 #d7f1ff inset,
|
||||
0 8px 3px -5px #319BDB inset,
|
||||
0 -1px 0 #2A7CB1 inset;
|
||||
--pinned-tab-glow: radial-gradient(22px at center calc(100% - 2px), rgba(76,158,217,0.9) 13%, transparent 16%);
|
||||
|
||||
|
||||
/* Toolbar buttons */
|
||||
--toolbarbutton-hover-background: #D7D7D8;
|
||||
@ -281,7 +284,7 @@ window:not([chromehidden~="toolbar"]) #urlbar-wrapper {
|
||||
|
||||
.tabbrowser-tab[pinned][titlechanged]:not([selected="true"]) > .tab-stack > .tab-content {
|
||||
/* The -2px in `calc` is the height of `tabToolbarNavbarOverlap` plus a 1px offset from the center */
|
||||
background-image: radial-gradient(22px at center calc(100% - 2px), rgba(76,158,217,0.9) 13%, rgba(0,0,0,0.4) 16%, rgba(29,79,115,0) 70%);
|
||||
background-image: var(--pinned-tab-glow);
|
||||
background-position: center;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
19
configure.in
19
configure.in
@ -3297,24 +3297,6 @@ if test "$ac_cv_cpp_unused_required" = yes ; then
|
||||
fi
|
||||
|
||||
|
||||
dnl Some compilers have trouble comparing a constant reference to a templatized
|
||||
dnl class to zero, and require an explicit operator==() to be defined that takes
|
||||
dnl an int. This test separates the strong from the weak.
|
||||
|
||||
AC_CACHE_CHECK(for trouble comparing to zero near std::operator!=(),
|
||||
ac_cv_trouble_comparing_to_zero,
|
||||
[AC_TRY_COMPILE([#include <algorithm>
|
||||
template <class T> class Foo {};
|
||||
class T2;
|
||||
template <class T> int operator==(const T2*, const T&) { return 0; }
|
||||
template <class T> int operator!=(const T2*, const T&) { return 0; }],
|
||||
[Foo<int> f; return (0 != f);],
|
||||
ac_cv_trouble_comparing_to_zero=no,
|
||||
ac_cv_trouble_comparing_to_zero=yes)])
|
||||
if test "$ac_cv_trouble_comparing_to_zero" = yes ; then
|
||||
AC_DEFINE(HAVE_CPP_TROUBLE_COMPARING_TO_ZERO)
|
||||
fi
|
||||
|
||||
# try harder, when checking for __thread support, see bug 521750 comment #33 and below
|
||||
# We pass MOZ_OPTIMIZE_LDFLAGS to the linker because if dead_strip is
|
||||
# enabled, the linker in xcode 4.1 will crash. Without this it would crash when
|
||||
@ -9039,7 +9021,6 @@ CPP_THROW_NEW
|
||||
HAVE_CPP_AMBIGUITY_RESOLVING_USING
|
||||
HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
|
||||
HAVE_CPP_PARTIAL_SPECIALIZATION
|
||||
HAVE_CPP_TROUBLE_COMPARING_TO_ZERO
|
||||
NEED_CPP_UNUSED_IMPLEMENTATIONS
|
||||
HAVE_GETPAGESIZE
|
||||
HAVE_ICONV
|
||||
|
@ -515,14 +515,6 @@ DragDataProducer::Produce(DataTransfer* aDataTransfer,
|
||||
// grab the href as the url, use alt text as the title of the
|
||||
// area if it's there. the drag data is the image tag and src
|
||||
// attribute.
|
||||
nsCOMPtr<nsIURI> imageURI;
|
||||
image->GetCurrentURI(getter_AddRefs(imageURI));
|
||||
if (imageURI) {
|
||||
nsAutoCString spec;
|
||||
imageURI->GetSpec(spec);
|
||||
CopyUTF8toUTF16(spec, mUrlString);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDOMElement> imageElement(do_QueryInterface(image));
|
||||
// XXXbz Shouldn't we use the "title" attr for title? Using
|
||||
// "alt" seems very wrong....
|
||||
@ -530,13 +522,10 @@ DragDataProducer::Produce(DataTransfer* aDataTransfer,
|
||||
imageElement->GetAttribute(NS_LITERAL_STRING("alt"), mTitleString);
|
||||
}
|
||||
|
||||
if (mTitleString.IsEmpty()) {
|
||||
mTitleString = mUrlString;
|
||||
}
|
||||
|
||||
nsCOMPtr<imgIRequest> imgRequest;
|
||||
mUrlString.Truncate();
|
||||
|
||||
// grab the image data, and its request.
|
||||
nsCOMPtr<imgIRequest> imgRequest;
|
||||
nsCOMPtr<imgIContainer> img =
|
||||
nsContentUtils::GetImageFromContent(image,
|
||||
getter_AddRefs(imgRequest));
|
||||
@ -547,7 +536,7 @@ DragDataProducer::Produce(DataTransfer* aDataTransfer,
|
||||
// Fix the file extension in the URL if necessary
|
||||
if (imgRequest && mimeService) {
|
||||
nsCOMPtr<nsIURI> imgUri;
|
||||
imgRequest->GetURI(getter_AddRefs(imgUri));
|
||||
imgRequest->GetCurrentURI(getter_AddRefs(imgUri));
|
||||
|
||||
nsCOMPtr<nsIURL> imgUrl(do_QueryInterface(imgUri));
|
||||
|
||||
@ -568,6 +557,7 @@ DragDataProducer::Produce(DataTransfer* aDataTransfer,
|
||||
|
||||
// pass out the image source string
|
||||
CopyUTF8toUTF16(spec, mImageSourceString);
|
||||
mUrlString = mImageSourceString;
|
||||
|
||||
bool validExtension;
|
||||
if (extension.IsEmpty() ||
|
||||
@ -602,6 +592,18 @@ DragDataProducer::Produce(DataTransfer* aDataTransfer,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mUrlString.IsEmpty()) {
|
||||
nsCOMPtr<nsIURI> imageURI;
|
||||
image->GetCurrentURI(getter_AddRefs(imageURI));
|
||||
if (imageURI) {
|
||||
nsAutoCString spec;
|
||||
imageURI->GetSpec(spec);
|
||||
CopyUTF8toUTF16(spec, mUrlString);
|
||||
}
|
||||
}
|
||||
if (mTitleString.IsEmpty()) {
|
||||
mTitleString = mUrlString;
|
||||
}
|
||||
|
||||
if (parentLink) {
|
||||
// If we are dragging around an image in an anchor, then we
|
||||
|
@ -6,7 +6,7 @@
|
||||
dupe-manifest =
|
||||
head = xpcshell-head-parent-process.js
|
||||
tail =
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
skip-if = toolkit == 'gonk'
|
||||
support-files =
|
||||
bug1056939.zip
|
||||
GlobalObjectsChild.js
|
||||
@ -21,6 +21,7 @@ support-files =
|
||||
[test_blob_file_backed.js]
|
||||
[test_bug1056939.js]
|
||||
[test_globalObjects_ipc.js]
|
||||
skip-if = toolkit == 'android'
|
||||
[test_invalidate.js]
|
||||
# disabled for the moment.
|
||||
skip-if = true
|
||||
|
@ -1,8 +1,6 @@
|
||||
# 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/.
|
||||
[DEFAULT]
|
||||
skip-if = toolkit == 'gonk'
|
||||
|
||||
[test_add_put.js]
|
||||
[test_add_twice_failure.js]
|
||||
|
@ -469,9 +469,7 @@ child:
|
||||
RealKeyEvent(WidgetKeyboardEvent event, MaybeNativeKeyBinding keyBinding);
|
||||
MouseWheelEvent(WidgetWheelEvent event);
|
||||
RealTouchEvent(WidgetTouchEvent aEvent, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
|
||||
// We use a separate message for touchmove events only to apply
|
||||
// compression to them.
|
||||
RealTouchMoveEvent(WidgetTouchEvent aEvent, ScrollableLayerGuid aGuid, uint64_t aInputBlockId) compress;
|
||||
RealTouchMoveEvent(WidgetTouchEvent aEvent, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
|
||||
|
||||
/**
|
||||
* @see nsIDOMWindowUtils sendKeyEvent.
|
||||
|
@ -1173,6 +1173,7 @@ MediaCache::Update()
|
||||
// Figure out where we should be reading from. It's the first
|
||||
// uncached byte after the current mStreamOffset.
|
||||
int64_t dataOffset = stream->GetCachedDataEndInternal(stream->mStreamOffset);
|
||||
MOZ_ASSERT(dataOffset >= 0);
|
||||
|
||||
// Compute where we'd actually seek to to read at readOffset
|
||||
int64_t desiredOffset = dataOffset;
|
||||
@ -1705,6 +1706,7 @@ MediaCacheStream::NotifyDataStarted(int64_t aOffset)
|
||||
ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
|
||||
NS_WARN_IF_FALSE(aOffset == mChannelOffset,
|
||||
"Server is giving us unexpected offset");
|
||||
MOZ_ASSERT(aOffset >= 0);
|
||||
mChannelOffset = aOffset;
|
||||
if (mStreamLength >= 0) {
|
||||
// If we started reading at a certain offset, then for sure
|
||||
@ -2134,23 +2136,28 @@ MediaCacheStream::Seek(int32_t aWhence, int64_t aOffset)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
int64_t oldOffset = mStreamOffset;
|
||||
int64_t newOffset = mStreamOffset;
|
||||
switch (aWhence) {
|
||||
case PR_SEEK_END:
|
||||
if (mStreamLength < 0)
|
||||
return NS_ERROR_FAILURE;
|
||||
mStreamOffset = mStreamLength + aOffset;
|
||||
newOffset = mStreamLength + aOffset;
|
||||
break;
|
||||
case PR_SEEK_CUR:
|
||||
mStreamOffset += aOffset;
|
||||
newOffset += aOffset;
|
||||
break;
|
||||
case PR_SEEK_SET:
|
||||
mStreamOffset = aOffset;
|
||||
newOffset = aOffset;
|
||||
break;
|
||||
default:
|
||||
NS_ERROR("Unknown whence");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (newOffset < 0)
|
||||
return NS_ERROR_FAILURE;
|
||||
mStreamOffset = newOffset;
|
||||
|
||||
CACHE_LOG(PR_LOG_DEBUG, ("Stream %p Seek to %lld", this, (long long)mStreamOffset));
|
||||
gMediaCache->NoteSeek(this, oldOffset);
|
||||
|
||||
@ -2192,11 +2199,10 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
|
||||
break;
|
||||
}
|
||||
size = std::min(size, bytesRemaining);
|
||||
// Clamp size until 64-bit file size issues (bug 500784) are fixed.
|
||||
// Clamp size until 64-bit file size issues are fixed.
|
||||
size = std::min(size, int64_t(INT32_MAX));
|
||||
}
|
||||
|
||||
int32_t bytes;
|
||||
int32_t cacheBlock = streamBlock < mBlocks.Length() ? mBlocks[streamBlock] : -1;
|
||||
if (cacheBlock < 0) {
|
||||
// We don't have a complete cached block here.
|
||||
@ -2224,7 +2230,10 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
|
||||
// We can just use the data in mPartialBlockBuffer. In fact we should
|
||||
// use it rather than waiting for the block to fill and land in
|
||||
// the cache.
|
||||
bytes = std::min<int64_t>(size, streamWithPartialBlock->mChannelOffset - mStreamOffset);
|
||||
int64_t bytes = std::min<int64_t>(size, streamWithPartialBlock->mChannelOffset - mStreamOffset);
|
||||
// Clamp bytes until 64-bit file size issues are fixed.
|
||||
bytes = std::min(bytes, int64_t(INT32_MAX));
|
||||
NS_ABORT_IF_FALSE(bytes >= 0 && bytes <= aCount, "Bytes out of range.");
|
||||
memcpy(aBuffer,
|
||||
reinterpret_cast<char*>(streamWithPartialBlock->mPartialBlockBuffer.get()) + offsetInStreamBlock, bytes);
|
||||
if (mCurrentMode == MODE_METADATA) {
|
||||
@ -2248,6 +2257,7 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
|
||||
gMediaCache->NoteBlockUsage(this, cacheBlock, mCurrentMode, TimeStamp::Now());
|
||||
|
||||
int64_t offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
|
||||
int32_t bytes;
|
||||
NS_ABORT_IF_FALSE(size >= 0 && size <= INT32_MAX, "Size out of range.");
|
||||
nsresult rv = gMediaCache->ReadCacheFile(offset, aBuffer + count, int32_t(size), &bytes);
|
||||
if (NS_FAILED(rv)) {
|
||||
@ -2284,9 +2294,7 @@ MediaCacheStream::ReadAt(int64_t aOffset, char* aBuffer,
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaCacheStream::ReadFromCache(char* aBuffer,
|
||||
int64_t aOffset,
|
||||
int64_t aCount)
|
||||
MediaCacheStream::ReadFromCache(char* aBuffer, int64_t aOffset, int64_t aCount)
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
|
||||
if (mClosed)
|
||||
@ -2308,7 +2316,7 @@ MediaCacheStream::ReadFromCache(char* aBuffer,
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
size = std::min(size, bytesRemaining);
|
||||
// Clamp size until 64-bit file size issues (bug 500784) are fixed.
|
||||
// Clamp size until 64-bit file size issues are fixed.
|
||||
size = std::min(size, int64_t(INT32_MAX));
|
||||
}
|
||||
|
||||
@ -2319,7 +2327,10 @@ MediaCacheStream::ReadFromCache(char* aBuffer,
|
||||
// We can just use the data in mPartialBlockBuffer. In fact we should
|
||||
// use it rather than waiting for the block to fill and land in
|
||||
// the cache.
|
||||
bytes = std::min<int64_t>(size, mChannelOffset - streamOffset);
|
||||
// Clamp bytes until 64-bit file size issues are fixed.
|
||||
int64_t toCopy = std::min<int64_t>(size, mChannelOffset - streamOffset);
|
||||
bytes = std::min(toCopy, int64_t(INT32_MAX));
|
||||
NS_ABORT_IF_FALSE(bytes >= 0 && bytes <= toCopy, "Bytes out of range.");
|
||||
memcpy(aBuffer + count,
|
||||
reinterpret_cast<char*>(mPartialBlockBuffer.get()) + offsetInStreamBlock, bytes);
|
||||
} else {
|
||||
|
@ -221,9 +221,6 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset,
|
||||
hr = buffer->Lock(&data, &maxLength, ¤tLength);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
|
||||
|
||||
int32_t numSamples = currentLength / mAudioBytesPerSample;
|
||||
int32_t numFrames = numSamples / mAudioChannels;
|
||||
|
||||
// Sometimes when starting decoding, the AAC decoder gives us samples
|
||||
// with a negative timestamp. AAC does usually have preroll (or encoder
|
||||
// delay) encoded into its bitstream, but the amount encoded to the stream
|
||||
@ -245,6 +242,13 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset,
|
||||
int32_t numFramesToStrip = 0;
|
||||
sample->GetUINT32(MFSampleExtension_Discontinuity, &discontinuity);
|
||||
if (mMustRecaptureAudioPosition || discontinuity) {
|
||||
// Update the output type, in case this segment has a different
|
||||
// rate. This also triggers on the first sample, which can have a
|
||||
// different rate than is advertised in the container, and sometimes we
|
||||
// don't get a MF_E_TRANSFORM_STREAM_CHANGE when the rate changes.
|
||||
hr = UpdateOutputType();
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
|
||||
|
||||
mAudioFrameSum = 0;
|
||||
LONGLONG timestampHns = 0;
|
||||
hr = sample->GetSampleTime(×tampHns);
|
||||
@ -258,15 +262,10 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset,
|
||||
mAudioFrameOffset = 0;
|
||||
}
|
||||
mMustRecaptureAudioPosition = false;
|
||||
|
||||
// Also update the output type, in case this segment has a different
|
||||
// rate. This also triggers on the first sample, which can have a
|
||||
// different rate than is advertised in the container, and sometimes
|
||||
// we don't get a MF_E_TRANSFORM_STREAM_CHANGE when the rate changes.
|
||||
hr = UpdateOutputType();
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
|
||||
}
|
||||
MOZ_ASSERT(numFramesToStrip >= 0);
|
||||
int32_t numSamples = currentLength / mAudioBytesPerSample;
|
||||
int32_t numFrames = numSamples / mAudioChannels;
|
||||
int32_t offset = std::min<int32_t>(numFramesToStrip, numFrames);
|
||||
numFrames -= offset;
|
||||
numSamples -= offset * mAudioChannels;
|
||||
|
@ -341,8 +341,6 @@ GMPChild::PreLoadLibraries(const std::string& aPluginPath)
|
||||
// This must be in sorted order and lowercase!
|
||||
static const char* whitelist[] =
|
||||
{
|
||||
"bcrypt.dll", // Used for OutputProtectionManager handshake
|
||||
"crypt32.dll", // Used for OutputProtectionManager handshake
|
||||
"d3d9.dll", // Create an `IDirect3D9` to get adapter information
|
||||
"dxva2.dll", // Get monitor information
|
||||
"msauddecmft.dll", // AAC decoder (on Windows 8)
|
||||
|
@ -33,6 +33,10 @@
|
||||
#include "OMXCodecProxy.h"
|
||||
#include "OmxDecoder.h"
|
||||
|
||||
#define LOG_TAG "OmxDecoder"
|
||||
#include <android/log.h>
|
||||
#define ALOG(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
PRLogModuleInfo *gOmxDecoderLog;
|
||||
#define LOG(type, msg...) PR_LOG(gOmxDecoderLog, type, (msg))
|
||||
@ -66,7 +70,8 @@ OmxDecoder::OmxDecoder(MediaResource *aResource,
|
||||
mIsVideoSeeking(false),
|
||||
mAudioMetadataRead(false),
|
||||
mAudioPaused(false),
|
||||
mVideoPaused(false)
|
||||
mVideoPaused(false),
|
||||
mVideoLastFrameTime(-1)
|
||||
{
|
||||
mLooper = new ALooper;
|
||||
mLooper->setName("OmxDecoder");
|
||||
@ -556,14 +561,43 @@ bool OmxDecoder::ReadVideo(VideoFrame *aFrame, int64_t aTimeUs,
|
||||
mIsVideoSeeking = true;
|
||||
}
|
||||
MediaSource::ReadOptions options;
|
||||
options.setSeekTo(aTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC);
|
||||
err = mVideoSource->read(&mVideoBuffer, &options);
|
||||
{
|
||||
Mutex::Autolock autoLock(mSeekLock);
|
||||
mIsVideoSeeking = false;
|
||||
PostReleaseVideoBuffer(nullptr, FenceHandle());
|
||||
MediaSource::ReadOptions::SeekMode seekMode;
|
||||
// If the last timestamp of decoded frame is smaller than seekTime,
|
||||
// seek to next key frame. Otherwise seek to the previos one.
|
||||
if (mVideoLastFrameTime > aTimeUs || mVideoLastFrameTime == -1) {
|
||||
seekMode = MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC;
|
||||
} else {
|
||||
seekMode = MediaSource::ReadOptions::SEEK_NEXT_SYNC;
|
||||
}
|
||||
|
||||
bool findNextBuffer = true;
|
||||
while (findNextBuffer) {
|
||||
options.setSeekTo(aTimeUs, seekMode);
|
||||
findNextBuffer = false;
|
||||
err = mVideoSource->read(&mVideoBuffer, &options);
|
||||
{
|
||||
Mutex::Autolock autoLock(mSeekLock);
|
||||
mIsVideoSeeking = false;
|
||||
PostReleaseVideoBuffer(nullptr, FenceHandle());
|
||||
}
|
||||
// If there is no next Keyframe, jump to the previous key frame.
|
||||
if (err == ERROR_END_OF_STREAM && seekMode == MediaSource::ReadOptions::SEEK_NEXT_SYNC) {
|
||||
seekMode = MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC;
|
||||
findNextBuffer = true;
|
||||
{
|
||||
Mutex::Autolock autoLock(mSeekLock);
|
||||
mIsVideoSeeking = true;
|
||||
}
|
||||
continue;
|
||||
} else if (err != OK) {
|
||||
ALOG("Unexpected error when seeking to %lld", aTimeUs);
|
||||
break;
|
||||
}
|
||||
if (mVideoBuffer->range_length() == 0) {
|
||||
ReleaseVideoBuffer();
|
||||
findNextBuffer = true;
|
||||
}
|
||||
}
|
||||
aDoSeek = false;
|
||||
} else {
|
||||
err = mVideoSource->read(&mVideoBuffer);
|
||||
@ -635,6 +669,7 @@ bool OmxDecoder::ReadVideo(VideoFrame *aFrame, int64_t aTimeUs,
|
||||
if ((aKeyframeSkip && timeUs < aTimeUs) || length == 0) {
|
||||
aFrame->mShouldSkip = true;
|
||||
}
|
||||
mVideoLastFrameTime = timeUs;
|
||||
}
|
||||
else if (err == INFO_FORMAT_CHANGED) {
|
||||
// If the format changed, update our cached info.
|
||||
|
@ -60,6 +60,8 @@ class OmxDecoder : public OMXCodecProxy::EventListener {
|
||||
int32_t mAudioChannels;
|
||||
int32_t mAudioSampleRate;
|
||||
int64_t mDurationUs;
|
||||
int64_t mVideoLastFrameTime;
|
||||
|
||||
VideoFrame mVideoFrame;
|
||||
AudioFrame mAudioFrame;
|
||||
MP3FrameParser mMP3FrameParser;
|
||||
|
@ -1279,6 +1279,10 @@ EventRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
||||
mProxy->mSeenUploadLoadStart = false;
|
||||
}
|
||||
else {
|
||||
if (!mProxy->mSeenLoadStart) {
|
||||
// We've already dispatched premature abort events.
|
||||
return true;
|
||||
}
|
||||
mProxy->mSeenLoadStart = false;
|
||||
}
|
||||
}
|
||||
@ -2404,7 +2408,9 @@ XMLHttpRequest::UpdateState(const StateData& aStateData,
|
||||
bool aUseCachedArrayBufferResponse)
|
||||
{
|
||||
if (aUseCachedArrayBufferResponse) {
|
||||
MOZ_ASSERT(JS_IsArrayBufferObject(mStateData.mResponse.toObjectOrNull()));
|
||||
MOZ_ASSERT(mStateData.mResponse.isObject() &&
|
||||
JS_IsArrayBufferObject(&mStateData.mResponse.toObject()));
|
||||
|
||||
JS::Rooted<JS::Value> response(mWorkerPrivate->GetJSContext(),
|
||||
mStateData.mResponse);
|
||||
mStateData = aStateData;
|
||||
|
@ -447,7 +447,8 @@ nsHTMLEditRules::AfterEditInner(EditAction action,
|
||||
res = PromoteRange(mDocChangeRange, action);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// if we did a ranged deletion, make sure we have a place to put caret.
|
||||
// if we did a ranged deletion or handling backspace key, make sure we have
|
||||
// a place to put caret.
|
||||
// Note we only want to do this if the overall operation was deletion,
|
||||
// not if deletion was done along the way for EditAction::loadHTML, EditAction::insertText, etc.
|
||||
// That's why this is here rather than DidDeleteSelection().
|
||||
@ -2045,6 +2046,10 @@ nsHTMLEditRules::WillDeleteSelection(Selection* aSelection,
|
||||
res = InsertBRIfNeeded(aSelection);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// Remember that we did a ranged delete for the benefit of
|
||||
// AfterEditInner().
|
||||
mDidRangedDelete = true;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -140,6 +140,7 @@ skip-if = os != "win"
|
||||
[test_bug998188.html]
|
||||
[test_bug1026397.html]
|
||||
[test_bug1067255.html]
|
||||
[test_bug1094000.html]
|
||||
skip-if = e10s
|
||||
[test_CF_HTML_clipboard.html]
|
||||
[test_contenteditable_focus.html]
|
||||
|
104
editor/libeditor/tests/test_bug1094000.html
Normal file
104
editor/libeditor/tests/test_bug1094000.html
Normal file
@ -0,0 +1,104 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1094000
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1094000</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1094000">Mozilla Bug 1094000</a>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
<div id="editor0" contenteditable></div>
|
||||
<div id="editor1" contenteditable><br></div>
|
||||
<div id="editor2" contenteditable>a</div>
|
||||
<div id="editor3" contenteditable>b</div>
|
||||
<div id="editor4" contenteditable>c</div>
|
||||
<div id="editor5" contenteditable>d</div>
|
||||
<div id="editor6" contenteditable>e</div>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug 1094000 **/
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
const kIsLinux = navigator.platform.indexOf("Linux") == 0;
|
||||
|
||||
function runTests()
|
||||
{
|
||||
var editor0 = document.getElementById("editor0");
|
||||
var editor1 = document.getElementById("editor1");
|
||||
var editor2 = document.getElementById("editor2");
|
||||
var editor3 = document.getElementById("editor3");
|
||||
var editor4 = document.getElementById("editor4");
|
||||
var editor5 = document.getElementById("editor5");
|
||||
var editor6 = document.getElementById("editor6");
|
||||
|
||||
ok(editor1.getBoundingClientRect().height - editor0.getBoundingClientRect().height > 1,
|
||||
"an editor having a <br> element and an empty editor shouldn't be same height");
|
||||
ok(Math.abs(editor1.getBoundingClientRect().height - editor2.getBoundingClientRect().height) <= 1,
|
||||
"an editor having only a <br> element and an editor having \"a\" should be same height");
|
||||
|
||||
editor2.focus();
|
||||
synthesizeKey("VK_RIGHT", {});
|
||||
synthesizeKey("VK_BACK_SPACE", {});
|
||||
is(editor2.innerHTML, "<br>",
|
||||
"an editor which had \"a\" should have only <br> element after Backspace keypress");
|
||||
ok(Math.abs(editor2.getBoundingClientRect().height - editor1.getBoundingClientRect().height) <= 1,
|
||||
"an editor whose content was removed by Backspace key should have a place to put a caret");
|
||||
|
||||
editor3.focus();
|
||||
synthesizeKey("VK_LEFT", {});
|
||||
synthesizeKey("VK_DELETE", {});
|
||||
is(editor3.innerHTML, "<br>",
|
||||
"an editor which had \"b\" should have only <br> element after Delete keypress");
|
||||
ok(Math.abs(editor3.getBoundingClientRect().height - editor1.getBoundingClientRect().height) <= 1,
|
||||
"an editor whose content was removed by Delete key should have a place to put a caret");
|
||||
|
||||
editor4.focus();
|
||||
window.getSelection().selectAllChildren(editor4);
|
||||
synthesizeKey("VK_BACK_SPACE", {});
|
||||
is(editor4.innerHTML, "<br>",
|
||||
"an editor which had \"c\" should have only <br> element after removing selected text with Backspace key");
|
||||
ok(Math.abs(editor4.getBoundingClientRect().height - editor1.getBoundingClientRect().height) <= 1,
|
||||
"an editor whose content was selected and removed by Backspace key should have a place to put a caret");
|
||||
|
||||
editor5.focus();
|
||||
window.getSelection().selectAllChildren(editor5);
|
||||
synthesizeKey("VK_DELETE", {});
|
||||
is(editor5.innerHTML, "<br>",
|
||||
"an editor which had \"d\" should have only <br> element after removing selected text with Delete key");
|
||||
ok(Math.abs(editor5.getBoundingClientRect().height - editor1.getBoundingClientRect().height) <= 1,
|
||||
"an editor whose content was selected and removed by Delete key should have a place to put a caret");
|
||||
|
||||
editor6.focus();
|
||||
window.getSelection().selectAllChildren(editor6);
|
||||
synthesizeKey("x", { accelKey: true });
|
||||
is(editor6.innerHTML, "<br>",
|
||||
"an editor which had \"e\" should have only <br> element after removing selected text by \"Cut\"");
|
||||
ok(Math.abs(editor6.getBoundingClientRect().height - editor1.getBoundingClientRect().height) <= 1,
|
||||
"an editor whose content was selected and removed by \"Cut\" should have a place to put a caret");
|
||||
|
||||
editor0.focus();
|
||||
synthesizeKey("VK_BACK_SPACE", {});
|
||||
is(editor0.innerHTML, "",
|
||||
"an empty editor should keep being empty even if Backspace key is pressed");
|
||||
synthesizeKey("VK_DELETE", {});
|
||||
is(editor0.innerHTML, "",
|
||||
"an empty editor should keep being empty even if Delete key is pressed");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForFocus(runTests);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -148,7 +148,9 @@ However, on the main thread, web content might be running Javascript code that p
|
||||
Scroll changes driven from the main thread are just as legitimate and need to be propagated to the compositor thread, so that the visual display updates in response.
|
||||
|
||||
Because the cross-thread messaging is asynchronous, reconciling the two types of scroll changes is a tricky problem.
|
||||
Our design solves this using various flags and generation counters that ensures compositor-driven scroll changes never overwrite content-driven scroll changes - this behaviour is usually the desired outcome.
|
||||
Our design solves this using various flags and generation counters.
|
||||
The general heuristic we have is that content-driven scroll position changes (e.g. scrollTo from JS) are never lost.
|
||||
For instance, if the user is doing an async scroll with their finger and content does a scrollTo in the middle, then some of the async scroll would occur before the "jump" and the rest after the "jump".
|
||||
|
||||
### Content preventing default behaviour of input events
|
||||
|
||||
@ -165,3 +167,116 @@ The way the APZ implementation deals with this is that upon receiving a touch ev
|
||||
It also schedules a 300ms timeout during which content is allowed to prevent scrolling.
|
||||
There is an API that allows the main-thread event dispatching code to notify the APZ as to whether or not the default action should be prevented.
|
||||
If the APZ content response timeout expires, or if the main-thread event dispatching code notifies the APZ of the preventDefault status, then the APZ continues with the processing of the events (which may involve discarding the events).
|
||||
|
||||
## Technical details
|
||||
|
||||
This section describes various pieces of the APZ code, and goes into more specific detail on APIs and code than the previous sections.
|
||||
The primary purpose of this section is to help people who plan on making changes to the code, while also not going into so much detail that it needs to be updated with every patch.
|
||||
|
||||
### Overall flow of input events
|
||||
|
||||
This section describes how input events flow through the APZ code.
|
||||
<ol>
|
||||
<li value="1">
|
||||
Input events arrive from the hardware/widget code into the APZ via APZCTreeManager::ReceiveInputEvent.
|
||||
The thread that invokes this is called the input thread, and may or may not be the same as the Gecko main thread.
|
||||
</li>
|
||||
<li value="2">
|
||||
Conceptually the first thing that the APZCTreeManager does is to group these events into "input blocks".
|
||||
An input block is a contiguous set of events that get handled together.
|
||||
For example with touch events, all events following a touchstart up to but not including the next touchstart are in the same block.
|
||||
All of the events in a given block will go to the same APZC instance and will either all be processed or all be dropped.
|
||||
</li>
|
||||
<li value="3">
|
||||
Using the first event in the input block, the APZCTreeManager does a hit-test to see which APZC it hits.
|
||||
If no APZC is hit, the events are discarded and we jump to step 6.
|
||||
Otherwise, the input block is tagged with the APZC and put into a global APZ input queue.
|
||||
</li>
|
||||
<li value="4">
|
||||
<ol>
|
||||
<li value="i">
|
||||
If the input events are not touch events, or if the APZC is for content without touch listeners, any available events in the input block are processed.
|
||||
These may trigger behaviours like scrolling or tap gestures.
|
||||
</li>
|
||||
<li value="ii">
|
||||
If the input events are touch events and the APZC is for content with touch listeners, the events are left in the queue and a 300ms timeout is initiated.
|
||||
If the timeout expires before step 9 is completed, the APZ assumes the input block was not cancelled and processes them as part of step 10.
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li value="5">
|
||||
The call stack unwinds back to APZCTreeManager::ReceiveInputEvent, which does an in-place modification of the input event so that any async transforms are removed.
|
||||
</li>
|
||||
<li value="6">
|
||||
The call stack unwinds back to the widget code that called ReceiveInputEvent.
|
||||
This code now has the event in the coordinate space Gecko is expecting, and so can dispatch it to the Gecko main thread.
|
||||
</li>
|
||||
<li value="7">
|
||||
Gecko performs its own usual hit-testing and event dispatching for the event.
|
||||
As part of this, it records whether any touch listeners cancelled the input block by calling preventDefault().
|
||||
</li>
|
||||
<li value="8">
|
||||
The call stack unwinds back to the widget code, which sends a notification to the APZ code by calling APZCTreeManager::ContentReceivedTouch on the input thread.
|
||||
This happens only once per input block.
|
||||
</li>
|
||||
<li value="9">
|
||||
<ol>
|
||||
<li value="i">
|
||||
If the events were processed as part of step 4(i), the call to ContentReceivedTouch is ignored and step 10 is skipped.
|
||||
</li>
|
||||
<li value="ii">
|
||||
If events were queued as part of step 4(ii), and steps 5-8 take less than 300ms, the ContentReceivedTouch call marks the input block ready for processing.
|
||||
</li>
|
||||
<li value="iii">
|
||||
If events were queued as part of step 4(ii), but steps 5-8 take longer than 300ms, the ContentReceivedTouch will be ignored and step 10 will already have happened.
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li value="10">
|
||||
If events were queued as part of step 4(ii) they are now either processed (if the input block was not cancelled, or if the timeout expired) or dropped (if it was cancelled).
|
||||
Processing the events may trigger behaviours like scrolling or tap gestures.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
If the CSS touch-action property is enabled, the above steps are modified as follows:
|
||||
<ul>
|
||||
<li>
|
||||
In step 4, the APZC also requires the allowed touch-action behaviours for the input event. This is not available yet, so the events are always queued.
|
||||
</li>
|
||||
<li>
|
||||
In step 6, the widget code determines the content element at the point under the input element, and notifies the APZ code of the allowed touch-action behaviours.
|
||||
This is done via a call to APZCTreeManager::SetAllowedTouchBehavior on the input thread.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
#### Threading considerations
|
||||
|
||||
The bulk of the input processing in the APZ code happens on what we call "the input thread".
|
||||
In practice the input thread could be the Gecko main thread, the compositor thread, or some other thread.
|
||||
There are obvious downsides to using the Gecko main thread - that is, "asynchronous" panning and zooming is not really asynchronous as input events can only be processed while Gecko is idle.
|
||||
However, this is the current state of things on B2G and Metro.
|
||||
Using the compositor thread as the input thread could work on some platforms, but may be inefficient on others.
|
||||
For example, on Android (Fennec) we receive input events from the system on a dedicated UI thread.
|
||||
We would have to redispatch the input events to the compositor thread if we wanted to the input thread to be the same as the compositor thread.
|
||||
This introduces a potential for higher latency, particularly if the compositor does any blocking operations - blocking SwapBuffers operations, for example.
|
||||
As a result, the APZ code itself does not assume that the input thread will be the same as the Gecko main thread or the compositor thread.
|
||||
|
||||
#### Active vs. inactive scrollframes
|
||||
|
||||
The number of scrollframes on a page is potentially unbounded.
|
||||
However, we do not want to create a separate layer for each scrollframe right away, as this would require large amounts of memory.
|
||||
Therefore, scrollframes as designated as either "active" or "inactive".
|
||||
Active scrollframes are the ones that do have their contents put on a separate layer (or set of layers), and inactive ones do not.
|
||||
|
||||
Consider a page with a scrollframe that is initially inactive.
|
||||
When layout generates the layers for this page, it inserts a "scrollinfo" layer into the layer tree to let the APZ know that there is potentially scrollable content there.
|
||||
The scrollinfo layer is an empty ContainerLayer, which does not require much extra memory.
|
||||
The composition bounds of this scrollinfo layer are used on the compositor for hit-testing, and a "placeholder" APZC is created for this scrollframe.
|
||||
When the user starts interacting with that content, the hit-test in the APZ code finds the placeholder APZC and starts routing it the events as usual.
|
||||
The APZC eventually sends a repaint request to the main thread, and that repaint request sets a displayport on the scrollframe.
|
||||
Setting the displayport activates the scrollframe and causes it to get pushed onto a separate layer (or set of layers).
|
||||
|
||||
This model imples that when the user initially attempts to scroll an inactive scrollframe, it will not initially scroll visually.
|
||||
(This is because although the APZC is tracking the events and updating scroll position, there is no layer to which it can apply the async scroll offset.)
|
||||
Only after the round-trip to the gecko thread is complete is there a layer for async scrolling to actually occur.
|
||||
At that point the scrollframe will visually "jump" to the correct scroll offset.
|
||||
|
@ -485,6 +485,12 @@ BasicLayerManager::EndTransactionInternal(DrawPaintedLayerCallback aCallback,
|
||||
mTransactionIncomplete = false;
|
||||
|
||||
if (mRoot) {
|
||||
if (aFlags & END_NO_COMPOSITE) {
|
||||
// Apply pending tree updates before recomputing effective
|
||||
// properties.
|
||||
mRoot->ApplyPendingUpdatesToSubtree();
|
||||
}
|
||||
|
||||
// Need to do this before we call ApplyDoubleBuffering,
|
||||
// which depends on correct effective transforms
|
||||
if (mTarget) {
|
||||
@ -499,12 +505,6 @@ BasicLayerManager::EndTransactionInternal(DrawPaintedLayerCallback aCallback,
|
||||
if (mRoot->GetMaskLayer()) {
|
||||
ToData(mRoot->GetMaskLayer())->Validate(aCallback, aCallbackData, nullptr);
|
||||
}
|
||||
|
||||
if (aFlags & END_NO_COMPOSITE) {
|
||||
// Apply pending tree updates before recomputing effective
|
||||
// properties.
|
||||
mRoot->ApplyPendingUpdatesToSubtree();
|
||||
}
|
||||
}
|
||||
|
||||
if (mTarget && mRoot &&
|
||||
|
@ -584,7 +584,7 @@ gfxDWriteFont::ProvidesGlyphWidths() const
|
||||
}
|
||||
|
||||
int32_t
|
||||
gfxDWriteFont::GetGlyphWidth(gfxContext *aCtx, uint16_t aGID)
|
||||
gfxDWriteFont::GetGlyphWidth(DrawTarget& aDrawTarget, uint16_t aGID)
|
||||
{
|
||||
if (!mGlyphWidths) {
|
||||
mGlyphWidths = new nsDataHashtable<nsUint32HashKey,int32_t>(128);
|
||||
|
@ -54,7 +54,7 @@ public:
|
||||
|
||||
virtual bool ProvidesGlyphWidths() const;
|
||||
|
||||
virtual int32_t GetGlyphWidth(gfxContext *aCtx, uint16_t aGID);
|
||||
virtual int32_t GetGlyphWidth(DrawTarget& aDrawTarget, uint16_t aGID);
|
||||
|
||||
virtual mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions>
|
||||
GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams = nullptr) MOZ_OVERRIDE;
|
||||
|
@ -168,7 +168,7 @@ gfxFT2FontBase::GetGlyph(uint32_t unicode, uint32_t variation_selector)
|
||||
}
|
||||
|
||||
int32_t
|
||||
gfxFT2FontBase::GetGlyphWidth(gfxContext *aCtx, uint16_t aGID)
|
||||
gfxFT2FontBase::GetGlyphWidth(DrawTarget& aDrawTarget, uint16_t aGID)
|
||||
{
|
||||
cairo_text_extents_t extents;
|
||||
GetGlyphExtents(aGID, &extents);
|
||||
|
@ -25,7 +25,7 @@ public:
|
||||
virtual bool ProvidesGetGlyph() const { return true; }
|
||||
virtual uint32_t GetGlyph(uint32_t unicode, uint32_t variation_selector);
|
||||
virtual bool ProvidesGlyphWidths() const { return true; }
|
||||
virtual int32_t GetGlyphWidth(gfxContext *aCtx, uint16_t aGID);
|
||||
virtual int32_t GetGlyphWidth(DrawTarget& aDrawTarget, uint16_t aGID);
|
||||
|
||||
cairo_scaled_font_t *CairoScaledFont() { return mScaledFont; };
|
||||
virtual bool SetupCairoFont(gfxContext *aContext);
|
||||
|
@ -791,7 +791,7 @@ gfxFont::GetGlyphHAdvance(gfxContext *aCtx, uint16_t aGID)
|
||||
return 0;
|
||||
}
|
||||
if (ProvidesGlyphWidths()) {
|
||||
return GetGlyphWidth(aCtx, aGID) / 65536.0;
|
||||
return GetGlyphWidth(*aCtx->GetDrawTarget(), aGID) / 65536.0;
|
||||
}
|
||||
if (mFUnitsConvFactor == 0.0f) {
|
||||
GetMetrics(eHorizontal);
|
||||
@ -806,7 +806,7 @@ gfxFont::GetGlyphHAdvance(gfxContext *aCtx, uint16_t aGID)
|
||||
if (!shaper->Initialize()) {
|
||||
return 0;
|
||||
}
|
||||
return shaper->GetGlyphHAdvance(aCtx, aGID) / 65536.0;
|
||||
return shaper->GetGlyphHAdvance(aGID) / 65536.0;
|
||||
}
|
||||
|
||||
/*static*/
|
||||
|
@ -1267,6 +1267,9 @@ class gfxFont {
|
||||
friend class gfxHarfBuzzShaper;
|
||||
friend class gfxGraphiteShaper;
|
||||
|
||||
protected:
|
||||
typedef mozilla::gfx::DrawTarget DrawTarget;
|
||||
|
||||
public:
|
||||
nsrefcnt AddRef(void) {
|
||||
NS_PRECONDITION(int32_t(mRefCnt) >= 0, "illegal refcnt");
|
||||
@ -1709,7 +1712,7 @@ public:
|
||||
|
||||
virtual FontType GetType() const = 0;
|
||||
|
||||
virtual mozilla::TemporaryRef<mozilla::gfx::ScaledFont> GetScaledFont(mozilla::gfx::DrawTarget *aTarget)
|
||||
virtual mozilla::TemporaryRef<mozilla::gfx::ScaledFont> GetScaledFont(DrawTarget* aTarget)
|
||||
{ return gfxPlatform::GetPlatform()->GetScaledFontForFont(aTarget, this); }
|
||||
|
||||
bool KerningDisabled() {
|
||||
@ -1820,7 +1823,7 @@ protected:
|
||||
|
||||
// The return value is interpreted as a horizontal advance in 16.16 fixed
|
||||
// point format.
|
||||
virtual int32_t GetGlyphWidth(gfxContext *aCtx, uint16_t aGID) {
|
||||
virtual int32_t GetGlyphWidth(DrawTarget& aDrawTarget, uint16_t aGID) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -460,7 +460,7 @@ gfxGDIFont::GetGlyph(uint32_t aUnicode, uint32_t aVarSelector)
|
||||
}
|
||||
|
||||
int32_t
|
||||
gfxGDIFont::GetGlyphWidth(gfxContext *aCtx, uint16_t aGID)
|
||||
gfxGDIFont::GetGlyphWidth(DrawTarget& aDrawTarget, uint16_t aGID)
|
||||
{
|
||||
if (!mGlyphWidths) {
|
||||
mGlyphWidths = new nsDataHashtable<nsUint32HashKey,int32_t>(128);
|
||||
@ -471,7 +471,7 @@ gfxGDIFont::GetGlyphWidth(gfxContext *aCtx, uint16_t aGID)
|
||||
return width;
|
||||
}
|
||||
|
||||
DCFromContext dc(aCtx);
|
||||
DCFromDrawTarget dc(aDrawTarget);
|
||||
AutoSelectFont fs(dc, GetHFONT());
|
||||
|
||||
int devWidth;
|
||||
|
@ -60,7 +60,7 @@ public:
|
||||
virtual bool ProvidesGlyphWidths() const { return true; }
|
||||
|
||||
// get hinted glyph width in pixels as 16.16 fixed-point value
|
||||
virtual int32_t GetGlyphWidth(gfxContext *aCtx, uint16_t aGID);
|
||||
virtual int32_t GetGlyphWidth(DrawTarget& aDrawTarget, uint16_t aGID);
|
||||
|
||||
virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
|
||||
FontCacheSizes* aSizes) const;
|
||||
|
@ -50,7 +50,8 @@ gfxGraphiteShaper::GrGetAdvance(const void* appFontHandle, uint16_t glyphid)
|
||||
{
|
||||
const CallbackData *cb =
|
||||
static_cast<const CallbackData*>(appFontHandle);
|
||||
return FixedToFloat(cb->mFont->GetGlyphWidth(cb->mContext, glyphid));
|
||||
return FixedToFloat(cb->mFont->GetGlyphWidth(*cb->mContext->GetDrawTarget(),
|
||||
glyphid));
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
|
@ -184,8 +184,7 @@ struct GlyphMetrics {
|
||||
};
|
||||
|
||||
hb_position_t
|
||||
gfxHarfBuzzShaper::GetGlyphHAdvance(gfxContext *aContext,
|
||||
hb_codepoint_t glyph) const
|
||||
gfxHarfBuzzShaper::GetGlyphHAdvance(hb_codepoint_t glyph) const
|
||||
{
|
||||
// font did not implement GetGlyphWidth, so get an unhinted value
|
||||
// directly from the font tables
|
||||
@ -208,8 +207,7 @@ gfxHarfBuzzShaper::GetGlyphHAdvance(gfxContext *aContext,
|
||||
}
|
||||
|
||||
hb_position_t
|
||||
gfxHarfBuzzShaper::GetGlyphVAdvance(gfxContext *aContext,
|
||||
hb_codepoint_t glyph) const
|
||||
gfxHarfBuzzShaper::GetGlyphVAdvance(hb_codepoint_t glyph) const
|
||||
{
|
||||
if (!mVmtxTable) {
|
||||
// Must be a "vertical" font that doesn't actually have vertical metrics;
|
||||
@ -243,10 +241,9 @@ gfxHarfBuzzShaper::HBGetGlyphHAdvance(hb_font_t *font, void *font_data,
|
||||
static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data);
|
||||
gfxFont *gfxfont = fcd->mShaper->GetFont();
|
||||
if (gfxfont->ProvidesGlyphWidths()) {
|
||||
return gfxfont->GetGlyphWidth(fcd->mContext, glyph);
|
||||
} else {
|
||||
return fcd->mShaper->GetGlyphHAdvance(fcd->mContext, glyph);
|
||||
return gfxfont->GetGlyphWidth(*fcd->mContext->GetDrawTarget(), glyph);
|
||||
}
|
||||
return fcd->mShaper->GetGlyphHAdvance(glyph);
|
||||
}
|
||||
|
||||
/* static */
|
||||
@ -258,10 +255,9 @@ gfxHarfBuzzShaper::HBGetGlyphVAdvance(hb_font_t *font, void *font_data,
|
||||
static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data);
|
||||
gfxFont *gfxfont = fcd->mShaper->GetFont();
|
||||
if (gfxfont->ProvidesGlyphWidths()) {
|
||||
return gfxfont->GetGlyphWidth(fcd->mContext, glyph);
|
||||
} else {
|
||||
return fcd->mShaper->GetGlyphVAdvance(fcd->mContext, glyph);
|
||||
return gfxfont->GetGlyphWidth(*fcd->mContext->GetDrawTarget(), glyph);
|
||||
}
|
||||
return fcd->mShaper->GetGlyphVAdvance(glyph);
|
||||
}
|
||||
|
||||
/* static */
|
||||
@ -296,15 +292,15 @@ gfxHarfBuzzShaper::HBGetGlyphVOrigin(hb_font_t *font, void *font_data,
|
||||
{
|
||||
const gfxHarfBuzzShaper::FontCallbackData *fcd =
|
||||
static_cast<const gfxHarfBuzzShaper::FontCallbackData*>(font_data);
|
||||
fcd->mShaper->GetGlyphVOrigin(fcd->mContext, glyph, x, y);
|
||||
fcd->mShaper->GetGlyphVOrigin(glyph, x, y);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
gfxHarfBuzzShaper::GetGlyphVOrigin(gfxContext *aContext, hb_codepoint_t aGlyph,
|
||||
gfxHarfBuzzShaper::GetGlyphVOrigin(hb_codepoint_t aGlyph,
|
||||
hb_position_t *aX, hb_position_t *aY) const
|
||||
{
|
||||
*aX = -0.5 * GetGlyphHAdvance(aContext, aGlyph);
|
||||
*aX = -0.5 * GetGlyphHAdvance(aGlyph);
|
||||
|
||||
if (mVORGTable) {
|
||||
// We checked in Initialize() that the VORG table is safely readable,
|
||||
|
@ -42,13 +42,11 @@ public:
|
||||
hb_codepoint_t variation_selector) const;
|
||||
|
||||
// get harfbuzz glyph advance, in font design units
|
||||
hb_position_t GetGlyphHAdvance(gfxContext *aContext,
|
||||
hb_codepoint_t glyph) const;
|
||||
hb_position_t GetGlyphHAdvance(hb_codepoint_t glyph) const;
|
||||
|
||||
hb_position_t GetGlyphVAdvance(gfxContext *aContext,
|
||||
hb_codepoint_t glyph) const;
|
||||
hb_position_t GetGlyphVAdvance(hb_codepoint_t glyph) const;
|
||||
|
||||
void GetGlyphVOrigin(gfxContext *aContext, hb_codepoint_t aGlyph,
|
||||
void GetGlyphVOrigin(hb_codepoint_t aGlyph,
|
||||
hb_position_t *aX, hb_position_t *aY) const;
|
||||
|
||||
// get harfbuzz horizontal advance in 16.16 fixed point format.
|
||||
|
@ -4,10 +4,11 @@
|
||||
* 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/. */
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
|
||||
#include "gfxWindowsPlatform.h"
|
||||
|
||||
#include "cairo.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
|
||||
#include "gfxImageSurface.h"
|
||||
#include "gfxWindowsSurface.h"
|
||||
|
||||
@ -80,6 +81,31 @@ using namespace mozilla::layers;
|
||||
using namespace mozilla::widget;
|
||||
using namespace mozilla::image;
|
||||
|
||||
DCFromDrawTarget::DCFromDrawTarget(DrawTarget& aDrawTarget)
|
||||
{
|
||||
mDC = nullptr;
|
||||
if (aDrawTarget.GetBackendType() == BackendType::CAIRO) {
|
||||
cairo_surface_t *surf = (cairo_surface_t*)
|
||||
aDrawTarget.GetNativeSurface(NativeSurfaceType::CAIRO_SURFACE);
|
||||
cairo_surface_type_t surfaceType = cairo_surface_get_type(surf);
|
||||
if (surfaceType == CAIRO_SURFACE_TYPE_WIN32 ||
|
||||
surfaceType == CAIRO_SURFACE_TYPE_WIN32_PRINTING) {
|
||||
mDC = cairo_win32_surface_get_dc(surf);
|
||||
mNeedsRelease = false;
|
||||
SaveDC(mDC);
|
||||
cairo_t* ctx = (cairo_t*)
|
||||
aDrawTarget.GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT);
|
||||
cairo_scaled_font_t* scaled = cairo_get_scaled_font(ctx);
|
||||
cairo_win32_scaled_font_select_font(scaled, mDC);
|
||||
}
|
||||
if (!mDC) {
|
||||
mDC = GetDC(nullptr);
|
||||
SetGraphicsMode(mDC, GM_ADVANCED);
|
||||
mNeedsRelease = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CAIRO_HAS_D2D_SURFACE
|
||||
|
||||
static const char *kFeatureLevelPref =
|
||||
@ -1531,7 +1557,7 @@ bool DoesD3D11DeviceWork(ID3D11Device *device)
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
if (displayLinkModuleVersion <= GFX_DRIVER_VERSION(8,6,1,36484)) {
|
||||
if (displayLinkModuleVersion <= V(8,6,1,36484)) {
|
||||
#if defined(MOZ_CRASHREPORTER)
|
||||
CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("DisplayLink: too old version\n"));
|
||||
#endif
|
||||
|
@ -20,8 +20,8 @@
|
||||
#include "gfxDWriteFonts.h"
|
||||
#endif
|
||||
#include "gfxPlatform.h"
|
||||
#include "gfxContext.h"
|
||||
|
||||
#include "gfxTypes.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsDataHashtable.h"
|
||||
|
||||
@ -44,6 +44,9 @@
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
class DrawTarget;
|
||||
}
|
||||
namespace layers {
|
||||
class DeviceManagerD3D9;
|
||||
class ReadbackManagerD3D11;
|
||||
@ -55,44 +58,31 @@ struct IDXGIAdapter1;
|
||||
|
||||
class nsIMemoryReporter;
|
||||
|
||||
// Utility to get a Windows HDC from a thebes context,
|
||||
// used by both GDI and Uniscribe font shapers
|
||||
struct DCFromContext {
|
||||
DCFromContext(gfxContext *aContext) {
|
||||
dc = nullptr;
|
||||
nsRefPtr<gfxASurface> aSurface = aContext->CurrentSurface();
|
||||
if (aSurface &&
|
||||
(aSurface->GetType() == gfxSurfaceType::Win32 ||
|
||||
aSurface->GetType() == gfxSurfaceType::Win32Printing))
|
||||
{
|
||||
dc = static_cast<gfxWindowsSurface*>(aSurface.get())->GetDC();
|
||||
needsRelease = false;
|
||||
SaveDC(dc);
|
||||
cairo_scaled_font_t* scaled =
|
||||
cairo_get_scaled_font(aContext->GetCairo());
|
||||
cairo_win32_scaled_font_select_font(scaled, dc);
|
||||
}
|
||||
if (!dc) {
|
||||
dc = GetDC(nullptr);
|
||||
SetGraphicsMode(dc, GM_ADVANCED);
|
||||
needsRelease = true;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Utility to get a Windows HDC from a Moz2D DrawTarget. If the DrawTarget is
|
||||
* not backed by a HDC this will get the HDC for the screen device context
|
||||
* instead.
|
||||
*/
|
||||
class MOZ_STACK_CLASS DCFromDrawTarget MOZ_FINAL
|
||||
{
|
||||
public:
|
||||
DCFromDrawTarget(mozilla::gfx::DrawTarget& aDrawTarget);
|
||||
|
||||
~DCFromContext() {
|
||||
if (needsRelease) {
|
||||
ReleaseDC(nullptr, dc);
|
||||
~DCFromDrawTarget() {
|
||||
if (mNeedsRelease) {
|
||||
ReleaseDC(nullptr, mDC);
|
||||
} else {
|
||||
RestoreDC(dc, -1);
|
||||
RestoreDC(mDC, -1);
|
||||
}
|
||||
}
|
||||
|
||||
operator HDC () {
|
||||
return dc;
|
||||
return mDC;
|
||||
}
|
||||
|
||||
HDC dc;
|
||||
bool needsRelease;
|
||||
private:
|
||||
HDC mDC;
|
||||
bool mNeedsRelease;
|
||||
};
|
||||
|
||||
// ClearType parameters set by running ClearType tuner
|
||||
|
@ -19,7 +19,7 @@ interface nsIPrincipal;
|
||||
* @version 0.1
|
||||
* @see imagelib2
|
||||
*/
|
||||
[scriptable, builtinclass, uuid(710f22f0-558b-11e4-8ed6-0800200c9a66)]
|
||||
[scriptable, builtinclass, uuid(dc61f0ea-4139-4c2a-ae69-cec82d33e089)]
|
||||
interface imgIRequest : nsIRequest
|
||||
{
|
||||
/**
|
||||
@ -83,6 +83,11 @@ interface imgIRequest : nsIRequest
|
||||
*/
|
||||
readonly attribute nsIURI URI;
|
||||
|
||||
/**
|
||||
* The URI of the resource we ended up loading after all redirects, etc.
|
||||
*/
|
||||
readonly attribute nsIURI currentURI;
|
||||
|
||||
readonly attribute imgINotificationObserver notificationObserver;
|
||||
|
||||
readonly attribute string mimeType;
|
||||
|
@ -245,35 +245,6 @@ Decoder::AllocateFrame()
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
Decoder::FlushInvalidations()
|
||||
{
|
||||
NS_ABORT_IF_FALSE(!HasDecoderError(),
|
||||
"Not allowed to make more decoder calls after error!");
|
||||
|
||||
// If we've got an empty invalidation rect, we have nothing to do
|
||||
if (mInvalidRect.IsEmpty())
|
||||
return;
|
||||
|
||||
if (mObserver) {
|
||||
#ifdef XP_MACOSX
|
||||
// Bug 703231
|
||||
// Because of high quality down sampling on mac we show scan lines while decoding.
|
||||
// Bypass this problem by redrawing the border.
|
||||
if (mImageMetadata.HasSize()) {
|
||||
nsIntRect mImageBound(0, 0, mImageMetadata.GetWidth(), mImageMetadata.GetHeight());
|
||||
|
||||
mInvalidRect.Inflate(1);
|
||||
mInvalidRect = mInvalidRect.Intersect(mImageBound);
|
||||
}
|
||||
#endif
|
||||
mObserver->FrameChanged(&mInvalidRect);
|
||||
}
|
||||
|
||||
// Clear the invalidation rectangle
|
||||
mInvalidRect.SetEmpty();
|
||||
}
|
||||
|
||||
void
|
||||
Decoder::SetSizeOnImage()
|
||||
{
|
||||
@ -320,11 +291,6 @@ Decoder::PostFrameStart()
|
||||
// We shouldn't already be mid-frame
|
||||
NS_ABORT_IF_FALSE(!mInFrame, "Starting new frame but not done with old one!");
|
||||
|
||||
// We should take care of any invalidation region when wrapping up the
|
||||
// previous frame
|
||||
NS_ABORT_IF_FALSE(mInvalidRect.IsEmpty(),
|
||||
"Start image frame with non-empty invalidation region!");
|
||||
|
||||
// Update our state to reflect the new frame
|
||||
mFrameCount++;
|
||||
mInFrame = true;
|
||||
@ -334,11 +300,6 @@ Decoder::PostFrameStart()
|
||||
// reported by the Image.
|
||||
NS_ABORT_IF_FALSE(mFrameCount == mImage.GetNumFrames(),
|
||||
"Decoder frame count doesn't match image's!");
|
||||
|
||||
// Fire notifications
|
||||
if (mObserver) {
|
||||
mObserver->OnStartFrame();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -363,9 +324,6 @@ Decoder::PostFrameStop(FrameBlender::FrameAlpha aFrameAlpha /* = FrameBlender::k
|
||||
mCurrentFrame->SetBlendMethod(aBlendMethod);
|
||||
mCurrentFrame->ImageUpdated(mCurrentFrame->GetRect());
|
||||
|
||||
// Flush any invalidations before we finish the frame
|
||||
FlushInvalidations();
|
||||
|
||||
// Fire notifications
|
||||
if (mObserver) {
|
||||
mObserver->OnStopFrame();
|
||||
|
@ -69,14 +69,17 @@ public:
|
||||
void FinishSharedDecoder();
|
||||
|
||||
/**
|
||||
* Tells the decoder to flush any pending invalidations. This informs the image
|
||||
* frame of its decoded region, and sends the appropriate OnDataAvailable call
|
||||
* to consumers.
|
||||
*
|
||||
* This can be called any time when we're midway through decoding a frame,
|
||||
* and must be called after finishing a frame (before starting a new one).
|
||||
* Gets the invalidation region accumulated by the decoder so far, and clears
|
||||
* the decoder's invalidation region. This means that each call to
|
||||
* TakeInvalidRect() returns only the invalidation region accumulated since
|
||||
* the last call to TakeInvalidRect().
|
||||
*/
|
||||
void FlushInvalidations();
|
||||
nsIntRect TakeInvalidRect()
|
||||
{
|
||||
nsIntRect invalidRect = mInvalidRect;
|
||||
mInvalidRect.SetEmpty();
|
||||
return invalidRect;
|
||||
}
|
||||
|
||||
// We're not COM-y, so we don't get refcounts by default
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Decoder)
|
||||
|
@ -484,10 +484,10 @@ RasterImage::RequestRefresh(const TimeStamp& aTime)
|
||||
|
||||
UpdateImageContainer();
|
||||
|
||||
// Explicitly call this on mStatusTracker so we're sure to not interfere
|
||||
// with the decoding process
|
||||
if (mStatusTracker)
|
||||
mStatusTracker->FrameChanged(&res.dirtyRect);
|
||||
if (mStatusTracker) {
|
||||
mStatusTracker->SyncNotifyDifference(ImageStatusDiff::NoChange(),
|
||||
res.dirtyRect);
|
||||
}
|
||||
}
|
||||
|
||||
if (res.animationFinished) {
|
||||
@ -655,6 +655,17 @@ RasterImage::GetRequestedFrameIndex(uint32_t aWhichFrame) const
|
||||
return aWhichFrame == FRAME_FIRST ? 0 : GetCurrentFrameIndex();
|
||||
}
|
||||
|
||||
nsIntRect
|
||||
RasterImage::GetFirstFrameRect()
|
||||
{
|
||||
if (mAnim) {
|
||||
return mAnim->GetFirstFrameRefreshArea();
|
||||
}
|
||||
|
||||
// Fall back to our size. This is implicitly zero-size if !mHasSize.
|
||||
return nsIntRect(nsIntPoint(0,0), mSize);
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
/* [notxpcom] boolean frameIsOpaque(in uint32_t aWhichFrame); */
|
||||
NS_IMETHODIMP_(bool)
|
||||
@ -1457,7 +1468,7 @@ RasterImage::ResetAnimation()
|
||||
// Update display
|
||||
if (mStatusTracker) {
|
||||
nsIntRect rect = mAnim->GetFirstFrameRefreshArea();
|
||||
mStatusTracker->FrameChanged(&rect);
|
||||
mStatusTracker->SyncNotifyDifference(ImageStatusDiff::NoChange(), rect);
|
||||
}
|
||||
|
||||
// Start the animation again. It may not have been running before, if
|
||||
@ -1558,14 +1569,6 @@ RasterImage::AddSourceData(const char *aBuffer, uint32_t aCount)
|
||||
rv = WriteToDecoder(aBuffer, aCount, DECODE_SYNC);
|
||||
CONTAINER_ENSURE_SUCCESS(rv);
|
||||
|
||||
// We're not storing source data, so this data is probably coming straight
|
||||
// from the network. In this case, we want to display data as soon as we
|
||||
// get it, so we want to flush invalidations after every write.
|
||||
nsRefPtr<Decoder> kungFuDeathGrip = mDecoder;
|
||||
mInDecoder = true;
|
||||
mDecoder->FlushInvalidations();
|
||||
mInDecoder = false;
|
||||
|
||||
rv = FinishedSomeDecoding();
|
||||
CONTAINER_ENSURE_SUCCESS(rv);
|
||||
}
|
||||
@ -2416,15 +2419,6 @@ RasterImage::SyncDecode()
|
||||
DECODE_SYNC);
|
||||
CONTAINER_ENSURE_SUCCESS(rv);
|
||||
|
||||
// When we're doing a sync decode, we want to get as much information from the
|
||||
// image as possible. We've send the decoder all of our data, so now's a good
|
||||
// time to flush any invalidations (in case we don't have all the data and what
|
||||
// we got left us mid-frame).
|
||||
nsRefPtr<Decoder> kungFuDeathGrip = mDecoder;
|
||||
mInDecoder = true;
|
||||
mDecoder->FlushInvalidations();
|
||||
mInDecoder = false;
|
||||
|
||||
rv = FinishedSomeDecoding();
|
||||
CONTAINER_ENSURE_SUCCESS(rv);
|
||||
|
||||
@ -2498,9 +2492,8 @@ RasterImage::NotifyNewScaledFrame()
|
||||
if (mStatusTracker) {
|
||||
// Send an invalidation so observers will repaint and can take advantage of
|
||||
// the new scaled frame if possible.
|
||||
// XXX(seth): Why does FrameChanged take a pointer and not a reference?
|
||||
nsIntRect invalidationRect(0, 0, mSize.width, mSize.height);
|
||||
mStatusTracker->FrameChanged(&invalidationRect);
|
||||
nsIntRect rect(0, 0, mSize.width, mSize.height);
|
||||
mStatusTracker->SyncNotifyDifference(ImageStatusDiff::NoChange(), rect);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3001,9 +2994,12 @@ RasterImage::FinishedSomeDecoding(eShutdownIntent aIntent /* = eShutdownIntent_D
|
||||
|
||||
bool done = false;
|
||||
bool wasSize = false;
|
||||
nsIntRect invalidRect;
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
if (image->mDecoder) {
|
||||
invalidRect = image->mDecoder->TakeInvalidRect();
|
||||
|
||||
if (request && request->mChunkCount && !image->mDecoder->IsSizeDecode()) {
|
||||
Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS, request->mChunkCount);
|
||||
}
|
||||
@ -3046,6 +3042,17 @@ RasterImage::FinishedSomeDecoding(eShutdownIntent aIntent /* = eShutdownIntent_D
|
||||
}
|
||||
}
|
||||
|
||||
if (GetCurrentFrameIndex() > 0) {
|
||||
// Don't send invalidations for animated frames after the first; let
|
||||
// RequestRefresh take care of that.
|
||||
invalidRect = nsIntRect();
|
||||
}
|
||||
if (mHasBeenDecoded && !invalidRect.IsEmpty()) {
|
||||
// Don't send partial invalidations if we've been decoded before.
|
||||
invalidRect = mDecoded ? GetFirstFrameRect()
|
||||
: nsIntRect();
|
||||
}
|
||||
|
||||
ImageStatusDiff diff =
|
||||
request ? image->mStatusTracker->Difference(request->mStatusTracker)
|
||||
: image->mStatusTracker->DecodeStateAsDifference();
|
||||
@ -3058,13 +3065,15 @@ RasterImage::FinishedSomeDecoding(eShutdownIntent aIntent /* = eShutdownIntent_D
|
||||
// FinishedSomeDecoding on the stack.
|
||||
NS_WARNING("Recursively notifying in RasterImage::FinishedSomeDecoding!");
|
||||
mStatusDiff.Combine(diff);
|
||||
mInvalidRect.Union(invalidRect);
|
||||
} else {
|
||||
MOZ_ASSERT(mStatusDiff.IsNoChange(), "Shouldn't have an accumulated change at this point");
|
||||
MOZ_ASSERT(mInvalidRect.IsEmpty(), "Shouldn't have an accumulated invalidation rect here");
|
||||
|
||||
while (!diff.IsNoChange()) {
|
||||
while (!diff.IsNoChange() || !invalidRect.IsEmpty()) {
|
||||
// Tell the observers what happened.
|
||||
mNotifying = true;
|
||||
image->mStatusTracker->SyncNotifyDifference(diff);
|
||||
image->mStatusTracker->SyncNotifyDifference(diff, invalidRect);
|
||||
mNotifying = false;
|
||||
|
||||
// Gather any status changes that may have occurred as a result of sending
|
||||
@ -3072,6 +3081,8 @@ RasterImage::FinishedSomeDecoding(eShutdownIntent aIntent /* = eShutdownIntent_D
|
||||
// notifications for them next.
|
||||
diff = mStatusDiff;
|
||||
mStatusDiff = ImageStatusDiff::NoChange();
|
||||
invalidRect = mInvalidRect;
|
||||
mInvalidRect = nsIntRect();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3484,31 +3495,6 @@ RasterImage::DecodePool::DecodeSomeOfImage(RasterImage* aImg,
|
||||
aImg->mDecodeRequest->mChunkCount += chunkCount;
|
||||
}
|
||||
|
||||
// Flush invalidations (and therefore paint) now that we've decoded all the
|
||||
// chunks we're going to.
|
||||
//
|
||||
// However, don't paint if:
|
||||
//
|
||||
// * This was an until-size decode. Until-size decodes are always followed
|
||||
// by normal decodes, so don't bother painting.
|
||||
//
|
||||
// * The decoder flagged an error. The decoder may have written garbage
|
||||
// into the output buffer; don't paint it to the screen.
|
||||
//
|
||||
// * We have all the source data. This disables progressive display of
|
||||
// previously-decoded images, thus letting us finish decoding faster,
|
||||
// since we don't waste time painting while we decode.
|
||||
// Decoder::PostFrameStop() will flush invalidations once the decode is
|
||||
// done.
|
||||
|
||||
if (aDecodeType != DECODE_TYPE_UNTIL_SIZE &&
|
||||
!aImg->mDecoder->HasError() &&
|
||||
!aImg->mHasSourceData) {
|
||||
aImg->mInDecoder = true;
|
||||
aImg->mDecoder->FlushInvalidations();
|
||||
aImg->mInDecoder = false;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -552,6 +552,8 @@ private:
|
||||
uint32_t GetCurrentFrameIndex() const;
|
||||
uint32_t GetRequestedFrameIndex(uint32_t aWhichFrame) const;
|
||||
|
||||
nsIntRect GetFirstFrameRect();
|
||||
|
||||
size_t SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation,
|
||||
MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
@ -662,6 +664,7 @@ private: // data
|
||||
|
||||
// Notification state. Used to avoid recursive notifications.
|
||||
ImageStatusDiff mStatusDiff;
|
||||
nsIntRect mInvalidRect;
|
||||
bool mNotifying:1;
|
||||
|
||||
// Boolean flags (clustered together to conserve space):
|
||||
|
@ -571,8 +571,10 @@ VectorImage::SendInvalidationNotifications()
|
||||
|
||||
if (mStatusTracker) {
|
||||
SurfaceCache::Discard(this);
|
||||
mStatusTracker->FrameChanged(&nsIntRect::GetMaxSizedIntRect());
|
||||
mStatusTracker->OnStopFrame();
|
||||
ImageStatusDiff diff;
|
||||
diff.diffState = FLAG_FRAME_STOPPED;
|
||||
mStatusTracker->ApplyDifference(diff);
|
||||
mStatusTracker->SyncNotifyDifference(diff, nsIntRect::GetMaxSizedIntRect());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1119,17 +1121,11 @@ VectorImage::OnSVGDocumentLoaded()
|
||||
|
||||
// Tell *our* observers that we're done loading.
|
||||
if (mStatusTracker) {
|
||||
nsRefPtr<imgStatusTracker> clone = mStatusTracker->CloneForRecording();
|
||||
imgDecoderObserver* observer = clone->GetDecoderObserver();
|
||||
|
||||
observer->OnStartContainer(); // Signal that width/height are available.
|
||||
observer->FrameChanged(&nsIntRect::GetMaxSizedIntRect());
|
||||
observer->OnStopFrame();
|
||||
observer->OnStopDecode(NS_OK); // Unblock page load.
|
||||
|
||||
ImageStatusDiff diff = mStatusTracker->Difference(clone);
|
||||
ImageStatusDiff diff;
|
||||
diff.diffState = FLAG_HAS_SIZE | FLAG_FRAME_STOPPED | FLAG_DECODE_STOPPED |
|
||||
FLAG_ONLOAD_UNBLOCKED;
|
||||
mStatusTracker->ApplyDifference(diff);
|
||||
mStatusTracker->SyncNotifyDifference(diff);
|
||||
mStatusTracker->SyncNotifyDifference(diff, nsIntRect::GetMaxSizedIntRect());
|
||||
}
|
||||
|
||||
EvaluateAnimation();
|
||||
|
@ -66,20 +66,6 @@ public:
|
||||
*/
|
||||
virtual void OnStartContainer() = 0;
|
||||
|
||||
/**
|
||||
* Decode notification.
|
||||
*
|
||||
* Called when we know a frame has begun decoding.
|
||||
*/
|
||||
virtual void OnStartFrame() = 0;
|
||||
|
||||
/**
|
||||
* Decode notification.
|
||||
*
|
||||
* called when there is more to paint.
|
||||
*/
|
||||
virtual void FrameChanged(const nsIntRect * aDirtyRect) = 0;
|
||||
|
||||
/**
|
||||
* Decode notification.
|
||||
*
|
||||
|
@ -40,14 +40,17 @@ using namespace mozilla;
|
||||
using namespace mozilla::image;
|
||||
|
||||
#if defined(PR_LOGGING)
|
||||
PRLogModuleInfo *
|
||||
PRLogModuleInfo*
|
||||
GetImgLog()
|
||||
{
|
||||
static PRLogModuleInfo *sImgLog;
|
||||
static PRLogModuleInfo* sImgLog;
|
||||
if (!sImgLog)
|
||||
sImgLog = PR_NewLogModule("imgRequest");
|
||||
return sImgLog;
|
||||
}
|
||||
#define LOG_TEST(level) (GetImgLog() && PR_LOG_TEST(GetImgLog(), (level)))
|
||||
#else
|
||||
#define LOG_TEST(level) false
|
||||
#endif
|
||||
|
||||
NS_IMPL_ISUPPORTS(imgRequest,
|
||||
@ -367,6 +370,21 @@ nsresult imgRequest::GetURI(ImageURL **aURI)
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult imgRequest::GetCurrentURI(nsIURI **aURI)
|
||||
{
|
||||
MOZ_ASSERT(aURI);
|
||||
|
||||
LOG_FUNC(GetImgLog(), "imgRequest::GetCurrentURI");
|
||||
|
||||
if (mCurrentURI) {
|
||||
*aURI = mCurrentURI;
|
||||
NS_ADDREF(*aURI);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult imgRequest::GetImageErrorCode()
|
||||
{
|
||||
return mImageErrorCode;
|
||||
@ -1064,16 +1082,24 @@ imgRequest::OnRedirectVerifyCallback(nsresult result)
|
||||
mTimedChannel = do_QueryInterface(mChannel);
|
||||
mNewRedirectChannel = nullptr;
|
||||
|
||||
#if defined(PR_LOGGING)
|
||||
nsAutoCString oldspec;
|
||||
if (mCurrentURI)
|
||||
mCurrentURI->GetSpec(oldspec);
|
||||
LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnChannelRedirect", "old", oldspec.get());
|
||||
#endif
|
||||
if (LOG_TEST(PR_LOG_DEBUG)) {
|
||||
nsAutoCString spec;
|
||||
if (mCurrentURI)
|
||||
mCurrentURI->GetSpec(spec);
|
||||
LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnChannelRedirect", "old", spec.get());
|
||||
}
|
||||
|
||||
// make sure we have a protocol that returns data rather than opens
|
||||
// an external application, e.g. mailto:
|
||||
mChannel->GetURI(getter_AddRefs(mCurrentURI));
|
||||
|
||||
if (LOG_TEST(PR_LOG_DEBUG)) {
|
||||
nsAutoCString spec;
|
||||
if (mCurrentURI)
|
||||
mCurrentURI->GetSpec(spec);
|
||||
LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnChannelRedirect", "new", spec.get());
|
||||
}
|
||||
|
||||
bool doesNotReturnData = false;
|
||||
nsresult rv =
|
||||
NS_URIChainHasFlags(mCurrentURI, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
|
||||
|
@ -137,6 +137,7 @@ public:
|
||||
|
||||
// OK to use on any thread.
|
||||
nsresult GetURI(ImageURL **aURI);
|
||||
nsresult GetCurrentURI(nsIURI **aURI);
|
||||
|
||||
nsresult GetImageErrorCode(void);
|
||||
|
||||
|
@ -537,6 +537,14 @@ NS_IMETHODIMP imgRequestProxy::GetURI(nsIURI **aURI)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult imgRequestProxy::GetCurrentURI(nsIURI **aURI)
|
||||
{
|
||||
if (!GetOwner())
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
return GetOwner()->GetCurrentURI(aURI);
|
||||
}
|
||||
|
||||
nsresult imgRequestProxy::GetURI(ImageURL **aURI)
|
||||
{
|
||||
if (!mURI)
|
||||
|
@ -62,22 +62,6 @@ public:
|
||||
tracker->RecordStartContainer(image);
|
||||
}
|
||||
|
||||
virtual void OnStartFrame() MOZ_OVERRIDE
|
||||
{
|
||||
LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStartFrame");
|
||||
nsRefPtr<imgStatusTracker> tracker = mTracker.get();
|
||||
if (!tracker) { return; }
|
||||
tracker->RecordStartFrame();
|
||||
}
|
||||
|
||||
virtual void FrameChanged(const nsIntRect* dirtyRect) MOZ_OVERRIDE
|
||||
{
|
||||
LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::FrameChanged");
|
||||
nsRefPtr<imgStatusTracker> tracker = mTracker.get();
|
||||
if (!tracker) { return; }
|
||||
tracker->RecordFrameChanged(dirtyRect);
|
||||
}
|
||||
|
||||
virtual void OnStopFrame() MOZ_OVERRIDE
|
||||
{
|
||||
LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStopFrame");
|
||||
@ -159,7 +143,6 @@ imgStatusTracker::imgStatusTracker(const imgStatusTracker& aOther)
|
||||
// - mProperties, because we don't need it and it'd just point at the same
|
||||
// object
|
||||
// - mConsumers, because we don't need to talk to consumers
|
||||
// - mInvalidRect, because the point of it is to be fired off and reset
|
||||
{
|
||||
mTrackerObserver = new imgStatusTrackerObserver(this);
|
||||
}
|
||||
@ -370,7 +353,7 @@ imgStatusTracker::NotifyCurrentState(imgRequestProxy* proxy)
|
||||
|
||||
#define NOTIFY_IMAGE_OBSERVERS(func) \
|
||||
do { \
|
||||
ProxyArray::ForwardIterator iter(proxies); \
|
||||
ProxyArray::ForwardIterator iter(aProxies); \
|
||||
while (iter.HasMore()) { \
|
||||
nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get(); \
|
||||
if (proxy && !proxy->NotificationsDeferred()) { \
|
||||
@ -380,57 +363,57 @@ imgStatusTracker::NotifyCurrentState(imgRequestProxy* proxy)
|
||||
} while (false);
|
||||
|
||||
/* static */ void
|
||||
imgStatusTracker::SyncNotifyState(ProxyArray& proxies,
|
||||
bool hasImage, uint32_t state,
|
||||
nsIntRect& dirtyRect)
|
||||
imgStatusTracker::SyncNotifyState(ProxyArray& aProxies,
|
||||
bool aHasImage, uint32_t aState,
|
||||
const nsIntRect& aDirtyRect)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
// OnStartRequest
|
||||
if (state & FLAG_REQUEST_STARTED)
|
||||
if (aState & FLAG_REQUEST_STARTED)
|
||||
NOTIFY_IMAGE_OBSERVERS(OnStartRequest());
|
||||
|
||||
// OnStartContainer
|
||||
if (state & FLAG_HAS_SIZE)
|
||||
if (aState & FLAG_HAS_SIZE)
|
||||
NOTIFY_IMAGE_OBSERVERS(OnStartContainer());
|
||||
|
||||
// OnStartDecode
|
||||
if (state & FLAG_DECODE_STARTED)
|
||||
if (aState & FLAG_DECODE_STARTED)
|
||||
NOTIFY_IMAGE_OBSERVERS(OnStartDecode());
|
||||
|
||||
// BlockOnload
|
||||
if (state & FLAG_ONLOAD_BLOCKED)
|
||||
if (aState & FLAG_ONLOAD_BLOCKED)
|
||||
NOTIFY_IMAGE_OBSERVERS(BlockOnload());
|
||||
|
||||
if (hasImage) {
|
||||
if (aHasImage) {
|
||||
// OnFrameUpdate
|
||||
// If there's any content in this frame at all (always true for
|
||||
// vector images, true for raster images that have decoded at
|
||||
// least one frame) then send OnFrameUpdate.
|
||||
if (!dirtyRect.IsEmpty())
|
||||
NOTIFY_IMAGE_OBSERVERS(OnFrameUpdate(&dirtyRect));
|
||||
if (!aDirtyRect.IsEmpty())
|
||||
NOTIFY_IMAGE_OBSERVERS(OnFrameUpdate(&aDirtyRect));
|
||||
|
||||
if (state & FLAG_FRAME_STOPPED)
|
||||
if (aState & FLAG_FRAME_STOPPED)
|
||||
NOTIFY_IMAGE_OBSERVERS(OnStopFrame());
|
||||
|
||||
// OnImageIsAnimated
|
||||
if (state & FLAG_IS_ANIMATED)
|
||||
if (aState & FLAG_IS_ANIMATED)
|
||||
NOTIFY_IMAGE_OBSERVERS(OnImageIsAnimated());
|
||||
}
|
||||
|
||||
// Send UnblockOnload before OnStopDecode and OnStopRequest. This allows
|
||||
// observers that can fire events when they receive those notifications to do
|
||||
// so then, instead of being forced to wait for UnblockOnload.
|
||||
if (state & FLAG_ONLOAD_UNBLOCKED) {
|
||||
if (aState & FLAG_ONLOAD_UNBLOCKED) {
|
||||
NOTIFY_IMAGE_OBSERVERS(UnblockOnload());
|
||||
}
|
||||
|
||||
if (state & FLAG_DECODE_STOPPED) {
|
||||
NS_ABORT_IF_FALSE(hasImage, "stopped decoding without ever having an image?");
|
||||
if (aState & FLAG_DECODE_STOPPED) {
|
||||
MOZ_ASSERT(aHasImage, "Stopped decoding without ever having an image?");
|
||||
NOTIFY_IMAGE_OBSERVERS(OnStopDecode());
|
||||
}
|
||||
|
||||
if (state & FLAG_REQUEST_STOPPED) {
|
||||
NOTIFY_IMAGE_OBSERVERS(OnStopRequest(state & FLAG_MULTIPART_STOPPED));
|
||||
if (aState & FLAG_REQUEST_STOPPED) {
|
||||
NOTIFY_IMAGE_OBSERVERS(OnStopRequest(aState & FLAG_MULTIPART_STOPPED));
|
||||
}
|
||||
}
|
||||
|
||||
@ -440,22 +423,6 @@ imgStatusTracker::Difference(imgStatusTracker* aOther) const
|
||||
MOZ_ASSERT(aOther, "aOther cannot be null");
|
||||
ImageStatusDiff diff;
|
||||
diff.diffState = ~mState & aOther->mState;
|
||||
|
||||
// Only record partial invalidations if we haven't been decoded before.
|
||||
// When images are re-decoded after discarding, we don't want to display
|
||||
// partially decoded versions to the user.
|
||||
const uint32_t combinedState = mState | aOther->mState;
|
||||
const bool doInvalidations = !(mState & FLAG_DECODE_STOPPED) ||
|
||||
aOther->mState & FLAG_DECODE_STOPPED ||
|
||||
combinedState & FLAG_HAS_ERROR;
|
||||
|
||||
// Record and reset the invalid rectangle.
|
||||
// XXX(seth): We shouldn't be resetting anything here; see bug 910441.
|
||||
if (doInvalidations) {
|
||||
diff.invalidRect = aOther->mInvalidRect;
|
||||
aOther->mInvalidRect.SetEmpty();
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
@ -476,18 +443,15 @@ imgStatusTracker::ApplyDifference(const ImageStatusDiff& aDiff)
|
||||
}
|
||||
|
||||
void
|
||||
imgStatusTracker::SyncNotifyDifference(const ImageStatusDiff& diff)
|
||||
imgStatusTracker::SyncNotifyDifference(const ImageStatusDiff& aDiff,
|
||||
const nsIntRect& aInvalidRect /* = nsIntRect() */)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only");
|
||||
LOG_SCOPE(GetImgLog(), "imgStatusTracker::SyncNotifyDifference");
|
||||
|
||||
nsIntRect invalidRect = mInvalidRect.Union(diff.invalidRect);
|
||||
SyncNotifyState(mConsumers, !!mImage, aDiff.diffState, aInvalidRect);
|
||||
|
||||
SyncNotifyState(mConsumers, !!mImage, diff.diffState, invalidRect);
|
||||
|
||||
mInvalidRect.SetEmpty();
|
||||
|
||||
if (diff.diffState & FLAG_HAS_ERROR) {
|
||||
if (aDiff.diffState & FLAG_HAS_ERROR) {
|
||||
FireFailureNotification();
|
||||
}
|
||||
}
|
||||
@ -648,14 +612,6 @@ imgStatusTracker::SendStartContainer(imgRequestProxy* aProxy)
|
||||
aProxy->OnStartContainer();
|
||||
}
|
||||
|
||||
void
|
||||
imgStatusTracker::RecordStartFrame()
|
||||
{
|
||||
mInvalidRect.SetEmpty();
|
||||
}
|
||||
|
||||
// No SendStartFrame since it's not observed below us.
|
||||
|
||||
void
|
||||
imgStatusTracker::RecordStopFrame()
|
||||
{
|
||||
@ -746,23 +702,6 @@ imgStatusTracker::OnUnlockedDraw()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
imgStatusTracker::RecordFrameChanged(const nsIntRect* aDirtyRect)
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mImage,
|
||||
"RecordFrameChanged called before we have an Image");
|
||||
mInvalidRect = mInvalidRect.Union(*aDirtyRect);
|
||||
}
|
||||
|
||||
void
|
||||
imgStatusTracker::SendFrameChanged(imgRequestProxy* aProxy,
|
||||
const nsIntRect* aDirtyRect)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!aProxy->NotificationsDeferred())
|
||||
aProxy->OnFrameUpdate(aDirtyRect);
|
||||
}
|
||||
|
||||
/* non-virtual sort-of-nsIRequestObserver methods */
|
||||
void
|
||||
imgStatusTracker::RecordStartRequest()
|
||||
@ -895,22 +834,6 @@ imgStatusTracker::OnDiscard()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
imgStatusTracker::FrameChanged(const nsIntRect* aDirtyRect)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
RecordFrameChanged(aDirtyRect);
|
||||
|
||||
/* notify the kids */
|
||||
ProxyArray::ForwardIterator iter(mConsumers);
|
||||
while (iter.HasMore()) {
|
||||
nsRefPtr<imgRequestProxy> proxy = iter.GetNext().get();
|
||||
if (proxy) {
|
||||
SendFrameChanged(proxy, aDirtyRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
imgStatusTracker::OnStopFrame()
|
||||
{
|
||||
|
@ -46,8 +46,7 @@ enum {
|
||||
struct ImageStatusDiff
|
||||
{
|
||||
ImageStatusDiff()
|
||||
: invalidRect()
|
||||
, diffState(0)
|
||||
: diffState(0)
|
||||
{ }
|
||||
|
||||
static ImageStatusDiff NoChange() { return ImageStatusDiff(); }
|
||||
@ -55,17 +54,14 @@ struct ImageStatusDiff
|
||||
|
||||
bool operator!=(const ImageStatusDiff& aOther) const { return !(*this == aOther); }
|
||||
bool operator==(const ImageStatusDiff& aOther) const {
|
||||
return aOther.invalidRect == invalidRect
|
||||
&& aOther.diffState == diffState;
|
||||
return aOther.diffState == diffState;
|
||||
}
|
||||
|
||||
void Combine(const ImageStatusDiff& aOther) {
|
||||
invalidRect = invalidRect.Union(aOther.invalidRect);
|
||||
diffState |= aOther.diffState;
|
||||
}
|
||||
|
||||
nsIntRect invalidRect;
|
||||
uint32_t diffState;
|
||||
uint32_t diffState;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
@ -179,7 +175,7 @@ public:
|
||||
void RecordLoaded();
|
||||
|
||||
// Shorthand for recording all the decode notifications: StartDecode,
|
||||
// StartFrame, DataAvailable, StopFrame, StopDecode.
|
||||
// DataAvailable, StopFrame, StopDecode.
|
||||
void RecordDecoded();
|
||||
|
||||
/* non-virtual imgDecoderObserver methods */
|
||||
@ -189,10 +185,6 @@ public:
|
||||
void SendStartDecode(imgRequestProxy* aProxy);
|
||||
void RecordStartContainer(imgIContainer* aContainer);
|
||||
void SendStartContainer(imgRequestProxy* aProxy);
|
||||
void RecordStartFrame();
|
||||
// No SendStartFrame since it's not observed below us.
|
||||
void RecordFrameChanged(const nsIntRect* aDirtyRect);
|
||||
void SendFrameChanged(imgRequestProxy* aProxy, const nsIntRect* aDirtyRect);
|
||||
void RecordStopFrame();
|
||||
void SendStopFrame(imgRequestProxy* aProxy);
|
||||
void RecordStopDecode(nsresult statusg);
|
||||
@ -219,7 +211,6 @@ public:
|
||||
void OnDataAvailable();
|
||||
void OnStopRequest(bool aLastPart, nsresult aStatus);
|
||||
void OnDiscard();
|
||||
void FrameChanged(const nsIntRect* aDirtyRect);
|
||||
void OnUnlockedDraw();
|
||||
// This is called only by VectorImage, and only to ensure tests work
|
||||
// properly. Do not use it.
|
||||
@ -265,9 +256,8 @@ public:
|
||||
// Notify for the changes captured in an ImageStatusDiff. Because this may
|
||||
// result in recursive notifications, no decoding locks may be held.
|
||||
// Called on the main thread only.
|
||||
void SyncNotifyDifference(const mozilla::image::ImageStatusDiff& aDiff);
|
||||
|
||||
nsIntRect GetInvalidRect() const { return mInvalidRect; }
|
||||
void SyncNotifyDifference(const mozilla::image::ImageStatusDiff& aDiff,
|
||||
const nsIntRect& aInvalidRect = nsIntRect());
|
||||
|
||||
private:
|
||||
typedef nsTObserverArray<mozilla::WeakPtr<imgRequestProxy>> ProxyArray;
|
||||
@ -282,16 +272,12 @@ private:
|
||||
|
||||
// Main thread only, since imgRequestProxy calls are expected on the main
|
||||
// thread, and mConsumers is not threadsafe.
|
||||
static void SyncNotifyState(ProxyArray& proxies,
|
||||
bool hasImage, uint32_t state,
|
||||
nsIntRect& dirtyRect);
|
||||
static void SyncNotifyState(ProxyArray& aProxies,
|
||||
bool aHasImage, uint32_t aState,
|
||||
const nsIntRect& aInvalidRect);
|
||||
|
||||
nsCOMPtr<nsIRunnable> mRequestRunnable;
|
||||
|
||||
// The invalid area of the most recent frame we know about. (All previous
|
||||
// frames are assumed to be fully valid.)
|
||||
nsIntRect mInvalidRect;
|
||||
|
||||
// This weak ref should be set null when the image goes out of scope.
|
||||
mozilla::image::Image* mImage;
|
||||
|
||||
|
@ -33,6 +33,7 @@ var gRanEvent = false;
|
||||
var gObserver;
|
||||
var gImg1;
|
||||
var gImg2;
|
||||
var gFirstImageLoaded = false;
|
||||
var gOuter;
|
||||
var gFinished = false;
|
||||
var gFirstRequest = null;
|
||||
@ -71,20 +72,37 @@ function failTest() {
|
||||
cleanUpAndFinish();
|
||||
}
|
||||
|
||||
function waitForLoadAndTest(image) {
|
||||
return () => {
|
||||
// Draw the image into a canvas to ensure it's decoded.
|
||||
var canvas = document.createElement('canvas');
|
||||
var context = canvas.getContext('2d');
|
||||
context.drawImage(image, 0, 0);
|
||||
|
||||
// Attach the observer.
|
||||
var imgLoadingContent = image.QueryInterface(Ci.nsIImageLoadingContent);
|
||||
imgLoadingContent.addObserver(gOuter);
|
||||
|
||||
// If the other image already loaded, add both images to the document, which
|
||||
// begins the real test.
|
||||
if (gFirstImageLoaded) {
|
||||
gContent.appendChild(gImg1);
|
||||
gContent.appendChild(gImg2);
|
||||
} else {
|
||||
gFirstImageLoaded = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function main() {
|
||||
gImg1 = new Image();
|
||||
gImg2 = new Image();
|
||||
|
||||
// Create, customize & attach decoder observer
|
||||
// Create and customize decoder observer
|
||||
var obs = new ImageDecoderObserverStub();
|
||||
obs.frameUpdate = frameUpdate;
|
||||
|
||||
gOuter = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools).createScriptedObserver(obs);
|
||||
var imgLoadingContent = gImg1.QueryInterface(Ci.nsIImageLoadingContent);
|
||||
imgLoadingContent.addObserver(gOuter);
|
||||
|
||||
imgLoadingContent = gImg2.QueryInterface(Ci.nsIImageLoadingContent);
|
||||
imgLoadingContent.addObserver(gOuter);
|
||||
|
||||
// We want to test the cold loading behavior, so clear cache in case an
|
||||
// earlier test got our image in there already.
|
||||
@ -94,8 +112,9 @@ function main() {
|
||||
gImg1.src = "animated1.gif";
|
||||
gImg2.src = "animated2.gif";
|
||||
|
||||
gContent.appendChild(gImg1);
|
||||
gContent.appendChild(gImg2);
|
||||
// Wait for each image to load.
|
||||
gImg1.addEventListener('load', waitForLoadAndTest(gImg1));
|
||||
gImg2.addEventListener('load', waitForLoadAndTest(gImg2));
|
||||
|
||||
// In case something goes wrong, fail earlier than mochitest timeout,
|
||||
// and with more information.
|
||||
|
@ -583,6 +583,19 @@ MessageChannel::ShouldDeferMessage(const Message& aMsg)
|
||||
return mSide == ParentSide && aMsg.transaction_id() != mCurrentTransaction;
|
||||
}
|
||||
|
||||
// Predicate that is true for messages that should be consolidated if 'compress' is set.
|
||||
class MatchingKinds {
|
||||
typedef IPC::Message Message;
|
||||
Message::msgid_t mType;
|
||||
int32_t mRoutingId;
|
||||
public:
|
||||
MatchingKinds(Message::msgid_t aType, int32_t aRoutingId) :
|
||||
mType(aType), mRoutingId(aRoutingId) {}
|
||||
bool operator()(const Message &msg) {
|
||||
return msg.type() == mType && msg.routing_id() == mRoutingId;
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
MessageChannel::OnMessageReceivedFromLink(const Message& aMsg)
|
||||
{
|
||||
@ -604,15 +617,22 @@ MessageChannel::OnMessageReceivedFromLink(const Message& aMsg)
|
||||
// Prioritized messages cannot be compressed.
|
||||
MOZ_ASSERT(!aMsg.compress() || aMsg.priority() == IPC::Message::PRIORITY_NORMAL);
|
||||
|
||||
bool compress = (aMsg.compress() && !mPending.empty() &&
|
||||
mPending.back().type() == aMsg.type() &&
|
||||
mPending.back().routing_id() == aMsg.routing_id());
|
||||
bool compress = (aMsg.compress() && !mPending.empty());
|
||||
if (compress) {
|
||||
// This message type has compression enabled, and the back of the
|
||||
// queue was the same message type and routed to the same destination.
|
||||
// Replace it with the newer message.
|
||||
MOZ_ASSERT(mPending.back().compress());
|
||||
mPending.pop_back();
|
||||
// Check the message queue for another message with this type/destination.
|
||||
auto it = std::find_if(mPending.rbegin(), mPending.rend(),
|
||||
MatchingKinds(aMsg.type(), aMsg.routing_id()));
|
||||
if (it != mPending.rend()) {
|
||||
// This message type has compression enabled, and the queue holds
|
||||
// a message with the same message type and routed to the same destination.
|
||||
// Erase it. Note that, since we always compress these redundancies, There Can
|
||||
// Be Only One.
|
||||
MOZ_ASSERT((*it).compress());
|
||||
mPending.erase((++it).base());
|
||||
} else {
|
||||
// No other messages with the same type/destination exist.
|
||||
compress = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldWakeUp = AwaitingInterruptReply() ||
|
||||
|
@ -500,7 +500,7 @@ AsmJSHandleExecutionInterrupt()
|
||||
{
|
||||
AsmJSActivation *act = PerThreadData::innermostAsmJSActivation();
|
||||
act->module().setInterrupted(true);
|
||||
bool ret = HandleExecutionInterrupt(act->cx());
|
||||
bool ret = CheckForInterrupt(act->cx());
|
||||
act->module().setInterrupted(false);
|
||||
return ret;
|
||||
}
|
||||
@ -673,8 +673,8 @@ AddressOf(AsmJSImmKind kind, ExclusiveContext *cx)
|
||||
switch (kind) {
|
||||
case AsmJSImm_Runtime:
|
||||
return cx->runtimeAddressForJit();
|
||||
case AsmJSImm_RuntimeInterrupt:
|
||||
return cx->runtimeAddressOfInterrupt();
|
||||
case AsmJSImm_RuntimeInterruptUint32:
|
||||
return cx->runtimeAddressOfInterruptUint32();
|
||||
case AsmJSImm_StackLimit:
|
||||
return cx->stackLimitAddressForJitCode(StackForUntrustedScript);
|
||||
case AsmJSImm_ReportOverRecursed:
|
||||
|
@ -2664,24 +2664,6 @@ if test "$ac_cv_cpp_unused_required" = yes ; then
|
||||
fi
|
||||
|
||||
|
||||
dnl Some compilers have trouble comparing a constant reference to a templatized
|
||||
dnl class to zero, and require an explicit operator==() to be defined that takes
|
||||
dnl an int. This test separates the strong from the weak.
|
||||
|
||||
AC_CACHE_CHECK(for trouble comparing to zero near std::operator!=(),
|
||||
ac_cv_trouble_comparing_to_zero,
|
||||
[AC_TRY_COMPILE([#include <algorithm>
|
||||
template <class T> class Foo {};
|
||||
class T2;
|
||||
template <class T> int operator==(const T2*, const T&) { return 0; }
|
||||
template <class T> int operator!=(const T2*, const T&) { return 0; }],
|
||||
[Foo<int> f; return (0 != f);],
|
||||
ac_cv_trouble_comparing_to_zero=no,
|
||||
ac_cv_trouble_comparing_to_zero=yes)])
|
||||
if test "$ac_cv_trouble_comparing_to_zero" = yes ; then
|
||||
AC_DEFINE(HAVE_CPP_TROUBLE_COMPARING_TO_ZERO)
|
||||
fi
|
||||
|
||||
# try harder, when checking for __thread support, see bug 521750 comment #33 and below
|
||||
# We pass MOZ_OPTIMIZE_LDFLAGS to the linker because if dead_strip is
|
||||
# enabled, the linker in xcode 4.1 will crash. Without this it would crash when
|
||||
|
@ -271,6 +271,7 @@ class GCRuntime
|
||||
void decFJMinorCollecting() { fjCollectionCounter--; }
|
||||
|
||||
bool triggerGC(JS::gcreason::Reason reason);
|
||||
void maybeAllocTriggerZoneGC(Zone *zone, const AutoLockGC &lock);
|
||||
bool triggerZoneGC(Zone *zone, JS::gcreason::Reason reason);
|
||||
bool maybeGC(Zone *zone);
|
||||
void maybePeriodicFullGC();
|
||||
@ -481,11 +482,15 @@ class GCRuntime
|
||||
void freeUnusedLifoBlocksAfterSweeping(LifoAlloc *lifo);
|
||||
void freeAllLifoBlocksAfterSweeping(LifoAlloc *lifo);
|
||||
|
||||
// Public here for ReleaseArenaLists and FinalizeTypedArenas.
|
||||
void releaseArena(ArenaHeader *aheader, const AutoLockGC &lock);
|
||||
|
||||
private:
|
||||
// For ArenaLists::allocateFromArena()
|
||||
friend class ArenaLists;
|
||||
Chunk *pickChunk(const AutoLockGC &lock,
|
||||
AutoMaybeStartBackgroundAllocation &maybeStartBGAlloc);
|
||||
ArenaHeader *allocateArena(Chunk *chunk, Zone *zone, AllocKind kind, const AutoLockGC &lock);
|
||||
inline void arenaAllocatedDuringGC(JS::Zone *zone, ArenaHeader *arena);
|
||||
|
||||
template <AllowGC allowGC>
|
||||
@ -543,8 +548,7 @@ class GCRuntime
|
||||
bool sweepPhase(SliceBudget &sliceBudget);
|
||||
void endSweepPhase(bool lastGC);
|
||||
void sweepZones(FreeOp *fop, bool lastGC);
|
||||
void decommitArenasFromAvailableList(Chunk **availableListHeadp);
|
||||
void decommitArenas();
|
||||
void decommitArenas(const AutoLockGC &lock);
|
||||
void expireChunksAndArenas(bool shouldShrink, const AutoLockGC &lock);
|
||||
void sweepBackgroundThings();
|
||||
void assertBackgroundSweepingFinished();
|
||||
|
@ -37,6 +37,7 @@ struct Runtime;
|
||||
|
||||
namespace js {
|
||||
|
||||
class AutoLockGC;
|
||||
class FreeOp;
|
||||
|
||||
#ifdef DEBUG
|
||||
@ -945,9 +946,10 @@ struct Chunk
|
||||
inline void insertToAvailableList(Chunk **insertPoint);
|
||||
inline void removeFromAvailableList();
|
||||
|
||||
ArenaHeader *allocateArena(JS::Zone *zone, AllocKind kind);
|
||||
ArenaHeader *allocateArena(JSRuntime *rt, JS::Zone *zone, AllocKind kind,
|
||||
const AutoLockGC &lock);
|
||||
|
||||
void releaseArena(ArenaHeader *aheader);
|
||||
void releaseArena(JSRuntime *rt, ArenaHeader *aheader, const AutoLockGC &lock);
|
||||
void recycleArena(ArenaHeader *aheader, SortedArenaList &dest, AllocKind thingKind,
|
||||
size_t thingsPerArena);
|
||||
|
||||
@ -1147,17 +1149,6 @@ ArenaHeader::unsetAllocDuringSweep()
|
||||
auxNextLink = 0;
|
||||
}
|
||||
|
||||
inline void
|
||||
ReleaseArenaList(ArenaHeader *aheader)
|
||||
{
|
||||
ArenaHeader *next;
|
||||
for (; aheader; aheader = next) {
|
||||
// Copy aheader->next before releasing.
|
||||
next = aheader->next;
|
||||
aheader->chunk()->releaseArena(aheader);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
AssertValidColor(const TenuredCell *thing, uint32_t color)
|
||||
{
|
||||
|
@ -152,7 +152,7 @@ NativeRegExpMacroAssembler::GenerateCode(JSContext *cx, bool match_only)
|
||||
|
||||
// Check if we have space on the stack.
|
||||
Label stack_ok;
|
||||
void *stack_limit = &runtime->mainThread.jitStackLimit;
|
||||
void *stack_limit = runtime->mainThread.addressOfJitStackLimit();
|
||||
masm.branchPtr(Assembler::Below, AbsoluteAddress(stack_limit), StackPointer, &stack_ok);
|
||||
|
||||
// Exit with an exception. There is not enough space on the stack
|
||||
@ -502,7 +502,7 @@ NativeRegExpMacroAssembler::Backtrack()
|
||||
// Check for an interrupt.
|
||||
Label noInterrupt;
|
||||
masm.branch32(Assembler::Equal,
|
||||
AbsoluteAddress(&runtime->interrupt), Imm32(0),
|
||||
AbsoluteAddress(runtime->addressOfInterruptUint32()), Imm32(0),
|
||||
&noInterrupt);
|
||||
masm.movePtr(ImmWord(RegExpRunStatus_Error), temp0);
|
||||
masm.jump(&exit_label_);
|
||||
|
@ -496,7 +496,7 @@ bool
|
||||
BaselineCompiler::emitStackCheck(bool earlyCheck)
|
||||
{
|
||||
Label skipCall;
|
||||
uintptr_t *limitAddr = &cx->runtime()->mainThread.jitStackLimit;
|
||||
void *limitAddr = cx->runtime()->mainThread.addressOfJitStackLimit();
|
||||
uint32_t slotsSize = script->nslots() * sizeof(Value);
|
||||
uint32_t tolerance = earlyCheck ? slotsSize : 0;
|
||||
|
||||
@ -646,7 +646,7 @@ BaselineCompiler::emitInterruptCheck()
|
||||
frame.syncStack(0);
|
||||
|
||||
Label done;
|
||||
void *interrupt = (void *)&cx->runtime()->interrupt;
|
||||
void *interrupt = cx->runtimeAddressOfInterruptUint32();
|
||||
masm.branch32(Assembler::Equal, AbsoluteAddress(interrupt), Imm32(0), &done);
|
||||
|
||||
prepareVMCall();
|
||||
|
@ -3772,7 +3772,7 @@ CodeGenerator::visitCheckOverRecursedPar(LCheckOverRecursedPar *lir)
|
||||
Register tempReg = ToRegister(lir->getTempReg());
|
||||
|
||||
masm.loadPtr(Address(cxReg, offsetof(ForkJoinContext, perThreadData)), tempReg);
|
||||
masm.loadPtr(Address(tempReg, offsetof(PerThreadData, jitStackLimit)), tempReg);
|
||||
masm.loadPtr(Address(tempReg, PerThreadData::offsetOfJitStackLimit()), tempReg);
|
||||
|
||||
// Conditional forward (unlikely) branch to failure.
|
||||
CheckOverRecursedFailure *ool = new(alloc()) CheckOverRecursedFailure(lir);
|
||||
@ -9950,7 +9950,7 @@ CodeGenerator::visitInterruptCheck(LInterruptCheck *lir)
|
||||
if (!ool)
|
||||
return false;
|
||||
|
||||
AbsoluteAddress interruptAddr(GetIonContext()->runtime->addressOfInterrupt());
|
||||
AbsoluteAddress interruptAddr(GetIonContext()->runtime->addressOfInterruptUint32());
|
||||
masm.branch32(Assembler::NotEqual, interruptAddr, Imm32(0), ool->entry());
|
||||
masm.bind(ool->rejoin());
|
||||
return true;
|
||||
@ -9960,8 +9960,8 @@ bool
|
||||
CodeGenerator::visitAsmJSInterruptCheck(LAsmJSInterruptCheck *lir)
|
||||
{
|
||||
Register scratch = ToRegister(lir->scratch());
|
||||
masm.movePtr(AsmJSImmPtr(AsmJSImm_RuntimeInterrupt), scratch);
|
||||
masm.load8ZeroExtend(Address(scratch, 0), scratch);
|
||||
masm.movePtr(AsmJSImmPtr(AsmJSImm_RuntimeInterruptUint32), scratch);
|
||||
masm.load32(Address(scratch, 0), scratch);
|
||||
Label rejoin;
|
||||
masm.branch32(Assembler::Equal, scratch, Imm32(0), &rejoin);
|
||||
{
|
||||
|
@ -43,7 +43,7 @@ CompileRuntime::addressOfJitTop()
|
||||
const void *
|
||||
CompileRuntime::addressOfJitStackLimit()
|
||||
{
|
||||
return &runtime()->mainThread.jitStackLimit;
|
||||
return runtime()->mainThread.addressOfJitStackLimit();
|
||||
}
|
||||
|
||||
const void *
|
||||
@ -73,15 +73,15 @@ CompileRuntime::addressOfGCZeal()
|
||||
#endif
|
||||
|
||||
const void *
|
||||
CompileRuntime::addressOfInterrupt()
|
||||
CompileRuntime::addressOfInterruptUint32()
|
||||
{
|
||||
return &runtime()->interrupt;
|
||||
return runtime()->addressOfInterruptUint32();
|
||||
}
|
||||
|
||||
const void *
|
||||
CompileRuntime::addressOfInterruptPar()
|
||||
CompileRuntime::addressOfInterruptParUint32()
|
||||
{
|
||||
return &runtime()->interruptPar;
|
||||
return runtime()->addressOfInterruptParUint32();
|
||||
}
|
||||
|
||||
const void *
|
||||
|
@ -50,8 +50,8 @@ class CompileRuntime
|
||||
const void *addressOfGCZeal();
|
||||
#endif
|
||||
|
||||
const void *addressOfInterrupt();
|
||||
const void *addressOfInterruptPar();
|
||||
const void *addressOfInterruptUint32();
|
||||
const void *addressOfInterruptParUint32();
|
||||
|
||||
const void *addressOfThreadPool();
|
||||
|
||||
|
@ -428,7 +428,7 @@ JitRuntime::ensureIonCodeAccessible(JSRuntime *rt)
|
||||
ionCodeProtected_ = false;
|
||||
}
|
||||
|
||||
if (rt->interrupt) {
|
||||
if (rt->hasPendingInterrupt()) {
|
||||
// The interrupt handler needs to be invoked by this thread, but we may
|
||||
// be inside a signal handler and have no idea what is above us on the
|
||||
// stack (probably we are executing Ion code at an arbitrary point, but
|
||||
@ -1162,7 +1162,7 @@ IonScript::copyPatchableBackedges(JSContext *cx, JitCode *code,
|
||||
// whether an interrupt is currently desired, matching the targets
|
||||
// established by ensureIonCodeAccessible() above. We don't handle the
|
||||
// interrupt immediately as the interrupt lock is held here.
|
||||
if (cx->runtime()->interrupt)
|
||||
if (cx->runtime()->hasPendingInterrupt())
|
||||
PatchBackedge(backedge, interruptCheck, JitRuntime::BackedgeInterruptCheck);
|
||||
else
|
||||
PatchBackedge(backedge, loopHeader, JitRuntime::BackedgeLoopHeader);
|
||||
|
@ -1238,7 +1238,7 @@ MacroAssembler::loadStringChar(Register str, Register index, Register output)
|
||||
void
|
||||
MacroAssembler::checkInterruptFlagPar(Register tempReg, Label *fail)
|
||||
{
|
||||
movePtr(ImmPtr(GetIonContext()->runtime->addressOfInterruptPar()), tempReg);
|
||||
movePtr(ImmPtr(GetIonContext()->runtime->addressOfInterruptParUint32()), tempReg);
|
||||
branch32(Assembler::NonZero, Address(tempReg, 0), Imm32(0), fail);
|
||||
}
|
||||
|
||||
|
@ -147,7 +147,7 @@ jit::CheckOverRecursedPar(ForkJoinContext *cx)
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!JS_CHECK_STACK_SIZE(cx->perThreadData->jitStackLimit, &stackDummy_)) {
|
||||
if (!JS_CHECK_STACK_SIZE(cx->perThreadData->jitStackLimit(), &stackDummy_)) {
|
||||
cx->bailoutRecord->joinCause(ParallelBailoutOverRecursed);
|
||||
return false;
|
||||
}
|
||||
|
@ -113,28 +113,17 @@ NewGCObject(JSContext *cx, gc::AllocKind allocKind, gc::InitialHeap initialHeap)
|
||||
bool
|
||||
CheckOverRecursed(JSContext *cx)
|
||||
{
|
||||
// IonMonkey's stackLimit is equal to nativeStackLimit by default. When we
|
||||
// request an interrupt, we set the jitStackLimit to nullptr, which causes
|
||||
// the stack limit check to fail.
|
||||
//
|
||||
// There are two states we're concerned about here:
|
||||
// (1) The interrupt bit is set, and we need to fire the interrupt callback.
|
||||
// (2) The stack limit has been exceeded, and we need to throw an error.
|
||||
//
|
||||
// Note that we can reach here if jitStackLimit is MAXADDR, but interrupt
|
||||
// has not yet been set to 1. That's okay; it will be set to 1 very shortly,
|
||||
// and in the interim we might just fire a few useless calls to
|
||||
// CheckOverRecursed.
|
||||
// We just failed the jitStackLimit check. There are two possible reasons:
|
||||
// - jitStackLimit was the real stack limit and we're over-recursed
|
||||
// - jitStackLimit was set to UINTPTR_MAX by JSRuntime::requestInterrupt
|
||||
// and we need to call JSRuntime::handleInterrupt.
|
||||
#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
|
||||
JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, 0, return false);
|
||||
#else
|
||||
JS_CHECK_RECURSION(cx, return false);
|
||||
#endif
|
||||
|
||||
if (cx->runtime()->interrupt)
|
||||
return InterruptCheck(cx);
|
||||
|
||||
return true;
|
||||
gc::MaybeVerifyBarriers(cx);
|
||||
return cx->runtime()->handleInterrupt(cx);
|
||||
}
|
||||
|
||||
// This function can get called in two contexts. In the usual context, it's
|
||||
@ -178,10 +167,8 @@ CheckOverRecursedWithExtra(JSContext *cx, BaselineFrame *frame,
|
||||
JS_CHECK_RECURSION_WITH_SP(cx, checkSp, return false);
|
||||
#endif
|
||||
|
||||
if (cx->runtime()->interrupt)
|
||||
return InterruptCheck(cx);
|
||||
|
||||
return true;
|
||||
gc::MaybeVerifyBarriers(cx);
|
||||
return cx->runtime()->handleInterrupt(cx);
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -783,7 +783,7 @@ enum AsmJSImmKind
|
||||
AsmJSImm_PowD = AsmJSExit::Builtin_PowD,
|
||||
AsmJSImm_ATan2D = AsmJSExit::Builtin_ATan2D,
|
||||
AsmJSImm_Runtime,
|
||||
AsmJSImm_RuntimeInterrupt,
|
||||
AsmJSImm_RuntimeInterruptUint32,
|
||||
AsmJSImm_StackLimit,
|
||||
AsmJSImm_ReportOverRecursed,
|
||||
AsmJSImm_OnDetached,
|
||||
|
@ -2031,68 +2031,48 @@ JS_GetExternalStringFinalizer(JSString *str)
|
||||
}
|
||||
|
||||
static void
|
||||
SetNativeStackQuota(JSRuntime *rt, StackKind kind, size_t stackSize)
|
||||
SetNativeStackQuotaAndLimit(JSRuntime *rt, StackKind kind, size_t stackSize)
|
||||
{
|
||||
rt->nativeStackQuota[kind] = stackSize;
|
||||
if (rt->nativeStackBase)
|
||||
RecomputeStackLimit(rt, kind);
|
||||
}
|
||||
|
||||
void
|
||||
js::RecomputeStackLimit(JSRuntime *rt, StackKind kind)
|
||||
{
|
||||
size_t stackSize = rt->nativeStackQuota[kind];
|
||||
#if JS_STACK_GROWTH_DIRECTION > 0
|
||||
if (stackSize == 0) {
|
||||
rt->mainThread.nativeStackLimit[kind] = UINTPTR_MAX;
|
||||
} else {
|
||||
MOZ_ASSERT(rt->nativeStackBase <= size_t(-1) - stackSize);
|
||||
rt->mainThread.nativeStackLimit[kind] =
|
||||
rt->nativeStackBase + stackSize - 1;
|
||||
rt->mainThread.nativeStackLimit[kind] = rt->nativeStackBase + stackSize - 1;
|
||||
}
|
||||
#else
|
||||
if (stackSize == 0) {
|
||||
rt->mainThread.nativeStackLimit[kind] = 0;
|
||||
} else {
|
||||
MOZ_ASSERT(rt->nativeStackBase >= stackSize);
|
||||
rt->mainThread.nativeStackLimit[kind] =
|
||||
rt->nativeStackBase - (stackSize - 1);
|
||||
rt->mainThread.nativeStackLimit[kind] = rt->nativeStackBase - (stackSize - 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
// If there's no pending interrupt request set on the runtime's main thread's
|
||||
// jitStackLimit, then update it so that it reflects the new nativeStacklimit.
|
||||
//
|
||||
// Note that, for now, we use the untrusted limit for ion. This is fine,
|
||||
// because it's the most conservative limit, and if we hit it, we'll bail
|
||||
// out of ion into the interpeter, which will do a proper recursion check.
|
||||
if (kind == StackForUntrustedScript) {
|
||||
JSRuntime::AutoLockForInterrupt lock(rt);
|
||||
if (rt->mainThread.jitStackLimit != uintptr_t(-1)) {
|
||||
rt->mainThread.jitStackLimit = rt->mainThread.nativeStackLimit[kind];
|
||||
#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
|
||||
rt->mainThread.jitStackLimit = jit::Simulator::StackLimit();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS_SetNativeStackQuota(JSRuntime *rt, size_t systemCodeStackSize,
|
||||
size_t trustedScriptStackSize,
|
||||
JS_SetNativeStackQuota(JSRuntime *rt, size_t systemCodeStackSize, size_t trustedScriptStackSize,
|
||||
size_t untrustedScriptStackSize)
|
||||
{
|
||||
MOZ_ASSERT_IF(trustedScriptStackSize,
|
||||
trustedScriptStackSize < systemCodeStackSize);
|
||||
MOZ_ASSERT(rt->requestDepth == 0);
|
||||
|
||||
if (!trustedScriptStackSize)
|
||||
trustedScriptStackSize = systemCodeStackSize;
|
||||
MOZ_ASSERT_IF(untrustedScriptStackSize,
|
||||
untrustedScriptStackSize < trustedScriptStackSize);
|
||||
else
|
||||
MOZ_ASSERT(trustedScriptStackSize < systemCodeStackSize);
|
||||
|
||||
if (!untrustedScriptStackSize)
|
||||
untrustedScriptStackSize = trustedScriptStackSize;
|
||||
SetNativeStackQuota(rt, StackForSystemCode, systemCodeStackSize);
|
||||
SetNativeStackQuota(rt, StackForTrustedScript, trustedScriptStackSize);
|
||||
SetNativeStackQuota(rt, StackForUntrustedScript, untrustedScriptStackSize);
|
||||
else
|
||||
MOZ_ASSERT(untrustedScriptStackSize < trustedScriptStackSize);
|
||||
|
||||
SetNativeStackQuotaAndLimit(rt, StackForSystemCode, systemCodeStackSize);
|
||||
SetNativeStackQuotaAndLimit(rt, StackForTrustedScript, trustedScriptStackSize);
|
||||
SetNativeStackQuotaAndLimit(rt, StackForUntrustedScript, untrustedScriptStackSize);
|
||||
|
||||
rt->mainThread.initJitStackLimit();
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
|
@ -2272,6 +2272,9 @@ JS_GetExternalStringFinalizer(JSString *str);
|
||||
* The stack quotas for each kind of code should be monotonically descending,
|
||||
* and may be specified with this function. If 0 is passed for a given kind
|
||||
* of code, it defaults to the value of the next-highest-priority kind.
|
||||
*
|
||||
* This function may only be called immediately after the runtime is initialized
|
||||
* and before any code is executed and/or interrupts requested.
|
||||
*/
|
||||
extern JS_PUBLIC_API(void)
|
||||
JS_SetNativeStackQuota(JSRuntime *cx, size_t systemCodeStackSize,
|
||||
|
@ -41,7 +41,6 @@
|
||||
#include "gc/Marking.h"
|
||||
#include "jit/Ion.h"
|
||||
#include "js/CharacterEncoding.h"
|
||||
#include "vm/Debugger.h"
|
||||
#include "vm/HelperThreads.h"
|
||||
#include "vm/Shape.h"
|
||||
|
||||
@ -971,90 +970,6 @@ js_GetErrorMessage(void *userRef, const unsigned errorNumber)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
js::InvokeInterruptCallback(JSContext *cx)
|
||||
{
|
||||
MOZ_ASSERT(cx->runtime()->requestDepth >= 1);
|
||||
|
||||
JSRuntime *rt = cx->runtime();
|
||||
MOZ_ASSERT(rt->interrupt);
|
||||
|
||||
// Reset the callback counter first, then run GC and yield. If another
|
||||
// thread is racing us here we will accumulate another callback request
|
||||
// which will be serviced at the next opportunity.
|
||||
rt->interrupt = false;
|
||||
|
||||
// IonMonkey sets its stack limit to UINTPTR_MAX to trigger interrupt
|
||||
// callbacks.
|
||||
rt->resetJitStackLimit();
|
||||
|
||||
cx->gcIfNeeded();
|
||||
|
||||
rt->interruptPar = false;
|
||||
|
||||
// A worker thread may have requested an interrupt after finishing an Ion
|
||||
// compilation.
|
||||
jit::AttachFinishedCompilations(cx);
|
||||
|
||||
// Important: Additional callbacks can occur inside the callback handler
|
||||
// if it re-enters the JS engine. The embedding must ensure that the
|
||||
// callback is disconnected before attempting such re-entry.
|
||||
JSInterruptCallback cb = cx->runtime()->interruptCallback;
|
||||
if (!cb)
|
||||
return true;
|
||||
|
||||
if (cb(cx)) {
|
||||
// Debugger treats invoking the interrupt callback as a "step", so
|
||||
// invoke the onStep handler.
|
||||
if (cx->compartment()->debugMode()) {
|
||||
ScriptFrameIter iter(cx);
|
||||
if (iter.script()->stepModeEnabled()) {
|
||||
RootedValue rval(cx);
|
||||
switch (Debugger::onSingleStep(cx, &rval)) {
|
||||
case JSTRAP_ERROR:
|
||||
return false;
|
||||
case JSTRAP_CONTINUE:
|
||||
return true;
|
||||
case JSTRAP_RETURN:
|
||||
// See note in Debugger::propagateForcedReturn.
|
||||
Debugger::propagateForcedReturn(cx, iter.abstractFramePtr(), rval);
|
||||
return false;
|
||||
case JSTRAP_THROW:
|
||||
cx->setPendingException(rval);
|
||||
return false;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// No need to set aside any pending exception here: ComputeStackString
|
||||
// already does that.
|
||||
JSString *stack = ComputeStackString(cx);
|
||||
JSFlatString *flat = stack ? stack->ensureFlat(cx) : nullptr;
|
||||
|
||||
const char16_t *chars;
|
||||
AutoStableStringChars stableChars(cx);
|
||||
if (flat && stableChars.initTwoByte(cx, flat))
|
||||
chars = stableChars.twoByteRange().start().get();
|
||||
else
|
||||
chars = MOZ_UTF16("(stack not available)");
|
||||
JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_WARNING, js_GetErrorMessage, nullptr,
|
||||
JSMSG_TERMINATED, chars);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
js::HandleExecutionInterrupt(JSContext *cx)
|
||||
{
|
||||
if (cx->runtime()->interrupt)
|
||||
return InvokeInterruptCallback(cx);
|
||||
return true;
|
||||
}
|
||||
|
||||
ThreadSafeContext::ThreadSafeContext(JSRuntime *rt, PerThreadData *pt, ContextKind kind)
|
||||
: ContextFriendFields(rt),
|
||||
contextKind_(kind),
|
||||
|
@ -289,7 +289,7 @@ struct ThreadSafeContext : ContextFriendFields,
|
||||
PropertyName *emptyString() { return runtime_->emptyString; }
|
||||
FreeOp *defaultFreeOp() { return runtime_->defaultFreeOp(); }
|
||||
void *runtimeAddressForJit() { return runtime_; }
|
||||
void *runtimeAddressOfInterrupt() { return &runtime_->interrupt; }
|
||||
void *runtimeAddressOfInterruptUint32() { return runtime_->addressOfInterruptUint32(); }
|
||||
void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; }
|
||||
void *stackLimitAddressForJitCode(StackKind kind);
|
||||
size_t gcSystemPageSize() { return gc::SystemPageSize(); }
|
||||
@ -782,33 +782,15 @@ extern const JSErrorFormatString js_ErrorFormatString[JSErr_Limit];
|
||||
|
||||
namespace js {
|
||||
|
||||
/*
|
||||
* Invoke the interrupt callback and return false if the current execution
|
||||
* is to be terminated.
|
||||
*/
|
||||
bool
|
||||
InvokeInterruptCallback(JSContext *cx);
|
||||
|
||||
bool
|
||||
HandleExecutionInterrupt(JSContext *cx);
|
||||
|
||||
/*
|
||||
* Process any pending interrupt requests. Long-running inner loops in C++ must
|
||||
* call this periodically to make sure they are interruptible --- that is, to
|
||||
* make sure they do not prevent the slow script dialog from appearing.
|
||||
*
|
||||
* This can run a full GC or call the interrupt callback, which could do
|
||||
* anything. In the browser, it displays the slow script dialog.
|
||||
*
|
||||
* If this returns true, the caller can continue; if false, the caller must
|
||||
* break out of its loop. This happens if, for example, the user clicks "Stop
|
||||
* script" on the slow script dialog; treat it as an uncatchable error.
|
||||
*/
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
CheckForInterrupt(JSContext *cx)
|
||||
{
|
||||
MOZ_ASSERT(cx->runtime()->requestDepth >= 1);
|
||||
return !cx->runtime()->interrupt || InvokeInterruptCallback(cx);
|
||||
// Add an inline fast-path since we have to check for interrupts in some hot
|
||||
// C++ loops of library builtins.
|
||||
JSRuntime *rt = cx->runtime();
|
||||
if (rt->hasPendingInterrupt())
|
||||
return rt->handleInterrupt(cx);
|
||||
return true;
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
|
227
js/src/jsgc.cpp
227
js/src/jsgc.cpp
@ -545,6 +545,9 @@ Arena::finalize(FreeOp *fop, AllocKind thingKind, size_t thingSize)
|
||||
return nmarked;
|
||||
}
|
||||
|
||||
// Finalize arenas from src list, releasing empty arenas if keepArenas wasn't
|
||||
// specified and inserting the others into the appropriate destination size
|
||||
// bins.
|
||||
template<typename T>
|
||||
static inline bool
|
||||
FinalizeTypedArenas(FreeOp *fop,
|
||||
@ -554,11 +557,10 @@ FinalizeTypedArenas(FreeOp *fop,
|
||||
SliceBudget &budget,
|
||||
ArenaLists::KeepArenasEnum keepArenas)
|
||||
{
|
||||
/*
|
||||
* Finalize arenas from src list, releasing empty arenas if keepArenas
|
||||
* wasn't specified and inserting the others into the appropriate
|
||||
* destination size bins.
|
||||
*/
|
||||
// When operating in the foreground, take the lock at the top.
|
||||
Maybe<AutoLockGC> maybeLock;
|
||||
if (!fop->runtime()->gc.isBackgroundSweeping())
|
||||
maybeLock.emplace(fop->runtime());
|
||||
|
||||
/*
|
||||
* During parallel sections, we sometimes finalize the parallel arenas,
|
||||
@ -575,12 +577,18 @@ FinalizeTypedArenas(FreeOp *fop,
|
||||
size_t nmarked = aheader->getArena()->finalize<T>(fop, thingKind, thingSize);
|
||||
size_t nfree = thingsPerArena - nmarked;
|
||||
|
||||
if (nmarked)
|
||||
if (nmarked) {
|
||||
dest.insertAt(aheader, nfree);
|
||||
else if (keepArenas)
|
||||
} else if (keepArenas) {
|
||||
aheader->chunk()->recycleArena(aheader, dest, thingKind, thingsPerArena);
|
||||
else
|
||||
aheader->chunk()->releaseArena(aheader);
|
||||
} else if (fop->runtime()->gc.isBackgroundSweeping()) {
|
||||
// When background sweeping, take the lock around each release so
|
||||
// that we do not block the foreground for extended periods.
|
||||
AutoLockGC lock(fop->runtime());
|
||||
fop->runtime()->gc.releaseArena(aheader, lock);
|
||||
} else {
|
||||
fop->runtime()->gc.releaseArena(aheader, maybeLock.ref());
|
||||
}
|
||||
|
||||
budget.step(thingsPerArena);
|
||||
if (budget.isOverBudget())
|
||||
@ -938,67 +946,14 @@ Chunk::fetchNextFreeArena(JSRuntime *rt)
|
||||
}
|
||||
|
||||
ArenaHeader *
|
||||
Chunk::allocateArena(Zone *zone, AllocKind thingKind)
|
||||
Chunk::allocateArena(JSRuntime *rt, Zone *zone, AllocKind thingKind, const AutoLockGC &lock)
|
||||
{
|
||||
MOZ_ASSERT(hasAvailableArenas());
|
||||
|
||||
JSRuntime *rt = zone->runtimeFromAnyThread();
|
||||
if (!rt->isHeapMinorCollecting() &&
|
||||
!rt->isHeapCompacting() &&
|
||||
rt->gc.usage.gcBytes() >= rt->gc.tunables.gcMaxBytes())
|
||||
{
|
||||
#ifdef JSGC_FJGENERATIONAL
|
||||
// This is an approximation to the best test, which would check that
|
||||
// this thread is currently promoting into the tenured area. I doubt
|
||||
// the better test would make much difference.
|
||||
if (!rt->isFJMinorCollecting())
|
||||
return nullptr;
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
ArenaHeader *aheader = MOZ_LIKELY(info.numArenasFreeCommitted > 0)
|
||||
ArenaHeader *aheader = info.numArenasFreeCommitted > 0
|
||||
? fetchNextFreeArena(rt)
|
||||
: fetchNextDecommittedArena();
|
||||
aheader->init(zone, thingKind);
|
||||
if (MOZ_UNLIKELY(!hasAvailableArenas()))
|
||||
removeFromAvailableList();
|
||||
|
||||
zone->usage.addGCArena();
|
||||
|
||||
if (!rt->isHeapCompacting()) {
|
||||
size_t usedBytes = zone->usage.gcBytes();
|
||||
size_t thresholdBytes = zone->threshold.gcTriggerBytes();
|
||||
size_t igcThresholdBytes = thresholdBytes * rt->gc.tunables.zoneAllocThresholdFactor();
|
||||
|
||||
if (usedBytes >= thresholdBytes) {
|
||||
// The threshold has been surpassed, immediately trigger a GC,
|
||||
// which will be done non-incrementally.
|
||||
AutoUnlockGC unlock(rt);
|
||||
rt->gc.triggerZoneGC(zone, JS::gcreason::ALLOC_TRIGGER);
|
||||
} else if (usedBytes >= igcThresholdBytes) {
|
||||
// Reduce the delay to the start of the next incremental slice.
|
||||
if (zone->gcDelayBytes < ArenaSize)
|
||||
zone->gcDelayBytes = 0;
|
||||
else
|
||||
zone->gcDelayBytes -= ArenaSize;
|
||||
|
||||
if (!zone->gcDelayBytes) {
|
||||
// Start or continue an in progress incremental GC. We do this
|
||||
// to try to avoid performing non-incremental GCs on zones
|
||||
// which allocate a lot of data, even when incremental slices
|
||||
// can't be triggered via scheduling in the event loop.
|
||||
AutoUnlockGC unlock(rt);
|
||||
rt->gc.triggerZoneGC(zone, JS::gcreason::ALLOC_TRIGGER);
|
||||
|
||||
// Delay the next slice until a certain amount of allocation
|
||||
// has been performed.
|
||||
zone->gcDelayBytes = rt->gc.tunables.zoneAllocDelayBytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return aheader;
|
||||
}
|
||||
|
||||
@ -1028,20 +983,10 @@ Chunk::recycleArena(ArenaHeader *aheader, SortedArenaList &dest, AllocKind thing
|
||||
}
|
||||
|
||||
void
|
||||
Chunk::releaseArena(ArenaHeader *aheader)
|
||||
Chunk::releaseArena(JSRuntime *rt, ArenaHeader *aheader, const AutoLockGC &lock)
|
||||
{
|
||||
MOZ_ASSERT(aheader->allocated());
|
||||
MOZ_ASSERT(!aheader->hasDelayedMarking);
|
||||
Zone *zone = aheader->zone;
|
||||
JSRuntime *rt = zone->runtimeFromAnyThread();
|
||||
|
||||
Maybe<AutoLockGC> maybeLock;
|
||||
if (rt->gc.isBackgroundSweeping())
|
||||
maybeLock.emplace(rt);
|
||||
|
||||
if (rt->gc.isBackgroundSweeping())
|
||||
zone->threshold.updateForRemovedArena(rt->gc.tunables);
|
||||
zone->usage.removeGCArena();
|
||||
|
||||
aheader->setAsNotAllocated();
|
||||
addArenaToFreeList(rt, aheader);
|
||||
@ -1053,12 +998,10 @@ Chunk::releaseArena(ArenaHeader *aheader)
|
||||
} else if (!unused()) {
|
||||
MOZ_ASSERT(info.prevp);
|
||||
} else {
|
||||
if (maybeLock.isNothing())
|
||||
maybeLock.emplace(rt);
|
||||
MOZ_ASSERT(unused());
|
||||
removeFromAvailableList();
|
||||
decommitAllArenas(rt);
|
||||
rt->gc.moveChunkToFreePool(this, maybeLock.ref());
|
||||
rt->gc.moveChunkToFreePool(this, lock);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1161,6 +1104,46 @@ GCRuntime::pickChunk(const AutoLockGC &lock,
|
||||
return chunk;
|
||||
}
|
||||
|
||||
ArenaHeader *
|
||||
GCRuntime::allocateArena(Chunk *chunk, Zone *zone, AllocKind thingKind, const AutoLockGC &lock)
|
||||
{
|
||||
MOZ_ASSERT(chunk->hasAvailableArenas());
|
||||
|
||||
// Fail the allocation if we are over our heap size limits.
|
||||
if (!isHeapMinorCollecting() &&
|
||||
!isHeapCompacting() &&
|
||||
usage.gcBytes() >= tunables.gcMaxBytes())
|
||||
{
|
||||
#ifdef JSGC_FJGENERATIONAL
|
||||
// This is an approximation to the best test, which would check that
|
||||
// this thread is currently promoting into the tenured area. I doubt
|
||||
// the better test would make much difference.
|
||||
if (!isFJMinorCollecting())
|
||||
return nullptr;
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
ArenaHeader *aheader = chunk->allocateArena(rt, zone, thingKind, lock);
|
||||
zone->usage.addGCArena();
|
||||
|
||||
// Trigger an incremental slice if needed.
|
||||
if (!isHeapMinorCollecting() && !isHeapCompacting())
|
||||
maybeAllocTriggerZoneGC(zone, lock);
|
||||
|
||||
return aheader;
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::releaseArena(ArenaHeader *aheader, const AutoLockGC &lock)
|
||||
{
|
||||
aheader->zone->usage.removeGCArena();
|
||||
if (isBackgroundSweeping())
|
||||
aheader->zone->threshold.updateForRemovedArena(tunables);
|
||||
return aheader->chunk()->releaseArena(rt, aheader, lock);
|
||||
}
|
||||
|
||||
GCRuntime::GCRuntime(JSRuntime *rt) :
|
||||
rt(rt),
|
||||
systemZone(nullptr),
|
||||
@ -1906,7 +1889,8 @@ ZoneHeapThreshold::updateForRemovedArena(const GCSchedulingTunables &tunables)
|
||||
}
|
||||
|
||||
Allocator::Allocator(Zone *zone)
|
||||
: zone_(zone)
|
||||
: arenas(zone->runtimeFromMainThread()),
|
||||
zone_(zone)
|
||||
{}
|
||||
|
||||
inline void
|
||||
@ -1992,7 +1976,7 @@ ArenaLists::allocateFromArena(JS::Zone *zone, AllocKind thingKind,
|
||||
|
||||
// Although our chunk should definitely have enough space for another arena,
|
||||
// there are other valid reasons why Chunk::allocateArena() may fail.
|
||||
aheader = chunk->allocateArena(zone, thingKind);
|
||||
aheader = rt->gc.allocateArena(chunk, zone, thingKind, maybeLock.ref());
|
||||
if (!aheader)
|
||||
return nullptr;
|
||||
|
||||
@ -2553,6 +2537,7 @@ void
|
||||
GCRuntime::releaseRelocatedArenas(ArenaHeader *relocatedList)
|
||||
{
|
||||
// Release the relocated arenas, now containing only forwarding pointers
|
||||
AutoLockGC lock(rt);
|
||||
|
||||
unsigned count = 0;
|
||||
while (relocatedList) {
|
||||
@ -2575,16 +2560,44 @@ GCRuntime::releaseRelocatedArenas(ArenaHeader *relocatedList)
|
||||
JS_MOVED_TENURED_PATTERN, Arena::thingsSpan(thingSize));
|
||||
#endif
|
||||
|
||||
aheader->chunk()->releaseArena(aheader);
|
||||
releaseArena(aheader, lock);
|
||||
++count;
|
||||
}
|
||||
|
||||
AutoLockGC lock(rt);
|
||||
expireChunksAndArenas(true, lock);
|
||||
}
|
||||
|
||||
#endif // JSGC_COMPACTING
|
||||
|
||||
void
|
||||
ReleaseArenaList(JSRuntime *rt, ArenaHeader *aheader, const AutoLockGC &lock)
|
||||
{
|
||||
ArenaHeader *next;
|
||||
for (; aheader; aheader = next) {
|
||||
next = aheader->next;
|
||||
rt->gc.releaseArena(aheader, lock);
|
||||
}
|
||||
}
|
||||
|
||||
ArenaLists::~ArenaLists()
|
||||
{
|
||||
AutoLockGC lock(runtime_);
|
||||
|
||||
for (size_t i = 0; i != FINALIZE_LIMIT; ++i) {
|
||||
/*
|
||||
* We can only call this during the shutdown after the last GC when
|
||||
* the background finalization is disabled.
|
||||
*/
|
||||
MOZ_ASSERT(backgroundFinalizeState[i] == BFS_DONE);
|
||||
ReleaseArenaList(runtime_, arenaLists[i].head(), lock);
|
||||
}
|
||||
ReleaseArenaList(runtime_, incrementalSweptArenas.head(), lock);
|
||||
|
||||
for (size_t i = 0; i < FINALIZE_OBJECT_LIMIT; i++)
|
||||
ReleaseArenaList(runtime_, savedObjectArenas[i].head(), lock);
|
||||
ReleaseArenaList(runtime_, savedEmptyObjectArenas, lock);
|
||||
}
|
||||
|
||||
void
|
||||
ArenaLists::finalizeNow(FreeOp *fop, const FinalizePhase& phase)
|
||||
{
|
||||
@ -2751,7 +2764,8 @@ ArenaLists::queueForegroundObjectsForSweep(FreeOp *fop)
|
||||
void
|
||||
ArenaLists::mergeForegroundSweptObjectArenas()
|
||||
{
|
||||
ReleaseArenaList(savedEmptyObjectArenas);
|
||||
AutoLockGC lock(runtime_);
|
||||
ReleaseArenaList(runtime_, savedEmptyObjectArenas, lock);
|
||||
savedEmptyObjectArenas = nullptr;
|
||||
|
||||
mergeSweptArenas(FINALIZE_OBJECT0);
|
||||
@ -3023,6 +3037,40 @@ GCRuntime::triggerGC(JS::gcreason::Reason reason)
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::maybeAllocTriggerZoneGC(Zone *zone, const AutoLockGC &lock)
|
||||
{
|
||||
size_t usedBytes = zone->usage.gcBytes();
|
||||
size_t thresholdBytes = zone->threshold.gcTriggerBytes();
|
||||
size_t igcThresholdBytes = thresholdBytes * tunables.zoneAllocThresholdFactor();
|
||||
|
||||
if (usedBytes >= thresholdBytes) {
|
||||
// The threshold has been surpassed, immediately trigger a GC,
|
||||
// which will be done non-incrementally.
|
||||
AutoUnlockGC unlock(rt);
|
||||
triggerZoneGC(zone, JS::gcreason::ALLOC_TRIGGER);
|
||||
} else if (usedBytes >= igcThresholdBytes) {
|
||||
// Reduce the delay to the start of the next incremental slice.
|
||||
if (zone->gcDelayBytes < ArenaSize)
|
||||
zone->gcDelayBytes = 0;
|
||||
else
|
||||
zone->gcDelayBytes -= ArenaSize;
|
||||
|
||||
if (!zone->gcDelayBytes) {
|
||||
// Start or continue an in progress incremental GC. We do this
|
||||
// to try to avoid performing non-incremental GCs on zones
|
||||
// which allocate a lot of data, even when incremental slices
|
||||
// can't be triggered via scheduling in the event loop.
|
||||
AutoUnlockGC unlock(rt);
|
||||
triggerZoneGC(zone, JS::gcreason::ALLOC_TRIGGER);
|
||||
|
||||
// Delay the next slice until a certain amount of allocation
|
||||
// has been performed.
|
||||
zone->gcDelayBytes = tunables.zoneAllocDelayBytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
GCRuntime::triggerZoneGC(Zone *zone, JS::gcreason::Reason reason)
|
||||
{
|
||||
@ -3123,8 +3171,9 @@ GCRuntime::maybePeriodicFullGC()
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::decommitArenasFromAvailableList(Chunk **availableListHeadp)
|
||||
GCRuntime::decommitArenas(const AutoLockGC &lock)
|
||||
{
|
||||
Chunk **availableListHeadp = &availableChunkListHead;
|
||||
Chunk *chunk = *availableListHeadp;
|
||||
if (!chunk)
|
||||
return;
|
||||
@ -3231,12 +3280,6 @@ GCRuntime::decommitArenasFromAvailableList(Chunk **availableListHeadp)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::decommitArenas()
|
||||
{
|
||||
decommitArenasFromAvailableList(&availableChunkListHead);
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::expireChunksAndArenas(bool shouldShrink, const AutoLockGC &lock)
|
||||
{
|
||||
@ -3250,7 +3293,7 @@ GCRuntime::expireChunksAndArenas(bool shouldShrink, const AutoLockGC &lock)
|
||||
}
|
||||
|
||||
if (shouldShrink)
|
||||
decommitArenas();
|
||||
decommitArenas(lock);
|
||||
}
|
||||
|
||||
void
|
||||
@ -6620,7 +6663,7 @@ ArenaLists::adoptArenas(JSRuntime *rt, ArenaLists *fromArenaLists)
|
||||
// Therefore, if fromHeader is empty, send it back to the
|
||||
// chunk now. Otherwise, attach to |toList|.
|
||||
if (fromHeader->isEmpty())
|
||||
fromHeader->chunk()->releaseArena(fromHeader);
|
||||
rt->gc.releaseArena(fromHeader, lock);
|
||||
else
|
||||
toList->insertAtCursor(fromHeader);
|
||||
}
|
||||
|
@ -595,6 +595,8 @@ class SortedArenaList
|
||||
|
||||
class ArenaLists
|
||||
{
|
||||
JSRuntime *runtime_;
|
||||
|
||||
/*
|
||||
* For each arena kind its free list is represented as the first span with
|
||||
* free things. Initially all the spans are initialized as empty. After we
|
||||
@ -639,7 +641,7 @@ class ArenaLists
|
||||
ArenaHeader *savedEmptyObjectArenas;
|
||||
|
||||
public:
|
||||
ArenaLists() {
|
||||
ArenaLists(JSRuntime *rt) : runtime_(rt) {
|
||||
for (size_t i = 0; i != FINALIZE_LIMIT; ++i)
|
||||
freeLists[i].initAsEmpty();
|
||||
for (size_t i = 0; i != FINALIZE_LIMIT; ++i)
|
||||
@ -654,21 +656,7 @@ class ArenaLists
|
||||
savedEmptyObjectArenas = nullptr;
|
||||
}
|
||||
|
||||
~ArenaLists() {
|
||||
for (size_t i = 0; i != FINALIZE_LIMIT; ++i) {
|
||||
/*
|
||||
* We can only call this during the shutdown after the last GC when
|
||||
* the background finalization is disabled.
|
||||
*/
|
||||
MOZ_ASSERT(backgroundFinalizeState[i] == BFS_DONE);
|
||||
ReleaseArenaList(arenaLists[i].head());
|
||||
}
|
||||
ReleaseArenaList(incrementalSweptArenas.head());
|
||||
|
||||
for (size_t i = 0; i < FINALIZE_OBJECT_LIMIT; i++)
|
||||
ReleaseArenaList(savedObjectArenas[i].head());
|
||||
ReleaseArenaList(savedEmptyObjectArenas);
|
||||
}
|
||||
~ArenaLists();
|
||||
|
||||
static uintptr_t getFreeListOffset(AllocKind thingKind) {
|
||||
uintptr_t offset = offsetof(ArenaLists, freeLists);
|
||||
|
@ -526,7 +526,7 @@ CheckAllocatorState(ThreadSafeContext *cx, AllocKind kind)
|
||||
rt->gc.runDebugGC();
|
||||
#endif
|
||||
|
||||
if (rt->interrupt) {
|
||||
if (rt->hasPendingInterrupt()) {
|
||||
// Invoking the interrupt callback can fail and we can't usefully
|
||||
// handle that here. Just check in case we need to collect instead.
|
||||
ncx->gcIfNeeded();
|
||||
|
@ -18,6 +18,7 @@ inline uintptr_t
|
||||
GetNativeStackBase()
|
||||
{
|
||||
uintptr_t stackBase = reinterpret_cast<uintptr_t>(GetNativeStackBaseImpl());
|
||||
MOZ_ASSERT(stackBase != 0);
|
||||
MOZ_ASSERT(stackBase % sizeof(void *) == 0);
|
||||
return stackBase;
|
||||
}
|
||||
|
@ -1440,7 +1440,7 @@ ForkJoinShared::execute()
|
||||
// Sometimes a GC request occurs *just before* we enter into the
|
||||
// parallel section. Rather than enter into the parallel section
|
||||
// and then abort, we just check here and abort early.
|
||||
if (cx_->runtime()->interruptPar)
|
||||
if (cx_->runtime()->hasPendingInterruptPar())
|
||||
return TP_RETRY_SEQUENTIALLY;
|
||||
|
||||
AutoLockMonitor lock(*this);
|
||||
@ -1518,7 +1518,7 @@ ForkJoinShared::executeFromWorker(ThreadPoolWorker *worker, uintptr_t stackLimit
|
||||
|
||||
// Don't use setIonStackLimit() because that acquires the ionStackLimitLock, and the
|
||||
// lock has not been initialized in these cases.
|
||||
thisThread.jitStackLimit = stackLimit;
|
||||
thisThread.initJitStackLimitPar(stackLimit);
|
||||
executePortion(&thisThread, worker);
|
||||
TlsPerThreadData.set(nullptr);
|
||||
|
||||
@ -1551,7 +1551,7 @@ ForkJoinShared::executeFromMainThread(ThreadPoolWorker *worker)
|
||||
//
|
||||
// Thus, use GetNativeStackLimit instead of just propagating the
|
||||
// main thread's.
|
||||
thisThread.jitStackLimit = GetNativeStackLimit(cx_);
|
||||
thisThread.initJitStackLimitPar(GetNativeStackLimit(cx_));
|
||||
executePortion(&thisThread, worker);
|
||||
TlsPerThreadData.set(oldData);
|
||||
|
||||
@ -1647,7 +1647,7 @@ ForkJoinShared::executePortion(PerThreadData *perThread, ThreadPoolWorker *worke
|
||||
void
|
||||
ForkJoinShared::setAbortFlagDueToInterrupt(ForkJoinContext &cx)
|
||||
{
|
||||
MOZ_ASSERT(cx_->runtime()->interruptPar);
|
||||
MOZ_ASSERT(cx_->runtime()->hasPendingInterruptPar());
|
||||
// The GC Needed flag should not be set during parallel
|
||||
// execution. Instead, one of the requestGC() or
|
||||
// requestZoneGC() methods should be invoked.
|
||||
@ -1826,7 +1826,7 @@ ForkJoinContext::hasAcquiredJSContext() const
|
||||
bool
|
||||
ForkJoinContext::check()
|
||||
{
|
||||
if (runtime()->interruptPar) {
|
||||
if (runtime()->hasPendingInterruptPar()) {
|
||||
shared_->setAbortFlagDueToInterrupt(*this);
|
||||
return false;
|
||||
}
|
||||
@ -2273,13 +2273,6 @@ js::ParallelTestsShouldPass(JSContext *cx)
|
||||
cx->runtime()->gcZeal() == 0;
|
||||
}
|
||||
|
||||
void
|
||||
js::RequestInterruptForForkJoin(JSRuntime *rt, JSRuntime::InterruptMode mode)
|
||||
{
|
||||
if (mode != JSRuntime::RequestInterruptAnyThreadDontStopIon)
|
||||
rt->interruptPar = true;
|
||||
}
|
||||
|
||||
bool
|
||||
js::intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
|
@ -546,8 +546,6 @@ bool InExclusiveParallelSection();
|
||||
|
||||
bool ParallelTestsShouldPass(JSContext *cx);
|
||||
|
||||
void RequestInterruptForForkJoin(JSRuntime *rt, JSRuntime::InterruptMode mode);
|
||||
|
||||
bool intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp);
|
||||
extern const JSJitInfo intrinsic_SetForkJoinTargetRegionInfo;
|
||||
|
||||
|
@ -621,14 +621,8 @@ RegExpShared::execute(JSContext *cx, HandleLinearString input, size_t start,
|
||||
// in the bytecode interpreter, which can execute while tolerating
|
||||
// future interrupts. Otherwise, if we keep getting interrupted we
|
||||
// will never finish executing the regexp.
|
||||
bool interrupted;
|
||||
{
|
||||
JSRuntime::AutoLockForInterrupt lock(cx->runtime());
|
||||
interrupted = cx->runtime()->interrupt;
|
||||
}
|
||||
|
||||
if (interrupted) {
|
||||
if (!InvokeInterruptCallback(cx))
|
||||
if (cx->runtime()->hasPendingInterrupt()) {
|
||||
if (!cx->runtime()->handleInterrupt(cx))
|
||||
return RegExpRunStatus_Error;
|
||||
break;
|
||||
}
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "jit/PcScriptCache.h"
|
||||
#include "js/MemoryMetrics.h"
|
||||
#include "js/SliceBudget.h"
|
||||
#include "vm/Debugger.h"
|
||||
|
||||
#include "jscntxtinlines.h"
|
||||
#include "jsgcinlines.h"
|
||||
@ -73,7 +74,7 @@ PerThreadData::PerThreadData(JSRuntime *runtime)
|
||||
runtime_(runtime),
|
||||
jitTop(nullptr),
|
||||
jitJSContext(nullptr),
|
||||
jitStackLimit(0),
|
||||
jitStackLimit_(0xbad),
|
||||
#ifdef JS_TRACE_LOGGING
|
||||
traceLogger(nullptr),
|
||||
#endif
|
||||
@ -135,8 +136,8 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime)
|
||||
),
|
||||
mainThread(this),
|
||||
parentRuntime(parentRuntime),
|
||||
interrupt(false),
|
||||
interruptPar(false),
|
||||
interrupt_(false),
|
||||
interruptPar_(false),
|
||||
handlingSignal(false),
|
||||
interruptCallback(nullptr),
|
||||
interruptLock(nullptr),
|
||||
@ -155,7 +156,7 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime)
|
||||
execAlloc_(nullptr),
|
||||
jitRuntime_(nullptr),
|
||||
selfHostingGlobal_(nullptr),
|
||||
nativeStackBase(0),
|
||||
nativeStackBase(GetNativeStackBase()),
|
||||
cxCallback(nullptr),
|
||||
destroyCompartmentCallback(nullptr),
|
||||
destroyZoneCallback(nullptr),
|
||||
@ -321,8 +322,6 @@ JSRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
nativeStackBase = GetNativeStackBase();
|
||||
|
||||
jitSupportsFloatingPoint = js::jit::JitSupportsFloatingPoint();
|
||||
jitSupportsSimd = js::jit::JitSupportsSimd();
|
||||
|
||||
@ -464,17 +463,6 @@ NewObjectCache::clearNurseryObjects(JSRuntime *rt)
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::resetJitStackLimit()
|
||||
{
|
||||
AutoLockForInterrupt lock(this);
|
||||
mainThread.setJitStackLimit(mainThread.nativeStackLimit[js::StackForUntrustedScript]);
|
||||
|
||||
#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
|
||||
mainThread.setJitStackLimit(js::jit::Simulator::StackLimit());
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes *rtSizes)
|
||||
{
|
||||
@ -529,33 +517,120 @@ JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::Runtim
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool
|
||||
InvokeInterruptCallback(JSContext *cx)
|
||||
{
|
||||
MOZ_ASSERT(cx->runtime()->requestDepth >= 1);
|
||||
|
||||
cx->gcIfNeeded();
|
||||
|
||||
// A worker thread may have requested an interrupt after finishing an Ion
|
||||
// compilation.
|
||||
jit::AttachFinishedCompilations(cx);
|
||||
|
||||
// Important: Additional callbacks can occur inside the callback handler
|
||||
// if it re-enters the JS engine. The embedding must ensure that the
|
||||
// callback is disconnected before attempting such re-entry.
|
||||
JSInterruptCallback cb = cx->runtime()->interruptCallback;
|
||||
if (!cb)
|
||||
return true;
|
||||
|
||||
if (cb(cx)) {
|
||||
// Debugger treats invoking the interrupt callback as a "step", so
|
||||
// invoke the onStep handler.
|
||||
if (cx->compartment()->debugMode()) {
|
||||
ScriptFrameIter iter(cx);
|
||||
if (iter.script()->stepModeEnabled()) {
|
||||
RootedValue rval(cx);
|
||||
switch (Debugger::onSingleStep(cx, &rval)) {
|
||||
case JSTRAP_ERROR:
|
||||
return false;
|
||||
case JSTRAP_CONTINUE:
|
||||
return true;
|
||||
case JSTRAP_RETURN:
|
||||
// See note in Debugger::propagateForcedReturn.
|
||||
Debugger::propagateForcedReturn(cx, iter.abstractFramePtr(), rval);
|
||||
return false;
|
||||
case JSTRAP_THROW:
|
||||
cx->setPendingException(rval);
|
||||
return false;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// No need to set aside any pending exception here: ComputeStackString
|
||||
// already does that.
|
||||
JSString *stack = ComputeStackString(cx);
|
||||
JSFlatString *flat = stack ? stack->ensureFlat(cx) : nullptr;
|
||||
|
||||
const char16_t *chars;
|
||||
AutoStableStringChars stableChars(cx);
|
||||
if (flat && stableChars.initTwoByte(cx, flat))
|
||||
chars = stableChars.twoByteRange().start().get();
|
||||
else
|
||||
chars = MOZ_UTF16("(stack not available)");
|
||||
JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_WARNING, js_GetErrorMessage, nullptr,
|
||||
JSMSG_TERMINATED, chars);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
PerThreadData::resetJitStackLimit()
|
||||
{
|
||||
// Note that, for now, we use the untrusted limit for ion. This is fine,
|
||||
// because it's the most conservative limit, and if we hit it, we'll bail
|
||||
// out of ion into the interpeter, which will do a proper recursion check.
|
||||
#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
|
||||
jitStackLimit_ = jit::Simulator::StackLimit();
|
||||
#else
|
||||
jitStackLimit_ = nativeStackLimit[StackForUntrustedScript];
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
PerThreadData::initJitStackLimit()
|
||||
{
|
||||
resetJitStackLimit();
|
||||
}
|
||||
|
||||
void
|
||||
PerThreadData::initJitStackLimitPar(uintptr_t limit)
|
||||
{
|
||||
jitStackLimit_ = limit;
|
||||
}
|
||||
|
||||
void
|
||||
JSRuntime::requestInterrupt(InterruptMode mode)
|
||||
{
|
||||
AutoLockForInterrupt lock(this);
|
||||
interrupt_ = true;
|
||||
interruptPar_ = true;
|
||||
mainThread.jitStackLimit_ = UINTPTR_MAX;
|
||||
|
||||
/*
|
||||
* Invalidate ionTop to trigger its over-recursion check. Note this must be
|
||||
* set before interrupt, to avoid racing with js::InvokeInterruptCallback,
|
||||
* into a weird state where interrupt is stuck at 0 but jitStackLimit is
|
||||
* MAXADDR.
|
||||
*/
|
||||
mainThread.setJitStackLimit(-1);
|
||||
|
||||
interrupt = true;
|
||||
|
||||
RequestInterruptForForkJoin(this, mode);
|
||||
|
||||
/*
|
||||
* asm.js and normal Ion code optionally use memory protection and signal
|
||||
* handlers to halt running code.
|
||||
*/
|
||||
if (canUseSignalHandlers()) {
|
||||
AutoLockForInterrupt lock(this);
|
||||
RequestInterruptForAsmJSCode(this, mode);
|
||||
jit::RequestInterruptForIonCode(this, mode);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
JSRuntime::handleInterrupt(JSContext *cx)
|
||||
{
|
||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
|
||||
if (interrupt_ || mainThread.jitStackLimit_ == UINTPTR_MAX) {
|
||||
interrupt_ = false;
|
||||
interruptPar_ = false;
|
||||
mainThread.resetJitStackLimit();
|
||||
return InvokeInterruptCallback(cx);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
jit::ExecutableAllocator *
|
||||
JSRuntime::createExecutableAllocator(JSContext *cx)
|
||||
{
|
||||
|
@ -519,13 +519,20 @@ class PerThreadData : public PerThreadDataFriendFields
|
||||
*/
|
||||
JSContext *jitJSContext;
|
||||
|
||||
/*
|
||||
* The stack limit checked by JIT code. This stack limit may be temporarily
|
||||
* set to null to force JIT code to exit (e.g., for the operation callback).
|
||||
*/
|
||||
uintptr_t jitStackLimit;
|
||||
/* See comment for JSRuntime::interrupt_. */
|
||||
private:
|
||||
mozilla::Atomic<uintptr_t, mozilla::Relaxed> jitStackLimit_;
|
||||
void resetJitStackLimit();
|
||||
friend struct ::JSRuntime;
|
||||
public:
|
||||
void initJitStackLimit();
|
||||
void initJitStackLimitPar(uintptr_t limit);
|
||||
|
||||
inline void setJitStackLimit(uintptr_t limit);
|
||||
uintptr_t jitStackLimit() const { return jitStackLimit_; }
|
||||
|
||||
// For read-only JIT use:
|
||||
void *addressOfJitStackLimit() { return &jitStackLimit_; }
|
||||
static size_t offsetOfJitStackLimit() { return offsetof(PerThreadData, jitStackLimit_); }
|
||||
|
||||
// Information about the heap allocated backtrack stack used by RegExp JIT code.
|
||||
irregexp::RegExpStack regexpStack;
|
||||
@ -678,8 +685,6 @@ class PerThreadData : public PerThreadDataFriendFields
|
||||
|
||||
class AutoLockForExclusiveAccess;
|
||||
|
||||
void RecomputeStackLimit(JSRuntime *rt, StackKind kind);
|
||||
|
||||
} // namespace js
|
||||
|
||||
struct JSRuntime : public JS::shadow::Runtime,
|
||||
@ -703,18 +708,56 @@ struct JSRuntime : public JS::shadow::Runtime,
|
||||
*/
|
||||
JSRuntime *parentRuntime;
|
||||
|
||||
/*
|
||||
* If true, we've been asked to call the interrupt callback as soon as
|
||||
* possible.
|
||||
*/
|
||||
mozilla::Atomic<bool, mozilla::Relaxed> interrupt;
|
||||
private:
|
||||
mozilla::Atomic<uint32_t, mozilla::Relaxed> interrupt_;
|
||||
mozilla::Atomic<uint32_t, mozilla::Relaxed> interruptPar_;
|
||||
public:
|
||||
|
||||
/*
|
||||
* If non-zero, ForkJoin should service an interrupt. This is a separate
|
||||
* flag from |interrupt| because we cannot use the mprotect trick with PJS
|
||||
* code and ignore the TriggerCallbackAnyThreadDontStopIon trigger.
|
||||
*/
|
||||
mozilla::Atomic<bool, mozilla::Relaxed> interruptPar;
|
||||
enum InterruptMode {
|
||||
RequestInterruptMainThread,
|
||||
RequestInterruptAnyThread,
|
||||
RequestInterruptAnyThreadDontStopIon,
|
||||
RequestInterruptAnyThreadForkJoin
|
||||
};
|
||||
|
||||
// Any thread can call requestInterrupt() to request that the main JS thread
|
||||
// stop running and call the interrupt callback (allowing the interrupt
|
||||
// callback to halt execution). To stop the main JS thread, requestInterrupt
|
||||
// sets two fields: interrupt_ (set to true) and jitStackLimit_ (set to
|
||||
// UINTPTR_MAX). The JS engine must continually poll one of these fields
|
||||
// and call handleInterrupt if either field has the interrupt value. (The
|
||||
// point of setting jitStackLimit_ to UINTPTR_MAX is that JIT code already
|
||||
// needs to guard on jitStackLimit_ in every function prologue to avoid
|
||||
// stack overflow, so we avoid a second branch on interrupt_ by setting
|
||||
// jitStackLimit_ to a value that is guaranteed to fail the guard.)
|
||||
//
|
||||
// Note that the writes to interrupt_ and jitStackLimit_ use a Relaxed
|
||||
// Atomic so, while the writes are guaranteed to eventually be visible to
|
||||
// the main thread, it can happen in any order. handleInterrupt calls the
|
||||
// interrupt callback if either is set, so it really doesn't matter as long
|
||||
// as the JS engine is continually polling at least one field. In corner
|
||||
// cases, this relaxed ordering could lead to an interrupt handler being
|
||||
// called twice in succession after a single requestInterrupt call, but
|
||||
// that's fine.
|
||||
void requestInterrupt(InterruptMode mode);
|
||||
bool handleInterrupt(JSContext *cx);
|
||||
|
||||
MOZ_ALWAYS_INLINE bool hasPendingInterrupt() const {
|
||||
return interrupt_;
|
||||
}
|
||||
MOZ_ALWAYS_INLINE bool hasPendingInterruptPar() const {
|
||||
return interruptPar_;
|
||||
}
|
||||
|
||||
// For read-only JIT use:
|
||||
void *addressOfInterruptUint32() {
|
||||
static_assert(sizeof(interrupt_) == sizeof(uint32_t), "Assumed by JIT callers");
|
||||
return &interrupt_;
|
||||
}
|
||||
void *addressOfInterruptParUint32() {
|
||||
static_assert(sizeof(interruptPar_) == sizeof(uint32_t), "Assumed by JIT callers");
|
||||
return &interruptPar_;
|
||||
}
|
||||
|
||||
/* Set when handling a signal for a thread associated with this runtime. */
|
||||
bool handlingSignal;
|
||||
@ -900,7 +943,7 @@ struct JSRuntime : public JS::shadow::Runtime,
|
||||
void setDefaultVersion(JSVersion v) { defaultVersion_ = v; }
|
||||
|
||||
/* Base address of the native stack for the current thread. */
|
||||
uintptr_t nativeStackBase;
|
||||
const uintptr_t nativeStackBase;
|
||||
|
||||
/* The native stack size limit that runtime should not exceed. */
|
||||
size_t nativeStackQuota[js::StackKindCount];
|
||||
@ -1266,10 +1309,6 @@ struct JSRuntime : public JS::shadow::Runtime,
|
||||
bool jitSupportsFloatingPoint;
|
||||
bool jitSupportsSimd;
|
||||
|
||||
// Used to reset stack limit after a signaled interrupt (i.e. jitStackLimit_ = -1)
|
||||
// has been noticed by Ion/Baseline.
|
||||
void resetJitStackLimit();
|
||||
|
||||
// Cache for jit::GetPcScript().
|
||||
js::jit::PcScriptCache *ionPcScriptCache;
|
||||
|
||||
@ -1330,17 +1369,6 @@ struct JSRuntime : public JS::shadow::Runtime,
|
||||
/* onOutOfMemory but can call the largeAllocationFailureCallback. */
|
||||
JS_FRIEND_API(void *) onOutOfMemoryCanGC(void *p, size_t bytes);
|
||||
|
||||
// Ways in which the interrupt callback on the runtime can be triggered,
|
||||
// varying based on which thread is triggering the callback.
|
||||
enum InterruptMode {
|
||||
RequestInterruptMainThread,
|
||||
RequestInterruptAnyThread,
|
||||
RequestInterruptAnyThreadDontStopIon,
|
||||
RequestInterruptAnyThreadForkJoin
|
||||
};
|
||||
|
||||
void requestInterrupt(InterruptMode mode);
|
||||
|
||||
void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes *runtime);
|
||||
|
||||
private:
|
||||
@ -1564,13 +1592,6 @@ class MOZ_STACK_CLASS AutoKeepAtoms
|
||||
}
|
||||
};
|
||||
|
||||
inline void
|
||||
PerThreadData::setJitStackLimit(uintptr_t limit)
|
||||
{
|
||||
MOZ_ASSERT(runtime_->currentThreadOwnsInterruptLock());
|
||||
jitStackLimit = limit;
|
||||
}
|
||||
|
||||
inline JSRuntime *
|
||||
PerThreadData::runtimeFromMainThread()
|
||||
{
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/gfx/PathHelpers.h"
|
||||
#include "mozilla/MathAlgorithms.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsFontMetrics.h"
|
||||
#include "nsGkAtoms.h"
|
||||
@ -51,20 +52,14 @@ NS_QUERYFRAME_TAIL_INHERITING(nsFrame)
|
||||
|
||||
nsBulletFrame::~nsBulletFrame()
|
||||
{
|
||||
NS_ASSERTION(!mBlockingOnload, "Still blocking onload in destructor?");
|
||||
}
|
||||
|
||||
void
|
||||
nsBulletFrame::DestroyFrom(nsIFrame* aDestructRoot)
|
||||
{
|
||||
// Stop image loading first
|
||||
if (mImageRequest) {
|
||||
// Deregister our image request from the refresh driver
|
||||
nsLayoutUtils::DeregisterImageRequest(PresContext(),
|
||||
mImageRequest,
|
||||
&mRequestRegistered);
|
||||
mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
|
||||
mImageRequest = nullptr;
|
||||
}
|
||||
// Stop image loading first.
|
||||
DeregisterAndCancelImageRequest();
|
||||
|
||||
if (mListener) {
|
||||
mListener->SetFrame(nullptr);
|
||||
@ -132,35 +127,21 @@ nsBulletFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
|
||||
}
|
||||
|
||||
if (needNewRequest) {
|
||||
nsRefPtr<imgRequestProxy> oldRequest = mImageRequest;
|
||||
newRequest->Clone(mListener, getter_AddRefs(mImageRequest));
|
||||
nsRefPtr<imgRequestProxy> newRequestClone;
|
||||
newRequest->Clone(mListener, getter_AddRefs(newRequestClone));
|
||||
|
||||
// Deregister the old request. We wait until after Clone is done in case
|
||||
// the old request and the new request are the same underlying image
|
||||
// accessed via different URLs.
|
||||
if (oldRequest) {
|
||||
nsLayoutUtils::DeregisterImageRequest(PresContext(), oldRequest,
|
||||
&mRequestRegistered);
|
||||
oldRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
|
||||
oldRequest = nullptr;
|
||||
}
|
||||
DeregisterAndCancelImageRequest();
|
||||
|
||||
// Register the new request.
|
||||
if (mImageRequest) {
|
||||
nsLayoutUtils::RegisterImageRequestIfAnimated(PresContext(),
|
||||
mImageRequest,
|
||||
&mRequestRegistered);
|
||||
}
|
||||
mImageRequest = Move(newRequestClone);
|
||||
RegisterImageRequest(/* aKnownToBeAnimated = */ false);
|
||||
}
|
||||
} else {
|
||||
// No image request on the new style context
|
||||
if (mImageRequest) {
|
||||
nsLayoutUtils::DeregisterImageRequest(PresContext(), mImageRequest,
|
||||
&mRequestRegistered);
|
||||
|
||||
mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
|
||||
mImageRequest = nullptr;
|
||||
}
|
||||
// No image request on the new style context.
|
||||
DeregisterAndCancelImageRequest();
|
||||
}
|
||||
|
||||
#ifdef ACCESSIBILITY
|
||||
@ -692,14 +673,67 @@ nsBulletFrame::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aDa
|
||||
// Register the image request with the refresh driver now that we know it's
|
||||
// animated.
|
||||
if (aRequest == mImageRequest) {
|
||||
nsLayoutUtils::RegisterImageRequest(PresContext(), mImageRequest,
|
||||
&mRequestRegistered);
|
||||
RegisterImageRequest(/* aKnownToBeAnimated = */ true);
|
||||
}
|
||||
}
|
||||
|
||||
if (aType == imgINotificationObserver::LOAD_COMPLETE) {
|
||||
// Unconditionally start decoding for now.
|
||||
// XXX(seth): We eventually want to decide whether to do this based on
|
||||
// visibility. We should get that for free from bug 1091236.
|
||||
if (aRequest == mImageRequest) {
|
||||
mImageRequest->RequestDecode();
|
||||
}
|
||||
InvalidateFrame();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBulletFrame::BlockOnload(imgIRequest* aRequest)
|
||||
{
|
||||
if (aRequest != mImageRequest) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_ASSERTION(!mBlockingOnload, "Double BlockOnload for an nsBulletFrame?");
|
||||
|
||||
nsIDocument* doc = GetOurCurrentDoc();
|
||||
if (doc) {
|
||||
mBlockingOnload = true;
|
||||
doc->BlockOnload();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBulletFrame::UnblockOnload(imgIRequest* aRequest)
|
||||
{
|
||||
if (aRequest != mImageRequest) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_ASSERTION(!mBlockingOnload, "Double UnblockOnload for an nsBulletFrame?");
|
||||
|
||||
nsIDocument* doc = GetOurCurrentDoc();
|
||||
if (doc) {
|
||||
doc->UnblockOnload(false);
|
||||
}
|
||||
mBlockingOnload = false;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsIDocument*
|
||||
nsBulletFrame::GetOurCurrentDoc() const
|
||||
{
|
||||
nsIContent* parentContent = GetParent()->GetContent();
|
||||
return parentContent ? parentContent->GetComposedDoc()
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
nsresult nsBulletFrame::OnStartContainer(imgIRequest *aRequest,
|
||||
imgIContainer *aImage)
|
||||
{
|
||||
@ -873,7 +907,57 @@ nsBulletFrame::GetSpokenText(nsAString& aText)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsBulletFrame::RegisterImageRequest(bool aKnownToBeAnimated)
|
||||
{
|
||||
if (mImageRequest) {
|
||||
// mRequestRegistered is a bitfield; unpack it temporarily so we can take
|
||||
// the address.
|
||||
bool isRequestRegistered = mRequestRegistered;
|
||||
|
||||
if (aKnownToBeAnimated) {
|
||||
nsLayoutUtils::RegisterImageRequest(PresContext(), mImageRequest,
|
||||
&isRequestRegistered);
|
||||
} else {
|
||||
nsLayoutUtils::RegisterImageRequestIfAnimated(PresContext(),
|
||||
mImageRequest,
|
||||
&isRequestRegistered);
|
||||
}
|
||||
|
||||
isRequestRegistered = mRequestRegistered;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
nsBulletFrame::DeregisterAndCancelImageRequest()
|
||||
{
|
||||
if (mImageRequest) {
|
||||
// mRequestRegistered is a bitfield; unpack it temporarily so we can take
|
||||
// the address.
|
||||
bool isRequestRegistered = mRequestRegistered;
|
||||
|
||||
// Deregister our image request from the refresh driver.
|
||||
nsLayoutUtils::DeregisterImageRequest(PresContext(),
|
||||
mImageRequest,
|
||||
&isRequestRegistered);
|
||||
|
||||
isRequestRegistered = mRequestRegistered;
|
||||
|
||||
// Unblock onload if we blocked it.
|
||||
if (mBlockingOnload) {
|
||||
nsIDocument* doc = GetOurCurrentDoc();
|
||||
if (doc) {
|
||||
doc->UnblockOnload(false);
|
||||
}
|
||||
mBlockingOnload = false;
|
||||
}
|
||||
|
||||
// Cancel the image request and forget about it.
|
||||
mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
|
||||
mImageRequest = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -894,7 +978,26 @@ nsBulletListener::~nsBulletListener()
|
||||
NS_IMETHODIMP
|
||||
nsBulletListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
|
||||
{
|
||||
if (!mFrame)
|
||||
if (!mFrame) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return mFrame->Notify(aRequest, aType, aData);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBulletListener::BlockOnload(imgIRequest* aRequest)
|
||||
{
|
||||
if (!mFrame) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return mFrame->BlockOnload(aRequest);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBulletListener::UnblockOnload(imgIRequest* aRequest)
|
||||
{
|
||||
if (!mFrame) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return mFrame->UnblockOnload(aRequest);
|
||||
}
|
||||
|
@ -12,19 +12,22 @@
|
||||
#include "nsFrame.h"
|
||||
|
||||
#include "imgINotificationObserver.h"
|
||||
#include "imgIOnloadBlocker.h"
|
||||
|
||||
class imgIContainer;
|
||||
class imgRequestProxy;
|
||||
|
||||
class nsBulletFrame;
|
||||
|
||||
class nsBulletListener MOZ_FINAL : public imgINotificationObserver
|
||||
class nsBulletListener MOZ_FINAL : public imgINotificationObserver,
|
||||
public imgIOnloadBlocker
|
||||
{
|
||||
public:
|
||||
nsBulletListener();
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_IMGINOTIFICATIONOBSERVER
|
||||
NS_DECL_IMGIONLOADBLOCKER
|
||||
|
||||
void SetFrame(nsBulletFrame *frame) { mFrame = frame; }
|
||||
|
||||
@ -50,11 +53,14 @@ public:
|
||||
: nsFrame(aContext)
|
||||
, mPadding(GetWritingMode())
|
||||
, mIntrinsicSize(GetWritingMode())
|
||||
{
|
||||
}
|
||||
, mRequestRegistered(false)
|
||||
, mBlockingOnload(false)
|
||||
{ }
|
||||
virtual ~nsBulletFrame();
|
||||
|
||||
NS_IMETHOD Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData);
|
||||
NS_IMETHOD Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData);
|
||||
NS_IMETHOD BlockOnload(imgIRequest* aRequest);
|
||||
NS_IMETHOD UnblockOnload(imgIRequest* aRequest);
|
||||
|
||||
// nsIFrame
|
||||
virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE;
|
||||
@ -111,6 +117,7 @@ protected:
|
||||
float aFontSizeInflation);
|
||||
|
||||
void GetLoadGroup(nsPresContext *aPresContext, nsILoadGroup **aLoadGroup);
|
||||
nsIDocument* GetOurCurrentDoc() const;
|
||||
|
||||
mozilla::LogicalMargin mPadding;
|
||||
nsRefPtr<imgRequestProxy> mImageRequest;
|
||||
@ -120,10 +127,15 @@ protected:
|
||||
int32_t mOrdinal;
|
||||
|
||||
private:
|
||||
void RegisterImageRequest(bool aKnownToBeAnimated);
|
||||
void DeregisterAndCancelImageRequest();
|
||||
|
||||
// This is a boolean flag indicating whether or not the current image request
|
||||
// has been registered with the refresh driver.
|
||||
bool mRequestRegistered;
|
||||
bool mRequestRegistered : 1;
|
||||
|
||||
// Whether we're currently blocking onload.
|
||||
bool mBlockingOnload : 1;
|
||||
};
|
||||
|
||||
#endif /* nsBulletFrame_h___ */
|
||||
|
@ -127,7 +127,7 @@ skip-if(B2G) == img-fragment-2a.html img-fragment-2-ref.html # bug 773482
|
||||
skip-if(B2G) == img-fragment-2b.html img-fragment-2-ref.html # bug 773482
|
||||
skip-if(B2G) == img-fragment-2c.html img-fragment-2-ref.html # bug 773482
|
||||
|
||||
skip-if(B2G) == list-simple-1.html list-simple-1-ref.html # bug 773482
|
||||
fuzzy-if(B2G,68,4) == list-simple-1.html list-simple-1-ref.html
|
||||
|
||||
== svg-image-simple-1.svg lime100x100.svg
|
||||
== svg-image-simple-2.svg lime100x100.svg
|
||||
|
@ -226,7 +226,10 @@ FontFace::FontFace(nsISupports* aParent, nsPresContext* aPresContext)
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
|
||||
|
||||
if (global) {
|
||||
// If the pref is not set, don't create the Promise (which the page wouldn't
|
||||
// be able to get to anyway) as it causes the window.FontFace constructor
|
||||
// to be created.
|
||||
if (global && FontFaceSet::PrefEnabled()) {
|
||||
ErrorResult rv;
|
||||
mLoaded = Promise::Create(global, rv);
|
||||
}
|
||||
|
@ -39,6 +39,8 @@ using namespace mozilla::dom;
|
||||
#define LOG_ENABLED() PR_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), \
|
||||
PR_LOG_DEBUG)
|
||||
|
||||
#define FONT_LOADING_API_ENABLED_PREF "layout.css.font-loading-api.enabled"
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(FontFaceSet)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FontFaceSet, DOMEventTargetHelper)
|
||||
@ -83,7 +85,10 @@ FontFaceSet::FontFaceSet(nsPIDOMWindow* aWindow, nsPresContext* aPresContext)
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aWindow);
|
||||
|
||||
if (global) {
|
||||
// If the pref is not set, don't create the Promise (which the page wouldn't
|
||||
// be able to get to anyway) as it causes the window.FontFaceSet constructor
|
||||
// to be created.
|
||||
if (global && PrefEnabled()) {
|
||||
ErrorResult rv;
|
||||
mReady = Promise::Create(global, rv);
|
||||
}
|
||||
@ -1337,7 +1342,7 @@ FontFaceSet::CheckLoadingStarted()
|
||||
false))->RunDOMEventWhenSafe();
|
||||
}
|
||||
|
||||
if (mReadyIsResolved) {
|
||||
if (mReadyIsResolved && PrefEnabled()) {
|
||||
nsRefPtr<Promise> ready;
|
||||
if (GetParentObject()) {
|
||||
ErrorResult rv;
|
||||
@ -1496,6 +1501,18 @@ FontFaceSet::HandleEvent(nsIDOMEvent* aEvent)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
FontFaceSet::PrefEnabled()
|
||||
{
|
||||
static bool initialized = false;
|
||||
static bool enabled;
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
Preferences::AddBoolVarCache(&enabled, FONT_LOADING_API_ENABLED_PREF);
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
|
||||
// nsICSSLoaderObserver
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -157,6 +157,11 @@ public:
|
||||
*/
|
||||
void DidRefresh();
|
||||
|
||||
/**
|
||||
* Returns whether the "layout.css.font-loading-api.enabled" pref is true.
|
||||
*/
|
||||
static bool PrefEnabled();
|
||||
|
||||
// nsICSSLoaderObserver
|
||||
NS_IMETHOD StyleSheetLoaded(mozilla::CSSStyleSheet* aSheet,
|
||||
bool aWasAlternate,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user