mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 12:25:53 +00:00
Merge mozilla-central to autoland
--HG-- extra : rebase_source : 46bdde97ce6567ce8a06b1609de547ef11b24c2a
This commit is contained in:
commit
87edaf78ee
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -7,6 +7,6 @@ DevToolsModules(
|
||||
'debugger.css',
|
||||
'debugger.js',
|
||||
'panel.js',
|
||||
'parser-worker.js',
|
||||
'pretty-print-worker.js',
|
||||
'source-map-worker.js'
|
||||
)
|
||||
|
@ -4,7 +4,7 @@
|
||||
"use strict";
|
||||
|
||||
const { Task } = require("devtools/shared/task");
|
||||
var { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
var {LocalizationHelper} = require("devtools/shared/l10n");
|
||||
|
||||
const DBG_STRINGS_URI = "devtools/client/locales/debugger.properties";
|
||||
var L10N = new LocalizationHelper(DBG_STRINGS_URI);
|
||||
@ -16,46 +16,38 @@ function DebuggerPanel(iframeWindow, toolbox) {
|
||||
}
|
||||
|
||||
DebuggerPanel.prototype = {
|
||||
open: async function() {
|
||||
open: Task.async(function* () {
|
||||
if (!this.toolbox.target.isRemote) {
|
||||
await this.toolbox.target.makeRemote();
|
||||
yield this.toolbox.target.makeRemote();
|
||||
}
|
||||
|
||||
const {
|
||||
actions,
|
||||
store,
|
||||
selectors,
|
||||
client
|
||||
} = await this.panelWin.Debugger.bootstrap({
|
||||
yield this.panelWin.Debugger.bootstrap({
|
||||
threadClient: this.toolbox.threadClient,
|
||||
tabTarget: this.toolbox.target,
|
||||
debuggerClient: this.toolbox.target._client,
|
||||
sourceMaps: this.toolbox.sourceMapService
|
||||
tabTarget: this.toolbox.target
|
||||
});
|
||||
|
||||
this._actions = actions;
|
||||
this._store = store;
|
||||
this._selectors = selectors;
|
||||
this._client = client;
|
||||
this.isReady = true;
|
||||
return this;
|
||||
}),
|
||||
|
||||
_store: function () {
|
||||
return this.panelWin.Debugger.store;
|
||||
},
|
||||
|
||||
getVarsForTests() {
|
||||
return {
|
||||
store: this._store,
|
||||
selectors: this._selectors,
|
||||
actions: this._actions,
|
||||
client: this._client
|
||||
};
|
||||
_getState: function () {
|
||||
return this._store().getState();
|
||||
},
|
||||
|
||||
_getState: function() {
|
||||
return this._store.getState();
|
||||
_actions: function () {
|
||||
return this.panelWin.Debugger.actions;
|
||||
},
|
||||
|
||||
getFrames: function() {
|
||||
let frames = this._selectors.getFrames(this._getState());
|
||||
_selectors: function () {
|
||||
return this.panelWin.Debugger.selectors;
|
||||
},
|
||||
|
||||
getFrames: function () {
|
||||
let frames = this._selectors().getFrames(this._getState());
|
||||
|
||||
// Frames is null when the debugger is not paused.
|
||||
if (!frames) {
|
||||
@ -66,7 +58,7 @@ DebuggerPanel.prototype = {
|
||||
}
|
||||
|
||||
frames = frames.toJS();
|
||||
const selectedFrame = this._selectors.getSelectedFrame(this._getState());
|
||||
const selectedFrame = this._selectors().getSelectedFrame(this._getState());
|
||||
const selected = frames.findIndex(frame => frame.id == selectedFrame.id);
|
||||
|
||||
frames.forEach(frame => {
|
||||
@ -76,15 +68,7 @@ DebuggerPanel.prototype = {
|
||||
return { frames, selected };
|
||||
},
|
||||
|
||||
selectSource(sourceURL, sourceLine) {
|
||||
this._actions.selectSourceURL(sourceURL, { line: sourceLine });
|
||||
},
|
||||
|
||||
getSource(sourceURL) {
|
||||
return this._selectors.getSourceByURL(this._getState(), sourceURL);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
destroy: function () {
|
||||
this.panelWin.Debugger.destroy();
|
||||
this.emit("destroyed");
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -55,7 +55,7 @@ return /******/ (function(modules) { // webpackBootstrap
|
||||
/***/ 0:
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
module.exports = __webpack_require__(964);
|
||||
module.exports = __webpack_require__(801);
|
||||
|
||||
|
||||
/***/ },
|
||||
@ -75,6 +75,76 @@ return /******/ (function(modules) { // webpackBootstrap
|
||||
|
||||
/***/ },
|
||||
|
||||
/***/ 801:
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var prettyFast = __webpack_require__(802);
|
||||
var assert = __webpack_require__(223);
|
||||
|
||||
function prettyPrint(_ref) {
|
||||
var url = _ref.url,
|
||||
indent = _ref.indent,
|
||||
source = _ref.source;
|
||||
|
||||
try {
|
||||
var prettified = prettyFast(source, {
|
||||
url: url,
|
||||
indent: " ".repeat(indent)
|
||||
});
|
||||
|
||||
return {
|
||||
code: prettified.code,
|
||||
mappings: prettified.map._mappings
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error(`${e.message}\n${e.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
function invertMappings(mappings) {
|
||||
return mappings._array.map(m => {
|
||||
var mapping = {
|
||||
generated: {
|
||||
line: m.originalLine,
|
||||
column: m.originalColumn
|
||||
}
|
||||
};
|
||||
if (m.source) {
|
||||
mapping.source = m.source;
|
||||
mapping.original = {
|
||||
line: m.generatedLine,
|
||||
column: m.generatedColumn
|
||||
};
|
||||
mapping.name = m.name;
|
||||
}
|
||||
return mapping;
|
||||
});
|
||||
}
|
||||
|
||||
self.onmessage = function (msg) {
|
||||
var _msg$data = msg.data,
|
||||
id = _msg$data.id,
|
||||
args = _msg$data.args;
|
||||
|
||||
assert(msg.data.method === "prettyPrint", "Method must be `prettyPrint`");
|
||||
|
||||
try {
|
||||
var _prettyPrint = prettyPrint(args[0]),
|
||||
code = _prettyPrint.code,
|
||||
mappings = _prettyPrint.mappings;
|
||||
|
||||
self.postMessage({ id, response: {
|
||||
code, mappings: invertMappings(mappings)
|
||||
} });
|
||||
} catch (e) {
|
||||
self.postMessage({ id, error: e });
|
||||
}
|
||||
};
|
||||
|
||||
/***/ },
|
||||
|
||||
/***/ 802:
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
@ -5848,80 +5918,6 @@ return /******/ (function(modules) { // webpackBootstrap
|
||||
}.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
|
||||
|
||||
|
||||
/***/ },
|
||||
|
||||
/***/ 964:
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var prettyFast = __webpack_require__(802);
|
||||
var assert = __webpack_require__(223);
|
||||
|
||||
function prettyPrint(_ref) {
|
||||
var url = _ref.url,
|
||||
indent = _ref.indent,
|
||||
source = _ref.source;
|
||||
|
||||
try {
|
||||
var prettified = prettyFast(source, {
|
||||
url: url,
|
||||
indent: " ".repeat(indent)
|
||||
});
|
||||
|
||||
return {
|
||||
code: prettified.code,
|
||||
mappings: prettified.map._mappings
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error(`${e.message}\n${e.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
function invertMappings(mappings) {
|
||||
return mappings._array.map(m => {
|
||||
var mapping = {
|
||||
generated: {
|
||||
line: m.originalLine,
|
||||
column: m.originalColumn
|
||||
}
|
||||
};
|
||||
if (m.source) {
|
||||
mapping.source = m.source;
|
||||
mapping.original = {
|
||||
line: m.generatedLine,
|
||||
column: m.generatedColumn
|
||||
};
|
||||
mapping.name = m.name;
|
||||
}
|
||||
return mapping;
|
||||
});
|
||||
}
|
||||
|
||||
self.onmessage = function (msg) {
|
||||
var _msg$data = msg.data,
|
||||
id = _msg$data.id,
|
||||
args = _msg$data.args;
|
||||
|
||||
assert(msg.data.method === "prettyPrint", "Method must be `prettyPrint`");
|
||||
|
||||
try {
|
||||
var _prettyPrint = prettyPrint(args[0]),
|
||||
code = _prettyPrint.code,
|
||||
mappings = _prettyPrint.mappings;
|
||||
|
||||
self.postMessage({
|
||||
id,
|
||||
response: {
|
||||
code,
|
||||
mappings: invertMappings(mappings)
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
self.postMessage({ id, error: e });
|
||||
}
|
||||
};
|
||||
|
||||
/***/ }
|
||||
|
||||
/******/ })
|
||||
|
5617
devtools/client/debugger/new/source-map-worker.js
Normal file
5617
devtools/client/debugger/new/source-map-worker.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -42,7 +42,6 @@ support-files =
|
||||
[browser_dbg-breakpoints.js]
|
||||
[browser_dbg-breakpoints-cond.js]
|
||||
[browser_dbg-call-stack.js]
|
||||
[browser_dbg-expressions.js]
|
||||
[browser_dbg-scopes.js]
|
||||
[browser_dbg-chrome-create.js]
|
||||
[browser_dbg-chrome-debugging.js]
|
||||
|
@ -54,6 +54,6 @@ add_task(function* () {
|
||||
|
||||
button = toggleButton(dbg);
|
||||
frames = findAllElements(dbg, "frames");
|
||||
is(button.innerText, "Collapse Rows", "toggle button should be collapsed");
|
||||
is(button.innerText, "Collapse Rows", "toggle button should be collapse");
|
||||
is(frames.length, 22, "All of the frames should be shown");
|
||||
});
|
||||
|
@ -18,9 +18,7 @@ add_task(function* () {
|
||||
// Wait for the source text to load and make sure we're in the right
|
||||
// place.
|
||||
yield waitForDispatch(dbg, "LOAD_SOURCE_TEXT");
|
||||
|
||||
// TODO: revisit highlighting lines when the debugger opens
|
||||
//assertHighlightLocation(dbg, "long.js", 66);
|
||||
assertHighlightLocation(dbg, "long.js", 66);
|
||||
|
||||
// Jump to line 16 and make sure the editor scrolled.
|
||||
yield selectSource(dbg, "long.js", 16);
|
||||
|
@ -50,5 +50,5 @@ add_task(function* () {
|
||||
invokeInTab("testModel");
|
||||
yield waitForPaused(dbg);
|
||||
assertPausedLocation(dbg, longSrc, 66);
|
||||
ok(isElementVisible(dbg, "breakpoint"), "Breakpoint is visible");
|
||||
// ok(isElementVisible(dbg, "breakpoint"), "Breakpoint is visible");
|
||||
});
|
||||
|
@ -1,56 +1,12 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* tests the watch expressions component
|
||||
* 1. add watch expressions
|
||||
* 2. edit watch expressions
|
||||
* 3. delete watch expressions
|
||||
*/
|
||||
|
||||
const expressionSelectors = {
|
||||
input: "input.input-expression"
|
||||
};
|
||||
|
||||
function getLabel(dbg, index) {
|
||||
return findElement(dbg, "expressionNode", index).innerText;
|
||||
}
|
||||
|
||||
function getValue(dbg, index) {
|
||||
return findElement(dbg, "expressionValue", index).innerText;
|
||||
}
|
||||
|
||||
async function addExpression(dbg, input) {
|
||||
info("Adding an expression");
|
||||
findElementWithSelector(dbg, expressionSelectors.input).focus();
|
||||
type(dbg, input);
|
||||
pressKey(dbg, "Enter");
|
||||
|
||||
await waitForDispatch(dbg, "EVALUATE_EXPRESSION");
|
||||
}
|
||||
|
||||
async function editExpression(dbg, input) {
|
||||
info("updating the expression");
|
||||
dblClickElement(dbg, "expressionNode", 1);
|
||||
type(dbg, input);
|
||||
pressKey(dbg, "Enter");
|
||||
await waitForDispatch(dbg, "EVALUATE_EXPRESSION");
|
||||
}
|
||||
const {
|
||||
setupTestRunner,
|
||||
expressions
|
||||
} = require("devtools/client/debugger/new/integration-tests");
|
||||
|
||||
add_task(function*() {
|
||||
const dbg = yield initDebugger("doc-script-switching.html");
|
||||
|
||||
invokeInTab("firstCall");
|
||||
yield waitForPaused(dbg);
|
||||
|
||||
yield addExpression(dbg, "f");
|
||||
is(getLabel(dbg, 1), "f");
|
||||
is(getValue(dbg, 1), "ReferenceError");
|
||||
|
||||
yield editExpression(dbg, "oo");
|
||||
is(getLabel(dbg, 1), "foo()");
|
||||
is(getValue(dbg, 1), "");
|
||||
|
||||
yield deleteExpression(dbg, "foo");
|
||||
is(findAllElements(dbg, "expressionNodes").length, 0);
|
||||
setupTestRunner(this);
|
||||
yield expressions(this);
|
||||
});
|
||||
|
@ -20,6 +20,7 @@ add_task(function* () {
|
||||
const dbg = yield initDebugger("doc-exceptions.html");
|
||||
|
||||
// test skipping an uncaught exception
|
||||
yield togglePauseOnExceptions(dbg, false, false);
|
||||
yield uncaughtException();
|
||||
ok(!isPaused(dbg));
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
// Tests basic pretty-printing functionality.
|
||||
|
||||
add_task(function*() {
|
||||
add_task(function* () {
|
||||
const dbg = yield initDebugger("doc-minified.html");
|
||||
|
||||
yield selectSource(dbg, "math.min.js");
|
||||
|
@ -19,13 +19,4 @@ add_task(function* () {
|
||||
|
||||
is(getLabel(dbg, 1), "secondCall");
|
||||
is(getLabel(dbg, 2), "<this>");
|
||||
is(getLabel(dbg, 4), "foo()");
|
||||
|
||||
toggleNode(dbg, 4);
|
||||
yield waitForDispatch(dbg, "LOAD_OBJECT_PROPERTIES");
|
||||
is(getLabel(dbg, 5), "prototype");
|
||||
|
||||
yield stepOver(dbg);
|
||||
is(getLabel(dbg, 4), "foo()");
|
||||
is(getLabel(dbg, 5), "prototype");
|
||||
});
|
||||
|
@ -4,10 +4,7 @@
|
||||
// Test that an error while loading a sourcemap does not break
|
||||
// debugging.
|
||||
|
||||
add_task(function*() {
|
||||
// NOTE: the CORS call makes the test run times inconsistent
|
||||
requestLongerTimeout(2);
|
||||
|
||||
add_task(function* () {
|
||||
const dbg = yield initDebugger("doc-sourcemap-bogus.html");
|
||||
const { selectors: { getSources }, getState } = dbg;
|
||||
|
||||
|
@ -4,10 +4,7 @@
|
||||
// Tests loading sourcemapped sources, setting breakpoints, and
|
||||
// stepping in them.
|
||||
|
||||
add_task(function*() {
|
||||
// NOTE: the CORS call makes the test run times inconsistent
|
||||
requestLongerTimeout(2);
|
||||
|
||||
add_task(function* () {
|
||||
const dbg = yield initDebugger("doc-sourcemaps.html");
|
||||
const { selectors: { getBreakpoint, getBreakpoints }, getState } = dbg;
|
||||
|
||||
@ -16,27 +13,21 @@ add_task(function*() {
|
||||
const entrySrc = findSource(dbg, "entry.js");
|
||||
|
||||
yield selectSource(dbg, entrySrc);
|
||||
ok(
|
||||
dbg.win.cm.getValue().includes("window.keepMeAlive"),
|
||||
"Original source text loaded correctly"
|
||||
);
|
||||
ok(dbg.win.cm.getValue().includes("window.keepMeAlive"),
|
||||
"Original source text loaded correctly");
|
||||
|
||||
// Test that breakpoint sliding is not attempted. The breakpoint
|
||||
// should not move anywhere.
|
||||
yield addBreakpoint(dbg, entrySrc, 13);
|
||||
is(getBreakpoints(getState()).size, 1, "One breakpoint exists");
|
||||
ok(
|
||||
getBreakpoint(getState(), { sourceId: entrySrc.id, line: 13 }),
|
||||
"Breakpoint has correct line"
|
||||
);
|
||||
ok(getBreakpoint(getState(), { sourceId: entrySrc.id, line: 13 }),
|
||||
"Breakpoint has correct line");
|
||||
|
||||
// Test breaking on a breakpoint
|
||||
yield addBreakpoint(dbg, "entry.js", 15);
|
||||
is(getBreakpoints(getState()).size, 2, "Two breakpoints exist");
|
||||
ok(
|
||||
getBreakpoint(getState(), { sourceId: entrySrc.id, line: 15 }),
|
||||
"Breakpoint has correct line"
|
||||
);
|
||||
ok(getBreakpoint(getState(), { sourceId: entrySrc.id, line: 15 }),
|
||||
"Breakpoint has correct line");
|
||||
|
||||
invokeInTab("keepMeAlive");
|
||||
yield waitForPaused(dbg);
|
||||
|
@ -6,10 +6,7 @@
|
||||
|
||||
// This source map does not have source contents, so it's fetched separately
|
||||
|
||||
add_task(function*() {
|
||||
// NOTE: the CORS call makes the test run times inconsistent
|
||||
requestLongerTimeout(2);
|
||||
|
||||
add_task(function* () {
|
||||
const dbg = yield initDebugger("doc-sourcemaps2.html");
|
||||
const { selectors: { getBreakpoint, getBreakpoints }, getState } = dbg;
|
||||
|
||||
@ -23,10 +20,8 @@ add_task(function*() {
|
||||
// Test that breakpoint is not off by a line.
|
||||
yield addBreakpoint(dbg, mainSrc, 4);
|
||||
is(getBreakpoints(getState()).size, 1, "One breakpoint exists");
|
||||
ok(
|
||||
getBreakpoint(getState(), { sourceId: mainSrc.id, line: 4 }),
|
||||
"Breakpoint has correct line"
|
||||
);
|
||||
ok(getBreakpoint(getState(), { sourceId: mainSrc.id, line: 4 }),
|
||||
"Breakpoint has correct line");
|
||||
|
||||
invokeInTab("logMessage");
|
||||
|
||||
|
@ -6,7 +6,6 @@
|
||||
<body>
|
||||
<script>
|
||||
debugger;
|
||||
|
||||
// This inline script allows this HTML page to show up as a
|
||||
// source. It also needs to introduce a new global variable so
|
||||
// it's not immediately garbage collected.
|
||||
|
@ -33,14 +33,13 @@
|
||||
*/
|
||||
|
||||
// shared-head.js handles imports, constants, and utility functions
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
|
||||
this
|
||||
);
|
||||
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
|
||||
var { Toolbox } = require("devtools/client/framework/toolbox");
|
||||
const EXAMPLE_URL = "http://example.com/browser/devtools/client/debugger/new/test/mochitest/examples/";
|
||||
|
||||
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", true);
|
||||
Services.prefs.clearUserPref("devtools.debugger.tabs")
|
||||
Services.prefs.clearUserPref("devtools.debugger.pending-selected-location")
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend");
|
||||
@ -77,9 +76,9 @@ function _afterDispatchDone(store, type) {
|
||||
type: "@@service/waitUntil",
|
||||
predicate: action => {
|
||||
if (action.type === type) {
|
||||
return action.status
|
||||
? action.status === "done" || action.status === "error"
|
||||
: true;
|
||||
return action.status ?
|
||||
(action.status === "done" || action.status === "error") :
|
||||
true;
|
||||
}
|
||||
},
|
||||
run: (dispatch, getState, action) => {
|
||||
@ -103,7 +102,7 @@ function _afterDispatchDone(store, type) {
|
||||
function waitForDispatch(dbg, type, eventRepeat = 1) {
|
||||
let count = 0;
|
||||
|
||||
return Task.spawn(function*() {
|
||||
return Task.spawn(function* () {
|
||||
info("Waiting for " + type + " to dispatch " + eventRepeat + " time(s)");
|
||||
while (count < eventRepeat) {
|
||||
yield _afterDispatchDone(dbg.store, type);
|
||||
@ -171,23 +170,21 @@ function waitForSources(dbg, ...sources) {
|
||||
|
||||
info("Waiting on sources: " + sources.join(", "));
|
||||
const { selectors: { getSources }, store } = dbg;
|
||||
return Promise.all(
|
||||
sources.map(url => {
|
||||
function sourceExists(state) {
|
||||
return getSources(state).some(s => {
|
||||
return s.get("url").includes(url);
|
||||
});
|
||||
}
|
||||
return Promise.all(sources.map(url => {
|
||||
function sourceExists(state) {
|
||||
return getSources(state).some(s => {
|
||||
return s.get("url").includes(url);
|
||||
});
|
||||
}
|
||||
|
||||
if (!sourceExists(store.getState())) {
|
||||
return waitForState(dbg, sourceExists);
|
||||
}
|
||||
})
|
||||
);
|
||||
if (!sourceExists(store.getState())) {
|
||||
return waitForState(dbg, sourceExists);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function waitForElement(dbg, selector) {
|
||||
return waitUntil(() => findElementWithSelector(dbg, selector));
|
||||
return waitUntil(() => findElementWithSelector(dbg, selector))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -212,10 +209,8 @@ function assertPausedLocation(dbg, source, line) {
|
||||
is(location.get("line"), line);
|
||||
|
||||
// Check the debug line
|
||||
ok(
|
||||
dbg.win.cm.lineInfo(line - 1).wrapClass.includes("debug-line"),
|
||||
"Line is highlighted as paused"
|
||||
);
|
||||
ok(dbg.win.cm.lineInfo(line - 1).wrapClass.includes("debug-line"),
|
||||
"Line is highlighted as paused");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -237,14 +232,10 @@ function assertHighlightLocation(dbg, source, line) {
|
||||
// Check the highlight line
|
||||
const lineEl = findElement(dbg, "highlightLine");
|
||||
ok(lineEl, "Line is highlighted");
|
||||
ok(
|
||||
isVisibleWithin(findElement(dbg, "codeMirror"), lineEl),
|
||||
"Highlighted line is visible"
|
||||
);
|
||||
ok(
|
||||
dbg.win.cm.lineInfo(line - 1).wrapClass.includes("highlight-line"),
|
||||
"Line is highlighted"
|
||||
);
|
||||
// ok(isVisibleWithin(findElement(dbg, "codeMirror"), lineEl),
|
||||
// "Highlighted line is visible");
|
||||
ok(dbg.win.cm.lineInfo(line - 1).wrapClass.includes("highlight-line"),
|
||||
"Line is highlighted");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -267,11 +258,12 @@ function isPaused(dbg) {
|
||||
* @static
|
||||
*/
|
||||
function waitForPaused(dbg) {
|
||||
return Task.spawn(function*() {
|
||||
return Task.spawn(function* () {
|
||||
// We want to make sure that we get both a real paused event and
|
||||
// that the state is fully populated. The client may do some more
|
||||
// work (call other client methods) before populating the state.
|
||||
yield waitForThreadEvents(dbg, "paused"), yield waitForState(dbg, state => {
|
||||
yield waitForThreadEvents(dbg, "paused"),
|
||||
yield waitForState(dbg, state => {
|
||||
const pause = dbg.selectors.getPause(state);
|
||||
// Make sure we have the paused state.
|
||||
if (!pause) {
|
||||
@ -287,16 +279,15 @@ function waitForPaused(dbg) {
|
||||
}
|
||||
|
||||
function createDebuggerContext(toolbox) {
|
||||
const panel = toolbox.getPanel("jsdebugger");
|
||||
const win = panel.panelWin;
|
||||
const { store, client, selectors, actions } = panel.getVarsForTests();
|
||||
const win = toolbox.getPanel("jsdebugger").panelWin;
|
||||
const store = win.Debugger.store;
|
||||
|
||||
return {
|
||||
actions: actions,
|
||||
selectors: selectors,
|
||||
actions: win.Debugger.actions,
|
||||
selectors: win.Debugger.selectors,
|
||||
getState: store.getState,
|
||||
store: store,
|
||||
client: client,
|
||||
client: win.Debugger.client,
|
||||
toolbox: toolbox,
|
||||
win: win
|
||||
};
|
||||
@ -312,13 +303,9 @@ function createDebuggerContext(toolbox) {
|
||||
* @static
|
||||
*/
|
||||
function initDebugger(url, ...sources) {
|
||||
return Task.spawn(function*() {
|
||||
Services.prefs.clearUserPref("devtools.debugger.pause-on-exceptions");
|
||||
Services.prefs.clearUserPref("devtools.debugger.ignore-caught-exceptions");
|
||||
Services.prefs.clearUserPref("devtools.debugger.tabs");
|
||||
Services.prefs.clearUserPref("devtools.debugger.pending-selected-location");
|
||||
Services.prefs.clearUserPref("devtools.debugger.pending-breakpoints");
|
||||
Services.prefs.clearUserPref("devtools.debugger.expressions");
|
||||
return Task.spawn(function* () {
|
||||
Services.prefs.clearUserPref("devtools.debugger.tabs")
|
||||
Services.prefs.clearUserPref("devtools.debugger.pending-selected-location")
|
||||
const toolbox = yield openNewTabAndToolbox(EXAMPLE_URL + url, "jsdebugger");
|
||||
return createDebuggerContext(toolbox);
|
||||
});
|
||||
@ -442,11 +429,6 @@ function resume(dbg) {
|
||||
return waitForThreadEvents(dbg, "resumed");
|
||||
}
|
||||
|
||||
function deleteExpression(dbg, input) {
|
||||
info("Resuming");
|
||||
return dbg.actions.deleteExpression({ input });
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the debuggee.
|
||||
*
|
||||
@ -518,11 +500,8 @@ function removeBreakpoint(dbg, sourceId, line, col) {
|
||||
* @return {Promise}
|
||||
* @static
|
||||
*/
|
||||
function togglePauseOnExceptions(
|
||||
dbg,
|
||||
pauseOnExceptions,
|
||||
ignoreCaughtExceptions
|
||||
) {
|
||||
function togglePauseOnExceptions(dbg,
|
||||
pauseOnExceptions, ignoreCaughtExceptions) {
|
||||
const command = dbg.actions.pauseOnExceptions(
|
||||
pauseOnExceptions,
|
||||
ignoreCaughtExceptions
|
||||
@ -547,7 +526,7 @@ function togglePauseOnExceptions(
|
||||
*/
|
||||
function invokeInTab(fnc) {
|
||||
info(`Invoking function ${fnc} in tab`);
|
||||
return ContentTask.spawn(gBrowser.selectedBrowser, fnc, function*(fnc) {
|
||||
return ContentTask.spawn(gBrowser.selectedBrowser, fnc, function* (fnc) {
|
||||
content.wrappedJSObject[fnc](); // eslint-disable-line mozilla/no-cpows-in-tests, max-len
|
||||
});
|
||||
}
|
||||
@ -555,21 +534,18 @@ function invokeInTab(fnc) {
|
||||
const isLinux = Services.appinfo.OS === "Linux";
|
||||
const cmdOrCtrl = isLinux ? { ctrlKey: true } : { metaKey: true };
|
||||
const keyMappings = {
|
||||
sourceSearch: { code: "p", modifiers: cmdOrCtrl },
|
||||
fileSearch: { code: "f", modifiers: cmdOrCtrl },
|
||||
Enter: { code: "VK_RETURN" },
|
||||
Up: { code: "VK_UP" },
|
||||
Down: { code: "VK_DOWN" },
|
||||
Tab: { code: "VK_TAB" },
|
||||
Escape: { code: "VK_ESCAPE" },
|
||||
sourceSearch: { code: "p", modifiers: cmdOrCtrl},
|
||||
fileSearch: { code: "f", modifiers: cmdOrCtrl},
|
||||
"Enter": { code: "VK_RETURN" },
|
||||
"Up": { code: "VK_UP" },
|
||||
"Down": { code: "VK_DOWN" },
|
||||
"Tab": { code: "VK_TAB" },
|
||||
"Escape": { code: "VK_ESCAPE" },
|
||||
pauseKey: { code: "VK_F8" },
|
||||
resumeKey: { code: "VK_F8" },
|
||||
stepOverKey: { code: "VK_F10" },
|
||||
stepInKey: { code: "VK_F11", modifiers: { ctrlKey: isLinux } },
|
||||
stepOutKey: {
|
||||
code: "VK_F11",
|
||||
modifiers: { ctrlKey: isLinux, shiftKey: true }
|
||||
}
|
||||
stepInKey: { code: "VK_F11", modifiers: { ctrlKey: isLinux }},
|
||||
stepOutKey: { code: "VK_F11", modifiers: { ctrlKey: isLinux, shiftKey: true }}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -585,7 +561,11 @@ function pressKey(dbg, keyName) {
|
||||
let keyEvent = keyMappings[keyName];
|
||||
|
||||
const { code, modifiers } = keyEvent;
|
||||
return EventUtils.synthesizeKey(code, modifiers || {}, dbg.win);
|
||||
return EventUtils.synthesizeKey(
|
||||
code,
|
||||
modifiers || {},
|
||||
dbg.win
|
||||
);
|
||||
}
|
||||
|
||||
function type(dbg, string) {
|
||||
@ -597,19 +577,14 @@ function type(dbg, string) {
|
||||
function isVisibleWithin(outerEl, innerEl) {
|
||||
const innerRect = innerEl.getBoundingClientRect();
|
||||
const outerRect = outerEl.getBoundingClientRect();
|
||||
return innerRect.top > outerRect.top && innerRect.bottom < outerRect.bottom;
|
||||
|
||||
return innerRect.top > outerRect.top &&
|
||||
innerRect.bottom < outerRect.bottom;
|
||||
}
|
||||
|
||||
const selectors = {
|
||||
callStackHeader: ".call-stack-pane ._header",
|
||||
callStackBody: ".call-stack-pane .pane",
|
||||
expressionNode: i =>
|
||||
`.expressions-list .tree-node:nth-child(${i}) .object-label`,
|
||||
expressionValue: i =>
|
||||
`.expressions-list .tree-node:nth-child(${i}) .object-value`,
|
||||
expressionClose: i =>
|
||||
`.expressions-list .expression-container:nth-child(${i}) .close`,
|
||||
expressionNodes: ".expressions-list .tree-node",
|
||||
scopesHeader: ".scopes-pane ._header",
|
||||
breakpointItem: i => `.breakpoints-list .breakpoint:nth-child(${i})`,
|
||||
scopeNode: i => `.scopes-list .tree-node:nth-child(${i}) .object-label`,
|
||||
@ -630,7 +605,7 @@ const selectors = {
|
||||
sourceFooter: ".source-footer",
|
||||
sourceNode: i => `.sources-list .tree-node:nth-child(${i})`,
|
||||
sourceNodes: ".sources-list .tree-node",
|
||||
sourceArrow: i => `.sources-list .tree-node:nth-child(${i}) .arrow`
|
||||
sourceArrow: i => `.sources-list .tree-node:nth-child(${i}) .arrow`,
|
||||
};
|
||||
|
||||
function getSelector(elementName, ...args) {
|
||||
@ -672,9 +647,6 @@ function findAllElements(dbg, elementName, ...args) {
|
||||
*/
|
||||
function clickElement(dbg, elementName, ...args) {
|
||||
const selector = getSelector(elementName, ...args);
|
||||
const el = findElement(dbg, elementName, ...args);
|
||||
el.scrollIntoView();
|
||||
|
||||
return EventUtils.synthesizeMouseAtCenter(
|
||||
findElementWithSelector(dbg, selector),
|
||||
{},
|
||||
@ -682,22 +654,12 @@ function clickElement(dbg, elementName, ...args) {
|
||||
);
|
||||
}
|
||||
|
||||
function dblClickElement(dbg, elementName, ...args) {
|
||||
const selector = getSelector(elementName, ...args);
|
||||
|
||||
return EventUtils.synthesizeMouseAtCenter(
|
||||
findElementWithSelector(dbg, selector),
|
||||
{ clickCount: 2 },
|
||||
dbg.win
|
||||
);
|
||||
}
|
||||
|
||||
function rightClickElement(dbg, elementName, ...args) {
|
||||
const selector = getSelector(elementName, ...args);
|
||||
const doc = dbg.win.document;
|
||||
return EventUtils.synthesizeMouseAtCenter(
|
||||
doc.querySelector(selector),
|
||||
{ type: "contextmenu" },
|
||||
{type: "contextmenu"},
|
||||
dbg.win
|
||||
);
|
||||
}
|
||||
@ -707,10 +669,10 @@ function selectMenuItem(dbg, index) {
|
||||
const doc = dbg.toolbox.win.document;
|
||||
|
||||
// there are several context menus, we want the one with the menu-api
|
||||
const popup = doc.querySelector('menupopup[menu-api="true"]');
|
||||
const popup = doc.querySelector("menupopup[menu-api=\"true\"]");
|
||||
|
||||
const item = popup.querySelector(`menuitem:nth-child(${index})`);
|
||||
return EventUtils.synthesizeMouseAtCenter(item, {}, dbg.toolbox.win);
|
||||
return EventUtils.synthesizeMouseAtCenter(item, {}, dbg.toolbox.win );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,10 +124,6 @@ blackBoxCheckboxTooltip=Toggle black boxing
|
||||
# searching all the source files the debugger has seen.
|
||||
sources.search.key=P
|
||||
|
||||
# LOCALIZATION NOTE (sources.noSourcesAvailable): Text shown when the debugger
|
||||
# does not have any sources.
|
||||
sources.noSourcesAvailable=This page has no sources
|
||||
|
||||
# LOCALIZATION NOTE (sources.searchAlt.key): Alternate key shortcut to open
|
||||
# the search for searching all the source files the debugger has seen.
|
||||
sources.searchAlt.key=O
|
||||
@ -239,17 +235,10 @@ callStack.expand=Expand Rows
|
||||
# for the summarizing the selected search result. e.g. 5 of 10 results.
|
||||
editor.searchResults=%d of %d results
|
||||
|
||||
# LOCALIZATION NOTE (sourceSearch.singleResult): Copy shown when there is one result.
|
||||
editor.singleResult=1 result
|
||||
|
||||
# LOCALIZATION NOTE (editor.noResults): Editor Search bar message
|
||||
# for when no results found.
|
||||
editor.noResults=no results
|
||||
|
||||
# LOCALIZATION NOTE (editor.searchTypeToggleTitle): Search bar title for
|
||||
# toggling search type buttons(function search, variable search)
|
||||
editor.searchTypeToggleTitle=Search for:
|
||||
|
||||
# LOCALIZATION NOTE (editor.addBreakpoint): Editor gutter context menu item
|
||||
# for adding a breakpoint on a line.
|
||||
editor.addBreakpoint=Add Breakpoint
|
||||
@ -282,9 +271,9 @@ editor.conditionalPanel.placeholder=This breakpoint will pause when the expressi
|
||||
# close button inside ConditionalPanel component
|
||||
editor.conditionalPanel.close=Cancel edit breakpoint and close
|
||||
|
||||
# LOCALIZATION NOTE (editor.jumpToMappedLocation): Context menu item
|
||||
# LOCALIZATION NOTE (editor.jumpToMappedLocation1): Context menu item
|
||||
# for navigating to a source mapped location
|
||||
editor.jumpToMappedLocation=Jump to %s location
|
||||
editor.jumpToMappedLocation1=Jump to %S location
|
||||
|
||||
# LOCALIZATION NOTE (generated): Source Map term for a server source location
|
||||
generated=generated
|
||||
@ -352,22 +341,6 @@ sourceTabs.prettyPrint=Pretty Print Source
|
||||
# the editor context menu.
|
||||
sourceTabs.prettyPrint.accesskey=p
|
||||
|
||||
# LOCALIZATION NOTE (sourceFooter.blackbox): Tooltip text associated
|
||||
# with the black box button
|
||||
sourceFooter.blackbox=Blackbox Source
|
||||
|
||||
# LOCALIZATION NOTE (sourceFooter.unblackbox): Tooltip text associated
|
||||
# with the black box button
|
||||
sourceFooter.unblackbox=Unblackbox Source
|
||||
|
||||
# LOCALIZATION NOTE (sourceFooter.blackbox.accesskey): Access key to black box
|
||||
# an associated source
|
||||
sourceFooter.blackbox.accesskey=b
|
||||
|
||||
# LOCALIZATION NOTE (sourceFooter.blackboxed): Text associated
|
||||
# with a blackboxed source
|
||||
sourceFooter.blackboxed=Blackboxed Source
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.closeTabButtonTooltip): The tooltip that is displayed
|
||||
# for close tab button in source tabs.
|
||||
sourceTabs.closeTabButtonTooltip=Close tab
|
||||
@ -420,6 +393,10 @@ sourceSearch.search=Search Sources…
|
||||
# message when the query did not match any of the sources.
|
||||
sourceSearch.noResults=No files matching %S found
|
||||
|
||||
# LOCALIZATION NOTE (sourceFooter.debugBtnTooltip): Tooltip text associated
|
||||
# with the pretty-print button
|
||||
sourceFooter.debugBtnTooltip=Prettify Source
|
||||
|
||||
# LOCALIZATION NOTE (ignoreExceptions): The pause on exceptions button tooltip
|
||||
# when the debugger will not pause on exceptions.
|
||||
ignoreExceptions=Ignore exceptions. Click to pause on uncaught exceptions
|
||||
@ -531,30 +508,8 @@ watchExpressionsSeparatorLabel2=\u0020→
|
||||
# in the functions search panel as a separator between function's inferred name
|
||||
# and its real name (if available).
|
||||
functionSearchSeparatorLabel=←
|
||||
|
||||
# LOCALIZATION NOTE(symbolSearch.search.functionsPlaceholder): The placeholder
|
||||
# text displayed when the user searches for functions in a file
|
||||
symbolSearch.search.functionsPlaceholder=Search functions…
|
||||
|
||||
# LOCALIZATION NOTE(symbolSearch.search.variablesPlaceholder): The placeholder
|
||||
# text displayed when the user searches for variables in a file
|
||||
symbolSearch.search.variablesPlaceholder=Search variables…
|
||||
|
||||
# LOCALIZATION NOTE(symbolSearch.search.key): The shortcut (cmd+shift+o) for
|
||||
# searching for a function or variable
|
||||
symbolSearch.search.key=O
|
||||
|
||||
# LOCALIZATION NOTE(symbolSearch.searchModifier.regex): A search option
|
||||
# when searching text in a file
|
||||
symbolSearch.searchModifier.regex=Regex
|
||||
|
||||
# LOCALIZATION NOTE(symbolSearch.searchModifier.caseSensitive): A search option
|
||||
# when searching text in a file
|
||||
symbolSearch.searchModifier.caseSensitive=Case sensitive
|
||||
|
||||
# LOCALIZATION NOTE(symbolSearch.searchModifier.wholeWord): A search option
|
||||
# when searching text in a file
|
||||
symbolSearch.searchModifier.wholeWord=Whole word
|
||||
functionSearch.search.placeholder=Search Functions…
|
||||
functionSearch.search.key=O
|
||||
|
||||
# LOCALIZATION NOTE (resumptionOrderPanelTitle): This is the text that appears
|
||||
# as a description in the notification panel popup, when multiple debuggers are
|
||||
@ -622,7 +577,3 @@ whyPaused.debugCommand=Paused on debugged function
|
||||
# in a info block explaining how the debugger is currently paused on an event
|
||||
# listener breakpoint set
|
||||
whyPaused.other=Debugger paused
|
||||
|
||||
# LOCALIZATION NOTE (ctrl): The text that is used for documenting
|
||||
# keyboard shortcuts that use the control key
|
||||
ctrl=Ctrl
|
||||
|
@ -1,5 +1,7 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
# -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#ifdef RELEASE_OR_BETA
|
||||
pref("devtools.debugger.new-debugger-frontend", false);
|
||||
@ -15,7 +17,7 @@ pref("devtools.debugger.chrome-debugging-websocket", false);
|
||||
pref("devtools.debugger.remote-host", "localhost");
|
||||
pref("devtools.debugger.remote-timeout", 20000);
|
||||
pref("devtools.debugger.pause-on-exceptions", false);
|
||||
pref("devtools.debugger.ignore-caught-exceptions", false);
|
||||
pref("devtools.debugger.ignore-caught-exceptions", true);
|
||||
pref("devtools.debugger.source-maps-enabled", true);
|
||||
pref("devtools.debugger.client-source-maps-enabled", true);
|
||||
pref("devtools.debugger.pretty-print-enabled", true);
|
||||
@ -36,5 +38,4 @@ pref("devtools.debugger.start-panel-collapsed", false);
|
||||
pref("devtools.debugger.end-panel-collapsed", false);
|
||||
pref("devtools.debugger.tabs", "[]");
|
||||
pref("devtools.debugger.pending-selected-location", "{}");
|
||||
pref("devtools.debugger.pending-breakpoints", "[]");
|
||||
pref("devtools.debugger.expressions", "[]");
|
||||
|
||||
|
@ -59,10 +59,10 @@ exports.viewSourceInDebugger = Task.async(function* (toolbox, sourceURL, sourceL
|
||||
|
||||
// New debugger frontend
|
||||
if (Services.prefs.getBoolPref("devtools.debugger.new-debugger-frontend")) {
|
||||
const source = dbg.getSource(sourceURL);
|
||||
const source = dbg._selectors().getSourceByURL(dbg._getState(), sourceURL);
|
||||
if (source) {
|
||||
yield toolbox.selectTool("jsdebugger");
|
||||
dbg.selectSource(sourceURL, sourceLine);
|
||||
dbg._actions().selectSourceURL(sourceURL, { line: sourceLine });
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ function* checkClickOnNode(hud, toolbox, frameLinkNode) {
|
||||
|
||||
let dbg = toolbox.getPanel("jsdebugger");
|
||||
is(
|
||||
dbg._selectors.getSelectedSource(dbg._getState()).get("url"),
|
||||
dbg._selectors().getSelectedSource(dbg._getState()).get("url"),
|
||||
url,
|
||||
"expected source url"
|
||||
);
|
||||
|
@ -56,7 +56,7 @@ function* checkClickOnNode(hud, toolbox, frameLinkNode) {
|
||||
let url = frameLinkNode.getAttribute("data-url");
|
||||
let dbg = toolbox.getPanel("jsdebugger");
|
||||
is(
|
||||
dbg._selectors.getSelectedSource(dbg._getState()).get("url"),
|
||||
dbg._selectors().getSelectedSource(dbg._getState()).get("url"),
|
||||
url,
|
||||
`Debugger is opened at expected source url (${url})`
|
||||
);
|
||||
|
@ -81,7 +81,7 @@ function test() {
|
||||
|
||||
let toolbox = yield gDevTools.getToolbox(hud.target);
|
||||
let dbg = toolbox.getPanel("jsdebugger");
|
||||
is(dbg._selectors.getSelectedSource(dbg._getState()).get("url"),
|
||||
is(dbg._selectors().getSelectedSource(dbg._getState()).get("url"),
|
||||
url,
|
||||
"expected source url");
|
||||
}
|
||||
|
@ -8790,6 +8790,59 @@ nsContentUtils::StorageAllowedForPrincipal(nsIPrincipal* aPrincipal)
|
||||
return InternalStorageAllowedForPrincipal(aPrincipal, nullptr);
|
||||
}
|
||||
|
||||
// static, private
|
||||
void
|
||||
nsContentUtils::GetCookieBehaviorForPrincipal(nsIPrincipal* aPrincipal,
|
||||
uint32_t* aLifetimePolicy,
|
||||
uint32_t* aBehavior)
|
||||
{
|
||||
*aLifetimePolicy = sCookiesLifetimePolicy;
|
||||
*aBehavior = sCookiesBehavior;
|
||||
|
||||
// Any permissions set for the given principal will override our default
|
||||
// settings from preferences.
|
||||
nsCOMPtr<nsIPermissionManager> permissionManager =
|
||||
services::GetPermissionManager();
|
||||
if (!permissionManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t perm;
|
||||
permissionManager->TestPermissionFromPrincipal(aPrincipal, "cookie", &perm);
|
||||
switch (perm) {
|
||||
case nsICookiePermission::ACCESS_ALLOW:
|
||||
*aBehavior = nsICookieService::BEHAVIOR_ACCEPT;
|
||||
*aLifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
|
||||
break;
|
||||
case nsICookiePermission::ACCESS_DENY:
|
||||
*aBehavior = nsICookieService::BEHAVIOR_REJECT;
|
||||
*aLifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
|
||||
break;
|
||||
case nsICookiePermission::ACCESS_SESSION:
|
||||
*aBehavior = nsICookieService::BEHAVIOR_ACCEPT;
|
||||
*aLifetimePolicy = nsICookieService::ACCEPT_SESSION;
|
||||
break;
|
||||
case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY:
|
||||
*aBehavior = nsICookieService::BEHAVIOR_REJECT_FOREIGN;
|
||||
// NOTE: The decision was made here to override the lifetime policy to be
|
||||
// ACCEPT_NORMALLY for consistency with ACCESS_ALLOW, but this does
|
||||
// prevent us from expressing BEHAVIOR_REJECT_FOREIGN/ACCEPT_SESSION for a
|
||||
// specific domain. As BEHAVIOR_REJECT_FOREIGN isn't visible in our UI,
|
||||
// this is probably not an issue.
|
||||
*aLifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
|
||||
break;
|
||||
case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY:
|
||||
*aBehavior = nsICookieService::BEHAVIOR_LIMIT_FOREIGN;
|
||||
// NOTE: The decision was made here to override the lifetime policy to be
|
||||
// ACCEPT_NORMALLY for consistency with ACCESS_ALLOW, but this does
|
||||
// prevent us from expressing BEHAVIOR_REJECT_FOREIGN/ACCEPT_SESSION for a
|
||||
// specific domain. As BEHAVIOR_LIMIT_FOREIGN isn't visible in our UI,
|
||||
// this is probably not an issue.
|
||||
*aLifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// static, private
|
||||
nsContentUtils::StorageAccess
|
||||
nsContentUtils::InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal,
|
||||
@ -8819,28 +8872,12 @@ nsContentUtils::InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal,
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPermissionManager> permissionManager =
|
||||
services::GetPermissionManager();
|
||||
if (!permissionManager) {
|
||||
return StorageAccess::eDeny;
|
||||
}
|
||||
|
||||
// check the permission manager for any allow or deny permissions
|
||||
// for cookies for the window.
|
||||
uint32_t perm;
|
||||
permissionManager->TestPermissionFromPrincipal(aPrincipal, "cookie", &perm);
|
||||
if (perm == nsIPermissionManager::DENY_ACTION) {
|
||||
return StorageAccess::eDeny;
|
||||
}
|
||||
if (perm == nsICookiePermission::ACCESS_SESSION) {
|
||||
return std::min(access, StorageAccess::eSessionScoped);
|
||||
}
|
||||
if (perm == nsIPermissionManager::ALLOW_ACTION) {
|
||||
return access;
|
||||
}
|
||||
uint32_t lifetimePolicy;
|
||||
uint32_t behavior;
|
||||
GetCookieBehaviorForPrincipal(aPrincipal, &lifetimePolicy, &behavior);
|
||||
|
||||
// Check if we should only allow storage for the session, and record that fact
|
||||
if (sCookiesLifetimePolicy == nsICookieService::ACCEPT_SESSION) {
|
||||
if (lifetimePolicy == nsICookieService::ACCEPT_SESSION) {
|
||||
// Storage could be StorageAccess::ePrivateBrowsing or StorageAccess::eAllow
|
||||
// so perform a std::min comparison to make sure we preserve ePrivateBrowsing
|
||||
// if it has been set.
|
||||
@ -8881,13 +8918,13 @@ nsContentUtils::InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal,
|
||||
}
|
||||
|
||||
// We don't want to prompt for every attempt to access permissions.
|
||||
if (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT) {
|
||||
if (behavior == nsICookieService::BEHAVIOR_REJECT) {
|
||||
return StorageAccess::eDeny;
|
||||
}
|
||||
|
||||
// In the absense of a window, we assume that we are first-party.
|
||||
if (aWindow && (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
|
||||
sCookiesBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN)) {
|
||||
if (aWindow && (behavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
|
||||
behavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN)) {
|
||||
nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
|
||||
do_GetService(THIRDPARTYUTIL_CONTRACTID);
|
||||
MOZ_ASSERT(thirdPartyUtil);
|
||||
|
@ -2911,6 +2911,16 @@ private:
|
||||
CallOnRemoteChildFunction aCallback,
|
||||
void* aArg);
|
||||
|
||||
/**
|
||||
* Gets the current cookie lifetime policy and cookie behavior for a given
|
||||
* principal by checking with preferences and the permission manager.
|
||||
*
|
||||
* Used in the implementation of InternalStorageAllowedForPrincipal.
|
||||
*/
|
||||
static void GetCookieBehaviorForPrincipal(nsIPrincipal* aPrincipal,
|
||||
uint32_t* aLifetimePolicy,
|
||||
uint32_t* aBehavior);
|
||||
|
||||
/*
|
||||
* Checks if storage for a given principal is permitted by the user's
|
||||
* preferences. If aWindow is non-null, its principal must be passed as
|
||||
|
@ -1376,8 +1376,6 @@ nsIDocument::nsIDocument()
|
||||
mUserHasInteracted(false)
|
||||
{
|
||||
SetIsInDocument();
|
||||
|
||||
PR_INIT_CLIST(&mDOMMediaQueryLists);
|
||||
}
|
||||
|
||||
nsDocument::nsDocument(const char* aContentType)
|
||||
@ -1457,7 +1455,7 @@ nsDocument::ClearAllBoxObjects()
|
||||
|
||||
nsIDocument::~nsIDocument()
|
||||
{
|
||||
MOZ_ASSERT(PR_CLIST_IS_EMPTY(&mDOMMediaQueryLists),
|
||||
MOZ_ASSERT(mDOMMediaQueryLists.isEmpty(),
|
||||
"must not have media query lists left");
|
||||
|
||||
if (mNodeInfoManager) {
|
||||
@ -1858,9 +1856,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument)
|
||||
// We own only the items in mDOMMediaQueryLists that have listeners;
|
||||
// this reference is managed by their AddListener and RemoveListener
|
||||
// methods.
|
||||
for (PRCList *l = PR_LIST_HEAD(&tmp->mDOMMediaQueryLists);
|
||||
l != &tmp->mDOMMediaQueryLists; l = PR_NEXT_LINK(l)) {
|
||||
MediaQueryList *mql = static_cast<MediaQueryList*>(l);
|
||||
for (auto mql : tmp->mDOMMediaQueryLists) {
|
||||
if (mql->HasListeners()) {
|
||||
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
|
||||
cb.NoteXPCOMChild(mql);
|
||||
@ -1974,12 +1970,10 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
|
||||
// We own only the items in mDOMMediaQueryLists that have listeners;
|
||||
// this reference is managed by their AddListener and RemoveListener
|
||||
// methods.
|
||||
for (PRCList *l = PR_LIST_HEAD(&tmp->mDOMMediaQueryLists);
|
||||
l != &tmp->mDOMMediaQueryLists; ) {
|
||||
PRCList *next = PR_NEXT_LINK(l);
|
||||
MediaQueryList *mql = static_cast<MediaQueryList*>(l);
|
||||
for (MediaQueryList* mql = tmp->mDOMMediaQueryLists.getFirst(); mql;) {
|
||||
MediaQueryList* next = mql->getNext();
|
||||
mql->Disconnect();
|
||||
l = next;
|
||||
mql = next;
|
||||
}
|
||||
|
||||
tmp->mInUnlinkOrDeletion = false;
|
||||
@ -7182,8 +7176,7 @@ nsIDocument::MatchMedia(const nsAString& aMediaQueryList)
|
||||
{
|
||||
RefPtr<MediaQueryList> result = new MediaQueryList(this, aMediaQueryList);
|
||||
|
||||
// Insert the new item at the end of the linked list.
|
||||
PR_INSERT_BEFORE(result, &mDOMMediaQueryLists);
|
||||
mDOMMediaQueryLists.insertBack(result);
|
||||
|
||||
return result.forget();
|
||||
}
|
||||
|
@ -31,7 +31,6 @@
|
||||
#include "nsContentListDeclarations.h"
|
||||
#include "nsExpirationTracker.h"
|
||||
#include "nsClassHashtable.h"
|
||||
#include "prclist.h"
|
||||
#include "mozilla/CORSMode.h"
|
||||
#include "mozilla/dom/DispatcherTrait.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
@ -1875,8 +1874,8 @@ public:
|
||||
already_AddRefed<mozilla::dom::MediaQueryList>
|
||||
MatchMedia(const nsAString& aMediaQueryList);
|
||||
|
||||
const PRCList* MediaQueryLists() const {
|
||||
return &mDOMMediaQueryLists;
|
||||
mozilla::LinkedList<mozilla::dom::MediaQueryList>& MediaQueryLists() {
|
||||
return mDOMMediaQueryLists;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3375,7 +3374,7 @@ protected:
|
||||
uint32_t mBlockDOMContentLoaded;
|
||||
|
||||
// Our live MediaQueryLists
|
||||
PRCList mDOMMediaQueryLists;
|
||||
mozilla::LinkedList<mozilla::dom::MediaQueryList> mDOMMediaQueryLists;
|
||||
|
||||
// Flags for use counters used directly by this document.
|
||||
std::bitset<mozilla::eUseCounter_Count> mUseCounters;
|
||||
|
@ -919,10 +919,6 @@ nsCSPHashSrc::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
|
||||
rv = hasher->Finish(true, hash);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
// The NSS Base64 encoder automatically adds linebreaks "\r\n" every 64
|
||||
// characters. We need to remove these so we can properly validate longer
|
||||
// (SHA-512) base64-encoded hashes
|
||||
hash.StripChars("\r\n");
|
||||
return NS_ConvertUTF16toUTF8(mHash).Equals(hash);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,10 @@
|
||||
<script>
|
||||
try {
|
||||
sessionStorage.setItem("am_i_blocked", "nope");
|
||||
window.parent.postMessage('sessionStorage=true', '*');
|
||||
document.body.innerHTML += 'yes';
|
||||
} catch (ex) {
|
||||
window.parent.postMessage('sessionStorage=false', '*');
|
||||
document.body.innerHTML += 'no';
|
||||
}
|
||||
</script>
|
@ -16,6 +16,7 @@ support-files =
|
||||
interOriginTest2.js
|
||||
localStorageCommon.js
|
||||
frameLocalStorageSessionOnly.html
|
||||
file_tryAccessSessionStorage.html
|
||||
|
||||
[test_brokenUTF-16.html]
|
||||
[test_bug600307-DBOps.html]
|
||||
@ -50,3 +51,4 @@ skip-if = toolkit == 'android'
|
||||
[test_lowDeviceStorage.html]
|
||||
[test_storageConstructor.html]
|
||||
[test_localStorageSessionPrefOverride.html]
|
||||
[test_firstPartyOnlyPermission.html]
|
||||
|
@ -0,0 +1,62 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>first party storage permission test</title>
|
||||
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
|
||||
<script type="text/javascript">
|
||||
TRY_ACCESS_SESSION_STORAGE =
|
||||
'http://example.com/tests/dom/tests/mochitest/localstorage/file_tryAccessSessionStorage.html';
|
||||
|
||||
add_task(function*() {
|
||||
yield SpecialPowers.pushPrefEnv({
|
||||
set: [['network.cookie.cookieBehavior', SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT]],
|
||||
});
|
||||
|
||||
try {
|
||||
sessionStorage.setItem("blocked", "blocked");
|
||||
ok(false, "Shouldn't be avaliable yet");
|
||||
} catch (ex) {
|
||||
ok(true, "Shouldn't be avaliable yet");
|
||||
}
|
||||
|
||||
yield new Promise(resolve => SpecialPowers.pushPermissions([{
|
||||
type: 'cookie',
|
||||
allow: SpecialPowers.Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY,
|
||||
context: document,
|
||||
}], resolve));
|
||||
|
||||
// With the permission set to ACCESS_ALLOW_FIRST_PARTY_ONLY, we should be
|
||||
// able to run it from this iframe (as we are first party with the test
|
||||
// runner parent document).
|
||||
try {
|
||||
sessionStorage.setItem("blocked", "blocked");
|
||||
ok(true, "Should be avaliable");
|
||||
} catch (ex) {
|
||||
ok(false, "Should be avaliable");
|
||||
}
|
||||
|
||||
// A third party iframe should not have access however.
|
||||
yield new Promise(resolve => {
|
||||
window.onmessage = evt => {
|
||||
window.onmessage = null;
|
||||
is(evt.data, "sessionStorage=false");
|
||||
resolve();
|
||||
};
|
||||
|
||||
let iframe = document.createElement('iframe');
|
||||
iframe.setAttribute('src', TRY_ACCESS_SESSION_STORAGE);
|
||||
document.body.appendChild(iframe);
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -33,7 +33,6 @@
|
||||
#include "ScopedGLHelpers.h"
|
||||
#include "SharedSurfaceGL.h"
|
||||
#include "GfxTexturesReporter.h"
|
||||
#include "TextureGarbageBin.h"
|
||||
#include "gfx2DGlue.h"
|
||||
#include "gfxPrefs.h"
|
||||
#include "mozilla/IntegerPrintfMacros.h"
|
||||
@ -947,8 +946,6 @@ GLContext::InitWithPrefixImpl(const char* prefix, bool trygl)
|
||||
mCaps.alpha = false;
|
||||
}
|
||||
|
||||
mTexGarbageBin = new TextureGarbageBin(this);
|
||||
|
||||
MOZ_ASSERT(IsCurrent());
|
||||
|
||||
if (ShouldSpew() && IsExtensionSupported(KHR_debug)) {
|
||||
@ -2106,9 +2103,7 @@ GLContext::MarkDestroyed()
|
||||
mBlitHelper = nullptr;
|
||||
mReadTexImageHelper = nullptr;
|
||||
|
||||
if (MakeCurrent()) {
|
||||
mTexGarbageBin->GLContextTeardown();
|
||||
} else {
|
||||
if (!MakeCurrent()) {
|
||||
NS_WARNING("MakeCurrent() failed during MarkDestroyed! Skipping GL object teardown.");
|
||||
}
|
||||
|
||||
@ -2388,12 +2383,6 @@ GLContext::CleanDirtyScreen()
|
||||
AfterGLReadCall();
|
||||
}
|
||||
|
||||
void
|
||||
GLContext::EmptyTexGarbageBin()
|
||||
{
|
||||
TexGarbageBin()->EmptyGarbage();
|
||||
}
|
||||
|
||||
bool
|
||||
GLContext::IsOffscreenSizeAllowed(const IntSize& aSize) const
|
||||
{
|
||||
|
@ -63,7 +63,6 @@ namespace mozilla {
|
||||
class GLReadTexImageHelper;
|
||||
class GLScreenBuffer;
|
||||
class SharedSurface;
|
||||
class TextureGarbageBin;
|
||||
struct SurfaceCaps;
|
||||
} // namespace gl
|
||||
|
||||
@ -3457,17 +3456,6 @@ public:
|
||||
|
||||
bool IsDrawingToDefaultFramebuffer();
|
||||
|
||||
protected:
|
||||
RefPtr<TextureGarbageBin> mTexGarbageBin;
|
||||
|
||||
public:
|
||||
TextureGarbageBin* TexGarbageBin() {
|
||||
MOZ_ASSERT(mTexGarbageBin);
|
||||
return mTexGarbageBin;
|
||||
}
|
||||
|
||||
void EmptyTexGarbageBin();
|
||||
|
||||
bool IsOffscreenSizeAllowed(const gfx::IntSize& aSize) const;
|
||||
|
||||
protected:
|
||||
|
@ -11,7 +11,6 @@
|
||||
#include "GLReadTexImageHelper.h"
|
||||
#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor, etc
|
||||
#include "SharedSurface.h"
|
||||
#include "TextureGarbageBin.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gl {
|
||||
|
@ -15,7 +15,6 @@ namespace gl {
|
||||
|
||||
class GLContext;
|
||||
class GLLibraryEGL;
|
||||
class TextureGarbageBin;
|
||||
|
||||
class SharedSurface_EGLImage
|
||||
: public SharedSurface
|
||||
|
@ -1,45 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim: set ts=8 sts=4 et sw=4 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "TextureGarbageBin.h"
|
||||
#include "GLContext.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gl;
|
||||
|
||||
void
|
||||
TextureGarbageBin::GLContextTeardown()
|
||||
{
|
||||
EmptyGarbage();
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
mGL = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
TextureGarbageBin::Trash(GLuint tex)
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (!mGL)
|
||||
return;
|
||||
|
||||
mGarbageTextures.push(tex);
|
||||
}
|
||||
|
||||
void
|
||||
TextureGarbageBin::EmptyGarbage()
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
if (!mGL)
|
||||
return;
|
||||
|
||||
MOZ_RELEASE_ASSERT(mGL->IsCurrent(), "GFX: GL context not current.");
|
||||
while (!mGarbageTextures.empty()) {
|
||||
GLuint tex = mGarbageTextures.top();
|
||||
mGarbageTextures.pop();
|
||||
mGL->fDeleteTextures(1, &tex);
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim: set ts=8 sts=4 et sw=4 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef TEXTUREGARBAGEBIN_H_
|
||||
#define TEXTUREGARBAGEBIN_H_
|
||||
|
||||
#include <stack>
|
||||
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
|
||||
#include "GLContextTypes.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gl {
|
||||
|
||||
class TextureGarbageBin final {
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TextureGarbageBin)
|
||||
|
||||
private:
|
||||
// Private destructor, to discourage deletion outside of Release():
|
||||
~TextureGarbageBin()
|
||||
{
|
||||
}
|
||||
|
||||
GLContext* mGL;
|
||||
Mutex mMutex;
|
||||
std::stack<GLuint> mGarbageTextures;
|
||||
|
||||
public:
|
||||
explicit TextureGarbageBin(GLContext* gl)
|
||||
: mGL(gl)
|
||||
, mMutex("TextureGarbageBin mutex")
|
||||
{}
|
||||
|
||||
void GLContextTeardown();
|
||||
void Trash(GLuint tex);
|
||||
void EmptyGarbage();
|
||||
};
|
||||
|
||||
} // namespace gl
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // TEXTUREGARBAGEBIN_H_
|
@ -51,7 +51,6 @@ EXPORTS += [
|
||||
'SharedSurfaceEGL.h',
|
||||
'SharedSurfaceGL.h',
|
||||
'SurfaceTypes.h',
|
||||
'TextureGarbageBin.h',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_X11']:
|
||||
@ -140,7 +139,6 @@ UNIFIED_SOURCES += [
|
||||
'SharedSurfaceEGL.cpp',
|
||||
'SharedSurfaceGL.cpp',
|
||||
'SurfaceTypes.cpp',
|
||||
'TextureGarbageBin.cpp',
|
||||
'TextureImageEGL.cpp',
|
||||
]
|
||||
|
||||
|
@ -855,7 +855,7 @@ static const uint32_t JSCLASS_FOREGROUND_FINALIZE = 1 << (JSCLASS_HIGH_FLAGS
|
||||
// application.
|
||||
static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5;
|
||||
static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT =
|
||||
JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 46;
|
||||
JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 37;
|
||||
|
||||
#define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \
|
||||
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
|
||||
|
@ -237,12 +237,6 @@ DataViewObject::constructSameCompartment(JSContext* cx, HandleObject bufobj, con
|
||||
// A's DataView.prototype. So even though we're creating the DataView in B,
|
||||
// its [[Prototype]] must be (a cross-compartment wrapper for) the
|
||||
// DataView.prototype in A.
|
||||
//
|
||||
// As if this were not confusing enough, the way we actually do this is also
|
||||
// tricky. We call compartment A's createDataViewForThis method, passing it
|
||||
// bufobj as `this`. That calls ArrayBufferObject::createDataViewForThis(),
|
||||
// which uses CallNonGenericMethod to switch to compartment B so that
|
||||
// the new DataView is created there.
|
||||
bool
|
||||
DataViewObject::constructWrapped(JSContext* cx, HandleObject bufobj, const CallArgs& args)
|
||||
{
|
||||
|
@ -57,7 +57,6 @@ class MOZ_STACK_CLASS BytecodeCompiler
|
||||
JSScript* compileScript(HandleObject environment, SharedContext* sc);
|
||||
bool checkLength();
|
||||
bool createScriptSource(const Maybe<uint32_t>& parameterListEnd);
|
||||
bool enqueueOffThreadSourceCompression();
|
||||
bool canLazilyParse();
|
||||
bool createParser();
|
||||
bool createSourceAndParser(const Maybe<uint32_t>& parameterListEnd = Nothing());
|
||||
@ -83,7 +82,6 @@ class MOZ_STACK_CLASS BytecodeCompiler
|
||||
|
||||
RootedScriptSource sourceObject;
|
||||
ScriptSource* scriptSource;
|
||||
SourceCompressionTask* sourceCompressionTask_;
|
||||
|
||||
Maybe<UsedNameTracker> usedNames;
|
||||
Maybe<Parser<SyntaxParseHandler>> syntaxParser;
|
||||
@ -173,7 +171,6 @@ BytecodeCompiler::BytecodeCompiler(JSContext* cx,
|
||||
enclosingScope(cx, enclosingScope),
|
||||
sourceObject(cx),
|
||||
scriptSource(nullptr),
|
||||
sourceCompressionTask_(nullptr),
|
||||
directives(options.strictOption),
|
||||
startPosition(keepAtoms),
|
||||
script(cx)
|
||||
@ -219,40 +216,6 @@ BytecodeCompiler::createScriptSource(const Maybe<uint32_t>& parameterListEnd)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BytecodeCompiler::enqueueOffThreadSourceCompression()
|
||||
{
|
||||
// There are several cases where source compression is not a good idea:
|
||||
// - If the script is tiny, then compression will save little or no space.
|
||||
// - If there is only one core, then compression will contend with JS
|
||||
// execution (which hurts benchmarketing).
|
||||
//
|
||||
// Otherwise, enqueue a compression task to be processed when a major
|
||||
// GC is requested.
|
||||
|
||||
if (!scriptSource->hasUncompressedSource())
|
||||
return true;
|
||||
|
||||
bool canCompressOffThread =
|
||||
HelperThreadState().cpuCount > 1 &&
|
||||
HelperThreadState().threadCount >= 2 &&
|
||||
CanUseExtraThreads();
|
||||
const size_t TINY_SCRIPT = 256;
|
||||
if (TINY_SCRIPT <= sourceBuffer.length() && canCompressOffThread) {
|
||||
// Heap allocate the task. It will be freed upon compression
|
||||
// completing in AttachFinishedCompressedSources.
|
||||
SourceCompressionTask* task = cx->new_<SourceCompressionTask>(cx->runtime(),
|
||||
scriptSource);
|
||||
if (!task)
|
||||
return false;
|
||||
if (!EnqueueOffThreadCompression(cx, task))
|
||||
return false;
|
||||
sourceCompressionTask_ = task;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BytecodeCompiler::canLazilyParse()
|
||||
{
|
||||
@ -416,7 +379,7 @@ BytecodeCompiler::compileScript(HandleObject environment, SharedContext* sc)
|
||||
script->scriptSource()->recordParseEnded();
|
||||
|
||||
// Enqueue an off-thread source compression task after finishing parsing.
|
||||
if (!enqueueOffThreadSourceCompression())
|
||||
if (!scriptSource->tryCompressOffThread(cx))
|
||||
return nullptr;
|
||||
|
||||
MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending());
|
||||
@ -481,7 +444,7 @@ BytecodeCompiler::compileModule()
|
||||
module->setInitialEnvironment(env);
|
||||
|
||||
// Enqueue an off-thread source compression task after finishing parsing.
|
||||
if (!enqueueOffThreadSourceCompression())
|
||||
if (!scriptSource->tryCompressOffThread(cx))
|
||||
return nullptr;
|
||||
|
||||
MOZ_ASSERT_IF(!cx->helperThread(), !cx->isExceptionPending());
|
||||
@ -537,7 +500,7 @@ BytecodeCompiler::compileStandaloneFunction(MutableHandleFunction fun,
|
||||
return false;
|
||||
|
||||
// Enqueue an off-thread source compression task after finishing parsing.
|
||||
if (!enqueueOffThreadSourceCompression())
|
||||
if (!scriptSource->tryCompressOffThread(cx))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@ -549,12 +512,6 @@ BytecodeCompiler::sourceObjectPtr() const
|
||||
return sourceObject.get();
|
||||
}
|
||||
|
||||
SourceCompressionTask*
|
||||
BytecodeCompiler::sourceCompressionTask() const
|
||||
{
|
||||
return sourceCompressionTask_;
|
||||
}
|
||||
|
||||
ScriptSourceObject*
|
||||
frontend::CreateScriptSourceObject(JSContext* cx, const ReadOnlyCompileOptions& options,
|
||||
const Maybe<uint32_t>& parameterListEnd /* = Nothing() */)
|
||||
@ -606,22 +563,17 @@ class MOZ_STACK_CLASS AutoInitializeSourceObject
|
||||
{
|
||||
BytecodeCompiler& compiler_;
|
||||
ScriptSourceObject** sourceObjectOut_;
|
||||
SourceCompressionTask** sourceCompressionTaskOut_;
|
||||
|
||||
public:
|
||||
AutoInitializeSourceObject(BytecodeCompiler& compiler,
|
||||
ScriptSourceObject** sourceObjectOut,
|
||||
SourceCompressionTask** sourceCompressionTaskOut)
|
||||
ScriptSourceObject** sourceObjectOut)
|
||||
: compiler_(compiler),
|
||||
sourceObjectOut_(sourceObjectOut),
|
||||
sourceCompressionTaskOut_(sourceCompressionTaskOut)
|
||||
sourceObjectOut_(sourceObjectOut)
|
||||
{ }
|
||||
|
||||
~AutoInitializeSourceObject() {
|
||||
if (sourceObjectOut_)
|
||||
*sourceObjectOut_ = compiler_.sourceObjectPtr();
|
||||
if (sourceCompressionTaskOut_)
|
||||
*sourceCompressionTaskOut_ = compiler_.sourceCompressionTask();
|
||||
}
|
||||
};
|
||||
|
||||
@ -629,12 +581,11 @@ JSScript*
|
||||
frontend::CompileGlobalScript(JSContext* cx, LifoAlloc& alloc, ScopeKind scopeKind,
|
||||
const ReadOnlyCompileOptions& options,
|
||||
SourceBufferHolder& srcBuf,
|
||||
ScriptSourceObject** sourceObjectOut,
|
||||
SourceCompressionTask** sourceCompressionTaskOut)
|
||||
ScriptSourceObject** sourceObjectOut)
|
||||
{
|
||||
MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic);
|
||||
BytecodeCompiler compiler(cx, alloc, options, srcBuf, /* enclosingScope = */ nullptr);
|
||||
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut, sourceCompressionTaskOut);
|
||||
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
|
||||
return compiler.compileGlobalScript(scopeKind);
|
||||
}
|
||||
|
||||
@ -643,19 +594,17 @@ frontend::CompileEvalScript(JSContext* cx, LifoAlloc& alloc,
|
||||
HandleObject environment, HandleScope enclosingScope,
|
||||
const ReadOnlyCompileOptions& options,
|
||||
SourceBufferHolder& srcBuf,
|
||||
ScriptSourceObject** sourceObjectOut,
|
||||
SourceCompressionTask** sourceCompressionTaskOut)
|
||||
ScriptSourceObject** sourceObjectOut)
|
||||
{
|
||||
BytecodeCompiler compiler(cx, alloc, options, srcBuf, enclosingScope);
|
||||
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut, sourceCompressionTaskOut);
|
||||
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
|
||||
return compiler.compileEvalScript(environment, enclosingScope);
|
||||
}
|
||||
|
||||
ModuleObject*
|
||||
frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInput,
|
||||
SourceBufferHolder& srcBuf, LifoAlloc& alloc,
|
||||
ScriptSourceObject** sourceObjectOut,
|
||||
SourceCompressionTask** sourceCompressionTaskOut)
|
||||
ScriptSourceObject** sourceObjectOut)
|
||||
{
|
||||
MOZ_ASSERT(srcBuf.get());
|
||||
MOZ_ASSERT_IF(sourceObjectOut, *sourceObjectOut == nullptr);
|
||||
@ -667,7 +616,7 @@ frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& optionsInpu
|
||||
|
||||
RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
|
||||
BytecodeCompiler compiler(cx, alloc, options, srcBuf, emptyGlobalScope);
|
||||
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut, sourceCompressionTaskOut);
|
||||
AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
|
||||
return compiler.compileModule();
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,6 @@ class LazyScript;
|
||||
class LifoAlloc;
|
||||
class ModuleObject;
|
||||
class ScriptSourceObject;
|
||||
class SourceCompressionTask;
|
||||
|
||||
namespace frontend {
|
||||
|
||||
@ -35,16 +34,14 @@ JSScript*
|
||||
CompileGlobalScript(JSContext* cx, LifoAlloc& alloc, ScopeKind scopeKind,
|
||||
const ReadOnlyCompileOptions& options,
|
||||
SourceBufferHolder& srcBuf,
|
||||
ScriptSourceObject** sourceObjectOut = nullptr,
|
||||
SourceCompressionTask** sourceCompressionTaskOut = nullptr);
|
||||
ScriptSourceObject** sourceObjectOut = nullptr);
|
||||
|
||||
JSScript*
|
||||
CompileEvalScript(JSContext* cx, LifoAlloc& alloc,
|
||||
HandleObject scopeChain, HandleScope enclosingScope,
|
||||
const ReadOnlyCompileOptions& options,
|
||||
SourceBufferHolder& srcBuf,
|
||||
ScriptSourceObject** sourceObjectOut = nullptr,
|
||||
SourceCompressionTask** sourceCompressionTaskOut = nullptr);
|
||||
ScriptSourceObject** sourceObjectOut = nullptr);
|
||||
|
||||
ModuleObject*
|
||||
CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
|
||||
@ -53,8 +50,7 @@ CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
|
||||
ModuleObject*
|
||||
CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
|
||||
SourceBufferHolder& srcBuf, LifoAlloc& alloc,
|
||||
ScriptSourceObject** sourceObjectOut = nullptr,
|
||||
SourceCompressionTask** sourceCompressionTaskOut = nullptr);
|
||||
ScriptSourceObject** sourceObjectOut = nullptr);
|
||||
|
||||
MOZ_MUST_USE bool
|
||||
CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length);
|
||||
|
@ -5053,22 +5053,18 @@ SweepCompressionTasksTask::run()
|
||||
// Attach finished compression tasks.
|
||||
auto& finished = HelperThreadState().compressionFinishedList(lock);
|
||||
for (size_t i = 0; i < finished.length(); i++) {
|
||||
SourceCompressionTask* task = finished[i];
|
||||
if (task->runtimeMatches(runtime())) {
|
||||
if (finished[i]->runtimeMatches(runtime())) {
|
||||
UniquePtr<SourceCompressionTask> task(Move(finished[i]));
|
||||
HelperThreadState().remove(finished, &i);
|
||||
task->complete();
|
||||
js_delete(task);
|
||||
}
|
||||
}
|
||||
|
||||
// Sweep pending tasks that are holding onto should-be-dead ScriptSources.
|
||||
auto& pending = HelperThreadState().compressionPendingList(lock);
|
||||
for (size_t i = 0; i < pending.length(); i++) {
|
||||
SourceCompressionTask* task = pending[i];
|
||||
if (task->shouldCancel()) {
|
||||
if (pending[i]->shouldCancel())
|
||||
HelperThreadState().remove(pending, &i);
|
||||
js_delete(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1821,6 +1821,50 @@ ScriptSource::setSource(SharedImmutableTwoByteString&& string)
|
||||
data = SourceType(Uncompressed(mozilla::Move(string)));
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptSource::tryCompressOffThread(JSContext* cx)
|
||||
{
|
||||
if (!data.is<Uncompressed>())
|
||||
return true;
|
||||
|
||||
// There are several cases where source compression is not a good idea:
|
||||
// - If the script is tiny, then compression will save little or no space.
|
||||
// - If there is only one core, then compression will contend with JS
|
||||
// execution (which hurts benchmarketing).
|
||||
//
|
||||
// Otherwise, enqueue a compression task to be processed when a major
|
||||
// GC is requested.
|
||||
|
||||
bool canCompressOffThread =
|
||||
HelperThreadState().cpuCount > 1 &&
|
||||
HelperThreadState().threadCount >= 2 &&
|
||||
CanUseExtraThreads();
|
||||
const size_t TINY_SCRIPT = 256;
|
||||
if (TINY_SCRIPT > length() || !canCompressOffThread)
|
||||
return true;
|
||||
|
||||
// The SourceCompressionTask needs to record the major GC number for
|
||||
// scheduling. If we're parsing off thread, this number is not safe to
|
||||
// access.
|
||||
//
|
||||
// When parsing on the main thread, the attempts made to compress off
|
||||
// thread in BytecodeCompiler will succeed.
|
||||
//
|
||||
// When parsing off-thread, the above attempts will fail and the attempt
|
||||
// made in ParseTask::finish will succeed.
|
||||
if (!CurrentThreadCanAccessRuntime(cx->runtime()))
|
||||
return true;
|
||||
|
||||
// Heap allocate the task. It will be freed upon compression
|
||||
// completing in AttachFinishedCompressedSources.
|
||||
auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), this);
|
||||
if (!task) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
return EnqueueOffThreadCompression(cx, Move(task));
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool
|
||||
ScriptSource::setCompressedSource(JSContext* cx,
|
||||
mozilla::UniquePtr<char[], JS::FreePolicy>&& raw,
|
||||
|
@ -573,6 +573,8 @@ class ScriptSource
|
||||
size_t length);
|
||||
void setSource(SharedImmutableTwoByteString&& string);
|
||||
|
||||
MOZ_MUST_USE bool tryCompressOffThread(JSContext* cx);
|
||||
|
||||
MOZ_MUST_USE bool setCompressedSource(JSContext* cx,
|
||||
UniqueChars&& raw,
|
||||
size_t rawLength,
|
||||
|
230
js/src/tests/ecma_6/TypedArray/constructor-buffer-sequence.js
Normal file
230
js/src/tests/ecma_6/TypedArray/constructor-buffer-sequence.js
Normal file
@ -0,0 +1,230 @@
|
||||
// 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] )
|
||||
|
||||
// Ensure the various error conditions are tested in the correct order.
|
||||
|
||||
const otherGlobal = newGlobal();
|
||||
|
||||
function* createBuffers(lengths = [0, 8]) {
|
||||
for (let length of lengths) {
|
||||
let buffer = new ArrayBuffer(length);
|
||||
yield {buffer, detach: () => detachArrayBuffer(buffer)};
|
||||
}
|
||||
|
||||
for (let length of lengths) {
|
||||
let buffer = new otherGlobal.ArrayBuffer(length);
|
||||
yield {buffer, detach: () => otherGlobal.detachArrayBuffer(buffer)};
|
||||
}
|
||||
}
|
||||
|
||||
const poisonedValue = new Proxy({}, new Proxy({}, {
|
||||
get() {
|
||||
// Throws an exception when any proxy trap is invoked.
|
||||
throw new Error("Poisoned Value");
|
||||
}
|
||||
}));
|
||||
|
||||
class ExpectedError extends Error { }
|
||||
|
||||
function ConstructorWithThrowingPrototype(detach) {
|
||||
return Object.defineProperty(function(){}.bind(null), "prototype", {
|
||||
get() {
|
||||
if (detach)
|
||||
detach();
|
||||
throw new ExpectedError();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function ValueThrowing(detach) {
|
||||
return {
|
||||
valueOf() {
|
||||
if (detach)
|
||||
detach();
|
||||
throw new ExpectedError();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function ValueReturning(value, detach) {
|
||||
return {
|
||||
valueOf() {
|
||||
if (detach)
|
||||
detach();
|
||||
return value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Ensure step 4 |AllocateTypedArray| is executed before step 6 |ToIndex(byteOffset)|.
|
||||
for (let {buffer} of createBuffers()) {
|
||||
let constructor = ConstructorWithThrowingPrototype();
|
||||
|
||||
assertThrowsInstanceOf(() =>
|
||||
Reflect.construct(Int32Array, [buffer, poisonedValue, 0], constructor), ExpectedError);
|
||||
}
|
||||
|
||||
// Ensure step 4 |AllocateTypedArray| is executed before step 9 |IsDetachedBuffer(buffer)|.
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let constructor = ConstructorWithThrowingPrototype();
|
||||
|
||||
detach();
|
||||
assertThrowsInstanceOf(() =>
|
||||
Reflect.construct(Int32Array, [buffer, 0, 0], constructor), ExpectedError);
|
||||
}
|
||||
|
||||
// Ensure step 4 |AllocateTypedArray| is executed before step 9 |IsDetachedBuffer(buffer)|.
|
||||
// - Variant: Detach buffer dynamically.
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let constructor = ConstructorWithThrowingPrototype(detach);
|
||||
|
||||
assertThrowsInstanceOf(() =>
|
||||
Reflect.construct(Int32Array, [buffer, 0, 0], constructor), ExpectedError);
|
||||
}
|
||||
|
||||
// Ensure step 4 |AllocateTypedArray| is executed before step 8.a |ToIndex(length)|.
|
||||
for (let {buffer} of createBuffers()) {
|
||||
let constructor = ConstructorWithThrowingPrototype();
|
||||
|
||||
assertThrowsInstanceOf(() =>
|
||||
Reflect.construct(Int32Array, [buffer, 0, poisonedValue], constructor), ExpectedError);
|
||||
}
|
||||
|
||||
// Ensure step 6 |ToIndex(byteOffset)| is executed before step 9 |IsDetachedBuffer(buffer)|.
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let byteOffset = ValueThrowing();
|
||||
|
||||
detach();
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, 0), ExpectedError);
|
||||
}
|
||||
|
||||
// Ensure step 6 |ToIndex(byteOffset)| is executed before step 9 |IsDetachedBuffer(buffer)|.
|
||||
// - Variant: Detach buffer dynamically.
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let byteOffset = ValueThrowing(detach);
|
||||
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, 0), ExpectedError);
|
||||
}
|
||||
|
||||
// Ensure step 6 |ToIndex(byteOffset)| is executed before step 8.a |ToIndex(length)|.
|
||||
for (let {buffer} of createBuffers()) {
|
||||
let byteOffset = ValueThrowing();
|
||||
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, poisonedValue), ExpectedError);
|
||||
}
|
||||
|
||||
// Ensure step 7 |offset modulo elementSize ≠ 0| is executed before step 9 |IsDetachedBuffer(buffer)|.
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let byteOffset = 1;
|
||||
|
||||
detach();
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, 0), RangeError);
|
||||
}
|
||||
|
||||
// Ensure step 7 |offset modulo elementSize ≠ 0| is executed before step 9 |IsDetachedBuffer(buffer)|.
|
||||
// - Variant: Detach buffer dynamically.
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let byteOffset = ValueReturning(1, detach);
|
||||
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, 0), RangeError);
|
||||
}
|
||||
|
||||
// Ensure step 7 |offset modulo elementSize ≠ 0| is executed before step 8.a |ToIndex(length)|.
|
||||
for (let {buffer} of createBuffers()) {
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, 1, poisonedValue), RangeError);
|
||||
}
|
||||
|
||||
// Ensure step 8.a |ToIndex(length)| is executed before step 9 |IsDetachedBuffer(buffer)|.
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let byteOffset = 0;
|
||||
let length = ValueThrowing();
|
||||
|
||||
detach();
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), ExpectedError);
|
||||
}
|
||||
|
||||
// Ensure step 8.a |ToIndex(length)| is executed before step 9 |IsDetachedBuffer(buffer)|.
|
||||
// - Variant: Detach buffer dynamically (1).
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let byteOffset = ValueReturning(0, detach);
|
||||
let length = ValueThrowing();
|
||||
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), ExpectedError);
|
||||
}
|
||||
|
||||
// Ensure step 8.a |ToIndex(length)| is executed before step 9 |IsDetachedBuffer(buffer)|.
|
||||
// - Variant: Detach buffer dynamically (2).
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let byteOffset = 0;
|
||||
let length = ValueThrowing(detach);
|
||||
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), ExpectedError);
|
||||
}
|
||||
|
||||
// Ensure step 9 |IsDetachedBuffer(buffer)| is executed before step 11.a |bufferByteLength modulo elementSize ≠ 0|.
|
||||
for (let {buffer, detach} of createBuffers([1, 9])) {
|
||||
let byteOffset = 0;
|
||||
|
||||
detach();
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset), TypeError);
|
||||
}
|
||||
|
||||
// Ensure step 9 |IsDetachedBuffer(buffer)| is executed before step 11.a |bufferByteLength modulo elementSize ≠ 0|.
|
||||
// - Variant: Detach buffer dynamically.
|
||||
for (let {buffer, detach} of createBuffers([1, 9])) {
|
||||
let byteOffset = ValueReturning(0, detach);
|
||||
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset), TypeError);
|
||||
}
|
||||
|
||||
// Ensure step 9 |IsDetachedBuffer(buffer)| is executed before step 11.c |newByteLength < 0|.
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let byteOffset = 64;
|
||||
|
||||
detach();
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset), TypeError);
|
||||
}
|
||||
|
||||
// Ensure step 9 |IsDetachedBuffer(buffer)| is executed before step 11.c |newByteLength < 0|.
|
||||
// - Variant: Detach buffer dynamically.
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let byteOffset = ValueReturning(64, detach);
|
||||
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset), TypeError);
|
||||
}
|
||||
|
||||
// Ensure step 9 |IsDetachedBuffer(buffer)| is executed before step 12.b |offset+newByteLength > bufferByteLength|.
|
||||
// - Case A: The given byteOffset is too large.
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let byteOffset = 64;
|
||||
let length = ValueReturning(0, detach);
|
||||
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), TypeError);
|
||||
}
|
||||
|
||||
// Ensure step 9 |IsDetachedBuffer(buffer)| is executed before step 12.b |offset+newByteLength > bufferByteLength|.
|
||||
// - Case B: The given length is too large.
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let byteOffset = 0;
|
||||
let length = ValueReturning(64, detach);
|
||||
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), TypeError);
|
||||
}
|
||||
|
||||
// Ensure we handle the case when ToIndex(byteOffset) detaches the array buffer.
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let byteOffset = ValueReturning(0, detach);
|
||||
let length = 0;
|
||||
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), TypeError);
|
||||
}
|
||||
|
||||
// Ensure we handle the case when ToIndex(length) detaches the array buffer.
|
||||
for (let {buffer, detach} of createBuffers()) {
|
||||
let byteOffset = 0;
|
||||
let length = ValueReturning(0, detach);
|
||||
|
||||
assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), TypeError);
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
@ -254,11 +254,6 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
|
||||
// initialize()d to become a real, content-visible ArrayBufferObject.
|
||||
static ArrayBufferObject* createEmpty(JSContext* cx);
|
||||
|
||||
template<typename T>
|
||||
static bool createTypedArrayFromBufferImpl(JSContext* cx, const CallArgs& args);
|
||||
template<typename T>
|
||||
static bool createTypedArrayFromBuffer(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
static void copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex,
|
||||
Handle<ArrayBufferObject*> fromBuffer, uint32_t fromIndex,
|
||||
uint32_t count);
|
||||
|
@ -72,20 +72,6 @@ class GlobalObject : public NativeObject
|
||||
EVAL = APPLICATION_SLOTS + STANDARD_CLASS_SLOTS,
|
||||
THROWTYPEERROR,
|
||||
|
||||
/*
|
||||
* Instances of the internal createArrayFromBuffer function used by the
|
||||
* typed array code, one per typed array element type.
|
||||
*/
|
||||
FROM_BUFFER_UINT8,
|
||||
FROM_BUFFER_INT8,
|
||||
FROM_BUFFER_UINT16,
|
||||
FROM_BUFFER_INT16,
|
||||
FROM_BUFFER_UINT32,
|
||||
FROM_BUFFER_INT32,
|
||||
FROM_BUFFER_FLOAT32,
|
||||
FROM_BUFFER_FLOAT64,
|
||||
FROM_BUFFER_UINT8CLAMPED,
|
||||
|
||||
/* One-off properties stored after slots for built-ins. */
|
||||
LEXICAL_ENVIRONMENT,
|
||||
EMPTY_GLOBAL_SCOPE,
|
||||
@ -260,21 +246,6 @@ class GlobalObject : public NativeObject
|
||||
return classIsInitialized(JSProto_DataView);
|
||||
}
|
||||
|
||||
Value createArrayFromBufferHelper(uint32_t slot) const {
|
||||
MOZ_ASSERT(FROM_BUFFER_UINT8 <= slot && slot <= FROM_BUFFER_UINT8CLAMPED);
|
||||
return getSlot(slot);
|
||||
}
|
||||
|
||||
void setCreateArrayFromBufferHelper(uint32_t slot, Handle<JSFunction*> fun) {
|
||||
MOZ_ASSERT(getSlotRef(slot).isUndefined());
|
||||
setSlot(slot, ObjectValue(*fun));
|
||||
}
|
||||
|
||||
public:
|
||||
template<typename T>
|
||||
inline void setCreateArrayFromBuffer(Handle<JSFunction*> fun);
|
||||
|
||||
private:
|
||||
// Disallow use of unqualified JSObject::create in GlobalObject.
|
||||
static GlobalObject* create(...) = delete;
|
||||
|
||||
@ -779,9 +750,6 @@ class GlobalObject : public NativeObject
|
||||
return &v.toObject();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline Value createArrayFromBuffer() const;
|
||||
|
||||
static bool isRuntimeCodeGenEnabled(JSContext* cx, Handle<GlobalObject*> global);
|
||||
|
||||
// Warn about use of the deprecated watch/unwatch functions in the global
|
||||
@ -889,132 +857,6 @@ class GlobalObject : public NativeObject
|
||||
JSObject* getStarGeneratorFunctionPrototype();
|
||||
};
|
||||
|
||||
template<>
|
||||
inline void
|
||||
GlobalObject::setCreateArrayFromBuffer<uint8_t>(Handle<JSFunction*> fun)
|
||||
{
|
||||
setCreateArrayFromBufferHelper(FROM_BUFFER_UINT8, fun);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void
|
||||
GlobalObject::setCreateArrayFromBuffer<int8_t>(Handle<JSFunction*> fun)
|
||||
{
|
||||
setCreateArrayFromBufferHelper(FROM_BUFFER_INT8, fun);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void
|
||||
GlobalObject::setCreateArrayFromBuffer<uint16_t>(Handle<JSFunction*> fun)
|
||||
{
|
||||
setCreateArrayFromBufferHelper(FROM_BUFFER_UINT16, fun);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void
|
||||
GlobalObject::setCreateArrayFromBuffer<int16_t>(Handle<JSFunction*> fun)
|
||||
{
|
||||
setCreateArrayFromBufferHelper(FROM_BUFFER_INT16, fun);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void
|
||||
GlobalObject::setCreateArrayFromBuffer<uint32_t>(Handle<JSFunction*> fun)
|
||||
{
|
||||
setCreateArrayFromBufferHelper(FROM_BUFFER_UINT32, fun);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void
|
||||
GlobalObject::setCreateArrayFromBuffer<int32_t>(Handle<JSFunction*> fun)
|
||||
{
|
||||
setCreateArrayFromBufferHelper(FROM_BUFFER_INT32, fun);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void
|
||||
GlobalObject::setCreateArrayFromBuffer<float>(Handle<JSFunction*> fun)
|
||||
{
|
||||
setCreateArrayFromBufferHelper(FROM_BUFFER_FLOAT32, fun);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void
|
||||
GlobalObject::setCreateArrayFromBuffer<double>(Handle<JSFunction*> fun)
|
||||
{
|
||||
setCreateArrayFromBufferHelper(FROM_BUFFER_FLOAT64, fun);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void
|
||||
GlobalObject::setCreateArrayFromBuffer<uint8_clamped>(Handle<JSFunction*> fun)
|
||||
{
|
||||
setCreateArrayFromBufferHelper(FROM_BUFFER_UINT8CLAMPED, fun);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline Value
|
||||
GlobalObject::createArrayFromBuffer<uint8_t>() const
|
||||
{
|
||||
return createArrayFromBufferHelper(FROM_BUFFER_UINT8);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline Value
|
||||
GlobalObject::createArrayFromBuffer<int8_t>() const
|
||||
{
|
||||
return createArrayFromBufferHelper(FROM_BUFFER_INT8);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline Value
|
||||
GlobalObject::createArrayFromBuffer<uint16_t>() const
|
||||
{
|
||||
return createArrayFromBufferHelper(FROM_BUFFER_UINT16);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline Value
|
||||
GlobalObject::createArrayFromBuffer<int16_t>() const
|
||||
{
|
||||
return createArrayFromBufferHelper(FROM_BUFFER_INT16);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline Value
|
||||
GlobalObject::createArrayFromBuffer<uint32_t>() const
|
||||
{
|
||||
return createArrayFromBufferHelper(FROM_BUFFER_UINT32);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline Value
|
||||
GlobalObject::createArrayFromBuffer<int32_t>() const
|
||||
{
|
||||
return createArrayFromBufferHelper(FROM_BUFFER_INT32);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline Value
|
||||
GlobalObject::createArrayFromBuffer<float>() const
|
||||
{
|
||||
return createArrayFromBufferHelper(FROM_BUFFER_FLOAT32);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline Value
|
||||
GlobalObject::createArrayFromBuffer<double>() const
|
||||
{
|
||||
return createArrayFromBufferHelper(FROM_BUFFER_FLOAT64);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline Value
|
||||
GlobalObject::createArrayFromBuffer<uint8_clamped>() const
|
||||
{
|
||||
return createArrayFromBufferHelper(FROM_BUFFER_UINT8CLAMPED);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unless otherwise specified, define ctor.prototype = proto as non-enumerable,
|
||||
* non-configurable, and non-writable; and define proto.constructor = ctor as
|
||||
|
@ -301,7 +301,7 @@ ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
|
||||
alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
|
||||
parseGlobal(parseGlobal),
|
||||
callback(callback), callbackData(callbackData),
|
||||
script(nullptr), sourceObject(nullptr), sourceCompressionTask(nullptr),
|
||||
script(nullptr), sourceObject(nullptr),
|
||||
overRecursed(false), outOfMemory(false)
|
||||
{
|
||||
}
|
||||
@ -313,7 +313,7 @@ ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
|
||||
alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
|
||||
parseGlobal(parseGlobal),
|
||||
callback(callback), callbackData(callbackData),
|
||||
script(nullptr), sourceObject(nullptr), sourceCompressionTask(nullptr),
|
||||
script(nullptr), sourceObject(nullptr),
|
||||
overRecursed(false), outOfMemory(false)
|
||||
{
|
||||
}
|
||||
@ -340,8 +340,8 @@ ParseTask::finish(JSContext* cx)
|
||||
RootedScriptSource sso(cx, sourceObject);
|
||||
if (!ScriptSourceObject::initFromOptions(cx, sso, options))
|
||||
return false;
|
||||
if (sourceCompressionTask)
|
||||
sourceCompressionTask->fixupMajorGCNumber(cx->runtime());
|
||||
if (!sso->source()->tryCompressOffThread(cx))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -385,8 +385,7 @@ ScriptParseTask::parse(JSContext* cx)
|
||||
SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
|
||||
script = frontend::CompileGlobalScript(cx, alloc, ScopeKind::Global,
|
||||
options, srcBuf,
|
||||
/* sourceObjectOut = */ &sourceObject,
|
||||
/* sourceCompressionTaskOut = */ &sourceCompressionTask);
|
||||
/* sourceObjectOut = */ &sourceObject);
|
||||
}
|
||||
|
||||
ModuleParseTask::ModuleParseTask(JSContext* cx, JSObject* parseGlobal,
|
||||
@ -401,8 +400,7 @@ void
|
||||
ModuleParseTask::parse(JSContext* cx)
|
||||
{
|
||||
SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
|
||||
ModuleObject* module = frontend::CompileModule(cx, options, srcBuf, alloc, &sourceObject,
|
||||
&sourceCompressionTask);
|
||||
ModuleObject* module = frontend::CompileModule(cx, options, srcBuf, alloc, &sourceObject);
|
||||
if (module)
|
||||
script = module->script();
|
||||
}
|
||||
@ -1123,13 +1121,13 @@ GlobalHelperThreadState::scheduleCompressionTasks(const AutoLockHelperThreadStat
|
||||
{
|
||||
auto& pending = compressionPendingList(lock);
|
||||
auto& worklist = compressionWorklist(lock);
|
||||
MOZ_ASSERT(worklist.capacity() >= pending.length());
|
||||
|
||||
for (size_t i = 0; i < pending.length(); i++) {
|
||||
SourceCompressionTask* task = pending[i];
|
||||
if (task->shouldStart()) {
|
||||
if (pending[i]->shouldStart()) {
|
||||
// OOMing during appending results in the task not being scheduled
|
||||
// and deleted.
|
||||
Unused << worklist.append(Move(pending[i]));
|
||||
remove(pending, &i);
|
||||
worklist.infallibleAppend(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1730,8 +1728,13 @@ HelperThread::handleCompressionWorkload(AutoLockHelperThreadState& locked)
|
||||
MOZ_ASSERT(HelperThreadState().canStartCompressionTask(locked));
|
||||
MOZ_ASSERT(idle());
|
||||
|
||||
currentTask.emplace(HelperThreadState().compressionWorklist(locked).popCopy());
|
||||
SourceCompressionTask* task = compressionTask();
|
||||
UniquePtr<SourceCompressionTask> task;
|
||||
{
|
||||
auto& worklist = HelperThreadState().compressionWorklist(locked);
|
||||
task = Move(worklist.back());
|
||||
worklist.popBack();
|
||||
currentTask.emplace(task.get());
|
||||
}
|
||||
|
||||
{
|
||||
AutoUnlockHelperThreadState unlock(locked);
|
||||
@ -1744,7 +1747,7 @@ HelperThread::handleCompressionWorkload(AutoLockHelperThreadState& locked)
|
||||
|
||||
{
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
if (!HelperThreadState().compressionFinishedList(locked).append(task))
|
||||
if (!HelperThreadState().compressionFinishedList(locked).append(Move(task)))
|
||||
oomUnsafe.crash("handleCompressionWorkload");
|
||||
}
|
||||
|
||||
@ -1755,22 +1758,14 @@ HelperThread::handleCompressionWorkload(AutoLockHelperThreadState& locked)
|
||||
}
|
||||
|
||||
bool
|
||||
js::EnqueueOffThreadCompression(JSContext* cx, SourceCompressionTask* task)
|
||||
js::EnqueueOffThreadCompression(JSContext* cx, UniquePtr<SourceCompressionTask> task)
|
||||
{
|
||||
AutoLockHelperThreadState lock;
|
||||
|
||||
auto& pending = HelperThreadState().compressionPendingList(lock);
|
||||
auto& worklist = HelperThreadState().compressionWorklist(lock);
|
||||
if (!pending.append(task)) {
|
||||
if (!pending.append(Move(task))) {
|
||||
if (!cx->helperThread())
|
||||
ReportOutOfMemory(cx);
|
||||
js_delete(task);
|
||||
return false;
|
||||
}
|
||||
if (!worklist.reserve(pending.length())) {
|
||||
if (!cx->helperThread())
|
||||
ReportOutOfMemory(cx);
|
||||
pending.popBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1782,11 +1777,8 @@ static void
|
||||
ClearCompressionTaskList(T& list, JSRuntime* runtime)
|
||||
{
|
||||
for (size_t i = 0; i < list.length(); i++) {
|
||||
SourceCompressionTask* task = list[i];
|
||||
if (task->runtimeMatches(runtime)) {
|
||||
if (list[i]->runtimeMatches(runtime))
|
||||
HelperThreadState().remove(list, &i);
|
||||
js_delete(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ class GlobalHelperThreadState
|
||||
|
||||
typedef Vector<jit::IonBuilder*, 0, SystemAllocPolicy> IonBuilderVector;
|
||||
typedef Vector<ParseTask*, 0, SystemAllocPolicy> ParseTaskVector;
|
||||
typedef Vector<SourceCompressionTask*, 0, SystemAllocPolicy> SourceCompressionTaskVector;
|
||||
typedef Vector<UniquePtr<SourceCompressionTask>, 0, SystemAllocPolicy> SourceCompressionTaskVector;
|
||||
typedef Vector<GCHelperState*, 0, SystemAllocPolicy> GCHelperStateVector;
|
||||
typedef Vector<GCParallelTask*, 0, SystemAllocPolicy> GCParallelTaskVector;
|
||||
typedef Vector<PromiseTask*, 0, SystemAllocPolicy> PromiseTaskVector;
|
||||
@ -165,7 +165,10 @@ class GlobalHelperThreadState
|
||||
template <typename T>
|
||||
void remove(T& vector, size_t* index)
|
||||
{
|
||||
vector[(*index)--] = vector.back();
|
||||
// Self-moving is undefined behavior.
|
||||
if (*index != vector.length() - 1)
|
||||
vector[*index] = mozilla::Move(vector.back());
|
||||
(*index)--;
|
||||
vector.popBack();
|
||||
}
|
||||
|
||||
@ -550,7 +553,7 @@ struct AutoEnqueuePendingParseTasksAfterGC {
|
||||
|
||||
// Enqueue a compression job to be processed if there's a major GC.
|
||||
bool
|
||||
EnqueueOffThreadCompression(JSContext* cx, SourceCompressionTask* task);
|
||||
EnqueueOffThreadCompression(JSContext* cx, UniquePtr<SourceCompressionTask> task);
|
||||
|
||||
// Cancel all scheduled, in progress, or finished compression tasks for
|
||||
// runtime.
|
||||
@ -623,10 +626,6 @@ struct ParseTask
|
||||
// Holds the ScriptSourceObject generated for the script compilation.
|
||||
ScriptSourceObject* sourceObject;
|
||||
|
||||
// Holds the SourceCompressionTask, if any were enqueued for the
|
||||
// ScriptSource of sourceObject.
|
||||
SourceCompressionTask* sourceCompressionTask;
|
||||
|
||||
// Any errors or warnings produced during compilation. These are reported
|
||||
// when finishing the script.
|
||||
Vector<CompileError*, 0, SystemAllocPolicy> errors;
|
||||
@ -704,7 +703,6 @@ class SourceCompressionTask
|
||||
JSRuntime* runtime_;
|
||||
|
||||
// The major GC number of the runtime when the task was enqueued.
|
||||
static const uint64_t MajorGCNumberWaitingForFixup = UINT64_MAX;
|
||||
uint64_t majorGCNumber_;
|
||||
|
||||
// The source to be compressed.
|
||||
@ -717,31 +715,19 @@ class SourceCompressionTask
|
||||
mozilla::Maybe<SharedImmutableString> resultString_;
|
||||
|
||||
public:
|
||||
// The majorGCNumber is used for scheduling tasks. If the task is being
|
||||
// enqueued from an off-thread parsing task, leave the GC number
|
||||
// UINT64_MAX to be fixed up when the parse task finishes.
|
||||
// The majorGCNumber is used for scheduling tasks.
|
||||
SourceCompressionTask(JSRuntime* rt, ScriptSource* source)
|
||||
: runtime_(rt),
|
||||
majorGCNumber_(CurrentThreadCanAccessRuntime(rt)
|
||||
? rt->gc.majorGCCount()
|
||||
: MajorGCNumberWaitingForFixup),
|
||||
majorGCNumber_(rt->gc.majorGCCount()),
|
||||
sourceHolder_(source)
|
||||
{ }
|
||||
|
||||
bool runtimeMatches(JSRuntime* runtime) const {
|
||||
return runtime == runtime_;
|
||||
}
|
||||
|
||||
void fixupMajorGCNumber(JSRuntime* runtime) {
|
||||
MOZ_ASSERT(majorGCNumber_ == MajorGCNumberWaitingForFixup);
|
||||
majorGCNumber_ = runtime->gc.majorGCCount();
|
||||
}
|
||||
|
||||
bool shouldStart() const {
|
||||
// We wait 2 major GCs to start compressing, in order to avoid
|
||||
// immediate compression.
|
||||
if (majorGCNumber_ == MajorGCNumberWaitingForFixup)
|
||||
return false;
|
||||
return runtime_->gc.majorGCCount() > majorGCNumber_ + 1;
|
||||
}
|
||||
|
||||
|
@ -384,28 +384,6 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
return fun;
|
||||
}
|
||||
|
||||
static bool
|
||||
getOrCreateCreateArrayFromBufferFunction(JSContext* cx, MutableHandleValue fval)
|
||||
{
|
||||
RootedValue cache(cx, cx->global()->createArrayFromBuffer<NativeType>());
|
||||
if (cache.isObject()) {
|
||||
MOZ_ASSERT(cache.toObject().is<JSFunction>());
|
||||
fval.set(cache);
|
||||
return true;
|
||||
}
|
||||
|
||||
RootedFunction fun(cx);
|
||||
fun = NewNativeFunction(cx, ArrayBufferObject::createTypedArrayFromBuffer<NativeType>,
|
||||
0, nullptr);
|
||||
if (!fun)
|
||||
return false;
|
||||
|
||||
cx->global()->setCreateArrayFromBuffer<NativeType>(fun);
|
||||
|
||||
fval.setObject(*fun);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline const Class* instanceClass()
|
||||
{
|
||||
return TypedArrayObject::classForType(ArrayTypeID());
|
||||
@ -479,10 +457,12 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
}
|
||||
|
||||
static TypedArrayObject*
|
||||
makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, uint32_t byteOffset, uint32_t len,
|
||||
HandleObject proto)
|
||||
makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, uint32_t byteOffset,
|
||||
uint32_t len, HandleObject proto)
|
||||
{
|
||||
MOZ_ASSERT_IF(!buffer, byteOffset == 0);
|
||||
MOZ_ASSERT_IF(buffer, !buffer->isDetached());
|
||||
MOZ_ASSERT(len < INT32_MAX / sizeof(NativeType));
|
||||
|
||||
gc::AllocKind allocKind = buffer
|
||||
? GetGCObjectKind(instanceClass())
|
||||
@ -574,14 +554,6 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
return obj;
|
||||
}
|
||||
|
||||
static TypedArrayObject*
|
||||
makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
|
||||
uint32_t byteOffset, uint32_t len)
|
||||
{
|
||||
RootedObject proto(cx, nullptr);
|
||||
return makeInstance(cx, buffer, byteOffset, len, proto);
|
||||
}
|
||||
|
||||
static TypedArrayObject*
|
||||
makeTemplateObject(JSContext* cx, int32_t len)
|
||||
{
|
||||
@ -735,6 +707,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
static JSObject*
|
||||
create(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
@ -767,13 +740,17 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
if (!UncheckedUnwrap(dataObj)->is<ArrayBufferObjectMaybeShared>())
|
||||
return fromArray(cx, dataObj, newTarget);
|
||||
|
||||
/* (ArrayBuffer, [byteOffset, [length]]) */
|
||||
// 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] )
|
||||
|
||||
// Step 4.
|
||||
// 22.2.4.2.1 AllocateTypedArray, step 1.
|
||||
RootedObject proto(cx);
|
||||
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
|
||||
return nullptr;
|
||||
|
||||
int32_t byteOffset = 0;
|
||||
if (args.hasDefined(1)) {
|
||||
// Step 6.
|
||||
if (!ToInt32(cx, args[1], &byteOffset))
|
||||
return nullptr;
|
||||
if (byteOffset < 0) {
|
||||
@ -782,10 +759,18 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
"1");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Step 7.
|
||||
if (byteOffset % sizeof(NativeType) != 0) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t length = -1;
|
||||
if (args.hasDefined(2)) {
|
||||
// Step 8.a.
|
||||
if (!ToInt32(cx, args[2], &length))
|
||||
return nullptr;
|
||||
if (length < 0) {
|
||||
@ -796,131 +781,180 @@ class TypedArrayObjectTemplate : public TypedArrayObject
|
||||
}
|
||||
}
|
||||
|
||||
return fromBufferWithProto(cx, dataObj, byteOffset, length, proto);
|
||||
// Steps 9-17.
|
||||
if (dataObj->is<ArrayBufferObjectMaybeShared>()) {
|
||||
HandleArrayBufferObjectMaybeShared buffer = dataObj.as<ArrayBufferObjectMaybeShared>();
|
||||
return fromBufferSameCompartment(cx, buffer, byteOffset, length, proto);
|
||||
}
|
||||
return fromBufferWrapped(cx, dataObj, byteOffset, length, proto);
|
||||
}
|
||||
|
||||
// ES2018 draft rev 8340bf9a8427ea81bb0d1459471afbcc91d18add
|
||||
// 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] )
|
||||
// Steps 9-12.
|
||||
static bool
|
||||
computeAndCheckLength(JSContext* cx, HandleArrayBufferObjectMaybeShared bufferMaybeUnwrapped,
|
||||
uint32_t byteOffset, int32_t lengthInt, uint32_t* length)
|
||||
{
|
||||
MOZ_ASSERT(byteOffset % sizeof(NativeType) == 0);
|
||||
|
||||
// Step 9.
|
||||
if (bufferMaybeUnwrapped->isDetached()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 10.
|
||||
uint32_t bufferByteLength = bufferMaybeUnwrapped->byteLength();
|
||||
|
||||
// 11.c, 12.b.
|
||||
if (byteOffset > bufferByteLength) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
|
||||
return false; // invalid byteOffset
|
||||
}
|
||||
|
||||
uint32_t len;
|
||||
if (lengthInt < 0) {
|
||||
// Step 11.b.
|
||||
uint32_t newByteLength = bufferByteLength - byteOffset;
|
||||
len = newByteLength / sizeof(NativeType);
|
||||
|
||||
// Step 11.a.
|
||||
if (len * sizeof(NativeType) != newByteLength) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
|
||||
return false; // given byte array doesn't map exactly to sizeof(NativeType) * N
|
||||
}
|
||||
|
||||
// ArrayBuffer is too large for TypedArrays:
|
||||
// Standalone ArrayBuffers can hold up to INT32_MAX bytes, whereas
|
||||
// buffers in TypedArrays must have less than or equal to
|
||||
// |INT32_MAX - sizeof(NativeType) - INT32_MAX % sizeof(NativeType)|
|
||||
// bytes.
|
||||
if (len >= INT32_MAX / sizeof(NativeType)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Step 12.a (implicit).
|
||||
len = uint32_t(lengthInt);
|
||||
|
||||
// Step 12.b.
|
||||
if (len >= INT32_MAX / sizeof(NativeType)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
|
||||
return false;
|
||||
}
|
||||
uint32_t newByteLength = len * sizeof(NativeType);
|
||||
|
||||
// Step 12.b (|byteOffset| moved to the RHS to avoid overflow).
|
||||
if (newByteLength > bufferByteLength - byteOffset) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
|
||||
return false; // |byteOffset + newByteLength| is too big for the arraybuffer
|
||||
}
|
||||
}
|
||||
|
||||
*length = len;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ES2018 draft rev 8340bf9a8427ea81bb0d1459471afbcc91d18add
|
||||
// 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] )
|
||||
// Steps 9-17.
|
||||
static JSObject*
|
||||
fromBufferSameCompartment(JSContext* cx, HandleArrayBufferObjectMaybeShared buffer,
|
||||
uint32_t byteOffset, int32_t lengthInt, HandleObject proto)
|
||||
{
|
||||
// Steps 9-12.
|
||||
uint32_t length;
|
||||
if (!computeAndCheckLength(cx, buffer, byteOffset, lengthInt, &length))
|
||||
return nullptr;
|
||||
|
||||
// Steps 13-17.
|
||||
return makeInstance(cx, buffer, byteOffset, length, proto);
|
||||
}
|
||||
|
||||
// Create a TypedArray object in another compartment.
|
||||
//
|
||||
// ES6 supports creating a TypedArray in global A (using global A's
|
||||
// TypedArray constructor) backed by an ArrayBuffer created in global B.
|
||||
//
|
||||
// Our TypedArrayObject implementation doesn't support a TypedArray in
|
||||
// compartment A backed by an ArrayBuffer in compartment B. So in this
|
||||
// case, we create the TypedArray in B (!) and return a cross-compartment
|
||||
// wrapper.
|
||||
//
|
||||
// Extra twist: the spec says the new TypedArray's [[Prototype]] must be
|
||||
// A's TypedArray.prototype. So even though we're creating the TypedArray
|
||||
// in B, its [[Prototype]] must be (a cross-compartment wrapper for) the
|
||||
// TypedArray.prototype in A.
|
||||
static JSObject*
|
||||
fromBufferWrapped(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt,
|
||||
HandleObject proto)
|
||||
{
|
||||
JSObject* unwrapped = CheckedUnwrap(bufobj);
|
||||
if (!unwrapped) {
|
||||
ReportAccessDenied(cx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!unwrapped->is<ArrayBufferObjectMaybeShared>()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedArrayBufferObjectMaybeShared unwrappedBuffer(cx);
|
||||
unwrappedBuffer = &unwrapped->as<ArrayBufferObjectMaybeShared>();
|
||||
|
||||
uint32_t length;
|
||||
if (!computeAndCheckLength(cx, unwrappedBuffer, byteOffset, lengthInt, &length))
|
||||
return nullptr;
|
||||
|
||||
// Make sure to get the [[Prototype]] for the created typed array from
|
||||
// this compartment.
|
||||
RootedObject protoRoot(cx, proto);
|
||||
if (!protoRoot) {
|
||||
if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(instanceClass()), &protoRoot))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedObject typedArray(cx);
|
||||
{
|
||||
JSAutoCompartment ac(cx, unwrappedBuffer);
|
||||
|
||||
RootedObject wrappedProto(cx, protoRoot);
|
||||
if (!cx->compartment()->wrap(cx, &wrappedProto))
|
||||
return nullptr;
|
||||
|
||||
typedArray = makeInstance(cx, unwrappedBuffer, byteOffset, length, wrappedProto);
|
||||
if (!typedArray)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!cx->compartment()->wrap(cx, &typedArray))
|
||||
return nullptr;
|
||||
|
||||
return typedArray;
|
||||
}
|
||||
|
||||
public:
|
||||
static JSObject*
|
||||
fromBuffer(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt) {
|
||||
return fromBufferWithProto(cx, bufobj, byteOffset, lengthInt, nullptr);
|
||||
}
|
||||
|
||||
static JSObject*
|
||||
fromBufferWithProto(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt,
|
||||
HandleObject proto)
|
||||
fromBuffer(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt)
|
||||
{
|
||||
if (bufobj->is<ProxyObject>()) {
|
||||
/*
|
||||
* Normally, NonGenericMethodGuard handles the case of transparent
|
||||
* wrappers. However, we have a peculiar situation: we want to
|
||||
* construct the new typed array in the compartment of the buffer,
|
||||
* so that the typed array can point directly at their buffer's
|
||||
* data without crossing compartment boundaries. So we use the
|
||||
* machinery underlying NonGenericMethodGuard directly to proxy the
|
||||
* native call. We will end up with a wrapper in the origin
|
||||
* compartment for a view in the target compartment referencing the
|
||||
* ArrayBufferObject in that same compartment.
|
||||
*/
|
||||
JSObject* wrapped = CheckedUnwrap(bufobj);
|
||||
if (!wrapped) {
|
||||
ReportAccessDenied(cx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!IsAnyArrayBuffer(wrapped)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
|
||||
return nullptr; // must be arrayBuffer
|
||||
}
|
||||
|
||||
/*
|
||||
* And for even more fun, the new view's prototype should be
|
||||
* set to the origin compartment's prototype object, not the
|
||||
* target's (specifically, the actual view in the target
|
||||
* compartment will use as its prototype a wrapper around the
|
||||
* origin compartment's view.prototype object).
|
||||
*
|
||||
* Rather than hack some crazy solution together, implement
|
||||
* this all using a private helper function, created when
|
||||
* ArrayBufferObject was initialized and cached in the global.
|
||||
* This reuses all the existing cross-compartment crazy so we
|
||||
* don't have to do anything *uniquely* crazy here.
|
||||
*/
|
||||
|
||||
RootedObject protoRoot(cx, proto);
|
||||
if (!protoRoot) {
|
||||
if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(instanceClass()), &protoRoot))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FixedInvokeArgs<3> args(cx);
|
||||
|
||||
args[0].setNumber(byteOffset);
|
||||
args[1].setInt32(lengthInt);
|
||||
args[2].setObject(*protoRoot);
|
||||
|
||||
RootedValue fval(cx);
|
||||
if (!getOrCreateCreateArrayFromBufferFunction(cx, &fval))
|
||||
return nullptr;
|
||||
|
||||
RootedValue thisv(cx, ObjectValue(*bufobj));
|
||||
RootedValue rval(cx);
|
||||
if (!js::Call(cx, fval, thisv, args, &rval))
|
||||
return nullptr;
|
||||
|
||||
return &rval.toObject();
|
||||
}
|
||||
|
||||
if (!IsAnyArrayBuffer(bufobj)) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
|
||||
return nullptr; // must be arrayBuffer
|
||||
}
|
||||
|
||||
Rooted<ArrayBufferObjectMaybeShared*> buffer(cx);
|
||||
if (IsArrayBuffer(bufobj)) {
|
||||
ArrayBufferObject& buf = AsArrayBuffer(bufobj);
|
||||
if (buf.isDetached()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
buffer = static_cast<ArrayBufferObjectMaybeShared*>(&buf);
|
||||
} else {
|
||||
buffer = static_cast<ArrayBufferObjectMaybeShared*>(&AsSharedArrayBuffer(bufobj));
|
||||
}
|
||||
|
||||
if (byteOffset > buffer->byteLength() || byteOffset % sizeof(NativeType) != 0) {
|
||||
if (byteOffset % sizeof(NativeType) != 0) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
|
||||
return nullptr; // invalid byteOffset
|
||||
}
|
||||
|
||||
uint32_t len;
|
||||
if (lengthInt == -1) {
|
||||
len = (buffer->byteLength() - byteOffset) / sizeof(NativeType);
|
||||
if (len * sizeof(NativeType) != buffer->byteLength() - byteOffset) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
|
||||
return nullptr; // given byte array doesn't map exactly to sizeof(NativeType) * N
|
||||
}
|
||||
} else {
|
||||
len = uint32_t(lengthInt);
|
||||
if (bufobj->is<ArrayBufferObjectMaybeShared>()) {
|
||||
HandleArrayBufferObjectMaybeShared buffer = bufobj.as<ArrayBufferObjectMaybeShared>();
|
||||
return fromBufferSameCompartment(cx, buffer, byteOffset, lengthInt, nullptr);
|
||||
}
|
||||
|
||||
// Go slowly and check for overflow.
|
||||
uint32_t arrayByteLength = len * sizeof(NativeType);
|
||||
if (len >= INT32_MAX / sizeof(NativeType) || byteOffset >= INT32_MAX - arrayByteLength) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
|
||||
return nullptr; // overflow when calculating byteOffset + len * sizeof(NativeType)
|
||||
}
|
||||
|
||||
if (arrayByteLength + byteOffset > buffer->byteLength()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
|
||||
return nullptr; // byteOffset + len is too big for the arraybuffer
|
||||
}
|
||||
|
||||
return makeInstance(cx, buffer, byteOffset, len, proto);
|
||||
return fromBufferWrapped(cx, bufobj, byteOffset, lengthInt, nullptr);
|
||||
}
|
||||
|
||||
static bool
|
||||
@ -1779,38 +1813,6 @@ TypedArrayObject::sharedTypedArrayPrototypeClass = {
|
||||
&TypedArrayObjectSharedTypedArrayPrototypeClassSpec
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
bool
|
||||
ArrayBufferObject::createTypedArrayFromBufferImpl(JSContext* cx, const CallArgs& args)
|
||||
{
|
||||
typedef TypedArrayObjectTemplate<T> ArrayType;
|
||||
MOZ_ASSERT(IsAnyArrayBuffer(args.thisv()));
|
||||
MOZ_ASSERT(args.length() == 3);
|
||||
|
||||
Rooted<JSObject*> buffer(cx, &args.thisv().toObject());
|
||||
Rooted<JSObject*> proto(cx, &args[2].toObject());
|
||||
|
||||
Rooted<JSObject*> obj(cx);
|
||||
double byteOffset = args[0].toNumber();
|
||||
MOZ_ASSERT(0 <= byteOffset);
|
||||
MOZ_ASSERT(byteOffset <= UINT32_MAX);
|
||||
MOZ_ASSERT(byteOffset == uint32_t(byteOffset));
|
||||
obj = ArrayType::fromBufferWithProto(cx, buffer, uint32_t(byteOffset), args[1].toInt32(),
|
||||
proto);
|
||||
if (!obj)
|
||||
return false;
|
||||
args.rval().setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool
|
||||
ArrayBufferObject::createTypedArrayFromBuffer(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod<IsAnyArrayBuffer, createTypedArrayFromBufferImpl<T> >(cx, args);
|
||||
}
|
||||
|
||||
// this default implementation is only valid for integer types
|
||||
// less than 32-bits in size.
|
||||
template<typename NativeType>
|
||||
|
@ -427,23 +427,6 @@ TypedArrayElemSize(Scalar::Type viewType)
|
||||
extern void
|
||||
SetDisjointTypedElements(TypedArrayObject* target, uint32_t targetOffset,
|
||||
TypedArrayObject* unsafeSrcCrossCompartment);
|
||||
static inline bool
|
||||
IsAnyArrayBuffer(HandleObject obj)
|
||||
{
|
||||
return IsArrayBuffer(obj) || IsSharedArrayBuffer(obj);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
IsAnyArrayBuffer(JSObject* obj)
|
||||
{
|
||||
return IsArrayBuffer(obj) || IsSharedArrayBuffer(obj);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
IsAnyArrayBuffer(HandleValue v)
|
||||
{
|
||||
return v.isObject() && IsAnyArrayBuffer(&v.toObject());
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
|
@ -2073,7 +2073,7 @@ nsPresContext::MediaFeatureValuesChanged(nsRestyleHint aRestyleHint,
|
||||
mPendingViewportChange = false;
|
||||
|
||||
if (mDocument->IsBeingUsedAsImage()) {
|
||||
MOZ_ASSERT(PR_CLIST_IS_EMPTY(mDocument->MediaQueryLists()));
|
||||
MOZ_ASSERT(mDocument->MediaQueryLists().isEmpty());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2088,13 +2088,11 @@ nsPresContext::MediaFeatureValuesChanged(nsRestyleHint aRestyleHint,
|
||||
// Note that we do this after the new style from media queries in
|
||||
// style sheets has been computed.
|
||||
|
||||
if (!PR_CLIST_IS_EMPTY(mDocument->MediaQueryLists())) {
|
||||
if (!mDocument->MediaQueryLists().isEmpty()) {
|
||||
// We build a list of all the notifications we're going to send
|
||||
// before we send any of them.
|
||||
for (PRCList *l = PR_LIST_HEAD(mDocument->MediaQueryLists());
|
||||
l != mDocument->MediaQueryLists(); l = PR_NEXT_LINK(l)) {
|
||||
for (auto mql : mDocument->MediaQueryLists()) {
|
||||
nsAutoMicroTask mt;
|
||||
MediaQueryList *mql = static_cast<MediaQueryList*>(l);
|
||||
mql->MaybeNotify();
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<title>CSS transforms: Creating containing block for fixed positioned elements</title>
|
||||
<link rel="author" title="L. David Baron" href="https://dbaron.org/">
|
||||
<link rel="author" title="Mozilla" href="http://www.mozilla.org/">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-transforms-1/#perspective-property">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-transforms-2/#perspective-property">
|
||||
<link rel="match" href="containing-block-dynamic-1-ref.html">
|
||||
<meta name="assert" content="It also establishes a containing block (somewhat similar to position: relative), just like the transform property does.">
|
||||
<meta name="flags" content="dom">
|
||||
|
@ -3,7 +3,7 @@
|
||||
<title>CSS transforms: Creating containing block for fixed positioned elements</title>
|
||||
<link rel="author" title="L. David Baron" href="https://dbaron.org/">
|
||||
<link rel="author" title="Mozilla" href="http://www.mozilla.org/">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-transforms-1/#perspective-property">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-transforms-2/#perspective-property">
|
||||
<link rel="match" href="containing-block-dynamic-1-ref.html">
|
||||
<meta name="assert" content="It also establishes a containing block (somewhat similar to position: relative), just like the transform property does.">
|
||||
<meta name="flags" content="dom">
|
||||
|
@ -28,17 +28,11 @@ MediaQueryList::MediaQueryList(nsIDocument* aDocument,
|
||||
mMediaList =
|
||||
MediaList::Create(aDocument->GetStyleBackendType(), aMediaQueryList);
|
||||
|
||||
PR_INIT_CLIST(this);
|
||||
|
||||
KeepAliveIfHasListenersFor(ONCHANGE_STRING);
|
||||
}
|
||||
|
||||
MediaQueryList::~MediaQueryList()
|
||||
{
|
||||
if (mDocument) {
|
||||
PR_REMOVE_LINK(this);
|
||||
}
|
||||
}
|
||||
{}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(MediaQueryList)
|
||||
|
||||
@ -50,7 +44,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaQueryList,
|
||||
DOMEventTargetHelper)
|
||||
if (tmp->mDocument) {
|
||||
PR_REMOVE_LINK(tmp);
|
||||
tmp->remove();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
|
||||
}
|
||||
tmp->Disconnect();
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsTArray.h"
|
||||
#include "prclist.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "nsWrapperCache.h"
|
||||
#include "mozilla/DOMEventTargetHelper.h"
|
||||
@ -27,7 +27,7 @@ namespace dom {
|
||||
class MediaList;
|
||||
|
||||
class MediaQueryList final : public DOMEventTargetHelper,
|
||||
public PRCList
|
||||
public mozilla::LinkedListElement<MediaQueryList>
|
||||
{
|
||||
public:
|
||||
// The caller who constructs is responsible for calling Evaluate
|
||||
|
357
mfbt/DoublyLinkedList.h
Normal file
357
mfbt/DoublyLinkedList.h
Normal file
@ -0,0 +1,357 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/** A doubly-linked list with flexible next/prev naming. */
|
||||
|
||||
#ifndef mozilla_DoublyLinkedList_h
|
||||
#define mozilla_DoublyLinkedList_h
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
/**
|
||||
* Where mozilla::LinkedList strives for ease of use above all other
|
||||
* considerations, mozilla::DoublyLinkedList strives for flexibility. The
|
||||
* following are things that can be done with mozilla::DoublyLinkedList that
|
||||
* cannot be done with mozilla::LinkedList:
|
||||
*
|
||||
* * Arbitrary next/prev placement and naming. With the tools provided here,
|
||||
* the next and previous pointers can be at the end of the structure, in a
|
||||
* sub-structure, stored with a tag, in a union, wherever, as long as you
|
||||
* can look them up and set them on demand.
|
||||
* * Can be used without deriving from a new base and, thus, does not require
|
||||
* use of constructors.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* class Observer : public DoublyLinkedListElement<Observer>
|
||||
* {
|
||||
* public:
|
||||
* void observe(char* aTopic) { ... }
|
||||
* };
|
||||
*
|
||||
* class ObserverContainer
|
||||
* {
|
||||
* private:
|
||||
* DoublyLinkedList<Observer> mList;
|
||||
*
|
||||
* public:
|
||||
* void addObserver(Observer* aObserver)
|
||||
* {
|
||||
* // Will assert if |aObserver| is part of another list.
|
||||
* mList.pushBack(aObserver);
|
||||
* }
|
||||
*
|
||||
* void removeObserver(Observer* aObserver)
|
||||
* {
|
||||
* // Will assert if |aObserver| is not part of |list|.
|
||||
* mList.remove(aObserver);
|
||||
* }
|
||||
*
|
||||
* void notifyObservers(char* aTopic)
|
||||
* {
|
||||
* for (Observer* o : mList) {
|
||||
* o->observe(aTopic);
|
||||
* }
|
||||
* }
|
||||
* };
|
||||
*/
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* Provides access to a next and previous element pointer named |mNext| and
|
||||
* |mPrev| respectively. This class is the default and will work if the list
|
||||
* element derives from DoublyLinkedListElement.
|
||||
*
|
||||
* Although designed to work with DoublyLinkedListElement this will als work
|
||||
* with any class that defines |mNext| and |mPrev| members with the correct
|
||||
* type.
|
||||
*/
|
||||
template <typename T>
|
||||
struct DoublyLinkedSiblingAccess {
|
||||
static void SetNext(T* aElm, T* aNext) { aElm->mNext = aNext; }
|
||||
static T* GetNext(T* aElm) { return aElm->mNext; }
|
||||
static void SetPrev(T* aElm, T* aPrev) { aElm->mPrev = aPrev; }
|
||||
static T* GetPrev(T* aElm) { return aElm->mPrev; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Deriving from this will allow T to be inserted into and removed from a
|
||||
* DoublyLinkedList.
|
||||
*/
|
||||
template <typename T>
|
||||
struct DoublyLinkedListElement
|
||||
{
|
||||
friend struct DoublyLinkedSiblingAccess<T>;
|
||||
T* mNext;
|
||||
T* mPrev;
|
||||
|
||||
public:
|
||||
DoublyLinkedListElement() : mNext(nullptr), mPrev(nullptr) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* A doubly linked list. |T| is the type of element stored in this list. |T|
|
||||
* must contain or have access to unique next and previous element pointers.
|
||||
* The template argument |SiblingAccess| provides code to tell this list how to
|
||||
* get and set the next and previous pointers. The actual storage of next/prev
|
||||
* links may reside anywhere and be encoded in any way.
|
||||
*/
|
||||
template <typename T, typename SiblingAccess = DoublyLinkedSiblingAccess<T>>
|
||||
class DoublyLinkedList final
|
||||
{
|
||||
T* mHead;
|
||||
T* mTail;
|
||||
|
||||
/**
|
||||
* Checks that either the list is empty and both mHead and mTail are nullptr
|
||||
* or the list has entries and both mHead and mTail are non-null.
|
||||
*/
|
||||
bool isStateValid() const {
|
||||
return (mHead != nullptr) == (mTail != nullptr);
|
||||
}
|
||||
|
||||
static bool ElementNotInList(T* aElm) {
|
||||
return !SiblingAccess::GetNext(aElm) && !SiblingAccess::GetPrev(aElm);
|
||||
}
|
||||
|
||||
public:
|
||||
DoublyLinkedList() : mHead(nullptr), mTail(nullptr) {}
|
||||
|
||||
class Iterator final {
|
||||
T* mCurrent;
|
||||
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = T;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = T*;
|
||||
using reference = T&;
|
||||
|
||||
Iterator() : mCurrent(nullptr) {}
|
||||
explicit Iterator(T* aCurrent) : mCurrent(aCurrent) {}
|
||||
|
||||
T& operator *() const { return *mCurrent; }
|
||||
T* operator ->() const { return mCurrent; }
|
||||
|
||||
Iterator& operator++() {
|
||||
mCurrent = SiblingAccess::GetNext(mCurrent);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator++(int) {
|
||||
Iterator result = *this;
|
||||
++(*this);
|
||||
return result;
|
||||
}
|
||||
|
||||
Iterator& operator--() {
|
||||
mCurrent = SiblingAccess::GetPrev(mCurrent);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator--(int) {
|
||||
Iterator result = *this;
|
||||
--(*this);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator& aOther) const {
|
||||
return mCurrent != aOther.mCurrent;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator& aOther) const {
|
||||
return mCurrent == aOther.mCurrent;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return mCurrent;
|
||||
}
|
||||
};
|
||||
|
||||
Iterator begin() { return Iterator(mHead); }
|
||||
const Iterator begin() const { return Iterator(mHead); }
|
||||
const Iterator cbegin() const { return Iterator(mHead); }
|
||||
|
||||
Iterator end() { return Iterator(); }
|
||||
const Iterator end() const { return Iterator(); }
|
||||
const Iterator cend() const { return Iterator(); }
|
||||
|
||||
/**
|
||||
* Returns true if the list contains no elements.
|
||||
*/
|
||||
bool isEmpty() const {
|
||||
MOZ_ASSERT(isStateValid());
|
||||
return mHead == nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts aElm into the list at the head position. |aElm| must not already
|
||||
* be in a list.
|
||||
*/
|
||||
void pushFront(T* aElm) {
|
||||
MOZ_ASSERT(aElm);
|
||||
MOZ_ASSERT(ElementNotInList(aElm));
|
||||
MOZ_ASSERT(isStateValid());
|
||||
|
||||
SiblingAccess::SetNext(aElm, mHead);
|
||||
if (mHead) {
|
||||
MOZ_ASSERT(!SiblingAccess::GetPrev(mHead));
|
||||
SiblingAccess::SetPrev(mHead, aElm);
|
||||
}
|
||||
|
||||
mHead = aElm;
|
||||
if (!mTail) {
|
||||
mTail = aElm;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the head of the list and return it. Calling this on an empty list
|
||||
* will assert.
|
||||
*/
|
||||
T* popFront() {
|
||||
MOZ_ASSERT(!isEmpty());
|
||||
MOZ_ASSERT(isStateValid());
|
||||
|
||||
T* result = mHead;
|
||||
mHead = result ? SiblingAccess::GetNext(result) : nullptr;
|
||||
if (mHead) {
|
||||
SiblingAccess::SetPrev(mHead, nullptr);
|
||||
}
|
||||
|
||||
if (mTail == result) {
|
||||
mTail = nullptr;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
SiblingAccess::SetNext(result, nullptr);
|
||||
SiblingAccess::SetPrev(result, nullptr);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts aElm into the list at the tail position. |aElm| must not already
|
||||
* be in a list.
|
||||
*/
|
||||
void pushBack(T* aElm) {
|
||||
MOZ_ASSERT(aElm);
|
||||
MOZ_ASSERT(ElementNotInList(aElm));
|
||||
MOZ_ASSERT(isStateValid());
|
||||
|
||||
SiblingAccess::SetNext(aElm, nullptr);
|
||||
SiblingAccess::SetPrev(aElm, mTail);
|
||||
if (mTail) {
|
||||
MOZ_ASSERT(!SiblingAccess::GetNext(mTail));
|
||||
SiblingAccess::SetNext(mTail, aElm);
|
||||
}
|
||||
|
||||
mTail = aElm;
|
||||
if (!mHead) {
|
||||
mHead = aElm;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the tail of the list and return it. Calling this on an empty list
|
||||
* will assert.
|
||||
*/
|
||||
T* popBack() {
|
||||
MOZ_ASSERT(!isEmpty());
|
||||
MOZ_ASSERT(isStateValid());
|
||||
|
||||
T* result = mTail;
|
||||
mTail = result ? SiblingAccess::GetPrev(result) : nullptr;
|
||||
if (mTail) {
|
||||
SiblingAccess::SetNext(mTail, nullptr);
|
||||
}
|
||||
|
||||
if (mHead == result) {
|
||||
mHead = nullptr;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
SiblingAccess::SetNext(result, nullptr);
|
||||
SiblingAccess::SetPrev(result, nullptr);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the given |aElm| *before* |aIter|.
|
||||
*/
|
||||
void insertBefore(const Iterator& aIter, T* aElm) {
|
||||
MOZ_ASSERT(aElm);
|
||||
MOZ_ASSERT(ElementNotInList(aElm));
|
||||
MOZ_ASSERT(isStateValid());
|
||||
|
||||
if (!aIter) {
|
||||
return pushBack(aElm);
|
||||
} else if (aIter == begin()) {
|
||||
return pushFront(aElm);
|
||||
}
|
||||
|
||||
T* after = &(*aIter);
|
||||
T* before = SiblingAccess::GetPrev(after);
|
||||
MOZ_ASSERT(before);
|
||||
|
||||
SiblingAccess::SetNext(before, aElm);
|
||||
SiblingAccess::SetPrev(aElm, before);
|
||||
SiblingAccess::SetNext(aElm, after);
|
||||
SiblingAccess::SetPrev(after, aElm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given element from the list. The element must be in this list.
|
||||
*/
|
||||
void remove(T* aElm) {
|
||||
MOZ_ASSERT(aElm);
|
||||
MOZ_ASSERT(SiblingAccess::GetNext(aElm) || SiblingAccess::GetPrev(aElm) ||
|
||||
(aElm == mHead && aElm == mTail),
|
||||
"Attempted to remove element not in this list");
|
||||
|
||||
if (T* prev = SiblingAccess::GetPrev(aElm)) {
|
||||
SiblingAccess::SetNext(prev, SiblingAccess::GetNext(aElm));
|
||||
} else {
|
||||
MOZ_ASSERT(mHead == aElm);
|
||||
mHead = SiblingAccess::GetNext(aElm);
|
||||
}
|
||||
|
||||
if (T* next = SiblingAccess::GetNext(aElm)) {
|
||||
SiblingAccess::SetPrev(next, SiblingAccess::GetPrev(aElm));
|
||||
} else {
|
||||
MOZ_ASSERT(mTail == aElm);
|
||||
mTail = SiblingAccess::GetPrev(aElm);
|
||||
}
|
||||
|
||||
SiblingAccess::SetNext(aElm, nullptr);
|
||||
SiblingAccess::SetPrev(aElm, nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator referencing the first found element whose value matches
|
||||
* the given element according to operator==.
|
||||
*/
|
||||
Iterator find(const T& aElm) {
|
||||
return std::find(begin(), end(), aElm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given element is in the list. Note that this uses
|
||||
* T::operator==, not pointer comparison.
|
||||
*/
|
||||
bool contains(const T& aElm) {
|
||||
return find(aElm) != Iterator();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_DoublyLinkedList_h
|
@ -35,6 +35,7 @@ EXPORTS.mozilla = [
|
||||
'decimal/Decimal.h',
|
||||
'double-conversion/source/double-conversion.h',
|
||||
'double-conversion/source/utils.h',
|
||||
'DoublyLinkedList.h',
|
||||
'EndianUtils.h',
|
||||
'EnumeratedArray.h',
|
||||
'EnumeratedRange.h',
|
||||
|
173
mfbt/tests/TestDoublyLinkedList.cpp
Normal file
173
mfbt/tests/TestDoublyLinkedList.cpp
Normal file
@ -0,0 +1,173 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/DoublyLinkedList.h"
|
||||
|
||||
using mozilla::DoublyLinkedList;
|
||||
using mozilla::DoublyLinkedListElement;
|
||||
|
||||
struct SomeClass : public DoublyLinkedListElement<SomeClass> {
|
||||
unsigned int mValue;
|
||||
explicit SomeClass(int aValue) : mValue(aValue) {}
|
||||
void incr() { ++mValue; }
|
||||
bool operator==(const SomeClass& other) { return mValue == other.mValue; }
|
||||
};
|
||||
|
||||
template <typename ListType, size_t N>
|
||||
static void
|
||||
CheckListValues(ListType& list, unsigned int (&values)[N])
|
||||
{
|
||||
size_t count = 0;
|
||||
for (auto& x : list) {
|
||||
MOZ_RELEASE_ASSERT(x.mValue == values[count]);
|
||||
++count;
|
||||
}
|
||||
MOZ_RELEASE_ASSERT(count == N);
|
||||
}
|
||||
|
||||
static void
|
||||
TestDoublyLinkedList()
|
||||
{
|
||||
DoublyLinkedList<SomeClass> list;
|
||||
|
||||
SomeClass one(1), two(2), three(3);
|
||||
|
||||
MOZ_RELEASE_ASSERT(list.isEmpty());
|
||||
MOZ_RELEASE_ASSERT(!list.begin());
|
||||
MOZ_RELEASE_ASSERT(!list.end());
|
||||
|
||||
for (SomeClass& x : list) {
|
||||
MOZ_RELEASE_ASSERT(x.mValue);
|
||||
MOZ_RELEASE_ASSERT(false);
|
||||
}
|
||||
|
||||
list.pushFront(&one);
|
||||
{ unsigned int check[] { 1 }; CheckListValues(list, check); }
|
||||
|
||||
MOZ_RELEASE_ASSERT(list.contains(one));
|
||||
MOZ_RELEASE_ASSERT(!list.contains(two));
|
||||
MOZ_RELEASE_ASSERT(!list.contains(three));
|
||||
|
||||
MOZ_RELEASE_ASSERT(!list.isEmpty());
|
||||
MOZ_RELEASE_ASSERT(list.begin()->mValue == 1);
|
||||
MOZ_RELEASE_ASSERT(!list.end());
|
||||
|
||||
list.pushFront(&two);
|
||||
{ unsigned int check[] { 2, 1 }; CheckListValues(list, check); }
|
||||
|
||||
MOZ_RELEASE_ASSERT(list.begin()->mValue == 2);
|
||||
MOZ_RELEASE_ASSERT(!list.end());
|
||||
MOZ_RELEASE_ASSERT(!list.contains(three));
|
||||
|
||||
list.pushBack(&three);
|
||||
{ unsigned int check[] { 2, 1, 3 }; CheckListValues(list, check); }
|
||||
|
||||
MOZ_RELEASE_ASSERT(list.begin()->mValue == 2);
|
||||
MOZ_RELEASE_ASSERT(!list.end());
|
||||
|
||||
list.remove(&one);
|
||||
{ unsigned int check[] { 2, 3 }; CheckListValues(list, check); }
|
||||
|
||||
list.insertBefore(list.find(three), &one);
|
||||
{ unsigned int check[] { 2, 1, 3 }; CheckListValues(list, check); }
|
||||
|
||||
list.remove(&three);
|
||||
{ unsigned int check[] { 2, 1 }; CheckListValues(list, check); }
|
||||
|
||||
list.insertBefore(list.find(two), &three);
|
||||
{ unsigned int check[] { 3, 2, 1 }; CheckListValues(list, check); }
|
||||
|
||||
list.remove(&three);
|
||||
{ unsigned int check[] { 2, 1 }; CheckListValues(list, check); }
|
||||
|
||||
list.insertBefore(++list.find(two), &three);
|
||||
{ unsigned int check[] { 2, 3, 1 }; CheckListValues(list, check); }
|
||||
|
||||
list.remove(&one);
|
||||
{ unsigned int check[] { 2, 3 }; CheckListValues(list, check); }
|
||||
|
||||
list.remove(&two);
|
||||
{ unsigned int check[] { 3 }; CheckListValues(list, check); }
|
||||
|
||||
list.insertBefore(list.find(three), &two);
|
||||
{ unsigned int check[] { 2, 3 }; CheckListValues(list, check); }
|
||||
|
||||
list.remove(&three);
|
||||
{ unsigned int check[] { 2 }; CheckListValues(list, check); }
|
||||
|
||||
list.remove(&two);
|
||||
MOZ_RELEASE_ASSERT(list.isEmpty());
|
||||
|
||||
list.pushBack(&three);
|
||||
{ unsigned int check[] { 3 }; CheckListValues(list, check); }
|
||||
|
||||
list.pushFront(&two);
|
||||
{ unsigned int check[] { 2, 3 }; CheckListValues(list, check); }
|
||||
|
||||
// This should modify the values of |two| and |three| as pointers to them are
|
||||
// stored in the list, not copies.
|
||||
for (SomeClass& x : list) {
|
||||
x.incr();
|
||||
}
|
||||
|
||||
MOZ_RELEASE_ASSERT(*list.begin() == two);
|
||||
MOZ_RELEASE_ASSERT(*++list.begin() == three);
|
||||
|
||||
SomeClass four(4);
|
||||
MOZ_RELEASE_ASSERT(++list.begin() == list.find(four));
|
||||
}
|
||||
|
||||
static void
|
||||
TestCustomAccessor()
|
||||
{
|
||||
struct InTwoLists {
|
||||
explicit InTwoLists(unsigned int aValue) : mValue(aValue) {}
|
||||
DoublyLinkedListElement<InTwoLists> mListOne;
|
||||
DoublyLinkedListElement<InTwoLists> mListTwo;
|
||||
unsigned int mValue;
|
||||
};
|
||||
|
||||
struct ListOneSiblingAccess {
|
||||
static void SetNext(InTwoLists* aElm, InTwoLists* aNext) { aElm->mListOne.mNext = aNext; }
|
||||
static InTwoLists* GetNext(InTwoLists* aElm) { return aElm->mListOne.mNext; }
|
||||
static void SetPrev(InTwoLists* aElm, InTwoLists* aPrev) { aElm->mListOne.mPrev = aPrev; }
|
||||
static InTwoLists* GetPrev(InTwoLists* aElm) { return aElm->mListOne.mPrev; }
|
||||
};
|
||||
|
||||
struct ListTwoSiblingAccess {
|
||||
static void SetNext(InTwoLists* aElm, InTwoLists* aNext) { aElm->mListTwo.mNext = aNext; }
|
||||
static InTwoLists* GetNext(InTwoLists* aElm) { return aElm->mListTwo.mNext; }
|
||||
static void SetPrev(InTwoLists* aElm, InTwoLists* aPrev) { aElm->mListTwo.mPrev = aPrev; }
|
||||
static InTwoLists* GetPrev(InTwoLists* aElm) { return aElm->mListTwo.mPrev; }
|
||||
};
|
||||
|
||||
DoublyLinkedList<InTwoLists, ListOneSiblingAccess> listOne;
|
||||
DoublyLinkedList<InTwoLists, ListTwoSiblingAccess> listTwo;
|
||||
|
||||
InTwoLists one(1);
|
||||
InTwoLists two(2);
|
||||
|
||||
listOne.pushBack(&one);
|
||||
listOne.pushBack(&two);
|
||||
{ unsigned int check[] { 1, 2 }; CheckListValues(listOne, check); }
|
||||
|
||||
listTwo.pushBack(&one);
|
||||
listTwo.pushBack(&two);
|
||||
{ unsigned int check[] { 1, 2 }; CheckListValues(listTwo, check); }
|
||||
|
||||
(void)listTwo.popBack();
|
||||
{ unsigned int check[] { 1, 2 }; CheckListValues(listOne, check); }
|
||||
{ unsigned int check[] { 1 }; CheckListValues(listTwo, check); }
|
||||
}
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
TestDoublyLinkedList();
|
||||
TestCustomAccessor();
|
||||
return 0;
|
||||
}
|
@ -21,6 +21,7 @@ CppUnitTests([
|
||||
'TestCheckedInt',
|
||||
'TestCountPopulation',
|
||||
'TestCountZeroes',
|
||||
'TestDoublyLinkedList',
|
||||
'TestEndian',
|
||||
'TestEnumeratedArray',
|
||||
'TestEnumSet',
|
||||
|
@ -10,7 +10,7 @@
|
||||
windowtype="navigator:browser"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<browser id="content" type="content" primary="true" src="about:blank" flex="1" remote="true"/>
|
||||
<browser id="content" type="content" primary="true" src="about:blank" flex="1"/>
|
||||
|
||||
<script type="application/javascript" src="chrome://geckoview/content/geckoview.js"/>
|
||||
</window>
|
||||
|
@ -22,10 +22,23 @@ public final class GeckoViewSettings {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Key to enabled and disable tracking protection.
|
||||
*/
|
||||
public static final Key<Boolean> USE_TRACKING_PROTECTION =
|
||||
new Key<Boolean>("useTrackingProtection");
|
||||
/*
|
||||
* Key to enabled and disable private mode browsing.
|
||||
*/
|
||||
public static final Key<Boolean> USE_PRIVATE_MODE =
|
||||
new Key<Boolean>("usePrivateMode");
|
||||
/*
|
||||
* Key to enabled and disable multiprocess browsing (e10s).
|
||||
* Note: can only be set during GeckoView initialization, changes during an
|
||||
* active GeckoView session will be ignored.
|
||||
*/
|
||||
public static final Key<Boolean> USE_MULTIPROCESS =
|
||||
new Key<Boolean>("useMultiprocess");
|
||||
|
||||
private final EventDispatcher mEventDispatcher;
|
||||
private final GeckoBundle mBundle;
|
||||
@ -40,6 +53,7 @@ public final class GeckoViewSettings {
|
||||
|
||||
setBoolean(USE_TRACKING_PROTECTION, false);
|
||||
setBoolean(USE_PRIVATE_MODE, false);
|
||||
setBoolean(USE_MULTIPROCESS, true);
|
||||
}
|
||||
|
||||
/* package */ GeckoViewSettings(GeckoViewSettings settings, EventDispatcher eventDispatcher) {
|
||||
|
@ -40,7 +40,15 @@ public final class HardwareCodecCapabilityUtils {
|
||||
|
||||
@WrapForJNI
|
||||
public static boolean findDecoderCodecInfoForMimeType(String aMimeType) {
|
||||
for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
|
||||
int numCodecs = 0;
|
||||
try {
|
||||
numCodecs = MediaCodecList.getCodecCount();
|
||||
} catch (final RuntimeException e) {
|
||||
Log.e(LOGTAG, "Failed to retrieve media codec count", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < numCodecs; ++i) {
|
||||
MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
|
||||
if (info.isEncoder()) {
|
||||
continue;
|
||||
|
@ -51,18 +51,18 @@ public class GeckoViewActivity extends Activity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent externalIntent) {
|
||||
Log.d(LOGTAG, "SNORP: onNewIntent: " + externalIntent.getData().toString());
|
||||
loadFromIntent(externalIntent);
|
||||
protected void onNewIntent(final Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
|
||||
if (intent.getData() != null) {
|
||||
loadFromIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadFromIntent(Intent intent) {
|
||||
Uri u = intent.getData();
|
||||
if (u != null) {
|
||||
mGeckoView.loadUri(u.toString());
|
||||
} else {
|
||||
mGeckoView.loadUri(DEFAULT_URL);
|
||||
}
|
||||
private void loadFromIntent(final Intent intent) {
|
||||
final Uri uri = intent.getData();
|
||||
mGeckoView.loadUri(uri != null ? uri.toString() : DEFAULT_URL);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -23,10 +23,15 @@ function debug(aMsg) {
|
||||
|
||||
// Handles GeckoView settings including:
|
||||
// * tracking protection
|
||||
// * multiprocess
|
||||
class GeckoViewSettings extends GeckoViewModule {
|
||||
init() {
|
||||
this._isSafeBrowsingInit = false;
|
||||
this._useTrackingProtection = false;
|
||||
|
||||
// We only allow to set this setting during initialization, further updates
|
||||
// will be ignored.
|
||||
this.useMultiprocess = !!this.settings.useMultiprocess;
|
||||
}
|
||||
|
||||
onSettingsUpdate() {
|
||||
@ -52,4 +57,23 @@ class GeckoViewSettings extends GeckoViewModule {
|
||||
this._useTrackingProtection = aUse;
|
||||
}
|
||||
}
|
||||
|
||||
get useMultiprocess() {
|
||||
return this.browser.getAttribute("remote") == "true";
|
||||
}
|
||||
|
||||
set useMultiprocess(aUse) {
|
||||
if (aUse == this.useMultiprocess) {
|
||||
return;
|
||||
}
|
||||
let parentNode = this.browser.parentNode;
|
||||
parentNode.removeChild(this.browser);
|
||||
|
||||
if (aUse) {
|
||||
this.browser.setAttribute("remote", "true");
|
||||
} else {
|
||||
this.browser.removeAttribute("remote");
|
||||
}
|
||||
parentNode.appendChild(this.browser);
|
||||
}
|
||||
}
|
||||
|
@ -1528,7 +1528,7 @@ pref("network.http.sendRefererHeader", 2);
|
||||
pref("network.http.referer.userControlPolicy", 3);
|
||||
// false=real referer, true=spoof referer (use target URI as referer)
|
||||
pref("network.http.referer.spoofSource", false);
|
||||
// false=allow onion referer, true=hide onion referer (use target URI as referer)
|
||||
// false=allow onion referer, true=hide onion referer (use empty referer)
|
||||
pref("network.http.referer.hideOnionSource", false);
|
||||
// 0=full URI, 1=scheme+host+port+path, 2=scheme+host+port
|
||||
pref("network.http.referer.trimmingPolicy", 0);
|
||||
|
@ -930,31 +930,24 @@ nsHttpConnectionMgr::DispatchPendingQ(nsTArray<RefPtr<nsHttpConnectionMgr::Pendi
|
||||
}
|
||||
|
||||
uint32_t
|
||||
nsHttpConnectionMgr::AvailableNewConnectionCount(nsConnectionEntry * ent)
|
||||
nsHttpConnectionMgr::TotalActiveConnections(nsConnectionEntry *ent) const
|
||||
{
|
||||
// Add in the in-progress tcp connections, we will assume they are
|
||||
// keepalive enabled.
|
||||
// Exclude half-open's that has already created a usable connection.
|
||||
// This prevents the limit being stuck on ipv6 connections that
|
||||
// eventually time out after typical 21 seconds of no ACK+SYN reply.
|
||||
uint32_t totalCount =
|
||||
ent->mActiveConns.Length() + ent->UnconnectedHalfOpens();
|
||||
|
||||
uint16_t maxPersistConns;
|
||||
return ent->mActiveConns.Length() + ent->UnconnectedHalfOpens();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
nsHttpConnectionMgr::MaxPersistConnections(nsConnectionEntry *ent) const
|
||||
{
|
||||
if (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingConnect()) {
|
||||
maxPersistConns = mMaxPersistConnsPerProxy;
|
||||
} else {
|
||||
maxPersistConns = mMaxPersistConnsPerHost;
|
||||
return static_cast<uint32_t>(mMaxPersistConnsPerProxy);
|
||||
}
|
||||
|
||||
LOG(("nsHttpConnectionMgr::AvailableNewConnectionCount "
|
||||
"total connection count = %d, limit %d\n",
|
||||
totalCount, maxPersistConns));
|
||||
|
||||
return maxPersistConns > totalCount
|
||||
? maxPersistConns - totalCount
|
||||
: 0;
|
||||
return static_cast<uint32_t>(mMaxPersistConnsPerHost);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -986,7 +979,12 @@ nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool consid
|
||||
return dispatchedSuccessfully;
|
||||
}
|
||||
|
||||
uint32_t availableConnections = AvailableNewConnectionCount(ent);
|
||||
uint32_t totalCount = TotalActiveConnections(ent);
|
||||
uint32_t maxPersistConns = MaxPersistConnections(ent);
|
||||
uint32_t availableConnections = maxPersistConns > totalCount
|
||||
? maxPersistConns - totalCount
|
||||
: 0;
|
||||
|
||||
// No need to try dispatching if we reach the active connection limit.
|
||||
if (!availableConnections) {
|
||||
return dispatchedSuccessfully;
|
||||
@ -1099,14 +1097,15 @@ bool
|
||||
nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry *ent, uint32_t caps)
|
||||
{
|
||||
nsHttpConnectionInfo *ci = ent->mConnInfo;
|
||||
uint32_t totalCount = TotalActiveConnections(ent);
|
||||
uint32_t maxPersistConns = MaxPersistConnections(ent);
|
||||
|
||||
LOG(("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x]\n",
|
||||
ci->HashKey().get(), caps));
|
||||
|
||||
uint32_t availableConnections = AvailableNewConnectionCount(ent);
|
||||
LOG(("nsHttpConnectionMgr::AtActiveConnectionLimit [ci=%s caps=%x,"
|
||||
"totalCount=%u, maxPersistConns=%u]\n",
|
||||
ci->HashKey().get(), caps, totalCount, maxPersistConns));
|
||||
|
||||
if (caps & NS_HTTP_URGENT_START) {
|
||||
if (availableConnections > static_cast<uint32_t>(mMaxUrgentExcessiveConns)) {
|
||||
if (totalCount >= (mMaxUrgentExcessiveConns + maxPersistConns)) {
|
||||
LOG(("The number of total connections are greater than or equal to sum of "
|
||||
"max urgent-start queue length and the number of max persistent connections.\n"));
|
||||
return true;
|
||||
@ -1131,7 +1130,7 @@ nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry *ent, uint32_t ca
|
||||
return true;
|
||||
}
|
||||
|
||||
bool result = !availableConnections;
|
||||
bool result = (totalCount >= maxPersistConns);
|
||||
LOG(("AtActiveConnectionLimit result: %s", result ? "true" : "false"));
|
||||
return result;
|
||||
}
|
||||
|
@ -476,10 +476,14 @@ private:
|
||||
nsConnectionEntry *ent,
|
||||
bool considerAll);
|
||||
|
||||
// Given the connection entry, return the available count for creating
|
||||
// new connections. Return 0 if the active connection count is
|
||||
// exceeded |mMaxPersistConnsPerProxy| or |mMaxPersistConnsPerHost|.
|
||||
uint32_t AvailableNewConnectionCount(nsConnectionEntry * ent);
|
||||
// Return total active connection count, which is the sum of
|
||||
// active connections and unconnected half open connections.
|
||||
uint32_t TotalActiveConnections(nsConnectionEntry *ent) const;
|
||||
|
||||
// Return |mMaxPersistConnsPerProxy| or |mMaxPersistConnsPerHost|,
|
||||
// depending whether the proxy is used.
|
||||
uint32_t MaxPersistConnections(nsConnectionEntry *ent) const;
|
||||
|
||||
bool AtActiveConnectionLimit(nsConnectionEntry *, uint32_t caps);
|
||||
MOZ_MUST_USE nsresult TryDispatchTransaction(nsConnectionEntry *ent,
|
||||
bool onlyReusedConnection,
|
||||
|
@ -758,10 +758,6 @@
|
||||
// See bug 341047 and comments in overflow handler as to why
|
||||
// try..catch is needed here
|
||||
this._updateScrollButtonsDisabledState();
|
||||
|
||||
let childNodes = this._getScrollableElements();
|
||||
if (childNodes && childNodes.length)
|
||||
this.ensureElementIsVisible(childNodes[0], false);
|
||||
} catch (e) {
|
||||
this.removeAttribute("notoverflowing");
|
||||
}
|
||||
|
@ -174,7 +174,6 @@ this.AUSTLMY = {
|
||||
DWNLD_RETRY_NET_RESET: 4,
|
||||
DWNLD_ERR_NO_UPDATE: 5,
|
||||
DWNLD_ERR_NO_UPDATE_PATCH: 6,
|
||||
DWNLD_ERR_NO_PATCH_FILE: 7,
|
||||
DWNLD_ERR_PATCH_SIZE_LARGER: 8,
|
||||
DWNLD_ERR_PATCH_SIZE_NOT_EQUAL: 9,
|
||||
DWNLD_ERR_BINDING_ABORTED: 10,
|
||||
|
@ -61,7 +61,7 @@
|
||||
#define UNEXPECTED_MAR_ERROR 40
|
||||
#define UNEXPECTED_BSPATCH_ERROR 41
|
||||
#define UNEXPECTED_FILE_OPERATION_ERROR 42
|
||||
#define FILESYSTEM_MOUNT_READWRITE_ERROR 43
|
||||
// #define FILESYSTEM_MOUNT_READWRITE_ERROR 43 // Removed support for gonk
|
||||
#define DELETE_ERROR_EXPECTED_DIR 46
|
||||
#define DELETE_ERROR_EXPECTED_FILE 47
|
||||
#define RENAME_ERROR_EXPECTED_FILE 48
|
||||
|
@ -183,12 +183,6 @@ interface nsIUpdate : nsISupports
|
||||
*/
|
||||
attribute boolean isSecurityUpdate;
|
||||
|
||||
/**
|
||||
* Whether or not the update being downloaded is an OS update. This is
|
||||
* generally only possible in Gonk right now.
|
||||
*/
|
||||
attribute boolean isOSUpdate;
|
||||
|
||||
/**
|
||||
* When the update was installed.
|
||||
*/
|
||||
@ -386,14 +380,6 @@ interface nsIApplicationUpdateService : nsISupports
|
||||
*/
|
||||
AString downloadUpdate(in nsIUpdate update, in boolean background);
|
||||
|
||||
/**
|
||||
* Apply the OS update which has been downloaded and staged as applied.
|
||||
* @param update
|
||||
* The update has been downloaded and staged as applied.
|
||||
* @throws if the update object is not an OS update.
|
||||
*/
|
||||
void applyOsUpdate(in nsIUpdate update);
|
||||
|
||||
/**
|
||||
* Get the Active Updates directory
|
||||
* @returns An nsIFile for the active updates directory.
|
||||
|
@ -60,8 +60,6 @@ const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.proper
|
||||
|
||||
const KEY_UPDROOT = "UpdRootD";
|
||||
const KEY_EXECUTABLE = "XREExeF";
|
||||
// Gonk only
|
||||
const KEY_UPDATE_ARCHIVE_DIR = "UpdArchD";
|
||||
|
||||
const DIR_UPDATES = "updates";
|
||||
|
||||
@ -83,7 +81,6 @@ const STATE_PENDING_SERVICE = "pending-service";
|
||||
const STATE_PENDING_ELEVATE = "pending-elevate";
|
||||
const STATE_APPLYING = "applying";
|
||||
const STATE_APPLIED = "applied";
|
||||
const STATE_APPLIED_OS = "applied-os";
|
||||
const STATE_APPLIED_SERVICE = "applied-service";
|
||||
const STATE_SUCCEEDED = "succeeded";
|
||||
const STATE_DOWNLOAD_FAILED = "download-failed";
|
||||
@ -203,17 +200,6 @@ const APPID_TO_TOPIC = {
|
||||
|
||||
var gUpdateMutexHandle = null;
|
||||
|
||||
// Gonk only
|
||||
var gSDCardMountLock = null;
|
||||
|
||||
// Gonk only
|
||||
XPCOMUtils.defineLazyGetter(this, "gExtStorage", function aus_gExtStorage() {
|
||||
if (AppConstants.platform != "gonk") {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
return Services.env.get("EXTERNAL_STORAGE");
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
|
||||
"resource://gre/modules/UpdateUtils.jsm");
|
||||
|
||||
@ -568,13 +554,6 @@ function getCanStageUpdates() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// For Gonk, the updater will remount the /system partition to move staged
|
||||
// files into place.
|
||||
if (AppConstants.platform == "gonk") {
|
||||
LOG("getCanStageUpdates - able to stage updates because this is gonk");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!hasUpdateMutex()) {
|
||||
LOG("getCanStageUpdates - unable to apply updates because another " +
|
||||
"instance of the application is already handling updates for this " +
|
||||
@ -799,105 +778,6 @@ function writeVersionFile(dir, version) {
|
||||
writeStringToFile(versionFile, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gonk only function that reads the link file specified in the update.link file
|
||||
* in the specified directory and returns the nsIFile for the corresponding
|
||||
* file.
|
||||
* @param dir
|
||||
* The dir to look for an update.link file in
|
||||
* @return A nsIFile for the file path specified in the
|
||||
* update.link file or null if the update.link file
|
||||
* doesn't exist.
|
||||
*/
|
||||
function getFileFromUpdateLink(dir) {
|
||||
if (AppConstants.platform != "gonk") {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
let linkFile = dir.clone();
|
||||
linkFile.append(FILE_UPDATE_LINK);
|
||||
let link = readStringFromFile(linkFile);
|
||||
LOG("getFileFromUpdateLink linkFile.path: " + linkFile.path + ", link: " + link);
|
||||
if (!link) {
|
||||
return null;
|
||||
}
|
||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
file.initWithPath(link);
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gonk only function to create a link file. This allows the actual patch to
|
||||
* live in a directory different from the update directory.
|
||||
* @param dir
|
||||
* The patch directory where the update.link file
|
||||
* should be written.
|
||||
* @param patchFile
|
||||
* The fully qualified filename of the patchfile.
|
||||
*/
|
||||
function writeLinkFile(dir, patchFile) {
|
||||
if (AppConstants.platform != "gonk") {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
let linkFile = dir.clone();
|
||||
linkFile.append(FILE_UPDATE_LINK);
|
||||
writeStringToFile(linkFile, patchFile.path);
|
||||
if (patchFile.path.indexOf(gExtStorage) == 0) {
|
||||
// The patchfile is being stored on external storage. Try to lock it
|
||||
// so that it doesn't get shared with the PC while we're downloading
|
||||
// to it.
|
||||
acquireSDCardMountLock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gonk only function to acquire a VolumeMountLock for the sdcard volume.
|
||||
*
|
||||
* This prevents the SDCard from being shared with the PC while
|
||||
* we're downloading the update.
|
||||
*/
|
||||
function acquireSDCardMountLock() {
|
||||
if (AppConstants.platform != "gonk") {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
let volsvc = Cc["@mozilla.org/telephony/volume-service;1"].
|
||||
getService(Ci.nsIVolumeService);
|
||||
if (volsvc) {
|
||||
gSDCardMountLock = volsvc.createMountLock("sdcard");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gonk only function that determines if the state corresponds to an
|
||||
* interrupted update. This could either be because the download was
|
||||
* interrupted, or because staging the update was interrupted.
|
||||
*
|
||||
* @return true if the state corresponds to an interrupted
|
||||
* update.
|
||||
*/
|
||||
function isInterruptedUpdate(status) {
|
||||
if (AppConstants.platform != "gonk") {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
return (status == STATE_DOWNLOADING) ||
|
||||
(status == STATE_PENDING) ||
|
||||
(status == STATE_APPLYING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases any SDCard mount lock that we might have.
|
||||
*
|
||||
* This once again allows the SDCard to be shared with the PC.
|
||||
*/
|
||||
function releaseSDCardMountLock() {
|
||||
if (AppConstants.platform != "gonk") {
|
||||
throw Cr.NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
if (gSDCardMountLock) {
|
||||
gSDCardMountLock.unlock();
|
||||
gSDCardMountLock = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the service should be used to attempt an update
|
||||
* or not.
|
||||
@ -1060,15 +940,6 @@ function cleanUpUpdatesDir(aRemovePatchFiles = true) {
|
||||
let dirEntries = updateDir.directoryEntries;
|
||||
while (dirEntries.hasMoreElements()) {
|
||||
let file = dirEntries.getNext().QueryInterface(Ci.nsIFile);
|
||||
if (AppConstants.platform == "gonk") {
|
||||
if (file.leafName == FILE_UPDATE_LINK) {
|
||||
let linkedFile = getFileFromUpdateLink(updateDir);
|
||||
if (linkedFile && linkedFile.exists()) {
|
||||
linkedFile.remove(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now, recursively remove this file. The recursive removal is needed for
|
||||
// Mac OSX because this directory will contain a copy of updater.app,
|
||||
// which is itself a directory and the MozUpdater directory on platforms
|
||||
@ -1080,9 +951,6 @@ function cleanUpUpdatesDir(aRemovePatchFiles = true) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (AppConstants.platform == "gonk") {
|
||||
releaseSDCardMountLock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1350,9 +1218,6 @@ function pingStateAndStatusCodes(aUpdate, aStartup, aStatus) {
|
||||
case STATE_APPLIED:
|
||||
stateCode = 7;
|
||||
break;
|
||||
case STATE_APPLIED_OS:
|
||||
stateCode = 8;
|
||||
break;
|
||||
case STATE_APPLIED_SERVICE:
|
||||
stateCode = 9;
|
||||
break;
|
||||
@ -1830,12 +1695,6 @@ function UpdateService() {
|
||||
LOG("Creating UpdateService");
|
||||
Services.obs.addObserver(this, "xpcom-shutdown");
|
||||
Services.prefs.addObserver(PREF_APP_UPDATE_LOG, this);
|
||||
if (AppConstants.platform == "gonk") {
|
||||
// PowerManagerService::SyncProfile (which is called for Reboot, PowerOff
|
||||
// and Restart) sends the profile-change-net-teardown event. We can then
|
||||
// pause the download in a similar manner to xpcom-shutdown.
|
||||
Services.obs.addObserver(this, "profile-change-net-teardown");
|
||||
}
|
||||
}
|
||||
|
||||
UpdateService.prototype = {
|
||||
@ -1917,7 +1776,6 @@ UpdateService.prototype = {
|
||||
gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
|
||||
}
|
||||
break;
|
||||
case "profile-change-net-teardown": // fall thru
|
||||
case "xpcom-shutdown":
|
||||
Services.obs.removeObserver(this, topic);
|
||||
Services.prefs.removeObserver(PREF_APP_UPDATE_LOG, this);
|
||||
@ -1983,23 +1841,6 @@ UpdateService.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (AppConstants.platform == "gonk") {
|
||||
// This code is called very early in the boot process, before we've even
|
||||
// had a chance to setup the UI so we can give feedback to the user.
|
||||
//
|
||||
// Since the download may be occuring over a link which has associated
|
||||
// cost, we want to require user-consent before resuming the download.
|
||||
// Also, applying an already downloaded update now is undesireable,
|
||||
// since the phone will look dead while the update is being applied.
|
||||
// Applying the update can take several minutes. Instead we wait until
|
||||
// the UI is initialized so it is possible to give feedback to and get
|
||||
// consent to update from the user.
|
||||
if (isInterruptedUpdate(status)) {
|
||||
LOG("UpdateService:_postUpdateProcessing - interrupted update detected - wait for user consent");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (status == STATE_DOWNLOADING) {
|
||||
LOG("UpdateService:_postUpdateProcessing - patch found in downloading " +
|
||||
"state");
|
||||
@ -2041,34 +1882,6 @@ UpdateService.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (AppConstants.platform == "gonk") {
|
||||
// The update is only applied but not selected to be installed
|
||||
if (status == STATE_APPLIED && update && update.isOSUpdate) {
|
||||
LOG("UpdateService:_postUpdateProcessing - update staged as applied found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == STATE_APPLIED_OS && update && update.isOSUpdate) {
|
||||
// In gonk, we need to check for OS update status after startup, since
|
||||
// the recovery partition won't write to update.status for us
|
||||
let recoveryService = Cc["@mozilla.org/recovery-service;1"].
|
||||
getService(Ci.nsIRecoveryService);
|
||||
let fotaStatus = recoveryService.getFotaUpdateStatus();
|
||||
switch (fotaStatus) {
|
||||
case Ci.nsIRecoveryService.FOTA_UPDATE_SUCCESS:
|
||||
status = STATE_SUCCEEDED;
|
||||
break;
|
||||
case Ci.nsIRecoveryService.FOTA_UPDATE_FAIL:
|
||||
status = STATE_FAILED + ": " + FOTA_GENERAL_ERROR;
|
||||
break;
|
||||
case Ci.nsIRecoveryService.FOTA_UPDATE_UNKNOWN:
|
||||
default:
|
||||
status = STATE_FAILED + ": " + FOTA_UNKNOWN_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!update) {
|
||||
if (status != STATE_SUCCEEDED) {
|
||||
LOG("UpdateService:_postUpdateProcessing - previous patch failed " +
|
||||
@ -2527,11 +2340,6 @@ UpdateService.prototype = {
|
||||
var um = Cc["@mozilla.org/updates/update-manager;1"].
|
||||
getService(Ci.nsIUpdateManager);
|
||||
if (um.activeUpdate) {
|
||||
if (AppConstants.platform == "gonk") {
|
||||
// For gonk, the user isn't necessarily aware of the update, so we need
|
||||
// to show the prompt to make sure.
|
||||
this._showPrompt(um.activeUpdate);
|
||||
}
|
||||
AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_HAS_ACTIVEUPDATE);
|
||||
return;
|
||||
}
|
||||
@ -2734,21 +2542,6 @@ UpdateService.prototype = {
|
||||
}
|
||||
this._downloader.cancel();
|
||||
}
|
||||
if (AppConstants.platform == "gonk") {
|
||||
let um = Cc["@mozilla.org/updates/update-manager;1"].
|
||||
getService(Ci.nsIUpdateManager);
|
||||
let activeUpdate = um.activeUpdate;
|
||||
if (activeUpdate &&
|
||||
(activeUpdate.appVersion != update.appVersion ||
|
||||
activeUpdate.buildID != update.buildID)) {
|
||||
// We have an activeUpdate (which presumably was interrupted), and are
|
||||
// about start downloading a new one. Make sure we remove all traces
|
||||
// of the active one (otherwise we'll start appending the new update.mar
|
||||
// the the one that's been partially downloaded).
|
||||
LOG("UpdateService:downloadUpdate - removing stale active update.");
|
||||
cleanupActiveUpdate();
|
||||
}
|
||||
}
|
||||
// Set the previous application version prior to downloading the update.
|
||||
update.previousAppVersion = Services.appinfo.version;
|
||||
this._downloader = new Downloader(background, this);
|
||||
@ -2782,55 +2575,6 @@ UpdateService.prototype = {
|
||||
return this._downloader && this._downloader.isBusy;
|
||||
},
|
||||
|
||||
/**
|
||||
* See nsIUpdateService.idl
|
||||
*/
|
||||
applyOsUpdate: function AUS_applyOsUpdate(aUpdate) {
|
||||
if (!aUpdate.isOSUpdate || aUpdate.state != STATE_APPLIED) {
|
||||
aUpdate.statusText = "fota-state-error";
|
||||
throw Cr.NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
aUpdate.QueryInterface(Ci.nsIWritablePropertyBag);
|
||||
let osApplyToDir = aUpdate.getProperty("osApplyToDir");
|
||||
|
||||
if (!osApplyToDir) {
|
||||
LOG("UpdateService:applyOsUpdate - Error: osApplyToDir is not defined" +
|
||||
"in the nsIUpdate!");
|
||||
pingStateAndStatusCodes(aUpdate, false,
|
||||
STATE_FAILED + ": " + FOTA_FILE_OPERATION_ERROR);
|
||||
handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
let updateFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
updateFile.initWithPath(osApplyToDir + "/update.zip");
|
||||
if (!updateFile.exists()) {
|
||||
LOG("UpdateService:applyOsUpdate - Error: OS update is not found at " +
|
||||
updateFile.path);
|
||||
pingStateAndStatusCodes(aUpdate, false,
|
||||
STATE_FAILED + ": " + FOTA_FILE_OPERATION_ERROR);
|
||||
handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED_OS);
|
||||
LOG("UpdateService:applyOsUpdate - Rebooting into recovery to apply " +
|
||||
"FOTA update: " + updateFile.path);
|
||||
try {
|
||||
let recoveryService = Cc["@mozilla.org/recovery-service;1"]
|
||||
.getService(Ci.nsIRecoveryService);
|
||||
recoveryService.installFotaUpdate(updateFile.path);
|
||||
} catch (e) {
|
||||
LOG("UpdateService:applyOsUpdate - Error: Couldn't reboot into recovery" +
|
||||
" to apply FOTA update " + updateFile.path);
|
||||
pingStateAndStatusCodes(aUpdate, false,
|
||||
STATE_FAILED + ": " + FOTA_RECOVERY_ERROR);
|
||||
writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED);
|
||||
handleUpdateFailure(aUpdate, FOTA_RECOVERY_ERROR);
|
||||
}
|
||||
},
|
||||
|
||||
classID: UPDATESERVICE_CID,
|
||||
classInfo: XPCOMUtils.generateCI({classID: UPDATESERVICE_CID,
|
||||
contractID: UPDATESERVICE_CONTRACTID,
|
||||
@ -3156,21 +2900,6 @@ UpdateManager.prototype = {
|
||||
"the update was staged. topic: update-staged, status: " + update.state);
|
||||
Services.obs.notifyObservers(update, "update-staged", update.state);
|
||||
|
||||
if (AppConstants.platform == "gonk") {
|
||||
// Do this after everything else, since it will likely cause the app to
|
||||
// shut down.
|
||||
if (update.state == STATE_APPLIED) {
|
||||
// Notify the user that an update has been staged and is ready for
|
||||
// installation (i.e. that they should restart the application). We do
|
||||
// not notify on failed update attempts.
|
||||
let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
|
||||
createInstance(Ci.nsIUpdatePrompt);
|
||||
prompter.showUpdateDownloaded(update, true);
|
||||
} else {
|
||||
releaseSDCardMountLock();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Only prompt when the UI isn't already open.
|
||||
let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
|
||||
if (Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME) ||
|
||||
@ -3522,9 +3251,6 @@ Downloader.prototype = {
|
||||
if (this._request && this._request instanceof Ci.nsIRequest) {
|
||||
this._request.cancel(cancelError);
|
||||
}
|
||||
if (AppConstants.platform == "gonk") {
|
||||
releaseSDCardMountLock();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -3653,19 +3379,10 @@ Downloader.prototype = {
|
||||
LOG("Downloader:_selectPatch - resuming download");
|
||||
return selectedPatch;
|
||||
}
|
||||
|
||||
if (AppConstants.platform == "gonk") {
|
||||
if (state == STATE_PENDING || state == STATE_APPLYING) {
|
||||
LOG("Downloader:_selectPatch - resuming interrupted apply");
|
||||
return selectedPatch;
|
||||
}
|
||||
if (state == STATE_APPLIED) {
|
||||
LOG("Downloader:_selectPatch - already downloaded and staged");
|
||||
return null;
|
||||
}
|
||||
} else if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
|
||||
state == STATE_PENDING_ELEVATE) {
|
||||
LOG("Downloader:_selectPatch - already downloaded and staged");
|
||||
if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
|
||||
state == STATE_PENDING_ELEVATE || state == STATE_APPLIED ||
|
||||
state == STATE_APPLIED_SERVICE) {
|
||||
LOG("Downloader:_selectPatch - already downloaded");
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -3721,25 +3438,6 @@ Downloader.prototype = {
|
||||
return this._request != null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the nsIFile to use for downloading the active update's selected patch
|
||||
*/
|
||||
_getUpdateArchiveFile: function Downloader__getUpdateArchiveFile() {
|
||||
var updateArchive;
|
||||
if (AppConstants.platform == "gonk") {
|
||||
try {
|
||||
updateArchive = FileUtils.getDir(KEY_UPDATE_ARCHIVE_DIR, [], true);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
updateArchive = getUpdatesDir().clone();
|
||||
}
|
||||
|
||||
updateArchive.append(FILE_UPDATE_MAR);
|
||||
return updateArchive;
|
||||
},
|
||||
|
||||
/**
|
||||
* Download and stage the given update.
|
||||
* @param update
|
||||
@ -3766,96 +3464,8 @@ Downloader.prototype = {
|
||||
}
|
||||
this.isCompleteUpdate = this._patch.type == "complete";
|
||||
|
||||
let patchFile = null;
|
||||
|
||||
// Only used by gonk
|
||||
let status = STATE_NONE;
|
||||
if (AppConstants.platform == "gonk") {
|
||||
status = readStatusFile(updateDir);
|
||||
if (isInterruptedUpdate(status)) {
|
||||
LOG("Downloader:downloadUpdate - interruptted update");
|
||||
// The update was interrupted. Try to locate the existing patch file.
|
||||
// For an interrupted download, this allows a resume rather than a
|
||||
// re-download.
|
||||
patchFile = getFileFromUpdateLink(updateDir);
|
||||
if (!patchFile) {
|
||||
// No link file. We'll just assume that the update.mar is in the
|
||||
// update directory.
|
||||
patchFile = updateDir.clone();
|
||||
patchFile.append(FILE_UPDATE_MAR);
|
||||
}
|
||||
if (patchFile.exists()) {
|
||||
LOG("Downloader:downloadUpdate - resuming with patchFile " + patchFile.path);
|
||||
if (patchFile.fileSize == this._patch.size) {
|
||||
LOG("Downloader:downloadUpdate - patchFile appears to be fully downloaded");
|
||||
// Bump the status along so that we don't try to redownload again.
|
||||
if (getElevationRequired()) {
|
||||
status = STATE_PENDING_ELEVATE;
|
||||
} else {
|
||||
status = STATE_PENDING;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG("Downloader:downloadUpdate - patchFile " + patchFile.path +
|
||||
" doesn't exist - performing full download");
|
||||
// The patchfile doesn't exist, we might as well treat this like
|
||||
// a new download.
|
||||
patchFile = null;
|
||||
}
|
||||
if (patchFile && status != STATE_DOWNLOADING) {
|
||||
// It looks like the patch was downloaded, but got interrupted while it
|
||||
// was being verified or applied. So we'll fake the downloading portion.
|
||||
|
||||
if (getElevationRequired()) {
|
||||
writeStatusFile(updateDir, STATE_PENDING_ELEVATE);
|
||||
} else {
|
||||
writeStatusFile(updateDir, STATE_PENDING);
|
||||
}
|
||||
|
||||
// Since the code expects the onStopRequest callback to happen
|
||||
// asynchronously (And you have to call AUS_addDownloadListener
|
||||
// after calling AUS_downloadUpdate) we need to defer this.
|
||||
|
||||
this._downloadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this._downloadTimer.initWithCallback(function() {
|
||||
this._downloadTimer = null;
|
||||
// Send a fake onStopRequest. Filling in the destination allows
|
||||
// _verifyDownload to work, and then the update will be applied.
|
||||
this._request = {destination: patchFile};
|
||||
this.onStopRequest(this._request, null, Cr.NS_OK);
|
||||
}.bind(this), 0, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
|
||||
// Returning STATE_DOWNLOADING makes UpdatePrompt think we're
|
||||
// downloading. The onStopRequest that we spoofed above will make it
|
||||
// look like the download finished.
|
||||
return STATE_DOWNLOADING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!patchFile) {
|
||||
// Find a place to put the patchfile that we're going to download.
|
||||
patchFile = this._getUpdateArchiveFile();
|
||||
}
|
||||
if (!patchFile) {
|
||||
AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
|
||||
AUSTLMY.DWNLD_ERR_NO_PATCH_FILE);
|
||||
return STATE_NONE;
|
||||
}
|
||||
|
||||
if (AppConstants.platform == "gonk") {
|
||||
if (patchFile.path.indexOf(updateDir.path) != 0) {
|
||||
// The patchFile is in a directory which is different from the
|
||||
// updateDir, create a link file.
|
||||
writeLinkFile(updateDir, patchFile);
|
||||
|
||||
if (!isInterruptedUpdate(status) && patchFile.exists()) {
|
||||
// Remove stale patchFile
|
||||
patchFile.remove(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let patchFile = getUpdatesDir().clone();
|
||||
patchFile.append(FILE_UPDATE_MAR);
|
||||
update.QueryInterface(Ci.nsIPropertyBag);
|
||||
let interval = this.background ? update.getProperty("backgroundInterval")
|
||||
: DOWNLOAD_FOREGROUND_INTERVAL;
|
||||
@ -4127,13 +3737,6 @@ Downloader.prototype = {
|
||||
this._update.statusText = getStatusTextFromCode(status,
|
||||
Cr.NS_BINDING_FAILED);
|
||||
|
||||
if (AppConstants.platform == "gonk") {
|
||||
// bug891009: On FirefoxOS, manaully retry OTA download will reuse
|
||||
// the Update object. We need to remove selected patch so that download
|
||||
// can be triggered again successfully.
|
||||
this._update.selectedPatch.selected = false;
|
||||
}
|
||||
|
||||
// Destroy the updates directory, since we're done with it.
|
||||
cleanUpUpdatesDir();
|
||||
|
||||
@ -4213,13 +3816,6 @@ Downloader.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
if (AppConstants.platform == "gonk") {
|
||||
// We always forward errors in B2G, since Gaia controls the update UI
|
||||
let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
|
||||
createInstance(Ci.nsIUpdatePrompt);
|
||||
prompter.showUpdateError(this._update);
|
||||
}
|
||||
|
||||
// Prevent leaking the update object (bug 454964).
|
||||
this._update = null;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user